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