Analysis Seoul Crime

J·2024년 8월 22일

범죄현황 분석 (서울시) - 재 업로드

1. 프로젝트 개요

실제 강남3구가 범죄로부터 안전하다고 말할 수 있는지 검증

  • GoogleMaps, Folium, Seaborn, Pandas의 Pivot_table 익히기
  • 데이터 출처 - 공공데이터포털

2. 데이터 개요

필요한 모듈 import

import numpy as np 
import pandas as pd 

데이터 읽기

crime_raw_data = pd.read_csv("../data/02. crime_in_Seoul.csv", thousands=",", encoding="euc-kr") # thousands 숫자값을 문자로 인식할 수 있어서 설정
crime_raw_data.head()

데이터 기본 정보

  • RangeIndex가 65534인데, 310개이다 (오류 확인)
crime_raw_data.info()

특정 column에서 uniqe 확인

ime_raw_data["죄종"].unique()

nan값 확인

crime_raw_data[crime_raw_data["죄종"].isnull()].head()

데이터 정리 (nan값 아닌 값들만 다시 저장)

crime_raw_data = crime_raw_data[crime_raw_data["죄종"].notnull()]

다시 기본 정보 확인 (index와 데이터 값 310개 동일)

crime_raw_data.info()

3. 서울시 범죄 현황 데이터 정리

	crime_raw_data.head()

Pivot 테이블로 정리

crime_station = crime_raw_data.pivot_table(
    crime_raw_data, 
    index="구분", 
    columns=["죄종", "발생검거"], 
    aggfunc=[np.sum])
crime_station.head()

현재 Multi-index로 되어있음

crime_station.columns

특정 column 값 확인

crime_station["sum", "건수", "강도", "검거"][:5]

Multi-index에서 불필요한 column 제거

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

다시 데이터 확인

crime_station.head()

현재 경찰서 이름으로 index 설정되어있음 -> 경찰서 이름으로 구를 알아내야 함

crime_station.index

4. Google Maps를 이용한 데이터 정리

import googlemaps

gmaps_key = "개인마다 다름 (unique key provied from google)"
gmaps = googlemaps.Client(key=gmaps_key)

gmaps.geocode("서울영등포경찰서", language="ko")  # 테스트 코드

tmp = gmaps.geocode("서울영등포경찰서", language="ko")
len(tmp)

데이터 형태 확인(type)

type(tmp[0].get("geometry")["location"])

해당 경찰서의 위도와 경도 확인

print(tmp[0].get("geometry")["location"]["lat"])
print(tmp[0].get("geometry")["location"]["lng"])

이름 확인

tmp[0].get("formatted_address").split()[2]

기존 데이터에 구별,위도,경도 column 추가

crime_station["구별"] = np.nan
crime_station["lat"] = np.nan
crime_station["lng"] = np.nan

crime_station.head()

iterrows() 반복문 사용해서 경찰서 이름에서 구이름, 위도, 경도 얻기

count = 0 

for idx, rows in crime_station.iterrows():
    station_name = "서울" + str(idx) + "경찰서"
    tmp = gmaps.geocode(station_name, language="ko")

    tmp[0].get("formatted_address")
    tmp_gu = tmp[0].get("formatted_address")

    lat = tmp[0].get("geometry")["location"]["lat"]
    lng = tmp[0].get("geometry")["location"]["lng"]

    crime_station.loc[idx, "lat"] = lat 
    crime_station.loc[idx, "lng"] = lng
    crime_station.loc[idx, "구별"] = tmp_gu.split()[2]

    print(count)
    count = count + 1 
    

다시 데이터 확인

crime_station.head()

특정 column 확인

crime_station.columns.get_level_values(0)[2] + crime_station.columns.get_level_values(1)[2]

0,1 column 합치기

tmp = [ 
    crime_station.columns.get_level_values(0)[n] + crime_station.columns.get_level_values(1)[n]
    for n in range(0, len(crime_station.columns.get_level_values(0)))
]
tmp

데이터에 업데이트 하기

crime_station.columns = tmp 
crime_station.head()

1차 저장

crime_station.to_csv("../data/02. crime_in_Seuol_raw.csv", sep=",", encoding="utf-8")

5. 구별 데이터로 정리

index_col "구분"을 인덱스 컬럼으로 설정

crime_anal_station = pd.read_csv("../data/02. crime_in_Seoul_raw.csv", index_col=0, encoding="utf-8") 
crime_anal_station.head()

Pivot 테이블을 이용해서 구별로 정리, func은 Sum 그리고 불필요 column 제거

crime_anal_gu = pd.pivot_table(crime_anal_station, index="구별", aggfunc=np.sum)

del crime_anal_gu["lat"]
crime_anal_gu.drop("lng", axis=1, inplace=True)

crime_anal_gu.head()

검거률 column 생성

target = ["강간검거율", "강도검거율", "살인검거율", "절도검거율", "폭력검거율"]

num = ["강간검거", "강도검거", "살인검거", "절도검거", "폭력검거"]
den = ["강간발생", "강도발생", "살인발생", "절도발생", "폭력발생"]

crime_anal_gu[target] = crime_anal_gu[num].div(crime_anal_gu[den].values) * 100 
crime_anal_gu.head()

불필요한 column 제거

del crime_anal_gu["강간검거"]
del crime_anal_gu["강도검거"]
crime_anal_gu.drop(["살인검거", "절도검거", "폭력검거"], axis=1, inplace=True)

crime_anal_gu.head()

데이터에 100보다 큰 값이 존재하기에 찾아서 100으로 바꾸기 (율이기 때문에 100을 넘길 수 없다)

crime_anal_gu[crime_anal_gu[target] > 100] = 100 
crime_anal_gu.head()

가시성을 높이기위해 column이름 짧게 줄이기

crime_anal_gu.rename(columns={"강간발생": "강간", "강도발생": "강도", "살인발생": "살인", "절도발생": "절도", "폭력발생": "폭력"},
                    inplace=True)
crime_anal_gu.head()

6. 범죄 데이터 정렬을 위한 데이터 정리

정규화에서 1은 최고 값, 0은 최소 값

# 특정 column 정규값 확인
crime_anal_gu["강도"] / crime_anal_gu["강도"].max()

모든 범죄 column의 정규화 값 계산

col = ["살인", "강도", "강간", "절도", "폭력"]
crime_anal_norm = crime_anal_gu[col] / crime_anal_gu[col].max()
crime_anal_norm.head()

정규화된 데이터 값에 위에서 만든 검거율 추가

col2 = ["강간검거율", "강도검거율", "살인검거율", "절도검거율", "폭력검거율"]
crime_anal_norm[col2] = crime_anal_gu[col2]
crime_anal_norm.head()

기존에 만들었던 "서울 구별 CCTV" 데이터 가져오기

result_CCTV = pd.read_csv("../data/01. CCTV_result.csv", index_col="구별", encoding="utf-8")
result_CCTV.head()

데이터 "서울 구별 CCTV"에서 인구수와 CCTV수 추가

crime_anal_norm[["인구수", "CCTV"]] = result_CCTV[["인구수", "소계"]]
crime_anal_norm.head()

정규화된 범죄발생 건수 전체의 평균을 구하고 범죄 column 만든 후 대표값으로 사용

col = ["강간", "강도", "살인", "절도", "폭력"]
crime_anal_norm["범죄"] = np.mean(crime_anal_norm[col], axis=1)
crime_anal_norm.head()

검거율 전체 평균을 구하고 검거 column 만든 후 대표값으로 사용

col = ["강간검거율", "강도검거율", "살인검거율", "절도검거율", "폭력검거율"]
crime_anal_norm["검거"] = np.mean(crime_anal_norm[col], axis=1) # axis=1 행을 따라서 연산하는 옵션 
crime_anal_norm.head()

전체 데이터 확인

crime_anal_norm

중간 저장

crime_anal_norm.to_csv("../data/02. crime_in_Seoul_final.csv", sep=",", encoding="utf-8")

7. 서울시 범죄현황 데이터 시각화

Seaborn

  • 앞서 사용한 matplotlib과 함께 사용 -> 그래프의 기능 강화
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns 
from matplotlib import rc 
rc("font", family="Malgun Gothic")

pairplot 사용 (강도, 살인, 폭력에 대한 상관관계 확인)

sns.pairplot(data=crime_anal_norm, vars=["살인", "강도", "폭력"], kind="reg", height=3);

  • 해석1-1 : 폭력 사건이 살인으로 이어지는 경우가 강도 사건이 살인으로 이어지는 것 보다 더 많다.

  • 해석2-1 : 강도와 폭력은 상관관계가 매우 높다.

pairplot 사용 (인구수, CCTV와 살인, 강도의 상관관계 확인)

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

  • 해석1-1 : 인구수가 증가하는 것에 비해 강도가 많이 증가한다고 볼 수는 없다. (아웃라이어도 존재하며, 이를 제외하면 증가폭은 더욱 줄어들 것이다)

  • 해석1-2 : 강남3구가 인구수가 많은 곳이라면, 강도 발생 비율이 낮으니까 안전하다고 느낄 수 있지 않을까?

  • 해석2-1 : 인구수가 증가함에 따라 살인은 증가하는 경향을 보인다.

  • 해석3-1 : CCTV가 많이 설치되어있을 수록 강도 사건이 많이 일어난다? 이는 해석의 오류. 그렇다면 CCTV가 많아서 강도사건이 많이 발생하니까, CCTV를 줄여야한다 라고 연결될 수 있다.

  • 해석3-2 : 강도 사건이 많이 발생하는 곳에 CCTV를 많이 설치한 것일 수도 있다.

  • 해석3-3 : 아웃라이어를 제외하면, 회귀선이 조금 더 내려가서 해석을 달리 할 수 있는 여지가 있다.

pairplot 사용 (인구수, CCTV와 살인검거율, 폭력검거율의 상관관계 확인)

  • 인구수, CCTV as x
  • 살인검거율, 폭력검거율 as y
def drawGraph():
    sns.pairplot(
        data=crime_anal_norm, 
        x_vars=["인구수", "CCTV"],
        y_vars=["살인검거율", "폭력검거율"],
        kind="reg",
        height=4
    )
    plt.show()
drawGraph()
  • 해석1-1 : 인구수가 증가할 수록 폭력검거율이 떨어진다.

  • 해석2-1 : 인구수와 살인검거율은 조금 높아지는 것 같은 느낌?

  • 해석3-1 : CCTV와 살인검거율은 해석하기 애매(100에 모여있는 이유는, 검거율은 100으로 제한했기 때문)

  • 해석4-1 : CCTV가 증가할수록 폭력검거율이 약간 하향세를 보인다.

pairplot 사용 (인구수, CCTV와 절도검거율, 강도검거율의 상관관계 확인)

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

  • 해석1-1 : CCTV가 증가할수록 절도검거율이 감소하고 있다.
  • 해석2-1 : CCTV가 증가할수록 강도검거율은 증가하고 있다.

heatmap 사용 (검거율) - 검거 column을 기준으로 정렬

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, # 간격설정 
        cmap="RdPu",
    )
    plt.title("범죄 검거 비율(정규화된 검거의 합으로 정렬")
    plt.show()

    drawGraph()

  • 해석1-1 : 검거율이 높은 곳을 보면, 강남3구가 없다.

heatmap 사용 (범죄 건수) - 범죄 column을 기준으로 정렬

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

  • 해석1-1 : 강남구는 살인을 제외하면, 전부 1등

  • 해석1-2 : 서초구도 상위권에 속함

  • 해석1-3 : 강남 3구의 범죄 발생 건수가 결코 낮지 않다

  • 해석2-1 : 검거율은 낮은데, 범죄 발생 비율이 높다.

  • 해석3-1 : 강남 송파 서초구가 과연 안전할까? 라는 의문을 계속 가질 수 있음

데이터 저장

crime_anal_norm.to_csv("../data/02. crime_in_Seoul_final.csv", sep=",", encoding="utf-8")

8.지도 시각화

Folium을 사용한 지도 시각화

  • 기존 데이터 파일 호출 및 folium사용 위한 기본 설정
import folium
import pandas as pd 
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"))

(1) 살인 사건

 my_map = folium.Map(location=[37.5502, 126.982], zoom_start=10, tiles='CartoDB Voyager')

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)

  • 해석1-1 : 영등포구에서 살인 사건이 제일 많이 일어났다.

(2) 성범죄

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,
    legend_name="정규화된 강간 발생 건수",).add_to(my_map)

  • 해석1-1 : 강남3구 중 2개 구가 포함되어 있다.

(3) 5대 범죄

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

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)

  • 해석1-1 : 강남구가 역시 포함되어 있다.

(4) 인구수 대비 범죄 발생 건수

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

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

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)
    

  • 해석1-1 : 종로구가 가장 인구수 대비 범죄 발생이 많다.

구별 범죄 현황과 경찰서별 검거에 대한 지도 시각화

경찰서별 정보를 가지고 범죄발생과 함께 정리

데이터 확인

import numpy as np

crime_anal_station = pd.read_csv(
    "../data/02. crime_in_Seoul_1st.csv", index_col=0, encoding="utf-8"
)
col = ["살인검거", "강도검거", "강간검거", "절도검거", "폭력검거"]
tmp = crime_anal_station[col] / crime_anal_station[col].max()
crime_anal_station["검거"] = np.mean(tmp, axis=1)
crime_anal_station.head()

경찰서 위치 지도에 표시

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

검거에 적절한 값은 곱해서 원의 넓이로 사용

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

구별 범죄 현황과 경찰서별 검거율을 함께 표시

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)

9.추가 검증

  • 강남의 범죄 발생이 많은 것은 유흥업소 밀집과 관련이 있을까

발생 장소별 데이터 확인

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

중복되지 않은 범죄명 column 값 확인

crime_loc_raw["범죄명"].unique()

중복되지 않은 장소 column 값 확인

crime_loc_raw["장소"].unique()

장소와 범죄종류로 pivot table 생성

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

정규화 값으로 변환

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

종합 column 생성 후 전체의 mean값 등록

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

Heatmap으로 시각화

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

  • 해석1-1 : 기타장소에서 여러 종류의 범죄가 가장 많이 일어났고 노상이 그 다음으로 많았다.
profile
Full of adventure

0개의 댓글