Python

고급 GroupBy 사용

nova-unum 2022. 8. 10. 14:55

고급 GroupBy 사용

● 그룹 변환과 GroupBy 객체 풀어내기

● 시계열 그룹 리샘플링


그룹 변환과 GroupBy 객체 풀어내기

transform 이라는 내장 메서드를 이용하면 apply 메서드와 유사하게 동작하면서도 사용할 수 있는 함수의 종류에 대해 좀 더 많은 제한을 포함할 수 있다.

. 그룹 형태로 브로드캐스트할 수 있는 스칼라값을 생성해야 한다.

. 입력 그룹과 같은 형태의 객체를 반환해야 한다.

. 입력을 변경하지 않아야 한다.

df = pd.DataFrame({'key':['a','b','c'] * 4, 'value':np.arange(12.)})
df

key에 따른 그룹의 평균을 구해보자.

g = df.groupby('key').value
g.mean()
key
a    4.5
b    5.5
c    6.5
Name: value, dtype: float64

df['value']와 같은 형태의 Series를 원한 것이 아니라  'key'에 따른 그룹의 평균값으로 값을 변경하기 원했다고 가정한다면 transform에 람다 함수 lambda x:x.mean()을 넘기면 된다.

g.transform(lambda x:x.mean())
0     4.5
1     5.5
2     6.5
3     4.5
4     5.5
5     6.5
6     4.5
7     5.5
8     6.5
9     4.5
10    5.5
11    6.5
Name: value, dtype: float64

 내장 요약함수에 대해서는 agg 메서드에서처럼 문자열 그룹 연산 이름을 넘기면 된다.

g.transform('mean')
0     4.5
1     5.5
2     6.5
3     4.5
4     5.5
5     6.5
6     4.5
7     5.5
8     6.5
9     4.5
10    5.5
11    6.5
Name: value, dtype: float64

apply와 마찬가지로 transform은 Series를 반환하는 함수만 사용할 수 있지만 결과는 입력과 똑같은 크기여야 한다. 예를 들어 람다 함수를 이용해서 각  그룹에 모두 2를 곱할 수 있다.

g.transform(lambda x: x* 2)
0      0.0
1      2.0
2      4.0
3      6.0
4      8.0
5     10.0
6     12.0
7     14.0
8     16.0
9     18.0
10    20.0
11    22.0
Name: value, dtype: float64

좀 더 복핮ㅂ한 예제로, 각 그룹에 대해 내림차순으로 순위를 계산할 수도 있다.

g.transform(lambda x:x.rank(ascending=False))
0     4.0
1     4.0
2     4.0
3     3.0
4     3.0
5     3.0
6     2.0
7     2.0
8     2.0
9     1.0
10    1.0
11    1.0
Name: value, dtype: float64

간단한 요약을 통해 그룹 변환을 수행하는 함수를 살펴보자.

def normalize(x):
    return (x - x.mean()) / x.std()

이 경우에는 transform이나 apply를 이용해서 같은 결과를 얻을 수 있다.

g.transform(normalize)
0    -1.161895
1    -1.161895
2    -1.161895
3    -0.387298
4    -0.387298
5    -0.387298
6     0.387298
7     0.387298
8     0.387298
9     1.161895
10    1.161895
11    1.161895
Name: value, dtype: float64
g.apply(normalize)
0    -1.161895
1    -1.161895
2    -1.161895
3    -0.387298
4    -0.387298
5    -0.387298
6     0.387298
7     0.387298
8     0.387298
9     1.161895
10    1.161895
11    1.161895
Name: value, dtype: float64

mean이나 sum 같은 내장 요약함수는 일반적인 apply 함수보다 더 빠르게 동작한다. 또한 이 함수들을 transform 과 함께 사용하면 뒤로 되돌릴 수 있는데 이를 통해 그룹 연산을 풀어낼 수 있다.

g.transform('mean')

normalized = (df['value'] - g.transform('mean')) / g.transform('std')
normalized
0    -1.161895
1    -1.161895
2    -1.161895
3    -0.387298
4    -0.387298
5    -0.387298
6     0.387298
7     0.387298
8     0.387298
9     1.161895
10    1.161895
11    1.161895
Name: value, dtype: float64

그룹 연산을 풀어내면 수차례의 그룹 연산을 수행하게 되지만 벡터 연산의 장점이 더 크다.

시계열 그룹 리샘플링

시계열 데이터에서 resample 메서드는 의미적으로 시간 간격에 기반한 그룹 연산이다. 

N = 15
times = pd.date_range('2017-05-20 00:00',freq='1min', periods=N)
times
DatetimeIndex(['2017-05-20 00:00:00', '2017-05-20 00:01:00',
               '2017-05-20 00:02:00', '2017-05-20 00:03:00',
               '2017-05-20 00:04:00', '2017-05-20 00:05:00',
               '2017-05-20 00:06:00', '2017-05-20 00:07:00',
               '2017-05-20 00:08:00', '2017-05-20 00:09:00',
               '2017-05-20 00:10:00', '2017-05-20 00:11:00',
               '2017-05-20 00:12:00', '2017-05-20 00:13:00',
               '2017-05-20 00:14:00'],
              dtype='datetime64[ns]', freq='T')
              
df = pd.DataFrame({'time':times, 'value':np.arange(N)})
df

여기서 time으로 색인한 후 리샘플해보자.

df.set_index('time').resample('5min').count()

key 컬럼으로 구분되는 여러 시계열 데이터를 담고 있는 DataFrame을 생각해보자.

df2 = pd.DataFrame({'time':times.repeat(3),
                   'key':np.tile(['a','b','c'], N),
                   'value':np.arange(N*3.)})