달나라 노트

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해줍니다.

 

 

 

 

 

 

 

 

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과 같은 기능을 아주 쉽게 사용할 수 있습니다.

 

 

 

 

 

728x90
반응형
Comments