엑셀, csv 데이터를 다루면서 특정 칼럼 값이 같은 행들만 추리는 작업을 많이 해보셨을겁니다. 엑셀에서는 filter 기능을 이용해 손쉽게 할 수 있지만 pandas 라이브러리에서는 groupby 함수를 이용해야 하는데요, 처음 groupby 함수를 사용하다보면 동작 방식이 명확히 이해되지 않았습니다. 이번 포스트에서는 미국 역대 의원 정보를 담은 다음 데이터를 이용해서 pandas groupby 함수의 동작 방식을 살펴보도록 하겠습니다.
먼저 데이터를 읽습니다. 문자열로 되어있는 칼럼은 공간효율성을 위해 category 타입으로 dtype을 정의합니다.
import pandas as pd
dtypes = {
"first_name": "category",
"gender": "category",
"type": "category",
"state": "category",
"party": "category",
}
df = pd.read_csv(
"groupby-data/legislators-historical.csv",
dtype=dtypes,
usecols=list(dtypes) + ["birthday", "last_name"],
parse_dates=["birthday"]
)
>>> df.tail()
last_name first_name birthday gender type state party
12044 Wright Ron 1953-04-08 M rep TX Republican
12045 Fudge Marcia 1952-10-29 F rep OH Democrat
12046 Haaland Debra 1960-12-02 F rep NM Democrat
12047 Hastings Alcee 1936-09-05 M rep FL Democrat
12048 Stivers Steve 1965-03-24 M rep OH Republican
Grouping할 컬럼으로는 주, 성별 (state, gender)가 있겠네요. 먼저 state, gender 칼럼을 이용해 groupby 함수를 수행해서 state/gender가 일치하는 행이 몇 개인지 살펴보겠습니다. 결과를 보면 pandas.series 데이터 타입이 되고 인덱스가 MultiIndex 형태가 됩니다. 만약, dataframe 형식으로 만드려면은 groupby 함수에서 as_index=False로 설정합니다. (이때 index는 RangeIndex가 됩니다.)
>>> df.groupby(["state", "gender"])["last_name"].count()
state gender
AK F 0
M 16
AL F 4
M 205
AR F 5
...
WI M 198
WV F 1
M 119
WY F 1
M 39
Name: last_name, Length: 116, dtype: int64
>>> df.groupby(["state", "gender"], as_index=False)["last_name"].count()
state gender last_name
0 AK F 0
1 AK M 16
2 AL F 4
3 AL M 205
4 AR F 5
.. ... ... ...
111 WI M 198
112 WV F 1
113 WV M 119
114 WY F 1
115 WY M 39
[116 rows x 3 columns]
그렇다면 groupby 함수는 어떻게 동작할까요? groupby 함수만 적용하면 다음과 같이 DataFrameGroupby 객체가 리턴되고 아무것도 안하는 것처럼 보입니다. 즉, groupby 함수 객체에 무엇인가를 더 해주어야만 (.count() 함수와 같은 메소드) 실제 결과물이 출력된다는 것이죠.
>>> df.groupby(['state', 'gender'])
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x00000180DBABAB80>
groupby 함수는 1) 테이블을 그룹으로 나누는 split, 2) 나눠진 그룹에 대해 어떤 operation을 적용하는 apply, 3) operation이 적용된 결과물을 합치는 combine 순서로 동작합니다. DataFrameGroupBy 객체는 __iter__() 메소드가 정의되어 iteration을 거침으로서 1번 split 과정을 볼 수 있습니다.
>>> by_state = df.groupby("state")
for state, frame in by_state:
print(f"First 2 entries for {state!r}")
print("------------------------")
print(frame.head(2), end="\n\n")
First 2 entries for 'AK'
------------------------
last_name first_name birthday gender type state party
6617 Waskey Frank 1875-04-20 M rep AK Democrat
6645 Cale Thomas 1848-09-17 M rep AK Independent
First 2 entries for 'AL'
------------------------
last_name first_name birthday gender type state party
911 Crowell John 1780-09-18 M rep AL Republican
990 Walker John 1783-08-12 M sen AL Republican
또한, GroupBy 객체의 .groups 속성은 {group name: group label} 사전 형태의 grouping 결과를 제공하고 .get_group() 함수는 split된 특정 그룹을 불러옵니다. 이것은 (df.loc[df["state"] == "PA"] 의 동작과 같죠.)
>>> by_state.groups["PA"]
Int64Index([ 4, 19, 21, 27, 38, 57, 69, 76, 84,
88,
...
11838, 11862, 11871, 11873, 11883, 11887, 11926, 11938, 11952,
11965],
dtype='int64', length=1053)
>>> by_state.get_group("PA")
last_name first_name birthday gender type state party
4 Clymer George 1739-03-16 M rep PA NaN
19 Maclay William 1737-07-20 M sen PA Anti-Administration
21 Morris Robert 1734-01-20 M sen PA Pro-Administration
27 Wynkoop Henry 1737-03-02 M rep PA NaN
38 Jacobs Israel 1726-06-09 M rep PA NaN
... ... ... ... ... ... ...
11887 Brady Robert 1945-04-07 M rep PA Democrat
11926 Shuster Bill 1961-01-10 M rep PA Republican
11938 Rothfus Keith 1962-04-25 M rep PA Republican
11952 Costello Ryan 1976-09-07 M rep PA Republican
11965 Marino Tom 1952-08-15 M rep PA Republican
[1053 rows x 7 columns]
또한, GroupBy 객체가 iterator 이므로 next/iter 함수를 이용할 수 있고 이를 사용하면 group 명, group table을 얻을 수 있습니다.
>>> state, frame = next(iter(by_state))
>>> state
'AK'
>>> frame.head(3)
last_name first_name birthday gender type state party
6617 Waskey Frank 1875-04-20 M rep AK Democrat
6645 Cale Thomas 1848-09-17 M rep AK Independent
7440 Grigsby George 1874-12-02 M rep AK NaN
이후의 apply 단계는 각 subgroup 별로 지정한 칼럼에 대해 특정한 operation을 적용합니다. 이후에 combine 단계는 operation을 적용한 결과를 전체 데이터프레임으로 합치는 것이죠. 종합해보면 groupby 함수 자체는 split을 수행하고 지정한 칼럼에 대해서 subgroup 순대로 iteration을 돌면서 operation을 적용한 뒤 합치는 과정이라 볼 수 있습니다.
>>> frame["last_name"].count()
16
만약 칼럼을 따로 지정하지 않고 operation만 적용한다면 groupby 대상 칼럼 이외의 칼럼에 대한 통계치를 데이터프레임 타입으로 제공합니다. 따라서 operation을 적용하고 컬럼을 filtering 하거나 컬럼을 지정하고 operation을 수행해도 동일한 결과가 리턴됩니다.
df = pd.DataFrame({'group': ['a', 'a', 'a', 'b', 'b', 'b'],
'value_1': np.arange(6),
'value_2': np.random.randn(6)})
>>> df.groupby('group').mean()
value_1 value_2
group
a 1.0 0.270852
b 4.0 -0.917279
>> df.groupby('group').mean()['value_1']
group
a 1.0
b 4.0
Name: value_1, dtype: float64
>>> df.groupby('group')['value_1'].mean()
group
a 1.0
b 4.0
Name: value_1, dtype: float64
'Computer > Pandas' 카테고리의 다른 글
Pandas - SettingWithCopyWarning (0) | 2021.08.11 |
---|---|
Pandas groupby (2) (0) | 2021.08.07 |
Pandas Categorical Data (0) | 2021.08.01 |
Onehot 인코딩의 역변환 (inverse transform) (0) | 2021.07.25 |
Pandas Multiple Columns Label Encoding (1) | 2021.07.14 |