2.6. 데이터프레임 실습 2: 그룹화와 결합#

2일차에서 pandas패키지의 dataframe형태의 데이터에 대해 mergegroup함수를 이용하여 데이터를 조작하는 법을 설명했다. 이때, 실제 데이터가 아닌 완성된 데이터의 일부를 이용하여 진행했는데, 이번 시간에는 raw data(아무것도 하지 않은 데이터)를 이용하여 원하는 결과를 확인하는 과정을 가지고자 한다.

이번 시간의 목표는 2일차때 사용한 데이터를 화재출동 현황 데이터를 이용하여 생성하는 것이다. 목표는 다음과 같다.

  1. 연도별, 시군구별 사망자수, 부상자수, 재산피해금액, 총 출동횟수

  2. 연도별, 시군구별 계절(여름, 겨울)에 대한 출동횟수

  3. (1.), (2.)에서 생성한 데이터 합쳐서 전체 데이터 생성

import pandas as pd

화재출동 현황 데이터#

  1. 화재출동 현황.csv 파일을 보면, 모든 변수가 영어로 입력되어 있다.

  2. 그리고 화재출동 현황_sample.xls 파일을 보면, 이 영어 변수들에 대한 한국어 변수명이 같이 저장되어 있다.

  3. csv파일과 xlsx파일을 같이 읽어, 변수들을 모두 한국어로 바꾸는 작업을 진행해본다.

fire_dat = pd.read_csv("https://uos-bigdata.github.io/lab_data/docs/assets/data_lab_fire/fire.csv")
sample_dat = pd.read_csv("https://uos-bigdata.github.io/lab_data/docs/assets/data_lab_fire/fire_sample.csv")
fire_dat.head()
wrinvstg_no fire_type_nm buld_srtfrm buld_strctr buld_srtrf buld_strct_dong_cnt ground_nof bstory_cnt totar bottom_area ... vhcle_ign_bhf fld_fire_se fld_fire_ign_bhf time_unit_tmprt time_unit_rainqty time_unit_ws time_unit_wd time_unit_humidity time_unit_msnf time_unit
0 170101044118025 건축/구조물 양식(옥) 철근콘크리트조 슬라브가 1.0 6.0 1.0 1133.0 168.0 ... NaN NaN NaN 0.2 NaN 2.2 20.0 79.0 NaN 491
1 170101044947860 건축/구조물 양식(옥) 치장벽돌조 슬라브가 1.0 2.0 1.0 248.0 81.0 ... NaN NaN NaN -0.7 NaN 2.0 50.0 84.0 NaN 436
2 170102110954553 건축/구조물 양식(옥) 철근콘크리트조 슬라브가 1.0 3.0 1.0 1175.0 490.0 ... NaN NaN NaN -0.7 NaN 2.0 50.0 84.0 NaN 436
3 170101162928231 건축/구조물 양식(옥) 철근콘크리트조 슬라브가 1.0 12.0 1.0 8989.0 816.0 ... NaN NaN NaN 4.0 NaN 1.3 50.0 69.0 NaN 433
4 170101140310046 기타(쓰레기 화재등) NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN 4.0 NaN 1.3 50.0 69.0 NaN 433

5 rows × 72 columns

sample_dat.head()
조사서번호 화재유형명 건물구조식 건물구조조 건물구조즙 건물구조동수 지상층수 지하층수 연면적 바닥면적 ... 차량발화지점 임야화재구분 임야화재발화지점 시간단위기온 시간단위강수량 시간단위풍속 시간단위풍향 시간단위습도 시간단위적설량 시간단위가시거리
0 wrinvstg_no fire_type_nm buld_srtfrm buld_strctr buld_srtrf buld_strct_dong_cnt ground_nof bstory_cnt totar bottom_area ... vhcle_ign_bhf fld_fire_se fld_fire_ign_bhf time_unit_tmprt time_unit_rainqty time_unit_ws time_unit_wd time_unit_humidity time_unit_msnf time_unit
1 170101044118025 건축/구조물 양식(옥) 철근콘크리트조 슬라브가 1 6 1 1133.000000 168.000000 ... NaN NaN NaN 0.2 NaN 2.2 20 79 NaN 491
2 170101044947860 건축/구조물 양식(옥) 치장벽돌조 슬라브가 1 2 1 248.000000 81.000000 ... NaN NaN NaN -0.7 NaN 2.0 50 84 NaN 436
3 170102110954553 건축/구조물 양식(옥) 철근콘크리트조 슬라브가 1 3 1 1175.000000 490.000000 ... NaN NaN NaN -0.7 NaN 2.0 50 84 NaN 436
4 170101162928231 건축/구조물 양식(옥) 철근콘크리트조 슬라브가 1 12 1 8989.000000 816.000000 ... NaN NaN NaN 4.0 NaN 1.3 50 69 NaN 433

5 rows × 72 columns

sample_dat을 살펴보면, 변수명들이 한국어로 되어 있고, 그에 대응되는 영문명들이 1번째 row에 저장되있음을 알 수 있다. 그러므로, 변수명과 1번째 row를 가져와서 데이터로 생성하여 연결하는 것을 진행한다.

col_name = sample_dat.loc[0, :].reset_index()
col_name.columns = ["han_name", 'eng_name']

col_name
han_name eng_name
0 조사서번호 wrinvstg_no
1 화재유형명 fire_type_nm
2 건물구조식 buld_srtfrm
3 건물구조조 buld_strctr
4 건물구조즙 buld_srtrf
... ... ...
67 시간단위풍속 time_unit_ws
68 시간단위풍향 time_unit_wd
69 시간단위습도 time_unit_humidity
70 시간단위적설량 time_unit_msnf
71 시간단위가시거리 time_unit

72 rows × 2 columns

han_name 변수는 한국어 변수명, eng_name 변수는 영문 변수명으로 지정하였다. 이 순서는 fire_dat에 있는 변수명과 같은 순서를 가진다. 그러므로 그대로 han_name을 가져와 fire_dat의 변수로 지정해주면 된다.

print(fire_dat.columns)
fire_dat.columns = col_name.iloc[:, 0]
Index(['wrinvstg_no', 'fire_type_nm', 'buld_srtfrm', 'buld_strctr',
       'buld_srtrf', 'buld_strct_dong_cnt', 'ground_nof', 'bstory_cnt',
       'totar', 'bottom_area', 'buld_sttus_nm', 'dth_cnt', 'injpsn_cnt',
       'dth_hnl_dmge_cnt', 'prprty_dmge_amt', 'fire_ocrn_yr', 'season_se_nm',
       'qtr_se', 'fire_ocrn_ymd', 'fire_ocrn_tm', 'fire_ocrn_mnth',
       'fire_ocrn_day', 'fire_ocrn_hour', 'fire_ocrn_min', 'daywk', 'frstt_nm',
       'ward_nm', 'lfdau_nm', 'dsp_reqre_time', 'fire_supesn_time', 'sido_nm',
       'sigungu_nm', 'emd_nm', 'cty_frmvl_se_nm', 'emd_se_nm', 'gis_x_axis',
       'gis_y_axis', 'longitude', 'la', 'spt_frstt_dist',
       'spt_safe_cnter_dist', 'spt_lfdau_dist', 'ign_htsrc_nm',
       'ign_htsrc_sclas_nm', 'ign_fctr_lclas_nm', 'ign_fctr_sclas_nm',
       'frst_igobj_lclas_nm', 'frst_igobj_sclas_nm', 'ign_mhrls_lclas_nm',
       'ign_mhrls_sclas_nm', 'cmbs_expobj_lclas_nm', 'cmbs_expobj_sclas_nm',
       'fclty_place_lclas_nm', 'fclty_place_mclas_nm', 'fclty_place_sclas_nm',
       'ign_floor_nm', 'soot_area', 'fio_join_trgt_yn', 'frmng_trgt_yn',
       'mub_yn', 'emrg_crg_oper_yn', 'vhcle_place', 'vhcle_ign_bhf',
       'fld_fire_se', 'fld_fire_ign_bhf', 'time_unit_tmprt',
       'time_unit_rainqty', 'time_unit_ws', 'time_unit_wd',
       'time_unit_humidity', 'time_unit_msnf', 'time_unit'],
      dtype='object')
fire_dat.head()
han_name 조사서번호 화재유형명 건물구조식 건물구조조 건물구조즙 건물구조동수 지상층수 지하층수 연면적 바닥면적 ... 차량발화지점 임야화재구분 임야화재발화지점 시간단위기온 시간단위강수량 시간단위풍속 시간단위풍향 시간단위습도 시간단위적설량 시간단위가시거리
0 170101044118025 건축/구조물 양식(옥) 철근콘크리트조 슬라브가 1.0 6.0 1.0 1133.0 168.0 ... NaN NaN NaN 0.2 NaN 2.2 20.0 79.0 NaN 491
1 170101044947860 건축/구조물 양식(옥) 치장벽돌조 슬라브가 1.0 2.0 1.0 248.0 81.0 ... NaN NaN NaN -0.7 NaN 2.0 50.0 84.0 NaN 436
2 170102110954553 건축/구조물 양식(옥) 철근콘크리트조 슬라브가 1.0 3.0 1.0 1175.0 490.0 ... NaN NaN NaN -0.7 NaN 2.0 50.0 84.0 NaN 436
3 170101162928231 건축/구조물 양식(옥) 철근콘크리트조 슬라브가 1.0 12.0 1.0 8989.0 816.0 ... NaN NaN NaN 4.0 NaN 1.3 50.0 69.0 NaN 433
4 170101140310046 기타(쓰레기 화재등) NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN 4.0 NaN 1.3 50.0 69.0 NaN 433

5 rows × 72 columns

fire_dat의 변수들이 모두 한국어로 변경되었음을 확인 할 수 있다.

시군구, 연도별 사망자수, 부상자수, 재산피해금액, 출동횟수 계산#

두번째 시간에 일부 시군구, 연도에 대해 groupby를 이용하여 변수에 대한 계산하는 법을 설명했다. 이를 화재출동 현황 데이터에 접목하여 시군구, 연도별로 사망자수, 부상자수, 재산피해금액, 출동횟수를 계산해보자. 이를 위한 순서는 다음과 같다.

  1. 먼저 계산할 변수들을 추출한다.

sub_fire_dat = fire_dat.loc[:, ["화재발생연도", "시군구명", "사망수", "부상자수", "재산피해금액"]]
  1. 추가적으로 출동횟수를 계산하기 위해, count변수에 1이라는 값을 저장한다. 그 이뉴는 agg method를 이용하여 sum함수를 한꺼번에 적용하기 위함이다.

sub_fire_dat["count"] = 1
  1. groupbyaggmethod를 이용하여 연도별, 시군구별로 합을 계산한다.

agg_dat = sub_fire_dat.groupby(["화재발생연도", "시군구명"]).agg(["sum"])
agg_dat
han_name 사망수 부상자수 재산피해금액 count
sum sum sum sum
화재발생연도 시군구명
2017 강남구 0 11 1565258 502
강동구 0 12 418593 269
강북구 0 6 339146 186
강서구 3 22 706871 364
관악구 3 20 654690 286
... ... ... ... ... ...
2021 용산구 0 5 296793 176
은평구 3 8 875722 160
종로구 0 12 465499 192
중구 0 16 2780374 171
중랑구 1 12 345257 213

125 rows × 4 columns

  1. reset_index() method를 이용하여 화재발생연도 및 시군구명을 인덱스에서 가져온다.

agg_dat = agg_dat.reset_index()
agg_dat
han_name 화재발생연도 시군구명 사망수 부상자수 재산피해금액 count
sum sum sum sum
0 2017 강남구 0 11 1565258 502
1 2017 강동구 0 12 418593 269
2 2017 강북구 0 6 339146 186
3 2017 강서구 3 22 706871 364
4 2017 관악구 3 20 654690 286
... ... ... ... ... ... ...
120 2021 용산구 0 5 296793 176
121 2021 은평구 3 8 875722 160
122 2021 종로구 0 12 465499 192
123 2021 중구 0 16 2780374 171
124 2021 중랑구 1 12 345257 213

125 rows × 6 columns

  1. 변수명들을 지정해주어 마무리해준다.

agg_dat.columns = ["화재발생연도", "시군구", "사망자수", "부상자수", "재산피해금액", "총출동횟수"]
agg_dat
화재발생연도 시군구 사망자수 부상자수 재산피해금액 총출동횟수
0 2017 강남구 0 11 1565258 502
1 2017 강동구 0 12 418593 269
2 2017 강북구 0 6 339146 186
3 2017 강서구 3 22 706871 364
4 2017 관악구 3 20 654690 286
... ... ... ... ... ... ...
120 2021 용산구 0 5 296793 176
121 2021 은평구 3 8 875722 160
122 2021 종로구 0 12 465499 192
123 2021 중구 0 16 2780374 171
124 2021 중랑구 1 12 345257 213

125 rows × 6 columns

계절별 출동횟수 생성#

  1. 먼저 계산에 필요한 변수들을 추출 및 생성한다.

sub_fire_dat = fire_dat.loc[:, ["화재발생연도", "시군구명", "계절구분명"]]
sub_fire_dat["count"] = 1
sub_fire_dat
han_name 화재발생연도 시군구명 계절구분명 count
0 2017 강남구 겨울 1
1 2017 마포구 겨울 1
2 2017 강서구 겨울 1
3 2017 강동구 겨울 1
4 2017 강북구 겨울 1
... ... ... ... ...
28261 2021 서대문구 겨울 1
28262 2021 마포구 겨울 1
28263 2021 성북구 겨울 1
28264 2021 은평구 겨울 1
28265 2021 강서구 겨울 1

28266 rows × 4 columns

  1. groupbyagg method, sum함수를 이용하여 화재발생연도, 시군구, 계절별로 출동 횟수를 계산한다.

ss_dat = sub_fire_dat.groupby(["화재발생연도", "시군구명", "계절구분명"]).agg('sum')
ss_dat
han_name count
화재발생연도 시군구명 계절구분명
2017 강남구 가을 113
겨울 129
140
여름 120
강동구 가을 65
... ... ... ...
2021 중구 여름 49
중랑구 가을 45
겨울 63
46
여름 59

500 rows × 1 columns

  1. reset_index() method를 이용하여 화재발생연도 및 시군구명을 인덱스에서 가져온다.

ss_dat = ss_dat.reset_index()
ss_dat
han_name 화재발생연도 시군구명 계절구분명 count
0 2017 강남구 가을 113
1 2017 강남구 겨울 129
2 2017 강남구 140
3 2017 강남구 여름 120
4 2017 강동구 가을 65
... ... ... ... ...
495 2021 중구 여름 49
496 2021 중랑구 가을 45
497 2021 중랑구 겨울 63
498 2021 중랑구 46
499 2021 중랑구 여름 59

500 rows × 4 columns

  1. 변수명을 할당하여 마무리한다.

ss_dat.columns = ["화재발생연도", "시군구", "계절", "출동횟수"]
ss_dat
화재발생연도 시군구 계절 출동횟수
0 2017 강남구 가을 113
1 2017 강남구 겨울 129
2 2017 강남구 140
3 2017 강남구 여름 120
4 2017 강동구 가을 65
... ... ... ... ...
495 2021 중구 여름 49
496 2021 중랑구 가을 45
497 2021 중랑구 겨울 63
498 2021 중랑구 46
499 2021 중랑구 여름 59

500 rows × 4 columns

이제, 계절별로 데이터가 생성 되었다. 이 데이터 셋에서 계절이 여름, 겨울인 데이터만 추출해 보자.

  1. 계절 변수가 “여름” 또는 “겨울”의 값을 가지는 데이터만 추출한다.

tmp_ind = (ss_dat["계절"] == "여름") | (ss_dat["계절"] == "겨울")
sub_ss_dat = ss_dat.loc[tmp_ind, :]
sub_ss_dat
화재발생연도 시군구 계절 출동횟수
1 2017 강남구 겨울 129
3 2017 강남구 여름 120
5 2017 강동구 겨울 63
7 2017 강동구 여름 73
9 2017 강북구 겨울 51
... ... ... ... ...
491 2021 종로구 여름 54
493 2021 중구 겨울 41
495 2021 중구 여름 49
497 2021 중랑구 겨울 63
499 2021 중랑구 여름 59

250 rows × 4 columns

데이터의 형태를 보면, 계절이란 변수에 “여름”, “겨울”로써 값이 들어가 있음을 알 수 있다. 그러므로 이를 변수의 형태로 변환하기 위해 다음과 같은 작업을 진행한다.

  1. 계절이 “여름”인 데이터들만 가져오고, 이 데이터의 출동횟수를 “출동횟수_여름”으로 할당한다.

tmp_ind = sub_ss_dat["계절"] == "여름"
sum_dat = sub_ss_dat.loc[tmp_ind, :].drop(["계절"], axis = 1)
sum_dat.columns = ["화재발생연도", "시군구", "출동횟수_여름"]

sum_dat
화재발생연도 시군구 출동횟수_여름
3 2017 강남구 120
7 2017 강동구 73
11 2017 강북구 41
15 2017 강서구 81
19 2017 관악구 69
... ... ... ...
483 2021 용산구 36
487 2021 은평구 42
491 2021 종로구 54
495 2021 중구 49
499 2021 중랑구 59

125 rows × 3 columns

  1. 계절이 “겨울”인 데이터들만 가져오고, 이 데이터의 출동횟수를 “출동횟수_겨울”으로 할당한다.

tmp_ind = sub_ss_dat["계절"] == "겨울"
win_dat = sub_ss_dat.loc[tmp_ind, :].drop(["계절"], axis = 1)
win_dat.columns = ["화재발생연도", "시군구", "출동횟수_겨울"]

win_dat
화재발생연도 시군구 출동횟수_겨울
1 2017 강남구 129
5 2017 강동구 63
9 2017 강북구 51
13 2017 강서구 89
17 2017 관악구 82
... ... ... ...
481 2021 용산구 47
485 2021 은평구 57
489 2021 종로구 48
493 2021 중구 41
497 2021 중랑구 63

125 rows × 3 columns

두 개의 데이터 셋 결합#

다음, pandas패키지의 merge함수를 이용하여 화재발생연도, 시군구 기준으로 조인을 진행한다.

sum_win_dat = pd.merge(sum_dat, win_dat, on = ["화재발생연도", "시군구"])
sum_win_dat
화재발생연도 시군구 출동횟수_여름 출동횟수_겨울
0 2017 강남구 120 129
1 2017 강동구 73 63
2 2017 강북구 41 51
3 2017 강서구 81 89
4 2017 관악구 69 82
... ... ... ... ...
120 2021 용산구 36 47
121 2021 은평구 42 57
122 2021 종로구 54 48
123 2021 중구 49 41
124 2021 중랑구 59 63

125 rows × 4 columns

마지막으로, 처음에 생성시킨 화재발생연도, 시군구 별 사망자수, 부상자수, 재산피해금액, 총출동횟수에 대한 데이터와 똑같은 변수(화재발생연도, 시군구)를 기준으로 pandas패키지의 merge 함수를 이용하여 결합한다.

final_dat = pd.merge(agg_dat, sum_win_dat, on = ["화재발생연도", "시군구"])
final_dat
화재발생연도 시군구 사망자수 부상자수 재산피해금액 총출동횟수 출동횟수_여름 출동횟수_겨울
0 2017 강남구 0 11 1565258 502 120 129
1 2017 강동구 0 12 418593 269 73 63
2 2017 강북구 0 6 339146 186 41 51
3 2017 강서구 3 22 706871 364 81 89
4 2017 관악구 3 20 654690 286 69 82
... ... ... ... ... ... ... ... ...
120 2021 용산구 0 5 296793 176 36 47
121 2021 은평구 3 8 875722 160 42 57
122 2021 종로구 0 12 465499 192 54 48
123 2021 중구 0 16 2780374 171 49 41
124 2021 중랑구 1 12 345257 213 59 63

125 rows × 8 columns

2일차때 연습으로 사용한 데이터는 이렇게 생성됩니다. 그리고, 최종으로 엑셀로 저장은 다음과 같다.

final_dat.to_csv("practice_dat.csv", index = False)

추가적인 내용#

일반적으로 위 final_dat의 데이터를 얻으면 다음과 같은 사항에 대해 궁금증을 가질 수 있다.

  1. 연도, 시군구별 사망자수, 부상자수, 재산피해금액 각 변수들에 대해 가장 높은지역은 어딜까??

  2. 여름은 겨울보다 습하고, 겨울은 여름보다 습하다. 그러므로, 화재 사건에 대해서는 겨울에 더 많이 발생할 가능성이 높다고 생각 할 수 있다. 이를 실제로 확인하기 위해 연도별로 여름, 겨울의 출동횟수의 차이를 계산해보자.

1.)을 확인하기 위해, 연도에 대한 데이터 셋을 생성하자.

year_ind = final_dat["화재발생연도"] == 2021
year_dat = final_dat.loc[year_ind, :]

먼저, 사망자수가 가장 높은 지역이다. 이는 year_dat에서 사망자수의 최댓값과 사망자수가 같은 index를 찾아 추출하면된다.

max_ind = year_dat["사망자수"] == year_dat['사망자수'].max()
year_dat.loc[max_ind, :]
화재발생연도 시군구 사망자수 부상자수 재산피해금액 총출동횟수 출동횟수_여름 출동횟수_겨울
102 2021 강북구 5 14 410205 146 38 29

같은 방법으로 부상자수, 재산피해금액, 총출동횟수는 각각 다음과 같다.

max_ind = year_dat["부상자수"] == year_dat['부상자수'].max()
year_dat.loc[max_ind, :]
화재발생연도 시군구 사망자수 부상자수 재산피해금액 총출동횟수 출동횟수_여름 출동횟수_겨울
116 2021 성북구 4 46 493231 168 37 56
max_ind = year_dat["재산피해금액"] == year_dat['재산피해금액'].max()
year_dat.loc[max_ind, :]
화재발생연도 시군구 사망자수 부상자수 재산피해금액 총출동횟수 출동횟수_여름 출동횟수_겨울
123 2021 중구 0 16 2780374 171 49 41
max_ind = year_dat["총출동횟수"] == year_dat['총출동횟수'].max()
year_dat.loc[max_ind, :]
화재발생연도 시군구 사망자수 부상자수 재산피해금액 총출동횟수 출동횟수_여름 출동횟수_겨울
100 2021 강남구 2 15 1354949 391 91 112

2.)를 확인하기 위해 여름의 출동횟수와 겨울의 출동횟수를 계산해보자.

final_dat["출동횟수_차이"] = final_dat["출동횟수_겨울"] - final_dat["출동횟수_여름"]
final_dat
화재발생연도 시군구 사망자수 부상자수 재산피해금액 총출동횟수 출동횟수_여름 출동횟수_겨울 출동횟수_차이
0 2017 강남구 0 11 1565258 502 120 129 9
1 2017 강동구 0 12 418593 269 73 63 -10
2 2017 강북구 0 6 339146 186 41 51 10
3 2017 강서구 3 22 706871 364 81 89 8
4 2017 관악구 3 20 654690 286 69 82 13
... ... ... ... ... ... ... ... ... ...
120 2021 용산구 0 5 296793 176 36 47 11
121 2021 은평구 3 8 875722 160 42 57 15
122 2021 종로구 0 12 465499 192 54 48 -6
123 2021 중구 0 16 2780374 171 49 41 -8
124 2021 중랑구 1 12 345257 213 59 63 4

125 rows × 9 columns

출동횟수_차이의 의미는 이 값이 0보다 크다면 겨울의 출동횟수가 많다는 것이고, 0보다 작으면 여름의 출동횟수가 많다는 것이다. 그러므로, 0보다 큰것의 횟수를 계산하면 된다.

final_dat["count"] = final_dat["출동횟수_차이"] > 0
final_dat.groupby("화재발생연도")["count"].agg('sum')
화재발생연도
2017    15
2018    15
2019    14
2020    14
2021    16
Name: count, dtype: int64

서울은 25개 시군구이고, 모두 13보다 큰 값을 가진다. 이 결과로써 실제로, 여름보다 겨울의 출동횟수가 더 큰 지역이 절반 이상의 지역임을 알 수 있다.