데이터분석/미니프로젝트 01. 서울시 CCTV 현황 분석

미니프로젝트 01-02-07. 서울시 범죄현황 시각화(folium), 추가 검증

책다니엘 2024. 3. 30. 07:39

지금까지 배운 것들을 바탕으로 이제 서울시 구별 범죄현황을 시각화할 차례다. 지도시각화를 위해서는 서울시의 지리정보가 포함된 json파일이 필요한데, 이번에는 Lucy Park님이 깃허브에 무료로 배포하신 정보를 바탕으로 진행한다. 해당 자료는 마지막 업데이트가 6년 전인 자료로, 한계는 분명히 존재하지만, 미니 프로젝트에 사용하기에는 무리가 없을 것이다.

https://github.com/southkorea/southkorea-maps

 

GitHub - southkorea/southkorea-maps: South Korea administrative divisions in ESRI Shapefile, GeoJSON and TopoJSON formats.

South Korea administrative divisions in ESRI Shapefile, GeoJSON and TopoJSON formats. - southkorea/southkorea-maps

github.com

 

1. 아래의 링크로 들어가 파일을 다운받는다.

https://github.com/southkorea/southkorea-maps/blob/master/kostat/2018/json/skorea-municipalities-2018-geo.json

 

southkorea-maps/kostat/2018/json/skorea-municipalities-2018-geo.json at master · southkorea/southkorea-maps

South Korea administrative divisions in ESRI Shapefile, GeoJSON and TopoJSON formats. - southkorea/southkorea-maps

github.com

 

2. 이 자료에서 서울시 부분만 추려서 다시 우리의 데이터로 활용한다. 아래의 코드를 작성한다.

import json

crime_anal_norm = pd.read_csv('../data/02. crime_in_seoul_final.csv', index_col=0, encoding = 'utf-8')

geo_path = '../data/02. skorea_municipalities_geo_simple.json'
geo_str = json.load(open(geo_path, encoding = 'utf-8'))

이전에 만들었던 범죄/CCTV 자료와 위에서 받은 지리자료를 받아 온다.

 

3. Stamen Toner 맵에 folium.Choropleth().add_to()를 이용해서 앞서 한 방법대로 범죄 현황을 표시한다.

tiles = "https://tiles.stadiamaps.com/tiles/stamen_toner/{z}/{x}/{y}{r}.png?api_key=API키 여기에 입력"
attr = "toner"

my_map = folium.Map(location=[37.5502, 126.982], zoom_start=11, tiles=tiles, attr=attr)

folium.Choropleth(
    geo_data = geo_str,
    data=crime_anal_norm['살인'],
    columns=[crime_anal_norm.index, crime_anal_norm['살인']],
    fill_color='PuRd',
    key_on='feature.id',
    fill_opacity=0.7,
    line_opacity=0.2,
    legend_name='정규화된 살인 발생 건수', 
).add_to(my_map)
my_map

강남구, 구로구가 가장 높게 나타나고, 그 뒤를 관악구가 따른다.

 

4. 이번엔 성범죄 발생 건수로 보자.

tiles = "https://tiles.stadiamaps.com/tiles/stamen_toner/{z}/{x}/{y}{r}.png?api_key=API키 여기에 입력"
attr = "toner"

my_map = folium.Map(location=[37.5502, 126.982], zoom_start=11, tiles=tiles, attr=attr)

folium.Choropleth(
    geo_data = geo_str,
    data=crime_anal_norm['강간,추행'],
    columns=[crime_anal_norm.index, crime_anal_norm['강간,추행']],
    fill_color='PuRd',
    key_on='feature.id',
    fill_opacity=0.7,
    line_opacity=0.2,
    legend_name='정규화된 성범죄 발생 건수', 
).add_to(my_map)
my_map

강남구가 가장 높게 나타난다. 그 다음은 서초구, 마포구 순

 

5. 이번에는 5대 범죄 종합 점수로 보자.

tiles = "https://tiles.stadiamaps.com/tiles/stamen_toner/{z}/{x}/{y}{r}.png?api_key=API키 여기에 입력"
attr = "toner"

my_map = folium.Map(location=[37.5502, 126.982], zoom_start=11, tiles=tiles, attr=attr)

folium.Choropleth(
    geo_data = geo_str,
    data=crime_anal_norm['범죄'],
    columns=[crime_anal_norm.index, crime_anal_norm['범죄']],
    fill_color='PuRd',
    key_on='feature.id',
    fill_opacity=0.7,
    line_opacity=0.2,
    legend_name='정규화된 범죄 발생 건수', 
).add_to(my_map)
my_map

강남구가 가장 높게 나타난다.

 

6. 단순 합산치로만 보면 강남3구가 여전히 모든 부문에서 가장 높게 나타난다. 이제 인구 대비 범죄 건수를 통해 보다 확실하게 데이터를 판단해보자. 

tiles = "https://tiles.stadiamaps.com/tiles/stamen_toner/{z}/{x}/{y}{r}.png?api_key=API키 여기에 입력"
attr = "toner"

tmp_criminal = crime_anal_norm['범죄'] / crime_anal_norm['인구수']

my_map = folium.Map(location=[37.5502, 126.982], zoom_start=11, tiles=tiles, attr=attr)

folium.Choropleth(
    geo_data = geo_str,
    data=tmp_criminal,
    columns=[crime_anal_norm.index, tmp_criminal],
    fill_color='PuRd',
    key_on='feature.id',
    fill_opacity=0.7,
    line_opacity=0.2,
    legend_name='정규화된 범죄 발생 건수', 
).add_to(my_map)
my_map

인구대비 범죄 건수로 보니 결과값이 다르다. 인구대비 범죄율이 가장 높은 구는 종로구였다.

 

7. 이번에는 경찰서별 정보를 바탕으로 범죄발생과 함께 정리해보자.

tmp는 numpy로 구한 axis=1행(가로), axis=1부분은 pandas로 가져오는 axis=1행(세로)를 의미한다.

 

8. 경찰서 데이터를 정렬할 때 googlemaps의 geo데이터로 가져왔던 lat, lng를 바탕으로 마커를 표시한다.

my_map = folium.Map(location=[37.5502, 126.982], zoom_start=11)

for idx, rows in crime_anal_station.iterrows():
    folium.Marker([rows['lat'], rows['lng']]).add_to(my_map)

my_map

9. 아직 부족하다. 좀 더 확실하게 시각화해주기 위해 '검거' 값에 적절한 값을 곱해 원의 넓이로 사용한다.

my_map = folium.Map(location=[37.5502, 126.982], zoom_start=11)

for idx, rows in crime_anal_station.iterrows():
    folium.CircleMarker(
        [rows['lat'], rows['lng']],
        radius=rows['검거'] * 50,
        popup = rows['구별'] + ' : ' + '%.2f' % rows['검거'],
        color = '#3186cc',
        fill = True,
        fill_color = '#3186cc',
    ).add_to(my_map)

my_map

 

10. 마지막으로, 구별 범죄 현황과 경찰서별 검거율을 함께 표시해보자.

my_map = folium.Map(location=[37.5502, 126.982], zoom_start=11)

folium.Choropleth(
    geo_data = geo_str,
    data = crime_anal_norm['범죄'],
    columns = [crime_anal_norm.index, crime_anal_norm['범죄']],
    fill_color='PuRd',
    key_on = 'feature.id',
    fill_opacity = 0.7,
    line_opacity = 0.2,
).add_to(my_map)

for idx, rows in crime_anal_station.iterrows():
    folium.CircleMarker(
        [rows['lat'], rows['lng']],
        radius=rows['검거'] * 50,
        popup = rows['구별'] + ' : ' + '%.2f' % rows['검거'],
        color = '#3186cc',
        fill = True,
        fill_color = '#3186cc',
    ).add_to(my_map)

my_map

강남3구는 범죄율도 높지만, 검거율도 높은 것을 알 수 있다.

 

11. 여기까지만 보면 강남3구는 범죄율도 검거율도 높다. 그런데 이런 의문을 가질 수 있다. 강남3구에는 유흥가가 밀집되어 있다. 이러한 지역적 특성이 범죄율이 높은 것과 관련이 있지는 않을까? 지역적 분석이 필요하다.

 

 

추가검증

12. 확인을 위해 아래 링크에서 발생 장소별 데이터를 받아 오자. data 디렉토리에, 파일 이름은

02. crime_in_Seoul_location.csv로 저장했다.

https://www.data.go.kr/data/15054737/fileData.do

 

경찰청 서울특별시경찰청_5대범죄 발생 장소별 현황_20221231

2022년 5대범죄(살인, 강도, 강간 및 추행, 절도, 폭력) 발생장소별 현황으로 범죄명, 발생장소, 발생건수의 현황을 나타내는 통계입니다.

www.data.go.kr

 

13. 컬럼마다 .unique()로 컬럼들을 확인한 다음, 피벗테이블을 만들어 준다.

14. 정규화를 해준 뒤, 평균치를 기록한 '종합' 컬럼을 만든다.

15. 내림차순 정렬을 해주고, 시각화를 해준다. 히트맵 그래프를 먼저 만들어주자.

crime_loc_norm_sort = crime_loc_norm.sort_values(by='종합', ascending=False)

def drawGraph():
    plt.figure(figsize=(10, 10))
    sns.heatmap(crime_loc_norm_sort, annot=True, fmt='f', linewidths=0.5, cmap='RdPu')
    plt.title('범죄와 발생 장소')
    plt.show()

drawGraph()

강도/절도/폭력은 노상에서 가장 많이 일어나고, 강간/추행은 주로 주택, 유흥업소 등에서 일어나는 등 범죄의 추세가 보인다.