Notice
Recent Posts
Recent Comments
Link
«   2026/02   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
Tags
more
Archives
Today
Total
관리 메뉴

Silver bullet

정형 데이터 전처리 & 시각화 본문

AI/AI

정형 데이터 전처리 & 시각화

밀크쌀과자 2024. 7. 1. 16:38

서울시 범죄현황 통계자료 분석 및 시각화

  • 서울에서 5대범죄가 가장 많이 일어나는 곳은?
  • 서울에서 인구수 대비 5대범죄가 가장 많이 일어나는 곳은?
  • 서울에서 5대범죄의 검거율이 제일 높은 곳은?

 

1. 데이터 전처리

1) 계 삭제하기

2) 경찰서를 구별로 정리하기

# dict를 get으로 꺼내면 없는 값에 대해서 dafalut값 설정 가능
df['구별'] = df['관서명'].apply(lambda x: police_to_gu.get(x, '구 없음'))
# 인덱스열을 일반 열로 강등시키기
df_temp.reset_index()

# 인덱스열 제거
df_temp.reset_index(drop=True)
# 관서별 데이터를 구별 데이터로 변경 (index : 관서 이름 -> 구 이름, column은 자동으로 오름차순 정렬됨)
# 같은 구의 경우에는 sum 을 적용 

gu_df = pd.pivot_table(df, index='구별', aggfunc=np.sum) # 피봇 테이블을 만드려면?
gu_df

gu_df = gu_df.drop(['구 없음']) # 행을 삭제할 때? (Database)

 

3) 검거율 계산하기

# 발생건수 대비 검거건수 -> 검거율 데이터 column을 범죄별로 생성

gu_df['강간검거율'] = gu_df['강간(검거)']/gu_df['강간(발생)']*100
gu_df['강도검거율'] = gu_df['강도(검거)']/gu_df['강도(발생)']*100
gu_df['살인검거율'] = gu_df['살인(검거)']/gu_df['살인(발생)']*100
gu_df['절도검거율'] = gu_df['절도(검거)']/gu_df['절도(발생)']*100
gu_df['폭력검거율'] = gu_df['폭력(검거)']/gu_df['폭력(발생)']*100
gu_df['검거율'] = gu_df['소계(검거)']/gu_df['소계(발생)']*100
gu_df.head()
# 여러 줄을 한번에 수정할 때 : ctrl 누르고 클릭
del gu_df['강간(검거)']
del gu_df['강도(검거)']
del gu_df['살인(검거)']
del gu_df['절도(검거)']
del gu_df['폭력(검거)']
del gu_df['소계(발생)']
del gu_df['소계(검거)']

*문제 : 검거율이 100보다 크다.

이유: 검거 건수는 당해가 아닌 이전 해에 발생한 범죄를 당해에 검거한 것

gu_df.at['강남구', '강도검거율'] # 강력 권장 도구! -> 알고리즘적으로 속도가 빠름
for row_index, row in gu_df_rate.iterrows(): # iterate + rows -> 행 하나씩 꺼내기
# 100이 넘는 검거율을 100으로 맞춰주기
gu_df[ gu_df[['강간검거율', '강도검거율', '살인검거율', '절도검거율', '폭력검거율']] > 100 ] = 100
gu_df.head(10)
# 살인사건 발생 건수가 7건이 넘고(and) 폭력사건 발생 건수가 2000건을 넘는 지역구를 추려내려면?
gu_df[ (gu_df['살인(발생)'] > 7) & (gu_df['폭력(발생)'] > 2000) ] # and의 비트 논리 연산자는 "&"입니다.

# 살인사건 발생 건수가 7건이 넘거나(or) 폭력사건 발생 건수가 2000건이 넘는 지역구를 추려내려면?
gu_df[ (gu_df['살인(발생)'] > 7) | (gu_df['폭력(발생)'] > 2000) ] # or의 비트 논리 연산자는 "|(Shift + 키보드 ₩)"입니다.

# 살인사건 발생 건수가 5건을 넘지 않는(not) 지역구를 추려내려면?
gu_df[ ~(gu_df['살인(발생)'] > 5) ] # not의 비트 논리 연산자는 "~"입니다.
# '살인검거율' 열의 결측치를 100으로 채워주기 (결측치가 채워진 열을 기존 열에 덮어써줘야 하는 것에 유의)
# 결측치 이유 : 살인이 발생하지 않아 숫자 0이기에 검거율이 NaN

gu_df['살인검거율'] = gu_df['살인검거율'].fillna(100) # 결측치(N/A)의 값을 채워주다(fill)

 

4) 인구 데이터 merge하기

A, B : pd.DataFrame
    
두개의 데이터 프레임 합치는 방법

1. A.join(B)
- 위아래로 섞여있어도 제자리로 잘 합쳐준다. (구별 이름을 맞추어 합쳐준다는 뜻)
- A, B 데이터프레임의 index colum이 동일해야 함. (A에 구별이면, B도 구별이라고 열이름 맞추기)

2. pd.merge(A, B, left_on='구별', right_on='구이름', how='inner')
- 위아래로 섞여있어도 제자리로 잘 합쳐준다.
- 기준이 되는 열이 서로 다른 이름을 가지고 있어도 ok.

3. pd.concat([A, B], axis=???) # concatenate
- 위아래로 섞여있으면 그대로 합쳐줌
- 행방향 or 열방향으로 적용 가능
# CSV 는 어떻게 읽을까요? (엑셀과 유사)
# utf-8, cp949, euc-kr

popul_df = pd.read_csv('pop_kor.csv', encoding='utf-8') # read_csv 는 encoding 옵션을 직접 지정해줄 수 있습니다. (utf-8, euc-kr, cp949)
popul_df.set_index("구별").head()
# 데이터프레임의 M&A 

gu_df = gu_df.join(popul_df) # df1.join(df2) : df1 의 index를 기준으로 df2 의 index 중 매칭되는 값을 매김
gu_df.head()

 

 

2. 데이터 시각화

import seaborn as sns

# 너무 까맣게 나옴
# 이유 : 강도, 살인에 비해 절도와 폭력 수가 너무 높음
# feature의 scale이 엉망이다.

sns.heatmap(gu_df[['강간', '강도', '살인', '절도', '폭력']])

 

1) 한글 데이터 시각화를 위한 준비

from matplotlib import font_manager, rc

# jupyter notebook 내에 figure를 보여주기
%matplotlib inline 

# matplotlib의 한글문제를 해결
font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
# font_name
rc('font', family=font_name)

## Mac OS
# rc('font', family="AppleGothic")

 

2) feature의 scale이 엉망일 때

  1. Min-Max Algorithm
    • min(열) = 0
    • max(열) = 1
    • 중간 값들은 0 -1 사이의 값으로 맞춰
  2. Standardization
    • mean(열) = 0
    • std(열) = 1

 

3) 간단한 정규화

gu_df.quantile(0.5) # 중위값 median
gu_df.quantile(0.75) # 4분위수
# 5대 범죄별 수치를 해당 범죄별 최대값으로 나눠줌 
weight_col = gu_df[['강간', '강도', '살인', '절도', '폭력']].max()
weight_col

crime_count_norm = gu_df[['강간', '강도', '살인', '절도', '폭력']] / weight_col
crime_count_norm
# 스케일링이 가능한 이유 : 하나의 열에서 행 간의 비교를 하기 때문이다.

 

4) 구별 살인 발생 순위 살펴보기

sns.heatmap(crime_count_norm.sort_values(by='살인', ascending=False)) # 내림차순으로 정렬하려면?
# 그래프가 색깔은 괜찮지만, 자세한 수치는 안보임
# 몇 가지 옵션으로 더 내용을 확인하기 편하도록 수정하기

# 전체 figure 의 사이즈를 조정
plt.figure(figsize = (10, 10))

# annot : 셀 내에 수치 입력 여부
# fmt : 셀 내 입력될 수치의 format (f == float)
# linewidths : 셀 간 이격거리 (하얀 부분, 내부 테두리)
# cmap : matplotlib colormap @ https://goo.gl/YWpBES
sns.heatmap(crime_count_norm.sort_values(by='살인', ascending=False), annot=True, fmt='f', linewidths=.5, cmap='Reds')

plt.title('범죄 발생(살인발생으로 정렬) - 각 항목별 최대값으로 나눠 정규화')
plt.show()
# 살인 대신 절도 기준으로 살펴보기

plt.figure(figsize = (10,10))
sns.heatmap(crime_count_norm.sort_values(by='절도', ascending=False), annot=True, fmt='f', linewidths=.5, cmap='RdPu')

plt.title('범죄 발생(절도발생으로 정렬) - 각 항목별 최대값으로 나눠 정규화')
plt.show()

 

5) (단순히 범죄 건수만 보지 말고) 인구수로 나눠서 인구대비 발생비율로 살펴보기

# 행(구)별로 구별 범죄 수 (max 대비 비율값) / 구별 인구 수 * 100000 
# 인구 수 단위인 10만을 곱해준다 (강서구 강간 = 9.795665e-07 -> 0.x 까지 끌어올리기)

crime_ratio = crime_count_norm.div(gu_df['인구수'], axis=0) * 100000 
crime_ratio.head()
plt.figure(figsize = (10,10))

sns.heatmap(crime_ratio.sort_values(by='살인', ascending=False), annot=True, fmt='f', linewidths=.5, cmap='Reds')
plt.title('범죄 발생(살인발생으로 정렬) - 각 항목을 정규화한 후 인구로 나눔')
plt.show()

 

6) [인구 수 대비] 구별 5대범죄 발생 수치 평균

# 구별 인구 대비 
crime_ratio['전체발생비율'] = crime_ratio.mean(axis=1) # 평균?
crime_ratio.head()

 

7) 경찰서별 검거율 계산하기

df = df.drop([0]) # "계" row를 삭제

# 경찰서 full-name column 생성하기 (구글맵스에서 좌표를 얻기 위해 경찰서 full-name으로 검색)
station_name = []
for name in df['관서명']:
    station_name.append('서울'+str(name[:-1])+'경찰서') # str(name[:-1] == ex. 중부서 -> 중부 

station_name
df['경찰서'] = station_name
df['검거율'] = df['소계(검거)']/df['소계(발생)']*100
df.head()

Min-Max Algorithm

# ['검거율'] 열을 대상으로, 
# 가장 낮은 검거율과 가장 높은 검거율을 가지는 경찰서를 일종의 점수 개념으로 간격을 벌림 (지도에서 보다 더 잘 비교되도록 하기 위함)

# 1) newMax-newMin 를 곱해주는 이유 : 0~1 대신에 특정한 range 로 변환 (여기서는 1~100)
# 2) newMin 인 1을 더해주는 이유 : 최소값인 0을 갖는 데이터가 시각화 시 아예 데이터가 표현되지 않는 것을 방지
def reRange(x, oldMin, oldMax, newMin, newMax):
    return (x - oldMin)*(newMax - newMin) / (oldMax - oldMin) + newMin 

df['점수'] = reRange(df['검거율'], min(df['검거율']), max(df['검거율']), 1, 100)
df.head()

 

 

 

 

+ JSON 구조를 쉽게 파악할 수 있게 해주는 도구, pyprnt

# PyPrnt @ http://j.mp/2WVZuGy

!pip install pyprnt==2.3.3
from pyprnt import prnt

menu = {
    "Kimchi": 5000,
    "Ice Cream": 100
}
prnt(menu)

block = {'index':1,'transaction':[{"sender":"Block_Reward","receipient":"30819f300d06092a864886f70d010101050003818d0030818902818100b9cadf2ca51ca6714cf645f015652a80b9b8fc7e1aafc888334ac6f4f7dc177465595ef713765b027ab97ca7929820d1afb54b64a03cb971f0f46582d5266568f78746d30c4a651b0a0cf14dacdd619f034b330f4c14f253c72496778ff921a1b907aa0e6201369bffb2bd2e0a059d034e711ef004a3100a8998c2786349579f0203010001","value":"5.0"},{"sender":"30819f300d06092a864886f70d010101050003818d0030818902818100b9cadf2ca51ca6714cf645f015652a80b9b8fc7e1aafc888334ac6f4f7dc177465595ef713765b027ab97ca7929820d1afb54b64a03cb971f0f46582d5266568f78746d30c4a651b0a0cf14dacdd619f034b330f4c14f253c72496778ff921a1b907aa0e6201369bffb2bd2e0a059d034e711ef004a3100a8998c2786349579f0203010001","receipient":"30819f300d06092a864886f70d010101050003818d0030818902818100ab65b338fc66d9fc4870b7319f3c21aaf5a0082bce02caf9e3de6dc159c9df91477786028e7380be451d2fb94ed83070e85b588b4ed9d540461d3256bd2aafd3ae0fefa92f82799064414d0ed9e667bc18ad0f48505a2ae9b790a4363fcbef4b526453f91e9572835feabb25aebe2ff38c9abff32b6140c39cb71f8cf0491b850203010001","value":5.0,"signature":"a3da555fe4afe5fc957d466161dbae8b7fbb02c22780cae6fd5a4bbdc3ad7b8753361f74948db662086209c4272ebdadf5b7a14216c18be7f1c3b86ddb3aa43267792f3edc99cc7294fa89bc95f90cfb0ecd2df73b0dde8520499836f86b57af79d837b3c3dc806a37d067ca4a55caee7883bec035fed0b2df40c910cdde99a2"}],'timestamp':'09/23/2019,16:08:19','previous_hash':'This_Is_Genesis_Block','hash':'00e63fb0a8474d78df37e0ba99816d526ba110fc16098ecae65358890975a645','nonce':222}
prnt(block, truncate=True, width=80) # 출력 결과가 깨져보일 경우 width 값을 조정해보세요! (ex. 60, 70, etc.)