EDA-서울시 범죄 현황 데이터 분석2

BlackLabel·2023년 10월 18일
0

EDA, 크롤링 기초 

목록 보기
6/13

서울시 범죄현황 데이터 최종 정리

  • 범죄의 경중에 따라 발생 건수의 차이가 크다
  • 살인이 한자리 발생일 때, 절도는 네자리 수 발생
  • 이런 현상 때문에 정규화를 진행( 0 ~ 1의 값으로 정리)
    -> 각 값을 해당 열의 최대값으로 나누어 준다

정규화

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

  • 정규화
  • 최고값을 1로 두고, 최소값을 0으로
col2 = ['강간검거율','강도검거율','살인검거율','절도검거율','폭력검거율']
# 각 값을 최대값으로 나누어 저장
crime_anal_norm[col2] = crime_anal_gu[col2]
crime_anal_norm.head()

데이터 자료 추가

  • CCTV수와 인구수 추가
result_CCTV = pd.read_csv('../data/CCTV파일',encoding='utf-8', index_col='구별')

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

crime_anal_norm['범죄'] = np.mean(crime_anal_norm[col],axis=1)
crime_anal_norm.head()

  • 정규화된 범죄발생 건수 전체의 평균을 구해서 범죄의 대표값으로 사용하자
    범죄 : 해당 행의 각 범죄 값의 평균
    검거 : 해당 행의 각 검거율의 평균
  • np.mean : 평균을 구하는 함수, axis에 따라 행(1), 열(0)로 계산
    pandas와 달리 numpy의 axis는 0이 열 1이 행

seaborn

  • seaborn : 시각화 라이브러리
    matplotlib을 기반으로 동작한다.
  • 기본 설정
    set_style() : white, dark, whitegrid, darkgrid, ticks
    despine() : 그래프의 왼쪽과 아래쪽만 선을 그리는 스타일
    offset : x축, y축 사이의 거리 조절
import seaborn as sns
  • 통상적으로 sns로 import 한다
  • import 하는 것만으로도 뭔가 효과를 준다
x = np.linspace(0,14,100)
y1 = np.sin(x)
y2 = 2 * np.sin(x + 0.5)
y3 = 3 * np.sin(x + 1.0)
y4 = 4 * np.sin(x + 1.5)

plt.figure(figsize=(10,6))
plt.plot(x,y1,x,y2,x,y3,x,y4)
plt.show()

sns.set_style('white')
plt.figure(figsize=(10,6))
plt.plot(x,y1,x,y2,x,y3,x,y4)
sns.despine()
plt.show()

  • style을 white로 하면 뒤에 바탕이 하얀색이 되고 grid가 없어진다
  • despine 옵션은 테두리가 왼쪽과 아랫쪽만 남는다

  • sns.despine(offset=10)
  • despine에 offset효과를 주면 그림처럼 x축과 y축이 떨어진다

실습용 데이터 tips

  • seaborn에는 실습용 데이터가 몇 개 내장되어 있다
  • tips는 그 중 하나
tips = sns.load_dataset('tips')
tips.head()

boxplot

  • boxplot : 0분위(0%), 1분위(25%), 2분위(50%), 3분위(75%), 4분위(100%)의 값을 박스 형태로 표현
plt.figure(figsize=(8,6))
sns.boxplot(x=tips['total_bill'])
plt.show()

  • 박스 밖에 2개 선안에 모든 데이터가 모여있다
  • 박스 안에 절반의 데이터가 모여있다
  • 박스 안에 선이 중간값이다
plt.figure(figsize=(8,6))
sns.boxplot(x='days', y='total_bill',data=tips)

plt.show()

  • boxplot에 컬럼을 지정

  • 컬럼 지정
    x : x축 지정
    y : y축 지정
    data : 데이터 지정
    hue : 값을 구분할 기준
    palette : seaborn이 제공하는 색상 선택(Set1~3)

plt.figure(figsize=(8,6))
sns.boxplot(x='days', y='total_bill',hue='smoker',data=tips,palette='Set3')
plt.show()

  • 컬럼을 지정하고 구분을 지을 수 있다
  • 'smoker'를 기준으로 구분지어서 그래프 출력
  • palette는 색상

swarmplot

  • swarmplot : 범주별 분포를 그리며 데이터의 분산까지 고려하여, 데이터 포인트가 서로 중복되지 않도록 그린다. 즉, 데이터가 퍼져 있는 정도를 입체적으로 볼 수 있다.
plt.figure(figsize=(8,6))
# color : 0 ~ 1(검정색 ~ 흰색)
sns.swarmplot(x='day',y='total_bill', data=tips, color='.5')
plt.show()

boxplot과 swarmplot 콜라보

plt.figure(figsize=(8,6))
sns.boxplot(x='day',y='total_bill',data=tips)
sns.swamplot(x='day',y='total_bill',data=tips, color='.25')
plt.show()

total bill과 tip 사이의 관계 파악

sns.set_style('darkgrid')
sns.lmplot(x='total_bill', y='tip', data=tips, size=7)
plt.show()

  • 기본적으로 scatter
  • 흐린 영역이 좁을수록 강한 상관관계를 가지고 있다

  • lmplot : 각 데이터의 분포와 회귀선을 그린다.
  • hue 옵션 사용
  • hue='smoker' 추가
  • smoker를 기준으로 구분지어서 그래프 출력

실습용 데이터 flights

flights = sns.load_dataset('flights')
flights.head()

flights = fligths.pivot('month','year','passengers')
flights.head()

  • pivot옵션을 사용할 수 있다

heatmap

  • heatmap : 열분포 형태와 같은 시각화 도구
    annot : 값 표시 True, 값 미표시 False
    fmt : 자료형 결정(d - 정수, f - 실수)
    cmap : 컬러맵 설정
plt.figure(figsize=(10,8))
sns.heatmap(flights, annot=True, fmt='d')
plt.show()

  • annot 옵션을 True로 하면 히트맵안에 값을 넣어준다
  • fmt 옵션은 데이터 타입을 정할 수 있다. ex) 'd'는 정수

  • cmap='YLGnBu' 추가
  • colormap을 조금 다르게
  • 색상표가 따로 있다

실습용 데이터 iris

sns.set(style='ticks')
iris = sns.load_dataset('iris')
iris.head(10)

pairplot

  • pairplot : 다수의 컬럼을 비교하여 그래프로 시각화
sns.pairplot(iris)
plt.show()

  • 특성 별로 상관관계를 바로 파악할 수 있다

  • 원하는 컬럼만 비교
    x_var : x축으로 사용할 컬럼
    y_var : y축으로 사용할 컬럼
  • pairplot에서도 hue 옵션
  • hue='species' 추가
  • setosa는 확실히 분류가 되는 걸로 보인다
sns.pairplot(
	iris, x_vars=['sepal_width','sepal_length'], y_vars=['petal_width', 'petal_length'])
plt.show()

  • 원하는 컬럼만 pairplot

실습용 데이터 anscombe

anscombe = sns.load_dataset('anscombe')
anscombe.head()

sns.set_style('darkgrid')

sns.lmplot(x='x', y='y', data=anscombe.query("dataset == 'I'"), ci=None, size=7)
plt.show()

  • dataset 컬럼에서 'I'라고 되어 있는 것만 가져와라
  • ci=None 옵션은 흐린 구역을 꺼버려라는 옵션
sns.lmplot(
	x='x',
    y='y',
    data=anscombe.query("dataset == 'I'"),
    ci=None,
    scatter_kws={'s':80},
    size=7
)
plt.show()

  • scatter_kws : scatter 마커 사이즈 변경
sns.lmplot(
	x='x',
    y='y',
    data=anscombe.query("dataset == 'II'"),
    order=1,
    ci=None,
    scatter_kws={'s':80},
    size=7
)
plt.show()

  • dataset이 'II'인 것들만 가져와라
  • 2차식이고 직선과 따로 논다
sns.lmplot(
	x='x',
    y='y',
    data=anscombe.query("dataset == 'II'"),
    order=2,
    ci=None,
    scatter_kws={'s':80},
    size=7
)
plt.show()

  • order를 증가시키면 2차식에 맞게끔 직선을 그려준다
sns.lmplot(
	x='x',
    y='y',
    data=anscombe.query("dataset == 'III'"),
    ci=None,
    scatter_kws={'s':80},
    size=7
)
plt.show()

  • 대부분 직선인데 데이터 하나가 삐져나왔다
  • 이걸 가지고 직선을 만들면 그래프처럼 된다
sns.lmplot(
	x='x',
    y='y',
    data=anscombe.query("dataset == 'III'"),
    robust=True,
    ci=None,
    scatter_kws={'s':80},
    size=7
)
plt.show()

  • robust=True : 굉장히 벗어난 데이터는 없는 셈 칠 수 있다

범죄현황데이터 시각화

강도, 살인, 폭력에 대한 상관관계

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

  • 강도사건이 살인사건으로 이어지는 것보다 폭력사건이 살인사건으로 이어지는 경우가 더 많다고 볼 수 있다
  • 강도와 폭력은 굉장히 상관관계가 높다 볼 수 있다
  • kind = 'reg' : 회귀분석 옵션 중 하나로, his, kde,scatter 가 옵션으로 있다.

상관관계 확인하기

def drawPlot():
	sns.pairplot(
    	crime_anal_norm, x_vars=['인구수','CCTV'], y_vars=['살인','강도'], 
        kind='reg',
        size=4
        )
  	plt.show()
drawPlot()

  • 인구수, CCTV와 살인/강도 발생과의 관계
  • 인구수가 증가한다고 강도가 증가한다고 보기 어렵다
  • 인구수가 증가함에 따라 살인사건도 조금 증가한다
  • 강도사건이 많이 발생하니까 CCTV를 늘렸다고 봐야 맞다
def drawPlot():
	sns.pairplot(
    sns.pairplot(
    	crime_anal_norm,
        x_vars=['인구수','CCTV'],
        y_vars=['살인검거율','폭력검거율'],
        kind='reg',
        size=4
    )
   	plt.show()
drawPlot()

  • 인구수, CCTV와 살인/폭력 검거율의 관계
  • 인구수가 증가할수록 폭력검거율이 떨어진다
def drawPlot():
	sns.pairplot(
    sns.pairplot(
    	crime_anal_norm,
        x_vars=['인구수','CCTV'],
        y_vars=['절도검거율','강도검거율'],
        kind='reg',
        size=4
    )
   	plt.show()
drawPlot()

  • 인구수, CCTV와 절도/강도 검거율의 관계

검거율 heatmap

def drawGraph():
	target_col = ['강간검거율','강도검거율','살인검거율','절도검거율','폭력검거율','범죄']
    
    crime_anal_norm_sort = crime_anal_norm.sort_values(by='검거', ascending=False)
    
    plt.figure(figsize=(10,10))
    sns.heatmap(
    	crime_anal_norm_sort[target_col],
        annot=True,
        fmt='f',
        linewidths='0.5
        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(
    	crime_anal_norm_sort[target_col],
        annot=True,
        fmt='f',
        linewidths='0.5
        cmap='RdPu'
    )
    plt.title('범죄비율(정규화된 발생 건수로 정렬)')
    plt.show()
drawGraph()

  • 범죄발생 건수로 heatmap
  • 대표값인 범죄를 기준으로 정렬

Folium 지도 시각화

Folium

  • folium : 지도 시각화 라이브러리
    Chrome에서 가장 동작이 좋다

기본적인 명령

  • Map(location=[위도, 경도], Map(location=(위도, 경도)
  • tiles : 스타일 설정(OpenStreetMap, Stamen Terrain/Toner/Watercolor, CartoDB positron, CartoDB dark_matter 등)
  • zoom_start : 줌 설정(0~18)
  • save : 지도를 html 형식으로 저장
  • Marker : 지도에 마커 추가
  • popup, tooltip : 마커 클릭 또는 커서 이동 시 문구 출력(html 문법 사용 가능)
  • icon : 다양한 모양의 아이콘 지원
  • add_to : Map을 매개변수로 받아 해당 지도에 마커 추가

conda install -c conda-forge folium

import folium

folium 연습

1

m =folium.Map(location=[위도, 경도])
m

  • 위도와 경도를 알려주면 된다
m.save('../data/index.html')
  • 지도를 html로 저장 가능

2

folium.Map(location=[위도,경도], tiles='Stamen Toner', zoom_start=13)

  • tiles 옵션 : 지도 스타일 옵션

3

my_map = folium.Map(location=[위도, 경도], zoom_start=12, tiles='Stamen Terrain')

folium.Marker([마커찍고 싶은 위치의 위도, 경도], popup='<i>Mt.Hood Meadows</i>').add_to(my_map)
folium.Marker([마커찍고 싶은 위치의 위도, 경도],popup='<b>Timberline Lodge</b>').add_to(my_map)

my_map

  • 마커를 추가할 수 있다
  • popup에 html 문법이 적용된다
  • tooltip은 마우스를 마커에 올려놓으면 텍스트가 보인다
  • tooltip에도 html 문법이 적용된다

4

m = folium.Map(location=[위도, 경도], zoom_start=12, tiles='Stamen Terrain')

folium.Marker(
	location=[마커찍고 싶은 위치의 위도, 경도],
    popup='Mt.Hood Meadows',
    icon=folium.Icon(icon='cloud'),
).add_to(m)
folium.Marker(
	location=[마커찍고 싶은 위치의 위도, 경도],
    popup='Timberline Lodge',
    icon=folium.Icon(color='green'),
).add_to(m)
folium.Marker(
	location=[마커찍고 싶은 위치의 위도, 경도],
    popup='한글테스트',
    icon=folium.Icon(color='red', icon='info-sign'),
).add_to(m)

icon 옵션

5

m = folium.Map(location=[위도,경도], tiles='Stamen Toner', zoom_start=13)

folium.Circle(
	radius=100,
    location=[원 중심 위도,경도],
    popup='The Waterfront',
    color='crimson',
    fill=False,
).add_to(m)

folium.CircleMarker(
	location=[위도,경도],
    radius=50,
    popup='Laurelhurst Park',
    color = '#3186cc',
    fill=True,
    fill_color='#3186cc',
).add_to(m)

  • ClickForMarker : 지도위에 마우스로 클릭 시 마커 생성
    popup : 마커 클릭 시 문구 출력

  • Circle 마커
    Circle, CircleMarker : 동일한 동작, 옵션이지만 radius만 다르게 적용된다.
    location,popup, tooltip Marker와 동일
    radius : 원의 반지름
    fill, fill_color : 원 내부 색상 설정

6

m = folium.Map(location=[위도,경도], tiles='Stamen Terrain', zoom_start=13)
m.add_child(folium.LatLngPopup())
m

  • LatLngPopup() : 지도위에 마우스 클릭시 위도, 경도 문구 출력
    맵.add_child(folium.LatLngPopup()

7

m = folium.Map(location=[위도,경도], tiles='OpenStreetMap', zoom_start=13)
m.add_child(folium.ClickForMarker())

  • 클릭했을 때 마커 생성
  • 마커를 클릭하면 popup으로 위도 경도
  • ClickForMarker(popup='입력하고 싶은 텍스트') : popup내용 수정 가능

미국 실업률 지도 시각화

  • json 파일 활용
  • folium.Choropleth() : 색상이나 패턴을 사용하여 특정 통계에 대한 데이터를 사전 정의된 영역과 관련시켜 시각화한 지도 유형
  • geo_data : 지도 데이터 파일(json 등)
  • data : Series or DataFrame 형태의 시각화 하고자 하는 데이터
  • columns : [지도 데이터와 매핑할 값, 시각화하고자 하는 데이터]
  • key_on : feature.데이터 파일과 매핑할 값
  • fill_color : 시각화에 쓰일 색상
  • fill_opacity : 투명도
  • line_opacity : 투명도
  • legend_name : 컬러 범주명
import json

state_data = pd.read_csv('../data/파일 이름')

m = folium.Map(location=[48, -102], zoom_start=3)
m.choropleth(
	geo_data='../data/02.us-states.json'),
    data=state_data,
    columns=['state','Unemployment'],
    key_on='feature.id',
    fill_color='YlGn',
    fill_opacity=0.7,
    line_opacity=0.2,
    legend_name='Unemployment Rate (%)',
)
m

  • data : Series or DataFrame 형태로 들어와야 한다
  • choropleth : 경계선을 따라서 하나의 덩어리로 잡고 각 덩어리에 색상을 입힐 수 있다

아파트 유형 지도 시각화

import pandas as pd

df = pd.read_csv('../data/02. 서울특별시 동작구_주택유형별 위치 정보 및 세대수 현황_20210825.csv',encoding='euc-kr') # or cp949

df.tail(2)

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

  • 몇 개의 데이터가 빠져 있다
  • NaN 데이터 제거
df = df.reset_index(drop=True)
df.tail(2)

del df['연번 ']
  • 연번 칼럼 삭제
# folium

m = folium.Map(
    location=[37.50589466533131,126.93450729567374],zoom_start=13
)

for idx,row in df.iterrows():
    # location
    lat,lng = row.위도, row.경도
    
    # Marker
    folium.Marker(
        location=[lat,lng],
        popup=row.주소,
        tooltip=row.분류,
        icon=folium.Icon(
            icon='home',
            color= 'lightred' if row.세대수 >= 199 else 'lightblue',
            icon_color='darkred' if row.세대수 >= 199 else 'darkblue',
        )
    ).add_to(m)
    
    # Circle
    folium.Circle(
        location=[lat,lng],
        radius=row.세대수 * 0.2,
        fill=True,
        color='pink' if row.세대수 >= 518 else 'green',
        fill_color='pink' if row.세대수 >= 518 else 'green'
    ).add_to(m)
m

서울시범죄현황 지도 시각화

import json

crime_anal_norm = pd.read_csv('파일이름', index_col=0, encoding='utf-8')
geo_path = '../data/json파일이름'
geo_str = json.load(open(geo_path, encoding='utf-8'))
  • 범죄율에 대한 지도 시각화

살인 발생 건수

my_map = folium.Map(location=[위도,경도], zoom_start=11, tiles='Stamen Toner')

my_map.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='정규화된 살인 발생 건수',
)

성범죄 발생 건수

my_map = folium.Map(location=[위도,경도], zoom_start=11, tiles='Stamen Toner')

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

5대 범죄 발생 건수

my_map = folium.Map(location=[위도,경도], zoom_start=11, tiles='Stamen Toner')

my_map.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='정규화된 범죄 발생 건수',
)

인구 대비 범죄 발생 건수

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

my_map = folium.Map(location=[위도,경도], zoom_start=11, tiles='Stamen Toner')

my_map.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='정규화된 범죄 발생 건수',
)

crime_anal_station = pd.read_cdv('파일이름',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) # numpy axis=1 은 행(가로),pandas axis=1은 열(세로)
crime_anal_station.tail()

  • 경찰서별 정보를 가지고 범죄발생과 함께 정리
  • 경찰서별 데이터에 검거 점수를 추가
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
)

for idx, rows in crime_anal_station.iterrows():
    folium.CircleMarker(
        location=[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['범죄']],
    key_on='feature.id',
    fill_color='PuRd',
    fill_opacity=0.7,
    line_opacity=0.2
).add_to(my_map)
# 검거율 : Circle로 표현
for idx, rows in crime_anal_station.iterrows():
    folium.CircleMarker(
        location=[rows['lat'],rows['lng']],
        radius=rows['검거'] * 50,
        popup=rows['구분'] + ':' +' %.2f' %rows['검거'],
        color='#3186cc',
        fill=True,
        fill_color='#3186cc',
        
    ).add_to(my_map)
my_map

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

서울시범죄현황 장소별 분석

  • 강남의 범죄 발생이 많은 것은 혹시 유흥업소의 밀집과 관련이 있지 않을까?
crime_loc_raw = pd.read_csv('파일 이름',thousands=',', encoding='euc-kr')
crime_loc_raw.head()

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

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

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',
        linewidth=0.5,
        cmap='RdPu'
    )
    plt.title('범죄 발생 장소')
    plt.show()
drawGraph()

profile
+database

0개의 댓글