서울시 CCTV 및 인구 현황¶
CCTV 현황¶
연도별 서울시 자치구 CCTV 설치 현황 자료는 사이트 https://www.data.go.kr/dataset/3073216/fileData.do에서 다운받을 수 있다. 링크는 변경될 수도 있다. 다운받은 파일은 data
디렉토리에 저장한다.
In [1]: import pandas as pd
...: pd.set_option('max_rows', 10)
...:
In [3]: cctv = pd.read_csv('./data/서울시 자치구 년도별 CCTV 설치 현황.csv', engine='python', encoding='utf-8')
...: cctv
...:
Out[4]:
기관명 소계 2013년도 이전 2014년 2015년 2016년
0 강남구 3238 1292 430 584 932
1 강동구 1010 379 99 155 377
2 강북구 831 369 120 138 204
3 강서구 911 388 258 184 81
4 관악구 2109 846 260 390 613
.. ... ... ... ... ... ...
20 용산구 2096 1368 218 112 398
21 은평구 2108 1138 224 278 468
22 종로구 1619 464 314 211 630
23 중구 1023 413 190 72 348
24 중랑구 916 509 121 177 109
[25 rows x 6 columns]
read_csv
함수가 한글 이름 파일을 부를 때 오류가 나면 위와 같이 engine='python'
이라고 적어주고 encoding='utf-8'
이라고 적는다. 그런데 가끔씩 헤더가 제대로 나오지 않는 경우가 있는데 이것은 파일 인코딩이 BOM 있음으로 되어 있는 경우 발생할 수 있다. Notepad++ 를 이용해 csv 파일을 열어서 BOM 없음으로 인코딩을 변경하여 저장하고 열면 제대로 열린다.
자료 정보¶
pd.DataFrame.info()
메소드를 이용하여 기본적인 정보를 얻을 수 있다.
In [5]: cctv.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 25 entries, 0 to 24
Data columns (total 6 columns):
기관명 25 non-null object
소계 25 non-null int64
2013년도 이전 25 non-null int64
2014년 25 non-null int64
2015년 25 non-null int64
2016년 25 non-null int64
dtypes: int64(5), object(1)
memory usage: 1.2+ KB
열의 갯수는 6개이고 인덱스의 갯수는 25개 임을 알 수 있다. 즉, 행의 갯수가 25개라는 것이다. 각 열의 정보가 나오는데 non-null
의 갯수가 모두 25개씩임을 알 수 있다. 첫번째 열 기관명
은 object이고 나머지는 정수형 타입인 것을 알 수 있다. 판다스에서 object 타입은 두 개 이상의 타입이 섞여 있거나 문자열로 이루어진 자료를 나타낸다. 따라서 object로 표시된 자료형은 부차적으로 이상치가 있는지를 확인해야 한다.
pd.unique
함수를 이용해서 object 타입의 열이 어떤 것들로 구성되어 있는지를 확인해보자.
In [6]: pd.unique(cctv.기관명)
Out[6]:
array(['강남구', '강동구', '강북구', '강서구', '관악구', '광진구', '구로구', '금천구', '노원구',
'도봉구', '동대문구', '동작구', '마포구', '서대문구', '서초구', '성동구', '성북구', '송파구',
'양천구', '영등포구', '용산구', '은평구', '종로구', '중구', '중랑구'], dtype=object)
중복되지 않는 것의 갯수를 확인해보자.
In [7]: len(pd.unique(cctv.기관명))
Out[7]: 25
행의 갯수와 같으므로 이상한 것을 발견할 수 없다.
Exercise
- null이 있는 열을 찾아서 null인 값들을 출력하라.
열 제거¶
필요없는 열 소계
를 제거하고 매년 더해진 값으로 변경하자. 데이터프레임 drop
메소드는 지정된 열 또는 행 인덱스 이름들을 이용하여 열 또는 행들을 제거할 수 있다.
In [8]: cctv.drop(columns='소계', inplace=True)
...: cctv
...:
Out[9]:
기관명 2013년도 이전 2014년 2015년 2016년
0 강남구 1292 430 584 932
1 강동구 379 99 155 377
2 강북구 369 120 138 204
3 강서구 388 258 184 81
4 관악구 846 260 390 613
.. ... ... ... ... ...
20 용산구 1368 218 112 398
21 은평구 1138 224 278 468
22 종로구 464 314 211 630
23 중구 413 190 72 348
24 중랑구 509 121 177 109
[25 rows x 5 columns]
열 인덱스 이름 변경¶
서울시 인구 자료와 통합하기 위해서 열 인덱스 이름을 변경하자. 열 이름 중에 숫자만 남기고 기관명
은 자치구
로 변경한다.
In [10]: import re
....: cctv.rename(lambda x: re.sub(r'.*(\d{4}).*', r'\1', x), axis=1, inplace=True)
....: cctv.rename({'기관명': '자치구'}, axis=1, inplace=True)
....: cctv.columns
....:
Out[13]: Index(['자치구', '2013', '2014', '2015', '2016'], dtype='object')
cctv 자료는 매년 설치된 숫자를 나타내므로 누적대수로 변경하자. 자치구를 인덱스로 변경하자.
In [14]: cctv.set_index('자치구', inplace=True)
행에 대해서 누적합을 계산한다.
In [15]: cctv = cctv.cumsum(axis=1)
자치구 열로 복귀한다.
In [16]: cctv.reset_index(inplace=True)
서울시 인구¶
자료 저장¶
서울시 주민등록 인구를 사이트 http://data.seoul.go.kr/dataList/datasetView.do?infId=419&srvType=S&serviceKind=2¤tPageNo=1&searchValue=&searchKey=null에서 다운받는다. 다운받을 때 분기를 2013년 1분기부터 시작하고 파일형식은 xls로 한다. 다운받은 파일을 data
디렉토리에 저장한다.
엑셀을 이용하여 자료를 열어보자. 엑셀이 없으면 리브레오피스를 설치해서 열어볼 수 있다.
자료 읽어 오기¶
엑셀 파일에 여러 행으로 이루어진 헤더가 있을 때는 header
옵션을 지정하여 원하는 행들을 헤더로 읽어올 수 있다.
In [17]: pop = pd.read_excel('data/서울시 주민등록인구 (구별) 통계.xls', header=[0, 1, 2])
In [18]: pop.head()
Out[18]:
기간 자치구 세대 인구 인구밀도 세대당인구 65세이상고령자
기간 자치구 세대 합계 한국인 등록외국인 인구밀도 세대당인구 65세이상고령자
기간 자치구 세대 계 남자 여자 계 남자 여자 계 남자 여자 인구밀도(명/㎢) 면적(㎢) 세대당인구 65세이상고령자
2013.1/4 합계 4182314 10437737 5155053 5282684 10192057 5037288 5154769 245680 117765 127915 17247 605.2 2.44 1130508
2013.1/4 종로구 74581 170681 84719 85962 162777 81033 81744 7904 3686 4218 7139 23.91 2.18 23853
2013.1/4 중구 61217 140127 70339 69788 132652 66605 66047 7475 3734 3741 14069 9.96 2.17 19100
2013.1/4 용산구 110479 254749 124300 130449 242630 118012 124618 12119 6288 5831 11649 21.87 2.20 33404
2013.1/4 성동구 127035 309312 154328 154984 302038 150875 151163 7274 3453 3821 18353 16.85 2.38 34721
Note
만일 pd.read_excel
함수를 이용해 xls 파일을 읽을 때 헤더가 여러 개인 셀들로 병합되어 있으면 읽는데 에러가 발생할 수도 있다. 우선 pandas의 설치 버전을 확인해 본다. 아나콘다 명령창에서 conda list pandas
라고 치면 버전 정보가 나온다. 만일 0.23.4 보다 작으면 최신 버전으로 설치한다. 설치하는 방법은 conda install pandas=0.23.4
를 명령창에 입력하면 된다.
자료 정보¶
기본적인 자료 정보를 확인해보자.
In [19]: pop.info()
<class 'pandas.core.frame.DataFrame'>
Index: 572 entries, 2013.1/4 to 2018.2/4
Data columns (total 15 columns):
(자치구, 자치구, 자치구) 572 non-null object
(세대, 세대, 세대) 572 non-null int64
(인구, 합계, 계) 572 non-null int64
(인구, 합계, 남자) 572 non-null int64
(인구, 합계, 여자) 572 non-null int64
(인구, 한국인, 계) 572 non-null int64
(인구, 한국인, 남자) 572 non-null int64
(인구, 한국인, 여자) 572 non-null int64
(인구, 등록외국인, 계) 572 non-null int64
(인구, 등록외국인, 남자) 572 non-null int64
(인구, 등록외국인, 여자) 572 non-null int64
(인구밀도, 인구밀도, 인구밀도(명/㎢)) 572 non-null object
(인구밀도, 인구밀도, 면적(㎢)) 572 non-null object
(세대당인구, 세대당인구, 세대당인구) 572 non-null float64
(65세이상고령자, 65세이상고령자, 65세이상고령자) 572 non-null int64
dtypes: float64(1), int64(11), object(3)
memory usage: 71.5+ KB
행 인덱스의 갯수는 572개이고 열의 갯수는 15개임을 알 수 있다. object 열은 자치구, 인구밀도, 면적이 있고 나머지는 지정된 타입이 있음을 알 수 있다.
지정된 타입의 열만 선택¶
pandas.DataFrame.select_dtypes
을 이용하여 원하는 타입의 열들만 골라 낼 수 있다.
In [20]: obj = pop.select_dtypes('object')
null의 갯수를 살펴보자.
In [21]: obj.info()
<class 'pandas.core.frame.DataFrame'>
Index: 572 entries, 2013.1/4 to 2018.2/4
Data columns (total 3 columns):
(자치구, 자치구, 자치구) 572 non-null object
(인구밀도, 인구밀도, 인구밀도(명/㎢)) 572 non-null object
(인구밀도, 인구밀도, 면적(㎢)) 572 non-null object
dtypes: object(3)
memory usage: 17.9+ KB
null의 갯수는 하나도 없는 것을 알 수 있다. 그럼 자치구 열이 이상치가 있는지를 unique 함수를 이용해서 살펴보자.
In [22]: pd.unique(obj['자치구'].squeeze())
Out[22]:
array(['합계', '종로구', '중구', '용산구', '성동구', '광진구', '동대문구', '중랑구', '성북구',
'강북구', '도봉구', '노원구', '은평구', '서대문구', '마포구', '양천구', '강서구', '구로구',
'금천구', '영등포구', '동작구', '관악구', '서초구', '강남구', '송파구', '강동구'],
dtype=object)
여기서 pd.DataFrame.squeeze()
메소드를 사용한 이유는 obj['자치구']
객체가 데이터프레임이기 때문에 pd.unique
함수를 사용할 수 없어서 squeeze
를 이용해서 시리즈로 변형하여 적용한 것이다.
살펴보면 합계 열만 하나 추가되어 있는 것을 알 수 있다. 아래에서 ‘합계’ 행은 모두 제거할 것이다.
다음은 인구밀도 열을 살펴보자. 레벨 2에 있는 인구밀도 이름에는 제곱 킬로미터가 들어가 있어서 텍스트로 표현하기가 까다롭다. 따라서 iloc
접근자를 이용해서 숫자로 접근하자. 또한 apply 메소드를 이용해서 각 성분의 type을 알아내어 몇 가지 타입으로 이루어졌는지를 확인하자.
In [23]: pd.unique(obj.iloc[:, 1].apply(type))
Out[23]: array([<class 'int'>, <class 'str'>], dtype=object)
정수형과 문자열형으로 이루어진 것을 알 수 있다. 문자열 형으로 이루어진 자료들의 형태를 알아보자.
In [24]: pd.unique(obj.iloc[:, 1][obj.iloc[:, 1].apply(lambda x: type(x) == str)])
Out[24]: array(['-'], dtype=object)
즉 -
하나밖에 없는 것을 알 수 있다. 마찬가지로 면적 열에 대해서 살펴보자.
In [25]: pd.unique(obj.iloc[:, 2].apply(type))
....: pd.unique(obj.iloc[:, 2][obj.iloc[:, 2].apply(lambda x: type(x) == str)])
....:
Out[25]: array([<class 'float'>, <class 'int'>, <class 'str'>], dtype=object)
Out[26]: array(['-'], dtype=object)
기간을 시간 객체화¶
인덱스를 판다스 Period 인덱스로 변환하자. Period 객체는 시간을 기간으로 표시한다. Period(freq='Q', year=2018, quarter=1)
형식을 사용하면 2018년 1분기 기간을 의미한다. 즉, 2018년 1월 1일부터 3월 31일까지 기간을 표현한다.
우선 pop 인덱스를 정규표현식을 이용하여 원하는 부분을 골라내어 처리하도록 하자.
In [27]: import re
....: regex = re.compile(r'(\d{4}).(\d)')
....:
In [29]: pop.index = pop.index.map(lambda x: pd.Period(freq='Q', year=int(regex.match(x)[1]),
....: quarter=int(regex.match(x)[2])))
....: pop.index
....:
Out[29]:
PeriodIndex(['2013Q1', '2013Q1', '2013Q1', '2013Q1', '2013Q1', '2013Q1',
'2013Q1', '2013Q1', '2013Q1', '2013Q1',
...
'2018Q2', '2018Q2', '2018Q2', '2018Q2', '2018Q2', '2018Q2',
'2018Q2', '2018Q2', '2018Q2', '2018Q2'],
dtype='period[Q-DEC]', length=572, freq='Q-DEC')
원하는 인덱스는 다음과 같이 선택할 수 있다.
2018년 1분기를 선택하려면 다음과 같이 한다.
In [30]: pop.loc['2018Q1']
Out[30]:
기간 자치구 세대 인구 인구밀도 세대당인구 65세이상고령자
기간 자치구 세대 합계 한국인 등록외국인 인구밀도 세대당인구 65세이상고령자
기간 자치구 세대 계 남자 여자 계 남자 여자 계 남자 여자 인구밀도(명/㎢) 면적(㎢) 세대당인구 65세이상고령자
2018Q1 합계 4237610 10112070 4948481 5163589 9838892 4817507 5021385 273178 130974 142204 - - 2.32 1382420
2018Q1 종로구 73879 164348 79962 84386 154549 75749 78800 9799 4213 5586 - - 2.09 26429
2018Q1 중구 60903 135139 66582 68557 126082 62376 63706 9057 4206 4851 - - 2.07 21655
2018Q1 용산구 108497 245411 119985 125426 229909 111262 118647 15502 8723 6779 - - 2.12 37238
2018Q1 성동구 134543 314551 154672 159879 306532 150937 155595 8019 3735 4284 - - 2.28 41752
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
2018Q1 관악구 258536 522292 262428 259864 504445 253911 250534 17847 8517 9330 - - 1.95 70807
2018Q1 서초구 174225 445164 213279 231885 440954 211156 229798 4210 2123 2087 - - 2.53 54055
2018Q1 강남구 231219 557865 267314 290551 552976 264834 288142 4889 2480 2409 - - 2.39 65859
2018Q1 송파구 266550 671994 326511 345483 665282 323228 342054 6712 3283 3429 - - 2.50 77978
2018Q1 강동구 177490 438225 217570 220655 434027 215588 218439 4198 1982 2216 - - 2.45 56983
[26 rows x 15 columns]
2013년도 전체를 선택하려면 다음과 같이 한다. 또는 pop['2013']
과 같이 해도 된다.
In [31]: pop.loc['2013Q1':'2013Q4']
Out[31]:
기간 자치구 세대 인구 인구밀도 세대당인구 65세이상고령자
기간 자치구 세대 합계 한국인 등록외국인 인구밀도 세대당인구 65세이상고령자
기간 자치구 세대 계 남자 여자 계 남자 여자 계 남자 여자 인구밀도(명/㎢) 면적(㎢) 세대당인구 65세이상고령자
2013Q1 합계 4182314 10437737 5155053 5282684 10192057 5037288 5154769 245680 117765 127915 17247 605.2 2.44 1130508
2013Q1 종로구 74581 170681 84719 85962 162777 81033 81744 7904 3686 4218 7139 23.91 2.18 23853
2013Q1 중구 61217 140127 70339 69788 132652 66605 66047 7475 3734 3741 14069 9.96 2.17 19100
2013Q1 용산구 110479 254749 124300 130449 242630 118012 124618 12119 6288 5831 11649 21.87 2.20 33404
2013Q1 성동구 127035 309312 154328 154984 302038 150875 151163 7274 3453 3821 18353 16.85 2.38 34721
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
2013Q4 관악구 248006 535128 270512 264616 518028 262248 255780 17100 8264 8836 18097 29.57 2.09 59705
2013Q4 서초구 170594 446541 215771 230770 441763 213285 228478 4778 2486 2292 9501 47 2.59 44674
2013Q4 강남구 230645 569152 273583 295569 563599 270601 292998 5553 2982 2571 14409 39.5 2.44 53461
2013Q4 송파구 257441 674955 330030 344925 668415 326919 341496 6540 3111 3429 19925 33.87 2.60 61745
2013Q4 강동구 186764 487969 243530 244439 483379 241503 241876 4590 2027 2563 19846 24.59 2.59 47812
[104 rows x 15 columns]
행 인덱스를 데이터프레임의 열로 변경하자.
In [32]: pop.reset_index(col_fill='기간', inplace=True)
....: pop
....:
Out[33]:
기간 index 자치구 세대 인구 인구밀도 세대당인구 65세이상고령자
기간 기간 자치구 세대 합계 한국인 등록외국인 인구밀도 세대당인구 65세이상고령자
기간 기간 자치구 세대 계 남자 여자 계 남자 여자 계 남자 여자 인구밀도(명/㎢) 면적(㎢) 세대당인구 65세이상고령자
0 2013Q1 합계 4182314 10437737 5155053 5282684 10192057 5037288 5154769 245680 117765 127915 17247 605.2 2.44 1130508
1 2013Q1 종로구 74581 170681 84719 85962 162777 81033 81744 7904 3686 4218 7139 23.91 2.18 23853
2 2013Q1 중구 61217 140127 70339 69788 132652 66605 66047 7475 3734 3741 14069 9.96 2.17 19100
3 2013Q1 용산구 110479 254749 124300 130449 242630 118012 124618 12119 6288 5831 11649 21.87 2.20 33404
4 2013Q1 성동구 127035 309312 154328 154984 302038 150875 151163 7274 3453 3821 18353 16.85 2.38 34721
.. ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
567 2018Q2 관악구 259681 521960 262235 259725 504048 253600 250448 17912 8635 9277 - - 1.94 71317
568 2018Q2 서초구 174268 443989 212679 231310 439844 210596 229248 4145 2083 2062 - - 2.52 54614
569 2018Q2 강남구 229160 551888 264332 287556 546952 261815 285137 4936 2517 2419 - - 2.39 66011
570 2018Q2 송파구 268325 673161 327051 346110 666439 323741 342698 6722 3310 3412 - - 2.48 79093
571 2018Q2 강동구 177605 437050 216814 220236 432749 214740 218009 4301 2074 2227 - - 2.44 57680
[572 rows x 16 columns]
0레벨 열 이름을 기간
으로 변경하자.
In [34]: pop.rename(columns={'index': '기간'}, inplace=True)
....: pop.head()
....:
Out[35]:
기간 기간 자치구 세대 인구 인구밀도 세대당인구 65세이상고령자
기간 기간 자치구 세대 합계 한국인 등록외국인 인구밀도 세대당인구 65세이상고령자
기간 기간 자치구 세대 계 남자 여자 계 남자 여자 계 남자 여자 인구밀도(명/㎢) 면적(㎢) 세대당인구 65세이상고령자
0 2013Q1 합계 4182314 10437737 5155053 5282684 10192057 5037288 5154769 245680 117765 127915 17247 605.2 2.44 1130508
1 2013Q1 종로구 74581 170681 84719 85962 162777 81033 81744 7904 3686 4218 7139 23.91 2.18 23853
2 2013Q1 중구 61217 140127 70339 69788 132652 66605 66047 7475 3734 3741 14069 9.96 2.17 19100
3 2013Q1 용산구 110479 254749 124300 130449 242630 118012 124618 12119 6288 5831 11649 21.87 2.20 33404
4 2013Q1 성동구 127035 309312 154328 154984 302038 150875 151163 7274 3453 3821 18353 16.85 2.38 34721
열 인덱스의 이름을 없애자.
In [36]: pop.columns.set_names([None, None, None], inplace=True)
....: pop.head()
....:
Out[37]:
기간 자치구 세대 인구 인구밀도 세대당인구 65세이상고령자
기간 자치구 세대 합계 한국인 등록외국인 인구밀도 세대당인구 65세이상고령자
기간 자치구 세대 계 남자 여자 계 남자 여자 계 남자 여자 인구밀도(명/㎢) 면적(㎢) 세대당인구 65세이상고령자
0 2013Q1 합계 4182314 10437737 5155053 5282684 10192057 5037288 5154769 245680 117765 127915 17247 605.2 2.44 1130508
1 2013Q1 종로구 74581 170681 84719 85962 162777 81033 81744 7904 3686 4218 7139 23.91 2.18 23853
2 2013Q1 중구 61217 140127 70339 69788 132652 66605 66047 7475 3734 3741 14069 9.96 2.17 19100
3 2013Q1 용산구 110479 254749 124300 130449 242630 118012 124618 12119 6288 5831 11649 21.87 2.20 33404
4 2013Q1 성동구 127035 309312 154328 154984 302038 150875 151163 7274 3453 3821 18353 16.85 2.38 34721
합계 행 제거¶
자치구 열중에서 합계 행을 제거하자.
In [38]: pop = pop[pop.loc[:, ('자치구', '자치구', '자치구')] != '합계']
....: pop
....:
Out[39]:
기간 자치구 세대 인구 인구밀도 세대당인구 65세이상고령자
기간 자치구 세대 합계 한국인 등록외국인 인구밀도 세대당인구 65세이상고령자
기간 자치구 세대 계 남자 여자 계 남자 여자 계 남자 여자 인구밀도(명/㎢) 면적(㎢) 세대당인구 65세이상고령자
1 2013Q1 종로구 74581 170681 84719 85962 162777 81033 81744 7904 3686 4218 7139 23.91 2.18 23853
2 2013Q1 중구 61217 140127 70339 69788 132652 66605 66047 7475 3734 3741 14069 9.96 2.17 19100
3 2013Q1 용산구 110479 254749 124300 130449 242630 118012 124618 12119 6288 5831 11649 21.87 2.20 33404
4 2013Q1 성동구 127035 309312 154328 154984 302038 150875 151163 7274 3453 3821 18353 16.85 2.38 34721
5 2013Q1 광진구 158987 383806 188983 194823 371170 183297 187873 12636 5686 6950 22494 17.06 2.33 36427
.. ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
567 2018Q2 관악구 259681 521960 262235 259725 504048 253600 250448 17912 8635 9277 - - 1.94 71317
568 2018Q2 서초구 174268 443989 212679 231310 439844 210596 229248 4145 2083 2062 - - 2.52 54614
569 2018Q2 강남구 229160 551888 264332 287556 546952 261815 285137 4936 2517 2419 - - 2.39 66011
570 2018Q2 송파구 268325 673161 327051 346110 666439 323741 342698 6722 3310 3412 - - 2.48 79093
571 2018Q2 강동구 177605 437050 216814 220236 432749 214740 218009 4301 2074 2227 - - 2.44 57680
[550 rows x 16 columns]
또는 drop
메소드를 이용해서 pop.drop(pop[(pop.loc[:, '자치구'] == '합계').squeeze()].index, inplace=True)
와 같이 해도 된다.
2013년 1분기 자료 분석¶
2013년도 1분기 자료만 선택하자.
In [40]: idx = pd.IndexSlice
In [41]: pop2013q1 = pop.loc[pop.loc[:, ('기간', '기간', '기간')] == pd.Period('2013Q1'), idx[:, ['자치구', '합계'], ['자치구', '계']]]
....: pop2013q1
....:
Out[42]:
자치구 인구
자치구 합계
자치구 계
1 종로구 170681
2 중구 140127
3 용산구 254749
4 성동구 309312
5 광진구 383806
.. ... ...
21 관악구 540467
22 서초구 442600
23 강남구 568982
24 송파구 676551
25 강동구 491770
[25 rows x 2 columns]
열 인덱스의 1, 2레벨을 제거하자.
In [43]: pop2013q1.columns = pop2013q1.columns.droplevel(level=[1, 2])
....: pop2013q1.head()
....:
Out[44]:
자치구 인구
1 종로구 170681
2 중구 140127
3 용산구 254749
4 성동구 309312
5 광진구 383806
seaborn 패키지를 이용하여 그래프를 그려보자. 한글을 사용할 수 있도록 글씨체를 변경한다.
In [45]: from matplotlib import font_manager, rc
....: font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
....: rc('font', family=font_name)
....:
sns.barplot
의 반환값은 matplotlib
의 axes
객체이다.
In [48]: import seaborn as sns
....: ax = sns.barplot(data=pop2013q1, x='자치구', y='인구')
....: ax.set_title('서울시 2013년 1분기 인구');
....: ax.set_xticklabels(ax.get_xticklabels(), rotation=70);
....: ax.figure.tight_layout()
....:
figure.tight_layout
메소드는 불필요한 공간과 필요한 공간들을 계산해서 보여준다.
차례로 정렬해서 보여주자. order
옵션을 이용한다.
In [53]: ax.clear()
....: ax = sns.barplot(x='자치구', y='인구', data=pop2013q1, order=pop2013q1.sort_values('인구')['자치구'])
....: ax.set_title('서울시 2013년 1분기 인구');
....: ax.set_xticklabels(ax.get_xticklabels(), rotation=70);
....: ax.figure.tight_layout()
....:
기간, 자치구, 인구, 고령자 열¶
기간, 자치구, 인구, 65세이상고령자만 간추리자.
In [58]: pop1 = pop.loc[:,['기간', '자치구', '인구', '65세이상고령자']]
열 이름 변경¶
열 이름을 변경하자.
In [59]: pop1.rename(columns={'65세이상고령자': '고령자', '등록외국인': '외국인'}, inplace=True)
연도 추가¶
연도 열을 추가하자.
In [60]: pop1[('연도', '연도', '연도')] = pop1[('기간', '기간', '기간')].map(lambda x: x.year)
....: pop1.head()
....:
Out[61]:
기간 자치구 인구 고령자 연도
기간 자치구 합계 한국인 외국인 고령자 연도
기간 자치구 계 남자 여자 계 남자 여자 계 남자 여자 고령자 연도
1 2013Q1 종로구 170681 84719 85962 162777 81033 81744 7904 3686 4218 23853 2013
2 2013Q1 중구 140127 70339 69788 132652 66605 66047 7475 3734 3741 19100 2013
3 2013Q1 용산구 254749 124300 130449 242630 118012 124618 12119 6288 5831 33404 2013
4 2013Q1 성동구 309312 154328 154984 302038 150875 151163 7274 3453 3821 34721 2013
5 2013Q1 광진구 383806 188983 194823 371170 183297 187873 12636 5686 6950 36427 2013
연도별 평균 인구¶
년도별 평균 인구수는 다음과 같다.
In [62]: pyear = pop1.groupby([('연도', '연도', '연도')]).agg('mean')
....: pyear.head()
....:
Out[63]:
인구 고령자
합계 한국인 외국인 고령자
계 남자 여자 계 남자 여자 계 남자 여자 고령자
(연도, 연도, 연도)
2013 416616.23 205667.22 210949.01 406789.53 200934.14 205855.39 9826.70 4733.08 5093.62 45955.58
2014 415250.77 204711.15 210539.62 404968.44 199724.41 205244.03 10282.33 4986.74 5295.59 48095.63
2015 413475.44 203471.00 210004.44 402570.45 198189.44 204381.01 10904.99 5281.56 5623.43 50153.99
2016 409842.58 201274.23 208568.35 398942.90 196046.02 202896.88 10899.68 5228.21 5671.47 51607.68
2017 406589.89 199215.66 207374.23 395902.23 194135.34 201766.89 10687.66 5080.32 5607.34 53792.81
그래프로 그려보자.
In [64]: pyear = pyear.reset_index()
....: ax.clear()
....: ax = sns.pointplot(data=pyear, x=('연도', '연도', '연도'), y=('인구', '합계', '계'))
....: ax.set_title('서울시 연도별 평균 인구')
....: ax.set_xlabel('연도')
....: ax.set_ylabel('인구')
....:
Out[67]: Text(0.5,1,'서울시 연도별 평균 인구')
Out[68]: Text(0.5,28.0781,'연도')
Out[69]: Text(28.0625,0.5,'인구')
자치구별 평균 인구¶
연도별 자치구별 평균 인구수를 구하자.
In [70]: pop_year_gu = pop1.groupby([('연도', '연도', '연도'), ('자치구', '자치구', '자치구')]).agg('mean')
....: pop_year_gu.head()
....:
Out[71]:
인구 고령자
합계 한국인 외국인 고령자
계 남자 여자 계 남자 여자 계 남자 여자 고령자
(연도, 연도, 연도) (자치구, 자치구, 자치구)
2013 강남구 568740.50 273372.00 295368.50 562984.50 270253.00 292731.50 5756.00 3119.00 2637.00 52363.00
강동구 489935.50 244664.00 245271.50 485206.50 242590.25 242616.25 4729.00 2073.75 2655.25 46857.75
강북구 343989.75 169604.25 174385.50 340749.75 168392.50 172357.25 3240.00 1211.75 2028.25 47696.75
강서구 574508.50 282051.25 292457.25 568303.50 278946.25 289357.25 6205.00 3105.00 3100.00 58282.00
관악구 538154.25 272205.75 265948.50 520870.50 263908.75 256961.75 17283.75 8297.00 8986.75 58694.50
인덱스를 열로 변경하자.
In [72]: yg = pop_year_gu.reset_index()
....: yg.head()
....:
Out[73]:
연도 자치구 인구 고령자
연도 자치구 합계 한국인 외국인 고령자
연도 자치구 계 남자 여자 계 남자 여자 계 남자 여자 고령자
0 2013 강남구 568740.50 273372.00 295368.50 562984.50 270253.00 292731.50 5756.00 3119.00 2637.00 52363.00
1 2013 강동구 489935.50 244664.00 245271.50 485206.50 242590.25 242616.25 4729.00 2073.75 2655.25 46857.75
2 2013 강북구 343989.75 169604.25 174385.50 340749.75 168392.50 172357.25 3240.00 1211.75 2028.25 47696.75
3 2013 강서구 574508.50 282051.25 292457.25 568303.50 278946.25 289357.25 6205.00 3105.00 3100.00 58282.00
4 2013 관악구 538154.25 272205.75 265948.50 520870.50 263908.75 256961.75 17283.75 8297.00 8986.75 58694.50
그래프를 그려보자.
In [74]: sns.relplot(x=('연도', '연도', '연도'), y=('인구', '합계', '계'),
....: data=yg, kind='line', hue=('자치구', '자치구', '자치구'))
....:
Out[74]: <seaborn.axisgrid.FacetGrid at 0x156b37f9278>
범위가 넓어서 인구의 증가를 알아보기 어렵다. 각각의 y
축의 범위를 다르게 설정하고 그려보자.
In [75]: g = sns.FacetGrid(yg, col=('자치구', '자치구', '자치구'), col_wrap=7, sharey=False)
....: g.map(sns.lineplot, ('연도', '연도', '연도'), ('인구', '합계', '계'))
....: g.fig.suptitle('서울시 자치구별 연 평균 인구', size=16)
....: g.fig.subplots_adjust(top=.9)
....:
Out[76]: <seaborn.axisgrid.FacetGrid at 0x156b2e36390>
Out[77]: Text(0.5,0.98,'서울시 자치구별 연 평균 인구')
분기별 인구수 변동¶
자치구별 분기에 대한 인구수 변동을 살펴보자.
In [79]: pop1[('날짜', '날짜', '날짜')] = pop1.loc[:, ('기간', '기간', '기간')].apply(lambda x: x.to_timestamp())
....: pop1
....:
Out[80]:
기간 자치구 인구 고령자 연도 날짜
기간 자치구 합계 한국인 외국인 고령자 연도 날짜
기간 자치구 계 남자 여자 계 남자 여자 계 남자 여자 고령자 연도 날짜
1 2013Q1 종로구 170681 84719 85962 162777 81033 81744 7904 3686 4218 23853 2013 2013-01-01
2 2013Q1 중구 140127 70339 69788 132652 66605 66047 7475 3734 3741 19100 2013 2013-01-01
3 2013Q1 용산구 254749 124300 130449 242630 118012 124618 12119 6288 5831 33404 2013 2013-01-01
4 2013Q1 성동구 309312 154328 154984 302038 150875 151163 7274 3453 3821 34721 2013 2013-01-01
5 2013Q1 광진구 383806 188983 194823 371170 183297 187873 12636 5686 6950 36427 2013 2013-01-01
.. ... ... ... ... ... ... ... ... ... ... ... ... ... ...
567 2018Q2 관악구 521960 262235 259725 504048 253600 250448 17912 8635 9277 71317 2018 2018-04-01
568 2018Q2 서초구 443989 212679 231310 439844 210596 229248 4145 2083 2062 54614 2018 2018-04-01
569 2018Q2 강남구 551888 264332 287556 546952 261815 285137 4936 2517 2419 66011 2018 2018-04-01
570 2018Q2 송파구 673161 327051 346110 666439 323741 342698 6722 3310 3412 79093 2018 2018-04-01
571 2018Q2 강동구 437050 216814 220236 432749 214740 218009 4301 2074 2227 57680 2018 2018-04-01
[550 rows x 14 columns]
In [81]: g = sns.FacetGrid(pop1, col=('자치구', '자치구', '자치구'), col_wrap=7, sharey=False)
....: g.map(sns.lineplot, ('날짜', '날짜', '날짜'), ('인구', '합계', '계'))
....: g.fig.suptitle('서울시 자치구 분기별 평균 인구', size=16)
....: g.fig.subplots_adjust(top=.9)
....: