달나라 노트

Python Pandas : groupby & rolling (window function 흉내내기) 본문

Python/Python Pandas

Python Pandas : groupby & rolling (window function 흉내내기)

CosmosProject 2021. 7. 2. 19:30
728x90
반응형

 

 

 

 

 

 

이 글에선 Pandas의 DataFrame에 groupby와 rolling method를 동시에 사용하는 예시를 보겠습니다.

 

이 글 이전에 아래 2개의 글을 먼저 읽고 오면 이해하는데 도움이 됩니다.

 

https://cosmosproject.tistory.com/156

 

Python Pandas : rolling (DataFrame window function)

Pandas에서 사용할 수 있는 window function 기능을 알아봅시다. import pandas as pd dict_test = { 'col1': [1, 1, 2, 2, 3, 3, 3, 4], 'col2': [1000, 1100, 2100, 2050, 3000, 3100, 3200, 4200], 'col3': ['a..

cosmosproject.tistory.com

 

 

https://cosmosproject.tistory.com/12

 

Python Pandas : DataFrame.groupby

DataFrame.groupby groupby는 특정 컬럼에 존재하는 값들에 대해서 동일한 값을 가진 행끼리 그룹화하고 그룹화된 행들에 어떤 연산(합, 평균, 최대, 최소 등)을 해주는 기능을 가집니다. 먼저 test용 DataF

cosmosproject.tistory.com

 

 

 

 

 

 

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에 할당된 것을 볼 수 있죠.

 

 

 

 

 

 

 

 

 

 

728x90
반응형
Comments