San Francisco Crime Classification EDA
- Kaggle 주소: San Francisco Crime Classification
- 경진대회의 목표: 특정 위치 데이터를 활용해서 특정 시간대, 요일, 관할서에서 일어난 범죄를 분석해서 범죄의 구체적인 종류를 예측하는 것
import pandas as pd
import numpy as np
Load Dataset
train = pd.read_csv('./data/train.csv')
print(train.shape)
train.head()
(878049, 9)
Dates | Category | Descript | DayOfWeek | PdDistrict | Resolution | Address | X | Y | |
---|---|---|---|---|---|---|---|---|---|
0 | 2015-05-13 23:53:00 | WARRANTS | WARRANT ARREST | Wednesday | NORTHERN | ARREST, BOOKED | OAK ST / LAGUNA ST | -122.425892 | 37.774599 |
1 | 2015-05-13 23:53:00 | OTHER OFFENSES | TRAFFIC VIOLATION ARREST | Wednesday | NORTHERN | ARREST, BOOKED | OAK ST / LAGUNA ST | -122.425892 | 37.774599 |
2 | 2015-05-13 23:33:00 | OTHER OFFENSES | TRAFFIC VIOLATION ARREST | Wednesday | NORTHERN | ARREST, BOOKED | VANNESS AV / GREENWICH ST | -122.424363 | 37.800414 |
3 | 2015-05-13 23:30:00 | LARCENY/THEFT | GRAND THEFT FROM LOCKED AUTO | Wednesday | NORTHERN | NONE | 1500 Block of LOMBARD ST | -122.426995 | 37.800873 |
4 | 2015-05-13 23:30:00 | LARCENY/THEFT | GRAND THEFT FROM LOCKED AUTO | Wednesday | PARK | NONE | 100 Block of BRODERICK ST | -122.438738 | 37.771541 |
train.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 878049 entries, 0 to 878048
Data columns (total 9 columns):
Dates 878049 non-null object
Category 878049 non-null object
Descript 878049 non-null object
DayOfWeek 878049 non-null object
PdDistrict 878049 non-null object
Resolution 878049 non-null object
Address 878049 non-null object
X 878049 non-null float64
Y 878049 non-null float64
dtypes: float64(2), object(7)
memory usage: 60.3+ MB
데이터 설명
- row의 사이즈가 매우 큰 데이터, 실전 데이터 분석에 도움이 될 듯
- column의 설명
- Datas: 범죄가 발생한 날짜와 시간
- Category: 범죄의 세부종류(label colum)
- Descript: 범죄의 세부정보(test.csv에는 없는 데이터, 참고용으로만 사용되는 데이터)
- DayOfWeek: 범죄가 발생한 요일(월~일)
- PdDistrict: 범죄 관할 경찰서 이름(총 10개)
- Resolution: 범죄의 상태, 범죄 해결 여부(test.csv에는 없는 데이터, 참고용으로만 사용되는 데이터)
- Address: 범죄가 발생한 구체적인 주소(미국의 주소체계, 샌프란시스코 주소체계에 대한 도메인 지식이 있다면 좋은 예측 모델을 나타낼 수 있음)
- X: 범죄가 발생한 좌표 정보(경도)
- Y: 범죄가 발생한 좌표 정보(위도)
Explore
- 탐험적 데이터 분석(Exploratory Data Analysis, EDA)이 중요
- 파이썬 패키지는 자유롭게 선택가능
- 데이터 시각화: matplotlib, seaborn, ggplot, bokeh 등 사용 가능
- Cross Validation을 활용해 머신러닝 모델의 성능을 측정, 점수로 피드백 받아 모델 개선(Try and Error 방식으로 모델을 점진적으로 개선함)
- 단, 모델이 개선되거나 개선되지 않았을 때 이유를 명확히 파악하기 어려움
- 모델 개선의 구체적 전략을 짜는 것이 어려움
Library loading
# matplotlib으로 시각화하는 것을 jupyter notebook에 바로 띄움
%matplotlib inline
import seaborn as sns
import matplotlib.pyplot as plt
Dates Column
- Dates 형식
- dtype('O'): 문자열, object
- to_datetime()으로 변환
train["Dates"].dtypes
dtype('O')
# 문자열(string, object) 데이터 타입을 DateTime 형태로 변환
pd.to_datetime(train["Dates"]).dtypes
dtype('<M8[ns]')
train["Dates"] = pd.to_datetime(train["Dates"])
train.dtypes
Dates datetime64[ns]
Category object
Descript object
DayOfWeek object
PdDistrict object
Resolution object
Address object
X float64
Y float64
dtype: object
# pandas에 Dates 컬럼을 년,월,일,시,분,초 새로운 컬럼으로 따로 추가
train["Dates-year"] = train["Dates"].dt.year
train["Dates-month"] = train["Dates"].dt.month
train["Dates-day"] = train["Dates"].dt.day
train["Dates-hour"] = train["Dates"].dt.hour
train["Dates-minute"] = train["Dates"].dt.minute
train["Dates-second"] = train["Dates"].dt.second
print(train.shape)
train[["Dates", "Dates-year", "Dates-month", "Dates-day", "Dates-hour", "Dates-minute", "Dates-second"]].head()
(878049, 15)
Dates | Dates-year | Dates-month | Dates-day | Dates-hour | Dates-minute | Dates-second | |
---|---|---|---|---|---|---|---|
0 | 2015-05-13 23:53:00 | 2015 | 5 | 13 | 23 | 53 | 0 |
1 | 2015-05-13 23:53:00 | 2015 | 5 | 13 | 23 | 53 | 0 |
2 | 2015-05-13 23:33:00 | 2015 | 5 | 13 | 23 | 33 | 0 |
3 | 2015-05-13 23:30:00 | 2015 | 5 | 13 | 23 | 30 | 0 |
4 | 2015-05-13 23:30:00 | 2015 | 5 | 13 | 23 | 30 | 0 |
시각화
- 시각화 전 시각화의 예상 결과를 미리 상상하는 것을 추천
- seaborn의 countplot을 이용하여 시각화
# 여러개의 시각화를 한 화면에 띄움: matplotlib의 subplots 사용
# 2*3으로 총 6개의 시각화를 한 화면에 띄움
figure, ((ax1, ax2, ax3), (ax4, ax5, ax6)) = plt.subplots(nrows=2, ncols=3)
# 시각화 전체 사이즈 지정
figure.set_size_inches(20, 8)
sns.countplot(data=train, x="Dates-year", ax=ax1) # ax1: 좌상단
sns.countplot(data=train, x="Dates-month", ax=ax2)
sns.countplot(data=train, x="Dates-day", ax=ax3)
sns.countplot(data=train, x="Dates-hour", ax=ax4)
sns.countplot(data=train, x="Dates-minute", ax=ax5)
sns.countplot(data=train, x="Dates-second", ax=ax6) # ax6: 우하단
<matplotlib.axes._subplots.AxesSubplot at 0x11dd31ef0>
- year: 2015년만 범죄 발생수가 작음
- month: 균등
- day: 1일은 많고 31일은 범죄 발생수가 적음(31일이 적은 이유는 1,3,5,7,8,10,12월만 존재하기 때문에)
- hour: 새벽에는 범죄 발생수가 적음
- minute: 0, 30분에 범죄발생수가 많음(0,30,45,15,20분 순으로 많음)
- second: 모두 0으로 범죄 발생에 영향 없음
feadback
- 초(Dates-second), 분(Dates-minute)는 정확하게 기록되어 있지 않을 가능성이 높다.
- 초(Dates-second)는 전부 0으로 기록되어 있기 때문에 범죄를 예측하는데 크게 중요하지 않은 것 같음, feature를 제거하는 것도 좋은 아이디어다.
- 시간(Dates-hour)은 범죄를 예측하는 데 큰 영향을 미칠 것으로 보인다.
- 분(Dates-minute)은 잘 정리하면 머신러닝 모델이 이 데이터를 이용하는데 도움이 될 것 같다.
- 0분, 30분은 배제하고 나머지 분들을 사용
train["Dates-minute"].value_counts().head()
0 268950
30 125173
45 40229
15 37904
20 26027
Name: Dates-minute, dtype: int64
train["Dates-hour"].value_counts().tail(10)
10 37806
9 35555
8 32900
1 26173
2 22296
7 22048
3 14014
6 13133
4 9863
5 8637
Name: Dates-hour, dtype: int64
X, Y Column
- X: 경도, Y: 위도
- seaborn의 lmplot을 이용해 좌표 정보를 시각화
train[["X", "Y"]]
X | Y | |
---|---|---|
0 | -122.425892 | 37.774599 |
1 | -122.425892 | 37.774599 |
2 | -122.424363 | 37.800414 |
3 | -122.426995 | 37.800873 |
4 | -122.438738 | 37.771541 |
... | ... | ... |
878044 | -122.459033 | 37.714056 |
878045 | -122.447364 | 37.731948 |
878046 | -122.403390 | 37.780266 |
878047 | -122.390531 | 37.780607 |
878048 | -122.394926 | 37.738212 |
878049 rows × 2 columns
시각화
# seaborn의 lmplot을 사용해 좌표 데이터를 출력
# fil_reg 옵션에 False 지정 (추세선을 그리지 않기 위해 지정, 계산속도 느림)
sns.lmplot(data=train, x="X", y="Y", fit_reg=False)
<seaborn.axisgrid.FacetGrid at 0x11d29f7f0>
train["X"].max(), train["Y"].max()
(-120.5, 90.0)
# outlier 데이터를 가져옴
X_outliers = train["X"] == train["X"].max()
Y_outliers = train["Y"] == train["Y"].max()
outlier = train[X_outliers & Y_outliers]
print(outlier.shape)
outlier.head()
(67, 15)
Dates | Category | Descript | DayOfWeek | PdDistrict | Resolution | Address | X | Y | Dates-year | Dates-month | Dates-day | Dates-hour | Dates-minute | Dates-second | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
660485 | 2005-12-30 17:00:00 | LARCENY/THEFT | GRAND THEFT FROM LOCKED AUTO | Friday | TENDERLOIN | NONE | 5THSTNORTH ST / OFARRELL ST | -120.5 | 90.0 | 2005 | 12 | 30 | 17 | 0 | 0 |
660711 | 2005-12-30 00:34:00 | ASSAULT | INFLICT INJURY ON COHABITEE | Friday | BAYVIEW | ARREST, BOOKED | JAMESLICKFREEWAY HY / SILVER AV | -120.5 | 90.0 | 2005 | 12 | 30 | 0 | 34 | 0 |
660712 | 2005-12-30 00:34:00 | ASSAULT | AGGRAVATED ASSAULT WITH BODILY FORCE | Friday | BAYVIEW | ARREST, BOOKED | JAMESLICKFREEWAY HY / SILVER AV | -120.5 | 90.0 | 2005 | 12 | 30 | 0 | 34 | 0 |
661106 | 2005-12-29 00:07:00 | NON-CRIMINAL | AIDED CASE, MENTAL DISTURBED | Thursday | TENDERLOIN | PSYCHOPATHIC CASE | 5THSTNORTH ST / EDDY ST | -120.5 | 90.0 | 2005 | 12 | 29 | 0 | 7 | 0 |
666430 | 2005-11-30 11:25:00 | OTHER OFFENSES | TRAFFIC VIOLATION | Wednesday | TENDERLOIN | ARREST, CITED | 5THSTNORTH ST / ELLIS ST | -120.5 | 90.0 | 2005 | 11 | 30 | 11 | 25 | 0 |
아웃라이어를 제외하고 시각화
- 샌프란시스코 지도 모양으로 시각화됨
# outlier가 아닌(~) 데이터만 색인으로 추출
non_outliers = train[~(X_outliers & Y_outliers)]
# 시각화
sns.lmplot(data=non_outliers, x="X", y="Y", fit_reg=False)
<seaborn.axisgrid.FacetGrid at 0x11d8ab320>
sns.scatterplot(x="X", y="Y", data=non_outliers)
<matplotlib.axes._subplots.AxesSubplot at 0x11d805c18>
feadback
- 아웃라이어가 전체 데이터에 0.01%도 되지 않기 때문에 이 아웃라이어를 해결한다고 모델의 성능이 크게 좋아지지는 않는다.
- 범죄의 종류마다 좌표 데이터가 차이가 있다면(특정 지역에서 많이 발생하는 범죄 등이 있다면) 이 데이터를 머신러닝 모델에 집어넣으면 성능을 좋게 만들 수 있을 것이다.
- 아웃라이어가 존재한다면, 이를 정상적인 값으로 고치거나 아예 아웃라이어를 배제하면 머신러닝 알고리즘이 아웃라이어에 편향되는 현상이 사라질 것이다.
DayOfWeek Column
non_outliers["DayOfWeek"].value_counts()
Friday 133723
Wednesday 129200
Saturday 126804
Thursday 125027
Tuesday 124954
Monday 121573
Sunday 116701
Name: DayOfWeek, dtype: int64
시각화
- 요일마다 범죄 발생률의 차이가 크지 않으나 금요일(Friday)이 비교적 높고 일요일(Sunday)이 비교적 낮다.
plt.figure(figsize=(12,4))
# seaborn의 경우 자동정렬이 되지 않으므로, 명시적으로 정렬 순서를 정해줌
dwofweek_list = ["Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday", "Sunday"]
sns.countplot(x="DayOfWeek", order=dwofweek_list, data=non_outliers)
<matplotlib.axes._subplots.AxesSubplot at 0x11db76e80>
Category Column
- LARCENY/THEFT: 절도(경범죄)
- OTHER OFFENSES: 기타
- NON-CRIMINAL: 범죄가 아님?
- ASSAULT: 폭행
- DRUG/NARCOTIC: 마약
- VEHICLE THEFT: 차량 절도
- VANDALISM: 기물 파손
- WARRANTS: 구속
- BURGLARY: 절도(중범죄)
- SUSPICIOUS OCC: 의심스러운 행위
- MISSING PERSON: 실종
- ROBBERY: 강도
- FRAUD: 사기
- FORGERY/COUNTERFEITING: 위조
- SECONDARY CODES: 2차적 배경들이 있는 범죄
- WEAPON LAWS: 총기 사고
- PROSTITUTION: 매춘
- TRESPASS: 무단 침입
- STOLEN PROPERTY: 도난
- SEX OFFENSES FORCIBLE: 강제적 성범죄
- DISORDERLY CONDUCT: 문란 행위
- DRUNKENNESS: 과음
- RECOVERED VEHICLE: 차량 탈취
- KIDNAPPING: 납치, 유괴
- DRIVING UNDER THE INFLUENCE: 음주 운전
- RUNAWAY: 가출
- LIQUOR LAWS: 음주 및 기타 주류 유통 위반
- ARSON: 방화
- LOITERING: 의심스러운 배회 및 방황
- EMBEZZLEMENT: 횡령
- SUICIDE: 자살
- FAMILY OFFENSES: 가정 범죄
- BAD CHECKS: 부도 수표
- BRIBERY: 뇌물 수수
- EXTORTION: 갈취
- SEX OFFENSES NON FORCIBLE: 비강제적 성범죄
- GAMBLING: 도박
- PORNOGRAPHY/OBSCENE MAT: 음란행위, 외설행위
- TREA: 무단 침입
non_outliers["Category"].value_counts().index
Index(['LARCENY/THEFT', 'OTHER OFFENSES', 'NON-CRIMINAL', 'ASSAULT',
'DRUG/NARCOTIC', 'VEHICLE THEFT', 'VANDALISM', 'WARRANTS', 'BURGLARY',
'SUSPICIOUS OCC', 'MISSING PERSON', 'ROBBERY', 'FRAUD',
'FORGERY/COUNTERFEITING', 'SECONDARY CODES', 'WEAPON LAWS',
'PROSTITUTION', 'TRESPASS', 'STOLEN PROPERTY', 'SEX OFFENSES FORCIBLE',
'DISORDERLY CONDUCT', 'DRUNKENNESS', 'RECOVERED VEHICLE', 'KIDNAPPING',
'DRIVING UNDER THE INFLUENCE', 'RUNAWAY', 'LIQUOR LAWS', 'ARSON',
'LOITERING', 'EMBEZZLEMENT', 'SUICIDE', 'FAMILY OFFENSES', 'BAD CHECKS',
'BRIBERY', 'EXTORTION', 'SEX OFFENSES NON FORCIBLE', 'GAMBLING',
'PORNOGRAPHY/OBSCENE MAT', 'TREA'],
dtype='object')
non_outliers["Category"].value_counts()
LARCENY/THEFT 174885
OTHER OFFENSES 126165
NON-CRIMINAL 92300
ASSAULT 76872
DRUG/NARCOTIC 53971
VEHICLE THEFT 53772
VANDALISM 44724
WARRANTS 42206
BURGLARY 36754
SUSPICIOUS OCC 31412
MISSING PERSON 25989
ROBBERY 22999
FRAUD 16679
FORGERY/COUNTERFEITING 10609
SECONDARY CODES 9985
WEAPON LAWS 8555
PROSTITUTION 7484
TRESPASS 7325
STOLEN PROPERTY 4539
SEX OFFENSES FORCIBLE 4387
DISORDERLY CONDUCT 4318
DRUNKENNESS 4280
RECOVERED VEHICLE 3138
KIDNAPPING 2341
DRIVING UNDER THE INFLUENCE 2268
RUNAWAY 1946
LIQUOR LAWS 1903
ARSON 1513
LOITERING 1225
EMBEZZLEMENT 1166
SUICIDE 508
FAMILY OFFENSES 491
BAD CHECKS 406
BRIBERY 289
EXTORTION 256
SEX OFFENSES NON FORCIBLE 148
GAMBLING 146
PORNOGRAPHY/OBSCENE MAT 22
TREA 6
Name: Category, dtype: int64
시각화
- LARCENY/THEFT가 제일 많음
# 상위 20개 범죄 종류만 시각화
sns.countplot(y="Category", data=non_outliers,
order=non_outliers["Category"].value_counts().index[:20])
<matplotlib.axes._subplots.AxesSubplot at 0x123a44358>
DayOfWeek와 Category관계 확인
- DayOfWeek 시각화에서 큰 차이를 볼 수 없어서 Category 컬럼도 같이 시각화
1. 첫번째 방법
# 다수개의 차트를 같이 표현하는 방법
figure, axes = plt.subplots(nrows=10, ncols=4)
# 각 차트의 사이즈 지정
figure.set_size_inches(30, 48)
# seaborn의 경우 자동정렬이 되지 않으므로, 명시적으로 정렬 순서를 정해줌
dwofweek_list = ["Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday", "Sunday"]
category_list = non_outliers["Category"].value_counts().index
# 반복문 수행
for row in range(10):
for col in range(4):
index = row * 4 + col #category의 개수가 39개
if index < len(category_list):
ax = axes[row][col]
category = category_list[index]
#print(row, col, index, category)
target = non_outliers[non_outliers["Category"] == category]
sns.countplot(x="DayOfWeek", order=dwofweek_list, data=target, ax=ax)
ax.set(xlabel = category)
feadback
- 몇몇 특정 범죄에 한해 주중이나 주말일 때 범죄 발생에 차이가 있다.
- 주말에 많이 발생: 특히 음주운전(DRIVING UNDER THE INFLUENCE), 과음(DRUNKENNESS), 무단침입(TREA)
- 주중에 많이 발생: 마약(DRUG/NARCOTIC), 절도(BURGLARY), 위조(FORGERY/COUNTERFEITING)
- 주말의 경우도 금,토 또는 토,일에 따라 범죄 발생률이 다르다.
- 요일(DayOfWeek)가 범죄를 예측하는데 중요한 영향을 끼친다는 것을 알 수 있으므로 예측 모델의 성능을 개선하는데 효과가 있을 것 같다.
- 범죄마다 중요한 요일의 타입이 다르며, 대부분 주중과 주말로 나뉘며, 주말도 금,토 또는 토,일에 따라 다르다.
- 예외적으로 특정 요일에만 발생하는 범죄가 있다.
- 무단침입(TREA): 토요일
- 실종(MISSING PERSON): 금요일
2. 두번째 방법
groups = non_outliers.groupby(['Category', 'DayOfWeek'])['DayOfWeek'].count()
groups
Category DayOfWeek
ARSON Friday 220
Monday 228
Saturday 220
Sunday 211
Thursday 199
...
WEAPON LAWS Saturday 1232
Sunday 1128
Thursday 1282
Tuesday 1176
Wednesday 1252
Name: DayOfWeek, Length: 271, dtype: int64
# Group으로 묶여진 데이터를 matrix형태로 전환
dw_category = groups.unstack(fill_value=0.0)
dw_category
DayOfWeek | Friday | Monday | Saturday | Sunday | Thursday | Tuesday | Wednesday |
---|---|---|---|---|---|---|---|
Category | |||||||
ARSON | 220.0 | 228.0 | 220.0 | 211.0 | 199.0 | 235.0 | 200.0 |
ASSAULT | 11157.0 | 10559.0 | 11995.0 | 12082.0 | 10246.0 | 10280.0 | 10553.0 |
BAD CHECKS | 62.0 | 66.0 | 45.0 | 20.0 | 66.0 | 76.0 | 71.0 |
BRIBERY | 49.0 | 41.0 | 42.0 | 41.0 | 39.0 | 37.0 | 40.0 |
BURGLARY | 6326.0 | 5262.0 | 4754.0 | 4231.0 | 5350.0 | 5374.0 | 5457.0 |
DISORDERLY CONDUCT | 541.0 | 608.0 | 624.0 | 586.0 | 643.0 | 657.0 | 659.0 |
DRIVING UNDER THE INFLUENCE | 352.0 | 263.0 | 457.0 | 442.0 | 282.0 | 251.0 | 221.0 |
DRUG/NARCOTIC | 7420.0 | 7823.0 | 6390.0 | 6143.0 | 8454.0 | 8474.0 | 9267.0 |
DRUNKENNESS | 622.0 | 513.0 | 833.0 | 813.0 | 496.0 | 461.0 | 542.0 |
EMBEZZLEMENT | 211.0 | 222.0 | 137.0 | 108.0 | 165.0 | 156.0 | 167.0 |
EXTORTION | 35.0 | 30.0 | 32.0 | 39.0 | 40.0 | 39.0 | 41.0 |
FAMILY OFFENSES | 82.0 | 69.0 | 59.0 | 54.0 | 63.0 | 85.0 | 79.0 |
FORGERY/COUNTERFEITING | 1757.0 | 1704.0 | 1178.0 | 901.0 | 1610.0 | 1752.0 | 1707.0 |
FRAUD | 2641.0 | 2533.0 | 2256.0 | 1874.0 | 2351.0 | 2506.0 | 2518.0 |
GAMBLING | 35.0 | 16.0 | 21.0 | 12.0 | 20.0 | 12.0 | 30.0 |
KIDNAPPING | 385.0 | 340.0 | 355.0 | 374.0 | 289.0 | 306.0 | 292.0 |
LARCENY/THEFT | 27102.0 | 23568.0 | 27214.0 | 24147.0 | 24415.0 | 23955.0 | 24484.0 |
LIQUOR LAWS | 291.0 | 188.0 | 297.0 | 222.0 | 248.0 | 323.0 | 334.0 |
LOITERING | 139.0 | 193.0 | 140.0 | 155.0 | 186.0 | 252.0 | 160.0 |
MISSING PERSON | 4663.0 | 3592.0 | 3752.0 | 3061.0 | 3680.0 | 3655.0 | 3586.0 |
NON-CRIMINAL | 13983.0 | 12854.0 | 14007.0 | 12972.0 | 12818.0 | 12738.0 | 12928.0 |
OTHER OFFENSES | 18587.0 | 17783.0 | 17128.0 | 15456.0 | 18459.0 | 18806.0 | 19946.0 |
PORNOGRAPHY/OBSCENE MAT | 4.0 | 3.0 | 1.0 | 3.0 | 5.0 | 3.0 | 3.0 |
PROSTITUTION | 1158.0 | 409.0 | 850.0 | 620.0 | 1547.0 | 1421.0 | 1479.0 |
RECOVERED VEHICLE | 494.0 | 530.0 | 343.0 | 307.0 | 432.0 | 517.0 | 515.0 |
ROBBERY | 3384.0 | 3193.0 | 3428.0 | 3284.0 | 3216.0 | 3221.0 | 3273.0 |
RUNAWAY | 344.0 | 280.0 | 268.0 | 205.0 | 305.0 | 275.0 | 269.0 |
SECONDARY CODES | 1392.0 | 1483.0 | 1462.0 | 1543.0 | 1389.0 | 1343.0 | 1373.0 |
SEX OFFENSES FORCIBLE | 620.0 | 607.0 | 662.0 | 690.0 | 585.0 | 597.0 | 626.0 |
SEX OFFENSES NON FORCIBLE | 28.0 | 23.0 | 21.0 | 16.0 | 15.0 | 23.0 | 22.0 |
STOLEN PROPERTY | 647.0 | 636.0 | 581.0 | 583.0 | 679.0 | 713.0 | 700.0 |
SUICIDE | 72.0 | 75.0 | 73.0 | 67.0 | 89.0 | 66.0 | 66.0 |
SUSPICIOUS OCC | 4923.0 | 4447.0 | 4155.0 | 4010.0 | 4510.0 | 4516.0 | 4851.0 |
TREA | 1.0 | 1.0 | 2.0 | 0.0 | 1.0 | 1.0 | 0.0 |
TRESPASS | 1064.0 | 1080.0 | 983.0 | 915.0 | 1047.0 | 1114.0 | 1122.0 |
VANDALISM | 7092.0 | 5945.0 | 7326.0 | 6602.0 | 5980.0 | 5852.0 | 5927.0 |
VEHICLE THEFT | 8612.0 | 7412.0 | 8117.0 | 7504.0 | 7454.0 | 7261.0 | 7412.0 |
WARRANTS | 5926.0 | 5811.0 | 5364.0 | 5280.0 | 6372.0 | 6425.0 | 7028.0 |
WEAPON LAWS | 1302.0 | 1183.0 | 1232.0 | 1128.0 | 1282.0 | 1176.0 | 1252.0 |
시각화
fg, ax = plt.subplots(figsize=(10, 10))
sns.heatmap(dw_category, linewidths=.5, ax=ax)
<matplotlib.axes._subplots.AxesSubplot at 0x124bf4240>
- 가장 많은 수의 범죄인 LARCENY/THREFT가 Saturday, Friday순으로 높음
dw_category.loc['LARCENY/THEFT'].sort_values(ascending=False)
DayOfWeek
Saturday 27214.0
Friday 27102.0
Wednesday 24484.0
Thursday 24415.0
Sunday 24147.0
Tuesday 23955.0
Monday 23568.0
Name: LARCENY/THEFT, dtype: float64
PdDistrict Column
non_outliers["PdDistrict"].unique()
array(['NORTHERN', 'PARK', 'INGLESIDE', 'BAYVIEW', 'RICHMOND', 'CENTRAL',
'TARAVAL', 'TENDERLOIN', 'MISSION', 'SOUTHERN'], dtype=object)
non_outliers["PdDistrict"].value_counts()
SOUTHERN 157174
MISSION 119907
NORTHERN 105284
BAYVIEW 89422
CENTRAL 85455
TENDERLOIN 81794
INGLESIDE 78841
TARAVAL 65592
PARK 49311
RICHMOND 45202
Name: PdDistrict, dtype: int64
시각화
- 범죄의 관할서(PdDistrict)는 SOUTHERN, MISSION, MORTHERN순으로 높음
sns.countplot(data=non_outliers, y="PdDistrict")
<matplotlib.axes._subplots.AxesSubplot at 0x11ee6bd68>
- PdDistrict와 X, Y 좌표의 위치를 시각화
- SOUTHERN, MISSION, MORTHERN의 위치가 몰려있음을 알 수 있음
fg2, ax2 = plt.subplots(figsize=(10,10))
sns.scatterplot(x="X", y="Y", hue="PdDistrict",
data=non_outliers, ax=ax2)
<matplotlib.axes._subplots.AxesSubplot at 0x114f939b0>
sns.lmplot(x="X", y="Y", hue="PdDistrict",
data=non_outliers, fit_reg=False)
<seaborn.axisgrid.FacetGrid at 0x114c1a3c8>
Category와 PdDistrict 관계 확인
1. 첫번째 방법
# 다수개의 차트를 같이 표현하는 방법
figure, axes = plt.subplots(nrows=10, ncols=4)
# 각 차트의 사이즈 지정
figure.set_size_inches(40, 48)
category_list = non_outliers["Category"].value_counts().index
# 반복문 수행
for row in range(10):
for col in range(4):
index = row * 4 + col #category의 개수가 39개
if index < len(category_list):
ax = axes[row][col]
category = category_list[index]
#print(row, col, index, category)
target = non_outliers[non_outliers["Category"] == category]
sns.countplot(y="PdDistrict", data=target, ax=ax)
ax.set(ylabel = category)
feadback
- 관할 경찰서가 곧 지역을 상징하므로, 범죄가 많이 발생하는 지역에 범죄 발생빈도가 몰릴 것 같다.
- 범죄 발생 빈도가 SOUTHERN은 높고, PARK와 RICHMOND는 범죄 빈도가 낮은 편이다.
- 범죄의 종류에 따라 특정 지역에서 자주 발생하는 범죄가 있을 것이다.
- 관할 경찰서가 범죄를 예측하는 모델을 개선하는데 효과가 클 것 같다.
- PdDistrict를 one-hot encoding해서 모델에 집어넣는다.
- 관할 경찰서라도 결국에는 지역 데이터를 상징한다. 그러므로 다른 지역 데이터인 주소, 좌표와 엮어줄 수 있는 방법이 있다면 모델의 성능을 개선하는데 큰 도움이 될 것 같다.
2. 두번째 방법
# 범죄 발생빈도가 높은 상위 10개 범죄 데이터
top10_category = non_outliers["Category"].value_counts().index[:10]
df_top10_category = non_outliers.loc[non_outliers["Category"].isin(top10_category)]
sns.catplot(data=df_top10_category, y="PdDistrict",
col="Category", col_wrap=5, kind="count",
order=non_outliers["PdDistrict"].value_counts().index)
<seaborn.axisgrid.FacetGrid at 0x15e142be0>
3. 세번째 방법
groups2 = non_outliers.groupby(['Category', 'PdDistrict'])['PdDistrict'].count()
groups2
Category PdDistrict
ARSON BAYVIEW 393
CENTRAL 111
INGLESIDE 182
MISSION 145
NORTHERN 149
...
WEAPON LAWS PARK 357
RICHMOND 327
SOUTHERN 1128
TARAVAL 567
TENDERLOIN 794
Name: PdDistrict, Length: 382, dtype: int64
ca_pdDistrict = groups2.unstack(fill_value=0.0)
ca_pdDistrict
PdDistrict | BAYVIEW | CENTRAL | INGLESIDE | MISSION | NORTHERN | PARK | RICHMOND | SOUTHERN | TARAVAL | TENDERLOIN |
---|---|---|---|---|---|---|---|---|---|---|
Category | ||||||||||
ARSON | 393.0 | 111.0 | 182.0 | 145.0 | 149.0 | 65.0 | 103.0 | 185.0 | 120.0 | 60.0 |
ASSAULT | 9855.0 | 6977.0 | 8532.0 | 11149.0 | 8318.0 | 3515.0 | 3202.0 | 12183.0 | 5463.0 | 7678.0 |
BAD CHECKS | 34.0 | 65.0 | 32.0 | 46.0 | 54.0 | 15.0 | 31.0 | 74.0 | 36.0 | 19.0 |
BRIBERY | 56.0 | 12.0 | 53.0 | 66.0 | 18.0 | 7.0 | 8.0 | 37.0 | 17.0 | 15.0 |
BURGLARY | 3929.0 | 4519.0 | 3331.0 | 3745.0 | 5854.0 | 2888.0 | 2696.0 | 4841.0 | 3463.0 | 1488.0 |
DISORDERLY CONDUCT | 219.0 | 494.0 | 171.0 | 1099.0 | 450.0 | 271.0 | 108.0 | 511.0 | 162.0 | 833.0 |
DRIVING UNDER THE INFLUENCE | 179.0 | 160.0 | 206.0 | 357.0 | 260.0 | 176.0 | 319.0 | 306.0 | 202.0 | 103.0 |
DRUG/NARCOTIC | 4498.0 | 1805.0 | 2373.0 | 8757.0 | 4511.0 | 2573.0 | 999.0 | 9228.0 | 1531.0 | 17696.0 |
DRUNKENNESS | 243.0 | 517.0 | 190.0 | 782.0 | 368.0 | 374.0 | 149.0 | 959.0 | 259.0 | 439.0 |
EMBEZZLEMENT | 100.0 | 180.0 | 78.0 | 124.0 | 131.0 | 50.0 | 43.0 | 275.0 | 94.0 | 91.0 |
EXTORTION | 13.0 | 51.0 | 29.0 | 26.0 | 24.0 | 8.0 | 23.0 | 38.0 | 36.0 | 8.0 |
FAMILY OFFENSES | 73.0 | 28.0 | 71.0 | 142.0 | 12.0 | 26.0 | 23.0 | 42.0 | 35.0 | 39.0 |
FORGERY/COUNTERFEITING | 774.0 | 1144.0 | 977.0 | 1281.0 | 1292.0 | 518.0 | 619.0 | 2345.0 | 1097.0 | 562.0 |
FRAUD | 866.0 | 2344.0 | 1213.0 | 1970.0 | 2055.0 | 974.0 | 1137.0 | 3441.0 | 1549.0 | 1130.0 |
GAMBLING | 29.0 | 31.0 | 16.0 | 18.0 | 10.0 | 1.0 | 4.0 | 17.0 | 8.0 | 12.0 |
KIDNAPPING | 313.0 | 197.0 | 348.0 | 312.0 | 231.0 | 94.0 | 104.0 | 340.0 | 191.0 | 211.0 |
LARCENY/THEFT | 10118.0 | 25058.0 | 10235.0 | 18223.0 | 28628.0 | 9146.0 | 9891.0 | 41841.0 | 11844.0 | 9901.0 |
LIQUOR LAWS | 114.0 | 141.0 | 106.0 | 495.0 | 124.0 | 149.0 | 81.0 | 385.0 | 122.0 | 186.0 |
LOITERING | 50.0 | 70.0 | 26.0 | 191.0 | 193.0 | 23.0 | 8.0 | 429.0 | 34.0 | 201.0 |
MISSING PERSON | 5038.0 | 1348.0 | 3181.0 | 3283.0 | 1956.0 | 2713.0 | 1183.0 | 3064.0 | 3390.0 | 833.0 |
NON-CRIMINAL | 6099.0 | 10940.0 | 6853.0 | 12372.0 | 10240.0 | 5925.0 | 5744.0 | 19745.0 | 6919.0 | 7463.0 |
OTHER OFFENSES | 17051.0 | 8899.0 | 13201.0 | 19329.0 | 12230.0 | 6184.0 | 5631.0 | 21307.0 | 8612.0 | 13721.0 |
PORNOGRAPHY/OBSCENE MAT | 2.0 | 2.0 | 0.0 | 3.0 | 5.0 | 0.0 | 1.0 | 4.0 | 4.0 | 1.0 |
PROSTITUTION | 70.0 | 778.0 | 26.0 | 3629.0 | 1831.0 | 5.0 | 24.0 | 135.0 | 82.0 | 904.0 |
RECOVERED VEHICLE | 735.0 | 178.0 | 662.0 | 325.0 | 273.0 | 117.0 | 125.0 | 326.0 | 262.0 | 135.0 |
ROBBERY | 2714.0 | 1999.0 | 2788.0 | 3629.0 | 2640.0 | 957.0 | 787.0 | 3878.0 | 1405.0 | 2202.0 |
RUNAWAY | 265.0 | 43.0 | 148.0 | 298.0 | 88.0 | 485.0 | 94.0 | 108.0 | 408.0 | 9.0 |
SECONDARY CODES | 1662.0 | 704.0 | 1300.0 | 1439.0 | 999.0 | 444.0 | 525.0 | 1205.0 | 983.0 | 724.0 |
SEX OFFENSES FORCIBLE | 387.0 | 394.0 | 504.0 | 718.0 | 435.0 | 212.0 | 216.0 | 818.0 | 383.0 | 320.0 |
SEX OFFENSES NON FORCIBLE | 22.0 | 7.0 | 22.0 | 35.0 | 9.0 | 6.0 | 10.0 | 17.0 | 15.0 | 5.0 |
STOLEN PROPERTY | 397.0 | 510.0 | 347.0 | 631.0 | 699.0 | 175.0 | 206.0 | 1007.0 | 231.0 | 336.0 |
SUICIDE | 37.0 | 60.0 | 65.0 | 72.0 | 67.0 | 20.0 | 42.0 | 59.0 | 59.0 | 27.0 |
SUSPICIOUS OCC | 3905.0 | 2841.0 | 3167.0 | 3808.0 | 3270.0 | 1656.0 | 2191.0 | 5065.0 | 2996.0 | 2513.0 |
TREA | 3.0 | 1.0 | 0.0 | 1.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
TRESPASS | 636.0 | 904.0 | 416.0 | 1021.0 | 780.0 | 290.0 | 245.0 | 1456.0 | 386.0 | 1191.0 |
VANDALISM | 5355.0 | 4469.0 | 5374.0 | 5294.0 | 5404.0 | 2613.0 | 3180.0 | 6550.0 | 4869.0 | 1616.0 |
VEHICLE THEFT | 7219.0 | 4210.0 | 8960.0 | 7148.0 | 6291.0 | 3961.0 | 4113.0 | 4724.0 | 6142.0 | 1004.0 |
WARRANTS | 4322.0 | 2777.0 | 2528.0 | 6615.0 | 4595.0 | 2318.0 | 1010.0 | 9101.0 | 1616.0 | 7324.0 |
WEAPON LAWS | 1647.0 | 487.0 | 1130.0 | 1329.0 | 789.0 | 357.0 | 327.0 | 1128.0 | 567.0 | 794.0 |
fg3, ax3 = plt.subplots(figsize=(10, 10))
sns.heatmap(ca_pdDistrict, linewidths=.5, ax=ax3)
<matplotlib.axes._subplots.AxesSubplot at 0x1147b0fd0>
ca_pdDistrict.loc['LARCENY/THEFT'].sort_values(ascending=False)
PdDistrict
SOUTHERN 41841.0
NORTHERN 28628.0
CENTRAL 25058.0
MISSION 18223.0
TARAVAL 11844.0
INGLESIDE 10235.0
BAYVIEW 10118.0
TENDERLOIN 9901.0
RICHMOND 9891.0
PARK 9146.0
Name: LARCENY/THEFT, dtype: float64
soutern = non_outliers[non_outliers["PdDistrict"] == 'SOUTHERN']
soutern.head()
Dates | Category | Descript | DayOfWeek | PdDistrict | Resolution | Address | X | Y | Dates-year | Dates-month | Dates-day | Dates-hour | Dates-minute | Dates-second | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
35 | 2015-05-13 20:30:00 | LARCENY/THEFT | GRAND THEFT FROM LOCKED AUTO | Wednesday | SOUTHERN | NONE | KING ST / 3RD ST | -122.391846 | 37.778125 | 2015 | 5 | 13 | 20 | 30 | 0 |
40 | 2015-05-13 20:23:00 | NON-CRIMINAL | AIDED CASE, MENTAL DISTURBED | Wednesday | SOUTHERN | NONE | 700 Block of MARKET ST | -122.405295 | 37.786307 | 2015 | 5 | 13 | 20 | 23 | 0 |
41 | 2015-05-13 20:15:00 | LARCENY/THEFT | PETTY THEFT FROM LOCKED AUTO | Wednesday | SOUTHERN | NONE | 1600 Block of MARKET ST | -122.422128 | 37.773033 | 2015 | 5 | 13 | 20 | 15 | 0 |
52 | 2015-05-13 19:30:00 | LARCENY/THEFT | PETTY THEFT FROM A BUILDING | Wednesday | SOUTHERN | NONE | 0 Block of 3RD ST | -122.403285 | 37.787306 | 2015 | 5 | 13 | 19 | 30 | 0 |
53 | 2015-05-13 19:30:00 | LARCENY/THEFT | GRAND THEFT FROM UNLOCKED AUTO | Wednesday | SOUTHERN | NONE | 500 Block of BRANNAN ST | -122.397616 | 37.777392 | 2015 | 5 | 13 | 19 | 30 | 0 |
fg4, ax4 = plt.subplots(figsize=(10,10))
sns.scatterplot(x="X", y="Y", hue="Category", data=soutern, ax=ax4)
<matplotlib.axes._subplots.AxesSubplot at 0x115195dd8>
참고
seaborn 시각화: https://datascienceschool.net/view-notebook/4c2d5ff1caab4b21a708cc662137bc65/