본문 바로가기
코딩/Python 기초

Python 데이터프레임 인덱싱 및 슬라이싱 - loc, iloc, at, iat

by 미생22 2024. 4. 17.
728x90

출처 : https://devocean.sk.com/blog/techBoardDetail.do?ID=164657&boardType=techBlog

 

Pandas 인덱싱 속도 높이기 - loc, iloc, at, iat, ix

 

devocean.sk.com

 

Python에서 Pandas를 사용하다 보면 내가 원하는 데이터만 잘라 쓰거나 뽑아야 할 경우가 많은데요,

Pandas에서는 편하게 Dataframe에서 'loc'이라는 메소드 외에도 iloc, at, iat 메소드를 사용할 수 있습니다.

간단하게 위의 메소드들은 인덱싱에 사용되는 메소드들이며 소괄호 대신 대괄호 '[]'를 사용한다는 공통점이 있지만, 인덱싱 방법부터, 이들의 인덱싱 속도까지 모두 다른데요,

 

이번 포스팅에서는 해당 메소드들 간의 기능부터 시작해 성능을 비교해 보며 어떻게 활용하면 좋을지 탐구해보도록 하겠습니다.

 

1. 기능 비교하기

a. ix

ix의 특징은 레이블 기반과 위치 기반의 인덱싱을 모두 지원한다는 점입니다.

즉, ix 함수에 행이나 열의 이름이나 번호를 넣어서 원하는 데이터를 가져올 수 있습니다.

예를 들어, 다음과 같은 데이터프레임이 있다고 가정해 보겠습니다.


ABCD

a -0.176282 -0.024792 -0.173389 -1.067029
b -0.237726 -1.316897 0.433125 -0.143812
c -1.055902 -0.057497 -2.011647 1.035801
d -0.116441 -1.040585 -2.043079 0.019520
e -1.051512 -2.087875 -0.291630 -1.017061

이때 ix 함수를 사용하면 다음과 같은 방식으로 데이터프레임을 슬라이싱할 수 있습니다.

# 정수 위치 기반 인덱싱
df.ix[1:3, :2]


AB

b -0 .237726 -1 .316897
c -1 .055902 -0 .057497
# 혼합된 인덱싱
df.ix[2:, ['A', 'C']]


AC

c -1 .055902 -2 .011647
d -0 .116441 -2 .043079
e -1 .051512 -0 .291630

위치 기반과 레이블 기반 모두 사용 할 수 있으니 편하게 느껴질 수 있습니다.

하지만 pandas 버전 0.20.0부터 ix 함수는 더 이상 사용할 수 없습니다. 그 이유는 다음과 같습니다.

  • ix 함수는 레이블과 위치 정수를 모두 사용할 수 있다는 장점이 있지만, 인덱싱을 할 때 모호해집니다.
  • 예를 들어 df.ix[0]은 첫 번째 행인지 아니면 레이블이 '0'인 행인지 알 수 없습니다.
  • ix 함수는 슬라이싱을 할 때 loc와 iloc의 규칙을 섞어서 적용합니다.
  • 예를 들어 df.ix[:3]은 loc처럼 처음부터 세 번째 행까지 포함하고, df.ix[:, :3]은 iloc처럼 처음부터 세 번째 열까지 포함하지 않습니다.
  • ix 함수는 해당 객체를 찾을 수 없을 경우 KeyError를 발생시킵니다. 단 슬라이싱은 예외입니다.

따라서 pandas 개발자들은 ix 함수의 사용을 권장하지 않고, 대신 loc와 iloc과 같은 보다 엄격한 인덱싱 방법을 사용할 것을 권장합니다.

 

b. loc / iloc

loc와 iloc은 모두 인덱싱에 사용되지만, 차이점은 다음과 같습니다 :

  • loc은 레이블(label)을 기준으로 행과 열을 선택합니다. 레이블은 인덱스(index)나 컬럼(column)의 이름입니다.
  • iloc은 위치(position)를 기준으로 행과 열을 선택합니다. 위치는 인덱스나 컬럼의 정수 번호입니다.


ABCD

a -0.176282 -0.024792 -0.173389 -1.067029
b -0.237726 -1.316897 0.433125 -0.143812
c -1.055902 -0.057497 -2.011647 1.035801
d -0.116441 -1.040585 -2.043079 0.019520
e -1.051512 -2.087875 -0.291630 -1.017061

아까와 같은 위 dataframe을 예시로 든다면..

# d이후의 행, B~C열 선택하기
df.loc['d':, 'B':'C']


BC

d -1.040585 -2.043079
e -2.087875 -0.291630
# d이후의 행, B~C열 선택하기
df.iloc[3:, 1:3]


BC

d -1.040585 -2.043079
e -2.087875 -0.291630

두 표현식은 모두 같은 결과를 냅니다.

하지만 loc은 레이블로만 접근이 가능하며, iloc은 정수형 인덱스로만 접근이 가능하다는 것을 알 수 있습니다.

 

c. at / iat

at iat은 데이터프레임에서 특정 위치의 값을 인덱싱하거나 변경할 때 사용하는 메소드입니다.

loc과 iloc과 비슷한 기능을 하지만, at과 iat은 단일 값에만 적용된다는 특징이 있죠.

 

아까와 같은 dataframe을 예시로, at과 iat을 사용해 보겠습니다.


ABCD

a -0.176282 -0.024792 -0.173389 -1.067029
b -0.237726 -1.316897 0.433125 -0.143812
c -1.055902 -0.057497 -2.011647 1.035801
d -0.116441 -1.040585 -2.043079 0.019520
e -1.051512 -2.087875 -0.291630 -1.017061

at 메소드는 행 인덱스와 열 이름을 사용하여 값을 가져오거나 변경합니다.

예를 들어, a행의 A열의 값을 가져오려면 다음과 같이 코드를 작성할 수 있습니다.

print(df.at['a', 'A'])
-0.176282

iat 메소드는 행 번호와 열 번호를 사용하여 값을 가져오거나 변경합니다.

예를 들어, 3번째 행의 3번째 열의 값을 가져오려면 다음과 같이 코드를 작성할 수 있습니다.

print(df.iat[2, 2])
-2.011647

at과 iat은 단일 값에만 적용되므로 여러 값이나 범위를 인덱싱하려면 loc이나 iloc을 사용해야 합니다.

하지만, loc과 iloc에 비해 속도가 더 빠르다는 장점이 있으니... 다음 장에서 살펴보도록 하겠습니다.

 

2. 속도 비교하기

이들의 특징과 용도는 파악했습니다.

그럼 이제 이들의 속도는 어떨까요? 그냥 편하게 loc이나 iloc만 쓰면 안 될까요?

아래와 같이 대형 데이터를 생성하고, 접근 속도를 비교해 보겠습니다.

import numpy as np
import pandas as pd

data = np.random.randint(0, 100, size=(200001, 3))
df = pd.DataFrame(data, columns=['가','나','다'])


가나다

0 57 67 83
1 7 86 8
2 78 9 86
... ... ... ...
200000 23 45 12

위와 같은 표를 생성시킨 후에, 가,나,다 열의 값을 '라'에 모두 sum해보도록 하겠습니다.

첫번째 'loc'함수로 진행해 볼까요?

# loc 메소드 사용
import time
start = time.time()

for index in range(len(df)):
        df.loc[index,'라'] = df.loc[index,'가'] + df.loc[index,'나'] + df.loc[index,'다']
end = time.time()
print(end - start)
-> 21.9371s

21.9초가 나왔습니다.

20만 행 x 4열 에 대한 인덱싱 이므로, 80만 번 인덱싱에 21.9초가 걸린 것이네요.

다음엔 iloc을 써보도록 하겠습니다.

# iloc 메소드 사용
import time
start = time.time()

df.loc[:,'라'] = 0
for index in range(len(df)):
        df.iloc[index,3] = df.iloc[index,0] + df.iloc[index,1] + df.iloc[index,2]
end = time.time()
print(end - start)
-> 27.6493s

iloc이 loc보다 30%정도 느리게 나왔네요.

그럼 이어서 at, iat까지 진행해 볼까요?

# at 메소드 사용
import time
start = time.time()

for index in range(len(df)):
        df.at[index,'라'] = df.at[index,'가'] + df.at[index,'나'] + df.at[index,'다']
end = time.time()
print(end - start)
-> 4.3233s
# iat 메소드 사용
import time
start = time.time()

df.loc[:,'라'] = 0
for index in range(len(df)):
        df.iat[index,3] = df.iat[index,0] + df.iat[index,1] + df.iat[index,2]
end = time.time()
print(end - start)
-> 14.8355s

결과를 보면 at loc보다 5배나 빠르고, iat iloc보다 2배나 빠르다는 것을 확인할 수 있었습니다.

단순히 한개의 값만 인덱싱 할 때에는 at 혹은 iat을 사용하는 것이 효율 측면에서 더 좋겠습니다.

 

하지만 at과 iat, 그리고 loc과 iloc 사이에서는 오히려 숫자 인덱싱을 사용하는 iat, iloc이 더 느리다는 것을 볼 수 있었는데요,

통념 상 이름을 찾기 위한 추가적인 연산이 필요 없는 loc이 더 느려야 할텐데, iloc계열이 더 느렸네요. 행 인덱스가 정수이고, 순차 접근해서 그런 것이 아닐까요?

 

혹시나 그런 경우일 수 있으니, 데이터프레임의 인덱스가 무작위로 되어 있는 경우로 테스트해보도록 하겠습니다.

저희가 진행한 데이터는 0부터 20000까지 순서대로 나열된 '숫자' 인덱스를 '순서대로' 접근했기에 loc함수가 접근하기 쉬웠을 수도 있죠.

그래서 다음과 같은 코드로 다시 진행해 보겠습니다.

import numpy as np
import pandas as pd
import random
import string

data = np.random.randint(0, 100, size=(200001, 3))
df = pd.DataFrame(data, columns=['가','나','다'])

# 무작위 알파벳으로 구성된 7자리 문자열을 생성하는 함수 정의
def random_string(length):
    letters = string.ascii_letters 
    return ''.join(random.choice(letters) for i in range(length))

# row index를 무작위 알파벳으로 바꾸기
df.index = [random_string(7) for i in range(len(df))]


가나다

oLiXBCg 57 67 83
xrSBAhE 7 86 8
sxoOsUT 78 9 86
... ... ... ...
DhLglsy 23 45 12

이런 무작위 알파벳에 다시 무작위로 접근한다면 iloc과 loc간의 접근 속도가 어떻게 달라질까요?

import time

index_list = df.index.tolist()
# 무작위로 index 섞기
random.shuffle(index_list)
start = time.time()

for index in index_list:
        df.loc[index,'라'] = df.loc[index,'가'] + df.loc[index,'나'] + df.loc[index,'다']
end = time.time()
print(end - start)
-> 22.3237s

무작위로 index를 생성 후, 다시 무작위 순서로 접근했는데도 불구하고 iloc을 사용한 27초대 보다 빠르게 나왔습니다.

일반적으로는 iloc이 loc보다 빠르다고 알려져 있지만, 이번 실험에서는 반대의 결과가 나왔습니다.

이는 데이터의 크기나 형태, 특징에 따라 성능이 달라질 수 있음을 의미합니다.

 

이상으로 pandas에서 loc / iloc, at/ iat을 비교해 보았습니다.

단 한 개의 값에만 접근할 때에는 at, iat을 사용하는 것이 최소 2배에서 최대 5배까지 효율적이었다는 것을 확인했습니다.

일반적으로는 위치 단위 인덱싱이 레이블 단위 인덱싱보다 빠르다고 생각할 수 있지만, iloc와 loc의 예시에서 보듯이 그렇지 않은 경우도 있습니다.

따라서 어떤 방식을 사용할지는 데이터의 특성에 따라 다르므로, 직접 확인하고 적절한 방법을 선택하는 것이 좋겠다는 결론을 내릴 수 있겠습니다.

728x90

'코딩 > Python 기초' 카테고리의 다른 글

zipping과 unpacking  (0) 2024.02.23
Python 얕은 복사, 깊은 복사  (0) 2023.02.06
Python 제어문 반복자 자료형  (0) 2023.01.11
Python 문자열 자료형  (0) 2023.01.05