Python Pandas : cumsum, cumprod (누적합, 누적곱)
cumsum은 누적합을 구하며
cumprod는 누적곱을 구합니다.
cumsum부터 알아봅시다.
Syntax
cumsum(skipna=True/False, axis=0/1)
cumprod(skipna=True/False, axis=0/1)
- skipna
True일 경우 NaN값을 무시하고 계산합니다.
False일 경우 NaN값을 고려하고 계산합니다.
- axis
누적합을 구할 축을 지정합니다.
기본값은 0이며 0으로 지정해야 컬럼 기준 누적합이 됩니다.
import pandas as pd
dict_test = {
'seq': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
'id': [1, 1, 1, 1, 1, 2, 2, 2, 2, 2],
'qty': [200, 80, 150, 50, 10, 20, 180, 70, 100, 100]
}
df_test = pd.DataFrame(dict_test)
print(df_test)
df_test_cumsum = df_test.loc[:, 'qty'].cumsum(skipna=True)
print(type(df_test_cumsum))
print(df_test_cumsum)
-- Result
seq id qty
0 0 1 200
1 1 1 80
2 2 1 150
3 3 1 50
4 4 1 10
5 5 2 20
6 6 2 180
7 7 2 70
8 8 2 100
9 9 2 100
<class 'pandas.core.series.Series'>
0 200
1 280
2 430
3 480
4 490
5 510
6 690
7 760
8 860
9 960
위 예시는 DataFrame의 qty 컬럼에 cumsum을 적용한 결과입니다.
df_test_cumsum = df_test.loc[:, 'qty'].cumsum(skipna=False)
cumsum의 결과를 보면 200, 280, 430 등등의 값이 적힌 Series가 return되었습니다.
import pandas as pd
dict_test = {
'seq': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
'id': [1, 1, 1, 1, 1, 2, 2, 2, 2, 2],
'qty': [200, 80, 150, 50, 10, 20, 180, 70, 100, 100]
}
df_test = pd.DataFrame(dict_test)
print(df_test)
df_test_cumsum = df_test.loc[:, 'qty'].cumsum(skipna=True)
print(type(df_test_cumsum))
print(df_test_cumsum)
df_test.loc[:, 'qty_cumsum'] = df_test.loc[:, 'qty'].cumsum(skipna=True)
print(df_test)
-- Result
seq id qty
0 0 1 200
1 1 1 80
2 2 1 150
3 3 1 50
4 4 1 10
5 5 2 20
6 6 2 180
7 7 2 70
8 8 2 100
9 9 2 100
<class 'pandas.core.series.Series'>
0 200
1 280
2 430
3 480
4 490
5 510
6 690
7 760
8 860
9 960
Name: qty, dtype: int64
seq id qty qty_cumsum
0 0 1 200 200
1 1 1 80 280
2 2 1 150 430
3 3 1 50 480
4 4 1 10 490
5 5 2 20 510
6 6 2 180 690
7 7 2 70 760
8 8 2 100 860
9 9 2 100 960
cumsum의 결과를 좀 더 보기 쉽게 하기 위해 df_test의 qty_cumsum 컬럼에 삽입했습니다.
seq id qty qty_cumsum
0 0 1 200 200
1 1 1 80 280
2 2 1 150 430
3 3 1 50 480
4 4 1 10 490
5 5 2 20 510
6 6 2 180 690
7 7 2 70 760
8 8 2 100 860
9 9 2 100 960
이 결과를 보면 cumsum의 기능을 알 수 있습니다.
qty 컬럼을 첫 행부터 읽어내고 첫행부터 현재 행까지의 값을 더하는 것입니다.
그래서 1행은 200이고
2행은 200 + 80 = 280
3행은 200 + 80 + 150 = 430
4행은 200 + 80 + 150 + 50 = 480
이런 식으로 계산이 되는 것입니다.
cumsum은 이렇게 첫 행부터 순차적으로 더하기 때문에 순서가 중요합니다.
import pandas as pd
dict_test = {
'seq': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
'id': [1, 1, 1, 1, 1, 2, 2, 2, 2, 2],
'qty': [200, 80, 150, 50, 10, 20, 180, 70, 100, 100]
}
df_test = pd.DataFrame(dict_test)
df_test = df_test.sort_values(by=['seq'], ascending=False, inplace=False)
print(df_test)
df_test.loc[:, 'qty_cumsum'] = df_test.loc[:, 'qty'].cumsum(skipna=True)
print(df_test)
-- Result
seq id qty
9 9 2 100
8 8 2 100
7 7 2 70
6 6 2 180
5 5 2 20
4 4 1 10
3 3 1 50
2 2 1 150
1 1 1 80
0 0 1 200
seq id qty qty_cumsum
9 9 2 100 100
8 8 2 100 200
7 7 2 70 270
6 6 2 180 450
5 5 2 20 470
4 4 1 10 480
3 3 1 50 530
2 2 1 150 680
1 1 1 80 760
0 0 1 200 960
위 예시는 DataFrame의 값을 seq 컬럼을 기준으로 내림차순하여 cumsum을 적용한 예시인데
cumsum의 결과를 보면 그 값이 마찬가지로 DataFrame의 첫 행부터 더하는 것을 알 수 있습니다.
cumprod도 마찬가지입니다.
import pandas as pd
dict_test = {
'seq': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
'id': [1, 1, 1, 1, 1, 2, 2, 2, 2, 2],
'qty': [200, 80, 150, 50, 10, 20, 180, 70, 100, 100]
}
df_test = pd.DataFrame(dict_test)
print(df_test)
df_test_cumsum = df_test.loc[:, 'qty'].cumprod(skipna=True)
print(type(df_test_cumsum))
print(df_test_cumsum)
df_test.loc[:, 'qty_cumprod'] = df_test.loc[:, 'qty'].cumprod(skipna=True)
print(df_test)
-- Result
seq id qty
0 0 1 200
1 1 1 80
2 2 1 150
3 3 1 50
4 4 1 10
5 5 2 20
6 6 2 180
7 7 2 70
8 8 2 100
9 9 2 100
<class 'pandas.core.series.Series'>
0 200
1 16000
2 2400000
3 120000000
4 1200000000
5 24000000000
6 4320000000000
7 302400000000000
8 30240000000000000
9 3024000000000000000
Name: qty, dtype: int64
seq id qty qty_cumprod
0 0 1 200 200
1 1 1 80 16000
2 2 1 150 2400000
3 3 1 50 120000000
4 4 1 10 1200000000
5 5 2 20 24000000000
6 6 2 180 4320000000000
7 7 2 70 302400000000000
8 8 2 100 30240000000000000
9 9 2 100 3024000000000000000
결과를 보면
1행은 200
2행은 200 * 80 = 16000
3행은 200 * 80 * 150 = 2400000
4행은 200 * 80 * 150 * 50 = 120000000
이런식으로 계산이 되는 것이죠.
import pandas as pd
dict_test = {
'seq': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
'id': [1, 1, 1, 1, 1, 2, 2, 2, 2, 2],
'qty': [200, 80, None, 50, 10, 20, None, 70, 100, 100]
}
df_test = pd.DataFrame(dict_test)
print(df_test)
df_test.loc[:, 'qty_cumsum'] = df_test.loc[:, 'qty'].cumsum(skipna=True)
print(df_test)
-- Result
seq id qty
0 0 1 200.0
1 1 1 80.0
2 2 1 NaN
3 3 1 50.0
4 4 1 10.0
5 5 2 20.0
6 6 2 NaN
7 7 2 70.0
8 8 2 100.0
9 9 2 100.0
seq id qty qty_cumsum
0 0 1 200.0 200.0
1 1 1 80.0 280.0
2 2 1 NaN NaN
3 3 1 50.0 330.0
4 4 1 10.0 340.0
5 5 2 20.0 360.0
6 6 2 NaN NaN
7 7 2 70.0 430.0
8 8 2 100.0 530.0
9 9 2 100.0 630.0
이번에는 qty 컬럼에 NaN값을 넣어봤습니다.
seq id qty qty_cumsum
0 0 1 200.0 200.0
1 1 1 80.0 280.0
2 2 1 NaN NaN
3 3 1 50.0 330.0
4 4 1 10.0 340.0
5 5 2 20.0 360.0
6 6 2 NaN NaN
7 7 2 70.0 430.0
8 8 2 100.0 530.0
9 9 2 100.0 630.0
결과를 보면 위와 같습니다.
1행은 200
2행은 200 + 80 = 280
여기까진 동일합니다.
근데
3행은 200 + 80 + NaN이 됩니다. 이 경우 skipna=True로 지정되어있으나 3행은 그 자체의 값이 NaN이므로 NaN이 return됩니다.
4행도 특이한데
4행은 200 + 80 + NaN + 50 = 330이 됩니다.
여기서 skipna=True의 진짜 의미를 알 수 있는데
더할 값 중에 NaN이 있으면 이것은 그냥 없는 셈 쳐서 skip하라는 것이 skipna=True의 의미입니다.
다만 해당 행이 NaN이라면 NaN을 return한다는 예외가 있죠.
import pandas as pd
dict_test = {
'seq': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
'id': [1, 1, 1, 1, 1, 2, 2, 2, 2, 2],
'qty': [200, 80, None, 50, 10, 20, None, 70, 100, 100]
}
df_test = pd.DataFrame(dict_test)
print(df_test)
df_test.loc[:, 'qty_cumsum'] = df_test.loc[:, 'qty'].cumsum(skipna=False)
print(df_test)
-- Result
seq id qty
0 0 1 200.0
1 1 1 80.0
2 2 1 NaN
3 3 1 50.0
4 4 1 10.0
5 5 2 20.0
6 6 2 NaN
7 7 2 70.0
8 8 2 100.0
9 9 2 100.0
seq id qty qty_cumsum
0 0 1 200.0 200.0
1 1 1 80.0 280.0
2 2 1 NaN NaN
3 3 1 50.0 NaN
4 4 1 10.0 NaN
5 5 2 20.0 NaN
6 6 2 NaN NaN
7 7 2 70.0 NaN
8 8 2 100.0 NaN
9 9 2 100.0 NaN
skipna=False 예시입니다.
1행 = 200
2행 = 200 + 80 = 280
3행 = 200 + 80 + NaN = NaN
여기까진 동일합니다.
근데 보면 4행부터 결과가 모두 NaN으로 찍혀있습니다.
그 이유는 skipna=False로 지정하였기 때문에 연산을 진행할 값 중 NaN이 있으면 이를 skip하지 못하고 고려하게 되어 모두 NaN이 return되는 것입니다.
이는 cumsum 뿐 아니라 cumprod에도 동일하게 적용됩니다.
cumsum, cumprod 또한 groupby와 함께 사용할 수 있습니다.
import pandas as pd
dict_test = {
'seq': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
'id': [1, 1, 1, 1, 1, 2, 2, 2, 2, 2],
'qty': [200, 80, 150, 50, 10, 20, 180, 70, 100, 100]
}
df_test = pd.DataFrame(dict_test)
print(df_test)
df_test_cumsum_with_groupby = df_test.groupby(by=['id'])[['qty']].apply(lambda df: df.cumsum(skipna=True))
print(df_test_cumsum_with_groupby)
df_test_cumsum_with_groupby = df_test_cumsum_with_groupby.reset_index(drop=False, inplace=False)
print(df_test_cumsum_with_groupby)
df_test.loc[:, 'qty_cumsum_groupby'] = df_test_cumsum_with_groupby.loc[:, 'qty']
print(df_test)
-- Result
seq id qty
0 0 1 200
1 1 1 80
2 2 1 150
3 3 1 50
4 4 1 10
5 5 2 20
6 6 2 180
7 7 2 70
8 8 2 100
9 9 2 100
qty
id
1 0 200
1 280
2 430
3 480
4 490
2 5 20
6 200
7 270
8 370
9 470
id level_1 qty
0 1 0 200
1 1 1 280
2 1 2 430
3 1 3 480
4 1 4 490
5 2 5 20
6 2 6 200
7 2 7 270
8 2 8 370
9 2 9 470
seq id qty qty_cumsum_groupby
0 0 1 200 200
1 1 1 80 280
2 2 1 150 430
3 3 1 50 480
4 4 1 10 490
5 5 2 20 20
6 6 2 180 200
7 7 2 70 270
8 8 2 100 370
9 9 2 100 470
groupby와 cumsum을 동시에 사용한 예시입니다.
df_test.groupby(by=['id'])[['qty']].apply(lambda df: df.cumsum(skipna=True))
위 예시에서 보면 groupby의 lambda에 cumsum을 적용하여 groupby 별 cumsum을 적용하였습니다.
즉, id 컬럼의 값을 기준으로 동일한 id 값을 가진 행들의 qty에 대해서 cumsum이 적용됩니다.
seq id qty qty_cumsum_groupby
0 0 1 200 200
1 1 1 80 280
2 2 1 150 430
3 3 1 50 480
4 4 1 10 490
5 5 2 20 20
6 6 2 180 200
7 7 2 70 270
8 8 2 100 370
9 9 2 100 470
그래서 결과를 보면 id = 1인 행들에 대해서
id = 1의 1행 -> 200
id = 1의 2행 -> 200 + 80 = 280
id = 1의 3행 -> 200 + 80 + 150 = 430
id = 1의 4행 -> 200 + 80 + 150 + 50 = 480
id = 1의 5행 -> 200 + 80 + 150 + 50 + 10 = 490
id = 2의 1행 -> 20
id = 2의 2행 -> 20 + 180 = 200
id = 2의 3행 -> 20 + 180 + 70 = 270
id = 2의 4행 -> 20 + 180 + 70 + 100 = 370
id = 2의 5행 -> 20 + 180 + 70 + 100 + 100 = 470
이렇게 계산이 됩니다.