일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- GIT
- numpy
- Python
- Kotlin
- array
- Excel
- c#
- string
- Google Excel
- Tkinter
- Apache
- google apps script
- PANDAS
- Github
- matplotlib
- PostgreSQL
- Mac
- Google Spreadsheet
- 파이썬
- Java
- list
- django
- hive
- SQL
- gas
- Redshift
- dataframe
- math
- PySpark
- Today
- Total
달나라 노트
Python Pandas : groupby & rolling (window function 흉내내기) 본문
Python Pandas : groupby & rolling (window function 흉내내기)
CosmosProject 2021. 7. 2. 19:30
이 글에선 Pandas의 DataFrame에 groupby와 rolling method를 동시에 사용하는 예시를 보겠습니다.
이 글 이전에 아래 2개의 글을 먼저 읽고 오면 이해하는데 도움이 됩니다.
https://cosmosproject.tistory.com/156
https://cosmosproject.tistory.com/12
import pandas as pd
dict_test = {
'col1': [
1, 1, 1, 1, 1, 1,
2, 2, 2, 2,
3, 3, 3,
4, 4, 4
],
'col2': [
'a', 'a', 'b', 'b', 'a', 'b',
'a', 'b', 'b', 'a',
'a', 'a', 'b',
'a', 'b', 'b'
],
'col3': [
1000, 1100, 1200, 1300, 1050, 1100,
2100, 2050, 2000, 2200,
3000, 3100, 3200,
4200, 4100, 4150
],
'col4': [
1, 2, 3, 4, 2, 1,
5, 6, 7, 8,
9, 10, 11,
12, 13, 14
]
}
df_test = pd.DataFrame(dict_test)
print(df_test)
-- Result
col1 col2 col3 col4
0 1 a 1000 1
1 1 a 1100 2
2 1 b 1200 3
3 1 b 1300 4
4 1 a 1050 2
5 1 b 1100 1
6 2 a 2100 5
7 2 b 2050 6
8 2 b 2000 7
9 2 a 2200 8
10 3 a 3000 9
11 3 a 3100 10
12 3 b 3200 11
13 4 a 4200 12
14 4 b 4100 13
15 4 b 4150 14
먼저 위처럼 test용 DataFrame을 만듭니다.
import pandas as pd
dict_test = {
'col1': [
1, 1, 1, 1, 1, 1,
2, 2, 2, 2,
3, 3, 3,
4, 4, 4
],
'col2': [
'a', 'a', 'b', 'b', 'a', 'b',
'a', 'b', 'b', 'a',
'a', 'a', 'b',
'a', 'b', 'b'
],
'col3': [
1000, 1100, 1200, 1300, 1050, 1100,
2100, 2050, 2000, 2200,
3000, 3100, 3200,
4200, 4100, 4150
],
'col4': [
1, 2, 3, 4, 2, 1,
5, 6, 7, 8,
9, 10, 11,
12, 13, 14
]
}
df_test = pd.DataFrame(dict_test)
print(df_test)
df_new = df_test.groupby(by=['col1', 'col2'])[['col3']].rolling(2).sum()
print(df_new)
-- Result
col1 col2 col3 col4
0 1 a 1000 1
1 1 a 1100 2
2 1 b 1200 3
3 1 b 1300 4
4 1 a 1050 2
5 1 b 1100 1
6 2 a 2100 5
7 2 b 2050 6
8 2 b 2000 7
9 2 a 2200 8
10 3 a 3000 9
11 3 a 3100 10
12 3 b 3200 11
13 4 a 4200 12
14 4 b 4100 13
15 4 b 4150 14
col1 col2
1 a 0 NaN
1 2100.0
4 2150.0
b 2 NaN
3 2500.0
5 2400.0
2 a 6 NaN
9 4300.0
b 7 NaN
8 4050.0
3 a 10 NaN
11 6100.0
b 12 NaN
4 a 13 NaN
b 14 NaN
15 8250.0
위 예시를 보면 df_test에 groupby와 rolling을 동시에 적용시켰습니다.
groupby(by=['col1', 'col2'])['col3']
col1, col2가 groupby의 기준 컬럼으로 사용되었으므로 return되는 결과값에도 col1, col2가 동시에 index로 들어가있습니다.
col1, col2로 group화한 후 rolling을 적용시킬 column은 col3입니다.
groupby(by=['col1', 'col2'])['col3'].rolling(2).sum()
col1, col2로 group화된 data의 col3에 rolling.sum()을 적용하는데 rolling.sum()을 적용할 대상 row는 2개입니다.
여기서 말하는 2개란 rolling이 col3의 첫 행부터 rolling을 적용시키게 되는데
이때 col3에서 현재 행 1개와 이전 행 1개의 값을 sum한다는 뜻입니다.
다만 이러한 rolling이 DataFrame의 전체 row에 대해 적용되는 것이 아니라,
groupby로 묶인 row들 끼리 진행된다는 것입니다.
예를들어 df_test에서 index=13인 행(col1 = 4, col2 = a)을 보면, df_test 상에선 이전 행(index=12)이 존재합니다.
그러나 결과값에선 col1 = 4, col2 = a인 index에 대한 값은 NaN입니다.
그 이유는 col1 = 4, col2 = a값을 가지는 행끼리 groupby를 하면 동일한 group에 들어가는(동일한 col1, col2 값을 가진) 그룹에 index=13인 값만 존재하기 때문에 해당 group내에선 이전행이 없다고 판단되기 때문입니다.
결과값을 보면 한 가지 더 특징이 있습니다.
col1, col2는 index로 갔고, rolling.sum의 결과가 가장 오른쪽 컬럼에 위치합니다.
근데 그 사이에 0부터 13의 index가 매겨졌습니다.
이것은 각각의 행(row)이 원본 dataframe에서 가지고있던 index를 의미합니다.
import pandas as pd
dict_test = {
'col1': [
1, 1, 1, 1, 1, 1,
2, 2, 2, 2,
3, 3, 3,
4, 4, 4
],
'col2': [
'a', 'a', 'b', 'b', 'a', 'b',
'a', 'b', 'b', 'a',
'a', 'a', 'b',
'a', 'b', 'b'
],
'col3': [
1000, 1100, 1200, 1300, 1050, 1100,
2100, 2050, 2000, 2200,
3000, 3100, 3200,
4200, 4100, 4150
],
'col4': [
1, 2, 3, 4, 2, 1,
5, 6, 7, 8,
9, 10, 11,
12, 13, 14
]
}
df_test = pd.DataFrame(dict_test)
print(df_test)
df_new = df_test.groupby(by=['col1', 'col2'])[['col3']].rolling(2).sum().shift(-1)
print(df_new)
-- Result
col1 col2 col3 col4
0 1 a 1000 1
1 1 a 1100 2
2 1 b 1200 3
3 1 b 1300 4
4 1 a 1050 2
5 1 b 1100 1
6 2 a 2100 5
7 2 b 2050 6
8 2 b 2000 7
9 2 a 2200 8
10 3 a 3000 9
11 3 a 3100 10
12 3 b 3200 11
13 4 a 4200 12
14 4 b 4100 13
15 4 b 4150 14
col1 col2
1 a 0 2100.0
1 2150.0
4 NaN
b 2 2500.0
3 2400.0
5 NaN
2 a 6 4300.0
9 NaN
b 7 4050.0
8 NaN
3 a 10 6100.0
11 NaN
b 12 NaN
4 a 13 NaN
b 14 8250.0
15 NaN
Name: col3, dtype: float64
rolling을 단일로 쓸때와 마찬가지로 shift도 적용가능합니다.
shift(-1)은 rolling의 값을 위로 1행씩 옮깁니다.(기존 데이터들의 위치를 기존 index - 1의 위치로 옮깁니다.)
import pandas as pd
dict_test = {
'col1': [
1, 1, 1, 1, 1, 1,
2, 2, 2, 2,
3, 3, 3,
4, 4, 4
],
'col2': [
'a', 'a', 'b', 'b', 'a', 'b',
'a', 'b', 'b', 'a',
'a', 'a', 'b',
'a', 'b', 'b'
],
'col3': [
1000, 1100, 1200, 1300, 1050, 1100,
2100, 2050, 2000, 2200,
3000, 3100, 3200,
4200, 4100, 4150
],
'col4': [
1, 2, 3, 4, 2, 1,
5, 6, 7, 8,
9, 10, 11,
12, 13, 14
]
}
df_test = pd.DataFrame(dict_test)
print(df_test)
df_new = df_test.groupby(by=['col1', 'col2'])[['col3']].rolling(2).sum().shift(-1).reset_index(drop=False, inplace=False)
print(df_new)
-- Result
col1 col2 col3 col4
0 1 a 1000 1
1 1 a 1100 2
2 1 b 1200 3
3 1 b 1300 4
4 1 a 1050 2
5 1 b 1100 1
6 2 a 2100 5
7 2 b 2050 6
8 2 b 2000 7
9 2 a 2200 8
10 3 a 3000 9
11 3 a 3100 10
12 3 b 3200 11
13 4 a 4200 12
14 4 b 4100 13
15 4 b 4150 14
col1 col2 level_2 col3
0 1 a 0 2100.0
1 1 a 1 2150.0
2 1 a 4 NaN
3 1 b 2 2500.0
4 1 b 3 2400.0
5 1 b 5 NaN
6 2 a 6 4300.0
7 2 a 9 NaN
8 2 b 7 4050.0
9 2 b 8 NaN
10 3 a 10 6100.0
11 3 a 11 NaN
12 3 b 12 NaN
13 4 a 13 NaN
14 4 b 14 8250.0
15 4 b 15 NaN
reset_index를 적용하면 위처럼 col1, col2를 column으로 불러올 수 있습니다.
근데 보면 level_2라는 컬럼이 있습니다.
이 컬럼은 groupby.rolling.sum을 적용하기 전 DataFrame의 index를 의미합니다.
level_2라는 이름은 gorupby에 사용된 column이 col1, col2 2개였기 때문이고,
만약 groupby의 by column으로 1개의 column만 사용했다면 level_1, 3개의 column을 사용했다면 level_3라는 이름으로 원본 DataFrame의 index정보를 담은 column이 level_x의 형태로 생성됩니다.
또한 rolling.sum의 기준이 되었던 col3는 당연히 col3라는 이름을 갖게 되었음을 알 수 있습니다.
import pandas as pd
dict_test = {
'col1': [
1, 1, 1, 1, 1, 1,
2, 2, 2, 2,
3, 3, 3,
4, 4, 4,
],
'col2': [
'a', 'a', 'b', 'b', 'a', 'b',
'a', 'b', 'b', 'a',
'a', 'a', 'b',
'a', 'b', 'b'
],
'col3': [
1000, 1100, 1200, 1300, 1050, 1100,
2100, 2050, 2000, 2200,
3000, 3100, 3200,
4200, 4100, 4150
],
'col4': [
1, 2, 3, 4, 2, 1,
5, 6, 7, 8,
9, 10, 11,
12, 13, 14
]
}
df_test = pd.DataFrame(dict_test)
print(df_test)
con = (df_test['col1'] == 2)
con = con | (df_test['col1'] == 4)
df_test_filtered = df_test.loc[con, :]
print(df_test_filtered)
df_test_shifted = df_test_filtered.groupby(by=['col1', 'col2'])[['col3']].rolling(2).sum().shift(-1).reset_index(drop=False, inplace=False)
print(df_test_shifted)
-- Result
col1 col2 col3 col4
0 1 a 1000 1
1 1 a 1100 2
2 1 b 1200 3
3 1 b 1300 4
4 1 a 1050 2
5 1 b 1100 1
6 2 a 2100 5
7 2 b 2050 6
8 2 b 2000 7
9 2 a 2200 8
10 3 a 3000 9
11 3 a 3100 10
12 3 b 3200 11
13 4 a 4200 12
14 4 b 4100 13
15 4 b 4150 14
col1 col2 col3 col4
6 2 a 2100 5
7 2 b 2050 6
8 2 b 2000 7
9 2 a 2200 8
13 4 a 4200 12
14 4 b 4100 13
15 4 b 4150 14
col1 col2 level_2 col3
0 2 a 6 4300.0
1 2 a 9 NaN
2 2 b 7 4050.0
3 2 b 8 NaN
4 4 a 13 NaN
5 4 b 14 8250.0
6 4 b 15 NaN
위 예시를 보면 rolling.sum을 적용시키기 전에 loc를 이용해 col1의 값이 2 또는 4인 행만 filtering을 하였습니다.
따라서 df_test_filtered를 보시면 index가 6, 7, 8, 9, 13, 14, 15로 나와있죠.
원본 DataFrame에서 필터만 걸린 상태니까 당연합니다.
그리고 rolling.sum을 적용한 결과 생성된 level_2를 보면 동일하게 6, 7, 8, 9, 13, 14, 15인 것을 볼 수 있습니다.
따라서 rolling.sum으로 생성된 level_x 컬럼은 원본 DataFrame의 index 정보를 그대로 보존하기 위해 생성되는 것임을 알 수 있죠.
import pandas as pd
dict_test = {
'col1': [
1, 1, 1, 1, 1, 1,
2, 2, 2, 2,
3, 3, 3,
4, 4, 4,
],
'col2': [
'a', 'a', 'a', 'b', 'b', 'b',
'a', 'a', 'b', 'b',
'a', 'a', 'b',
'a', 'b', 'b'
],
'col3': [
1000, 1100, 1200, 1300, 1050, 1100,
2100, 2050, 2000, 2200,
3000, 3100, 3200,
4200, 4100, 4150
],
'col4': [
1, 2, 3, 4, 2, 1,
5, 6, 7, 8,
9, 10, 11,
12, 13, 14
]
}
df_test = pd.DataFrame(dict_test)
print(df_test)
con = (df_test['col1'] == 2)
con = con | (df_test['col1'] == 4)
df_test_filtered = df_test.loc[con, :]
df_test_shifted = df_test_filtered.groupby(by=['col1', 'col2'])[['col3']].rolling(2).sum().shift(-1).reset_index(drop=False, inplace=False)
df_test_shifted.loc[:, 'col3'] = df_test_shifted.loc[:, 'col3'].fillna(0)
df_test_shifted_reindex = df_test_shifted.set_index(keys='level_2', drop=True, inplace=False)
print(df_test_shifted_reindex)
df_test.loc[:, 'new_col3'] = df_test_shifted_reindex.loc[:, ['col3']]
print(df_test)
-- Result
col1 col2 col3 col4
0 1 a 1000 1
1 1 a 1100 2
2 1 a 1200 3
3 1 b 1300 4
4 1 b 1050 2
5 1 b 1100 1
6 2 a 2100 5
7 2 a 2050 6
8 2 b 2000 7
9 2 b 2200 8
10 3 a 3000 9
11 3 a 3100 10
12 3 b 3200 11
13 4 a 4200 12
14 4 b 4100 13
15 4 b 4150 14
col1 col2 col3
level_2
6 2 a 4150.0
7 2 a 0.0
8 2 b 4200.0
9 2 b 0.0
13 4 a 0.0
14 4 b 8250.0
15 4 b 0.0
col1 col2 col3 col4 new_col3
0 1 a 1000 1 NaN
1 1 a 1100 2 NaN
2 1 a 1200 3 NaN
3 1 b 1300 4 NaN
4 1 b 1050 2 NaN
5 1 b 1100 1 NaN
6 2 a 2100 5 4150.0
7 2 a 2050 6 0.0
8 2 b 2000 7 4200.0
9 2 b 2200 8 0.0
10 3 a 3000 9 NaN
11 3 a 3100 10 NaN
12 3 b 3200 11 NaN
13 4 a 4200 12 0.0
14 4 b 4100 13 8250.0
15 4 b 4150 14 0.0
level_x 컬럼이 원본 DataFrame의 index를 보존해준다는 특성을 이용하면
rolling.sum이 적용된 새로운 컬럼을 원본 DataFrame에 추가할 수도 있습니다.
위 예시는 이전에 봤던 예시와 거의 동일하나 아래와 같은 부분이 다릅니다.
df_test_shifted.loc[:, 'col3'] = df_test_shifted.loc[:, 'col3'].fillna(0)
rolling.sum을 적용한 후 NaN값들을 0으로 채워줍니다.
df_test_shifted_reindex = df_test_shifted.set_index(keys='level_2', drop=True, inplace=False)
위 부분을 통해서 rolling.sum이 적용된 DataFrame의 index를 level_2값으로 바꿔줍니다.
df_test.loc[:, 'new_col3'] = df_test_shifted_reindex.loc[:, ['col3']]
그리고 위 부분은 원본 DataFrame인 df_test의 new_col3라는 새로운 컬럼에 df_test_shifted_reindex의 col3값을 할당하고 있습니다.
이것이 가능한 것은 loc를 통해 다른 DataFrame의 column값을 또 다른 DataFrame에 할당할 때, 동일한 index끼리만 할당이 되며 index가 일치하지 않으면 그 값은 무시되는 특성을 이용한 것입니다.
그래서 결과를 보면 df_test_shifted_index에 있는 index(6, 7, 8, 9, 13, 14, 15)인 행에 대한 값들만 new_col3에 할당된 것을 볼 수 있죠.
'Python > Python Pandas' 카테고리의 다른 글
Python Pandas : transpose (DataFrame 행/열 뒤집기) (0) | 2021.07.15 |
---|---|
Python Pandas : set_index (DataFrame의 index 변경하기) (0) | 2021.07.15 |
Python Pandas : min, max (컬럼간의 값 비교하기) (0) | 2021.07.02 |
Python Pandas : contains (문자열의 포함여부 판단하기) (0) | 2021.06.30 |
Python Pandas : pandas.io.sql.get_schema (DataFrame 내용을 sql create table syntax로 만들기) (0) | 2021.06.13 |