Pandas 데이터 프레임을 사용하고 있으며 기존 열의 함수로 새 열을 만들고 싶습니다. 나는 속도 차이에 대한 좋은 토론을 보지 못했습니다.df.apply()
과 np.vectorize()
내가 여기 물어 것이라고 생각 때문에.
Pandas apply()
기능이 느립니다. 내가 측정 한 것 (일부 실험에서 아래에 표시됨)에서 np.vectorize()
사용하면 apply()
적어도 2016 MacBook Pro에서 DataFrame 기능을 사용하는 것보다 25 배 더 빠릅니다 (또는 그 이상) . 이것은 예상 된 결과이며 그 이유는 무엇입니까?
예를 들어 N
행 이있는 다음 데이터 프레임이 있다고 가정 합니다.
N = 10
A_list = np.random.randint(1, 100, N)
B_list = np.random.randint(1, 100, N)
df = pd.DataFrame({'A': A_list, 'B': B_list})
df.head()
# A B
# 0 78 50
# 1 23 91
# 2 55 62
# 3 82 64
# 4 99 80
추가로 두 개의 열 A
및 의 함수로 새 열을 생성한다고 가정 B
합니다. 아래 예에서는 간단한 함수를 사용합니다 divide()
. 기능을 적용하려면 df.apply()
또는 np.vectorize()
다음 중 하나를 사용할 수 있습니다 .
def divide(a, b):
if b == 0:
return 0.0
return float(a)/b
df['result'] = df.apply(lambda row: divide(row['A'], row['B']), axis=1)
df['result2'] = np.vectorize(divide)(df['A'], df['B'])
df.head()
# A B result result2
# 0 78 50 1.560000 1.560000
# 1 23 91 0.252747 0.252747
# 2 55 62 0.887097 0.887097
# 3 82 64 1.281250 1.281250
# 4 99 80 1.237500 1.237500
내가 늘리면 N
100 만 이상의처럼 실제 크기에, 나는 그 관찰 np.vectorize()
25 배 빠른 이상보다 df.apply()
.
다음은 완전한 벤치마킹 코드입니다.
import pandas as pd
import numpy as np
import time
def divide(a, b):
if b == 0:
return 0.0
return float(a)/b
for N in [1000, 10000, 100000, 1000000, 10000000]:
print ''
A_list = np.random.randint(1, 100, N)
B_list = np.random.randint(1, 100, N)
df = pd.DataFrame({'A': A_list, 'B': B_list})
start_epoch_sec = int(time.time())
df['result'] = df.apply(lambda row: divide(row['A'], row['B']), axis=1)
end_epoch_sec = int(time.time())
result_apply = end_epoch_sec - start_epoch_sec
start_epoch_sec = int(time.time())
df['result2'] = np.vectorize(divide)(df['A'], df['B'])
end_epoch_sec = int(time.time())
result_vectorize = end_epoch_sec - start_epoch_sec
print 'N=%d, df.apply: %d sec, np.vectorize: %d sec' % \
(N, result_apply, result_vectorize)
# Make sure results from df.apply and np.vectorize match.
assert(df['result'].equals(df['result2']))
결과는 다음과 같습니다.
N=1000, df.apply: 0 sec, np.vectorize: 0 sec
N=10000, df.apply: 1 sec, np.vectorize: 0 sec
N=100000, df.apply: 2 sec, np.vectorize: 0 sec
N=1000000, df.apply: 24 sec, np.vectorize: 1 sec
N=10000000, df.apply: 262 sec, np.vectorize: 4 sec
경우는 np.vectorize()
항상 속도보다 일반적이다 df.apply()
, 왜되어 np.vectorize()
더 언급하지? 다음 df.apply()
과 같은 관련 StackOverflow 게시물 만 볼 수 있습니다.
Pandas ‘적용’기능을 여러 열에 어떻게 사용합니까?
Pandas 데이터 프레임의 두 열에 함수를 적용하는 방법
답변
먼저 Pandas 및 NumPy 배열의 힘이 숫자 배열에 대한 고성능 벡터화 된 계산에서 파생되었다고 말씀 드리겠습니다 . 1 벡터화 된 계산의 전체 요점은 계산을 고도로 최적화 된 C 코드로 이동하고 연속적인 메모리 블록을 활용하여 Python 수준의 루프를 방지하는 것입니다. 2
Python 수준 루프
이제 몇 가지 타이밍을 볼 수 있습니다. 아래는 모든 파이썬 수준 중 하나를 생산 루프 pd.Series
, np.ndarray
또는 list
같은 값을 포함하는 객체. 데이터 프레임 내에서 시리즈에 할당 할 목적으로 결과는 비슷합니다.
# Python 3.6.5, NumPy 1.14.3, Pandas 0.23.0
np.random.seed(0)
N = 10**5
%timeit list(map(divide, df['A'], df['B'])) # 43.9 ms
%timeit np.vectorize(divide)(df['A'], df['B']) # 48.1 ms
%timeit [divide(a, b) for a, b in zip(df['A'], df['B'])] # 49.4 ms
%timeit [divide(a, b) for a, b in df[['A', 'B']].itertuples(index=False)] # 112 ms
%timeit df.apply(lambda row: divide(*row), axis=1, raw=True) # 760 ms
%timeit df.apply(lambda row: divide(row['A'], row['B']), axis=1) # 4.83 s
%timeit [divide(row['A'], row['B']) for _, row in df[['A', 'B']].iterrows()] # 11.6 s
몇 가지 요점 :
tuple
기반 방법 (제 4) 이외의 요소가 더 효율적pd.Series
기반 방법 (마지막 3).np.vectorize
, 목록 이해력 +zip
및map
방법, 즉 상위 3 개는 모두 거의 동일한 성능을 갖습니다. 이는 .NET에서 일부 Pandas 오버 헤드를 사용tuple
하고 우회 하기 때문 입니다pd.DataFrame.itertuples
.- 사용
raw=True
하는 경우pd.DataFrame.apply
와 사용하지 않는 경우 속도가 크게 향상됩니다 . 이 옵션은 NumPy 배열을pd.Series
개체 대신 사용자 지정 함수에 제공 합니다.
pd.DataFrame.apply
: 또 다른 루프
Pandas가 전달하는 객체를 정확하게 보려면 함수를 간단하게 수정할 수 있습니다.
def foo(row):
print(type(row))
assert False # because you only need to see this once
df.apply(lambda row: foo(row), axis=1)
출력 : <class 'pandas.core.series.Series'>
. Pandas 시리즈 객체를 생성, 전달 및 쿼리하면 NumPy 배열에 비해 상당한 오버 헤드가 발생합니다. 이것은 놀라운 일이 아닙니다. Pandas 시리즈에는 색인, 값, 속성 등을 보유하기위한 적절한 양의 스캐 폴딩이 포함되어 있습니다.
와 같은 운동을 다시 raw=True
하면 <class 'numpy.ndarray'>
. 이 모든 것은 문서에 설명되어 있지만 더 설득력이 있습니다.
np.vectorize
: 가짜 벡터화
에 대한 문서 np.vectorize
에는 다음과 같은 메모가 있습니다.
벡터화 된 함수
pyfunc
는 numpy의 브로드 캐스팅 규칙을 사용하는 것을 제외하고는 python map 함수와 같은 입력 배열의 연속적인 튜플에 대해 평가 합니다.
여기서 “방송 규칙”은 입력 배열이 동일한 차원을 갖기 때문에 관련이 없습니다. 위 map
의 map
버전이 거의 동일한 성능을 갖기 때문에 병렬 은 유익 합니다. 소스 코드 무슨 일이 일어나고 있는지 쇼 : np.vectorize
로 입력 기능을 변환 범용 기능 을 통해 ( “ufunc”) np.frompyfunc
. 성능 향상으로 이어질 수있는 캐싱과 같은 최적화가 있습니다.
요컨대, np.vectorize
파이썬 수준의 루프 가해야 할 일 을 수행하지만 pd.DataFrame.apply
덩어리 오버 헤드를 추가합니다. 표시되는 JIT 컴파일이 없습니다 numba
(아래 참조). 그것은 단지 편의 입니다.
진정한 벡터화 : 사용해야 하는 것
위의 차이점이 어디에도 언급되지 않은 이유는 무엇입니까? 진정으로 벡터화 된 계산의 성능으로 인해 관련성이 없기 때문입니다.
%timeit np.where(df['B'] == 0, 0, df['A'] / df['B']) # 1.17 ms
%timeit (df['A'] / df['B']).replace([np.inf, -np.inf], 0) # 1.96 ms
예, 위의 루프 솔루션 중 가장 빠른 것보다 40 배 더 빠릅니다. 둘 중 하나가 허용됩니다. 제 생각에 첫 번째는 간결하고 읽기 쉽고 효율적입니다. numba
성능이 중요하고 이것이 병목 현상의 일부인 경우에만 아래의 다른 방법을 살펴보십시오 .
numba.njit
: 더 큰 효율성
루프 가 실행 가능한 것으로 간주 되면 일반적으로 다음을 통해 최적화됩니다.numba
으로 기본 NumPy 배열을 되어 가능한 한 C로 이동합니다.
실제로 numba
성능을 마이크로 초로 향상시킵니다 . 번거로운 작업 없이는 이것보다 훨씬 더 효율적으로되기 어려울 것입니다.
from numba import njit
@njit
def divide(a, b):
res = np.empty(a.shape)
for i in range(len(a)):
if b[i] != 0:
res[i] = a[i] / b[i]
else:
res[i] = 0
return res
%timeit divide(df['A'].values, df['B'].values) # 717 µs
사용하면 @njit(parallel=True)
더 큰 어레이에 대한 추가 향상을 제공 할 수 있습니다.
1 개 숫자 유형은 다음과 같습니다 : int
, float
, datetime
, bool
, category
. 그들은 제외 object
DTYPE 및 연속 메모리 블록에서 유지 될 수있다.
2
NumPy 작업이 Python에 비해 효율적인 이유는 최소한 두 가지입니다.
- 파이썬의 모든 것은 객체입니다. 여기에는 C와 달리 숫자가 포함됩니다. 따라서 Python 유형에는 네이티브 C 유형에는 존재하지 않는 오버 헤드가 있습니다.
- NumPy 메서드는 일반적으로 C 기반입니다. 또한 가능한 경우 최적화 된 알고리즘이 사용됩니다.
답변
함수가 복잡해질수록 (즉, numpy
내부로 이동할 수 있는 것이 적을수록 ) 성능이 크게 다르지 않다는 것을 더 많이 알 수 있습니다. 예를 들면 :
name_series = pd.Series(np.random.choice(['adam', 'chang', 'eliza', 'odom'], replace=True, size=100000))
def parse_name(name):
if name.lower().startswith('a'):
return 'A'
elif name.lower().startswith('e'):
return 'E'
elif name.lower().startswith('i'):
return 'I'
elif name.lower().startswith('o'):
return 'O'
elif name.lower().startswith('u'):
return 'U'
return name
parse_name_vec = np.vectorize(parse_name)
몇 가지 타이밍 수행 :
적용 사용
%timeit name_series.apply(parse_name)
결과 :
76.2 ms ± 626 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
사용 np.vectorize
%timeit parse_name_vec(name_series)
결과 :
77.3 ms ± 216 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
Numpy는 파이썬 함수를 numpy로 바꾸려고합니다. ufunc
호출 할 때 객체 합니다 np.vectorize
. 이것이 어떻게 작동하는지, 나는 실제로 알지 못합니다. ATM을 사용하는 것보다 numpy의 내부를 더 많이 파헤쳐 야합니다. 즉, 여기에서이 문자열 기반 함수보다 단순히 숫자 함수에서 더 나은 작업을 수행하는 것 같습니다.
크기를 최대 1,000,000까지 크 랭킹 :
name_series = pd.Series(np.random.choice(['adam', 'chang', 'eliza', 'odom'], replace=True, size=1000000))
apply
%timeit name_series.apply(parse_name)
결과 :
769 ms ± 5.88 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
np.vectorize
%timeit parse_name_vec(name_series)
결과 :
794 ms ± 4.85 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
더 나은 ( 벡터화 ) 방법 np.select
:
cases = [
name_series.str.lower().str.startswith('a'), name_series.str.lower().str.startswith('e'),
name_series.str.lower().str.startswith('i'), name_series.str.lower().str.startswith('o'),
name_series.str.lower().str.startswith('u')
]
replacements = 'A E I O U'.split()
타이밍 :
%timeit np.select(cases, replacements, default=name_series)
결과 :
67.2 ms ± 683 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
답변
저는 파이썬을 처음 사용합니다. 그러나 아래 예제에서 ‘적용’은 ‘벡터화’보다 빠르게 작동하는 것 같거나 뭔가 빠졌습니다.
import numpy as np
import pandas as pd
B = np.random.rand(1000,1000)
fn = np.vectorize(lambda l: 1/(1-np.exp(-l)))
print(fn(B))
B = pd.DataFrame(np.random.rand(1000,1000))
fn = lambda l: 1/(1-np.exp(-l))
print(B.apply(fn))