[Pandas 기초] 그룹(group)객체 생성 및 집계(agg) 연산

업데이트:

1. 그룹 객체 만들기 : groupby()

groupby()함수는 그룹객체를 만들어주는 함수로 Dataframe객체.groupby(기준이 되는 열이름)로 사용된다.

이번에도 타이타닉 데이터를 불러와 특정열만 가져와보자.

import pandas as pd
import seaborn as sns

titanic = sns.load_dataset('titanic')
df = titanic[['age','sex','class','fare','survived']]
print("승객 수 :", len(df))
print(df.head())
[Outpt]
승객 수 : 891
    age     sex  class     fare  survived
0  22.0    male  Third   7.2500         0
1  38.0  female  First  71.2833         1
2  26.0  female  Third   7.9250         1
3  35.0  female  First  53.1000         1
4  35.0    male  Third   8.0500         0

class열에는 first, second, third라는 3개의 값들이 들어 있다. 이 열을 기준으로 그룹객체를 생성해보자.

grouped = df.groupby('class')
print(grouped)
print(type(grouped))
[Output]
<pandas.core.groupby.groupby.DataFrameGroupBy object at 0x0000021974982A90>
<class 'pandas.core.groupby.groupby.DataFrameGroupBy'>

1-1. 그룹객체에 대한 반복문(for문)

그룹객체는 반복문을 이용할 수 있다.

for i in grouped:
    print(type(i))
    
[Output]
<class 'tuple'>
<class 'tuple'>
<class 'tuple'>

총 그룹이 3개이므로, 3개의 튜플형태를 반환한 것을 알 수 있다. 튜플형태로 for문을 다시 설정하여 살펴보자.

그룹객체는 그룹이름(key)과 그룹으로 쪼개진 데이터(group)를 튜플형태로 묶어서 가지고 있는다. 또한 행 인덱스 번호를 확인해보면 잘라놓은 것 처럼, 기존의 행 인덱스번호를 그대로 가져오는 것을 알 수 있다.

for key, group in grouped:
    print("* key", key)
    print("* count", len(group))
    print(group.head())
    print('\n')
[Output]
* key First
* count 216
     age     sex  class     fare  survived
1   38.0  female  First  71.2833         1
3   35.0  female  First  53.1000         1
6   54.0    male  First  51.8625         0
11  58.0  female  First  26.5500         1
23  28.0    male  First  35.5000         1

* key Second
* count 184
     age     sex   class     fare  survived
9   14.0  female  Second  30.0708         1
15  55.0  female  Second  16.0000         1
17   NaN    male  Second  13.0000         1
20  35.0    male  Second  26.0000         0
21  34.0    male  Second  13.0000         1

* key Third
* count 491
    age     sex  class     fare  survived
0  22.0    male  Third   7.2500         0
2  26.0  female  Third   7.9250         1
4  35.0    male  Third   8.0500         0
5   NaN    male  Third   8.4583         0
7   2.0    male  Third  21.0750         0

2. 그룹객체에 대한 일반 연산

2-1. 그룹별 평균 : group객체.mean()

이렇게 생성한 그룹객체에 대해 연산함수를 적용하면 개별 그룹별(위에 짤려진 데이터별)로 연산을 수행해준다.

그룹별 평균값을 구해보자.

grouped.mean()
age fare survived
class
First 38.233441 84.154687 0.629630
Second 29.877630 20.662183 0.472826
Third 25.140620 13.675550 0.242363

결과를 보면 연산이 가능한(숫자형)열에 대해서만 결과를 반환해준 것을 알 수 있다.

2-2. 그룹 추출 : group객체.get_group('그룹이름')

get_group() 함수를 그룹객체에 적용해 나눠진 그룹중 가져오고 싶은 그룹만 가져올 수도 있다.

First그룹만 추출해보자

group1 = grouped.get_group('First')
group1.head()
age sex class fare survived
1 38.0 female First 71.2833 1
3 35.0 female First 53.1000 1
6 54.0 male First 51.8625 0
11 58.0 female First 26.5500 1
23 28.0 male First 35.5000 1

2-3. 여러 열을 기준으로 그룹객체 생성

한가지 열만 기준으로 하지않고 여러 열을 기준으로 하고싶을 경우,
dataframe객체.groupby(기준 열 리스트)형태로 사용할 수 있다.

아래의 경우 class를 기준으로 먼저 그룹하고 그 안에서 다시 sex로 그룹화하는 것을 알 수있다. 리스트 자료형은 순서가 존재하는 자료형이기 때문이다.
그러면 경우의 수 (3*2), 6가지 그룹을 생성한다.

grouped_two = df.groupby(['class','sex'])
for key, group in grouped_two:
    print("* key", key)
    print("* count", len(group))
    print(group.head())
    print('\n')
[Output]
* key ('First', 'female')
* count 94
     age     sex  class      fare  survived
1   38.0  female  First   71.2833         1
3   35.0  female  First   53.1000         1
11  58.0  female  First   26.5500         1
31   NaN  female  First  146.5208         1
52  49.0  female  First   76.7292         1

* key ('First', 'male')
* count 122
     age   sex  class      fare  survived
6   54.0  male  First   51.8625         0
23  28.0  male  First   35.5000         1
27  19.0  male  First  263.0000         0
30  40.0  male  First   27.7208         0
34  28.0  male  First   82.1708         0

* key ('Second', 'female')
* count 76
     age     sex   class     fare  survived
9   14.0  female  Second  30.0708         1
15  55.0  female  Second  16.0000         1
41  27.0  female  Second  21.0000         0
43   3.0  female  Second  41.5792         1
53  29.0  female  Second  26.0000         1

* key ('Second', 'male')
* count 108
     age   sex   class  fare  survived
17   NaN  male  Second  13.0         1
20  35.0  male  Second  26.0         0
21  34.0  male  Second  13.0         1
33  66.0  male  Second  10.5         0
70  32.0  male  Second  10.5         0

* key ('Third', 'female')
* count 144
     age     sex  class     fare  survived
2   26.0  female  Third   7.9250         1
8   27.0  female  Third  11.1333         1
10   4.0  female  Third  16.7000         1
14  14.0  female  Third   7.8542         0
18  31.0  female  Third  18.0000         0

* key ('Third', 'male')
* count 347
     age   sex  class     fare  survived
0   22.0  male  Third   7.2500         0
4   35.0  male  Third   8.0500         0
5    NaN  male  Third   8.4583         0
7    2.0  male  Third  21.0750         0
12  20.0  male  Third   8.0500         0

key값을 보면 한개의 그룹이름이 아닌, 두개의 요소를 가진 튜플임을 알 수 있다. 이것을 멀티인덱스(MultiIndex)라고 하며, 다음 포스팅에서 알아볼 것이다.

이상태에서 다시 집계함수를 적용하면,
등석(class)별, 성(sex)별 평균 연령대와 요금, 생존률을 확인할 수 있다.

grouped_two.mean()
age fare survived
class sex
First female 34.611765 106.125798 0.968085
male 41.281386 67.226127 0.368852
Second female 28.722973 21.970121 0.921053
male 30.740707 19.741782 0.157407
Third female 21.750000 16.118810 0.500000
male 26.507589 12.661633 0.135447

2-4. 여러가지 그룹연산 메소드

판다스에 내장된 기본 집계함수는 다음과 같다.
mean(), max(), min(), sum(), count(), size(), var(), std(), describe(), info(), first(), last() 등

다시 타이타닉 데이터를 그룹핑하고

grouped = df.groupby('class')

이번엔 표준편차를 구해보자

grouped.std()
age fare survived
class
First 14.802856 78.380373 0.484026
Second 14.001077 13.417399 0.500623
Third 12.495398 11.778142 0.428949

3. 활용도가 높은 고급 그룹연산

3-1. 여러개의 함수를 여러 열에 적용 : agg()함수

사용자 정의함수를 적용하고 싶다면?

def min_max(x):
    return x.max()-x.min()

일종의 매핑함수를 적용하는 것과 비슷하다.

grouped.agg(min_max)
age fare survived
class
First 79.08 512.3292 1
Second 69.33 73.5000 1
Third 73.58 69.5500 1

survived열을 0과 1을 가지므로 당연히 1밖에 될 수 없다.

사실 사용자 정의함수보다는 agg()함수를 많이 사용하는 이유는 여러개의 열에 여러가지 함수를 적용할 수 있기 때문이다.

모든열에 여러 함수를 매핑 : group객체.agg([함수1,함수2,함수3,…])
각 열마다 다른 함수를 매핑 : group객체.agg({‘열1’: 함수1, ‘열2’:함수2, …})

print(grouped.agg(['min','max']))
print('\n')
print(grouped.agg({'fare':['min','max'], 'age':'mean'}))
[Output]
         age           sex       fare           survived    
         min   max     min   max  min       max      min max
class                                                       
First   0.92  80.0  female  male  0.0  512.3292        0   1
Second  0.67  70.0  female  male  0.0   73.5000        0   1
Third   0.42  74.0  female  male  0.0   69.5500        0   1


       fare                  age
        min       max       mean
class                           
First   0.0  512.3292  38.233441
Second  0.0   73.5000  29.877630
Third   0.0   69.5500  25.140620

3-2. 연산 후 기존 데이터프레임의 형태로 : transfrom()

transform()함수는 그룹별로 매핑함수를 적용하긴 하지만, 그룹별로 집계하지 않고 원래 데이터프레임의 형태로 반환해주는 차이가 있다.

age열을 정규화하는 일반적 방법과 transform()을 활용한 방법 두가지를 비교하면서 알아보자

age_mean = grouped.age.mean()
age_std = grouped.age.std()

for key, group in grouped.age:
    group_zscore = (group - age_mean.loc[key]) / age_std.loc[key]
    print("* origin :", key)
    print(group_zscore.head(3))
    print('\n')
[Output]
* origin : First
1   -0.015770
3   -0.218434
6    1.065103
Name: age, dtype: float64


* origin : Second
9    -1.134029
15    1.794317
17         NaN
Name: age, dtype: float64


* origin : Third
0   -0.251342
2    0.068776
4    0.789041
Name: age, dtype: float64

단순히 for문을 이용해 함수를 적용하면, 이렇게 3개의 그룹 각각에 대해 연산하여 집계하는 일반적이다.

이번에는 transform()함수를 이용해보자.

def z_score(x):
    return (x - x.mean())/ x.std()
    
grouped.age.transform(z_score).head(10)
[Output]
0   -0.251342
1   -0.015770
2    0.068776
3   -0.218434
4    0.789041
5         NaN
6    1.065103
7   -1.851931
8    0.148805
9   -1.134029
Name: age, dtype: float64

이렇게 결과적으로는 그룹연산이 아닌것 같지만, 그룹을 기준으로 연산을 하고 그 결과는 다시 데이터프레임의 형태로 돌려준 것이다.

이해가 되지 않는다면 앞에서 구한 그룹별 집계의 첫번째 row의 인덱스를 추출해서 비교해보면, 같음을 알 수 있다.

grouped.age.transform(z_score).iloc[[1,9,0]]
[Output]
1   -0.015770
9   -1.134029
0   -0.251342
Name: age, dtype: float64

3-3. 그룹객체 필터링 : filter()

그룹객체에 대한 filter()함수는 내장함수filter함수와는 전혀 다른 기능을 한다.

group객체.filter(조건식 함수)로 사용하며, 개별원소에 대한 필터링이 아니라, group객체를 필터링 한다는 것을 유념해야 한다.
즉, 그룹핑한 그룹들중에 가져올 그룹과 안가져올 그룹을 필터링 하는 게 핵심이다.
추가로 그룹객체에 매핑함수를 적용할때는 lambda x에 해당하는 x가 각각의 그룹들이라는 것을 생각하자.

그렇다면, 그룹별 row수가 200개 이상인 그룹만 뽑아와보자.

grouped = df.groupby('class')
test = grouped.filter(lambda x : len(x) >= 200)
print(test.head())
print('\n')
print(test['class'].unique())
[Output]
    age     sex  class     fare  survived
0  22.0    male  Third   7.2500         0
1  38.0  female  First  71.2833         1
2  26.0  female  Third   7.9250         1
3  35.0  female  First  53.1000         1
4  35.0    male  Third   8.0500         0

[Third, First]
Categories (2, object): [Third, First]

second그룹은 필터링되어 추출되지 않은 것도 확인했다.

이번에는 각 그룹의 age열의 평균이 30미만인 그룹만 불러오자.

test = grouped.filter(lambda x : x.age.mean() < 30)
print(test.head())
print('\n')
print(test['class'].unique())

[Output]
    age     sex  class     fare  survived
0  22.0    male  Third   7.2500         0
2  26.0  female  Third   7.9250         1
4  35.0    male  Third   8.0500         0
5   NaN    male  Third   8.4583         0
7   2.0    male  Third  21.0750         0

[Third, Second]
Categories (2, object): [Third, Second]

3-4. 그룹객체에 함수 매핑 : apply()

apply()함수는 앞의 함수매핑포스팅에서도 봤었다.
group객체.apply(매핑함수)로 사용되며, 개별 원소가 아닌 그룹별 매핑이 기준임을 명심하자.

그러면 각 그룹에 대한 기초통계 정보를 확인해보자.

grouped.apply(lambda x : x.describe())
age fare survived
class
First count 186.000000 216.000000 216.000000
mean 38.233441 84.154687 0.629630
std 14.802856 78.380373 0.484026
min 0.920000 0.000000 0.000000
25% 27.000000 30.923950 0.000000
50% 37.000000 60.287500 1.000000
75% 49.000000 93.500000 1.000000
max 80.000000 512.329200 1.000000
Second count 173.000000 184.000000 184.000000
mean 29.877630 20.662183 0.472826
std 14.001077 13.417399 0.500623
min 0.670000 0.000000 0.000000
25% 23.000000 13.000000 0.000000
50% 29.000000 14.250000 0.000000
75% 36.000000 26.000000 1.000000
max 70.000000 73.500000 1.000000
Third count 355.000000 491.000000 491.000000
mean 25.140620 13.675550 0.242363
std 12.495398 11.778142 0.428949
min 0.420000 0.000000 0.000000
25% 18.000000 7.750000 0.000000
50% 24.000000 8.050000 0.000000
75% 32.000000 15.500000 0.000000
max 74.000000 69.550000 1.000000

응용 : 평균나이가 30미만인 그룹들의 데이터를 출력해라

apply()함수와 지금까지 배운 그룹객체 및 연산법 응용해서 위를 수행해보자.

age_filter = grouped.apply(lambda x : x.age.mean() < 30)
print(age_filter)
print('\n')

for i in age_filter.index:
    if age_filter[i] :
        print(grouped.get_group(i).head())
        print('\n')
[Output]
class
First     False
Second     True
Third      True
dtype: bool


     age     sex   class     fare  survived
9   14.0  female  Second  30.0708         1
15  55.0  female  Second  16.0000         1
17   NaN    male  Second  13.0000         1
20  35.0    male  Second  26.0000         0
21  34.0    male  Second  13.0000         1


    age     sex  class     fare  survived
0  22.0    male  Third   7.2500         0
2  26.0  female  Third   7.9250         1
4  35.0    male  Third   8.0500         0
5   NaN    male  Third   8.4583         0
7   2.0    male  Third  21.0750         0


Reference

도서 [파이썬 머신러닝 판다스 데이터 분석]을 공부하며 작성하였습니다.

댓글남기기