[python] 그룹 객체에 변형 대 적용

다음 데이터 프레임을 고려하십시오.

     A      B         C         D
0  foo    one  0.162003  0.087469
1  bar    one -1.156319 -1.526272
2  foo    two  0.833892 -1.666304
3  bar  three -2.026673 -0.322057
4  foo    two  0.411452 -0.954371
5  bar    two  0.765878 -0.095968
6  foo    one -0.654890  0.678091
7  foo  three -1.789842 -1.130922

다음 명령이 작동합니다.

> df.groupby('A').apply(lambda x: (x['C'] - x['D']))
> df.groupby('A').apply(lambda x: (x['C'] - x['D']).mean())

그러나 다음 작업은 없습니다.

> df.groupby('A').transform(lambda x: (x['C'] - x['D']))
ValueError: could not broadcast input array from shape (5) into shape (5,3)

> df.groupby('A').transform(lambda x: (x['C'] - x['D']).mean())
 TypeError: cannot concatenate a non-NDFrame object

왜? 문서의 예제transform 는 그룹 을 호출 하면 행 단위 작업 처리를 수행 할 수 있다고 제안합니다 .

# Note that the following suggests row-wise operation (x.mean is the column mean)
zscore = lambda x: (x - x.mean()) / x.std()
transformed = ts.groupby(key).transform(zscore)

다시 말해, 변환은 본질적으로 특정 유형의 적용 (집계되지 않는 적용)이라고 생각했습니다. 내가 어디 틀렸어?

참고로 아래는 위의 원래 데이터 프레임 구성입니다.

df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar',
                          'foo', 'bar', 'foo', 'foo'],
                   'B' : ['one', 'one', 'two', 'three',
                         'two', 'two', 'one', 'three'],
                   'C' : randn(8), 'D' : randn(8)})



답변

두 가지 주요 차이점 사이 applytransform

transformapplygroupby 메소드 에는 두 가지 주요 차이점이 있습니다.

  • 입력:
    • apply암시 적으로 각 그룹의 모든 열을 DataFrame 으로 사용자 지정 함수에 전달합니다.
    • 상태 transformA와 개별적으로 각 그룹에 대해 각각의 열 전달 시리즈 맞춤 기능.
  • 산출:
    • 전달 된 사용자 지정 함수 apply는 스칼라 또는 Series 또는 DataFrame (또는 numpy 배열 또는 목록)을 반환수 있습니다 .
    • 전달 된 사용자 정의 함수 는 그룹과 길이가 같은 transform시퀀스 (1 차원 시리즈, 배열 또는 목록)를 반환해야합니다 .

따라서 transform한 번에 하나의 Series에서만 apply작동하고 전체 DataFrame에서 한 번에 작동합니다.

사용자 정의 기능 검사

apply또는에 전달 된 사용자 정의 함수에 대한 입력을 검사하는 데 도움이 될 수 있습니다 transform.

샘플 데이터를 만들고 그룹을 검사하여 내가 말하는 것을 볼 수 있습니다.

import pandas as pd
import numpy as np
df = pd.DataFrame({'State':['Texas', 'Texas', 'Florida', 'Florida'],
                   'a':[4,5,1,3], 'b':[6,10,3,11]})

     State  a   b
0    Texas  4   6
1    Texas  5  10
2  Florida  1   3
3  Florida  3  11

암시 적으로 전달 된 객체의 유형을 인쇄 한 다음 오류를 발생시켜 실행을 중지 할 수있는 간단한 사용자 지정 함수를 만들어 보겠습니다.

def inspect(x):
    print(type(x))
    raise

이제이 함수를 groupby applytransform메소드 모두에 전달하여 어떤 객체가 전달되는지 확인하십시오.

df.groupby('State').apply(inspect)

<class 'pandas.core.frame.DataFrame'>
<class 'pandas.core.frame.DataFrame'>
RuntimeError

보시다시피 DataFrame은 inspect함수 로 전달됩니다 . 왜 DataFrame 유형이 두 번 인쇄되었는지 궁금 할 것입니다. 팬더는 첫 번째 그룹을 두 번 실행합니다. 계산을 완료하는 빠른 방법이 있는지 여부를 판별하기 위해이를 수행합니다. 걱정할 필요가없는 사소한 세부 사항입니다.

자, 같은 작업을하겠습니다 transform

df.groupby('State').transform(inspect)
<class 'pandas.core.series.Series'>
<class 'pandas.core.series.Series'>
RuntimeError

완전히 다른 Pandas 객체 인 Series를 전달합니다.

따라서 transform한 번에 하나의 시리즈로만 작업 할 수 있습니다. 그것은 것입니다 하지 가 동시에 두 개의 열을 행동 불가능. 따라서 사용자 정의 함수 내부 a에서 열 을 빼려고 b하면에 오류가 발생합니다 transform. 아래를보십시오 :

def subtract_two(x):
    return x['a'] - x['b']

df.groupby('State').transform(subtract_two)
KeyError: ('a', 'occurred at index a')

팬더가 a존재하지 않는 Series 인덱스를 찾으려고 할 때 KeyError 가 발생합니다. apply전체 DataFrame이 있으므로이 작업을 완료 할 수 있습니다 .

df.groupby('State').apply(subtract_two)

State
Florida  2   -2
         3   -8
Texas    0   -2
         1   -5
dtype: int64

출력은 Series이며 원래 인덱스가 유지되므로 약간 혼란 스럽지만 모든 열에 액세스 할 수 있습니다.


전달 된 팬더 객체 표시

사용자 정의 기능 내에서 전체 팬더 객체를 표시하는 데 도움이되므로 작동중인 것을 정확하게 볼 수 있습니다. 당신이 사용할 수있는 print사용에처럼에 의해 문을 display로부터 기능을 IPython.displayDataFrames가 멋지게 jupyter 노트북에 HTML에 출력 얻을 그래서 모듈 :

from IPython.display import display
def subtract_two(x):
    display(x)
    return x['a'] - x['b']

스크린 샷 :
여기에 이미지 설명을 입력하십시오


변환은 그룹과 동일한 크기의 단일 차원 시퀀스를 반환해야합니다.

다른 차이점은 transform그룹과 동일한 크기의 단일 차원 시퀀스를 반환해야 한다는 것 입니다. 이 특정 인스턴스에서 각 그룹에는 두 개의 행이 있으므로 transform두 개의 행 시퀀스를 리턴해야합니다. 그렇지 않으면 오류가 발생합니다.

def return_three(x):
    return np.array([1, 2, 3])

df.groupby('State').transform(return_three)
ValueError: transform must return a scalar value for each group

오류 메시지는 실제로 문제를 설명하지 않습니다. 그룹과 동일한 길이의 시퀀스를 반환해야합니다. 따라서 이와 같은 기능이 작동합니다.

def rand_group_len(x):
    return np.random.rand(len(x))

df.groupby('State').transform(rand_group_len)

          a         b
0  0.962070  0.151440
1  0.440956  0.782176
2  0.642218  0.483257
3  0.056047  0.238208

단일 스칼라 객체를 반환해도 작동합니다. transform

사용자 정의 함수에서 단일 스칼라 만 반환 transform하면 그룹의 각 행에 대해 스칼라를 사용합니다.

def group_sum(x):
    return x.sum()

df.groupby('State').transform(group_sum)

   a   b
0  9  16
1  9  16
2  4  14
3  4  14


답변

.transform작업과 비슷하게 혼란스러워 .apply하면서 문제에 대한 약간의 답변을 발견했습니다. 예를 들어이 답변 은 매우 도움이되었습니다.

지금까지의 테이크 아웃 은 서로 분리되어.transform 작동 (또는 Series열) 할 것 입니다 . 이것이 의미하는 것은 마지막 두 번의 호출에서입니다.

df.groupby('A').transform(lambda x: (x['C'] - x['D']))
df.groupby('A').transform(lambda x: (x['C'] - x['D']).mean())

.transform두 열에서 값을 가져 오라고 요청 했지만 실제로는 두 열을 동시에 볼 수 없습니다. transform데이터 프레임 열을 하나씩보고 반복되는 반복되는 스칼라로 구성된 시리즈 (또는 시리즈 그룹)를 다시 반환 len(input_column)합니다.

사용해야이 스칼라이므로 .transform하게는 Series일부 감소 함수의 결과는 입력에인가 Series(한 번에 단지 하나 시리즈 / 열).

데이터 프레임에서이 예제를 고려하십시오.

zscore = lambda x: (x - x.mean()) / x.std() # Note that it does not reference anything outside of 'x' and for transform 'x' is one column.
df.groupby('A').transform(zscore)

산출 할 것이다 :

       C      D
0  0.989  0.128
1 -0.478  0.489
2  0.889 -0.589
3 -0.671 -1.150
4  0.034 -0.285
5  1.149  0.662
6 -1.404 -0.907
7 -0.509  1.653

한 번에 하나의 열에서만 사용하는 것과 정확히 동일합니다.

df.groupby('A')['C'].transform(zscore)

굽힐 수 있는:

0    0.989
1   -0.478
2    0.889
3   -0.671
4    0.034
5    1.149
6   -1.404
7   -0.509

참고 것을 .apply마지막 예에서 ( df.groupby('A')['C'].apply(zscore)) 정확히 같은 방식으로 작동합니다,하지만 당신은 dataframe에 그것을 사용하려고한다면 실패 할 것이다 :

df.groupby('A').apply(zscore)

오류를 준다 :

ValueError: operands could not be broadcast together with shapes (6,) (2,)

다른 .transform유용한 곳은 어디 입니까? 가장 간단한 경우는 축소 함수의 결과를 원래 데이터 프레임에 다시 할당하는 것입니다.

df['sum_C'] = df.groupby('A')['C'].transform(sum)
df.sort('A') # to clearly see the scalar ('sum') applies to the whole column of the group

굽힐 수 있는:

     A      B      C      D  sum_C
1  bar    one  1.998  0.593  3.973
3  bar  three  1.287 -0.639  3.973
5  bar    two  0.687 -1.027  3.973
4  foo    two  0.205  1.274  4.373
2  foo    two  0.128  0.924  4.373
6  foo    one  2.113 -0.516  4.373
7  foo  three  0.657 -1.179  4.373
0  foo    one  1.270  0.201  4.373

와 같은 시도는 .apply줄 것이라고 NaNs에서 sum_C. 때문에 .applyA가 감소 반환 Series이 다시 방송하는 방법을 알고하지 않는 :

df.groupby('A')['C'].apply(sum)

기부:

A
bar    3.973
foo    4.373

.transform데이터를 필터링하는 데 사용되는 경우도 있습니다 .

df[df.groupby(['B'])['D'].transform(sum) < -1]

     A      B      C      D
3  bar  three  1.287 -0.639
7  foo  three  0.657 -1.179

이것이 좀 더 명확 해지기를 바랍니다.


답변

차이점을 설명하기 위해 매우 간단한 스 니펫을 사용하겠습니다.

test = pd.DataFrame({'id':[1,2,3,1,2,3,1,2,3], 'price':[1,2,3,2,3,1,3,1,2]})
grouping = test.groupby('id')['price']

DataFrame은 다음과 같습니다.

    id  price
0   1   1
1   2   2
2   3   3
3   1   2
4   2   3
5   3   1
6   1   3
7   2   1
8   3   2   

이 표에는 3 개의 고객 ID가 있으며, 각 고객은 3 번의 거래를하고 매번 1,2,3 달러를 지불했습니다.

이제 각 고객의 최소 결제 금액을 찾고 싶습니다. 이를 수행하는 두 가지 방법이 있습니다.

  1. 사용 apply:

    grouping.min ()

리턴은 다음과 같습니다.

id
1    1
2    1
3    1
Name: price, dtype: int64

pandas.core.series.Series # return type
Int64Index([1, 2, 3], dtype='int64', name='id') #The returned Series' index
# lenght is 3
  1. 사용 transform:

    그룹화 변환 (분)

리턴은 다음과 같습니다.

0    1
1    1
2    1
3    1
4    1
5    1
6    1
7    1
8    1
Name: price, dtype: int64

pandas.core.series.Series # return type
RangeIndex(start=0, stop=9, step=1) # The returned Series' index
# length is 9    

두 방법 모두 Series객체를 반환 하지만 length첫 번째 방법은 3이고 length두 번째 방법은 9입니다.

에 대답 What is the minimum price paid by each customer하려면 apply방법이 더 적합합니다.

에 대답 What is the difference between the amount paid for each transaction vs the minimum payment하려면 다음을 사용하십시오 transform.

test['minimum'] = grouping.transform(min) # ceates an extra column filled with minimum payment
test.price - test.minimum # returns the difference for each row

Apply 여기서는 Series 3 크기를 반환하기 때문에 여기서는 작동하지 않지만 원본 df의 길이는 9입니다. 원본 df에 쉽게 통합 할 수 없습니다.


답변

tmp = df.groupby(['A'])['c'].transform('mean')

처럼

tmp1 = df.groupby(['A']).agg({'c':'mean'})
tmp = df['A'].map(tmp1['c'])

또는

tmp1 = df.groupby(['A'])['c'].mean()
tmp = df['A'].map(tmp1)


답변