데이터 과학이라고 거창하게 부르지 않아도 요즘은 데이터를 이용해서 실제 원하는 결과를 검증하고 이를 블로그에 올리거나, Github page에 올리는 경우를 많이 봅니다. 그 주제가 참 멋지고 그 과정이 아름다운 분들도 많구요^^. 저도 그냥 가벼운 마음에 통계자료를 가지고 살짝 뭔가를 해볼려고 합니다. 뭐 거창한 알고리즘을 쓴 건 아니구요. 그저 그래프나 깨작거리고 그리고, 데이터의 순서나 좀 바꾸던지.. 혹은 조금 만지작 거리는 수준입니다.^^.
살짝... "서울 강남 3구 체감안전도 높아"라는 위 기사를 보고~~~ 실제 통계자료도 그렇게 나타나는지를 볼려고 했습니다. 사람들이 생각하는 체감안전도와 혹시 통계자료에서 보는 안전도가 같을지 확인해 보는거죠^^
데이터 가져오기
공공데이터포털이라는 사이트에 가보면 아주아주 많은 통계 자료를 얻을 수 있습니다.
거기서 서울시 관서별 5대 범죄 발생 검거 현황이라는 자료가 있습니다. 그걸 받아서 사용해 볼려구요^^
데이터 다듬기 - 뭐 전처리라고 해둘까요^^
import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import platform from matplotlib import font_manager, rc if platform.system() == 'Darwin': rc('font', family='AppleGothic') elif platform.system() == 'Windows': font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name() rc('font', family=font_name) else: print('Unknown system... sorry~~~~') %matplotlib inline
먼저 들어가기에 앞서~ Jupyter Notebook에서 작성할 거구요... Anaconda 4.1.1에서 테스트되었습니다^^. 그리고 자료형은 기본적으로 제가 연재로 설명한 적이 있는 pandas[바로가기]를 사용합니다. 일단, matplotlib의 한글문제를 해결하기 위해 최근 이야기한 적[바로가기]이 있는 platform을 import해서 맥인지 윈도우인지를 확인하고 있습니다. 그리고...
df = pd.read_excel('data/2016서울범죄현황.xlsx', convert_float=True, encoding='euc-kr') df.head()
아까 공공데이터 포털에서 받은 데이터는 "2016서울범죄현황.xlsx"로 저장했구요. 그 내용은 ...
이렇네요... 5대 범죄에 대해 검거와 발생 건수가 각 경찰서 별로 정리가 되어 있군요^^ 문제는 저는 각 경찰서를 각 구별로 정리를 하고 싶다는 거죠^^ 위키백과의 서울시지방경찰청[바로가기] 항목에는 서울시의 각 경찰청 리스트가 글 하단에 있습니다. 여기서 서울시 경찰청의 소속 구를 확인하구요...
SeoulGu_name = {'서대문서': '서대문구', '수서서': '강남구', '강서서': '강서구', '서초서': '서초구', '서부서': '은평구', '중부서': '중구', '종로서': '종로구', '남대문서': '중구', '혜화서': '종로구', '용산서': '용산구', '성북서': '성북구', '동대문서': '동대문구', '마포서': '마포구', '영등포서': '영등포구', '성동서': '성동구', '동작서': '동작구', '광진서': '광진구', '강북서': '강북구', '금천서': '금천구', '중랑서': '중랑구', '강남서': '강남구', '관악서': '관악구', '강동서': '강동구', '종암서': '성북구', '구로서': '구로구', '양천서': '양천구', '송파서': '송파구', '노원서': '노원구', '방배서': '서초구', '은평서': '은평구', '도봉서': '도봉구'} df['구별'] = df['관서명'].apply(lambda v: SeoulGu_name.get(v, v)) df.head()
위에서 처럼 dict 자료형을 이용해서 구별~~이라는 column을 만들었습니다.
이제 위 결과처럼 df['구별']도 만들어 졌네요^^ 이제 '구별'로 구분은 되었지만... 구별 데이터를 쉽게 확인하기 위해서는 경찰서위주로 데이터가 되어 있는것 보다 구별로 되어있는것이 좋겠죠... 즉, 서초구처럼 경찰서가 두 개있는 구도 있으니, 전 구별로 모아서 데이터를 보고 싶다는 거죠^^. 그렇게 해주는 꽤 편리한 명령이 있습니다. .pivot_table[바로가기]입니다.^^
guDF = pd.pivot_table(df, index='구별', aggfunc=np.sum) guDF = guDF.drop(['계']) guDF.head()
이렇게 pivot_table을 사용하면 각 구별로 데이터를 쉽게 모아서 볼 수 있습니다. 그 결과는...
입니다.~~^^ 괜찮게 되었죠^^
guDF['강간검거율'] = guDF['강간(검거)']/guDF['강간(발생)']*100 guDF['강도검거율'] = guDF['강도(검거)']/guDF['강도(발생)']*100 guDF['살인검거율'] = guDF['살인(검거)']/guDF['살인(발생)']*100 guDF['절도검거율'] = guDF['절도(검거)']/guDF['절도(발생)']*100 guDF['폭력검거율'] = guDF['폭력(검거)']/guDF['폭력(발생)']*100 del guDF['강간(검거)'] del guDF['강도(검거)'] del guDF['살인(검거)'] del guDF['절도(검거)'] del guDF['폭력(검거)'] guDF.head()
이제 관심있는 데이터를 좀 만들고, 관심없는 데이터는 지우죠...
이렇게 되었습니다. 발생건수와 검거율만 남았네요~~~ 그런데 검거율이 100%가 넘는게 있네요... 아마 발생건수는 2016이고, 그 전해에 발생한 건수에 대한 검거가 2016에 이뤄지면 검거에 그게 반영된 모양입니다. 뭐 여기서는 그냥 100넘는건 100으로 하죠^^
guDF[guDF[['강간검거율', '강도검거율', '살인검거율', '절도검거율', '폭력검거율']] > 100] = 100 guDF.head(10)
넵~
수정되었습니다.^^.
guDF['검거율'] = guDF['소계(검거)']/guDF['소계(발생)']*100 guDF.head()
아차.. 전체 검거율도 만들어야죠~
음.. 가지고 놀만큼 되었네요^^그러나... 그냥 표가 넓어지는게 마음에 안들어서^^
guDF.rename(columns = {'강간(발생)':'강간', '강도(발생)':'강도', '살인(발생)':'살인', '절도(발생)':'절도', '폭력(발생)':'폭력'}, inplace=True) del guDF['소계(발생)'] del guDF['소계(검거)'] guDF.head()
Column의 이름을 바꾸도록 하겠습니다.^^
앗.. 표가 확 줄어든게 마음에 드네요 ㅋㅋㅋㅋ. (이상한거에 만족합니다^^)
위 파일을 받아서
popDF = pd.read_csv('data/pop_kor.csv', encoding='UTF-8', index_col='구별') popDF.head()
보면...
구별 인구수가 있습니다. 어디 다른데서 사용하던건데... 필요해서 가져다 옵니다. 아마 2015년 인구라 지금이랑 맞지는 않지만, 경향정도를 확인하는 걸로 사용하도록 하겠습니다.
guDF = guDF.join(popDF)
guDF.head()
그렇게 받은 두 데이터의 index가 같기 때문에 쉽게 join 명령으로 합치도록 하겠습니다.
잘 합쳐졌죠^^
guDF.sort_values(by='검거율', ascending=False, inplace=True) guDF.head()
일단.. 전체 검거율을 가지고 순위를 한 번 매겨 보겠습니다.
어때요... 종로, 용산, 서초, 등등의 구가 검거율이 높은게 아니네요... 강서구, 금천구, 강북구가.. 검거율이 높습니니다.. 응? 뭐.. 검거율이 체감안전도랑 좀 다르긴 할 수도 있겠죠.. 이제 조금더 가볼까요^^
그래프로 각 구별 현황 확인해보기
target_col = ['강간', '강도', '살인', '절도', '폭력'] weight_col = guDF[target_col].max() crime_count_norm = guDF[target_col]/weight_col crime_count_norm.head()
먼저... 5대범죄의 발생 건수만 대상으로 하고... 표현할려는 그래프의 특성을 잘 살리기 위해 각 범죄의 최댓값으로 각 column을 나눠서 정규화시키도록 하겠습니다.
넵... 각 범죄별 경중을 이야기할려는 것이 아니라 종합적인 시각화효과를 위해서 입니다^^
plt.figure(figsize = (10,10)) sns.heatmap(crime_count_norm.sort_values(by='살인', ascending=False), annot=True, fmt='f', linewidths=.5) plt.title('범죄 발생(살인발생으로 정렬) - 각 항목별 최대값으로 나눠 정규화') plt.show()
예전에 제가 연재한 적이 있는 seaborn의 heatmap[바로가기]을 사용했습니다.
비록 살인 발생 건수로 정렬했지만, 위로 갈수록 전반적으로 범죄 발생 건수가 높다는 것을 알 수 있습니다. 강남3구는 어디에 있나요^^ 네.. 결코 낮지 않네요.. 강남구는 누가봐도 5대 범죄 전체에 있어서 상위권입니다.
crime_ratio = crime_count_norm.div(guDF['인구수'], axis=0)*100000 plt.figure(figsize = (10,10)) sns.heatmap(crime_ratio.sort_values(by='살인', ascending=False), annot=True, fmt='f', linewidths=.5) plt.title('범죄 발생(살인발생으로 정렬) - 각 항목을 정규화한 후 인구로 나눔') plt.show()
이제 단순히 범죄건수만 보지 말고 이를 인구수로 나눠서 인구대비 발생비율로 보겠습니다.
이번에는 중구가 눈에 확~ 보이네요. 애초 기사에서 종로구에 있는 종로서가 체감안전도 1위였는데.. 종로구의 범죄발생 비율은 상위권이네요ㅠㅠ.
crime_ratio['전체발생비율'] = crime_ratio.mean(axis=1) plt.figure(figsize = (10,10)) sns.heatmap(crime_ratio.sort_values(by='전체발생비율', ascending=False), annot=True, fmt='f', linewidths=.5) plt.title('범죄 발생(전체발생비율로 정렬) - 각 항목을 정규화한 후 인구로 나눔') plt.show()
이번에는 전체발생비율로 정렬하고 다시 보죠^^
확실히 알 수 있습니다. 중구, 종로구, 영등포구가 인구대비 발생비율이 높습니다. 강남구와 서초구도 만만치 않네요. 인구대비로 보니 송파는 그래도 하위권이긴 합니다.
지도에 데이터를 표현하기...
지도에 데이터를 표현하는 것도 한 번 해볼까 합니다. 예전에 제가 소개한 적이 있는 Folium[바로가기]을 이용할려구요. 이 부분을 따라가기 전에 필요한 데이터가 하나 있는데....
skorea_municipalities_geo_simple.json
입니다. 출처는 [바로가기]에 있는 한국 지도 데이터 중 서울만 제가 따로 추려낸 것입니다.
import json import folium import warnings warnings.simplefilter(action = "ignore", category = FutureWarning) geo_path = 'data/skorea_municipalities_geo_simple.json' geo_str = json.load(open(geo_path, encoding='utf-8'))
일단 위 코드로 지도를 사용하기 위한 준비를 하구요
map = folium.Map(location=[37.5502, 126.982], zoom_start=11, tiles='Stamen Toner') map.choropleth(geo_str = geo_str, data = guDF['살인'], columns = [guDF.index, guDF['살인']], fill_color = 'PuRd', #PuRd, YlGnBu key_on = 'feature.id') map
심플하게 살인사건의 발생 건수를 표시해보죠
어떤거요... 갑가지 강남3구가 확 들어옵니다. 그리고 영등포구와 중랑구도 눈에 들어오네요... 저런...
map = folium.Map(location=[37.5502, 126.982], zoom_start=11, tiles='Stamen Toner') map.choropleth(geo_str = geo_str, data = crime_ratio['전체발생비율'], columns = [crime_ratio.index, crime_ratio['전체발생비율']], fill_color = 'PuRd', #PuRd, YlGnBu key_on = 'feature.id') map
인구대비 발생율로 계산한걸 합산한 전체발생비율로 다시 확인해 보겠습니다.
흠.. 종로구와 중구가 높네요... 강남구도 높구요... 아마 종로구와 중구는 관광 집중 지역이니 인구대비 비율이 높게 나타나는 것일 수도 있겠습니다.
map = folium.Map(location=[37.5502, 126.982], zoom_start=11, tiles='Stamen Toner') map.choropleth(geo_str = geo_str, data = guDF['검거율'], columns = [guDF.index, guDF['검거율']], fill_color = 'YlGnBu', #PuRd, YlGnBu key_on = 'feature.id') map
이번에는 검거율을 보죠^^
흠.. 검거율은 강서구와... 금천구, 강북구가 높네요.
경찰서의 위치 정보에 데이터를 포함시켜서 지도에 나타내기...
처음 데이터... df라고 저장했던 데이터에는 경찰서별 정보가 남아 있습니다. 여기서... 응? '계'는 없애구요^^
이걸 가지고 경찰서의 검거율을 지도에 같이 표현해보려구요..
station_name = [] for name in df['관서명']: station_name.append('서울'+str(name[:-1])+'경찰서') station_name
일단 제가 사용할 것은 googlemaps입니다. 그럴려면 경찰서의 fullname이 필요하거든요^^
이렇게 말이죠^^...
df['경찰서'] = station_name df['검거율'] = df['소계(검거)']/df['소계(발생)']*100 df.head()
그리고.. 경찰서별 검거율을 계산해두고...
그런데.. 검거율의 폭이 좀 좁아서 가장 낮은 검거율과 가장 높은 검거율을 가지는 경찰서를 일종의 점수 개념으로 간격을 좀 벌리겠습니다. 지도에 표기할때 눈에 잘 들어나게 할려구요^^
def reRange(x, oldMin, oldMax, newMin, newMax): return (x - oldMin)*(newMax - newMin) / (oldMax - oldMin) + newMin df['점수'] = reRange(df['검거율'], min(df['검거율']), max(df['검거율']), 1, 100) df.head()
그래서 위와 같이 점수로 표기합니다. 단순히 경찰서의 능력을 검거율로만 볼수 없겠죠... 단지 얻은 데이터를 기준으로 보는 것이니까요...
이렇게 되었네요^^ 이제 sort를 시켜서 결과를 보면~
어~ 강서경찰서, 금천경찰서가 검거율 1, 2위네요... 그 뒤를 강북서, 도봉서, 수서서가 따라가고 있습니다. 이제... [바로가기]에서 소개한 데로 googlemaps를 사용해서 각 경찰서의 위도, 경도 정보를 얻을 겁니다.
import googlemaps gmaps = googlemaps.Client(key="-- input your key --") lat = [] lng = [] for name in df['경찰서']: tmpMap = gmaps.geocode(name) tmpLoc = tmpMap[0].get('geometry') lat.append(tmpLoc['location']['lat']) lng.append(tmpLoc['location']['lng']) df['lat'] = lat df['lng'] = lng df.head()
위 코드의 결과는 각 경찰서의 위도 경도 정보를 얻을 수 있는거죠...
이렇게 말이죠^^...
map = folium.Map(location=[37.5502, 126.982], zoom_start=11) for n in df.index: folium.CircleMarker([df['lat'][n], df['lng'][n]], radius=df['점수'][n]*25, color='#3186cc', fill_color='#3186cc').add_to(map) map
이제 각 경찰서의 위치에 검거율을 환산한 점수를 원의 넓이로 표기하도록 하겠습니다.
아하.. 어떤가요.. 괜찮죠^^
map = folium.Map(location=[37.5502, 126.982], zoom_start=11) map.choropleth(geo_str = geo_str, data = crime_ratio['전체발생비율'], columns = [crime_ratio.index, crime_ratio['전체발생비율']], fill_color = 'PuRd', #PuRd, YlGnBu key_on = 'feature.id') for n in df.index: folium.CircleMarker([df['lat'][n], df['lng'][n]], radius=df['점수'][n]*25, color='#3186cc', fill_color='#3186cc').add_to(map) map
마지막으로...
이렇게 아까 했던 범죄발생비율과 경찰서의 검거율을 같이 지도에 그려보았습니다. 어떤가요^^
- 강남3구의 체감 안전도가 높다는 기사의 내용을 가지고, 실제 통계적으로도 그런 결과가 도출되는지 확인함
- 강남3구의 5대 범죄 발생 건수는 다른 구와 비교해서 높음
- 인구대비 발생 비율도 강남3구가 낮지는 않음
- 단, 강남구와 서초구는 유흥업소 밀집지역에서 범죄 발생이 높을 수 있음
아무튼.. 서울시 5대 범죄 발생건수와 검거율을 가지고 잠시 즐거운 분석 시간을 가져 보았습니다. 요즘.. 제 취미활동이거든요^^
'Theory > DataScience' 카테고리의 다른 글
텐서플로우(tensorflow)에서 텐서보드(tensorboard) 사용하기 (몹시 기초) (10) | 2017.04.05 |
---|---|
인구 소멸 위기 지역 파악해보기~~~ (22) | 2017.03.24 |
시각화하기 좋은 우리나라 지도 그리는 법을 소개해 드립니다.^^ (8) | 2017.03.15 |
맥OS 환경의 Anaconda에서 텐서플로우 tensorflow 설치하기 (6) | 2017.02.09 |
딥러닝 세미나를 다녀왔습니다.^^ (16) | 2017.01.16 |
지리적 정보를 시각화할 때 괜찮은 Python 지도 모듈 Folium (18) | 2016.12.28 |
Python Beautiful Soup으로 웹 페이지의 내용 쉽게 가져오기~ (11) | 2016.12.16 |