Python Pandas : cummin, cummax (누적최소값, 누적최대값)
cummin은 누적 최소값을 구하며
cummax는 누적 최대값을 구합니다.
이는 cumsum, cumprod와 매우 유사하게 작동합니다.
참고 cumsum, cumprod = https://cosmosproject.tistory.com/860
cummin부터 알아봅시다.
Syntax
cummin(skipna=True/False, axis=0/1)
cummax(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_temp = df_test.loc[:, 'qty'].cummin(skipna=True)
print(type(df_test_temp))
print(df_test_temp)
-- 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 80
2 80
3 50
4 10
5 10
6 10
7 10
8 10
9 10
Name: qty, dtype: int64
위 예시는 DataFrame의 qty 컬럼에 cummin을 적용한 결과입니다.
df_test_temp = df_test.loc[:, 'qty'].cummin(skipna=True)
cummin의 결과를 보면 200, 80, 80 등등의 값이 적힌 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_temp = df_test.loc[:, 'qty'].cummin(skipna=True)
print(type(df_test_temp))
print(df_test_temp)
df_test.loc[:, 'qty_cummin'] = df_test.loc[:, 'qty'].cummin(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 80
2 80
3 50
4 10
5 10
6 10
7 10
8 10
9 10
Name: qty, dtype: int64
seq id qty qty_cummin
0 0 1 200 200
1 1 1 80 80
2 2 1 150 80
3 3 1 50 50
4 4 1 10 10
5 5 2 20 10
6 6 2 180 10
7 7 2 70 10
8 8 2 100 10
9 9 2 100 10
cummin의 결과를 좀 더 보기 쉽게 하기 위해 df_test의 qty_cummin 컬럼에 삽입했습니다.
seq id qty qty_cummin
0 0 1 200 200
1 1 1 80 80
2 2 1 150 80
3 3 1 50 50
4 4 1 10 10
5 5 2 20 10
6 6 2 180 10
7 7 2 70 10
8 8 2 100 10
9 9 2 100 10
이 결과를 보면 cummin의 기능을 알 수 있습니다.
qty 컬럼을 첫 행부터 읽어내고 첫행부터 현재 행까지의 값 중 최소값을 return하는 것입니다.
그래서 1행은 200이고
2행은 200, 80 중 최소값 = 80
3행은 200, 80, 150 중 최소값 = 80
4행은 200, 80, 150, 50 중 최소값 = 50
이런 식으로 계산이 되는 것입니다.
cummin은 이렇게 첫 행부터 순차적으로 더하기 때문에 순서가 중요합니다.
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_cummin'] = df_test.loc[:, 'qty'].cummin(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_cummin
9 9 2 100 100
8 8 2 100 100
7 7 2 70 70
6 6 2 180 70
5 5 2 20 20
4 4 1 10 10
3 3 1 50 10
2 2 1 150 10
1 1 1 80 10
0 0 1 200 10
위 예시는 DataFrame의 값을 seq 컬럼을 기준으로 내림차순하여 cummin을 적용한 예시인데
cummin의 결과를 보면 그 값이 마찬가지로 DataFrame의 첫 행부터 고려하여 최소값을 return한다는 것을 알 수 있습니다.
cummax도 마찬가지입니다.
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_temp = df_test.loc[:, 'qty'].cummax(skipna=True)
print(type(df_test_temp))
print(df_test_temp)
df_test.loc[:, 'qty_cummax'] = df_test.loc[:, 'qty'].cummax(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 200
2 200
3 200
4 200
5 200
6 200
7 200
8 200
9 200
Name: qty, dtype: int64
seq id qty qty_cummax
0 0 1 200 200
1 1 1 80 200
2 2 1 150 200
3 3 1 50 200
4 4 1 10 200
5 5 2 20 200
6 6 2 180 200
7 7 2 70 200
8 8 2 100 200
9 9 2 100 200
결과를 보면
1행은 200 중 최대값 = 200
2행은 200, 80 중 최대값 = 200
3행은 200, 80, 150 중 최대값 = 200
4행은 200, 80, 150, 50 중 최대값 = 200
이런식으로 계산이 되는 것이죠.
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_cummin'] = df_test.loc[:, 'qty'].cummin(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_cummin
0 0 1 200.0 200.0
1 1 1 80.0 80.0
2 2 1 NaN NaN
3 3 1 50.0 50.0
4 4 1 10.0 10.0
5 5 2 20.0 10.0
6 6 2 NaN NaN
7 7 2 70.0 10.0
8 8 2 100.0 10.0
9 9 2 100.0 10.0
이번에는 qty 컬럼에 NaN값을 넣어봤습니다.
seq id qty qty_cummin
0 0 1 200.0 200.0
1 1 1 80.0 80.0
2 2 1 NaN NaN
3 3 1 50.0 50.0
4 4 1 10.0 10.0
5 5 2 20.0 10.0
6 6 2 NaN NaN
7 7 2 70.0 10.0
8 8 2 100.0 10.0
9 9 2 100.0 10.0
결과를 보면 위와 같습니다.
1행은 200 중 최소값 = 200
2행은 200, 80 중 최소값 = 80
여기까진 동일합니다.
근데
3행은 200, 80, NaN이 됩니다. 이 경우 skipna=True로 지정되어있으나 3행은 그 자체의 값이 NaN이므로 NaN이 return됩니다.
4행도 특이한데
4행은 200, 80, NaN, 50 중 최소값 = 50이 됩니다.
여기서 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_cummin'] = df_test.loc[:, 'qty'].cummin(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_cummin
0 0 1 200.0 200.0
1 1 1 80.0 80.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 중 최소값 = 200
2행 -> 200, 80 중 최소값 = 80
3행 -> 200, 80, NaN 중 최소값이나 3행 자체가 NaN이므로 = NaN
여기까진 동일합니다.
근데 보면 4행부터 결과가 모두 NaN으로 찍혀있습니다.
그 이유는 skipna=False로 지정하였기 때문에 고려할 값 중 NaN이 있으면 이를 skip하지 못하고 고려하게 되어 모두 NaN이 return되는 것입니다.
이는 cummin 뿐 아니라 cummax에도 동일하게 적용됩니다.
cummin, cummax 또한 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_cummin_with_groupby = df_test.groupby(by=['id'])[['qty']].apply(lambda df: df.cummin(skipna=True))
print(df_test_cummin_with_groupby)
df_test_cummin_with_groupby = df_test_cummin_with_groupby.reset_index(drop=False, inplace=False)
print(df_test_cummin_with_groupby)
df_test.loc[:, 'qty_cummin_groupby'] = df_test_cummin_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 80
2 80
3 50
4 10
2 5 20
6 20
7 20
8 20
9 20
id level_1 qty
0 1 0 200
1 1 1 80
2 1 2 80
3 1 3 50
4 1 4 10
5 2 5 20
6 2 6 20
7 2 7 20
8 2 8 20
9 2 9 20
seq id qty qty_cummin_groupby
0 0 1 200 200
1 1 1 80 80
2 2 1 150 80
3 3 1 50 50
4 4 1 10 10
5 5 2 20 20
6 6 2 180 20
7 7 2 70 20
8 8 2 100 20
9 9 2 100 20
groupby와 cummin을 동시에 사용한 예시입니다.
df_test.groupby(by=['id'])[['qty']].apply(lambda df: df.cummin(skipna=True))
위 예시에서 보면 groupby의 lambda에 cummin을 적용하여 groupby 별 cummin을 적용하였습니다.
즉, id 컬럼의 값을 기준으로 동일한 id 값을 가진 행들의 qty에 대해서 cummin이 적용됩니다.
seq id qty qty_cummin_groupby
0 0 1 200 200
1 1 1 80 80
2 2 1 150 80
3 3 1 50 50
4 4 1 10 10
5 5 2 20 20
6 6 2 180 20
7 7 2 70 20
8 8 2 100 20
9 9 2 100 20
그래서 결과를 보면 id = 1인 행들에 대해서
id = 1의 1행 -> 200 중 최소값 = 200
id = 1의 2행 -> 200, 80 중 최소값 = 80
id = 1의 3행 -> 200, 80, 150 중 최소값 = 80
id = 1의 4행 -> 200, 80, 150, 50 중 최소값 = 50
id = 1의 5행 -> 200, 80, 150, 50, 10 중 최소값 = 10
id = 2의 1행 -> 20 중 최소값 = 20
id = 2의 2행 -> 20, 180 중 최소값 = 200
id = 2의 3행 -> 20, 180, 70 중 최소값 = 200
id = 2의 4행 -> 20, 180, 70, 100 중 최소값 = 200
id = 2의 5행 -> 20, 180, 70, 100, 100 중 최소값 = 200
이렇게 계산이 됩니다.