서울시의 CCTV 현황은 구글 검색만으로도 쉽게 얻을 수 있다.
CSV 파일은 콤마로 구분된 텍스트 파일입니다. 우리나라에서는 잘통용되지 않지만 외국 자료를 검색하다 보면 자주 나타납니다. 텍스트 파일이기 때문에 메모장에서도 열립니다. 그런데 만약 CSV 파일에 한글이 UTF-8로 인코딩 되어있다면 엑셀에서 열었을 때 한글이 깨져 보일 수 있습니다. 그것은 MS 오피스 제품들이 UFT-8을 지원하지 않기 때문이며 파일에 문제가 있는 것은 아닙니다.
(저는 ubuntu를 사용하고 있기때문에 github를 클론 따와서 사용하였습니다.)
https://github.com/PinkWink/DataScience
파이썬에서는 이런 종류의 파일을 쉽게 읽을 수 있는 모듈이 있습니다. 바로 pandas라는 모듈입니다.
import pandas as pd
파이썬에서 원하는 모듈을 불러오는 명령이 import입니다. 그뒤에 원하는 모듈명을 넣게 됩니다. 모듈명이 다음의 as는 as뒤에 붙는 pd라는 짧은 단어로 pandas를 대체하겠다는 뜻입니다. 즉, pandas의 read_csv라는 의미로 pandas.read_csv라고 하지 않고 pd.read_csv라고 쓰겠다는 의미 입니다.
pandas에서 CSV 파일을 읽는 명령은 read_csv입니다. 그안에 한글을 사용하는 경우는 인코딩에 신경 써야 합니다. 우리가 받은 CCTV 데이터는 UTF-8로 인코딩되어 있어서 read_csv 명령을 쓸 때 incoding 옵션에 UTF-8이라고 저장해야 합니다. 이때 사용된 head() 명령은 pandas 데이터의 첫 5행만 보여달라는 것입니다.
CCTV_Seoul = pd.read_csv('./data/01. CCTV_in_Seoul.csv', encoding='utf-8')
CCTV_Seoul.head()
기관명 | 소계 | 2013년도 이전 | 2014년 | 2015년 | 2016년 | |
---|---|---|---|---|---|---|
0 | 강남구 | 2780 | 1292 | 430 | 584 | 932 |
1 | 강동구 | 773 | 379 | 99 | 155 | 377 |
2 | 강북구 | 748 | 369 | 120 | 138 | 204 |
3 | 강서구 | 884 | 388 | 258 | 184 | 81 |
4 | 관악구 | 1496 | 846 | 260 | 390 | 613 |
pandas 데이터는 제일 첫 줄에 보이는 것이 해당하는 열(column)을 대표하는ㄴ 일종의 제목입니다.
그래서 첫 줄은 특별히 column이라고 합니다. 데이터 뒤에 columns라고 하면 column의 이름들이 반환됩니다.
CCTV_Seoul.columns
Index(['기관명', '소계', '2013년도 이전', '2014년', '2015년', '2016년'], dtype='object')
CCTV_Seoul.columns[0]
'기관명'
그런데 기관명이라고 되어 있으니 이를 '구별'이라는 이름으로 하겠습니다. 이렇게 이름을 바꾸는 명령은 rename이라고 합니다. 그리고 그 명령 안에 사용된 inplace=True는 실제 CCTV_Seoul이라는 변수의 내용을 갱신하라는 의미 입니다.
CCTV_Seoul.rename(columns={CCTV_Seoul.columns[0] : '구별'}, inplace=True)
CCTV_Seoul.head()
구별 | 소계 | 2013년도 이전 | 2014년 | 2015년 | 2016년 | |
---|---|---|---|---|---|---|
0 | 강남구 | 2780 | 1292 | 430 | 584 | 932 |
1 | 강동구 | 773 | 379 | 99 | 155 | 377 |
2 | 강북구 | 748 | 369 | 120 | 138 | 204 |
3 | 강서구 | 884 | 388 | 258 | 184 | 81 |
4 | 관악구 | 1496 | 846 | 260 | 390 | 613 |
이제 엑셀 파일을 읽어야 합니다. 그명령은 read_excel입니다. 그냥 인코딩만 지정하고 읽었더니 약간 이상합니다. 그것은 원본 엑셀 파일이 첫 세줄이 열의 제목처럼 되어 있기 때문입니다.
pop_Seoul = pd.read_csv('./data/01. population_in_Seoul.csv', encoding='utf-8')
pop_Seoul.head()
기간 | 자치구 | 세대 | 인구 | 인구.1 | 인구.2 | 인구.3 | 인구.4 | 인구.5 | 인구.6 | 인구.7 | 인구.8 | 세대당인구 | 65세이상고령자 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 기간 | 자치구 | 세대 | 합계 | 합계 | 합계 | 한국인 | 한국인 | 한국인 | 등록외국인 | 등록외국인 | 등록외국인 | 세대당인구 | 65세이상고령자 |
1 | 기간 | 자치구 | 세대 | 계 | 남자 | 여자 | 계 | 남자 | 여자 | 계 | 남자 | 여자 | 세대당인구 | 65세이상고령자 |
2 | 2017.1/4 | 합계 | 4,202,888 | 10,197,604 | 5,000,005 | 5,197,599 | 9,926,968 | 4,871,560 | 5,055,408 | 270,636 | 128,445 | 142,191 | 2.36 | 1,321,458 |
3 | 2017.1/4 | 종로구 | 72,654 | 162,820 | 79,675 | 83,145 | 153,589 | 75,611 | 77,978 | 9,231 | 4,064 | 5,167 | 2.11 | 25,425 |
4 | 2017.1/4 | 중구 | 59,481 | 133,240 | 65,790 | 67,450 | 124,312 | 61,656 | 62,656 | 8,928 | 4,134 | 4,794 | 2.09 | 20,764 |
일단 엑셀을 읽는 rad_excel 명령 안에서 세 번째 줄부터 읽으라는 header=2라는 옵션을 걸고, B, D, G, J, N 열만 읽도록 parse_cols='B,D,G,J,N'이라는 옵션을 넣었습니다.
pop_Seoul = pd.read_csv('./data/01. population_in_Seoul.csv',
header = 2,
encoding='utf-8')
pop_Seoul.head()
기간 | 자치구 | 세대 | 계 | 남자 | 여자 | 계.1 | 남자.1 | 여자.1 | 계.2 | 남자.2 | 여자.2 | 세대당인구 | 65세이상고령자 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2017.1/4 | 합계 | 4,202,888 | 10,197,604 | 5,000,005 | 5,197,599 | 9,926,968 | 4,871,560 | 5,055,408 | 270,636 | 128,445 | 142,191 | 2.36 | 1,321,458 |
1 | 2017.1/4 | 종로구 | 72,654 | 162,820 | 79,675 | 83,145 | 153,589 | 75,611 | 77,978 | 9,231 | 4,064 | 5,167 | 2.11 | 25,425 |
2 | 2017.1/4 | 중구 | 59,481 | 133,240 | 65,790 | 67,450 | 124,312 | 61,656 | 62,656 | 8,928 | 4,134 | 4,794 | 2.09 | 20,764 |
3 | 2017.1/4 | 용산구 | 106,544 | 244,203 | 119,132 | 125,071 | 229,456 | 111,167 | 118,289 | 14,747 | 7,965 | 6,782 | 2.15 | 36,231 |
4 | 2017.1/4 | 성동구 | 130,868 | 311,244 | 153,768 | 157,476 | 303,380 | 150,076 | 153,304 | 7,864 | 3,692 | 4,172 | 2.32 | 39,997 |
출력된 모습도 간결하게 되었습니다. 그러나 컬럼의 이름에 문제가 있는 듯합니다. rename 명령을 사용해서 컬럼의 이름을 변경합니다.
pop_Seoul.rename(columns={pop_Seoul.columns[0] : '구별',
pop_Seoul.columns[1] : '인구수',
pop_Seoul.columns[2] : '한국인',
pop_Seoul.columns[3] : '외국인',
pop_Seoul.columns[4] : '고령자'}, inplace=True)
pop_Seoul.head()
구별 | 인구수 | 한국인 | 외국인 | 고령자 | 여자 | 계.1 | 남자.1 | 여자.1 | 계.2 | 남자.2 | 여자.2 | 세대당인구 | 65세이상고령자 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2017.1/4 | 합계 | 4,202,888 | 10,197,604 | 5,000,005 | 5,197,599 | 9,926,968 | 4,871,560 | 5,055,408 | 270,636 | 128,445 | 142,191 | 2.36 | 1,321,458 |
1 | 2017.1/4 | 종로구 | 72,654 | 162,820 | 79,675 | 83,145 | 153,589 | 75,611 | 77,978 | 9,231 | 4,064 | 5,167 | 2.11 | 25,425 |
2 | 2017.1/4 | 중구 | 59,481 | 133,240 | 65,790 | 67,450 | 124,312 | 61,656 | 62,656 | 8,928 | 4,134 | 4,794 | 2.09 | 20,764 |
3 | 2017.1/4 | 용산구 | 106,544 | 244,203 | 119,132 | 125,071 | 229,456 | 111,167 | 118,289 | 14,747 | 7,965 | 6,782 | 2.15 | 36,231 |
4 | 2017.1/4 | 성동구 | 130,868 | 311,244 | 153,768 | 157,476 | 303,380 | 150,076 | 153,304 | 7,864 | 3,692 | 4,172 | 2.32 | 39,997 |
CCTV_Seoul이라는 변수에는 '구별 CCTV 현황'을, pop_Seoul이라는 변수에는 '구별 인구 현황'을 저장했습니다. pandas에서 몇 줄 입력하지 않았는데 우리는 두종류의 파일을 보기 좋게 읽게 되었습니다.
pandas의 사용 빈도가 높아서 기초를 익히고 지나가자 그리고 본 절의 튜토리얼은 pandas의 튜토리얼이 다수 포함되어 있습니다.
import pandas as pd
import numpy as np
s = pd.Series([1,3,5,np.nan,6,8])
s
0 1.0 1 3.0 2 5.0 3 NaN 4 6.0 5 8.0 dtype: float64
pandas의 데이터 유형 중 기초가 되는 것이 Series입니다. 대괄호로 만드는 파이썬의 list 데이터로 만들 수 있습니다. 중간에 NaN(Not A Number)도 넣었습니다.
dates = pd.date_range('20130101', periods=6)
dates
DatetimeIndex(['2013-01-01', '2013-01-02', '2013-01-03', '2013-01-04', '2013-01-05', '2013-01-06'], dtype='datetime64[ns]', freq='D')
또 pandas에는 날짜형의 데이터인 date_range가 있습니다. 기본 날짜를 지정하고 periods 옵션으로 6일간이라고 지정합니다.
df = pd.DataFrame(np.random.randn(6,4), index=dates,
columns=['A','B','C','D'])
df
A | B | C | D | |
---|---|---|---|---|
2013-01-01 | -0.586623 | -1.711500 | -0.789121 | -1.037953 |
2013-01-02 | 1.039448 | 1.173826 | 0.596398 | -1.223662 |
2013-01-03 | 0.190482 | -0.793326 | -1.383294 | 0.182236 |
2013-01-04 | -0.457344 | -0.393111 | -0.047798 | -0.353287 |
2013-01-05 | -1.592768 | -0.342176 | -2.351460 | 0.018436 |
2013-01-06 | 0.398149 | 1.222480 | -0.424658 | -0.595704 |
이제 DataFrame 유형의 데이터를 만듭니다. 6행 4열의 random 변수를 만들고, 컬럼에는 columns=['A','B','C','D']로 지정하고, index 명령으로 만든 날짜형 데이터인 dates를 index=dates 옵션을 이용해서 저장했습니다.
df.head(3)
A | B | C | D | |
---|---|---|---|---|
2013-01-01 | -0.586623 | -1.711500 | -0.789121 | -1.037953 |
2013-01-02 | 1.039448 | 1.173826 | 0.596398 | -1.223662 |
2013-01-03 | 0.190482 | -0.793326 | -1.383294 | 0.182236 |
head() 명령을 사용하면 기본적으로 첫 5행을 보여줍니다. 괄호 안에 숫자를 널어두면 그 숫자만큼의 행을 볼 수 있습니다.
df.index
DatetimeIndex(['2013-01-01', '2013-01-02', '2013-01-03', '2013-01-04', '2013-01-05', '2013-01-06'], dtype='datetime64[ns]', freq='D')
df.columns
Index(['A', 'B', 'C', 'D'], dtype='object')
그리고 index,columns 명령을 이용하면 pandas의 DataFrame의 컬럼과 인덱스를 확안할 수 있습니다.
df.values
array([[-0.58662331, -1.71149972, -0.78912134, -1.03795269], [ 1.03944835, 1.17382569, 0.59639809, -1.22366171], [ 0.19048231, -0.7933259 , -1.38329444, 0.18223567], [-0.45734393, -0.39311119, -0.04779778, -0.35328667], [-1.59276821, -0.34217558, -2.35146007, 0.01843605], [ 0.39814867, 1.22247993, -0.42465849, -0.59570442]])
그리고 그 안에 들어가는 내용은 values 명령으로 확인할 수 있습니다. 또, info() 명령을 사용하면 DataFrame의 개요를 알 수 있습니다.
df.info()
<class 'pandas.core.frame.DataFrame'> DatetimeIndex: 6 entries, 2013-01-01 to 2013-01-06 Freq: D Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 A 6 non-null float64 1 B 6 non-null float64 2 C 6 non-null float64 3 D 6 non-null float64 dtypes: float64(4) memory usage: 240.0 bytes
describe 명령어는 통계적 개요를 확인 할 수 있습니다. 개수와 평균 뿐만 안니라 min,max와 각 1/4 지점,표준편차까지 한번에 알 수 있습니다.
df.describe()
A | B | C | D | |
---|---|---|---|---|
count | 6.000000 | 6.000000 | 6.000000 | 6.000000 |
mean | -0.168109 | -0.140634 | -0.733322 | -0.501656 |
std | 0.916067 | 1.147508 | 1.037046 | 0.561777 |
min | -1.592768 | -1.711500 | -2.351460 | -1.223662 |
25% | -0.554303 | -0.693272 | -1.234751 | -0.927391 |
50% | -0.133431 | -0.367643 | -0.606890 | -0.474496 |
75% | 0.346232 | 0.794825 | -0.142013 | -0.074495 |
max | 1.039448 | 1.222480 | 0.596398 | 0.182236 |
이때 values가 숫자가 아니라 문자라고 하더라도 그에 맞는 개요가 나타납니다.
df.sort_values(by='B', ascending=False)
A | B | C | D | |
---|---|---|---|---|
2013-01-06 | 0.398149 | 1.222480 | -0.424658 | -0.595704 |
2013-01-02 | 1.039448 | 1.173826 | 0.596398 | -1.223662 |
2013-01-05 | -1.592768 | -0.342176 | -2.351460 | 0.018436 |
2013-01-04 | -0.457344 | -0.393111 | -0.047798 | -0.353287 |
2013-01-03 | 0.190482 | -0.793326 | -1.383294 | 0.182236 |
2013-01-01 | -0.586623 | -1.711500 | -0.789121 | -1.037953 |
sort_values 명령은 by로 지정된 칼럼을 기준으로 정렬합니다. ascending 옵션을 사용하면 내림차순이나 오름차순으로 정렬시킬 수 있습니다.
df
A | B | C | D | |
---|---|---|---|---|
2013-01-01 | -0.586623 | -1.711500 | -0.789121 | -1.037953 |
2013-01-02 | 1.039448 | 1.173826 | 0.596398 | -1.223662 |
2013-01-03 | 0.190482 | -0.793326 | -1.383294 | 0.182236 |
2013-01-04 | -0.457344 | -0.393111 | -0.047798 | -0.353287 |
2013-01-05 | -1.592768 | -0.342176 | -2.351460 | 0.018436 |
2013-01-06 | 0.398149 | 1.222480 | -0.424658 | -0.595704 |
그냥 변수명을 적고 실행하면 그 내용이 나타납니다.
df['A']
2013-01-01 -0.586623 2013-01-02 1.039448 2013-01-03 0.190482 2013-01-04 -0.457344 2013-01-05 -1.592768 2013-01-06 0.398149 Freq: D, Name: A, dtype: float64
DataFrame으로 선언된 변수의 경우 변수명 뒤에['A']와 같이 붙여주면 헤당 컬럼만 Series로 보여줍니다. 물론 head()라는 명령이 있지만 중간부터 보고 싶다면 행의 범위만 지정해서 df[0:3]이라고 하면 됩니다.
df[0:3]
A | B | C | D | |
---|---|---|---|---|
2013-01-01 | -0.586623 | -1.711500 | -0.789121 | -1.037953 |
2013-01-02 | 1.039448 | 1.173826 | 0.596398 | -1.223662 |
2013-01-03 | 0.190482 | -0.793326 | -1.383294 | 0.182236 |
만약 2013.01.02부터 2013.01.04까지의 행을 보고 싶다면
df['20130102':'20130104']
A | B | C | D | |
---|---|---|---|---|
2013-01-02 | 1.039448 | 1.173826 | 0.596398 | -1.223662 |
2013-01-03 | 0.190482 | -0.793326 | -1.383294 | 0.182236 |
2013-01-04 | -0.457344 | -0.393111 | -0.047798 | -0.353287 |
dates 변수를 이용해서 특정 날짜의 데이터만 보고 싶으면 df.loc 명령을 사용하면 됩니다.
df.loc[dates[0]]
A -0.586623 B -1.711500 C -0.789121 D -1.037953 Name: 2013-01-01 00:00:00, dtype: float64
A,B열의 모든 행을 보고 싶다면
df.loc[:,['A','B']]
A | B | |
---|---|---|
2013-01-01 | -0.586623 | -1.711500 |
2013-01-02 | 1.039448 | 1.173826 |
2013-01-03 | 0.190482 | -0.793326 |
2013-01-04 | -0.457344 | -0.393111 |
2013-01-05 | -1.592768 | -0.342176 |
2013-01-06 | 0.398149 | 1.222480 |
행과 열의 범위를 모두 지정할 수도 있습니다.
df.loc['20130102':'20130104',['A','B']]
A | B | |
---|---|---|
2013-01-02 | 1.039448 | 1.173826 |
2013-01-03 | 0.190482 | -0.793326 |
2013-01-04 | -0.457344 | -0.393111 |
2013년 1월 2일부터 2013년 1월 4일까지의 데이터중 A와 B 컬럼의 데이터만 선택하는것입니다.
df.loc['20130102',['A','B']]
A 1.039448 B 1.173826 Name: 2013-01-02 00:00:00, dtype: float64
또, 2013년 1월 2일의 A,B 컬럼의 내용을 확인하는 것입니다.
df.loc[dates[0],'A']
-0.5866233078896008
dates[0]에 맞는 날짜인 2013년 1월 1일에 A컴럼의 데이터만 확인할 수 있습니다.
df.iloc[3]
A -0.457344 B -0.393111 C -0.047798 D -0.353287 Name: 2013-01-04 00:00:00, dtype: float64
먼저 iloc에 단지 숫자만 표시하면 3번 행을 의미합니다. (0번부터 시작하니깐 4번 행이 됩니다.)
df.iloc[3:5,0:2]
A | B | |
---|---|---|
2013-01-04 | -0.457344 | -0.393111 |
2013-01-05 | -1.592768 | -0.342176 |
또 3번째부터 5번째 앞, 즉 3번째부터 4번째까지 행과 0번부터 1번까지 열의 데이터만 가져올 수 있습니다.
df.iloc[[1,2,4],[0,2]]
A | C | |
---|---|---|
2013-01-02 | 1.039448 | 0.596398 |
2013-01-03 | 0.190482 | -1.383294 |
2013-01-05 | -1.592768 | -2.351460 |
또 범위가 아니라 콤마로 행이나 열을 지정해서 테이터를 가져올 수 있습니다. 행이나 열에 대해 한쪽은 전체를 의미하게 하고 싶을 때는 그냥 콜론만 사용하면 됩니다.
df.iloc[1:3,:]
A | B | C | D | |
---|---|---|---|---|
2013-01-02 | 1.039448 | 1.173826 | 0.596398 | -1.223662 |
2013-01-03 | 0.190482 | -0.793326 | -1.383294 | 0.182236 |
이렇게 행은 범위를 주고, 열은 콜론으로 전체를 의미하게 하는 것입니다.
df.iloc[:,1:3]
B | C | |
---|---|---|
2013-01-01 | -1.711500 | -0.789121 |
2013-01-02 | 1.173826 | 0.596398 |
2013-01-03 | -0.793326 | -1.383294 |
2013-01-04 | -0.393111 | -0.047798 |
2013-01-05 | -0.342176 | -2.351460 |
2013-01-06 | 1.222480 | -0.424658 |
반대로 행을 전체로 선택했습니다. 다시 df를 호출하겠습니다.
df
A | B | C | D | |
---|---|---|---|---|
2013-01-01 | -0.586623 | -1.711500 | -0.789121 | -1.037953 |
2013-01-02 | 1.039448 | 1.173826 | 0.596398 | -1.223662 |
2013-01-03 | 0.190482 | -0.793326 | -1.383294 | 0.182236 |
2013-01-04 | -0.457344 | -0.393111 | -0.047798 | -0.353287 |
2013-01-05 | -1.592768 | -0.342176 | -2.351460 | 0.018436 |
2013-01-06 | 0.398149 | 1.222480 | -0.424658 | -0.595704 |
DataFrame에서 특정 조건을 만족하는 데이터만 얻을 수 있습니다. 컬럼을 지정할 때 df['A']처럼 할 수도 있고, df.A와 같이 할 수도 있습니다. 컬럼 A에서 0보다 큰 행만 얻는 것입니다.
df[df.A > 0]
A | B | C | D | |
---|---|---|---|---|
2013-01-02 | 1.039448 | 1.173826 | 0.596398 | -1.223662 |
2013-01-03 | 0.190482 | -0.793326 | -1.383294 | 0.182236 |
2013-01-06 | 0.398149 | 1.222480 | -0.424658 | -0.595704 |
데이터 전체에서 조건을 걸면 만족하지 않은 곳은 NaN 처리가 됩니다.
df[df > 0]
A | B | C | D | |
---|---|---|---|---|
2013-01-01 | NaN | NaN | NaN | NaN |
2013-01-02 | 1.039448 | 1.173826 | 0.596398 | NaN |
2013-01-03 | 0.190482 | NaN | NaN | 0.182236 |
2013-01-04 | NaN | NaN | NaN | NaN |
2013-01-05 | NaN | NaN | NaN | 0.018436 |
2013-01-06 | 0.398149 | 1.222480 | NaN | NaN |
DataFrame을 복사할 때는 그냥 = 기호를 이용해서 복사해면 실제 데이터의 내용이 복사되는 것이 아니라 데이터 위치만 복사되기 때문에 원본 데이터는 하나만 있게 됩니다.
df2 = df.copy()
데이터의 내용까지 복사하라는 지령은 copy() 옵션을 붙여 넣으면 됩니다.
df2['E'] = ['one', 'one','two','three','four','three']
df2
A | B | C | D | E | |
---|---|---|---|---|---|
2013-01-01 | -0.586623 | -1.711500 | -0.789121 | -1.037953 | one |
2013-01-02 | 1.039448 | 1.173826 | 0.596398 | -1.223662 | one |
2013-01-03 | 0.190482 | -0.793326 | -1.383294 | 0.182236 | two |
2013-01-04 | -0.457344 | -0.393111 | -0.047798 | -0.353287 | three |
2013-01-05 | -1.592768 | -0.342176 | -2.351460 | 0.018436 | four |
2013-01-06 | 0.398149 | 1.222480 | -0.424658 | -0.595704 | three |
df2['E'].isin(['two','four'])
2013-01-01 False 2013-01-02 False 2013-01-03 True 2013-01-04 False 2013-01-05 True 2013-01-06 False Freq: D, Name: E, dtype: bool
이렇게 만든 E 컬럼에서 two와 four가 있는지 조건을 걸고 싶을 때는 isin 명령을 쓰면 좋습니다.
df2[df2['E'].isin(['two','four'])]
A | B | C | D | E | |
---|---|---|---|---|---|
2013-01-03 | 0.190482 | -0.793326 | -1.383294 | 0.182236 | two |
2013-01-05 | -1.592768 | -0.342176 | -2.351460 | 0.018436 | four |
그 결과는 True/False로 반환되는데, 이것을 이용하면 조건에 맞는 데이터를 선택할 수 있습니다.
df
A | B | C | D | |
---|---|---|---|---|
2013-01-01 | -0.586623 | -1.711500 | -0.789121 | -1.037953 |
2013-01-02 | 1.039448 | 1.173826 | 0.596398 | -1.223662 |
2013-01-03 | 0.190482 | -0.793326 | -1.383294 | 0.182236 |
2013-01-04 | -0.457344 | -0.393111 | -0.047798 | -0.353287 |
2013-01-05 | -1.592768 | -0.342176 | -2.351460 | 0.018436 |
2013-01-06 | 0.398149 | 1.222480 | -0.424658 | -0.595704 |
df변수에서 좀 더 통계 느낌의 데이터를 볼 때는 특정 함수를 적용시키면 좋습니다. 이때 사용하는 것이 apply 명렁입니다. 누적합을 알고 싶을 때는 numpy의 cumsum을 이용하면 됩니다.
df.apply(np.cumsum)
A | B | C | D | |
---|---|---|---|---|
2013-01-01 | -0.586623 | -1.711500 | -0.789121 | -1.037953 |
2013-01-02 | 0.452825 | -0.537674 | -0.192723 | -2.261614 |
2013-01-03 | 0.643307 | -1.331000 | -1.576018 | -2.079379 |
2013-01-04 | 0.185963 | -1.724111 | -1.623815 | -2.432665 |
2013-01-05 | -1.406805 | -2.066287 | -3.975276 | -2.414229 |
2013-01-06 | -1.008656 | -0.843807 | -4.399934 | -3.009934 |
특별히 최대값과 최소값의 차이를 알고 싶다면 one-line 함수인 lambda를 이용할 수도 있습니다.
df.apply(lambda x: x.max() - x.min())
A 2.632217 B 2.933980 C 2.947858 D 1.405897 dtype: float64
CCTV_Seoul.head()
구별 | 소계 | 2013년도 이전 | 2014년 | 2015년 | 2016년 | |
---|---|---|---|---|---|---|
0 | 강남구 | 2780 | 1292 | 430 | 584 | 932 |
1 | 강동구 | 773 | 379 | 99 | 155 | 377 |
2 | 강북구 | 748 | 369 | 120 | 138 | 204 |
3 | 강서구 | 884 | 388 | 258 | 184 | 81 |
4 | 관악구 | 1496 | 846 | 260 | 390 | 613 |
구별 CCTV 데이터에서 CCTV 전체 개수인 소계로 정렬하겠습니다.
CCTV_Seoul.sort_values(by='소계', ascending=True).head(5)
구별 | 소계 | 2013년도 이전 | 2014년 | 2015년 | 2016년 | |
---|---|---|---|---|---|---|
9 | 도봉구 | 485 | 238 | 159 | 42 | 386 |
12 | 마포구 | 574 | 314 | 118 | 169 | 379 |
17 | 송파구 | 618 | 529 | 21 | 68 | 463 |
24 | 중랑구 | 660 | 509 | 121 | 177 | 109 |
23 | 중구 | 671 | 413 | 190 | 72 | 348 |
CCTV의 전체 개수가 가장 작은 구는 '도봉구','마포구','송파구','중랑구','중구'라는 것을 알 수 있습니다. 의아스러운 것은 강남 3구 중 하나인 송파구가 CCTV가 가장 적은 구 중 하나라는 것인데, 2장에서 보면 송파구는 범죄율이 결코 낮은 구가 아닙니다. 그런데 CCTV가 적은 구 중에 하나입니다.
CCTV_Seoul.sort_values(by='소계', ascending=False).head(5)
구별 | 소계 | 2013년도 이전 | 2014년 | 2015년 | 2016년 | |
---|---|---|---|---|---|---|
0 | 강남구 | 2780 | 1292 | 430 | 584 | 932 |
18 | 양천구 | 2034 | 1843 | 142 | 30 | 467 |
14 | 서초구 | 1930 | 1406 | 157 | 336 | 398 |
21 | 은평구 | 1873 | 1138 | 224 | 278 | 468 |
20 | 용산구 | 1624 | 1368 | 218 | 112 | 398 |
그리고 CCTV가 가장 구는 표에있는 5개 구로 나타납니다. 특히 2014년분ㅌ터 2016년까지 최근 3년간 CCTV 수를 더하고 2013년 이전 CCTV수로 나눠서 최근 3년간 CCTV 증가율을 계산하겠습니다.
CCTV_Seoul['최근증가율'] = (CCTV_Seoul['2016년'] + CCTV_Seoul['2015년'] + \
CCTV_Seoul['2014년']) / CCTV_Seoul['2013년도 이전'] * 100
CCTV_Seoul.sort_values(by='최근증가율', ascending=False).head(5)
구별 | 소계 | 2013년도 이전 | 2014년 | 2015년 | 2016년 | 최근증가율 | |
---|---|---|---|---|---|---|---|
22 | 종로구 | 1002 | 464 | 314 | 211 | 630 | 248.922414 |
9 | 도봉구 | 485 | 238 | 159 | 42 | 386 | 246.638655 |
12 | 마포구 | 574 | 314 | 118 | 169 | 379 | 212.101911 |
8 | 노원구 | 1265 | 542 | 57 | 451 | 516 | 188.929889 |
1 | 강동구 | 773 | 379 | 99 | 155 | 377 | 166.490765 |
그 결과를 보면 최근 3년간 CCTV가 그 이전 대비 많이 증가한 구는 '종로구','도봉구','마포구','노원구','강동구'라는 것도 알 수 있습니다. 이제 서울시 인구 현황을 정리해보겠습니다,
pop_Seoul.head()
구별 | 인구수 | 한국인 | 외국인 | 고령자 | 여자 | 계.1 | 남자.1 | 여자.1 | 계.2 | 남자.2 | 여자.2 | 세대당인구 | 65세이상고령자 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2017.1/4 | 합계 | 4,202,888 | 10,197,604 | 5,000,005 | 5,197,599 | 9,926,968 | 4,871,560 | 5,055,408 | 270,636 | 128,445 | 142,191 | 2.36 | 1,321,458 |
1 | 2017.1/4 | 종로구 | 72,654 | 162,820 | 79,675 | 83,145 | 153,589 | 75,611 | 77,978 | 9,231 | 4,064 | 5,167 | 2.11 | 25,425 |
2 | 2017.1/4 | 중구 | 59,481 | 133,240 | 65,790 | 67,450 | 124,312 | 61,656 | 62,656 | 8,928 | 4,134 | 4,794 | 2.09 | 20,764 |
3 | 2017.1/4 | 용산구 | 106,544 | 244,203 | 119,132 | 125,071 | 229,456 | 111,167 | 118,289 | 14,747 | 7,965 | 6,782 | 2.15 | 36,231 |
4 | 2017.1/4 | 성동구 | 130,868 | 311,244 | 153,768 | 157,476 | 303,380 | 150,076 | 153,304 | 7,864 | 3,692 | 4,172 | 2.32 | 39,997 |
0번 행에 합계가 보입니다. 아마 서울시 전체 합계를 넣어둔 것같은데 우리에게는 필요가 없습니다. 이럴 때는 행을 지우는 drop 명령을 사용해서 지우도록 합니다.
pop_Seoul.drop([0], inplace=True)
pop_Seoul.head()
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-141-c96cb5eed5a4> in <module> ----> 1 pop_Seoul.drop([0], inplace=True) 2 pop_Seoul.head() ~/.local/lib/python3.6/site-packages/pandas/core/frame.py in drop(self, labels, axis, index, columns, level, inplace, errors) 4172 level=level, 4173 inplace=inplace, -> 4174 errors=errors, 4175 ) 4176 ~/.local/lib/python3.6/site-packages/pandas/core/generic.py in drop(self, labels, axis, index, columns, level, inplace, errors) 3887 for axis, labels in axes.items(): 3888 if labels is not None: -> 3889 obj = obj._drop_axis(labels, axis, level=level, errors=errors) 3890 3891 if inplace: ~/.local/lib/python3.6/site-packages/pandas/core/generic.py in _drop_axis(self, labels, axis, level, errors) 3921 new_axis = axis.drop(labels, level=level, errors=errors) 3922 else: -> 3923 new_axis = axis.drop(labels, errors=errors) 3924 result = self.reindex(**{axis_name: new_axis}) 3925 ~/.local/lib/python3.6/site-packages/pandas/core/indexes/base.py in drop(self, labels, errors) 5285 if mask.any(): 5286 if errors != "ignore": -> 5287 raise KeyError(f"{labels[mask]} not found in axis") 5288 indexer = indexer[~mask] 5289 return self.delete(indexer) KeyError: '[0] not found in axis'
유니크 조사는 반복된 데이터는 하나로 나타내서 한번 이상 나타난 데이터를 확인하는 것입니다.
pop_Seoul['구별'].unique()
array(['2017.1/4'], dtype=object)
이렇게 유니크 조사를 했더니 제일 끝에 NaN이 보입니다. NaN이 어디에 있는지 확인하기 위해서는 조건문을 사용해야 합니다. isnull 명령으로 NaN 데이터를 추출할 수 있습니다.
pop_Seoul[pop_Seoul['구별'].isnull()]
구별 | 인구수 | 한국인 | 외국인 | 고령자 | 여자 | 계.1 | 남자.1 | 여자.1 | 계.2 | 남자.2 | 여자.2 | 세대당인구 | 65세이상고령자 |
---|
행을 지우는 drop 명령으로 NaN이 있던 행을 삭제합니다.
pop_Seoul.drop([26], inplace=True)
pop_Seoul.head()
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-144-5ebe6c57e8a6> in <module> ----> 1 pop_Seoul.drop([26], inplace=True) 2 pop_Seoul.head() ~/.local/lib/python3.6/site-packages/pandas/core/frame.py in drop(self, labels, axis, index, columns, level, inplace, errors) 4172 level=level, 4173 inplace=inplace, -> 4174 errors=errors, 4175 ) 4176 ~/.local/lib/python3.6/site-packages/pandas/core/generic.py in drop(self, labels, axis, index, columns, level, inplace, errors) 3887 for axis, labels in axes.items(): 3888 if labels is not None: -> 3889 obj = obj._drop_axis(labels, axis, level=level, errors=errors) 3890 3891 if inplace: ~/.local/lib/python3.6/site-packages/pandas/core/generic.py in _drop_axis(self, labels, axis, level, errors) 3921 new_axis = axis.drop(labels, level=level, errors=errors) 3922 else: -> 3923 new_axis = axis.drop(labels, errors=errors) 3924 result = self.reindex(**{axis_name: new_axis}) 3925 ~/.local/lib/python3.6/site-packages/pandas/core/indexes/base.py in drop(self, labels, errors) 5285 if mask.any(): 5286 if errors != "ignore": -> 5287 raise KeyError(f"{labels[mask]} not found in axis") 5288 indexer = indexer[~mask] 5289 return self.delete(indexer) KeyError: '[26] not found in axis'
이제 각 구별 전체 인구를 이용해서 구별 '외국인비율'과 '고지령자비율'을 계산하겠습니다.
pop_Seoul['외국인비율'] = pop_Seoul['외국인'] / pop_Seoul['인구수'] * 100
pop_Seoul['고령자비율'] = pop_Seoul['고령자'] / pop_Seoul['인구수'] * 100
pop_Seoul.head()
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) ~/.local/lib/python3.6/site-packages/pandas/core/ops/array_ops.py in na_arithmetic_op(left, right, op, is_cmp) 142 try: --> 143 result = expressions.evaluate(op, left, right) 144 except TypeError: ~/.local/lib/python3.6/site-packages/pandas/core/computation/expressions.py in evaluate(op, a, b, use_numexpr) 232 if use_numexpr: --> 233 return _evaluate(op, op_str, a, b) # type: ignore 234 return _evaluate_standard(op, op_str, a, b) ~/.local/lib/python3.6/site-packages/pandas/core/computation/expressions.py in _evaluate_numexpr(op, op_str, a, b) 118 if result is None: --> 119 result = _evaluate_standard(op, op_str, a, b) 120 ~/.local/lib/python3.6/site-packages/pandas/core/computation/expressions.py in _evaluate_standard(op, op_str, a, b) 67 with np.errstate(all="ignore"): ---> 68 return op(a, b) 69 TypeError: unsupported operand type(s) for /: 'str' and 'str' During handling of the above exception, another exception occurred: TypeError Traceback (most recent call last) <ipython-input-145-796219eb36a2> in <module> ----> 1 pop_Seoul['외국인비율'] = pop_Seoul['외국인'] / pop_Seoul['인구수'] * 100 2 pop_Seoul['고령자비율'] = pop_Seoul['고령자'] / pop_Seoul['인구수'] * 100 3 pop_Seoul.head() ~/.local/lib/python3.6/site-packages/pandas/core/ops/common.py in new_method(self, other) 63 other = item_from_zerodim(other) 64 ---> 65 return method(self, other) 66 67 return new_method ~/.local/lib/python3.6/site-packages/pandas/core/ops/__init__.py in wrapper(left, right) 341 lvalues = extract_array(left, extract_numpy=True) 342 rvalues = extract_array(right, extract_numpy=True) --> 343 result = arithmetic_op(lvalues, rvalues, op) 344 345 return left._construct_result(result, name=res_name) ~/.local/lib/python3.6/site-packages/pandas/core/ops/array_ops.py in arithmetic_op(left, right, op) 188 else: 189 with np.errstate(all="ignore"): --> 190 res_values = na_arithmetic_op(lvalues, rvalues, op) 191 192 return res_values ~/.local/lib/python3.6/site-packages/pandas/core/ops/array_ops.py in na_arithmetic_op(left, right, op, is_cmp) 148 # will handle complex numbers incorrectly, see GH#32047 149 raise --> 150 result = masked_arith_op(left, right, op) 151 152 if is_cmp and (is_scalar(result) or result is NotImplemented): ~/.local/lib/python3.6/site-packages/pandas/core/ops/array_ops.py in masked_arith_op(x, y, op) 90 if mask.any(): 91 with np.errstate(all="ignore"): ---> 92 result[mask] = op(xrav[mask], yrav[mask]) 93 94 else: TypeError: unsupported operand type(s) for /: 'str' and 'str'
인구수로 정렬했더니 '송파구','강서구','강남구','노원구','관악구'순으로 인구가 많습니다.
pop_Seoul.sort_values(by='인구수', ascending=False).head(5)
구별 | 인구수 | 한국인 | 외국인 | 고령자 | 여자 | 계.1 | 남자.1 | 여자.1 | 계.2 | 남자.2 | 여자.2 | 세대당인구 | 65세이상고령자 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
7 | 2017.1/4 | 중랑구 | 177548 | 414503 | 206102 | 208401 | 409882 | 204265 | 205617 | 4621 | 1837 | 2784 | 2.31 | 56774 |
2 | 2017.1/4 | 중구 | 59,481 | 133,240 | 65,790 | 67,450 | 124,312 | 61,656 | 62,656 | 8,928 | 4,134 | 4,794 | 2.09 | 20,764 |
1 | 2017.1/4 | 종로구 | 72,654 | 162,820 | 79,675 | 83,145 | 153,589 | 75,611 | 77,978 | 9,231 | 4,064 | 5,167 | 2.11 | 25,425 |
12 | 2017.1/4 | 은평구 | 201869 | 494388 | 240220 | 254168 | 489943 | 238337 | 251606 | 4445 | 1883 | 2562 | 2.43 | 72334 |
3 | 2017.1/4 | 용산구 | 106,544 | 244,203 | 119,132 | 125,071 | 229,456 | 111,167 | 118,289 | 14,747 | 7,965 | 6,782 | 2.15 | 36,231 |
pop_Seoul.sort_values(by='외국인', ascending=False).head(5)
구별 | 인구수 | 한국인 | 외국인 | 고령자 | 여자 | 계.1 | 남자.1 | 여자.1 | 계.2 | 남자.2 | 여자.2 | 세대당인구 | 65세이상고령자 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
24 | 2017.1/4 | 송파구 | 259883 | 667483 | 325040 | 342443 | 660584 | 321676 | 338908 | 6899 | 3364 | 3535 | 2.54 | 72506 |
16 | 2017.1/4 | 강서구 | 247696 | 603772 | 294433 | 309339 | 597248 | 291249 | 305999 | 6524 | 3184 | 3340 | 2.41 | 72548 |
23 | 2017.1/4 | 강남구 | 234107 | 570500 | 273301 | 297199 | 565550 | 270726 | 294824 | 4950 | 2575 | 2375 | 2.42 | 63167 |
11 | 2017.1/4 | 노원구 | 219957 | 569384 | 276823 | 292561 | 565565 | 275211 | 290354 | 3819 | 1612 | 2207 | 2.57 | 71941 |
21 | 2017.1/4 | 관악구 | 253826 | 525515 | 264763 | 260752 | 507203 | 256090 | 251113 | 18312 | 8673 | 9639 | 2.00 | 68082 |
pop_Seoul.sort_values(by='외국인비율', ascending=False).head(5)
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-149-1b19b934223c> in <module> ----> 1 pop_Seoul.sort_values(by='외국인비율', ascending=False).head(5) ~/.local/lib/python3.6/site-packages/pandas/core/frame.py in sort_values(self, by, axis, ascending, inplace, kind, na_position, ignore_index, key) 5296 5297 by = by[0] -> 5298 k = self._get_label_or_level_values(by, axis=axis) 5299 5300 # need to rewrap column in Series to apply key function ~/.local/lib/python3.6/site-packages/pandas/core/generic.py in _get_label_or_level_values(self, key, axis) 1561 values = self.axes[axis].get_level_values(key)._values 1562 else: -> 1563 raise KeyError(key) 1564 1565 # Check for duplicates KeyError: '외국인비율'
외국인 숫자가 많은 구는 '영등포','구로구','금천구','관악구','동대문구'입니다만, 외국인 비율이 높은 구는 '영등포','금천구','구로구','중구','용산구'로 조금 바뀌는 것을 알 수 있습니다.
pop_Seoul.sort_values(by='고령자', ascending=False).head(5)
구별 | 인구수 | 한국인 | 외국인 | 고령자 | 여자 | 계.1 | 남자.1 | 여자.1 | 계.2 | 남자.2 | 여자.2 | 세대당인구 | 65세이상고령자 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 2017.1/4 | 종로구 | 72,654 | 162,820 | 79,675 | 83,145 | 153,589 | 75,611 | 77,978 | 9,231 | 4,064 | 5,167 | 2.11 | 25,425 |
2 | 2017.1/4 | 중구 | 59,481 | 133,240 | 65,790 | 67,450 | 124,312 | 61,656 | 62,656 | 8,928 | 4,134 | 4,794 | 2.09 | 20,764 |
24 | 2017.1/4 | 송파구 | 259883 | 667483 | 325040 | 342443 | 660584 | 321676 | 338908 | 6899 | 3364 | 3535 | 2.54 | 72506 |
16 | 2017.1/4 | 강서구 | 247696 | 603772 | 294433 | 309339 | 597248 | 291249 | 305999 | 6524 | 3184 | 3340 | 2.41 | 72548 |
11 | 2017.1/4 | 노원구 | 219957 | 569384 | 276823 | 292561 | 565565 | 275211 | 290354 | 3819 | 1612 | 2207 | 2.57 | 71941 |
pop_Seoul.sort_values(by='고령자비율', ascending=False).head(5)
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-151-fb1c1e23d068> in <module> ----> 1 pop_Seoul.sort_values(by='고령자비율', ascending=False).head(5) ~/.local/lib/python3.6/site-packages/pandas/core/frame.py in sort_values(self, by, axis, ascending, inplace, kind, na_position, ignore_index, key) 5296 5297 by = by[0] -> 5298 k = self._get_label_or_level_values(by, axis=axis) 5299 5300 # need to rewrap column in Series to apply key function ~/.local/lib/python3.6/site-packages/pandas/core/generic.py in _get_label_or_level_values(self, key, axis) 1561 values = self.axes[axis].get_level_values(key)._values 1562 else: -> 1563 raise KeyError(key) 1564 1565 # Check for duplicates KeyError: '고령자비율'
이제 고령자와 고령자 비율을 조사해보겠습니다. 우리는 CCTV의 현황을 완전히 파악한 것 같지는 않습니다. 인구 대비 CCTV 현황 같은 내용을 확인하고 싶은데, 그러기 위해서는 두 데이터를 병합해야 합니다.
진도를 나가기 전에 pandas의 고급 기능 중 하나를 알고 가면 좋을 것 같습니다.
df1 = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3'],
'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']},
index=[0, 1, 2, 3])
df2 = pd.DataFrame({'A': ['A4', 'A5', 'A6', 'A7'],
'B': ['B4', 'B5', 'B6', 'B7'],
'C': ['C4', 'C5', 'C6', 'C7'],
'D': ['D4', 'D5', 'D6', 'D7']},
index=[4, 5, 6, 7])
df3 = pd.DataFrame({'A': ['A8', 'A9', 'A10', 'A11'],
'B': ['B8', 'B9', 'B10', 'B11'],
'C': ['C8', 'C9', 'C10', 'C11'],
'D': ['D8', 'D9', 'D10', 'D11']},
index=[8, 9, 10, 11])
연습용 데이터를 3개 만들겠습니다.
df1
A | B | C | D | |
---|---|---|---|---|
0 | A0 | B0 | C0 | D0 |
1 | A1 | B1 | C1 | D1 |
2 | A2 | B2 | C2 | D2 |
3 | A3 | B3 | C3 | D3 |
df2
A | B | C | D | |
---|---|---|---|---|
4 | A4 | B4 | C4 | D4 |
5 | A5 | B5 | C5 | D5 |
6 | A6 | B6 | C6 | D6 |
7 | A7 | B7 | C7 | D7 |
df3
A | B | C | D | |
---|---|---|---|---|
8 | A8 | B8 | C8 | D8 |
9 | A9 | B9 | C9 | D9 |
10 | A10 | B10 | C10 | D10 |
11 | A11 | B11 | C11 | D11 |
이렇게 만든 데이터를 열 방향으로 단순히 합치는 것은 concat 명령입니다.
result = pd.concat([df1, df2, df3])
result
A | B | C | D | |
---|---|---|---|---|
0 | A0 | B0 | C0 | D0 |
1 | A1 | B1 | C1 | D1 |
2 | A2 | B2 | C2 | D2 |
3 | A3 | B3 | C3 | D3 |
4 | A4 | B4 | C4 | D4 |
5 | A5 | B5 | C5 | D5 |
6 | A6 | B6 | C6 | D6 |
7 | A7 | B7 | C7 | D7 |
8 | A8 | B8 | C8 | D8 |
9 | A9 | B9 | C9 | D9 |
10 | A10 | B10 | C10 | D10 |
11 | A11 | B11 | C11 | D11 |
단순하게 아무 옵션 없이 그냥 사용하면 열 방향으로 병합합니다.
result = pd.concat([df1, df2, df3], keys=['x', 'y', 'z'])
result
A | B | C | D | ||
---|---|---|---|---|---|
x | 0 | A0 | B0 | C0 | D0 |
1 | A1 | B1 | C1 | D1 | |
2 | A2 | B2 | C2 | D2 | |
3 | A3 | B3 | C3 | D3 | |
y | 4 | A4 | B4 | C4 | D4 |
5 | A5 | B5 | C5 | D5 | |
6 | A6 | B6 | C6 | D6 | |
7 | A7 | B7 | C7 | D7 | |
z | 8 | A8 | B8 | C8 | D8 |
9 | A9 | B9 | C9 | D9 | |
10 | A10 | B10 | C10 | D10 | |
11 | A11 | B11 | C11 | D11 |
특별히 concat 명령에 keys 옵션으로 구분할 수 있습니다. 이렇게 key 지정된 구분은 다중 index가 되어서 level을 형성합니다. 이를 확인하면,
result.index
MultiIndex([('x', 0), ('x', 1), ('x', 2), ('x', 3), ('y', 4), ('y', 5), ('y', 6), ('y', 7), ('z', 8), ('z', 9), ('z', 10), ('z', 11)], )
result.index.get_level_values(0)
Index(['x', 'x', 'x', 'x', 'y', 'y', 'y', 'y', 'z', 'z', 'z', 'z'], dtype='object')
result.index.get_level_values(1)
Int64Index([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], dtype='int64')
입니다.
df4 = pd.DataFrame({'B': ['B2', 'B3', 'B6', 'B7'],
'D': ['D2', 'D3', 'D6', 'D7'],
'F': ['F2', 'F3', 'F6', 'F7']},
index=[2, 3, 6, 7])
result = pd.concat([df1, df4], axis=1)
이렇게 만든 df4와 df1을,
df1
A | B | C | D | |
---|---|---|---|---|
0 | A0 | B0 | C0 | D0 |
1 | A1 | B1 | C1 | D1 |
2 | A2 | B2 | C2 | D2 |
3 | A3 | B3 | C3 | D3 |
df4
B | D | F | |
---|---|---|---|
2 | B2 | D2 | F2 |
3 | B3 | D3 | F3 |
6 | B6 | D6 | F6 |
7 | B7 | D7 | F7 |
옵션인 axis=1을 주어서 concat 시켜보면,
result
A | B | C | D | B | D | F | |
---|---|---|---|---|---|---|---|
0 | A0 | B0 | C0 | D0 | NaN | NaN | NaN |
1 | A1 | B1 | C1 | D1 | NaN | NaN | NaN |
2 | A2 | B2 | C2 | D2 | B2 | D2 | F2 |
3 | A3 | B3 | C3 | D3 | B3 | D3 | F3 |
6 | NaN | NaN | NaN | NaN | B6 | D6 | F6 |
7 | NaN | NaN | NaN | NaN | B7 | D7 | F7 |
이런 결과를 얻게 됩니다. df1의 index가 0,1,2,3이고, df4의 index가 2,3,6,7인데 concat 명령은 index를 기준으로 데이터를 합치기 때문입니다. 그래서 값을 가질 수 없는 곳에는 NaN이 저장됩니다. 이때 공통된 index로 합치고 공통되지 않은 index의 데이터는 버리도록 하는 옵션이 join='inneer'옵션입니다.
result = pd.concat([df1, df4], axis=1, join='inner')
result
A | B | C | D | B | D | F | |
---|---|---|---|---|---|---|---|
2 | A2 | B2 | C2 | D2 | B2 | D2 | F2 |
3 | A3 | B3 | C3 | D3 | B3 | D3 | F3 |
또 join_axes=[df1.index] 옵션으로 df1의 인덱스를 맞추도록 할 수도 있습니다.
result = pd.concat([df1, df4], axis=1, join_axes=[df1.index])
result
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-167-fefe7d75ae95> in <module> ----> 1 result = pd.concat([df1, df4], axis=1, join_axes=[df1.index]) 2 result TypeError: concat() got an unexpected keyword argument 'join_axes'
concat 명령을 사용하는데 열 방향으로 합치면서 ignore_index=True라고 옵션을 잡으면 두 데이터의 index를 무시하고 합친 후 다시 index를 부여합니다. 이때는 열을 기준으로 합치게 됩니다.
result = pd.concat([df1, df4], ignore_index=True)
result
A | B | C | D | F | |
---|---|---|---|---|---|
0 | A0 | B0 | C0 | D0 | NaN |
1 | A1 | B1 | C1 | D1 | NaN |
2 | A2 | B2 | C2 | D2 | NaN |
3 | A3 | B3 | C3 | D3 | NaN |
4 | NaN | B2 | NaN | D2 | F2 |
5 | NaN | B3 | NaN | D3 | F3 |
6 | NaN | B6 | NaN | D6 | F6 |
7 | NaN | B7 | NaN | D7 | F7 |
이제 다른 명령 하나 더 학습하겠습니다. 일단 데이터 두 개 만듭니다,
left = pd.DataFrame({'key': ['K0', 'K4', 'K2', 'K3'],
'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3']})
right = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'],
'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']})
left
key | A | B | |
---|---|---|---|
0 | K0 | A0 | B0 |
1 | K4 | A1 | B1 |
2 | K2 | A2 | B2 |
3 | K3 | A3 | B3 |
right
key | C | D | |
---|---|---|---|
0 | K0 | C0 | D0 |
1 | K1 | C1 | D1 |
2 | K2 | C2 | D2 |
3 | K3 | C3 | D3 |
이렇게 만든 두 데이터에 공통으로 있는 컬럼인 key를 기준으로 merge 명령에서 merge 기준을 설정하는 on옵션으로 합치면 공통된 key에 대해서만 합치게 됩니다.
pd.merge(left,right,on='key')
key | A | B | C | D | |
---|---|---|---|---|---|
0 | K0 | A0 | B0 | C0 | D0 |
1 | K2 | A2 | B2 | C2 | D2 |
2 | K3 | A3 | B3 | C3 | D3 |
합치는 두 데이터를 하나씩 기준으로 합칠 수도 있습니다. 이때 how 옵션으로 한쪽 데이터를 설정하면 됩니다.
pd.merge(left,right, how='left',on='key')
key | A | B | C | D | |
---|---|---|---|---|---|
0 | K0 | A0 | B0 | C0 | D0 |
1 | K4 | A1 | B1 | NaN | NaN |
2 | K2 | A2 | B2 | C2 | D2 |
3 | K3 | A3 | B3 | C3 | D3 |
pd.merge(left,right,how='right',on='key')
key | A | B | C | D | |
---|---|---|---|---|---|
0 | K0 | A0 | B0 | C0 | D0 |
1 | K1 | NaN | NaN | C1 | D1 |
2 | K2 | A2 | B2 | C2 | D2 |
3 | K3 | A3 | B3 | C3 | D3 |
또 merge한 데이터 결과를 모두 가지는 outer 옵션이 있습니다. 이 옵션을 사용하면 마치 합집합처럼 merge가 되며 공통된 요소가 아닌 곳은 NaN 처리가 됩니다.
pd.merge(left,right, how='outer',on='key')
key | A | B | C | D | |
---|---|---|---|---|---|
0 | K0 | A0 | B0 | C0 | D0 |
1 | K4 | A1 | B1 | NaN | NaN |
2 | K2 | A2 | B2 | C2 | D2 |
3 | K3 | A3 | B3 | C3 | D3 |
4 | K1 | NaN | NaN | C1 | D1 |
그리고 outer의 반대로 inner 옵션이 있습니다. 이옵션은 교집합 처럼 공통된 요소만 가집니다.
pd.merge(left,right,how='inner',on='key')
key | A | B | C | D | |
---|---|---|---|---|---|
0 | K0 | A0 | B0 | C0 | D0 |
1 | K2 | A2 | B2 | C2 | D2 |
2 | K3 | A3 | B3 | C3 | D3 |
1 - 5절에서 배운 대로 merge 명령으로 합치도록 하겠습니다. 당연히 두 데이터의 공통된 컬럼인 '구별'로 merge하면 됩니다.
data_result = pd.merge(CCTV_Seoul, pop_Seoul, on='구별')
data_result.head()
구별 | 소계 | 2013년도 이전 | 2014년 | 2015년 | 2016년 | 최근증가율 | 인구수 | 한국인 | 외국인 | 고령자 | 여자 | 계.1 | 남자.1 | 여자.1 | 계.2 | 남자.2 | 여자.2 | 세대당인구 | 65세이상고령자 |
---|
그리고 이제부터 의미 없는 컬럼을 지우도록 하겠습니다. 행 방향으로 삭제하는 것은 drop이고, 열을 삭제하는 명령은 del입니다.
del data_result['2013년도 이전']
del data_result['2014년']
del data_result['2015년']
del data_result['2016년']
data_result.head()
구별 | 소계 | 최근증가율 | 인구수 | 한국인 | 외국인 | 고령자 | 여자 | 계.1 | 남자.1 | 여자.1 | 계.2 | 남자.2 | 여자.2 | 세대당인구 | 65세이상고령자 |
---|
나중에 그래프 그릴 것을 생각하면 index는 구 이름이 되면 여러모로 유리합니다. 그렇게 설정하는 명령이 set_index입니다.
data_result.set_index('구별', inplace=True)
data_result.head()
소계 | 최근증가율 | 인구수 | 한국인 | 외국인 | 고령자 | 여자 | 계.1 | 남자.1 | 여자.1 | 계.2 | 남자.2 | 여자.2 | 세대당인구 | 65세이상고령자 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
구별 |
상관계수를 조사해보겠습니다. 상관계수의 절대값이 클수록 두 데이터는 관계가 있다고 볼 수 있습니다. 상관계수의 절대값이 0.1 이하면 거의 무시, 0.3 이하면 약한 상관관계, 0.7 이하면 뚜렷한 상관관계라고 합니다. 그럼 다수의 데이터 중 상관계수가 가장 큰 값인 데이터를 비교하겠습니다. 먼저 상관계수를 어떻게 계산 하는지 알아보겠습니다. numpy에 있는 corrcoef 명령입니다. 단, 이 명령의 결과는 행렬로 나타납니다. 주 대각선을 기준으로 대칭인 행령이고 대각선 빼고 다른 값을 읽으면 됩니다.
np.corrcoef(data_result['고령자비율'],data_result['소계'])
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) ~/.local/lib/python3.6/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance) 2897 try: -> 2898 return self._engine.get_loc(casted_key) 2899 except KeyError as err: pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc() pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc() pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item() pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item() KeyError: '고령자비율' The above exception was the direct cause of the following exception: KeyError Traceback (most recent call last) <ipython-input-180-afc5f2856929> in <module> ----> 1 np.corrcoef(data_result['고령자비율'],data_result['소계']) ~/.local/lib/python3.6/site-packages/pandas/core/frame.py in __getitem__(self, key) 2904 if self.columns.nlevels > 1: 2905 return self._getitem_multilevel(key) -> 2906 indexer = self.columns.get_loc(key) 2907 if is_integer(indexer): 2908 indexer = [indexer] ~/.local/lib/python3.6/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance) 2898 return self._engine.get_loc(casted_key) 2899 except KeyError as err: -> 2900 raise KeyError(key) from err 2901 2902 if tolerance is not None: KeyError: '고령자비율'
np.corrcoef(data_result['외국인비율'],data_result['소계'])
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) ~/.local/lib/python3.6/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance) 2897 try: -> 2898 return self._engine.get_loc(casted_key) 2899 except KeyError as err: pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc() pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc() pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item() pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item() KeyError: '외국인비율' The above exception was the direct cause of the following exception: KeyError Traceback (most recent call last) <ipython-input-181-adc9106aca4a> in <module> ----> 1 np.corrcoef(data_result['외국인비율'],data_result['소계']) ~/.local/lib/python3.6/site-packages/pandas/core/frame.py in __getitem__(self, key) 2904 if self.columns.nlevels > 1: 2905 return self._getitem_multilevel(key) -> 2906 indexer = self.columns.get_loc(key) 2907 if is_integer(indexer): 2908 indexer = [indexer] ~/.local/lib/python3.6/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance) 2898 return self._engine.get_loc(casted_key) 2899 except KeyError as err: -> 2900 raise KeyError(key) from err 2901 2902 if tolerance is not None: KeyError: '외국인비율'
np.corrcoef(data_result['인구수'],data_result['소계']
File "<ipython-input-182-519468f38a6b>", line 1 np.corrcoef(data_result['인구수'],data_result['소계'] ^ SyntaxError: unexpected EOF while parsing
조사해보니 CCTV 개수와 고령자비율은 약한 음의 상관관계이고, 외국인비율과는 큰 의미가 없다고 할 수 있습니다. 그런데 인구수와는 상관계수가 0.3이어서 약한 상관관계가 있다고 볼 수 있습니다. 그러면 CCTV와 인구수의 관계를 조금 더 들여다보겠습니다.
data_result.sort_values(by='소계', ascending=False).head(5)
소계 | 최근증가율 | 인구수 | 한국인 | 외국인 | 고령자 | 여자 | 계.1 | 남자.1 | 여자.1 | 계.2 | 남자.2 | 여자.2 | 세대당인구 | 65세이상고령자 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
구별 |
위에서 봤듯이 CCTV가 많이 설치된 구와,
data_result.sort_values(by='인구수', ascending=False).head(5)
소계 | 최근증가율 | 인구수 | 한국인 | 외국인 | 고령자 | 여자 | 계.1 | 남자.1 | 여자.1 | 계.2 | 남자.2 | 여자.2 | 세대당인구 | 65세이상고령자 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
구별 |
인구수가 많은 구를 시각적으로 비교하면 좋을 것 같습니다.
import matplotlib.pyplot as plt
%matplotlib inline
%matplotlib inline이라는 명령은 그래프의 결과를 출력 세션에 나타나게 하는 설정입니다.
plt.figure
plt.plot([1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,1,0])
plt.show()
numpy를 이용해서 sin을 만들고 이것을 그려보겠습니다.
import numpy as np
t = np.arange(0,12,0.01)
y = np.sin(t)
np.arrang 명령은 0부터 12까지 0.01간격으로 데이터를 만들고 그 리스트를 np.sin에 입력하면 sin값이 나타납니다. t라는 시간 혹은 그래프상에서 x축을 의미하는 데이터를 0부터 12 까지 만들고 그냥 사인함수 (np.sin)에 입렵해서 그 출력을 y로 저장했습니다. 단 두줄입니다. t는 값을 하나만 가진 변수가 아닙니다. 약 1200개 정도의 값을 가진 일종의 배열입니다. 그것을 반복문 없이 그냥 한 줄로 처리하는 것입니다.
plt.figure(figsize=(10,6))
plt.plot(t,y)
plt.show()
plt.figure(figsize=(10,6))
plt.plot(t,y)
plt.grid()
plt.xlabel('time')
plt.ylabel('Amplitude')
plt.title('Example of sinewave')
plt.show()
xlabel로 x축 라벨을, ylabel로 y축 라벨을, title 명령으로 제목을 정하고, 격자 무늬는 grid() 명령으로 만들수 있습니다.
plt.figure(figsize=(10,6))
plt.plot(t,np.sin(t))
plt.plot(t,np.cos(t))
plt.grid()
plt.xlabel('time')
plt.ylabel('Amplitude')
plt.title('Example of sinewave')
plt.show()
또 plot 명령을 두 개 넣어서 한 화면에 그래프를 두개 만들 수 있습니다. 여기에 범례(legend)를 추가하고 싶다면 plot을 그릴 때 label 욥션으로 텍스트를 잡아주고, plt.legend()라는 명령을 주면 됩니다.
plt.figure(figsize=(10,6))
plt.plot(t,np.sin(t),label='sin')
plt.plot(t,np.cos(t),label='cos')
plt.grid()
plt.legend()
plt.xlabel('time')
plt.ylabel('Amplitude')
plt.title('Example of sinewave')
plt.show()
또한 lw 옵션으로 선의 굵기를 지정할 수 있으며, color 옵션으로 색상을 지정할 수 있습니다.
plt.figure(figsize=(10,6))
plt.plot(t,np.sin(t), lw=3, label='sin')
plt.plot(t,np.cos(t), 'r', label='cos')
plt.grid()
plt.legend()
plt.xlabel('time')
plt.ylabel('Amplitude')
plt.title('Example of sinewave')
plt.show()
t = [0,1,2,3,4,5,6]
y = [1,4,5,8,9,5,3]
plt.figure(figsize=(10,6))
plt.plot(t,y,color='green')
plt.show()
또 color='green' 옵션으로 색상을 지정할 수도 있습니다.
t = [0,1,2,3,4,5,6]
y = [1,4,5,8,9,5,3]
plt.figure(figsize=(10,6))
plt.plot(t,y,color='green', linestyle='dashed')
plt.show()
linestyle='dashed' 옵션으로 선 스타일을 지정할 수 있습니다.
plt.figure(figsize=(10,6))
plt.plot(t,y,color='green', linestyle='dashed', marker='o')
plt.show
<function matplotlib.pyplot.show(close=None, block=None)>
marker 옵션으로 데이터가 존재하는 곳에 마킹할 수 있습니다. 여기에 markerfacecolor 옵션과 markersize 옵션으로 마커의 크기와 색상을 지정할 수 있습니다.
plt.figure(figsize=(10,6))
plt.plot(t,y,color='green', linestyle='dashed', marker='o', markerfacecolor='blue', markersize=12)
plt.show
<function matplotlib.pyplot.show(close=None, block=None)>
선을 그리는 plot 명령 외에 scatter 명령도 있습니다.
t = np.array([0,1,2,3,4,5,6,7,8,9])
y = np.array([9,8,7,9,8,3,2,4,3,4])
plt.figure(figsize=(10,6))
plt.scatter(t,y)
plt.show()
여기에 marker를 지정할 수 있습니다.
plt.figure(figsize=(10,6))
plt.scatter(t,y, marker='>')
plt.show()
그리고 x축 값인 t에 따라 색상을 바꾸는 color map을 지정할 수 있습니다. 이때 s 옵션은 마커의 크기 입니다.
colormap = t
plt.figure(figsize=(10,6))
plt.scatter(t,y, s=50, c=colormap,marker='>')
plt.colorbar()
plt.show()
numpy의 랜덤변수 함수를 이용해서 데이터 세 개를 만들겠습니다. 이때 loc 옵션으로 평균값과 scale 옵션으로 표준편차를 지정합니다.
s1 = np.random.normal(loc=0,scale=1,size=1000)
s2 = np.random.normal(loc=5,scale=0.5,size=1000)
s3 = np.random.normal(loc=10,scale=2,size=1000)
이것을 그리면,
plt.figure(figsize=(10,6))
plt.plot(s1,label='s1')
plt.plot(s2,label='s2')
plt.plot(s3,label='s3')
plt.legend()
plt.show()
이렇게 됩니다. 이것을 boxplot으로 표현할 수 있습니다.
plt.figure(figsize=(10,6))
plt.boxplot((s1,s2,s3))
plt.grid()
plt.show()
CCTV 현황을 그래프로 분석해보자, 그러나 mapplotlib이 기본으로 가진 폰트는 한글을 지원하지 않기 때문에 matplotlib의 폰트를 변경할 필요가 있습니다.
from matplotlib import font_manager, rc
plt.rcParams['axes.unicode_minus'] = False
if platform.system() == 'Darwin':
rc('font', family='AppleGothic')
elif platform.system() == 'Windows':
path = "c:/Windows/Fonts/malgun.ttf"
font_name = font_manager.FontProperties(fname=path).get_name()
rc('font', family=font_name)
elif platform.system() == 'Linux':
path = "/usr/share/fonts/NanumGothic.ttf"
font_name = font_manager.FontProperties(fname=path).get_name()
plt.rc('font', family=font_name)
else:
print('Unknown system... sorry~~~~')
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-205-6f47ab502de7> in <module> 2 plt.rcParams['axes.unicode_minus'] = False 3 ----> 4 if platform.system() == 'Darwin': 5 rc('font', family='AppleGothic') 6 elif platform.system() == 'Windows': NameError: name 'platform' is not defined
data_result.head()
소계 | 최근증가율 | 인구수 | 한국인 | 외국인 | 고령자 | 여자 | 계.1 | 남자.1 | 여자.1 | 계.2 | 남자.2 | 여자.2 | 세대당인구 | 65세이상고령자 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
구별 |
pandas 데이터 뒤에 plot 명령을 붙이면 바로 그려줍니다.
data_result['소계'].plot(kind='barh', grid=True, figsize=(10,10))
plt.show()
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-207-c33541afadd4> in <module> ----> 1 data_result['소계'].plot(kind='barh', grid=True, figsize=(10,10)) 2 plt.show() ~/.local/lib/python3.6/site-packages/pandas/plotting/_core.py in __call__(self, *args, **kwargs) 947 data.columns = label_name 948 --> 949 return plot_backend.plot(data, kind=kind, **kwargs) 950 951 __call__.__doc__ = __doc__ ~/.local/lib/python3.6/site-packages/pandas/plotting/_matplotlib/__init__.py in plot(data, kind, **kwargs) 59 kwargs["ax"] = getattr(ax, "left_ax", ax) 60 plot_obj = PLOT_CLASSES[kind](data, **kwargs) ---> 61 plot_obj.generate() 62 plot_obj.draw() 63 return plot_obj.result ~/.local/lib/python3.6/site-packages/pandas/plotting/_matplotlib/core.py in generate(self) 276 for ax in self.axes: 277 self._post_plot_logic_common(ax, self.data) --> 278 self._post_plot_logic(ax, self.data) 279 280 def _args_adjust(self): ~/.local/lib/python3.6/site-packages/pandas/plotting/_matplotlib/core.py in _post_plot_logic(self, ax, data) 1459 name = self._get_index_name() 1460 -> 1461 s_edge = self.ax_pos[0] - 0.25 + self.lim_offset 1462 e_edge = self.ax_pos[-1] + 0.25 + self.bar_width + self.lim_offset 1463 IndexError: index 0 is out of bounds for axis 0 with size 0
여기서 kind='barh'로 지정해서 수평바(bar)로 그리도록 했습니다. grid=True 로 grid를 그리라 했고 figsize로 그림 크기도 지정했습니다. 하지만 figsize를 지정했어도 표현되는 곳이 웹 브라우저이기 때문에 항상 그 크기를 유지하는 것은 아닐 수 있습니다. 유저가 창의 크기를 줄이거나 하면 변하게 될 것입니다. 위 그림은 큰 의미를 찾기 어렵습니다. 사실 수평바 그래프는 데이터가 정렬되어 있을 때 좀 더 보기 좋습니다.
data_result['소계'].sort_values().plot(kind='barh', grid=True, figsize=(10,10))
plt.show()
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-208-b29ea4e4542c> in <module> ----> 1 data_result['소계'].sort_values().plot(kind='barh', grid=True, figsize=(10,10)) 2 3 plt.show() ~/.local/lib/python3.6/site-packages/pandas/plotting/_core.py in __call__(self, *args, **kwargs) 947 data.columns = label_name 948 --> 949 return plot_backend.plot(data, kind=kind, **kwargs) 950 951 __call__.__doc__ = __doc__ ~/.local/lib/python3.6/site-packages/pandas/plotting/_matplotlib/__init__.py in plot(data, kind, **kwargs) 59 kwargs["ax"] = getattr(ax, "left_ax", ax) 60 plot_obj = PLOT_CLASSES[kind](data, **kwargs) ---> 61 plot_obj.generate() 62 plot_obj.draw() 63 return plot_obj.result ~/.local/lib/python3.6/site-packages/pandas/plotting/_matplotlib/core.py in generate(self) 276 for ax in self.axes: 277 self._post_plot_logic_common(ax, self.data) --> 278 self._post_plot_logic(ax, self.data) 279 280 def _args_adjust(self): ~/.local/lib/python3.6/site-packages/pandas/plotting/_matplotlib/core.py in _post_plot_logic(self, ax, data) 1459 name = self._get_index_name() 1460 -> 1461 s_edge = self.ax_pos[0] - 0.25 + self.lim_offset 1462 e_edge = self.ax_pos[-1] + 0.25 + self.bar_width + self.lim_offset 1463 IndexError: index 0 is out of bounds for axis 0 with size 0
이제 보니 CCTV 개수에서는 강남구가 월등하다는 것을 알 수 있습니다. 그 뒤를 이어서 양천구,서초구,은평구가 꽤 많은 CCTV가 설치됐다는 것을 알 수 있습니다. 그리고 하위 그룹이 얼마나 적은수의 CCTV를 가지고 있는지도 확인할 수 있습니다. 여기에 인구 대비 CCTV 비율을 계산해서 정렬하고 그려보겠습니다.
data_result['CCTV비율'] = data_result['소계'] / data_result['인구수'] * 100
data_result['CCTV비율'].sort_values().plot(kind='barh', grid=True, figsize=(10,10))
plt.show()
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-209-b13fe7e37a7b> in <module> 1 data_result['CCTV비율'] = data_result['소계'] / data_result['인구수'] * 100 ----> 2 data_result['CCTV비율'].sort_values().plot(kind='barh', grid=True, figsize=(10,10)) 3 4 plt.show() ~/.local/lib/python3.6/site-packages/pandas/plotting/_core.py in __call__(self, *args, **kwargs) 947 data.columns = label_name 948 --> 949 return plot_backend.plot(data, kind=kind, **kwargs) 950 951 __call__.__doc__ = __doc__ ~/.local/lib/python3.6/site-packages/pandas/plotting/_matplotlib/__init__.py in plot(data, kind, **kwargs) 59 kwargs["ax"] = getattr(ax, "left_ax", ax) 60 plot_obj = PLOT_CLASSES[kind](data, **kwargs) ---> 61 plot_obj.generate() 62 plot_obj.draw() 63 return plot_obj.result ~/.local/lib/python3.6/site-packages/pandas/plotting/_matplotlib/core.py in generate(self) 267 def generate(self): 268 self._args_adjust() --> 269 self._compute_plot_data() 270 self._setup_subplots() 271 self._make_plot() ~/.local/lib/python3.6/site-packages/pandas/plotting/_matplotlib/core.py in _compute_plot_data(self) 416 # no non-numeric frames or series allowed 417 if is_empty: --> 418 raise TypeError("no numeric data to plot") 419 420 # GH25587: cast ExtensionArray of pandas (IntegerArray, etc.) to TypeError: no numeric data to plot
인구 대비 CCTV 수를 보니 이번에는 용산구와 종로구가 월등이 높습니다. 그런데 송파구는 인구 대비로 봐도 CCTV 비율이 낮습니다. scatter 함수를 사용하여 s=50로 마커의 크기를 잡고 그려보겠습니다.
plt.figure(figsize=(6,6))
plt.scatter(data_result['인구수'], data_result['소계'], s=50)
plt.xlabel('인구수')
plt.ylabel('CCTV')
plt.grid()
plt.show()
/home/wntkdl94/.local/lib/python3.6/site-packages/matplotlib/backends/backend_agg.py:238: RuntimeWarning: Glyph 51064 missing from current font. font.set_text(s, 0.0, flags=flags) /home/wntkdl94/.local/lib/python3.6/site-packages/matplotlib/backends/backend_agg.py:238: RuntimeWarning: Glyph 44396 missing from current font. font.set_text(s, 0.0, flags=flags) /home/wntkdl94/.local/lib/python3.6/site-packages/matplotlib/backends/backend_agg.py:238: RuntimeWarning: Glyph 49688 missing from current font. font.set_text(s, 0.0, flags=flags) /home/wntkdl94/.local/lib/python3.6/site-packages/matplotlib/backends/backend_agg.py:201: RuntimeWarning: Glyph 51064 missing from current font. font.set_text(s, 0, flags=flags) /home/wntkdl94/.local/lib/python3.6/site-packages/matplotlib/backends/backend_agg.py:201: RuntimeWarning: Glyph 44396 missing from current font. font.set_text(s, 0, flags=flags) /home/wntkdl94/.local/lib/python3.6/site-packages/matplotlib/backends/backend_agg.py:201: RuntimeWarning: Glyph 49688 missing from current font. font.set_text(s, 0, flags=flags)
이제 저 데이터를 대표하는 직선을 하나 그리려고 합니다. 어차피 이전 절에서 CCTV와 인구수는 양의 상관관계가 있다고 봤으니까요.
fp1 = np.polyfit(data_result['인구수'], data_result['소계'],1)
fp1
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-211-29383fab81cc> in <module> ----> 1 fp1 = np.polyfit(data_result['인구수'], data_result['소계'],1) 2 fp1 <__array_function__ internals> in polyfit(*args, **kwargs) ~/.local/lib/python3.6/site-packages/numpy/lib/polynomial.py in polyfit(x, y, deg, rcond, full, w, cov) 597 raise TypeError("expected 1D vector for x") 598 if x.size == 0: --> 599 raise TypeError("expected non-empty vector for x") 600 if y.ndim < 1 or y.ndim > 2: 601 raise TypeError("expected 1D or 2D array for y") TypeError: expected non-empty vector for x
f1 = np.poly1d(fp1)
fx = np.linspace(100000, 700000, 100)
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-212-ac34cb6fcd8e> in <module> ----> 1 f1 = np.poly1d(fp1) 2 fx = np.linspace(100000, 700000, 100) NameError: name 'fp1' is not defined
여기서 numpy의 ployfit 명령으로 손쉽게 직선을 만들 수 있습니다. 그리고 이를 그리기 위해 x축과 y축 데이터를 얻어야 하는데, x축 데이터는 numpy의 linspace로 만들고, y축은 polyld로 만들 수 있습니다.
plt.figure(figsize=(10,10))
plt.scatter(data_result['인구수'], data_result['소계'], s=50)
plt.plot(fx, f1(fx), ls='dashed', lw=3, color='g')
plt.xlabel('인구수')
plt.ylabel('CCTV')
plt.grid()
plt.show()
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-213-2e9384a27cf0> in <module> 1 plt.figure(figsize=(10,10)) 2 plt.scatter(data_result['인구수'], data_result['소계'], s=50) ----> 3 plt.plot(fx, f1(fx), ls='dashed', lw=3, color='g') 4 plt.xlabel('인구수') 5 plt.ylabel('CCTV') NameError: name 'fx' is not defined
여기서 두가지 장치를 넣고 싶습니다. 하나는 직선이 이전체 데이터의 대표 값 역할을 한다면, 즉 인구수가 300000일 떄는 CCTV는 1100 정도여야 한다는 개념을 이야기하는 거라면 그 경향에서 멀리 있는 구는 이름이 같이 나타나도록 하고 싶다는 것과 직선에서 멀어질수록 다른 색을 나타내도록 하고 싶다는 것입니다. 그래서 오차를 계산할 수 있는 코드를 만들고 오차가 큰 순으로 데이터를 정렬해서 다시 저장했습니다.
fp1 = np.polyfit(data_result['인구수'], data_result['소계'], 1)
f1 = np.poly1d(fp1)
fx = np.linspace(100000, 700000, 100)
data_result['오차'] = np.abs(data_result['소계'] - f1(data_result['인구수']))
df_sort = data_result.sort_values(by='오차', ascending=False)
df_sort.head()
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-214-6877db2cd6f9> in <module> ----> 1 fp1 = np.polyfit(data_result['인구수'], data_result['소계'], 1) 2 3 f1 = np.poly1d(fp1) 4 fx = np.linspace(100000, 700000, 100) 5 <__array_function__ internals> in polyfit(*args, **kwargs) ~/.local/lib/python3.6/site-packages/numpy/lib/polynomial.py in polyfit(x, y, deg, rcond, full, w, cov) 597 raise TypeError("expected 1D vector for x") 598 if x.size == 0: --> 599 raise TypeError("expected non-empty vector for x") 600 if y.ndim < 1 or y.ndim > 2: 601 raise TypeError("expected 1D or 2D array for y") TypeError: expected non-empty vector for x
이제 텍스트와 color map을 입히겠습니다.
plt.figure(figsize=(14,10))
plt.scatter(data_result['인구수'], data_result['소계'],
c=data_result['오차'], s=50)
plt.plot(fx, f1(fx), ls='dashed', lw=3, color='g')
for n in range(10):
plt.text(df_sort['인구수'][n]*1.02, df_sort['소계'][n]*0.98,
df_sort.index[n], fontsize=15)
plt.xlabel('인구수')
plt.ylabel('인구당비율')
plt.colorbar()
plt.grid()
plt.show()
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) ~/.local/lib/python3.6/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance) 2897 try: -> 2898 return self._engine.get_loc(casted_key) 2899 except KeyError as err: pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc() pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc() pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item() pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item() KeyError: '오차' The above exception was the direct cause of the following exception: KeyError Traceback (most recent call last) <ipython-input-215-a21894533552> in <module> 1 plt.figure(figsize=(14,10)) 2 plt.scatter(data_result['인구수'], data_result['소계'], ----> 3 c=data_result['오차'], s=50) 4 plt.plot(fx, f1(fx), ls='dashed', lw=3, color='g') 5 ~/.local/lib/python3.6/site-packages/pandas/core/frame.py in __getitem__(self, key) 2904 if self.columns.nlevels > 1: 2905 return self._getitem_multilevel(key) -> 2906 indexer = self.columns.get_loc(key) 2907 if is_integer(indexer): 2908 indexer = [indexer] ~/.local/lib/python3.6/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance) 2898 return self._engine.get_loc(casted_key) 2899 except KeyError as err: -> 2900 raise KeyError(key) from err 2901 2902 if tolerance is not None: KeyError: '오차'
<Figure size 1008x720 with 0 Axes>
일단 직선을 기준으로 위에 있는 '강남구','양천구','서초구','은평구','용산구'는 서울시 전체 지역의 일반적인 경향보다 CCTV가 많이 설치된 지역입니다. 그리고 '송파구','강서구','중랑구','마포구','도봉구'는 일반적인 경향보다 CCTV가 적게 설치된 지역입니다. 특히 '강남구'는 월등히 많은 CCTV가 설치됬지만, '송파구'는 인구수에 비해 너무나도 적은 수의 CCTV를 가지고 있습니다.
파이썬으로 데이터 주무르기, 서울시 범죄 현황 분석 (0) | 2021.04.15 |
---|---|
2장 서울시 범죄 현황 분석 (0) | 2021.04.07 |