source

팬더를 코드에 적용하려면 언제 사용해야 합니까?

manycodes 2023. 9. 28. 08:36
반응형

팬더를 코드에 적용하려면 언제 사용해야 합니까?

는 Pandas 된 스택 오버플로 에를 과 관련된 오버플로에 .apply는 또한 에서 "라고 댓글을 을 본 적이 " 라고 " 에서.apply속도가 느리기 때문에 피해야 합니다."

는 에 대해 읽었습니다.apply느립니다.또한 문서에서 다음과 같은 내용의 면책 조항을 본 적이 있습니다.apply단순히 UDF 통과를 위한 편의 기능입니다(지금은 찾을 수 없는 것 같습니다).서,서.apply가능하면 피해야 합니다.이 는 다음과 합니다.됩니다.

  1. 한다면apply너무 나쁘네요, 그럼 왜 API에 있는 건가요?
  2. 를 로 은 언제입니까?apply- ?
  3. 어떤 한 적이 ?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:astypesapply

이것은 API의 특이점처럼 보입니다.으로.apply 시리즈의 다를 가 가능하며 astype.

enter image description here 그래프는 라이브러리를 사용하여 그려졌습니다.

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 같은

는 합니다를 해야 할 .apply1.1녹색은 효율적일 가능성이 있음을 의미하며 빨간색은 피합니다.

enter image description here

이 중 일부는 직관적입니다.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 예외도 있지만, 이러한 예외는 일반적으로 미미하거나 흔하지 않습니다.몇 가지 예를 들 수 있습니다.

  1. df['col'].apply(str) 뛰어날 수 .df['col'].astype(str).
  2. 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

반응형