티스토리 뷰

1단원 서울시 구별 CCTV현황 분석 -2

출처 : 파이썬으로 데이터 주무르기 by 민형기

1단원 서울시 구별 CCTV현황 분석

    1. CCTV 현황과 인구 현황 데이터 구하기
    1. 파이썬에서 텍스트 파일과 엑셀 파일을 읽기 -pandas
    1. pandas 기초 익히기
    1. pandas를 이용해서 CCTV와 인구 현황 데이터 파악하기
    1. pandas 고급기능 - 두 DataFrame 병합하기
    1. CCTV 데이터와 인구 현황 데이터를 합치고 분석하기
    1. 파이썬의 대표 시각화 도구 - Matplotlib
    1. CCTV 현황 그래프로 분석하기

지난번에 1-3 pandas 기초 익히기까지(https://jfun.tistory.com/210) 다루었다. 지난번에 이어 오늘은 1-4 pandas를 이용해서 CCTV와 인구 현황 데이터 파악하기 부터 시작하겠다.

1-4 pandas 이용해서 CCTV와 인구 현황 데이터 파악하기

In [43]:
CCTV_Seoul.head()
Out[43]:
구별 소계 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
In [45]:
# 소계로 정렬
CCTV_Seoul.sort_values(by='소계', ascending=True).head()
Out[45]:
구별 소계 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는 적네..

In [46]:
CCTV_Seoul.sort_values(by='소계', ascending=False).head()
Out[46]:
구별 소계 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의 전체 개수가 가장 많은 구는 '강남구', '양천구', '서초구', '은평구', '용산구'로 나타난다.

In [47]:
# 2014~2016 CCTV 증가율을 구해보자
# 증가율 = 2014~2016 CCTV수 / 2013년 이전 CCTV수
CCTV_Seoul['최근증가율'] = (CCTV_Seoul['2016년'] + CCTV_Seoul['2015년'] + CCTV_Seoul['2014년']) / CCTV_Seoul['2013년도 이전'] * 100
CCTV_Seoul.sort_values(by='최근증가율', ascending=False).head()
Out[47]:
구별 소계 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가 그 이전 대비 많이 증가한 구는 '종로구', '도봉구', '마포구', '노원구', '강동구'라는 것도 알 수 있다.

In [48]:
# 서울시 인구 현황
pop_Seoul.head()
Out[48]:
구별 인구수 한국인 외국인 고령자
0 합계 10197604.0 9926968.0 270636.0 1321458.0
1 종로구 162820.0 153589.0 9231.0 25425.0
2 중구 133240.0 124312.0 8928.0 20764.0
3 용산구 244203.0 229456.0 14747.0 36231.0
4 성동구 311244.0 303380.0 7864.0 39997.0
In [49]:
# 합계는 우리에게 필요가 없어 drop 시키기로 하자.
pop_Seoul.drop([0], inplace=True)
pop_Seoul.head()
Out[49]:
구별 인구수 한국인 외국인 고령자
1 종로구 162820.0 153589.0 9231.0 25425.0
2 중구 133240.0 124312.0 8928.0 20764.0
3 용산구 244203.0 229456.0 14747.0 36231.0
4 성동구 311244.0 303380.0 7864.0 39997.0
5 광진구 372164.0 357211.0 14953.0 42214.0
In [50]:
# '구별'컬럼의 unique를 조사하여 어떤 '구'들이 있는지 확인해보자.
pop_Seoul['구별'].unique()
Out[50]:
array(['종로구', '중구', '용산구', '성동구', '광진구', '동대문구', '중랑구', '성북구', '강북구',
       '도봉구', '노원구', '은평구', '서대문구', '마포구', '양천구', '강서구', '구로구', '금천구',
       '영등포구', '동작구', '관악구', '서초구', '강남구', '송파구', '강동구', nan],
      dtype=object)

'구'들을 나열했더니 마지막에 nan 값이 보인다. 이 nan값이 row가 어떻게 되는지 찾아보자.

In [52]:
# isnull 명령어를 이용하여 NaN 데이터를 추출해보자.
pop_Seoul[pop_Seoul['구별'].isnull()]
Out[52]:
구별 인구수 한국인 외국인 고령자
26 NaN NaN NaN NaN NaN
In [53]:
# NaN에 해당하는 26번 row를 삭제해주자.
pop_Seoul.drop([26], inplace=True)
pop_Seoul.head()
Out[53]:
구별 인구수 한국인 외국인 고령자
1 종로구 162820.0 153589.0 9231.0 25425.0
2 중구 133240.0 124312.0 8928.0 20764.0
3 용산구 244203.0 229456.0 14747.0 36231.0
4 성동구 311244.0 303380.0 7864.0 39997.0
5 광진구 372164.0 357211.0 14953.0 42214.0
In [54]:
# 구별 '외국인비율'과 '고령자비율'을 계산해보자.
pop_Seoul['외국인비율'] = pop_Seoul['외국인'] / pop_Seoul['인구수'] * 100
pop_Seoul['고령자비율'] = pop_Seoul['고령자'] / pop_Seoul['인구수'] * 100
pop_Seoul.head()
Out[54]:
구별 인구수 한국인 외국인 고령자 외국인비율 고령자비율
1 종로구 162820.0 153589.0 9231.0 25425.0 5.669451 15.615404
2 중구 133240.0 124312.0 8928.0 20764.0 6.700690 15.583909
3 용산구 244203.0 229456.0 14747.0 36231.0 6.038828 14.836427
4 성동구 311244.0 303380.0 7864.0 39997.0 2.526635 12.850689
5 광진구 372164.0 357211.0 14953.0 42214.0 4.017852 11.342849

전체 '인구수', '외국인', '외국인 비율'로 각각 정렬해보자.

In [55]:
# 인구수 정렬
pop_Seoul.sort_values(by='인구수', ascending=False).head()
Out[55]:
구별 인구수 한국인 외국인 고령자 외국인비율 고령자비율
24 송파구 667483.0 660584.0 6899.0 72506.0 1.033584 10.862599
16 강서구 603772.0 597248.0 6524.0 72548.0 1.080540 12.015794
23 강남구 570500.0 565550.0 4950.0 63167.0 0.867660 11.072217
11 노원구 569384.0 565565.0 3819.0 71941.0 0.670725 12.634883
21 관악구 525515.0 507203.0 18312.0 68082.0 3.484582 12.955291

인구수가 많은 순은 '송파구', '강서구', '강남구', '노원구', '관악구' 순이라는 것을 알 수 있다.

In [57]:
# 외국인 수 정렬
pop_Seoul.sort_values(by='외국인', ascending=False).head()
Out[57]:
구별 인구수 한국인 외국인 고령자 외국인비율 고령자비율
19 영등포구 402985.0 368072.0 34913.0 52413.0 8.663598 13.006191
17 구로구 447874.0 416487.0 31387.0 56833.0 7.007998 12.689506
18 금천구 255082.0 236353.0 18729.0 32970.0 7.342345 12.925255
21 관악구 525515.0 507203.0 18312.0 68082.0 3.484582 12.955291
6 동대문구 369496.0 354079.0 15417.0 54173.0 4.172440 14.661322

외국인 숫자가 많은 구는 '영등포구', '구로구', '금천구', '관악구', '동대문 구'라는 것을 알 수 있다.

In [59]:
# 외국인 비율 정렬
pop_Seoul.sort_values(by='외국인비율', ascending=False).head()
Out[59]:
구별 인구수 한국인 외국인 고령자 외국인비율 고령자비율
19 영등포구 402985.0 368072.0 34913.0 52413.0 8.663598 13.006191
18 금천구 255082.0 236353.0 18729.0 32970.0 7.342345 12.925255
17 구로구 447874.0 416487.0 31387.0 56833.0 7.007998 12.689506
2 중구 133240.0 124312.0 8928.0 20764.0 6.700690 15.583909
3 용산구 244203.0 229456.0 14747.0 36231.0 6.038828 14.836427

외국인 비율이 높은 구는 '영등포구', '금천구', '구로구', '중구', '용산구'로 외국인 숫자와는 조금 바뀌는 것을 알 수 있다.

이제 고령자와 고령자 비율을 조사해보자

In [61]:
# 고령자 수 정렬
pop_Seoul.sort_values(by='고령자', ascending=False).head()
Out[61]:
구별 인구수 한국인 외국인 고령자 외국인비율 고령자비율
16 강서구 603772.0 597248.0 6524.0 72548.0 1.080540 12.015794
24 송파구 667483.0 660584.0 6899.0 72506.0 1.033584 10.862599
12 은평구 494388.0 489943.0 4445.0 72334.0 0.899091 14.631019
11 노원구 569384.0 565565.0 3819.0 71941.0 0.670725 12.634883
21 관악구 525515.0 507203.0 18312.0 68082.0 3.484582 12.955291

고령자가 많은 구는 '강서구', '송파구', '은평구', '노원구', '관악구'이다.

In [62]:
# 고령자 비율 정렬
pop_Seoul.sort_values(by='고령자비율', ascending=False).head()
Out[62]:
구별 인구수 한국인 외국인 고령자 외국인비율 고령자비율
9 강북구 330192.0 326686.0 3506.0 54813.0 1.061806 16.600342
1 종로구 162820.0 153589.0 9231.0 25425.0 5.669451 15.615404
2 중구 133240.0 124312.0 8928.0 20764.0 6.700690 15.583909
3 용산구 244203.0 229456.0 14747.0 36231.0 6.038828 14.836427
13 서대문구 327163.0 314982.0 12181.0 48161.0 3.723221 14.720797

고령자 비율이 높은 구는 '강북구', '종로구', '중구', '용산구', '서대문구'로 고령자 수의 순위와는 차이가 좀 많이 난다는 것을 알 수 있다.

1-5 pandas 고급 기능 - 두 DataFrame 병합하기

1-4에서 보면 데이터들을 간단히 확인해 보았는데, CCTV의 현황을 완전히 파악한 것 같지는 않다. 인구 대비 CCTV 현황 같은 내용을 확인하기 위해서는 두 가지 종류의 데이터를 병합해야 한다.
진도를 나가기 전에 pandas 고급 기능인 병합 을 배워보자

1) concat

In [63]:
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])
In [64]:
df1
Out[64]:
A B C D
0 A0 B0 C0 D0
1 A1 B1 C1 D1
2 A2 B2 C2 D2
3 A3 B3 C3 D3
In [65]:
df2
Out[65]:
A B C D
4 A4 B4 C4 D4
5 A5 B5 C5 D5
6 A6 B6 C6 D6
7 A7 B7 C7 D7
In [66]:
df3
Out[66]:
A B C D
8 A8 B8 C8 D8
9 A9 B9 C9 D9
10 A10 B10 C10 D10
11 A11 B11 C11 D11
In [67]:
# 열방향으로 단순히 합치는 기능
result = pd.concat([df1, df2, df3])
result
Out[67]:
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
In [68]:
# concat에 옵션을 넣어보자.
# key 옵션을 넣으면 다중 index가 되어 DataFrame별로 level을 형성한다.
result = pd.concat([df1, df2, df3], keys=['x', 'y', 'z'])
result
Out[68]:
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

레벨은 다음과 같이 형성되어 있다.

In [69]:
result.index
Out[69]:
MultiIndex(levels=[['x', 'y', 'z'], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]],
           codes=[[0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]])
In [70]:
result.index.get_level_values(0)
Out[70]:
Index(['x', 'x', 'x', 'x', 'y', 'y', 'y', 'y', 'z', 'z', 'z', 'z'], dtype='object')
In [71]:
result.index.get_level_values(1)
Out[71]:
Int64Index([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], dtype='int64')
In [72]:
# 새로운 DataFrame을 만들어 axis 옵션을 사용하여 concat시켜보자.
df4 = pd.DataFrame({'B': ['B2', 'B3', 'B6', 'B7'],
                    'C': ['D2', 'D3', 'D6', 'D7'],
                    'F': ['F2', 'F3', 'F6', 'F7']},
                   index=[2, 3, 6, 7])
result = pd.concat([df1, df4], axis=1)
In [73]:
df1
Out[73]:
A B C D
0 A0 B0 C0 D0
1 A1 B1 C1 D1
2 A2 B2 C2 D2
3 A3 B3 C3 D3
In [74]:
df4
Out[74]:
B C F
2 B2 D2 F2
3 B3 D3 F3
6 B6 D6 F6
7 B7 D7 F7
In [75]:
# axis=1을 주고 concat 시켰더니?!
result
Out[75]:
A B C D B C 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이 저장된다.

In [76]:
#concat의 join 옵션을 사용해보자.
result = pd.concat([df1, df4], axis=1, join='inner')
result
Out[76]:
A B C D B C F
2 A2 B2 C2 D2 B2 D2 F2
3 A3 B3 C3 D3 B3 D3 F3
In [79]:
#concat의 join_axes 옵션을 사용해보자.
result = pd.concat([df1, df4], axis=1, join_axes=[df1.index])
result
Out[79]:
A B C D B C 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
In [85]:
# ignore_index=True 옵션을 사용해보자. 
# index를 무시하고 합친 후 다시 index를 부여
result = pd.concat([df1, df4], ignore_index=True)
result
C:\Users\whanh\AppData\Local\Continuum\anaconda3\lib\site-packages\ipykernel_launcher.py:3: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.

To accept the future behavior, pass 'sort=False'.

To retain the current behavior and silence the warning, pass 'sort=True'.

  This is separate from the ipykernel package so we can avoid doing imports until
Out[85]:
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 D2 NaN F2
5 NaN B3 D3 NaN F3
6 NaN B6 D6 NaN F6
7 NaN B7 D7 NaN F7

2) merge

In [86]:
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']})
In [87]:
left
Out[87]:
key A B
0 k0 A0 B0
1 k4 A1 B1
2 k2 A2 B2
3 k3 A3 B3
In [89]:
right
Out[89]:
key C D
0 k0 C0 D0
1 k1 C1 D1
2 k2 C2 D2
3 k3 C3 D3
In [90]:
# merge 명령에서 merge 기순을 설정하는 on 옵션으로 합치면 공통된 key에 대해서만 합치게 된다.
pd.merge(left, right, on='key')
Out[90]:
key A B C D
0 k0 A0 B0 C0 D0
1 k2 A2 B2 C2 D2
2 k3 A3 B3 C3 D3
In [91]:
# how 옵션을 사용하여 합치는 두 데이터를 한쪽 데이터를 기준으로 합칠 수도 있다.
# 왼쪽 기주운~
pd.merge(left, right, how='left', on='key')
Out[91]:
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
In [92]:
# 오른쪽 기준~
pd.merge(left, right, how='right', on='key')
Out[92]:
key A B C D
0 k0 A0 B0 C0 D0
1 k2 A2 B2 C2 D2
2 k3 A3 B3 C3 D3
3 k1 NaN NaN C1 D1
In [93]:
# how의 값을 outer로 하면 merge한 데이터 결과 모두를 가진다.
pd.merge(left, right, how='outer', on='key')
Out[93]:
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
In [94]:
# outer와 반대의 옵션인 inner 옵션
pd.merge(left, right, how='inner', on='key')
Out[94]:
key A B C D
0 k0 A0 B0 C0 D0
1 k2 A2 B2 C2 D2
2 k3 A3 B3 C3 D3
In [ ]:
 
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/01   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함