팬더를 코드에 적용하려면 언제 사용해야 합니까?
는 Pandas 된 스택 오버플로 에를 과 관련된 오버플로에 .apply
는 또한 에서 "라고 댓글을 을 본 적이 " 라고 " 에서.apply
속도가 느리기 때문에 피해야 합니다."
는 에 대해 읽었습니다.apply
느립니다.또한 문서에서 다음과 같은 내용의 면책 조항을 본 적이 있습니다.apply
단순히 UDF 통과를 위한 편의 기능입니다(지금은 찾을 수 없는 것 같습니다).서,서.apply
가능하면 피해야 합니다.이 는 다음과 합니다.됩니다.
- 한다면
apply
너무 나쁘네요, 그럼 왜 API에 있는 건가요? - 를 로 은 언제입니까?
apply
- ? - 어떤 한 적이 ?
apply
(다른 가능한 해결책보다) 좋습니까?
apply
,
우리는 OP에 있는 질문들을 하나씩 해결하는 것으로 시작합니다.
"만약에
apply
그렇게나 안 좋은데 왜 API에 있는 거죠?"
DataFrame.apply
는 DataFrame 및 Series 객체에 각각 정의된 편의 함수입니다.apply
에서는 DataFrame에 변환/집성을 적용하는 모든 사용자 정의 함수를 허용합니다.apply
현존하는 판다가 할 수 없는 것은 무엇이든 할 수 있는 효과적인 은빛 총알입니다.
중 apply
할 수 있는 작업:
- DataFrame 또는 Series에서 사용자 정의 함수 실행
- ()
axis=1
열 ()axis=0
) 데이터 프레임 상에서 - 함수를 적용하는 동안 인덱스 정렬 수행
- 사용자 우리는 합니다(합니다)를 합니다).
agg
아니면transform
이 우) - 요소 단위 변환 수행
- ( ) ).
result_type
논변). - 사용자 정의 함수에 전달할 위치/키워드 인수를 수락합니다.
...다른 것들 중에.자세한 내용은 설명서의 행 또는 열 단위 함수 응용프로그램을 참조하십시오.
이러한 을 모두 , , 는 요?apply
나쁘다고요? 왜냐하면 느리기 때문입니다.팬더는 함수의 특성에 대해 가정하지 않으므로 필요에 따라 각 행/열에 함수를 반복적으로 적용합니다.또한 위의 모든 상황을 처리한다는 것은apply
반복할 때마다 몇 가지 주요 오버헤드가 발생합니다.더,apply
메모리를 훨씬 더 많이 소비하기 때문에 메모리 제한 응용 프로그램의 어려움이 있습니다.
하는 .apply
를 사용하는 것이 적절합니다(아래의 자세한 내용).사용해야 하는지 확실하지 않다면 사용하지 않는 것이 좋습니다.
다음 문제를 다루도록 하겠습니다.
해야 ?
apply
"?"
다른 말로 하자면, 다음과 같은 몇 가지 일반적인 상황이 있습니다.apply
.
숫자 데이터
숫자 데이터를 사용하는 경우 이미 벡터화된 사이톤 함수가 실행되어 작업하려는 작업을 수행할 수 있습니다(실행되지 않은 경우 스택 오버플로에서 질문을 하거나 GitHub에서 기능 요청을 여십시오).
합니다의 합니다.apply
간단한 추가 작업에 사용할 수 있습니다.
df = pd.DataFrame({"A": [9, 4, 2, 1], "B": [12, 7, 5, 4]})
df
A B
0 9 12
1 4 7
2 2 5
3 1 4
<!- ->
df.apply(np.sum)
A 16
B 28
dtype: int64
df.sum()
A 16
B 28
dtype: int64
성능 면에서는 비교 대상이 없으며, 시톤화된 동급 제품이 훨씬 빠릅니다.장난감 데이터에도 차이가 분명하기 때문에 그래프가 필요 없습니다.
%timeit df.apply(np.sum)
%timeit df.sum()
2.22 ms ± 41.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
471 µs ± 8.16 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
를 통해 할 수 있도록 raw
논쟁은 여전히 두 배나 느립니다.
%timeit df.apply(np.sum, raw=True)
840 µs ± 691 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
또 다른 예:
df.apply(lambda x: x.max() - x.min())
A 8
B 8
dtype: int64
df.max() - df.min()
A 8
B 8
dtype: int64
%timeit df.apply(lambda x: x.max() - x.min())
%timeit df.max() - df.min()
2.43 ms ± 450 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
1.23 ms ± 14.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
일반적으로 가능한 경우 벡터화된 대안을 찾습니다.
문자열/Regex
팬더는 대부분의 상황에서 "벡터화된" 스트링 기능을 제공하지만, 그러한 기능이 없는 경우는 드물다...말하자면 "apply"입니다.
일반적인 문제는 열에 있는 값이 같은 행의 다른 열에 있는지 확인하는 것입니다.
df = pd.DataFrame({
'Name': ['mickey', 'donald', 'minnie'],
'Title': ['wonderland', "welcome to donald's castle", 'Minnie mouse clubhouse'],
'Value': [20, 10, 86]})
df
Name Value Title
0 mickey 20 wonderland
1 donald 10 welcome to donald's castle
2 minnie 86 Minnie mouse clubhouse
"donald"와 "minnie"가 각각의 "Title" 열에 있기 때문에 행 두 번째와 세 번째 행을 반환해야 합니다.
apply를 사용하면 다음을 사용합니다.
df.apply(lambda x: x['Name'].lower() in x['Title'].lower(), axis=1)
0 False
1 True
2 True
dtype: bool
df[df.apply(lambda x: x['Name'].lower() in x['Title'].lower(), axis=1)]
Name Title Value
1 donald welcome to donald's castle 10
2 minnie Minnie mouse clubhouse 86
그러나 목록 이해를 사용하면 더 나은 해결책이 있습니다.
df[[y.lower() in x.lower() for x, y in zip(df['Title'], df['Name'])]]
Name Title Value
1 donald welcome to donald's castle 10
2 minnie Minnie mouse clubhouse 86
<!- ->
%timeit df[df.apply(lambda x: x['Name'].lower() in x['Title'].lower(), axis=1)]
%timeit df[[y.lower() in x.lower() for x, y in zip(df['Title'], df['Name'])]]
2.85 ms ± 38.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
788 µs ± 16.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
이 보다 더 입니다.apply
오버헤드가 해야 하는 한 다음 내부의 할 수 NaN과 잘못된 dtype을 처리해야 하는 경우 사용자 지정 함수를 사용하여 이를 바탕으로 목록 이해 내부의 인수로 호출할 수 있습니다.
목록 이해가 좋은 옵션으로 간주되는 경우에 대한 자세한 내용은 다음 항목을 참조하십시오.팬더의 포루프는 정말 나쁜가요? 내가 언제 신경 써야 합니까?
날짜 및 날짜 시간 연산에도 벡터화된 버전이 있습니다.그러니까, 예를 들어, 당신은 당신이 선호하는 것은pd.to_datetime(df['date'])
,df['date'].apply(pd.to_datetime)
.자세한 내용은 문서를 참조하십시오.
흔한 함정: 폭발하는 목록 기둥
s = pd.Series([[1, 2]] * 3)
s
0 [1, 2]
1 [1, 2]
2 [1, 2]
dtype: object
사람들은 사용하고 싶은 유혹을 받습니다.apply(pd.Series)
. 이것은 성능 면에서 끔찍합니다.
s.apply(pd.Series)
0 1
0 1 2
1 1 2
2 1 2
더 좋은 방법은 열을 나열하여 pd에게 전달하는 것입니다.데이터 프레임.
pd.DataFrame(s.tolist())
0 1
0 1 2
1 1 2
2 1 2
<!- ->
%timeit s.apply(pd.Series)
%timeit pd.DataFrame(s.tolist())
2.65 ms ± 294 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
816 µs ± 40.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
마지막으로.
'을 할 ?
apply
좋습니까?"
적용은 편의기능이기 때문에 오버헤드가 용서할 수 있을 정도로 무시할 수 있는 상황이 발생합니다.이것은 실제로 함수를 몇 번 호출하느냐에 달려 있습니다.
프레임은
문자열 연산을 여러 열에 적용하려면 어떻게 해야 합니까?여러 열을 날짜 시간으로 변환하려면 어떻게 해야 합니까?이러한 함수는 영상 시리즈에 대해서만 벡터화되므로 변환/연산하려는 각 열에 적용해야 합니다.
df = pd.DataFrame(
pd.date_range('2018-12-31','2019-01-31', freq='2D').date.astype(str).reshape(-1, 2),
columns=['date1', 'date2'])
df
date1 date2
0 2018-12-31 2019-01-02
1 2019-01-04 2019-01-06
2 2019-01-08 2019-01-10
3 2019-01-12 2019-01-14
4 2019-01-16 2019-01-18
5 2019-01-20 2019-01-22
6 2019-01-24 2019-01-26
7 2019-01-28 2019-01-30
df.dtypes
date1 object
date2 object
dtype: object
은 입니다에 입니다.apply
:
df.apply(pd.to_datetime, errors='coerce').dtypes
date1 datetime64[ns]
date2 datetime64[ns]
dtype: object
에도 가 있다는 하시기 바랍니다.stack
, 아니면 그냥 명시적인 루프를 사용합니다.이 다를 .apply
그 할 수 작습니다
%timeit df.apply(pd.to_datetime, errors='coerce')
%timeit pd.to_datetime(df.stack(), errors='coerce').unstack()
%timeit pd.concat([pd.to_datetime(df[c], errors='coerce') for c in df], axis=1)
%timeit for c in df.columns: df[c] = pd.to_datetime(df[c], errors='coerce')
5.49 ms ± 247 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
3.94 ms ± 48.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
3.16 ms ± 216 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.41 ms ± 1.71 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
문자열 작업이나 범주로 변환과 같은 다른 작업에도 유사한 경우를 만들 수 있습니다.
u = df.apply(lambda x: x.str.contains(...))
v = df.apply(lambda x: x.astype(category))
v/s
u = pd.concat([df[c].str.contains(...) for c in df], axis=1)
v = df.copy()
for c in df:
v[c] = df[c].astype(category)
그래서..
를 로 하는 중str
:astype
sapply
이것은 API의 특이점처럼 보입니다.으로.apply
시리즈의 다를 가 가능하며 astype
.
import perfplot
perfplot.show(
setup=lambda n: pd.Series(np.random.randint(0, n, n)),
kernels=[
lambda s: s.astype(str),
lambda s: s.apply(str)
],
labels=['astype', 'apply'],
n_range=[2**k for k in range(1, 20)],
xlabel='N',
logx=True,
logy=True,
equality_check=lambda x, y: (x == y).all())
로, 을 봅니다.astype
도 하고 약간 .apply
이 있습니다 따라서 이것은 테스트의 데이터가 정수형이라는 사실과 관련이 있습니다.
GroupBy
GroupBy.apply
된 바는 만,GroupBy.apply
또한 입니다에서 하는 모든 할 수 입니다.GroupBy
함수는 그렇지 않습니다.
일반적으로 필요한 한 가지 사항은 GroupBy를 수행한 다음 "지연 합"과 같은 두 가지 주요 작업을 수행하는 것입니다.
df = pd.DataFrame({"A": list('aabcccddee'), "B": [12, 7, 5, 4, 5, 4, 3, 2, 1, 10]})
df
A B
0 a 12
1 a 7
2 b 5
3 c 4
4 c 5
5 c 4
6 d 3
7 d 2
8 e 1
9 e 10
<!- ->
여기에 전화로 두 개의 그룹을 연속적으로 연결해야 합니다.
df.groupby('A').B.cumsum().groupby(df.A).shift()
0 NaN
1 12.0
2 NaN
3 NaN
4 4.0
5 9.0
6 NaN
7 3.0
8 NaN
9 1.0
Name: B, dtype: float64
으로.apply
할 수 .
df.groupby('A').B.apply(lambda x: x.cumsum().shift())
0 NaN
1 12.0
2 NaN
3 NaN
4 4.0
5 9.0
6 NaN
7 3.0
8 NaN
9 1.0
Name: B, dtype: float64
성능은 데이터에 따라 달라지기 때문에 정량화하기가 매우 어렵습니다.으로.apply
입니다에가를 입니다.groupby
다면))groupby
(또한 꽤 비쌉니다.)
기타 주의사항
사항 , 에 도 언급할 가 있습니다.apply
첫 번째 행(또는 열)에서 두 번 작동합니다.이 작업은 기능에 부작용이 있는지 여부를 확인하기 위해 수행됩니다.아니라면,apply
결과를 평가하기 위해 빠른 경로를 사용할 수 있습니다. 그렇지 않으면 느린 구현으로 되돌아갑니다.
df = pd.DataFrame({
'A': [1, 2],
'B': ['x', 'y']
})
def func(x):
print(x['A'])
return x
df.apply(func, axis=1)
# 1
# 1
# 2
A B
0 1 x
1 2 y
이러한 동작은 다음에서도 볼 수 있습니다.GroupBy.apply
팬더 버전 <0.25>에서 (0.25로 수정되었습니다, 자세한 내용은 여기를 참조하십시오.)
전부는 아닙니다apply
같은
는 합니다를 해야 할 .apply
1.1녹색은 효율적일 가능성이 있음을 의미하며 빨간색은 피합니다.
이 중 일부는 직관적입니다.pd.Series.apply
는 파이썬 의 행 이며, dittopd.DataFrame.apply
단위로axis=1
하고 광범위합니다. 이들의 오용은 다양하고 광범위합니다.다른 게시물은 그들을 좀 더 깊이 다루고 있습니다. 또는정구)와 같은 를 사용하는 예).pd.DataFrame
자 예:함)apply(pd.Series)
).
pd.DataFrame.apply
행 위 ,raw=True
종종 (가능한 경우) 도움이 됩니다.이 단계에서는 보통이 더 나은 선택입니다.
GroupBy.apply
됨:
groupby
을 피하기 위한 apply
성능에 타격을 줄 수 있습니다.GroupBy.apply
사용자 지정 함수에서 사용하는 메서드 자체가 벡터화되어 있다면 여기서는 일반적으로 괜찮습니다.적용하고자 하는 그룹별 집계에 대한 고유 Pandas 방법이 없는 경우가 있습니다.이 는 다음과 같이 설명합니다.apply
사용자 지정 기능을 사용하면 여전히 합리적인 성능을 제공할 수 있습니다.
pd.DataFrame.apply
단위:위:
pd.DataFrame.apply
단위로axis=0
)는 흥미로운 사례입니다.행의 수가 적은 경우와 열의 수가 많은 경우에는 거의 항상 비용이 많이 듭니다.열에 상대적으로 많은 수의 행의 경우, 더 일반적인 경우, 때때로 다음을 사용하여 상당한 성능 개선을 볼 수 있습니다.apply
:
# Python 3.7, Pandas 0.23.4
np.random.seed(0)
df = pd.DataFrame(np.random.random((10**7, 3))) # Scenario_1, many rows
df = pd.DataFrame(np.random.random((10**4, 10**3))) # Scenario_2, many columns
# Scenario_1 | Scenario_2
%timeit df.sum() # 800 ms | 109 ms
%timeit df.apply(pd.Series.sum) # 568 ms | 325 ms
%timeit df.max() - df.min() # 1.63 s | 314 ms
%timeit df.apply(lambda x: x.max() - x.min()) # 838 ms | 473 ms
%timeit df.mean() # 108 ms | 94.4 ms
%timeit df.apply(pd.Series.mean) # 276 ms | 233 ms
1 예외도 있지만, 이러한 예외는 일반적으로 미미하거나 흔하지 않습니다.몇 가지 예를 들 수 있습니다.
df['col'].apply(str)
뛰어날 수 .df['col'].astype(str)
.df.apply(pd.to_datetime)
다에 이 잘for
리
위해서axis=1
행 즉,수)다 할 수 .apply
. 저는 이게 왜 그렇지 않은지 궁금합니다.pandas
행동., (복합 인덱스를 사용하여 테스트되지 않았지만, 보다 훨씬 빠른 것으로 보입니다)보다 빠른 .apply
)
def faster_df_apply(df, func):
cols = list(df.columns)
data, index = [], []
for row in df.itertuples(index=True):
row_dict = {f:v for f,v in zip(cols, row[1:])}
data.append(func(row_dict))
index.append(row[0])
return pd.Series(data, index=index)
어떤 한 적이 ?apply
좋아요? 네, 가끔.
작업: 유니코드 문자열을 해독합니다.
import numpy as np
import pandas as pd
import unidecode
s = pd.Series(['mañana','Ceñía'])
s.head()
0 mañana
1 Ceñía
s.apply(unidecode.unidecode)
0 manana
1 Cenia
다의 하지 않았습니다.apply
만 해도, NumPy
할 수 다다의 가 될 .pandas apply
하지만 @jpp가 알려준 덕분에 평범한 ollist 이해를 잊고 있었습니다.
언급URL : https://stackoverflow.com/questions/54432583/when-should-i-not-want-to-use-pandas-apply-in-my-code
'source' 카테고리의 다른 글
PowerShell 오류 메시지에서 touch 명령으로 새 파일 만들기 (0) | 2023.09.28 |
---|---|
jQuery UI 대화상자에서 첫번째 텍스트 상자에 포커스를 설정하지 못하도록 합니다. (0) | 2023.09.28 |
우커머스에서 행당 상품 수 변경 (0) | 2023.09.28 |
저장 프로시저는 두 행을 업데이트합니다. (0) | 2023.09.28 |
Oracle to_date 함수 테스트 (0) | 2023.09.28 |