2.4. 데이터프레임의 그룹화#
앞서 데이터프레임의 요약에서는 전체 데이터를 요약(count, mean, 분위수 등)했었다. 예를 들어, 2021년의 강남구 데이터의 데이터를 요약하려면 전체 데이터에서 해당 데이터를 추출하여 사용했다. 이 절에서는 그룹별로 한번에 요약하는 방법에 대해 알아보고자 한다. 살펴볼 내용은 다음과 같다.
그룹별 요약
여러 개의 그룹
다양한 집계들
간단한 예제#
먼저 예제 데이터로 실습해보자.
다음과 같은 간단한 데이터프레임 df 을 고려해 보자. 열 school 은 학교, sex는 성, score 는 점수를 나타낸다고 하자.
import pandas as pd
test = pd.DataFrame( {
"school" : ["A", "A", "A", "A", "B", "B", "B"],
"sex" : ["M", "M", "F", "F", "M", "F", "F"],
"score" : [100, 98, 34, 83, 56, 90, 65]
})
test
| school | sex | score | |
|---|---|---|---|
| 0 | A | M | 100 |
| 1 | A | M | 98 |
| 2 | A | F | 34 |
| 3 | A | F | 83 |
| 4 | B | M | 56 |
| 5 | B | F | 90 |
| 6 | B | F | 65 |
이제 데이터프레임 df 에서 학교별, 성별로 성적의 평균을 구해보자.
먼저 데이터프레임 df 을 학교별, 성별로 나누어 그룹회된 데이터프레임을 만들고 각 그룹에 대한 성적의 평균을 구해보자.
test.groupby( by =["school", "sex"]).mean()
| score | ||
|---|---|---|
| school | sex | |
| A | F | 58.5 |
| M | 99.0 | |
| B | F | 77.5 |
| M | 56.0 |
이렇게 데이터프레임은 groupby() 메소드를 이용하여 여러 개의 그룹으로 나누어 요약할 수 있다.
만약 그룹으로 나누는데 사용하는 열의 자료형식이 숫자면 어떤 일이 벌어질까. 자료의 범주가 숫자로 나타나는 경우가 많은데 이 경우 그룹화에 대하여 알아보자.
먼저 위에서 데이터프레임 df 과 동일한 자료를 가지지만 df2 는 여자는 1, 남자는 0 으로 자료가 저장되어 있다고 하자.
test2 = pd.DataFrame( {
"school" : ["A", "A", "A", "A", "B", "B", "B"],
"sex" : [0, 0, 1, 1, 0, 0, 1],
"score" : [100, 98, 34, 83, 56, 90, 65]
})
test2
| school | sex | score | |
|---|---|---|---|
| 0 | A | 0 | 100 |
| 1 | A | 0 | 98 |
| 2 | A | 1 | 34 |
| 3 | A | 1 | 83 |
| 4 | B | 0 | 56 |
| 5 | B | 0 | 90 |
| 6 | B | 1 | 65 |
메소드 groupby() 는 숫자로 구성된 열도 나타난 숫자에 따라서 그룹화를 해준다.
test2.groupby( by =["school", "sex"]).mean()
| score | ||
|---|---|---|
| school | sex | |
| A | 0 | 99.0 |
| 1 | 58.5 | |
| B | 0 | 73.0 |
| 1 | 65.0 |
그룹화에 사용된 열들을 인덱스에서 다시 열로 바꾸고 싶으면 reset_index()를 적용하자.
test2.groupby( by =["school", "sex"]).mean().reset_index()
| school | sex | score | |
|---|---|---|---|
| 0 | A | 0 | 99.0 |
| 1 | A | 1 | 58.5 |
| 2 | B | 0 | 73.0 |
| 3 | B | 1 | 65.0 |
화재출동 데이터#
화재출동 데이터(fire_calling_summary.csv)을 이용해 간단한 화재출동 현황을 요약하려 한다.
2017년부터 2021년의 5년간 서울시 모든 구의 화재출동 데이터입니다.
import pandas as pd
df = pd.read_csv("https://uos-bigdata.github.io/lab_data/docs/assets/data_lab_fire/fire_calling.csv", encoding='cp949')
df
| 화재발생연도 | 시군구 | 사망자수 | 부상자수 | 재산피해금액 | 출동횟수 | 출동횟수_겨울 | 출동횟수_여름 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2017 | 강남구 | NaN | 11 | 1565258 | 502 | 129 | 120 |
| 1 | 2017 | 강동구 | NaN | 12 | 418593 | 269 | 63 | 73 |
| 2 | 2017 | 강북구 | NaN | 6 | 339146 | 186 | 51 | 41 |
| 3 | 2017 | 강서구 | 3.0 | 22 | 706871 | 364 | 89 | 81 |
| 4 | 2017 | 관악구 | 3.0 | 20 | 654690 | 286 | 82 | 69 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 120 | 2021 | 용산구 | NaN | 5 | 296793 | 176 | 47 | 36 |
| 121 | 2021 | 은평구 | 3.0 | 8 | 875722 | 160 | 57 | 42 |
| 122 | 2021 | 종로구 | NaN | 12 | 465499 | 192 | 48 | 54 |
| 123 | 2021 | 중구 | NaN | 16 | 2780374 | 171 | 41 | 49 |
| 124 | 2021 | 중랑구 | 1.0 | 12 | 345257 | 213 | 63 | 59 |
125 rows × 8 columns
먼저 Nan값을 0으로 대체해준다.
df = df.fillna(0)
df
| 화재발생연도 | 시군구 | 사망자수 | 부상자수 | 재산피해금액 | 출동횟수 | 출동횟수_겨울 | 출동횟수_여름 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2017 | 강남구 | 0.0 | 11 | 1565258 | 502 | 129 | 120 |
| 1 | 2017 | 강동구 | 0.0 | 12 | 418593 | 269 | 63 | 73 |
| 2 | 2017 | 강북구 | 0.0 | 6 | 339146 | 186 | 51 | 41 |
| 3 | 2017 | 강서구 | 3.0 | 22 | 706871 | 364 | 89 | 81 |
| 4 | 2017 | 관악구 | 3.0 | 20 | 654690 | 286 | 82 | 69 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 120 | 2021 | 용산구 | 0.0 | 5 | 296793 | 176 | 47 | 36 |
| 121 | 2021 | 은평구 | 3.0 | 8 | 875722 | 160 | 57 | 42 |
| 122 | 2021 | 종로구 | 0.0 | 12 | 465499 | 192 | 48 | 54 |
| 123 | 2021 | 중구 | 0.0 | 16 | 2780374 | 171 | 41 | 49 |
| 124 | 2021 | 중랑구 | 1.0 | 12 | 345257 | 213 | 63 | 59 |
125 rows × 8 columns
먼저 연도별 5개 의 화재로 인한 출동횟수를 계산해 보자.
연도별 출동횟수를 계산하려면
전체 자료를 연도별로 나누고
각 연도에서 모든 시군구의 출동횟수를 더해야한다.
즉, 데이터프레임을 지정된 열(화재발생연도)의 값(2017, 2018, ..)에 따라서 그룹으로 나누어 전국 가구수를 계산해야 한다.
메소드 groupby( by=...)는 지정된 열의 값으로 데이터프레임을 그룹(group)으로 나누어 준다. 메소드 groupby( by=...)는 데이터프레임을 물리적으로 그룹을 나누어 주는 것은 아니고 메소드에서 지정된 그룹을 데이터프레임에 적용하는 기능을 한다.
by=는 그룹을 나눌 때 사용되는 열을 리스트 형식으로 지정한다.메소드
groupby( by=...)로 만들어진 데이터프레임의 자료 형식은DataFrameGroupBy이다. 따라서 보통 데이터프레임에 적용되는 메소드들을 사용할 수 없는 경우가 있다.
이제 데이터프레임 df 를 연도 화재발생연도를 이용하여 그룹화된 데이터프레임 df_grp을 만들어 보자.
df_grp = df.groupby( by=["화재발생연도"] )
그룹화된 데이터프레임 fire_grp 은 보통 데이터프레임과 다르게 출력해주는 내용이 없다. 그 이유는 그룹화된 데이터프레임은 그룹에 대한 정보만 가진 특별한 자료의 형식을 가진다.
df_grp
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x7ff6293389d0>
type(df_grp)
pandas.core.groupby.generic.DataFrameGroupBy
그룹별 요약#
이제 아래의 코드가 어떤 결과를 내는지 보자.
결과는 그룹으로 지정된 연도별로 모든 출동횟수를 더해서 연도별 출동횟수를 계산해준다.
주의할 점은 메소드
sum()에axis=선택문이 없다. 그 이유는 그룹화된 데이터프레임의sum()메소드는 언제나 그룹에 속한 모든 자료를 더해준다.
df_grp.sum()
/var/folders/3k/d8g2lfdn57b665jktbg_sglm0000gn/T/ipykernel_13278/3701876493.py:1: FutureWarning: The default value of numeric_only in DataFrameGroupBy.sum is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
df_grp.sum()
| 사망자수 | 부상자수 | 재산피해금액 | 출동횟수 | 출동횟수_겨울 | 출동횟수_여름 | |
|---|---|---|---|---|---|---|
| 화재발생연도 | ||||||
| 2017 | 37.0 | 246 | 15323890 | 5978 | 1465 | 1496 |
| 2018 | 53.0 | 307 | 21239984 | 6368 | 1748 | 1657 |
| 2019 | 37.0 | 361 | 92409010 | 5881 | 1476 | 1478 |
| 2020 | 37.0 | 236 | 17438606 | 5088 | 1244 | 1274 |
| 2021 | 37.0 | 280 | 18268346 | 4951 | 1367 | 1186 |
위의 결과를 보면 만들어진 데이터 프레임에서 다음과 같은 특성이 보인다.
열이름이 한 칸 위로 올라가 있다. 이는 문자로 된 열이름(column name)이 비어있다는 것이다.
행인덱스가 화재발생연도로 되어있으며 숫자로 된 행인덱스가 없는 것을 알 수 있다.
우리가 사용할 수 있는 유용한 데이터프레임을 만들기 위해서는 인덱스의 재구성이 필요하다. 메소드 reset_index()를 사용해 보자.
df_grp.sum().reset_index()
/var/folders/3k/d8g2lfdn57b665jktbg_sglm0000gn/T/ipykernel_13278/2650935280.py:1: FutureWarning: The default value of numeric_only in DataFrameGroupBy.sum is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
df_grp.sum().reset_index()
| 화재발생연도 | 사망자수 | 부상자수 | 재산피해금액 | 출동횟수 | 출동횟수_겨울 | 출동횟수_여름 | |
|---|---|---|---|---|---|---|---|
| 0 | 2017 | 37.0 | 246 | 15323890 | 5978 | 1465 | 1496 |
| 1 | 2018 | 53.0 | 307 | 21239984 | 6368 | 1748 | 1657 |
| 2 | 2019 | 37.0 | 361 | 92409010 | 5881 | 1476 | 1478 |
| 3 | 2020 | 37.0 | 236 | 17438606 | 5088 | 1244 | 1274 |
| 4 | 2021 | 37.0 | 280 | 18268346 | 4951 | 1367 | 1186 |
이제 연도별로 출동횟수 정보를 가진 새로운 데이터프레임 df_year를 만들어 보자.
df_year = df_grp.sum().reset_index()
df_year
/var/folders/3k/d8g2lfdn57b665jktbg_sglm0000gn/T/ipykernel_13278/3188372753.py:1: FutureWarning: The default value of numeric_only in DataFrameGroupBy.sum is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
df_year = df_grp.sum().reset_index()
| 화재발생연도 | 사망자수 | 부상자수 | 재산피해금액 | 출동횟수 | 출동횟수_겨울 | 출동횟수_여름 | |
|---|---|---|---|---|---|---|---|
| 0 | 2017 | 37.0 | 246 | 15323890 | 5978 | 1465 | 1496 |
| 1 | 2018 | 53.0 | 307 | 21239984 | 6368 | 1748 | 1657 |
| 2 | 2019 | 37.0 | 361 | 92409010 | 5881 | 1476 | 1478 |
| 3 | 2020 | 37.0 | 236 | 17438606 | 5088 | 1244 | 1274 |
| 4 | 2021 | 37.0 | 280 | 18268346 | 4951 | 1367 | 1186 |
새로운 정보#
이제 연도별로 나타난 정보에 대한 모든 열을 더하면 전체 값이 나온다. 전체 값을 새로운 변수 total 로 데이터프레임 fire_year에 추가하자.
df_year["total"] = df_year.sum(axis=1)
df_year
| 화재발생연도 | 사망자수 | 부상자수 | 재산피해금액 | 출동횟수 | 출동횟수_겨울 | 출동횟수_여름 | total | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2017 | 37.0 | 246 | 15323890 | 5978 | 1465 | 1496 | 15335129.0 |
| 1 | 2018 | 53.0 | 307 | 21239984 | 6368 | 1748 | 1657 | 21252135.0 |
| 2 | 2019 | 37.0 | 361 | 92409010 | 5881 | 1476 | 1478 | 92420262.0 |
| 3 | 2020 | 37.0 | 236 | 17438606 | 5088 | 1244 | 1274 | 17448505.0 |
| 4 | 2021 | 37.0 | 280 | 18268346 | 4951 | 1367 | 1186 | 18278188.0 |
!잠깐
위에서 사용한 메소드
sum(axis=1)는 모든 열 들을 더해주는 작업을 수행하는데 년도를 나타내는 열화재발생연도도 같이 결과에 더해졌다. 그리고 필요하지 않은 데이터까지 더해진 것을 볼 수 있다.앞에서 본 것 처럼
sum(axis=1)는 자동적으로 문자열을 포함한 열을 제외해주는데 이러한 자동 기능을 자주 이용하면 위와 같은 실수를 범할 수 있다. 왜냐하면 오류가 나오지 않기 떄문이다.안전한 프로그래밍을 위하여 합을 구할 모든 열의 이름을 가진 리스트로 슬라이싱을 먼저 하고 메소드
sum(axis=1)를 적용하자.
df_year["여름겨울출동횟수"] = df_year[ ["출동횟수_겨울", "출동횟수_여름"] ].sum(axis=1)
df_year
| 화재발생연도 | 사망자수 | 부상자수 | 재산피해금액 | 출동횟수 | 출동횟수_겨울 | 출동횟수_여름 | total | 여름겨울출동횟수 | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 2017 | 37.0 | 246 | 15323890 | 5978 | 1465 | 1496 | 15335129.0 | 2961 |
| 1 | 2018 | 53.0 | 307 | 21239984 | 6368 | 1748 | 1657 | 21252135.0 | 3405 |
| 2 | 2019 | 37.0 | 361 | 92409010 | 5881 | 1476 | 1478 | 92420262.0 | 2954 |
| 3 | 2020 | 37.0 | 236 | 17438606 | 5088 | 1244 | 1274 | 17448505.0 | 2518 |
| 4 | 2021 | 37.0 | 280 | 18268346 | 4951 | 1367 | 1186 | 18278188.0 | 2553 |
이제 출동횟수에 대한 백분율(percentage)를 새롭게 만들어 보자.
df_year['percent'] = df_year['여름겨울출동횟수'] / df_year['출동횟수'] *100
df_year
| 화재발생연도 | 사망자수 | 부상자수 | 재산피해금액 | 출동횟수 | 출동횟수_겨울 | 출동횟수_여름 | total | 여름겨울출동횟수 | percent | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2017 | 37.0 | 246 | 15323890 | 5978 | 1465 | 1496 | 15335129.0 | 2961 | 49.531616 |
| 1 | 2018 | 53.0 | 307 | 21239984 | 6368 | 1748 | 1657 | 21252135.0 | 3405 | 53.470477 |
| 2 | 2019 | 37.0 | 361 | 92409010 | 5881 | 1476 | 1478 | 92420262.0 | 2954 | 50.229553 |
| 3 | 2020 | 37.0 | 236 | 17438606 | 5088 | 1244 | 1274 | 17448505.0 | 2518 | 49.488994 |
| 4 | 2021 | 37.0 | 280 | 18268346 | 4951 | 1367 | 1186 | 18278188.0 | 2553 | 51.565340 |
편의상 percent 열을 소수점 2째 자리에서 반올림해보자 (실습)
접기/펼치기
df_year['percent'] = df_year['percent'].round(2)
df_year
여러 개의 그룹#
화재출동 데이터의 그룹화 기준을 시군구, 화재발생연도로 적용해보자.
먼저 데이터를 나누어보자. 그룹화에 사용할 5개 구를 선택해서 df5으로 저장한다. 이 경우 은평구를 기준으로 나머지 구들을 추출하였다.
df5 = df[df['시군구'] >= '은평구']
df5
| 화재발생연도 | 시군구 | 사망자수 | 부상자수 | 재산피해금액 | 출동횟수 | 출동횟수_겨울 | 출동횟수_여름 | |
|---|---|---|---|---|---|---|---|---|
| 21 | 2017 | 은평구 | 0.0 | 3 | 218200 | 159 | 51 | 32 |
| 22 | 2017 | 종로구 | 1.0 | 3 | 1077665 | 234 | 55 | 69 |
| 23 | 2017 | 중구 | 5.0 | 14 | 485392 | 198 | 48 | 47 |
| 24 | 2017 | 중랑구 | 2.0 | 5 | 332366 | 196 | 53 | 38 |
| 46 | 2018 | 은평구 | 5.0 | 10 | 419503 | 214 | 58 | 47 |
| 47 | 2018 | 종로구 | 14.0 | 22 | 574300 | 254 | 71 | 70 |
| 48 | 2018 | 중구 | 0.0 | 23 | 1257005 | 275 | 76 | 74 |
| 49 | 2018 | 중랑구 | 2.0 | 8 | 201421 | 254 | 72 | 55 |
| 71 | 2019 | 은평구 | 3.0 | 20 | 2412769 | 196 | 62 | 34 |
| 72 | 2019 | 종로구 | 4.0 | 16 | 801094 | 232 | 60 | 63 |
| 73 | 2019 | 중구 | 3.0 | 17 | 74077097 | 213 | 51 | 39 |
| 74 | 2019 | 중랑구 | 1.0 | 9 | 322650 | 210 | 54 | 49 |
| 96 | 2020 | 은평구 | 2.0 | 6 | 504788 | 192 | 48 | 46 |
| 97 | 2020 | 종로구 | 2.0 | 5 | 639751 | 217 | 50 | 49 |
| 98 | 2020 | 중구 | 0.0 | 10 | 1284422 | 185 | 41 | 54 |
| 99 | 2020 | 중랑구 | 2.0 | 12 | 229566 | 225 | 54 | 57 |
| 121 | 2021 | 은평구 | 3.0 | 8 | 875722 | 160 | 57 | 42 |
| 122 | 2021 | 종로구 | 0.0 | 12 | 465499 | 192 | 48 | 54 |
| 123 | 2021 | 중구 | 0.0 | 16 | 2780374 | 171 | 41 | 49 |
| 124 | 2021 | 중랑구 | 1.0 | 12 | 345257 | 213 | 63 | 59 |
인덱스를 초기화해준다.
df5.reset_index(drop = True, inplace = True)
df5
| 화재발생연도 | 시군구 | 사망자수 | 부상자수 | 재산피해금액 | 출동횟수 | 출동횟수_겨울 | 출동횟수_여름 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2017 | 은평구 | 0.0 | 3 | 218200 | 159 | 51 | 32 |
| 1 | 2017 | 종로구 | 1.0 | 3 | 1077665 | 234 | 55 | 69 |
| 2 | 2017 | 중구 | 5.0 | 14 | 485392 | 198 | 48 | 47 |
| 3 | 2017 | 중랑구 | 2.0 | 5 | 332366 | 196 | 53 | 38 |
| 4 | 2018 | 은평구 | 5.0 | 10 | 419503 | 214 | 58 | 47 |
| 5 | 2018 | 종로구 | 14.0 | 22 | 574300 | 254 | 71 | 70 |
| 6 | 2018 | 중구 | 0.0 | 23 | 1257005 | 275 | 76 | 74 |
| 7 | 2018 | 중랑구 | 2.0 | 8 | 201421 | 254 | 72 | 55 |
| 8 | 2019 | 은평구 | 3.0 | 20 | 2412769 | 196 | 62 | 34 |
| 9 | 2019 | 종로구 | 4.0 | 16 | 801094 | 232 | 60 | 63 |
| 10 | 2019 | 중구 | 3.0 | 17 | 74077097 | 213 | 51 | 39 |
| 11 | 2019 | 중랑구 | 1.0 | 9 | 322650 | 210 | 54 | 49 |
| 12 | 2020 | 은평구 | 2.0 | 6 | 504788 | 192 | 48 | 46 |
| 13 | 2020 | 종로구 | 2.0 | 5 | 639751 | 217 | 50 | 49 |
| 14 | 2020 | 중구 | 0.0 | 10 | 1284422 | 185 | 41 | 54 |
| 15 | 2020 | 중랑구 | 2.0 | 12 | 229566 | 225 | 54 | 57 |
| 16 | 2021 | 은평구 | 3.0 | 8 | 875722 | 160 | 57 | 42 |
| 17 | 2021 | 종로구 | 0.0 | 12 | 465499 | 192 | 48 | 54 |
| 18 | 2021 | 중구 | 0.0 | 16 | 2780374 | 171 | 41 | 49 |
| 19 | 2021 | 중랑구 | 1.0 | 12 | 345257 | 213 | 63 | 59 |
시군구를 기준으로 각 열의 평균 (mean)과 합계 (sum)를 구해보자.
df5.groupby( by =["시군구"]).mean()
| 화재발생연도 | 사망자수 | 부상자수 | 재산피해금액 | 출동횟수 | 출동횟수_겨울 | 출동횟수_여름 | |
|---|---|---|---|---|---|---|---|
| 시군구 | |||||||
| 은평구 | 2019.0 | 2.6 | 9.4 | 886196.4 | 184.2 | 55.2 | 40.2 |
| 종로구 | 2019.0 | 4.2 | 11.6 | 711661.8 | 225.8 | 56.8 | 61.0 |
| 중구 | 2019.0 | 1.6 | 16.0 | 15976858.0 | 208.4 | 51.4 | 52.6 |
| 중랑구 | 2019.0 | 1.6 | 9.2 | 286252.0 | 219.6 | 59.2 | 51.6 |
df5.groupby( by =["시군구"]).sum()
| 화재발생연도 | 사망자수 | 부상자수 | 재산피해금액 | 출동횟수 | 출동횟수_겨울 | 출동횟수_여름 | |
|---|---|---|---|---|---|---|---|
| 시군구 | |||||||
| 은평구 | 10095 | 13.0 | 47 | 4430982 | 921 | 276 | 201 |
| 종로구 | 10095 | 21.0 | 58 | 3558309 | 1129 | 284 | 305 |
| 중구 | 10095 | 8.0 | 80 | 79884290 | 1042 | 257 | 263 |
| 중랑구 | 10095 | 8.0 | 46 | 1431260 | 1098 | 296 | 258 |
화재발생연도를 기준으로 각 열의 평균 (mean)과 합계 (sum)를 구해보자.
df5.groupby( by =["화재발생연도"]).mean()
/var/folders/3k/d8g2lfdn57b665jktbg_sglm0000gn/T/ipykernel_13278/890159447.py:1: FutureWarning: The default value of numeric_only in DataFrameGroupBy.mean is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
df5.groupby( by =["화재발생연도"]).mean()
| 사망자수 | 부상자수 | 재산피해금액 | 출동횟수 | 출동횟수_겨울 | 출동횟수_여름 | |
|---|---|---|---|---|---|---|
| 화재발생연도 | ||||||
| 2017 | 2.00 | 6.25 | 528405.75 | 196.75 | 51.75 | 46.50 |
| 2018 | 5.25 | 15.75 | 613057.25 | 249.25 | 69.25 | 61.50 |
| 2019 | 2.75 | 15.50 | 19403402.50 | 212.75 | 56.75 | 46.25 |
| 2020 | 1.50 | 8.25 | 664631.75 | 204.75 | 48.25 | 51.50 |
| 2021 | 1.00 | 12.00 | 1116713.00 | 184.00 | 52.25 | 51.00 |
df5.groupby( by =["화재발생연도"]).sum()
/var/folders/3k/d8g2lfdn57b665jktbg_sglm0000gn/T/ipykernel_13278/2243519833.py:1: FutureWarning: The default value of numeric_only in DataFrameGroupBy.sum is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
df5.groupby( by =["화재발생연도"]).sum()
| 사망자수 | 부상자수 | 재산피해금액 | 출동횟수 | 출동횟수_겨울 | 출동횟수_여름 | |
|---|---|---|---|---|---|---|
| 화재발생연도 | ||||||
| 2017 | 8.0 | 25 | 2113623 | 787 | 207 | 186 |
| 2018 | 21.0 | 63 | 2452229 | 997 | 277 | 246 |
| 2019 | 11.0 | 62 | 77613610 | 851 | 227 | 185 |
| 2020 | 6.0 | 33 | 2658527 | 819 | 193 | 206 |
| 2021 | 4.0 | 48 | 4466852 | 736 | 209 | 204 |
그룹화 기준을 2개 이상으로 줄 수도 있다. 이 경우 시군구와 화재발생연도로 해보자.
df5.groupby( by =["시군구", "화재발생연도"]).mean()
| 사망자수 | 부상자수 | 재산피해금액 | 출동횟수 | 출동횟수_겨울 | 출동횟수_여름 | ||
|---|---|---|---|---|---|---|---|
| 시군구 | 화재발생연도 | ||||||
| 은평구 | 2017 | 0.0 | 3.0 | 218200.0 | 159.0 | 51.0 | 32.0 |
| 2018 | 5.0 | 10.0 | 419503.0 | 214.0 | 58.0 | 47.0 | |
| 2019 | 3.0 | 20.0 | 2412769.0 | 196.0 | 62.0 | 34.0 | |
| 2020 | 2.0 | 6.0 | 504788.0 | 192.0 | 48.0 | 46.0 | |
| 2021 | 3.0 | 8.0 | 875722.0 | 160.0 | 57.0 | 42.0 | |
| 종로구 | 2017 | 1.0 | 3.0 | 1077665.0 | 234.0 | 55.0 | 69.0 |
| 2018 | 14.0 | 22.0 | 574300.0 | 254.0 | 71.0 | 70.0 | |
| 2019 | 4.0 | 16.0 | 801094.0 | 232.0 | 60.0 | 63.0 | |
| 2020 | 2.0 | 5.0 | 639751.0 | 217.0 | 50.0 | 49.0 | |
| 2021 | 0.0 | 12.0 | 465499.0 | 192.0 | 48.0 | 54.0 | |
| 중구 | 2017 | 5.0 | 14.0 | 485392.0 | 198.0 | 48.0 | 47.0 |
| 2018 | 0.0 | 23.0 | 1257005.0 | 275.0 | 76.0 | 74.0 | |
| 2019 | 3.0 | 17.0 | 74077097.0 | 213.0 | 51.0 | 39.0 | |
| 2020 | 0.0 | 10.0 | 1284422.0 | 185.0 | 41.0 | 54.0 | |
| 2021 | 0.0 | 16.0 | 2780374.0 | 171.0 | 41.0 | 49.0 | |
| 중랑구 | 2017 | 2.0 | 5.0 | 332366.0 | 196.0 | 53.0 | 38.0 |
| 2018 | 2.0 | 8.0 | 201421.0 | 254.0 | 72.0 | 55.0 | |
| 2019 | 1.0 | 9.0 | 322650.0 | 210.0 | 54.0 | 49.0 | |
| 2020 | 2.0 | 12.0 | 229566.0 | 225.0 | 54.0 | 57.0 | |
| 2021 | 1.0 | 12.0 | 345257.0 | 213.0 | 63.0 | 59.0 |
그룹화 기준을 화재발생연도, 시군구로 바꾸어보자.
df5.groupby( by =["화재발생연도", "시군구"]).mean()
| 사망자수 | 부상자수 | 재산피해금액 | 출동횟수 | 출동횟수_겨울 | 출동횟수_여름 | ||
|---|---|---|---|---|---|---|---|
| 화재발생연도 | 시군구 | ||||||
| 2017 | 은평구 | 0.0 | 3.0 | 218200.0 | 159.0 | 51.0 | 32.0 |
| 종로구 | 1.0 | 3.0 | 1077665.0 | 234.0 | 55.0 | 69.0 | |
| 중구 | 5.0 | 14.0 | 485392.0 | 198.0 | 48.0 | 47.0 | |
| 중랑구 | 2.0 | 5.0 | 332366.0 | 196.0 | 53.0 | 38.0 | |
| 2018 | 은평구 | 5.0 | 10.0 | 419503.0 | 214.0 | 58.0 | 47.0 |
| 종로구 | 14.0 | 22.0 | 574300.0 | 254.0 | 71.0 | 70.0 | |
| 중구 | 0.0 | 23.0 | 1257005.0 | 275.0 | 76.0 | 74.0 | |
| 중랑구 | 2.0 | 8.0 | 201421.0 | 254.0 | 72.0 | 55.0 | |
| 2019 | 은평구 | 3.0 | 20.0 | 2412769.0 | 196.0 | 62.0 | 34.0 |
| 종로구 | 4.0 | 16.0 | 801094.0 | 232.0 | 60.0 | 63.0 | |
| 중구 | 3.0 | 17.0 | 74077097.0 | 213.0 | 51.0 | 39.0 | |
| 중랑구 | 1.0 | 9.0 | 322650.0 | 210.0 | 54.0 | 49.0 | |
| 2020 | 은평구 | 2.0 | 6.0 | 504788.0 | 192.0 | 48.0 | 46.0 |
| 종로구 | 2.0 | 5.0 | 639751.0 | 217.0 | 50.0 | 49.0 | |
| 중구 | 0.0 | 10.0 | 1284422.0 | 185.0 | 41.0 | 54.0 | |
| 중랑구 | 2.0 | 12.0 | 229566.0 | 225.0 | 54.0 | 57.0 | |
| 2021 | 은평구 | 3.0 | 8.0 | 875722.0 | 160.0 | 57.0 | 42.0 |
| 종로구 | 0.0 | 12.0 | 465499.0 | 192.0 | 48.0 | 54.0 | |
| 중구 | 0.0 | 16.0 | 2780374.0 | 171.0 | 41.0 | 49.0 | |
| 중랑구 | 1.0 | 12.0 | 345257.0 | 213.0 | 63.0 | 59.0 |
다양한 집계들#
셈 척도: 갯수 (count), 합산 (sum)
중심척도: 평균 (mean), 중위수 (median)
산포척도: 최댓값/최솟값 (max/min), 분산/표준편차 (var, std), 백분위 (quantile)
기초통계량 (describe)
위와 같이 여러 집계들이 있는데, 자료가 범주형인지 수치형인지에 따라 그 쓰임이 나뉜다.
범주형 자료: 셈 (count, percent)
수치형 자료: 셈 (sum), 중심 (mean, median), 산포 (man, min, var, std, quantile)
예를 들어, 화재출동 데이터의 경우 자료의 형태에 따라 다음과 같이 열을 나눌 수 있다.
범주형 자료:
화재발생연도와시군구수치형 자료:
화재발생연도와시군구를 제외한 나머지 열
그러면 그룹화와 집계를 위해 다음과 같은 문제를 고려하자
문제: 시군구별 평균 재산피해금액과 총 출동횟수
이 문제를 해결하기 위해 다음과 같이 설계할 수 있다.
그룹화: 시군구
계산하고 싶은 열: 재산피해금액, 출동횟수
집계함수: sum, mean
그러면 df5를 다시 집계해보자. 방금 전에 했던 방법과 같은 방법으로 각 집계를 구하면 제대로 집계가 이루어지지 않거나 불필요한 정보가 생긴다는 문제가 있었다.
df5.groupby(['시군구']).sum()
| 화재발생연도 | 사망자수 | 부상자수 | 재산피해금액 | 출동횟수 | 출동횟수_겨울 | 출동횟수_여름 | |
|---|---|---|---|---|---|---|---|
| 시군구 | |||||||
| 은평구 | 10095 | 13.0 | 47 | 4430982 | 921 | 276 | 201 |
| 종로구 | 10095 | 21.0 | 58 | 3558309 | 1129 | 284 | 305 |
| 중구 | 10095 | 8.0 | 80 | 79884290 | 1042 | 257 | 263 |
| 중랑구 | 10095 | 8.0 | 46 | 1431260 | 1098 | 296 | 258 |
df5.groupby(['시군구']).mean()
| 화재발생연도 | 사망자수 | 부상자수 | 재산피해금액 | 출동횟수 | 출동횟수_겨울 | 출동횟수_여름 | |
|---|---|---|---|---|---|---|---|
| 시군구 | |||||||
| 은평구 | 2019.0 | 2.6 | 9.4 | 886196.4 | 184.2 | 55.2 | 40.2 |
| 종로구 | 2019.0 | 4.2 | 11.6 | 711661.8 | 225.8 | 56.8 | 61.0 |
| 중구 | 2019.0 | 1.6 | 16.0 | 15976858.0 | 208.4 | 51.4 | 52.6 |
| 중랑구 | 2019.0 | 1.6 | 9.2 | 286252.0 | 219.6 | 59.2 | 51.6 |
이처럼 한번에 여러 컬럼을 다르게 연산해야 할 때가 있다. 다음과 같이 agg메소드를 사용하면 가능하다. 이 때 {계산하고 싶은 열 이름: 집계함수}와 같이 입력한다.
df5.groupby(['시군구']).agg({"재산피해금액" : "mean", "출동횟수" : "sum"})
| 재산피해금액 | 출동횟수 | |
|---|---|---|
| 시군구 | ||
| 은평구 | 886196.4 | 921 |
| 종로구 | 711661.8 | 1129 |
| 중구 | 15976858.0 | 1042 |
| 중랑구 | 286252.0 | 1098 |
이번에는 연도와 시군구별 평균 재산피해금액과 총 출동횟수를 계산해보자.
df5.groupby(['화재발생연도', '시군구']).agg({"재산피해금액" : "mean", "출동횟수" : "sum"})
| 재산피해금액 | 출동횟수 | ||
|---|---|---|---|
| 화재발생연도 | 시군구 | ||
| 2017 | 은평구 | 218200.0 | 159 |
| 종로구 | 1077665.0 | 234 | |
| 중구 | 485392.0 | 198 | |
| 중랑구 | 332366.0 | 196 | |
| 2018 | 은평구 | 419503.0 | 214 |
| 종로구 | 574300.0 | 254 | |
| 중구 | 1257005.0 | 275 | |
| 중랑구 | 201421.0 | 254 | |
| 2019 | 은평구 | 2412769.0 | 196 |
| 종로구 | 801094.0 | 232 | |
| 중구 | 74077097.0 | 213 | |
| 중랑구 | 322650.0 | 210 | |
| 2020 | 은평구 | 504788.0 | 192 |
| 종로구 | 639751.0 | 217 | |
| 중구 | 1284422.0 | 185 | |
| 중랑구 | 229566.0 | 225 | |
| 2021 | 은평구 | 875722.0 | 160 |
| 종로구 | 465499.0 | 192 | |
| 중구 | 2780374.0 | 171 | |
| 중랑구 | 345257.0 | 213 |
이렇게 만들어진 집계데이터를 활용해보자.
집계를 하면서 열 이름이 행의 인덱스로 들어갔다. 이를 reset_index로 인덱스 초기화를 해서 다시 열 이름으로 만들어 활용한다.
df_summ = df5.groupby(['화재발생연도', '시군구']).agg({"재산피해금액" : "mean", "출동횟수" : "sum"})
df_summ = df_summ.reset_index()
df_summ
| 화재발생연도 | 시군구 | 재산피해금액 | 출동횟수 | |
|---|---|---|---|---|
| 0 | 2017 | 은평구 | 218200.0 | 159 |
| 1 | 2017 | 종로구 | 1077665.0 | 234 |
| 2 | 2017 | 중구 | 485392.0 | 198 |
| 3 | 2017 | 중랑구 | 332366.0 | 196 |
| 4 | 2018 | 은평구 | 419503.0 | 214 |
| 5 | 2018 | 종로구 | 574300.0 | 254 |
| 6 | 2018 | 중구 | 1257005.0 | 275 |
| 7 | 2018 | 중랑구 | 201421.0 | 254 |
| 8 | 2019 | 은평구 | 2412769.0 | 196 |
| 9 | 2019 | 종로구 | 801094.0 | 232 |
| 10 | 2019 | 중구 | 74077097.0 | 213 |
| 11 | 2019 | 중랑구 | 322650.0 | 210 |
| 12 | 2020 | 은평구 | 504788.0 | 192 |
| 13 | 2020 | 종로구 | 639751.0 | 217 |
| 14 | 2020 | 중구 | 1284422.0 | 185 |
| 15 | 2020 | 중랑구 | 229566.0 | 225 |
| 16 | 2021 | 은평구 | 875722.0 | 160 |
| 17 | 2021 | 종로구 | 465499.0 | 192 |
| 18 | 2021 | 중구 | 2780374.0 | 171 |
| 19 | 2021 | 중랑구 | 345257.0 | 213 |
요약#
여러 개의 열을 이용하여 새로운 열을 만드는 방법은 열을 선택하고 산술적인 연산을 적용해 준다.
열에 대한 요약 통계는 다양한 메소드(예를 들어
sum(),mean()등)을 적용하며 선택문axis=0을 이용한다.자료의 그룹화는
groupby()를 사용하며 이를 통해 그룹별로 요약할 수 있다.
함께해봅시다
df자료에 대해 다음의 문제를 해결해보자.
재산피해금액의총합이 가장 많은시군구와재산피해금액의평균이 가장 큰시군구는 어디인가요? 동일한 시군구인가요?출동횟수가 가장 많았던 때는 언제, 어느 구인가요?00년도 00구로 알려주세요.여름과겨울을 합산하여 가장 출동이 많았던 때는 어느 구인가요? 그리고출동횟수의 편차가 가장 큰 구는 어느 구인가요?00구로 알려주세요.