[EDA] 02. 서울시 범죄 현황 분석 18~32

쩡이·2023년 8월 6일

EDA

목록 보기
5/12

9. 데이터 시각화

import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib import rc

plt.rcParams["axes.unicode_minus"] = False
get_ipython().run_line_magic("matplotlib", "inline")
rc("font", family="Malgun Gothic")

pairplot

# pairplot를 이용해서 강도, 살인, 폭력에 대한 상관관계 확인

sns.pairplot(data=crime_anal_norm, vars=["살인", "강도", "폭력"], 
kind="reg", height=3); #kind에는 reg, ked(지형도), hist(히스토그램), scatter(산점도)등이 있음 


살인사건과 강도사건이 증가하는 정도보다 살인사건과 폭력사건이 증가하는 정도가 더 높다. 강도사건이 살인사건으로 연결되는 것보다 폭력사건이 살인사건으로 연결되는 경우가 더 많다고 볼 수 있다.


# "인구수","CCTV"와 "살인","강도"의 상관관계 확인

def drawGraph():
    sns.pairplot(data=crime_anal_norm, 
                 x_vars=["인구수", "CCTV"],
                y_vars=["살인", "강도"],
                kind="reg",
                height=4
                )
    plt.show()
drawGraph()


# "인구수", "CCTV"와 "살인검거율", "폭력검거율"의 상관관계 확인

def drawGraph():
    sns.pairplot(data=crime_anal_norm, 
                 x_vars=["인구수", "CCTV"],
                y_vars=["살인검거율", "폭력검거율"],
                kind="reg",
                height=4
                )
    plt.show()
drawGraph()

인구수가 증가함에 따라 폭력검거율이 떨어진다.


# "인구수", "CCTV"와 "절도검거율", "강도검거율"의 상관관계 확인

def drawGraph():
    sns.pairplot(data=crime_anal_norm, 
                 x_vars=["인구수", "CCTV"],
                y_vars=["절도검거율", "강도검거율"],
                kind="reg",
                height=4
                )
    plt.show()
drawGraph()

CCTV가 증가함에 따라 절도검거율은 감소하고 있다.


heatmap

# 검거율 heatmap
#"검거" 컬럼을 기준으로 정렬

def drawGraph():
    
    #데이터 프레임 생성
    target_col = ["강간검거율", "강도검거율", "살인검거율", "절도검거율", "폭력검거율", "검거"]
    crime_anal_norm_sort = crime_anal_norm.sort_values(by="검거", ascending=False) #내림차순
    
    #그래프 설정
    plt.figure(figsize=(10,10))
    sns.heatmap(
        data=crime_anal_norm_sort[target_col],
        annot=True, #데이터값 표현
        fmt="f", #d:정수. f:실수
        linewidths=0.5, #간격설정, 0이 디폴트값
        cmap="RdPu"
    )
    plt.title("범죄 검거 비율(정규화된 검거의 합으로 정렬)")
    plt.show()
    
drawGraph()

종로구를 제외하면 대부분 높은 검거율을 가지고 있는 것 같다


#범죄 발생 건수 heatmap
#"범죄" 컬럼을 기준으로 정렬

def drawGraph():
    
    #데이터 프레임 생성
    target_col = ["살인", "강도", "강간", "절도", "폭력", "범죄"]
    crime_anal_norm_sort = crime_anal_norm.sort_values(by="범죄", ascending=False) #내림차순
    
    #그래프 설정
    plt.figure(figsize=(10,10))
    sns.heatmap(
        data=crime_anal_norm_sort[target_col],
        annot=True, #데이터 값 표현
        fmt="f", #실수 값으로 표현
        linewidths=0.5, #간격설정
        cmap="RdPu"
    )
    plt.title("범죄 비율(정규화된 발생 건수로 정렬)")
    plt.show()
drawGraph()

강남 3구의 범죄 발생 건수는 낮지 않고, 범죄 검거율도 높지 않다. 강남3구는 안전한가?에 대한 답을 하려면 인구대비현황을 고려해봐야 할 것 같다.


10. 지도 시각화

Folium

  • !pip install folium 입력하여 설치
  • 크롬에서 잘 작동함
  • 지도를 html로 저장 가능 m.save("파일경로/파일명.html")
  • 스타일을 tiles 옵션으로 지정
  • 마커 추가 가능
  • 다양한 모양의 아이콘 지원
  • 지도에 원모양 표시 가능 folium.Circle(), folium.CircleMarker()
  • 지도에 colormap 표현
  • json 파일을 이용해서 경계선과 id를 각 지역에 구현

folium.Map()

  • 형태
    m = folium.Map(location=[위도, 경도], zoom_start=15) 
    #zoom_start : 지도를 확대해서 출력 옵션이며 필수는 아님, 0 ~ 18 범위로 설정가능
    #[위도, 경도]대신 (위도, 경도)도 가능
  • 지도 스타일(tiles option)
- "OpenStreetMap"
- "Mapbox Bright" (Limited levels of zoom for free tiles)
- "Mapbox Control Room" (Limited levels of zoom for free tiles)
- "Stamen" (Terrain, Toner, and Watercolor)
- "Cloudmade" (Must pass API key)
- "Mapbox" (Must pass API key)
- "CartoDB" (positron and dark_matter)
m = folium.Map(
    location=[37.5118, 127.097], 
    zoom_start=15, # 0~18 사이 값
    tiles="OpenStreetMap"  #openstreetmap 디폴트 값
)
m

folium.Marker()

지도에 마커 생성

  • 형태
folium.Marker(
	location=[위도, 경도]
    popup="str"
    tooltip="str"
).add_to(변수)
변수 = folium.Map(
    location=[37.5118, 127.097], #롯데월드
    zoom_start=15, # 0~18 사이 값
    tiles="OpenStreetMap" #지도 스타일 지정
)

#뚝섬역
folium.Marker( (37.5487, 127.0445)).add_to(m) 

#popup str타입을 받고, 마커를 클릭하면 나오는 것, HTML문법 따름
#tooltip 마커에 마우스커서를 가져가면 나오는 것, HTML문법 따름
folium.Marker(
             location=[37.5118, 127.097],
             popup="<b>amusementPark</b>", #<b> 볼드체
             tooltip="<i>롯데월드</i>" #<i> 필기체
).add_to(m) 

#html문법 이용해서 링크연결
folium.Marker(
             location=[37.546, 127.0572],
             popup="<a href='https://zero-base.co.kr/'target=_blink'>제로베이스</a>", #클릭하면 링크로 이동
             tooltip="<i>Zerobase</i>"
).add_to(m) 

m


folium.icon()

https://fontawesome.com/search?m=free&o=r fa
https://getbootstrap.com/docs/3.3/components/ #glyphicon

형태

folium.Marker(
	location=[위도, 경도]
    icon=folium.Icon(
        color="컬러명",  #마커 컬러
        icon_color="white", #마커 안에 아이콘 컬러
        icon="apple", #아이콘 모양
        angle=50, #아이콘 기울기
        prefix="fa" #아이콘 출처, 아이콘이 뜨지 않을 경우 #을 넣어줘야함
        )
       ).add_to(변수)
m = folium.Map(
    location=[37.5118, 127.097], #롯데월드
    zoom_start=15, # 0~18 사이 값
    tiles="OpenStreetMap" #tiles : 지도 스타일 지정, openstreetmap이 디폴트 값
)

#icon basic
folium.Marker( (37.5487, 127.0445), 
              icon=folium.Icon(color = "black", info="info-sign") 
              #color 디폴트값 블루, info가 i로 마커에 찍힘
             ).add_to(m) 

#icon icon_color
folium.Marker(
             location=[37.5118, 127.097],
             popup="<b>amusementPark</b>",
             tooltip="icon color", 
             icon=folium.Icon(
                 color="red", #마커 색깔
                 icon_color="blue", #아이콘 색깔
                 icon="star") #아이콘 모양
             
).add_to(m)

#icon custom
folium.Marker(
    location=[37.5406, 127.0697], #건대입구역
    popup="건대입구역",
    tooltip="Icon custom",
    icon=folium.Icon(
        color="purple", #마커 색깔
        icon_color="white", #아이콘 색깔
        icon="apple", #아이콘 모양
        angle=50, #아이콘 기울기 
        prefix="fa")  #아이콘 참조 #fa or glyphicon
).add_to(m)
m


folium.ClikForMarker()

지도위에 마우스로 클릭했을 때 마커 생성

형태

변수 = folium.Map()
변수.add_child(folium.ClickForMarker(popup="ClickForMarker")) 
#옵션은 popup하나만 있음
#popup 넣지 않으면 위도,경도 값을 반환
m = folium.Map(
    location=[37.5118, 127.097], #롯데월드
    zoom_start=15, # 0 ~ 18
    tiles="OpenStreetMap" #tiles : 지도 스타일 지정
)
m.add_child(folium.ClickForMarker(popup="ClickForMarker")) 

folium.LatLngPopup()

지도를 마우스로 클릭했을 때 위도 경도 정보를 반환

형태

변수 = folium.Map()
변수.add_child(folium.LatLngPopup())
#넣을 옵션 없음
m = folium.Map(
   location=[37.5118, 127.097], #롯데월드
   zoom_start=15, # 0 ~ 18
   tiles="OpenStreetMap" #tiles : 지도 스타일 지정
) 
m.add_child(folium.LatLngPopup())


folium.Circle(), folium.CircleMarker()

원 모양 생성

형태

#Circle
folium.Circle(
    location=[위도, 경도]
    radius= 반지름,
    fill=True, #원을 채우는 옵션, 디폴트값은 False
    color="컬러값", #원 테두리 색
    fill_color="컬러명", #원을 채우는 색
).add_to(변수)

#CircleMarker #circle과 독스트링 동일함
folium.CircleMarker(
    location=[위도, 경도]
    radius= 반지름,
    fill=True, #원을 채우는 옵션, 디폴트값은 False
    color="컬러값", #원 테두리 색
    fill_color="컬러명", #원을 채우는 색
).add_to(m)
m = folium.Map(
    location=[37.5509, 127.0445], 
    zoom_start=15, # 0 ~ 18
    tiles="OpenStreetMap" #tiles : 지도 스타일 지정
)
m.add_child(folium.ClickForMarker(popup="ClickForMarker"))

#Circle
folium.Circle(
    location=[37.5556, 127.0437], #한양대학교
    radius=100,
    fill=True, #원을 채우는 옵션, 디폴트값은 False
    color="#e6d00b", #원 테두리 색
    fill_color="red", #원을 채우는 색
    popup="Circle Popup",
    tooltip="Circle tooltip"
).add_to(m)

#CircleMarker #circle과 독스트링 동일함
folium.CircleMarker(
    location=[37.5434, 127.0448], #서울숲
    radius=100, # 같은 값이라도 크기가 차이날 수 있음
    fill=True, #원을 채우는 옵션, 디폴트값 False
    color="#0be6c5", #원 테두리 색
    fill_color="#e60b66", #원을 채우는 색
    popup="CircleMarker Popup",
    tooltip="CircleMarker Tooltip"
).add_to(m)
m


folium.Choropleth()

위도,경도 정보로 그래프를 그린다.

형태

#데이터를 불러와서 개요 파악
import json
state_data = pd.read_csv("../data/02. US_Unemployment_Oct2012.csv")
state_data.tail(2)


folium.Choropleth(
	geo_data: string/object
    URL, file path, or data (json, dict, geopandas, etc) to your GeoJSON
    geometries
    
	data: Pandas DataFrame or Series, default None
    Data to bind to the GeoJSON.
    
	columns: dict or tuple

	key_on: string
    Ex: 'feature.id' or 'feature.properties.statename'.
	
    #아래부터는 꾸미는 옵션, 선택사항
	fill_color: string, optional
    Area fill color, defaults to blue. 

	fill_opacity: float, default 0.6, range 0-1

	line_opacity: float, default 1
    GeoJSON geopath line opacity, range 0-1.
    
	legend_name: string, default empty string
    Title for data legend.
)
    
m = folium.Map([43, -102], zoom_start=3) #밑바탕 지도

folium.Choropleth(
    geo_data="../data/02. us-states.json", # 경계선 좌표값이 담긴 데이터
    data=state_data, #Series or DataFrame 
    columns=["State", "Unemployment"], #DataFrame columns
    key_on="feature.id",
    #아래부터는 꾸미는 정보, 필수는 아님
    fill_opacity=1, #0~1 #채움 투명도
    line_opacity=1, #0~1 #선 투명도
    legend_name="Unemployment rate(%)" #제목
).add_to(m) #밑바탕 지도에 경계값에 따른 지도를 그린다
m


예제) 아파트 유형별로 지도 시각화 - 공공데이터포털 자료

import pandas as pd
df = pd.read_csv("../data/02. 서울특별시 동작구_주택유형별 위치 정보 및 세대수 현황_20210825.csv", encoding="cp949") 
#cp949는 euc-kr보다 업그레이드 된 버전
df.tail(2)

#데이터를 개요확인
df.info()

#NaN 데이터 제거
df = df.dropna()
df.info()

#drop=True: 인덱스가 컬럼으로 들어가지 않게 막아주는 옵션
df = df.reset_index(drop=True) 
df.tail(2)


# folium
#위도,경도로 지도 시각화
m = folium.Map(location=[37.5059, 126.9924], zoom_start=13)
m

for idx,rows in df.iterrows(): #df의 각 행마다 데이터를 받을 수 있음

    #location
    lat, lng = rows.위도, rows.경도
    
    #Marker
    folium.Marker(
        location=[lat, lng],
        popup=rows.주소,
        tooltip=rows.분류,
        icon=folium.Icon(
            icon="home",
            color="lightred" if rows.세대수 >= 199 else "lightblue", #세대수의 50% 되는 값이 199
            icon_color="darked" if rows.세대수 >= 199 else "darkblue")
    ).add_to(m)
    
    #Circle
    folium.Circle(
        location=[lat, lng],
        radius=rows.세대수 * 0.5,
        fill=True,
        color="pink" if rows.세대수 >=518 else "green",
        fill_color="pink" if rows.세대수 >=518 else "green",
    ).add_to(m)
m



10. 서울시 범죄 현황에 대한 지도 시각화

서울시 구별 경계선이 그려져 있는 json 파일이 필요하다.

import json
import folium
import pandas as pd

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"))


# 살인발생 건수 지도 시각화
my_map = folium.Map(
    location=[37.5502, 126.982],
    zoom_start=11,
    tiles="Stamen Toner"
)

folium.Choropleth(
    geo_data=geo_str, #우리나라 경계선 좌표값이 담긴 데이터
    data=crime_anal_norm["살인"],
    columns=[crime_anal_norm.index, crime_anal_norm["살인"]],
    key_on="feature.id", #데이터와 컬럼을 키로 묶어줌
    fill_color="PuRd",
    fill_opacity=0.7,
    line_opacity=0.2,
    legend_name="정규화된 살인 발생 건수"
).add_to(my_map)
my_map

위 코드에서 컬럼명만 바꿔서 그린 그래프

  1. 살인발생 건수

  2. 성범죄

  3. 5대 범죄 발생

강남3구가 범죄 발생 건수는 많다.
하지만 발생 건수에 영향을 미치는 요인이 다양하기 때문에
다른 요인들을 고려해서 살펴봐야할 것 같다.


# 인구 대비 범죄 발생 건수 

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


my_map = folium.Map(
    location=[37.5502, 126.982],
    zoom_start=11,
    tiles="Stamen Toner"

)

folium.Choropleth(
    geo_data=geo_str, #우리나라 경계선 좌표값이 담긴 데이터
    data=tmp_criminal,
    columns=[crime_anal_norm.index, tmp_criminal],
    key_on="feature.id", #데이터와 컬럼을 키로 묶어줌
    fill_color="PuRd",
    fill_opacity=0.7,
    line_opacity=0.2,
    legend_name="인구 대비 범죄 발생 건수 "
).add_to(my_map)
my_map

인구대비 범죄 발생 건수가 많은 지역은 종로구,중구이다.
강남3구가 발생 건수 자체는 많지만, 인구대비 발생 건수는 결코 높지 않다. 발생 건수가 높은 것은 인구수가 많기 때문이 아닐까?


경찰서별 검거에 대한 지도 시각화

# 경찰서명 정보를 범죄발생과 함께 정리
crime_anal_station = pd.read_csv("../data/02. crime_in_Seoul_raw.csv", encoding="utf-8")
col = ["살인검거", "강도검거", "강간검거", "절도검거", "폭력검거"]

#정규화 : 최댓값으로 나누어 주면 값들이 0 ~ 1로 바뀐다
tmp = crime_anal_station[col] / crime_anal_station[col].max() 
crime_anal_station["검거"] = np.mean(tmp, axis=1) #numpy axis=1 행(가로), pandas axis=1 열(세로)

# 경찰서 위치 마커 표시
my_map = folium.Map(
    location=[37.5502, 126.982], zoom_start=11
)

for idx,rows in crime_anal_station.iterrows(): #한 행씩 출력됨
    folium.Marker(
        location=[rows["lat"], rows["lng"]]
    ).add_to(my_map)

my_map

# 검거에 값을 곱한 뒤 원의 넓이 적용

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["범죄"]],
    key_on="feature.id",
    fill_color="PuRd",
    fill_opacity=0.7,
    line_opacity=0.2
).add_to(my_map)

for idx,rows in crime_anal_station.iterrows(): #한 행씩 출력됨
    folium.CircleMarker(
        location=[rows["lat"], rows["lng"]],
        radius=rows["검거"] * 50,
        popup=rows["구분"] + ":" + "%.2f" % rows["검거"], #강남 : 0.75 형태로 나옴
        color="#3186cc",
        fill=True,
        fill_color="#3186cc"
    ).add_to(my_map)

my_map

강남3구는 범죄 건수가 많지만, 검거율은 높은 편인 것 같다.


11. 서울시 범죄 현황 발생 장소 분석

강남3구에 범죄 발생 건수가 높은 이유는 유흥업소가 많기 때문이 아닐까? 장소에 따른 범죄 발생율을 알아보자

# 추가 검증

crime_loc_raw = pd.read_csv("../data/02. crime_in_Seoul_location.csv", thousands=",", encoding="euc-kr")

crime_loc_raw.info()
crime_loc_raw.범죄명.unique()
crime_loc_raw["장소"].unique()

crime_loc = crime_loc_raw.pivot_table(
    crime_loc_raw, index="장소", columns="범죄명", aggfunc=[np.sum]
)

crime_loc.columns = crime_loc.columns.droplevel([0,1])

col = ["살인", "강도", "강간", "절도", "폭력"]
#정규화
crime_loc_norm = crime_loc / crime_loc.max() 

crime_loc_norm["종합"] = np.mean(crime_loc_norm, axis=1)

#그래프 그리기
import matplotlib.pyplot as plt
import seaborn
from matplotlib import rc

rc("font", family="Malgun Gothic")
%matplotlib inline

crime_loc_norm_sort = crime_loc_norm.sort_values("종합", 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()

종합적으로 봤을 때, 기타장소를 제외하고 노상에서 가장 많은 범죄가 발생했다. 유흥업소에서는 생각만큼 범죄 발생율이 높지 않았다. 이 자료만으로 유흥업소가 많아서 범죄가 많이 발생한다는 결론을 내리기는 어려울 것 같다. 강남 3구에서 범죄가 발생한 장소만 있는 데이터로 분석을 하면 좋을 것 같다.

0개의 댓글