달나라 노트

Python Pandas : transform (유사 window function, 집계 결과를 index별로 추가하기) 본문

Python/Python Pandas

Python Pandas : transform (유사 window function, 집계 결과를 index별로 추가하기)

CosmosProject 2023. 1. 31. 02:09
728x90
반응형

 

 

 

transform method는 DataFrame에서 groupby로 집계한 결과를 동일한 index를 가진 행에 넣어서 return해줍니다.

말만 들으면 무슨 소린지 잘 감이 오지 않는데 실제 예시를 봅시다.

 

import pandas as pd

dict_item = {
    'date': [
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103, 20200104
    ],
    'item_id': [
        1, 1, 1,
        2, 2, 2,
        3, 3, 3,
        4, 4, 4, 4
    ],
    'item_name': [
        'a', 'a', 'a',
        'b', 'b', 'b',
        'c', 'c', 'c',
        'd', 'd', 'd', 'd'
    ],
    'price': [
        1000, 1000, 1010,
        2000, 2100, 2050,
        3000, 3100, 2950,
        4000, 3950, 3900, 3980
    ],
    'quantity': [
        100, 105, 98,
        50, 51, 55,
        201, 200, 220,
        30, 40, 38, 50
    ]
}
df_item = pd.DataFrame(dict_item)
print(df_item)



-- Result
        date  item_id item_name  price  quantity
0   20200101        1         a   1000       100
1   20200102        1         a   1000       105
2   20200103        1         a   1010        98
3   20200101        2         b   2000        50
4   20200102        2         b   2100        51
5   20200103        2         b   2050        55
6   20200101        3         c   3000       201
7   20200102        3         c   3100       200
8   20200103        3         c   2950       220
9   20200101        4         d   4000        30
10  20200102        4         d   3950        40
11  20200103        4         d   3900        38
12  20200104        4         d   3980        50

 

먼저 위처럼 테스트용 DataFrame을 만듭니다.

 

 

 

 

 

 

import pandas as pd

dict_item = {
    'date': [
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103, 20200104
    ],
    'item_id': [
        1, 1, 1,
        2, 2, 2,
        3, 3, 3,
        4, 4, 4, 4
    ],
    'item_name': [
        'a', 'a', 'a',
        'b', 'b', 'b',
        'c', 'c', 'c',
        'd', 'd', 'd', 'd'
    ],
    'price': [
        1000, 1000, 1010,
        2000, 2100, 2050,
        3000, 3100, 2950,
        4000, 3950, 3900, 3980
    ],
    'quantity': [
        100, 105, 98,
        50, 51, 55,
        201, 200, 220,
        30, 40, 38, 50
    ]
}
df_item = pd.DataFrame(dict_item)

df_grouped = df_item.groupby(by=['item_id'])[['price']].apply(sum)
print(df_grouped)


-- Result
item_id
1     3010
2     6150
3     9050
4    15830
Name: price, dtype: int64

위 예시는 groupby를 이용해서 동일한 item_id를 가진 행에 대해 price를 합한 결과를 return해줍니다.

 

item_id가 1인 행들의 price 합은 3010이고, item_id가 2인 행들의 price 합은 6150이라는 뜻이죠.

 

여기까진 좋은데 만약에 이 값을 원본 DataFrame에 추가하고싶다면 어떻게 해야할까요?

item_id가 1인 행에 3010이라는 값을 추가하고 item_id가 2인 행에는 6150이라는 값을 추가하고싶은거죠.

 

방법은 여러가지가 있을 수 있는데 그 중 하나는 다음과 같습니다.

 

 

 

 

 

import pandas as pd

dict_item = {
    'date': [
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103, 20200104
    ],
    'item_id': [
        1, 1, 1,
        2, 2, 2,
        3, 3, 3,
        4, 4, 4, 4
    ],
    'item_name': [
        'a', 'a', 'a',
        'b', 'b', 'b',
        'c', 'c', 'c',
        'd', 'd', 'd', 'd'
    ],
    'price': [
        1000, 1000, 1010,
        2000, 2100, 2050,
        3000, 3100, 2950,
        4000, 3950, 3900, 3980
    ],
    'quantity': [
        100, 105, 98,
        50, 51, 55,
        201, 200, 220,
        30, 40, 38, 50
    ]
}
df_item = pd.DataFrame(dict_item)

df_grouped = df_item.groupby(by=['item_id'])[['price']].apply(sum)
df_grouped = df_grouped.reset_index(drop=False, inplace=False)
print(df_grouped)
df_grouped = df_grouped.rename(columns={'price': 'price_sum'})
print(df_grouped)


df_item = pd.merge(df_item, df_grouped,
                   how='left',
                   left_on=['item_id'], right_on=['item_id'])
print(df_item)



-- Result
   item_id  price
0        1   3010
1        2   6150
2        3   9050
3        4  15830

   item_id  price_sum
0        1       3010
1        2       6150
2        3       9050
3        4      15830

        date  item_id item_name  price  quantity  price_sum
0   20200101        1         a   1000       100       3010
1   20200102        1         a   1000       105       3010
2   20200103        1         a   1010        98       3010
3   20200101        2         b   2000        50       6150
4   20200102        2         b   2100        51       6150
5   20200103        2         b   2050        55       6150
6   20200101        3         c   3000       201       9050
7   20200102        3         c   3100       200       9050
8   20200103        3         c   2950       220       9050
9   20200101        4         d   4000        30      15830
10  20200102        4         d   3950        40      15830
11  20200103        4         d   3900        38      15830
12  20200104        4         d   3980        50      15830

방식은 간단합니다.

 

 

 

 

df_grouped = df_item.groupby(by=['item_id'])[['price']].apply(sum)
df_grouped = df_grouped.reset_index(drop=False, inplace=False)
print(df_grouped)
df_grouped = df_grouped.rename(columns={'price': 'price_sum'})
print(df_grouped)

먼저 item_id를 기준으로 price 컬럼의 합을 구하도록 groupby를 하고 이로인해 생성된 DataFrame을 만듭니다.

그리고 reset_index method를 이용해서 index로 있었던 item_id를 하나의 컬럼으로 만듭니다.

(price 컬럼의 이름은 기존 DataFrame에 있는 price 컬럼과 구분하기 위해 price_sum으로 이름을 바꿨습니다.)

 

 

   item_id  price
0        1   3010
1        2   6150
2        3   9050
3        4  15830

   item_id  price_sum
0        1       3010
1        2       6150
2        3       9050
3        4      15830

그러면 위처럼 결과가 나옵니다.

 

 

df_item = pd.merge(df_item, df_grouped,
                   how='left',
                   left_on=['item_id'], right_on=['item_id'])

그리고 나서 위처럼 기존 DataFrame(df_item)에 groupby된 DataFrame(df_grouped)을 merge하면 됩니다.

 

 

 

        date  item_id item_name  price  quantity  price_sum
0   20200101        1         a   1000       100       3010
1   20200102        1         a   1000       105       3010
2   20200103        1         a   1010        98       3010
3   20200101        2         b   2000        50       6150
4   20200102        2         b   2100        51       6150
5   20200103        2         b   2050        55       6150
6   20200101        3         c   3000       201       9050
7   20200102        3         c   3100       200       9050
8   20200103        3         c   2950       220       9050
9   20200101        4         d   4000        30      15830
10  20200102        4         d   3950        40      15830
11  20200103        4         d   3900        38      15830
12  20200104        4         d   3980        50      15830

그러면 위같은 결과가 나옵니다.

저희가 원하던대로 item_id별로 group화 되어 합쳐진 price값이 price_sum이라는 컬럼에 들어갔죠.

마치 sql의 window function을 사용하는 것 같은 결과가 나왔습니다.

 

 

지금까지 좀 복잡한 길을 돌아왔는데 이와 동일한 과정을 tranform method를 이용하면 아주 간단하게 실행할 수 있습니다.

 

아래 코드에서 먼저 transform의 사용법과 어떤 식으로 data를 return하는지를 봅시다.

 

import pandas as pd

dict_item = {
    'date': [
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103, 20200104
    ],
    'item_id': [
        1, 1, 1,
        2, 2, 2,
        3, 3, 3,
        4, 4, 4, 4
    ],
    'item_name': [
        'a', 'a', 'a',
        'b', 'b', 'b',
        'c', 'c', 'c',
        'd', 'd', 'd', 'd'
    ],
    'price': [
        1000, 1000, 1010,
        2000, 2100, 2050,
        3000, 3100, 2950,
        4000, 3950, 3900, 3980
    ],
    'quantity': [
        100, 105, 98,
        50, 51, 55,
        201, 200, 220,
        30, 40, 38, 50
    ]
}
df_item = pd.DataFrame(dict_item)

df_temp = df_item.groupby(by=['item_id'])[['price']].transform('sum')
print(df_temp)



-- Result
0      3010
1      3010
2      3010
3      6150
4      6150
5      6150
6      9050
7      9050
8      9050
9     15830
10    15830
11    15830
12    15830
Name: price, dtype: int64

 

일단 transform은 groupby method와 같이 사용합니다.
그리고 위 예시에서 transform으로 인해 return되는 결과는 groupby(~~)[['price']] 로 대괄호를 2개 사용했기 때문에 DataFrame으로 return된다는 것에 주의합시다.

 

   item_id  price
0        1   3010
1        2   6150
2        3   9050
3        4  15830

그리고 결과를 보면 이전에 groupby만 썼을 때의 결과가 마치 각각의 행으로 펼쳐져있는 것 같죠.

 

 

df_temp = df_item.groupby(by=['item_id'])[['price']].transform('sum')

보면 groupby method를 다 쓰고 나서, apply(sum) 등 aggregate할 함수를 적어줘야는게 일반적이나,

transform을 사용하기 위해선 transform을 적어줍니다.

그리고 transform의 parameter로서 내가 원하는 aggregate 함수를 텍스트의 형태로 적어줍니다.

중요한건 sum을 텍스트의 형태로 적어줘야 한다는 것입니다.

 

.transform('sum') -> 당연히 groupby의 aggregate 함수로 sum을 사용하겠다는 것이죠.

평균을 사용하려면 mean을 쓰면 됩니다. -> transform('mean')

 

 

-- Result
0      3010
1      3010
2      3010
3      6150
4      6150
5      6150
6      9050
7      9050
8      9050
9     15830
10    15830
11    15830
12    15830
Name: price, dtype: int64

이제 결과를 봅시다.

 

일단 행의 개수를 보면 이전에 groupby를 한 결과와는 다릅니다.

 

   item_id  price
0        1   3010
1        2   6150
2        3   9050
3        4  15830

item_id는 1, 2, 3, 4 총 4개가 있으므로 groupby의 결과는 위처럼 4개 행이 나오는게 정상입니다.

 

근데 transform은 위처럼 groupby를 한 후, 그 결과를 원본 DataFrame을 기준으로 동일한 item_id를 가진 행마다 groupby의 결과를 넣어줍니다.

따라서 transform의 결과는 원본 DataFrame과 동일한 개수의 행을 가지게됩니다.

 

이를 이용하면 그냥 transform의 결과를 원본 DataFrame의 새로운 컬럼에 넣어주면 groupby의 결과를 원본 DataFrame의 모든 행에 넣어줄 수 있는겁니다.

 

import pandas as pd

dict_item = {
    'date': [
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103, 20200104
    ],
    'item_id': [
        1, 1, 1,
        2, 2, 2,
        3, 3, 3,
        4, 4, 4, 4
    ],
    'item_name': [
        'a', 'a', 'a',
        'b', 'b', 'b',
        'c', 'c', 'c',
        'd', 'd', 'd', 'd'
    ],
    'price': [
        1000, 1000, 1010,
        2000, 2100, 2050,
        3000, 3100, 2950,
        4000, 3950, 3900, 3980
    ],
    'quantity': [
        100, 105, 98,
        50, 51, 55,
        201, 200, 220,
        30, 40, 38, 50
    ]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'price_sum'] = df_item.groupby(by=['item_id'])[['price']].transform('sum').loc[:, 'price']
print(df_item)



-- Result
        date  item_id item_name  price  quantity  price_sum
0   20200101        1         a   1000       100       3010
1   20200102        1         a   1000       105       3010
2   20200103        1         a   1010        98       3010
3   20200101        2         b   2000        50       6150
4   20200102        2         b   2100        51       6150
5   20200103        2         b   2050        55       6150
6   20200101        3         c   3000       201       9050
7   20200102        3         c   3100       200       9050
8   20200103        3         c   2950       220       9050
9   20200101        4         d   4000        30      15830
10  20200102        4         d   3950        40      15830
11  20200103        4         d   3900        38      15830
12  20200104        4         d   3980        50      15830

 

- df_item.loc[:, 'price_sum'] = df_item.groupby(by=['item_id'])[['price']].transform('sum').loc[:, 'price']

보면 transform의 결과를 원본 DataFrame인 df_item의 price_sum 이라는 새로운 컬럼에 넣고있습니다.

 

그 결과를 보면 price_sum 컬럼이 생겼고, 거기에는 transform의 결과가 들어가 있습니다.

그리고 동일한 item_id별로 groupby되어 합산된 price 컬럼 값의 합이라는 것을 알 수 있습니다.

 

여기서 한 가지 주의할 점은

[['price']].transform('sum').loc[:, 'price']

groupby() method에서 [['price']] 와 같이 대괄호를 2개 썼으므로 transform의 결과도 DataFrame으로 return됩니다.

따라서 df_item의 price_sum 컬럼에 transform의 결과를 집어넣으려면 transform의 결과 중 price 컬럼의 데이터만 Series의 형태로 골라서 집어넣어야 합니다.

그래서 가장 오른 쪽에 .loc[:, 'price'] 처럼 loc를 이용해서 transform의 결과 중 price 컬럼만을 골라서 사용하겠다는 의미인 것이죠.

 

 

결과를 보면 SQL의 window function을 이용한 것과 완전히 동일한 것을 알 수 있습니다.

 

 

 

 

 

import pandas as pd

dict_item = {
    'date': [
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103, 20200104
    ],
    'item_id': [
        1, 1, 1,
        2, 2, 2,
        3, 3, 3,
        4, 4, 4, 4
    ],
    'item_name': [
        'a', 'a', 'a',
        'b', 'b', 'b',
        'c', 'c', 'c',
        'd', 'd', 'd', 'd'
    ],
    'price': [
        1000, 1000, 1010,
        2000, 2100, 2050,
        3000, 3100, 2950,
        4000, 3950, 3900, 3980
    ],
    'quantity': [
        100, 105, 98,
        50, 51, 55,
        201, 200, 220,
        30, 40, 38, 50
    ]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'price_sum'] = df_item.groupby(by=['date'])[['price']].transform('sum').loc[:, 'price']
print(df_item)


-- Result
        date  item_id item_name  price  quantity  price_sum
0   20200101        1         a   1000       100      10000
1   20200102        1         a   1000       105      10150
2   20200103        1         a   1010        98       9910
3   20200101        2         b   2000        50      10000
4   20200102        2         b   2100        51      10150
5   20200103        2         b   2050        55       9910
6   20200101        3         c   3000       201      10000
7   20200102        3         c   3100       200      10150
8   20200103        3         c   2950       220       9910
9   20200101        4         d   4000        30      10000
10  20200102        4         d   3950        40      10150
11  20200103        4         d   3900        38       9910
12  20200104        4         d   3980        50       3980

위 예시는 groupby의 기준을 date 컬럼으로 바꾸었습니다.

이렇게되면 동일한 date값을 가진 행들의 price 값이 합쳐지고 그것이 각각의 date 마다 넣어질겁니다.

 

date 컬럼의 값이 정려되어있지 않아서 결과를 보기 힘든데 date 기준으로 정렬하면 다음과 같습니다.

 

 

 

 

import pandas as pd

dict_item = {
    'date': [
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103, 20200104
    ],
    'item_id': [
        1, 1, 1,
        2, 2, 2,
        3, 3, 3,
        4, 4, 4, 4
    ],
    'item_name': [
        'a', 'a', 'a',
        'b', 'b', 'b',
        'c', 'c', 'c',
        'd', 'd', 'd', 'd'
    ],
    'price': [
        1000, 1000, 1010,
        2000, 2100, 2050,
        3000, 3100, 2950,
        4000, 3950, 3900, 3980
    ],
    'quantity': [
        100, 105, 98,
        50, 51, 55,
        201, 200, 220,
        30, 40, 38, 50
    ]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'price_sum'] = df_item.groupby(by=['date'])[['price']].transform('sum').loc[:, 'price']
print(df_item)

df_item_sorted = df_item.sort_values(by=['date'], ascending=True, inplace=False, ignore_index=True)
print(df_item_sorted)



-- Result
        date  item_id item_name  price  quantity  price_sum
0   20200101        1         a   1000       100      10000
1   20200101        2         b   2000        50      10000
2   20200101        3         c   3000       201      10000
3   20200101        4         d   4000        30      10000
4   20200102        1         a   1000       105      10150
5   20200102        2         b   2100        51      10150
6   20200102        3         c   3100       200      10150
7   20200102        4         d   3950        40      10150
8   20200103        1         a   1010        98       9910
9   20200103        2         b   2050        55       9910
10  20200103        3         c   2950       220       9910
11  20200103        4         d   3900        38       9910
12  20200104        4         d   3980        50       3980

date 기준으로 정렬을 하니까 동일한 date 값을 가진 행에 대해 price값을 합한 결과가 price_sum 컬럼에 있는 것이 보이시죠?

 

 

 

 

 

 

 

이번엔 원본 DataFrame의 데이터를 살짝 바꾼 후 예시를 살펴보겠습니다.

(item_id의 종류를 1, 2, 3, 4에서 1, 2만 존재하도록 바꿨습니다.)

 

import pandas as pd

dict_item = {
    'date': [
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103, 20200104
    ],
    'item_id': [
        1, 1, 1,
        2, 2, 2,
        1, 1, 1,
        2, 2, 2, 2
    ],
    'item_name': [
        'a', 'a', 'a',
        'b', 'b', 'b',
        'c', 'c', 'c',
        'd', 'd', 'd', 'd'
    ],
    'price': [
        1000, 1000, 1010,
        2000, 2100, 2050,
        3000, 3100, 2950,
        4000, 3950, 3900, 3980
    ],
    'quantity': [
        100, 105, 98,
        50, 51, 55,
        201, 200, 220,
        30, 40, 38, 50
    ]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'price_sum'] = df_item.groupby(by=['date', 'item_id'])[['price']].transform('sum').loc[:, 'price']
print(df_item)



-- Result
        date  item_id item_name  price  quantity  price_sum
0   20200101        1         a   1000       100       4000
1   20200102        1         a   1000       105       4100
2   20200103        1         a   1010        98       3960
3   20200101        2         b   2000        50       6000
4   20200102        2         b   2100        51       6050
5   20200103        2         b   2050        55       5950
6   20200101        1         c   3000       201       4000
7   20200102        1         c   3100       200       4100
8   20200103        1         c   2950       220       3960
9   20200101        2         d   4000        30       6000
10  20200102        2         d   3950        40       6050
11  20200103        2         d   3900        38       5950
12  20200104        2         d   3980        50       3980

transform method는 groupby의 기준 컬럼이 여러 개일 때도 사용할 수 있습니다.

 

- df_item.loc[:, 'price_sum'] = df_item.groupby(by=['item_id', 'date'])[['price']].transform('sum')

보면 groupby의 기준 컬럼을 item_id, date 두 개로 했습니다.

이렇게 되면 동일한 item_id, date 값을 가진 행들의 price 컬럼 값을 합하겠다는 것입니다.

 

 

 

결과를 좀 더 보기 쉽도록 정렬해보겠습니다.

 

import pandas as pd

dict_item = {
    'date': [
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103, 20200104
    ],
    'item_id': [
        1, 1, 1,
        2, 2, 2,
        1, 1, 1,
        2, 2, 2, 2
    ],
    'item_name': [
        'a', 'a', 'a',
        'b', 'b', 'b',
        'c', 'c', 'c',
        'd', 'd', 'd', 'd'
    ],
    'price': [
        1000, 1000, 1010,
        2000, 2100, 2050,
        3000, 3100, 2950,
        4000, 3950, 3900, 3980
    ],
    'quantity': [
        100, 105, 98,
        50, 51, 55,
        201, 200, 220,
        30, 40, 38, 50
    ]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'price_sum'] = df_item.groupby(by=['date', 'item_id'])[['price']].transform('sum').loc[:, 'price']

df_item_sorted = df_item.sort_values(by=['date', 'item_id'], ascending=True, inplace=False, ignore_index=True)
print(df_item_sorted)



-- Result
        date  item_id item_name  price  quantity  price_sum
0   20200101        1         a   1000       100       4000
1   20200101        1         c   3000       201       4000
2   20200101        2         b   2000        50       6000
3   20200101        2         d   4000        30       6000
4   20200102        1         a   1000       105       4100
5   20200102        1         c   3100       200       4100
6   20200102        2         b   2100        51       6050
7   20200102        2         d   3950        40       6050
8   20200103        1         a   1010        98       3960
9   20200103        1         c   2950       220       3960
10  20200103        2         b   2050        55       5950
11  20200103        2         d   3900        38       5950
12  20200104        2         d   3980        50       3980

이제 뭔가 좀 보이실겁니다.

date = 20200101이며 item_id = 1인 행은 가장 위에 있는 2개 행이고, 이 두 행의 price값의 합은 4000입니다.

그래서 가장 위에 있는 2개 행의 price_sum 컬럼의 값은 4000인거죠.

 

다른 행들도 동일합니다.

 

 

 

 

 

 

import pandas as pd

dict_item = {
    'date': [
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103, 20200104
    ],
    'item_id': [
        1, 1, 1,
        2, 2, 2,
        3, 3, 3,
        4, 4, 4, 4
    ],
    'item_name': [
        'a', 'a', 'a',
        'b', 'b', 'b',
        'c', 'c', 'c',
        'd', 'd', 'd', 'd'
    ],
    'price': [
        1000, 1000, 1010,
        2000, 2100, 2050,
        3000, 3100, 2950,
        4000, 3950, 3900, 3980
    ],
    'quantity': [
        100, 105, 98,
        50, 51, 55,
        201, 200, 220,
        30, 40, 38, 50
    ]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'max_quantity'] = df_item.groupby(by=['item_id'])[['quantity']].transform('max').loc[:, 'quantity']
df_item.loc[:, 'min_quantity'] = df_item.groupby(by=['item_id'])[['quantity']].transform('min').loc[:, 'quantity']

print(df_item)



-- Result
        date  item_id item_name  price  quantity  max_quantity  min_quantity
0   20200101        1         a   1000       100           105            98
1   20200102        1         a   1000       105           105            98
2   20200103        1         a   1010        98           105            98
3   20200101        2         b   2000        50            55            50
4   20200102        2         b   2100        51            55            50
5   20200103        2         b   2050        55            55            50
6   20200101        3         c   3000       201           220           200
7   20200102        3         c   3100       200           220           200
8   20200103        3         c   2950       220           220           200
9   20200101        4         d   4000        30            50            30
10  20200102        4         d   3950        40            50            30
11  20200103        4         d   3900        38            50            30
12  20200104        4         d   3980        50            50            30

sum, mean 외에도 min, max도 사용할 수 있습니다.

위 예시에서 max는 동일한 item_id를 가진 행들의 quantity 중 가장 작은 값을 return해줍니다.

min은 동일한 item_id를 가진 행들의 quantity 중 가장 작은 값을 return해줍니다.

 

 

 

 

import pandas as pd

dict_item = {
    'date': [
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102,
        20200101, 20200102, 20200103, 20200104
    ],
    'item_id': [
        1, 1, 1,
        2, 2, 2,
        3, 3,
        4, 4, 4, 4
    ],
    'item_name': [
        'a', 'a', 'a',
        'b', 'b', 'b',
        'c', 'c',
        'd', 'd', 'd', 'd'
    ],
    'price': [
        1000, 1000, 1010,
        2000, 2100, 2050,
        3000, 3100,
        4000, 3950, 3900, 3980
    ],
    'quantity': [
        100, 105, 98,
        50, 51, 55,
        201, 200,
        30, 40, 38, 50
    ]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'cnt_quantity'] = df_item.groupby(by=['item_id'])[['quantity']].transform('count').loc[:, 'quantity']

print(df_item)



-- Result
        date  item_id item_name  price  quantity  cnt_quantity
0   20200101        1         a   1000       100             3
1   20200102        1         a   1000       105             3
2   20200103        1         a   1010        98             3
3   20200101        2         b   2000        50             3
4   20200102        2         b   2100        51             3
5   20200103        2         b   2050        55             3
6   20200101        3         c   3000       201             2
7   20200102        3         c   3100       200             2
8   20200101        4         d   4000        30             4
9   20200102        4         d   3950        40             4
10  20200103        4         d   3900        38             4
11  20200104        4         d   3980        50             4

 

transform()에 count를 사용하면 개수도 구할 수 있습니다.

각 group별 개수를 구하기에 유용합니다.

 

 

 

 

 

 

 

 

transform에는 lambda를 이용해서 custom function을 적용할 수 있습니다.

 

import pandas as pd

dict_item = {
    'date': [
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103, 20200104
    ],
    'item_id': [
        1, 1, 1,
        2, 2, 2,
        3, 3, 3,
        4, 4, 4, 4
    ],
    'item_name': [
        'a', 'a', 'a',
        'b', 'b', 'b',
        'c', 'c', 'c',
        'd', 'd', 'd', 'd'
    ],
    'price': [
        1000, 1000, 1010,
        2000, 2100, 2050,
        3000, 3100, 2950,
        4000, 3950, 3900, 3980
    ],
    'quantity': [
        100, 105, 98,
        50, 51, 55,
        201, 200, 220,
        30, 40, 38, 50
    ]
}
df_item = pd.DataFrame(dict_item)


def custom_func(g):
    print(g)
    print(type(g))

df_item.loc[:, 'custom_func'] = df_item.groupby(by=['item_id'])[['quantity']].transform(lambda g: custom_func(g))



-- Result
0    100
1    105
2     98
Name: quantity, dtype: int64
<class 'pandas.core.series.Series'>

3    50
4    51
5    55
Name: quantity, dtype: int64
<class 'pandas.core.series.Series'>

6    201
7    200
8    220
Name: quantity, dtype: int64
<class 'pandas.core.series.Series'>

9     30
10    40
11    38
12    50
Name: quantity, dtype: int64
<class 'pandas.core.series.Series'>

 

먼저 custom function에서 print를 이용해 어떤 값들이 lambda를 통해 전달되는지를 보면 위와 같습니다.

 

보면 item_id 기준으로 group화 된 quantity column 값들의 group이 Series의 형태로 하나씩 lambda를 통해 전달되는 것을 볼 수 있습니다.

 

 

 

 

 

이를 이용하면 transform 내에서 내가 원하는 function을 이용해 원하는 로직을 얼마든지 구현할 수 있습니다.

 

import pandas as pd

dict_item = {
    'date': [
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103, 20200104
    ],
    'item_id': [
        1, 1, 1,
        2, 2, 2,
        3, 3, 3,
        4, 4, 4, 4
    ],
    'item_name': [
        'a', 'a', 'a',
        'b', 'b', 'b',
        'c', 'c', 'c',
        'd', 'd', 'd', 'd'
    ],
    'price': [
        1000, 1000, 1010,
        2000, 2100, 2050,
        3000, 3100, 2950,
        4000, 3950, 3900, 3980
    ],
    'quantity': [
        100, 105, 98,
        50, 51, 55,
        201, 200, 220,
        30, 40, 38, 50
    ]
}
df_item = pd.DataFrame(dict_item)


def custom_func(g):

    def func(price, quantity):
        if quantity <= 100:
            val_result = price * quantity
        else:
            val_result = 0

        return val_result

    result = df_item.loc[g.index, :].apply(
        lambda row: func(row['price'], row['quantity']),
        axis=1
    )
    print(result)
    print(type(result))

    return result

df_item.loc[:, 'custom_func'] = df_item.groupby(by=['item_id'])[['quantity']].transform(lambda g: custom_func(g)).loc[:, 'quantity']
print(df_item)



-- Result
Index([0, 1, 2], dtype='int64')
0    100000
1         0
2     98980
dtype: int64
<class 'pandas.core.series.Series'>

Index([3, 4, 5], dtype='int64')
3    100000
4    107100
5    112750
dtype: int64
<class 'pandas.core.series.Series'>

Index([6, 7, 8], dtype='int64')
6    0
7    0
8    0
dtype: int64
<class 'pandas.core.series.Series'>

Index([9, 10, 11, 12], dtype='int64')
9     120000
10    158000
11    148200
12    199000
dtype: int64
<class 'pandas.core.series.Series'>

        date  item_id item_name  price  quantity  custom_func
0   20200101        1         a   1000       100       100000
1   20200102        1         a   1000       105            0
2   20200103        1         a   1010        98        98980
3   20200101        2         b   2000        50       100000
4   20200102        2         b   2100        51       107100
5   20200103        2         b   2050        55       112750
6   20200101        3         c   3000       201            0
7   20200102        3         c   3100       200            0
8   20200103        3         c   2950       220            0
9   20200101        4         d   4000        30       120000
10  20200102        4         d   3950        40       158000
11  20200103        4         d   3900        38       148200
12  20200104        4         d   3980        50       199000

 

위 예시를 보면 transform 속 lambda를 통해 item_id 기준으로 group화 된 group의 데이터가 순차적으로 전달되며

각 group의 index에 대한 값을 원본 DataFrame에서 참조하여 내가 원하는 값을 가지고 내가 원하는 로직을 구현할 수 있게 됩니다.

 

 

 

 

 

 

import pandas as pd
import numpy as np

dict_item = {
    'date': [
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103, 20200104
    ],
    'item_id': [
        1, 1, 1,
        2, 2, 2,
        3, 3, 3,
        4, 4, 4, 4
    ],
    'item_name': [
        'a', 'a', 'a',
        'b', 'b', 'b',
        'c', 'c', 'c',
        'd', 'd', 'd', 'd'
    ],
    'price': [
        1000, 1000, 1010,
        2000, 2100, 2050,
        3000, 3100, 2950,
        4000, 3950, 3900, 3980
    ],
    'quantity': [
        100, 105, 98,
        50, 51, 55,
        201, 200, 220,
        30, 40, 38, 50
    ]
}
df_item = pd.DataFrame(dict_item)


def custom_func(g):
    result = np.sum(df_item.loc[g.index, 'quantity'])

    print(result)

    return result

df_item.loc[:, 'custom_func'] = df_item.groupby(by=['item_id'])[['quantity']].transform(lambda g: custom_func(g)).loc[:, 'quantity']
print(df_item)



-- Result
303
303
156
621
158

        date  item_id item_name  price  quantity  custom_func
0   20200101        1         a   1000       100          303
1   20200102        1         a   1000       105          303
2   20200103        1         a   1010        98          303
3   20200101        2         b   2000        50          156
4   20200102        2         b   2100        51          156
5   20200103        2         b   2050        55          156
6   20200101        3         c   3000       201          621
7   20200102        3         c   3100       200          621
8   20200103        3         c   2950       220          621
9   20200101        4         d   4000        30          158
10  20200102        4         d   3950        40          158
11  20200103        4         d   3900        38          158
12  20200104        4         d   3980        50          158

 

custom function 내에서 return하는 값은 반드시 Series일 필요는 없습니다.

위처럼 특정한 단일 value를 return해도 groupby.transform이 적용됩니다.

 

 

 

 

 

import pandas as pd
import numpy as np

dict_item = {
    'date': [
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103, 20200104
    ],
    'item_id': [
        1, 1, 1,
        2, 2, 2,
        3, 3, 3,
        4, 4, 4, 4
    ],
    'item_name': [
        'a', 'a', 'a',
        'b', 'b', 'b',
        'c', 'c', 'c',
        'd', 'd', 'd', 'd'
    ],
    'price': [
        1000, 1000, 1010,
        2000, 2100, 2050,
        3000, 3100, 2950,
        4000, 3950, 3900, 3980
    ],
    'quantity': [
        100, 105, 98,
        50, 51, 55,
        201, 200, 220,
        30, 40, 38, 50
    ]
}
df_item = pd.DataFrame(dict_item)


def custom_func(g):
    df_temp = df_item.loc[g.index, :]

    con = (df_temp['quantity'] >= 100)
    df_temp = df_temp.loc[con, :]

    result = np.sum(df_temp.loc[:, 'quantity'])

    print(result)

    return result

df_item.loc[:, 'custom_func'] = df_item.groupby(by=['item_id'])[['quantity']].transform(lambda g: custom_func(g)).loc[:, 'quantity']
print(df_item)




-- Result
205
205
0
621
0

        date  item_id item_name  price  quantity  custom_func
0   20200101        1         a   1000       100          205
1   20200102        1         a   1000       105          205
2   20200103        1         a   1010        98          205
3   20200101        2         b   2000        50            0
4   20200102        2         b   2100        51            0
5   20200103        2         b   2050        55            0
6   20200101        3         c   3000       201          621
7   20200102        3         c   3100       200          621
8   20200103        3         c   2950       220          621
9   20200101        4         d   4000        30            0
10  20200102        4         d   3950        40            0
11  20200103        4         d   3900        38            0
12  20200104        4         d   3980        50            0

 

이를 이용하면 custom function 안에서 원본 DataFrame에 대해 필터를 걸어서 내가 원하는 대로 tranform을 적용할 수 있습니다.

 

위 예시는 custom function 안에서 원본 DataFrame을 참조하여 quantity >= 100 이상인 숫자만 더하여 transform을 적용한 예시입니다.

 

그래서 result DataFrame의 custom_func 컬럼을 보면

quantity >= 100인 quantity만 더해져서 구성된 것을 볼 수 있습니다.

 

 

 

 

 

 

 

 

import pandas as pd
import numpy as np

dict_item = {
    'date': [
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103,
        20200101, 20200102, 20200103, 20200104
    ],
    'item_id': [
        1, 1, 1,
        2, 2, 2,
        3, 3, 3,
        4, 4, 4, 4
    ],
    'item_name': [
        'a', 'a', 'a',
        'b', 'b', 'b',
        'c', 'c', 'c',
        'd', 'd', 'd', 'd'
    ],
    'price': [
        1000, 1000, 1010,
        2000, 2100, 2050,
        3000, 3100, 2950,
        4000, 3950, 3900, 3980
    ],
    'quantity': [
        100, 105, 98,
        50, 51, 55,
        201, 200, 220,
        30, 40, 38, 50
    ]
}
df_item = pd.DataFrame(dict_item)


df_item.loc[:, 'custom_func'] = df_item.groupby(by=['item_id'])[['quantity']].transform(lambda g: np.percentile(a=g, q=20)).loc[:, 'quantity']
print(df_item)



-- Result
        date  item_id item_name  price  quantity  custom_func
0   20200101        1         a   1000       100         98.8
1   20200102        1         a   1000       105         98.8
2   20200103        1         a   1010        98         98.8
3   20200101        2         b   2000        50         50.4
4   20200102        2         b   2100        51         50.4
5   20200103        2         b   2050        55         50.4
6   20200101        3         c   3000       201        200.4
7   20200102        3         c   3100       200        200.4
8   20200103        3         c   2950       220        200.4
9   20200101        4         d   4000        30         34.8
10  20200102        4         d   3950        40         34.8
11  20200103        4         d   3900        38         34.8
12  20200104        4         d   3980        50         34.8

 

또한 반드시 custom function을 만들어서 쓰는 것이 아니라

위처럼 어떠한 함수를 적용시킬 수도 있습니다.

 

 

 

 

 

이렇게 transform method를 이용하면 SQL의 window function과 같은 기능을 아주 쉽게 사용할 수 있습니다.

 

 

 

 

FYI

transform을 사용할 때 한가지 주의점이 있습니다.

 

아래 예시를 보시죠.

import pandas as pd


df_1 = pd.DataFrame({
    'col1': [1, 1, 2, 2, 2, 3, 3, 3],
    'col2': [4, 5, 6, 1, 8, 3, 5, 1],
    'valid_yn': [1, 1, 1, 1, 0, 0, 0],
})

df_1.loc[:, 'col3'] = df_1.groupby(by=['col1'])[['col2']].transform('sum')
print(df_1)


-- Result
   col1  col2  valid_yn  col3
0     1     4         1     9
1     1     5         1     9
2     2     6         1    15
3     2     1         1    15
4     2     8         1    15
5     3     3         0     9
6     3     5         0     9
7     3     1         0     9

 

df_1에 transform을 적용하는 것입니다.

col2에 있는 값을 col1으로 group화하여 sum하는 구문이죠.

여기까지는 문제가 없습니다.

 

근데 transform 구문을 하나 더 넣어봅시다.

 

 

 

 

import pandas as pd


df_1 = pd.DataFrame({
    'col1': [1, 1, 2, 2, 2, 3, 3, 3],
    'col2': [4, 5, 6, 1, 8, 3, 5, 1],
    'valid_yn': [1, 1, 1, 1, 0, 0, 0],
})

df_1.loc[:, 'col3'] = df_1.groupby(by=['col1'])[['col2']].transform('sum')
print(df_1)

df_1.loc[:, 'col3'] = df_1.groupby(by=['col1'])[['col2']].transform('sum')
print(df_1)


-- Result
   col1  col2  valid_yn  col3
0     1     4         1     9
1     1     5         1     9
2     2     6         1    15
3     2     1         1    15
4     2     8         1    15
5     3     3         0     9
6     3     5         0     9
7     3     1         0     9


   col1  col2  valid_yn  col3
0     1     4         1   NaN
1     1     5         1   NaN
2     2     6         1   NaN
3     2     1         1   NaN
4     2     8         1   NaN
5     3     3         0   NaN
6     3     5         0   NaN
7     3     1         0   NaN

 

두 번째 결과를 보세요. col3의 값이 모두 NaN이 되었습니다.

transform 구문은 문제가 없습니다.

똑같이 col2의 값을 col1 기준으로 group화해서 sum하여 col3에 넣는 구문이죠.

똑같은 구문을 두번 실행했는데 두번째 transform의 결과는 col3을 NaN으로 바꿨습니다.

 

왜일까요?

 

이걸 해결한 구문을 봅시다.

 

import pandas as pd


df_1 = pd.DataFrame({
    'col1': [1, 1, 2, 2, 2, 3, 3, 3],
    'col2': [4, 5, 6, 1, 8, 3, 5, 1],
    'valid_yn': [1, 1, 1, 1, 1, 0, 0, 0],
})

df_1.loc[:, 'col3'] = df_1.groupby(by=['col1'])['col2'].transform('sum')
print(df_1)

df_1.loc[:, 'col3'] = df_1.groupby(by=['col1'])['col2'].transform('sum')
print(df_1)


-- Result
   col1  col2  valid_yn  col3
0     1     4         1     9
1     1     5         1     9
2     2     6         1    15
3     2     1         1    15
4     2     8         1    15
5     3     3         0     9
6     3     5         0     9
7     3     1         0     9


   col1  col2  valid_yn  col3
0     1     4         1     9
1     1     5         1     9
2     2     6         1    15
3     2     1         1    15
4     2     8         1    15
5     3     3         0     9
6     3     5         0     9
7     3     1         0     9

 

위 코드도 마찬가지로 똑같은 dataframe에 똑같은 transform을 똑같이 두번 실행하였습니다.

근데 그 결과는 아까와는 달리 첫 번째 출력과 두 번째 출력 모두 col3에 값이 제대로 넣어져있죠.

 

무슨 차이일까요?

 

 

df_1.loc[:, 'col3'] = df_1.groupby(by=['col1'])[['col2']].transform('sum')

df_1.loc[:, 'col3'] = df_1.groupby(by=['col1'])['col2'].transform('sum')

 

바로 transform을 적용할 대상의 차이입니다.

 

첫 번째 예시는 [['col2']].transform('sum')으로 적었습니다.

이는 col2 값을 DataFrame 형태로 받아 transform을 적용하고 결과를 DataFrame 형태로 return한다는 의미입니다.

 

두 번째 예시는 [['col2']].transform('sum')으로 적었습니다.

이는 col2 값을 Series 형태로 받아 transform을 적용하고 결과를 Series 형태로 return한다는 의미입니다.

 

 

import pandas as pd


df_1 = pd.DataFrame({
    'col1': [1, 1, 2, 2, 2, 3, 3, 3],
    'col2': [4, 5, 6, 1, 8, 3, 5, 1],
    'valid_yn': [1, 1, 1, 1, 1, 0, 0, 0],
})

transform_result = df_1.groupby(by=['col1'])[['col2']].transform('sum')
print(transform_result)
print(type(transform_result))

transform_result = df_1.groupby(by=['col1'])['col2'].transform('sum')
print(transform_result)
print(type(transform_result))


-- Result
   col2
0     9
1     9
2    15
3    15
4    15
5     9
6     9
7     9
<class 'pandas.core.frame.DataFrame'>


0     9
1     9
2    15
3    15
4    15
5     9
6     9
7     9
Name: col2, dtype: int64
<class 'pandas.core.series.Series'>

 

transform 결과가 어떤지는 위 예시를 통해서 알 수 있습니다.

 

 

 

 

자 다시 예시로 돌아와봅시다.

 

import pandas as pd


df_1 = pd.DataFrame({
    'col1': [1, 1, 2, 2, 2, 3, 3, 3],
    'col2': [4, 5, 6, 1, 8, 3, 5, 1],
    'valid_yn': [1, 1, 1, 1, 0, 0, 0],
})

df_1.loc[:, 'col3'] = df_1.groupby(by=['col1'])[['col2']].transform('sum')
print(df_1)

df_1.loc[:, 'col3'] = df_1.groupby(by=['col1'])[['col2']].transform('sum')
print(df_1)


-- Result
   col1  col2  valid_yn  col3
0     1     4         1     9
1     1     5         1     9
2     2     6         1    15
3     2     1         1    15
4     2     8         1    15
5     3     3         0     9
6     3     5         0     9
7     3     1         0     9


   col1  col2  valid_yn  col3
0     1     4         1   NaN
1     1     5         1   NaN
2     2     6         1   NaN
3     2     1         1   NaN
4     2     8         1   NaN
5     3     3         0   NaN
6     3     5         0   NaN
7     3     1         0   NaN

 

이 예시에서 첫 번째 transform은 정상적으로 작동합니다.

col3가 정상적으로 의도대로 생성되었고, col3에 들어간 값들도 정상이죠.

첫 번째 transform에서는 원래의 df_1에 col3라는 컬럼이 없었고, transform의 결과를 할당할 때 기존에 없던 새로운 비어있는 컬럼을 생성하여 col3를 채우게 됩니다.

이때는 Pandas가 데이터의 길이에 맞춰 순서대로 값을 잘 채워줍니다.

 

 

근데 두 번째 transform의 결과는 NaN입니다.

두 번째 transform을 할 때에는 col3가 이미 df_1에 존재합니다.

 

이미 col3가 존재하는 상태에서 다시 값을 넣으려고 하면, Pandas는 데이터프레임의 인덱스와 집계 결과의 인덱스를 비교합니다.

 

근데 transform의 대상으로 DataFrame 타입인 [['col2']]를 명시했고, transform의 output도 DataFrame으로 될 것이고 이 transform의 결과로 return된 DataFrame을 col3에 할당해야하죠.

여기서 중요한건 transform('sum')을 거친 이 DataFrame의 컬럼명은 여전히 col2입니다.

근데 이 값을 df_1의 col3 컬럼에 넣어야하죠.

Pandas는 컬럼 이름이 서로 다른 두 객체(col2 데이터를 가진 DataFrame vs col3 자리에 대입) 사이에서 이름이 일치하는 컬럼을 찾지 못해 모든 값을 NaN으로 처리합니다.

 

 

 

이에 대한 해결법은 매우 간단합니다.

transform을 적용할 때 [['col2']] 같이 명시하여 DataFrame 연산이 되게 하지 말고

['col2'] 처럼 적어서 Series 연산이 되도록 하는 것입니다.

 

import pandas as pd


df_1 = pd.DataFrame({
    'col1': [1, 1, 2, 2, 2, 3, 3, 3],
    'col2': [4, 5, 6, 1, 8, 3, 5, 1],
    'valid_yn': [1, 1, 1, 1, 1, 0, 0, 0],
})

df_1.loc[:, 'col3'] = df_1.groupby(by=['col1'])['col2'].transform('sum')
print(df_1)

df_1.loc[:, 'col3'] = df_1.groupby(by=['col1'])['col2'].transform('sum')
print(df_1)


-- Result
   col1  col2  valid_yn  col3
0     1     4         1     9
1     1     5         1     9
2     2     6         1    15
3     2     1         1    15
4     2     8         1    15
5     3     3         0     9
6     3     5         0     9
7     3     1         0     9


   col1  col2  valid_yn  col3
0     1     4         1     9
1     1     5         1     9
2     2     6         1    15
3     2     1         1    15
4     2     8         1    15
5     3     3         0     9
6     3     5         0     9
7     3     1         0     9

 

그러면 위처럼 몇번의 transform을 실행해서 col3에 값을 몇번 할당하더라도 전혀 상관이 없습니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

728x90
반응형
Comments