[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
도서 [파이썬 머신러닝 판다스 데이터 분석]을 공부하며 작성하였습니다.
댓글남기기