Performance_Eval_And_Query
Performance_Eval_And_Query
High-Performance Pandas: eval() and query()
Motivating query() and eval(): Compound Expressions
1
2
3
4
5
6
7
import numpy as np
rng = np.random.RandomState(42)
x = rng.rand(1000000)
y = rng.rand(1000000)
%timeit
x + y
1
1.65 ms ± 24.4 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
1
2
3
# 파이썬 루프나 컴프리헨션으로 더하는 것보다 빠름
%timeit
np.fromiter((xi + yi for xi, yi in zip(x, y)), dtype=x.dtype, count=len(x))
1
122 ms ± 1.38 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
1
2
3
4
5
6
7
# 복합 표현식에선 효율이 떨어짐
mask = (x > 0.5) & (y < 0.5)
# 모든 중간 단계가 명시적으로 메모리에 할당
# x, y 규모가 크면 메모리와 계산능력에 오버헤드 발생
tmp1 = (x > 0.5)
tmp2 = (y < 0.5)
mask = tmp1 & tmp2
1
2
3
4
5
import numexpr
# 전체 크기의 임시 배열을 사용하지 않고 표현식을 평가
mask_numexpr = numexpr.evaluate('(x > 0.5) & (y < 0.5)')
np.allclose(mask, mask_numexpr)
1
True
pandas.eval() for Efficient Operations
1
2
3
4
5
6
import pandas as pd
nrows, ncols = 100000, 100
rng = np.random.RandomState(42)
df1, df2, df3, df4 = (pd.DataFrame(rng.rand(nrows, ncols))
for i in range(4))
1
2
3
# 전형적인 Pandas 접근 방식
%timeit
df1 + df2 + df3 + df4
1
36.8 ms ± 1.46 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
1
2
3
# 표현식을 문자열로 구성
%timeit
pd.eval('df1 + df2 + df3 + df4')
1
16.4 ms ± 666 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
1
2
3
# 같은 결과를 주며 더 빠르고 적은 메모리 사용
np.allclose(df1 + df2 + df3 + df4,
pd.eval('df1 + df2 + df3 + df4'))
1
True
Operations supported by pd.eval()
1
2
df1, df2, df3, df4, df5 = (pd.DataFrame(rng.randint(0, 1000, (100, 3)))
for i in range(5))
Arithmetic operators
1
2
3
4
# pd.eval()은 모든 산술 연산자를 지원
result1 = -df1 * df2 / (df3 + df4) - df5
result2 = pd.eval('-df1 * df2 / (df3 + df4) - df5')
np.allclose(result1, result2)
1
True
Comparison operators
1
2
3
4
# 연쇄 표현식을 포함한 모든 비교 연산자 지원
result1 = (df1 < df2) & (df2 <= df3) & (df3 != df4)
result2 = pd.eval('df1 < df2 <= df3 != df4')
np.allclose(result1, result2)
1
True
Bitwise operators
1
2
3
4
# & 와 | 비트 단위 연산자 지원
result1 = (df1 < 0.5) & (df2 < 0.5) | (df3 < df4)
result2 = pd.eval('(df1 < 0.5) & (df2 < 0.5) | (df3 < df4)')
np.allclose(result1, result2)
1
True
Boolean expressions
1
2
3
# 리터널 and와 or 사용을 지원
result3 = pd.eval('(df1 < 0.5) and (df2 < 0.5) or (df3 < df4)')
np.allclose(result1, result3)
1
True
Object attributes and indices
1
2
3
4
5
# obj.attr 구문을 통해 객체 속성에 접근하는 것을 지원
# obj[index] 구문을 통해 인덱스에 접근하는 것을 지원
result1 = df2.T[0] + df3.iloc[1]
result2 = pd.eval('df2.T[0] + df3.iloc[1]')
np.allclose(result1, result2)
1
True
열 단위의 연산을 위한 DataFrame.eval() for Column-Wise Operations
1
2
df = pd.DataFrame(rng.rand(1000, 3), columns=['A', 'B', 'C'])
df.head()
| A | B | C | |
|---|---|---|---|
| 0 | 0.375506 | 0.406939 | 0.069938 |
| 1 | 0.069087 | 0.235615 | 0.154374 |
| 2 | 0.677945 | 0.433839 | 0.652324 |
| 3 | 0.264038 | 0.808055 | 0.347197 |
| 4 | 0.589161 | 0.252418 | 0.557789 |
1
2
3
4
# 세 개의 열이 있는 표현식 계산
result1 = (df['A'] + df['B']) / (df['C'] - 1)
result2 = pd.eval("(df.A + df.B) / (df.C - 1)")
np.allclose(result1, result2)
1
True
1
2
3
# 열을 사용하는 표현식을 훨씬 간결하게 평가
result3 = df.eval('(A + B) / (C - 1)')
np.allclose(result1, result3)
1
True
Assignment in DataFrame.eval()
1
df.head()
| A | B | C | |
|---|---|---|---|
| 0 | 0.375506 | 0.406939 | 0.069938 |
| 1 | 0.069087 | 0.235615 | 0.154374 |
| 2 | 0.677945 | 0.433839 | 0.652324 |
| 3 | 0.264038 | 0.808055 | 0.347197 |
| 4 | 0.589161 | 0.252418 | 0.557789 |
1
2
3
# D 열 생성 후 다른 열로부터 계산된 값을 할당
df.eval('D = (A + B) / C', inplace=True)
df.head()
| A | B | C | D | |
|---|---|---|---|---|
| 0 | 0.375506 | 0.406939 | 0.069938 | 11.187620 |
| 1 | 0.069087 | 0.235615 | 0.154374 | 1.973796 |
| 2 | 0.677945 | 0.433839 | 0.652324 | 1.704344 |
| 3 | 0.264038 | 0.808055 | 0.347197 | 3.087857 |
| 4 | 0.589161 | 0.252418 | 0.557789 | 1.508776 |
1
2
3
# 같은 방식의 수정
df.eval('D = (A - B) / C', inplace=True)
df.head()
| A | B | C | D | |
|---|---|---|---|---|
| 0 | 0.375506 | 0.406939 | 0.069938 | -0.449425 |
| 1 | 0.069087 | 0.235615 | 0.154374 | -1.078728 |
| 2 | 0.677945 | 0.433839 | 0.652324 | 0.374209 |
| 3 | 0.264038 | 0.808055 | 0.347197 | -1.566886 |
| 4 | 0.589161 | 0.252418 | 0.557789 | 0.603708 |
Local variables in DataFrame.eval()
1
2
3
4
5
6
# @기호는 열 이름이 아닌 변수 이름을 표시
# 열의 네임스페이스와 파이썬 객체의 네임스페이스를 포함하는 표현식 평가
column_mean = df.mean(1)
result1 = df['A'] + column_mean
result2 = df.eval('A + @column_mean')
np.allclose(result1, result2)
1
True
DataFrame.query() Method
1
2
3
result1 = df[(df.A < 0.5) & (df.B < 0.5)]
result2 = pd.eval('df[(df.A < 0.5) & (df.B < 0.5)]')
np.allclose(result1, result2)
1
True
1
2
3
# 열을 포함하는 표현식에선 DataFrame.eval() 대신 query() 로 필터링 연산
result2 = df.query('A < 0.5 and B < 0.5')
np.allclose(result1, result2)
1
True
1
2
3
4
5
# 지역 변수를 표시하기 위해 @플래그를 받음
Cmean = df['C'].mean()
result1 = df[(df.A < Cmean) & (df.B < Cmean)]
result2 = df.query('A < @Cmean and B < @Cmean')
np.allclose(result1, result2)
1
True
Performance: When to Use These Functions
1
2
3
4
5
6
7
8
9
# NumPy 배열이나 Pandas DataFrame 을 포함하는 모든 복합 표현식은 임시 배열을 생성
x = df[(df.A < 0.5) & (df.B < 0.5)]
tmp1 = df.A < 0.5
tmp2 = df.B < 0.5
tmp3 = tmp1 & tmp2
x = df[tmp3]
# 임시 DataFrame의 크기가 사용 가능한 메모리에 비해 상당히 크다면 eval()이나 query() 표현식 사용
df.values.nbytes
1
32000
이 기사는 저작권자의 CC BY-NC 4.0 라이센스를 따릅니다.