티스토리 뷰
2-7 좀 더 편리한 시각화 도구 - seaborn¶
seaborn이라는 단어의 시각화 도구가 있다. Matplotlib과 함께 사용하는 것인데 정말 괜찮다. 이 모듈은 터미널에서 pip install seaborn으로 설치하면 된다.
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
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()
간단하게 몇 개의 사인 함수를 그려보았다. seaborn을 import 할 때는 matplotlib도 같이 import 되어야 한다.
sns.set_style("whitegrid")
plt.figure(figsize=(10,6))
plt.plot(x,y1, x,y2, x,y3, x,y4)
plt.show()
seaborn은 whitegrid라는 스타일을 지원한다. 또한 seaborn은 연습할 만한 데이터셋을 몇 개 가지고 있다.
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
sns.set_style("whitegrid")
%matplotlib inline
tips = sns.load_dataset("tips")
tips.head(5)
total_bill | tip | sex | smoker | day | time | size | |
---|---|---|---|---|---|---|---|
0 | 16.99 | 1.01 | Female | No | Sun | Dinner | 2 |
1 | 10.34 | 1.66 | Male | No | Sun | Dinner | 3 |
2 | 21.01 | 3.50 | Male | No | Sun | Dinner | 3 |
3 | 23.68 | 3.31 | Male | No | Sun | Dinner | 2 |
4 | 24.59 | 3.61 | Female | No | Sun | Dinner | 4 |
tips라는 데이터셋인데 요일별 점심,저녁,흡연 여부와 식사 금액과 팁을 정리한 데이터이다.
plt.figure(figsize=(8,6))
sns.boxplot(x="day", y="total_bill", data=tips)
plt.show()
이렇게 boxplot을 그리는데 x축은 요일 y축은 전체금액을 그릴 수 있다. 이것만으로도 꽤 편리하다.
plt.figure(figsize=(8,6))
sns.boxplot(x="day",y="total_bill", hue="smoker", data=tips,palette="Set3")
plt.show()
더 놀라운 것은 hue 라는 옵션을 이용해서 구분할 수 있다. 위 그래프는 흡연여부로 구분한 것이다. 흡연자가 결제 금액의 범위가 크다.
sns.set_style("darkgrid")
sns.lmplot(x="total_bill", y="tip", data=tips, size=7)
plt.show()
/home/jaeyoon89/.local/lib/python3.6/site-packages/seaborn/regression.py:580: UserWarning: The `size` parameter has been renamed to `height`; please update your code.
warnings.warn(msg, UserWarning)
이번에는 dark grid 스타일로 하고 lmplot을 그렸다. 아래처럼 데이터를 scatter처럼 그리고 직선으로 retression한 그림도 같이 그려주고 유효범위도 ci로 잡아준다.
sns.lmplot(x="total_bill",y="tip",hue="smoker",data=tips,palette="Set1",height=7)
plt.show()
또, lmplot도 hue 옵션을 가질 수 있으며 미리 준비된 palette로 색상을 지정할 수 있다.
flights = sns.load_dataset("flights")
flights.head(5)
year | month | passengers | |
---|---|---|---|
0 | 1949 | Jan | 112 |
1 | 1949 | Feb | 118 |
2 | 1949 | Mar | 132 |
3 | 1949 | Apr | 129 |
4 | 1949 | May | 121 |
이번엔 연도 및 월별 항공기 승객수를 기록한 데이터를 가져오자.
flights=flights.pivot("month","year","passengers")
flights.head(5)
year | 1949 | 1950 | 1951 | 1952 | 1953 | 1954 | 1955 | 1956 | 1957 | 1958 | 1959 | 1960 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
month | ||||||||||||
Jan | 112 | 115 | 145 | 171 | 196 | 204 | 242 | 284 | 315 | 340 | 360 | 417 |
Feb | 118 | 126 | 150 | 180 | 196 | 188 | 233 | 277 | 301 | 318 | 342 | 391 |
Mar | 132 | 141 | 178 | 193 | 236 | 235 | 267 | 317 | 356 | 362 | 406 | 419 |
Apr | 129 | 135 | 163 | 181 | 235 | 227 | 269 | 313 | 348 | 348 | 396 | 461 |
May | 121 | 125 | 172 | 183 | 229 | 234 | 270 | 318 | 355 | 363 | 420 | 472 |
pivot 기능으로 간편하게 월별,연도별로 구분할 수 있다. 앞서 언급했다시피 pivot을 상상할 수 있다면 꽤 유용한 겨로가를 얻는다.
plt.figure(figsize=(10,8))
sns.heatmap(flights, annot=True, fmt='d')
plt.show()
heatmapt 이라는 도구를 이용하면 이런 종류의 데이터는 그 경향을 설명하기 좋다.
sns.set(style='ticks')
iris = sns.load_dataset("iris")
iris.head(10)
sepal_length | sepal_width | petal_length | petal_width | species | |
---|---|---|---|---|---|
0 | 5.1 | 3.5 | 1.4 | 0.2 | setosa |
1 | 4.9 | 3.0 | 1.4 | 0.2 | setosa |
2 | 4.7 | 3.2 | 1.3 | 0.2 | setosa |
3 | 4.6 | 3.1 | 1.5 | 0.2 | setosa |
4 | 5.0 | 3.6 | 1.4 | 0.2 | setosa |
5 | 5.4 | 3.9 | 1.7 | 0.4 | setosa |
6 | 4.6 | 3.4 | 1.4 | 0.3 | setosa |
7 | 5.0 | 3.4 | 1.5 | 0.2 | setosa |
8 | 4.4 | 2.9 | 1.4 | 0.2 | setosa |
9 | 4.9 | 3.1 | 1.5 | 0.1 | setosa |
이번에는 머신러닝에서 중요하게 다뤄질 아이리스 꽃에 대한 데이터를 가져오자. 꽃잎 꽃받침의 너비와 폭을 가지고 그 종을 구분할 수 있는지를 알아보자.
sns.pairplot(iris, hue="species")
plt.show()
2-8 범죄 데이터 시각화 하기¶
앞서 학습한 시각화 도구인 seaborn을 이용해서 성과를 얻어보자.
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
import platform
# import platform
from matplotlib import font_manager, rc
plt.rcParams['axes.unicode_minus'] = False
if platform.system() == 'Darwin':
rc('font', family='AppleGothic')
elif platform.system() == 'Windows':
path = "c:/Windows/Fonts/malgun.ttf"
font_name = font_manager.FontProperties(fname=path).get_name()
rc('font', family=font_name)
elif platform.system() == 'Linux':
path = "/usr/share/fonts/NanumGothic.ttf"
font_name = font_manager.FontProperties(fname=path).get_name()
plt.rc('font', family=font_name)
else:
print('Unknown system... sorry~~~~')
위와 같이 그래프에 대한 한글 폰트 문제를 해결하자. 아래와 같이 pariplot 으로 강도,살인,폭력 간의 상관관계를 그래프로 보자.
sns.pairplot(crime_anal_norm, vars=["강도","살인","폭력"], kind='reg', height=3)
plt.show()
강도와 폭력, 살인과 폭력, 강도와 살인 모두 양의 상관관계를 보인다.
sns.pairplot(crime_anal_norm, x_vars=["인구수", "CCTV"],
y_vars=["살인","강도"], kind='reg', height=3)
plt.show()
인구수와 CCTV 개수, 그리고 살인과 강도에 대해 조사했다. 전체적인 상관계수는 CCTV와 살인의 관계가 낮을지 몰라도 CCTV가 없을 때 살인이 많이 일어나는 구간이 있따. 즉, CCTV 개수를 기준으로 좌측면에 살인과 강도의 높은 수를 갖는 데이터를 보인다.
sns.pairplot(crime_anal_norm,
x_vars=["인구수","CCTV"],
y_vars=["살인검거율","폭력검거율"], kind='reg', height=3)
plt.show()
그런데 살인 및 폭력 검거율과 CCTV의 관계가 양의 상관관계가 아니다. 오히려 음의 상관관계도 보인다. 또 인구수와 살인 및 폭력 검거율도 음의 상관관계가 관찰된다.
tmp_max = crime_anal_norm['검거'].max()
crime_anal_norm['검거'] = crime_anal_norm['검거'] / tmp_max * 100
crime_anal_norm_sort = crime_anal_norm.sort_values(by='검거', ascending=False)
crime_anal_norm_sort.head()
강간 | 강도 | 살인 | 절도 | 폭력 | 강간검거율 | 강도검거율 | 살인검거율 | 절도검거율 | 폭력검거율 | 인구수 | CCTV | 범죄 | 검거 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
구별 | ||||||||||||||
도봉구 | 0.000000 | 0.235294 | 0.083333 | 0.000000 | 0.000000 | 100.000000 | 100.0 | 100.0 | 44.967074 | 87.626093 | 348646.0 | 485 | 0.318627 | 100.000000 |
금천구 | 0.141210 | 0.058824 | 0.083333 | 0.172426 | 0.134074 | 80.794702 | 100.0 | 100.0 | 56.668794 | 86.465433 | 255082.0 | 1015 | 0.589867 | 97.997139 |
광진구 | 0.397695 | 0.529412 | 0.166667 | 0.671570 | 0.269094 | 91.666667 | 100.0 | 100.0 | 42.200925 | 83.047619 | 372164.0 | 707 | 2.034438 | 96.375820 |
동대문구 | 0.204611 | 0.470588 | 0.250000 | 0.314061 | 0.250887 | 84.393064 | 100.0 | 100.0 | 41.090358 | 87.401884 | 369496.0 | 1294 | 1.490147 | 95.444250 |
용산구 | 0.265130 | 0.529412 | 0.250000 | 0.169004 | 0.133128 | 89.175258 | 100.0 | 100.0 | 37.700706 | 83.121951 | 244203.0 | 1624 | 1.346674 | 94.776790 |
여기서 검거율의 합계인 검거 항목 최고 값을 100으로 한정하고 그 값으로 정렬한 후
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=.5)
plt.title('범죄 검거 비율 (정규화된 검거의 합으로 정렬)')
plt.show()
결과를 보면 절도 검거율은 다른 검거율에 비해 낮다는 것을 알 수 있다. 그리고 그래프이 하단으로 갈수록 검거율이 낮은데 그 속에 강남3구 중에서 '서초구'가 있다.
target_col = ['강간','강도','살인','절도','폭력','범죄']
crime_anal_norm['범죄'] = crime_anal_norm['범죄'] / 5
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', linewidth=.5)
plt.show()
발생 건수로 보니 강남구,양천구,영등포구가 범죄 발생 건수가 높다. 그리고 송파구와 서초구다 낮다고 볼수 없다. 강남 3구가 안전하다고 하기엔 좀 의문이 생긴다.
crime_anal_norm.to_csv('/home/jaeyoon89/DataScience/data/02. crime_in_Seoul_final.csv', sep=',',
encoding='UTF-8')
2-9 지도 시각화 도구 - Folium¶
지도를 가지고 원하는 데이터를 표현할 수 있다는 것은 매력적인 일이다. 많은 지도 시각화 도구가 있지만 여기서는 Folium 라이브러리를 다루도록 하자. 먼저 터미널을 열고 pip install folium 이라고 입력해서 folium을 설치하자.
import folium
map_osm = folium.Map(location=[45.5236, -122.6750])
map_osm
위 코드처럼 위도와 경도 정보를 주면 지도를 그려준다.
stamen = folium.Map(location=[45.5236, -122.6750], zoom_start=13)
stamen
또 zoom_start라는 옵션으로 확대 비율을 정의할 수도 있다.
stamen = folium.Map(location=[45.5236, -122.6750], tiles='Stamen Toner')
stamen
tiles 옵션으로 위와 같은 모양의 지도도 만들 수 있다.
map_2 = folium.Map(location=[45.5236, -122.6750], tiles='Stamen Toner',
zoom_start=13)
folium.Marker([45.5244, -122.6699], popup='The Waterfront' ).add_to(map_2)
folium.CircleMarker([45.5215, -122.6261], radius=50,
popup='Laurelhurst Park', color='#3186cc',
fill_color='#3186cc', ).add_to(map_2)
map_2
이번에는 지도를 그리고 그 상태에서 원하는 좌표(위도,경도)에 Marker 명령으로 마크를 찍을 수 있다. 그리고 CircleMarker 명령으로 반경과 색상을 지정하면 원을 그려준다.
import folium
import pandas as pd
state_unemployment = '/home/jaeyoon89/DataScience/data/02. folium_US_Unemployment_Oct2012.csv'
state_data = pd.read_csv(state_unemployment)
state_data.head()
State | Unemployment | |
---|---|---|
0 | AL | 7.1 |
1 | AK | 6.8 |
2 | AZ | 8.1 |
3 | AR | 7.2 |
4 | CA | 10.1 |
위 파일에는 2012년 10월 기준 미국의 주별 실업률이 있다. 이것을 지도에 시각화 하려한다. 그러려면 jason 파일이 필요하다. json 파일에는 id로 주별 고유 ID, 그리고 주 이름 등의 좌표가 있다.
state_geo = '/home/jaeyoon89/DataScience/data/02. folium_us-states.json'
map = folium.Map(location=[40, -98], zoom_start=4)
map.choropleth(geo_data=state_geo, data=state_data,
columns=['State', 'Unemployment'],
key_on='feature.id',
fill_color='YlGn',
legend_name='Unemployment Rate(%)')
map
그래서 state_geo 라는 변수에 jason 파일 경로를 담고, folium에서 choropleth 명령으로 json파일과 지도에 표현하고 싶은 데이터를 입력하고 key_on 옵션으로 지도의 id를 알려주면 된다.여기서 지도의 id가 중복되면 안된다.
2-10 서울시 범죄율에 대한 지도 시각화¶
먼저 서울시 구별 경계선을 그릴 수 있는 json 파일이 필요하다. json파일 출처 : (https://github.com/southkorea/southkorea-maps)
import json
geo_path = '/home/jaeyoon89/DataScience/data/02. skorea_municipalities_geo_simple.json'
geo_str = json.load(open(geo_path, encoding='UTF-8'))
먼저 json 파일을 로딩한다. 그리고 서울시의 중심의 위도와 경도 정보를 먼저 입력하고 경계선을 그리는데, 컬러맵은 살인 발생건수로 지정한다.
map = folium.Map(location=[37.5502, 126.982], zoom_start=11,
tiles='Stamen Toner')
map.choropleth(geo_data = geo_str,
data = crime_anal_norm['살인'],
columns = [crime_anal_norm.index, crime_anal_norm['살인']],
fill_color = 'PuRd', #PuRd, YlGnBu
key_on = 'feature.id')
map
그 결과를 보면 살인 발생 건수에서 강남 3구가 안전하다고 보기는 힘들다.
map = folium.Map(location=[37.5502, 126.982], zoom_start=11,
tiles='Stamen Toner')
map.choropleth(geo_data = geo_str,
data = crime_anal_norm['강간'],
columns = [crime_anal_norm.index, crime_anal_norm['강간']],
fill_color = 'PuRd', #PuRd, YlGnBu
key_on = 'feature.id')
map
특히 강간 발생 건수로 다시 그려보면 더더욱 안전하지 않다.
map = folium.Map(location=[37.5502, 126.982], zoom_start=11,
tiles='Stamen Toner')
map.choropleth(geo_data = geo_str,
data = crime_anal_norm['범죄'],
columns = [crime_anal_norm.index, crime_anal_norm['범죄']],
fill_color = 'PuRd', #PuRd, YlGnBu
key_on = 'feature.id')
map
이제 이전에 만들어둔 범죄 발생 건수 전체에 대해 살펴보면 역시 강남 3구와 강서구 주변이 범죄 발생 건수가 높다. 하지만 인구수를 고려해야 할 것 같다. 즉 인구 대비 범죄 발생 비율을 알아보는 것이다. 그래서 범죄 전체 발생건수에 인구수를 나누고 소수점 밑으로 적절한 값을 곱해보자.
tmp_criminal = crime_anal_norm['살인'] / crime_anal_norm['인구수'] * 1000000
map = folium.Map(location=[37.5502, 126.982], zoom_start=11,
tiles='Stamen Toner')
map.choropleth(geo_data = geo_str,
data = tmp_criminal,
columns =[crime_anal.index, tmp_criminal],
fill_color = 'PuRd', #PuRd, YlGnBu
key_on = 'feature.id')
map
인구 대비 범죄 발생 건수로 보면 강남 3구가 1위는 아니지만 안전도가 제일 높다고 말할수는 없다.
2-11 서울시 경찰서별 검거율과 구별 범죄 발생율을 동시에 시각화하기¶
이제 좀 더 진행해서 경찰서별 검거율과 방금 전까지 수행한 범죄 발생율을 동시에 표현하는 게 효과적일 것 같다.
crime_anal_raw['lat'] = station_lat
crime_anal_raw['lng'] = station_lng
col =['살인 검거', '강도 검거', '강간 검거', '절도 검거', '폭력 검거']
tmp = crime_anal_raw[col] / crime_anal_raw[col].max()
crime_anal_raw['검거'] = np.sum(tmp, axis=1)
crime_anal_raw.head()
관서명 | 살인 발생 | 살인 검거 | 강도 발생 | 강도 검거 | 강간 발생 | 강간 검거 | 절도 발생 | 절도 검거 | 폭력 발생 | 폭력 검거 | 구별 | lat | lng | 검거 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 중부서 | 2 | 2 | 3 | 2 | 105 | 65 | 1395 | 477 | 1355 | 1170 | 중구 | 37.563646 | 126.989580 | 1.275416 |
1 | 종로서 | 3 | 3 | 6 | 5 | 115 | 98 | 1070 | 413 | 1278 | 1070 | 종로구 | 37.575548 | 126.984747 | 1.523847 |
2 | 남대문서 | 1 | 0 | 6 | 4 | 65 | 46 | 1153 | 382 | 869 | 794 | 중구 | 37.554758 | 126.973498 | 0.907372 |
3 | 서대문서 | 2 | 2 | 5 | 4 | 154 | 124 | 1812 | 738 | 2056 | 1711 | 서대문구 | 37.564744 | 126.966770 | 1.978299 |
4 | 혜화서 | 3 | 2 | 5 | 4 | 96 | 63 | 1114 | 424 | 1015 | 861 | 종로구 | 37.571853 | 126.998914 | 1.198382 |
검거만 따로 모아둔다. 그리고 앞서 수집해둔 각 경찰서의 위도와 경도 정보를 이용하자.
map = folium.Map(location=[37.5502, 126.982], zoom_start=11)
for n in crime_anal_raw.index:
folium.Marker([crime_anal_raw['lat'][n],
crime_anal_raw['lng'][n]]).add_to(map)
map
먼저 경찰서의 위치를 확인했다.
이제 검거의 정당한 값(10)을 곱해서 원 넓이를 정하고, 경찰서의 검거율을 원의 넓이로 표현하자.
map = folium.Map(location=[37.5502, 126.982], zoom_start=11)
for n in crime_anal_raw.index:
folium.CircleMarker([crime_anal_raw['lat'][n], crime_anal_raw['lng'][n]],
radius = crime_anal_raw['검거'][n]*10,
color='#3186cc', fill_color='#3186cc', fill=True).add_to(map)
map
이러면 이제 각 경찰서의 위치에서 넓은 원을 가지면 검거율이 높다고 보면 된다. 마치 경찰서별 범죄에 대한 방어력이 미치는 범위처럼 보인다. 이제 색상을 붉은 색으로 해서 범죄 발생 건수를 넣으면 될 것 같다.
map = folium.Map(location=[37.5502, 126.982], zoom_start=11)
map.choropleth(geo_data = geo_str,
data = crime_anal_norm['범죄'],
columns = [crime_anal_norm.index, crime_anal_norm['범죄']],
fill_color = 'PuRd', #PuRd, YlGnBu
key_on = 'feature.id')
for n in crime_anal_raw.index:
folium.CircleMarker([crime_anal_raw['lat'][n], crime_anal_raw['lng'][n]],
radius = crime_anal_raw['검거'][n]*10,
color='#3186cc', fill_color='#3186cc', fill=True).add_to(map)
map
위 결과를 보면 범죄가 많이 일어날수록 붉은색이고, 그 속에서 방어력이 높을 수록 큰 원을 가진 경찰서들이 배치된다. 그러면 서울 서부는 범죄는 많이 발생하지만 방어력은 높다. 서울 강북의 중앙부는 경찰서의 검거율도 높지 않지만, 범죄 발생 건수도 높지 않다.
출처 : 파이썬으로 데이터 주무르기
'파이썬으로 데이터 주무르기' 카테고리의 다른 글
파이썬으로 데이터 주무르기 ch.4 (0) | 2021.04.03 |
---|---|
파이썬으로 데이터 주무르기 ch.3-2 (0) | 2021.04.02 |
파이썬으로 데이터 주무르기 ch.3-1 (0) | 2021.04.01 |
파이썬으로 데이터 주무르기 ch.2-1 (0) | 2021.03.30 |
파이썬으로 데이터 주무르기 chapter.1 (0) | 2021.03.28 |