Python Pandas : transform (유사 window function, 집계 결과를 index별로 추가하기)
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해줍니다.
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과 같은 기능을 아주 쉽게 사용할 수 있습니다.