IT/언어

[python/pandas] 특정 위치의 값을 획득하거나 바꿀 때 사용하는 loc, iloc, iat, at

개발자 두더지 2021. 8. 16. 00:37
728x90

 pandas.DataFrame 임의의 위치 데이터를 획득하거나 변경(대입)하는 경우에 pandas.DataFrame의 속성인 loc, iloc, at, iat을 사용할 수 있다. 

 먼저 네 가지는 다음과 같은 차이점이 있다.

  loc iloc at iat
절대 좌표(위치) 지정 x o x o
라벨명 지정 o x o x
여러개의 요소 지정 o o x x
슬라이스 표기 o o x x

1) 좌표(위치)의 지정 방법

- at, loc : 행 명(행 라벨), 열 명(열 라벨)

- iat, iloc : 행 번호, 열 번호(절대 좌표(위치)지정)

2) 선택하여 확인, 변경할 수 있는 데이터

- at, iat : 하나의 요소 값

- loc, iloc : 하나 혹은 여러 개의 요소 값

  •  리스트, 슬라이스로 범위를 지정할 수 있다.
  • 행, 열을 선택하여 값을 획득하고 변경할 수 있다.

3) 그 외의 차이점

- 처리 속도는 at 과 iat의 경우가 loc과 iloc보다 빠르다

- 라벨과 번호를 합쳐서 위치를 지정하고 싶은 경우는 at 혹은 loc과 index나 column을 조합한다.

 

 이번 포스팅에서는 다음의 내용에 대해 다룰 것이다.

- at, iat : 하나의 요소 값을 선택, 획득, 변경

- loc, iloc : 하나 혹은 여러 개의 요소 값을 선택, 획득, 변경

  • 하나의 요소 값을 선택
  • 여러 개의 요소 값을 선택
  • 행, 열을 선택

- 행 명, 열 명이 중복된 값을 가진 경우

- 번호와 라벨 단위를 지정

- 행을 pandas.Series로 선택할 때 묵시적형 변환

 참고로 pandas.DataFrame의 행, 열, pandas.Series의 요소 값을 선택, 획득하는 경우는 인덱스 참고 df[]도 사용할 수 있다. 또한 DataFrame.get_value(), DataFrame.ix[]도 있지만, 둘 다 최신 버전에서는 추천하지 않는다.

 이번 샘플 코드에서는 아래의 csv 데이터를 read_csv로 읽어들여 사용한다. 인수 index_col로 맨 처음의 행을 index로 사용하고 있다.

import pandas as pd

df = pd.read_csv('data/src/sample_pandas_normal.csv', index_col=0)
print(df)
#          age state  point
# name                     
# Alice     24    NY     64
# Bob       42    CA     92
# Charlie   18    CA     70
# Dave      68    TX     70
# Ellen     24    CA     88
# Frank     30    NY     57

print(df.index.values)
# ['Alice' 'Bob' 'Charlie' 'Dave' 'Ellen' 'Frank']

print(df.columns.values)
# ['age' 'state' 'point']

 

 

at, iat : 하나의 요소 값을 선택, 획득, 변경


 at은 행 명과 열 명으로 위치를 지정한다. 데이터를 획득하기 위할 뿐만 아니라, 그 위치에 새로운 값을 설정(대입)하는 것도 가능하다.

print(df.at['Bob', 'age'])
print(df.at['Dave', 'state'])
# 42
# TX

df.at['Bob', 'age'] = 60
print(df.at['Bob', 'age'])
# 60

 iat은 행 번호와 열 번호로 위치를 지정한다. 행 번호, 열 번호는 0부터 시작한다.

 iat도 at과 동일하게, 데이터를 획득할 뿐 아니라 그 위치에 새로운 값을 설정(대입)할 수 있다.

print(df.iat[1, 0])
print(df.iat[3, 1])
# 60
# TX

df.iat[1, 0] = 42
print(df.iat[1, 0])
# 42

 

 

loc, iloc : 하나 혹은 여러 개의 요소 값을 선택, 획득, 변경


 loc과 iloc은 하나의 값뿐 아니라, 범위를 지정하여 여러 개의 데이터를 선택할 수 있다. loc은 행 명과 열 명으로 위치를 지정하고, iloc은 행 번호와 열 번호로 위치를 지정한다.

하나의 요소 값을 선택

 하나의 값에 액세스할 경우 at, iat와 동일하다. 처리 속도는 앞에서 말했듯 at, iat의 쪽이 빠르다.

print(df.loc['Bob', 'age'])
print(df.iloc[3, 1])
# 42
# TX

 데이터를 참고할 뿐만 아니라, 그 위치에 새로운 값을 설정(대입)하는 것도 가능하다.

df.loc['Bob', 'age'] = 60
print(df.loc['Bob', 'age'])
# 60

df.iloc[1, 0] = 42
print(df.iloc[1, 0])
# 42

여러 개의 요소 값을 선택

 여러 개의 값에 액세스할 경우는 리스트[a, b, c, ...]나 슬라이스 start:stop:step으로 데이터의 범위, 위치를 지정할 수 있다. pandas.Series 혹은 pandas.DataFrame가 반환된다.

 슬라이스는 보통의 슬라이스 작성법과 같다. step은 생략가능하다.

 슬라이스 start:stop:step으로 지정할 때, iloc으로 행 번호, 열 번호를 사용하는 경우 일반적인 슬라이스와 동일하게 stop의 한 단계 전까지이지만, loc으로 행 명, 열 명을 사용하는 경우는 stop의 값도 포함되므로 주의가 필요하다.

print(df.loc['Bob':'Dave', 'age'])
print(type(df.loc['Bob':'Dave', 'age']))
# name
# Bob        42
# Charlie    18
# Dave       68
# Name: age, dtype: int64
# <class 'pandas.core.series.Series'>

print(df.loc[:'Dave', ['age', 'point']])
print(type(df.loc[:'Dave', 'age':'point']))
#          age  point
# name               
# Alice     24     64
# Bob       42     92
# Charlie   18     70
# Dave      68     70
# <class 'pandas.core.frame.DataFrame'>

print(df.iloc[:3, [0, 2]])
print(type(df.iloc[:3, [0, 2]]))
#          age  point
# name               
# Alice     24     64
# Bob       42     92
# Charlie   18     70
# <class 'pandas.core.frame.DataFrame'>

 step을 지정하면, 홀수행 혹은 짝수행을 추출하여 획득하는 것도 가능하다.

print(df.iloc[::2, 0])
print(type(df.iloc[::2, 0]))
# name
# Alice      24
# Charlie    18
# Ellen      24
# Name: age, dtype: int64
# <class 'pandas.core.series.Series'>

print(df.iloc[1::2, 0])
print(type(df.iloc[1::2, 0]))
# name
# Bob      42
# Dave     68
# Frank    30
# Name: age, dtype: int64
# <class 'pandas.core.series.Series'>

 여러 개의 값을 일괄로 변경하는 것도 가능하다.

df.loc['Bob':'Dave', 'age'] = [20, 30, 40]
print(df.loc['Bob':'Dave', 'age'])
# name
# Bob        20
# Charlie    30
# Dave       40
# Name: age, dtype: int64

행, 열을 선택

  인덱스 참조 df[]로 행, 열을 선택할 수 있지만, 아래의 지정 방법에 한정된다.

  • 행의 선택 : 행 이름, 행 번호의 인덱스
  • 열의 선택 : 열 이름, 혹은 열 이름의 리스트
print(df['Bob':'Ellen'])
#          age state  point
# name                     
# Bob       20    CA     92
# Charlie   30    CA     70
# Dave      40    TX     70
# Ellen     24    CA     88

print(df[:3])
#          age state  point
# name                     
# Alice     24    NY     64
# Bob       20    CA     92
# Charlie   30    CA     70

print(df['age'])
# name
# Alice      24
# Bob        20
# Charlie    30
# Dave       40
# Ellen      24
# Frank      30
# Name: age, dtype: int64

print(df[['age', 'point']])
#          age  point
# name               
# Alice     24     64
# Bob       20     92
# Charlie   30     70
# Dave      40     70
# Ellen     24     88
# Frank     30     57​

 loc, iloc으로 행, 열을 선택할 경우는 인덱스 참고 df[]보다 유연하게 지정할 수 있다.

 loc, iloc에서 열의 지정을 생략하면 열 참조가 된다. 인덱스 참조에서는 할 수 없는 행 이름, 열 번호 단독 지정이나 리스트에 의한 지정도 가능하다.

print(df.loc['Bob'])
print(type(df.loc['Bob']))
# age      20
# state    CA
# point    92
# Name: Bob, dtype: object
# <class 'pandas.core.series.Series'>

print(df.iloc[[1, 4]])
print(type(df.iloc[[1, 4]]))
#        age state  point
# name                   
# Bob     20    CA     92
# Ellen   24    CA     88
# <class 'pandas.core.frame.DataFrame'>

 loc, iloc으로 행의 지정을 : (전체의 슬라이스)로 하면 열을 참조할 수 있다. 인덱스 참조에서는 할 수 없는 슬라이스에 의한 지정도 가능하다. iloc으로 열 번호를 사용하는 것도 가능하다.

print(df.loc[:, 'age':'point'])
print(type(df.loc[:, 'age':'point']))
#          age state  point
# name                     
# Alice     24    NY     64
# Bob       20    CA     92
# Charlie   30    CA     70
# Dave      40    TX     70
# Ellen     24    CA     88
# Frank     30    NY     57
# <class 'pandas.core.frame.DataFrame'>

print(df.iloc[:, [0, 2]])
print(type(df.iloc[:, [0, 2]]))
#          age  point
# name               
# Alice     24     64
# Bob       20     92
# Charlie   30     70
# Dave      40     70
# Ellen     24     88
# Frank     30     57
# <class 'pandas.core.frame.DataFrame'>

 행 이름-행 번호, 열 이름-열 번호를 하나로 지정하여 하나의 행-하나의 열을 선택하는 경우는 pandas.Series가 반환되지만, 동일한 하나의 행 - 하나의 열을 선택하는 경우에도 슬라이스나 리스트로 지정한 경우는 pandas.DataFrame이 된다.

print(df.loc['Bob'])
print(type(df.loc['Bob']))
# age      20
# state    CA
# point    92
# Name: Bob, dtype: object
# <class 'pandas.core.series.Series'>

print(df.loc['Bob':'Bob'])
print(type(df.loc['Bob':'Bob']))
#       age state  point
# name                  
# Bob    20    CA     92
# <class 'pandas.core.frame.DataFrame'>

print(df.loc[['Bob']])
print(type(df.loc[['Bob']]))
#       age state  point
# name                  
# Bob    20    CA     92
# <class 'pandas.core.frame.DataFrame'>
print(df.iloc[:, 1])
print(type(df.iloc[:, 1]))
# name
# Alice      NY
# Bob        CA
# Charlie    CA
# Dave       TX
# Ellen      CA
# Frank      NY
# Name: state, dtype: object
# <class 'pandas.core.series.Series'>

print(df.iloc[:, 1:2])
print(type(df.iloc[:, 1:2]))
#         state
# name         
# Alice      NY
# Bob        CA
# Charlie    CA
# Dave       TX
# Ellen      CA
# Frank      NY
# <class 'pandas.core.frame.DataFrame'>

print(df.iloc[:, [1]])
print(type(df.iloc[:, [1]]))
#         state
# name         
# Alice      NY
# Bob        CA
# Charlie    CA
# Dave       TX
# Ellen      CA
# Frank      NY
# <class 'pandas.core.frame.DataFrame'>

 특히, 행을 pandas.Series로 선택하면 암묵적 형변환이 이뤄질 가능성이 있으므로 주의할 필요가 있다.

 

 

행 명과 열 명이 중복된 값을 가지고 있는 경우


 행 명(행 라벨) index, 열 명(열 라벨) columns에 중복된 값이 포함되어 있어도 에러가 발생하지 않는다.

 중복된 값을 가진 열을 index에 지정한 경우 다음과 같은 예가 된다.

df_state = pd.read_csv('data/src/sample_pandas_normal.csv', index_col=2)
print(df_state)
#           name  age  point
# state                     
# NY       Alice   24     64
# CA         Bob   42     92
# CA     Charlie   18     70
# TX        Dave   68     70
# CA       Ellen   24     88
# NY       Frank   30     57

print(df_state.index.values)
# ['NY' 'CA' 'CA' 'TX' 'CA' 'NY']

 at으로 중복된 열 이름을 지정하면, numpy.ndarray로 여러 개의 값이 반환되어 온다.

print(df_state.at['NY', 'age'])
print(type(df_state.at['NY', 'age']))
# [24 30]
# <class 'numpy.ndarray'>

 loc으로 중복된 행 명을 지정하면, pandas.DataFrame 혹은 pandas.Series가 반환된다.

print(df_state.loc['NY', 'age'])
print(type(df_state.loc['NY', 'age']))
# state
# NY    24
# NY    30
# Name: age, dtype: int64
# <class 'pandas.core.series.Series'>

print(df_state.loc['NY', ['age', 'point']])
print(type(df_state.loc['NY', ['age', 'point']]))
#        age  point
# state            
# NY      24     64
# NY      30     57
# <class 'pandas.core.frame.DataFrame'>

 iat이나 iloc에서 행 번호으로 지정하는 경우는 행 번호가 중복되어 있어도 특히 문제가 없다.

print(df_state.iat[0, 1])
# 24

 혼란의 근원이되므로, 강력한 이유가 없다면 지정할 때, 행 이름-열 이름은 임의의 값으로 하는 편이 좋다.

 행 이름 - 열 이름이 임의의 값으로 되어 있는지 아닌지(중복되는지 아닌지)는 index.is_unique, columns.is_unique로 확인할 수 있다.

print(df_state.index.is_unique)
# False

print(df_state.columns.is_unique)
# True

 

 

번호와 라벨로 위치를 지정하기


 행 번호와 열 라벨과 같이 번호와 라벨의 조합으로 위치를 지정하고 싶은 경우, at 혹은 loc과 index나 columns를 사용하는 방법이 있다.

 index나 columns로 행 번호 혹은 열 변호로부터 그 행 라벨, 열 라벨을 획득할 수 있다.

print(df)
#          age state  point
# name                     
# Alice     24    NY     64
# Bob       20    CA     92
# Charlie   30    CA     70
# Dave      40    TX     70
# Ellen     24    CA     88
# Frank     30    NY     57

print(df.index[2])
# Charlie

print(df.columns[1])
# state

 이것과 at 혹은 loc를 사용하여, 번호와 라벨의 조합으로 위치를 지정할 수 있다.

print(df.at[df.index[2], 'age'])
# 30

print(df.loc[['Alice', 'Dave'], df.columns[1]])
# name
# Alice    NY
# Dave     TX
# Name: state, dtype: object

 위에서 서술했듯이, 슬라이스로 지정할 때, loc으로 행 라벨, 열 라벨을 사용하는 경우는 stop까지 포함되지만, iloc에서 행 번호, 열 번호를 사용하는 경우는 stop의 하나 앞까지가 된다. stop의 값을 번호로부터 라벨으로 변환하는 경우 index[n-1]로 할 필요가 있으므로 주의하자. 

 아래와 같이 []나 loc, iloc을 반복해 작성할 수 있지만, 이것은 chained indexing이라는 것으로 SettingWithCopyWarning이라는 경고의 원인이 된다.

 또한, 다음에 설명하는 것과 같이, 하나의 행을 선택할 경우는 암묵적 형변환이 일어나는 경우가 있다. 앞에서 표시된 index나 column을 사용하여 하나의 at이나 loc으로 묶어서 사용하는 편이 좋다.

print(df['age'][2])
# 30

print(df.age[2])
# 30

print(df.loc[['Alice', 'Dave']].iloc[:, 1])
# name
# Alice    NY
# Dave     TX
# Name: state, dtype: object

 

 

행을 pandas.Series로 선택할 때의 묵시적 형변환


 loc이나 iloc으로 하나의 행을 pandas.Series로 선택해 획득할 경우, 데이터형 dtype으로 통일되므로, 원래의 pandas.DataFrame 행 데이터 형이 달라지는 묵시적 형 변환이 일어난다.

 정수 int의 열와 부동 소수점 수 float을 가진 pandas.DataFrame을 통해 살펴보자.

df_mix = pd.DataFrame({'col_int': [0, 1, 2], 'col_float': [0.1, 0.2, 0.3]}, index=['A', 'B', 'C'])
print(df_mix)
#    col_int  col_float
# A        0        0.1
# B        1        0.2
# C        2        0.3

print(df_mix.dtypes)
# col_int        int64
# col_float    float64
# dtype: object

 loc이나 iloc으로 1행을 획득하면, float의 pandas.Series가 된다. int의 열에 있던 데이터는 float로 변환된다는 것이다.

print(df_mix.loc['B'])
# col_int      1.0
# col_float    0.2
# Name: B, dtype: float64

print(type(df_mix.loc['B']))
# <class 'pandas.core.series.Series'>

 다음과 같이 []을 연속해서 작성하면, float로 변환된 pandas.Series의 요소가 획득되게 된다. 원래 데이터형과 다른 데이터형으로 변환된 데이터 값을 얻게 되므로 주의해야한다.

print(df_mix.loc['B']['col_int'])
# 1.0

print(type(df_mix.loc['B']['col_int']))
# <class 'numpy.float64'>

 따라서 이러한 현상을 피하려면, at이나 iat을 사용하는 편이 좋다. at이나 iat이면 원래 데이터형 그대로 값을 취득할 수 있다.

print(df_mix.at['B', 'col_int'])
# 1

print(type(df_mix.at['B', 'col_int']))
# <class 'numpy.int64'>

 loc이나 iloc으로 1개의 리스트로 지정하는 경우, pandas.Series가 아닌 하나의 pandas.DataFrame행이 된다. 당연하지만, 이 경우는 원래 데이터형 dtype이 유지된다.

print(df_mix.loc[['B']])
#    col_int  col_float
# B        1        0.2

print(type(df_mix.loc[['B']]))
# <class 'pandas.core.frame.DataFrame'>

print(df_mix.loc[['B']].dtypes)
# col_int        int64
# col_float    float64
# dtype: object

참고자료

https://deepage.net/features/pandas-location.html

https://note.nkmk.me/python-pandas-at-iat-loc-iloc/

728x90