18 minute read

folium을 이용한 개선시급지역 시각화

import pandas as pd
from pandas import Series, DataFrame
import numpy as np
import matplotlib
matplotlib.rcParams['font.family'].insert(0, 'Malgun Gothic') 
%matplotlib inline

Open API 활용하여 데이터 가져오기

import requests
response=requests.get('http://openapi.seoul.go.kr:8088/sample/xml/VwsmTrdarFlpopQq/1/5/')
#response.text
response=requests.get('http://openapi.seoul.go.kr:8088/sample/json/VwsmTrdarFlpopQq/1/5/') # xml -> json
#response.text
type(response.text)
str
  • json (Java Script Object Notation)
  • 키(Key)와 값(Value)로 이루어진 데이터 객체
  • 파이썬의 사전(Dict) 데이터 타입과 동일
  • json online editor: https://jsoneditoronline.org/
# json test를 사전 타입으로 읽어오기
import json
data = json.loads(response.text)
type(data)
dict
#data
data['VwsmTrdarFlpopQq']['row'][0]['TRDAR_SE_CD_NM']
'골목상권'
# json 데이터 구조 파악
# https://jsoneditoronline.org/ 

데이터 실전 분석: 서울시 유동인구 분석

1. 데이터 적재

유동인구 = pd.read_excel('data/서울시유동인구/0_유동인구_유동인구기본_2015.xlsx', skiprows=[0, 1, 3])
location = pd.read_excel('data/서울시유동인구/4_유동인구_조사지점정보_2015.xlsx', sheet_name=None,
                         skiprows=[0, 1, 3])
location.keys()
dict_keys(['유동인구_조사지점정보_2015', 'FTPTH_STLE_CN', 'GU_CD', 'DONG_CD', 'SM_GU_CD'])
조사지점 = location['유동인구_조사지점정보_2015']
구코드 = location['GU_CD']
동코드 = location.get('DONG_CD')
집계구코드 = location.get('SM_GU_CD')
유동인구.head()
ID유동인구조사 조사지점코드 조사구분 조사요일 시간대 유동인구수 년도
0 1 30-001 지하철 07시-08시 129.0 2015
1 2 30-001 지하철 08시-09시 112.5 2015
2 3 30-001 지하철 09시-10시 108.0 2015
3 4 30-001 지하철 10시-11시 94.5 2015
4 5 30-001 지하철 11시-12시 87.0 2015
조사지점.head()
조사지점코드 조사지점명 구코드 동코드 주번지 부번지 도로명 보도너비 차선수 버스차로유무 ... 지구중심상세내용 도심부도심지역명 용도구분 거주유형구분 입지유형명 X좌표 Y좌표 집계구코드 년도 조사구분
0 01-003 신흥모피명품전문크리닝. 11010.0 1101055.0 127 11 NaN 3.0 8.0 ... NaN NaN 2종주거 NaN NaN 196423.97707 455511.52968 1.101055e+12 2015 본조사
1 01-004 GS25 11010.0 1101055.0 94 2 세검정로 230 3.0 7.0 ... NaN NaN 2종주거 NaN NaN 196315.80243 455621.38262 1.101055e+12 2015 본조사
2 01-005 세검정정류장 11010.0 1101055.0 92 0 세검정길 4.0 5.0 ... NaN NaN 1종주거 NaN NaN 196357.17125 455680.82580 1.101055e+12 2015 본조사
3 01-008 안성타워內 굿모닝파워공인중개사. 11010.0 1101056.0 72 72 NaN 4.0 4.0 ... NaN NaN 2종주거 NaN NaN 197904.19277 456718.34996 1.101056e+12 2015 본조사
4 01-009 복실 손뜨기. 11010.0 1101056.0 88 46 NaN 2.0 7.0 ... NaN NaN 2종주거 NaN NaN 196360.44943 456405.89296 1.101056e+12 2015 본조사

5 rows × 37 columns

2. 데이터 탐색

2.1 유동인구 데이터 탐색

유동인구.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 102956 entries, 0 to 102955
Data columns (total 7 columns):
 #   Column    Non-Null Count   Dtype  
---  ------    --------------   -----  
 0   ID유동인구조사  102956 non-null  int64  
 1   조사지점코드    102956 non-null  object 
 2   조사구분      102956 non-null  object 
 3   조사요일      102956 non-null  object 
 4   시간대       102956 non-null  object 
 5   유동인구수     102956 non-null  float64
 6   년도        102956 non-null  int64  
dtypes: float64(1), int64(2), object(4)
memory usage: 5.5+ MB
# 조사구분이 본조사인 데이터만 선택
유동인구 = 유동인구[유동인구.조사구분 == '본조사']
# 목요일 정보를 화요일로 변경
유동인구.조사요일 = 유동인구.조사요일.replace('목','화')
columns = ['조사구분', '조사요일', '시간대']
for col in columns:
    print(유동인구[col].value_counts())
    print()
본조사    85890
Name: 조사구분, dtype: int64

금    17178
수    17178
월    17178
토    17178
화    17178
Name: 조사요일, dtype: int64

07시-08시    6135
08시-09시    6135
09시-10시    6135
10시-11시    6135
11시-12시    6135
12시-13시    6135
13시-14시    6135
14시-15시    6135
15시-16시    6135
16시-17시    6135
17시-18시    6135
18시-19시    6135
19시-20시    6135
20시-21시    6135
Name: 시간대, dtype: int64
# 유동인구 조사지점 개수
len(유동인구.조사지점코드.unique())
1227
# 조사지점별 조사 횟수 확인
유동인구.조사지점코드.value_counts().value_counts()
70    1227
Name: 조사지점코드, dtype: int64

3.2.2 조사지점 데이터 탐색

조사지점.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1500 entries, 0 to 1499
Data columns (total 37 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   조사지점코드    1500 non-null   object 
 1   조사지점명     1500 non-null   object 
 2   구코드       1408 non-null   float64
 3   동코드       1408 non-null   float64
 4   주번지       1418 non-null   object 
 5   부번지       1373 non-null   object 
 6   도로명       1233 non-null   object 
 7   보도너비      1421 non-null   float64
 8   차선수       1421 non-null   float64
 9   버스차로유무    1421 non-null   object 
 10  중앙선여부     1421 non-null   object 
 11  장애물유무     1421 non-null   object 
 12  장애물종류     1421 non-null   object 
 13  보행도로구분    1421 non-null   object 
 14  점자블록유무    1421 non-null   object 
 15  경사로유무     1421 non-null   object 
 16  펜스유무      1421 non-null   object 
 17  버스정류장유무   1421 non-null   object 
 18  기타시설유무    0 non-null      float64
 19  지하철유무     1421 non-null   object 
 20  횡단보도유무    1421 non-null   object 
 21  보도형태      0 non-null      float64
 22  글로벌존지역명   0 non-null      float64
 23  주거지역명     0 non-null      float64
 24  지역중심명     0 non-null      float64
 25  지역중심상세명   0 non-null      float64
 26  지구중심명     0 non-null      float64
 27  지구중심상세내용  0 non-null      float64
 28  도심부도심지역명  0 non-null      float64
 29  용도구분      1273 non-null   object 
 30  거주유형구분    0 non-null      float64
 31  입지유형명     0 non-null      float64
 32  X좌표       1413 non-null   float64
 33  Y좌표       1413 non-null   float64
 34  집계구코드     1413 non-null   float64
 35  년도        1500 non-null   int64  
 36  조사구분      1500 non-null   object 
dtypes: float64(18), int64(1), object(18)
memory usage: 433.7+ KB
# 본조사만 선택
조사지점 = 조사지점[조사지점.조사구분 == '본조사']
조사지점.조사구분.value_counts()
본조사    1227
Name: 조사구분, dtype: int64
# 구코드가 누락된 데이터는 제거
# 해당 데이터는 미활용하기로 결정
조사지점 = 조사지점[조사지점.구코드.notnull()]
# 필요한 칼럼만 조사지점에서 선택
조사지점 = 조사지점[['조사지점코드','조사지점명','구코드','동코드','X좌표','Y좌표']]

3.2.3 데이터 합치기 - 분석에 필요한 컬럼들만 선택 (어떤 분석을 할지에 따라 변경 가능함)

최종데이터 = pd.merge(유동인구, 조사지점)
최종데이터.head()
ID유동인구조사 조사지점코드 조사구분 조사요일 시간대 유동인구수 년도 조사지점명 구코드 동코드 X좌표 Y좌표
0 3067 01-003 본조사 07시-08시 21.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968
1 3068 01-003 본조사 08시-09시 36.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968
2 3069 01-003 본조사 09시-10시 27.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968
3 3070 01-003 본조사 10시-11시 51.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968
4 3071 01-003 본조사 11시-12시 36.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968
최종데이터 = pd.merge(최종데이터, 구코드[['구코드','구명']])
최종데이터
ID유동인구조사 조사지점코드 조사구분 조사요일 시간대 유동인구수 년도 조사지점명 구코드 동코드 X좌표 Y좌표 구명
0 3067 01-003 본조사 07시-08시 21.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구
1 3068 01-003 본조사 08시-09시 36.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구
2 3069 01-003 본조사 09시-10시 27.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구
3 3070 01-003 본조사 10시-11시 51.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구
4 3071 01-003 본조사 11시-12시 36.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구
... ... ... ... ... ... ... ... ... ... ... ... ... ...
84835 88952 25-815 본조사 16시-17시 672.0 2015 현대자동차 성내지점 11250.0 1125066.0 211778.66913 448392.48055 강동구
84836 88953 25-815 본조사 17시-18시 732.0 2015 현대자동차 성내지점 11250.0 1125066.0 211778.66913 448392.48055 강동구
84837 88954 25-815 본조사 18시-19시 1077.0 2015 현대자동차 성내지점 11250.0 1125066.0 211778.66913 448392.48055 강동구
84838 88955 25-815 본조사 19시-20시 753.0 2015 현대자동차 성내지점 11250.0 1125066.0 211778.66913 448392.48055 강동구
84839 88956 25-815 본조사 20시-21시 546.0 2015 현대자동차 성내지점 11250.0 1125066.0 211778.66913 448392.48055 강동구

84840 rows × 13 columns

최종데이터 = pd.merge(최종데이터, 동코드[['동코드','동명']])
최종데이터
ID유동인구조사 조사지점코드 조사구분 조사요일 시간대 유동인구수 년도 조사지점명 구코드 동코드 X좌표 Y좌표 구명 동명
0 3067 01-003 본조사 07시-08시 21.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동
1 3068 01-003 본조사 08시-09시 36.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동
2 3069 01-003 본조사 09시-10시 27.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동
3 3070 01-003 본조사 10시-11시 51.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동
4 3071 01-003 본조사 11시-12시 36.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
84835 88742 25-430 본조사 16시-17시 2463.0 2015 신동아생활용품DC마트 11250.0 1125072.0 211375.75228 450168.33297 강동구 암사1동
84836 88743 25-430 본조사 17시-18시 2331.0 2015 신동아생활용품DC마트 11250.0 1125072.0 211375.75228 450168.33297 강동구 암사1동
84837 88744 25-430 본조사 18시-19시 2160.0 2015 신동아생활용품DC마트 11250.0 1125072.0 211375.75228 450168.33297 강동구 암사1동
84838 88745 25-430 본조사 19시-20시 1929.0 2015 신동아생활용품DC마트 11250.0 1125072.0 211375.75228 450168.33297 강동구 암사1동
84839 88746 25-430 본조사 20시-21시 1512.0 2015 신동아생활용품DC마트 11250.0 1125072.0 211375.75228 450168.33297 강동구 암사1동

84840 rows × 14 columns

(실습 #1) 유동인구를 시간대 별로 분석하여 시각화하기

ex1 = 최종데이터.pivot_table(index = '시간대', aggfunc = 'mean', values = '유동인구수')
ex1.plot()
<AxesSubplot:xlabel='시간대'>

png

ex1.plot(kind='bar')
<AxesSubplot:xlabel='시간대'>

png

(실습 #2) 구별 유동인구 통계 분석 및 시각화

구별로 조사지점의 개수가 다르므로, 조사지점 당 평균 유동인구수로 분석

# 구별로 조사지점 개수의 차이가 있는지 확인
최종데이터.pivot_table(index = '구코드', values = '조사지점코드',
                       aggfunc = Series.nunique)
조사지점코드
구코드
11010.0 76
11020.0 70
11030.0 50
11040.0 36
11050.0 39
11060.0 47
11070.0 42
11080.0 43
11090.0 33
11100.0 30
11110.0 71
11120.0 35
11130.0 28
11140.0 54
11150.0 53
11160.0 35
11170.0 47
11180.0 23
11190.0 64
11200.0 33
11210.0 41
11220.0 82
11230.0 80
11240.0 64
11250.0 36
구별인구통계 = 최종데이터.pivot_table(index = '구명', values='유동인구수', 
                                      aggfunc = 'mean')
구별인구통계.plot(kind='bar')
<AxesSubplot:xlabel='구명'>

png

구별인구통계.plot(kind='barh')
<AxesSubplot:ylabel='구명'>

png

# 지도에 시각화
import folium
map1 = folium.Map(location=[37.566535, 126.9779691999996], zoom_start=10,
                  zoom_control=False, control_scale=True,
                  tiles='https://tiles.stadiamaps.com/tiles/outdoors/{z}/{x}/{y}{r}.png',
                  attr='&copy; <a href="https://stadiamaps.com/">Stadia Maps</a>, &copy; <a href="https://openmaptiles.org/">OpenMapTiles</a> &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors')
map1

png

# GeoJson으로 행정구역 경계 표시
import json
with open('data/seoul_muncipalities_geo.json', encoding='utf-8') as file:
    geo = json.loads(file.read())
    file.close()
구별인구통계.reset_index()
구명 유동인구수
0 강남구 478.024821
1 강동구 523.748810
2 강북구 369.967532
3 강서구 304.261224
4 관악구 555.743206
5 광진구 405.991209
6 구로구 482.763830
7 금천구 409.511180
8 노원구 327.557143
9 도봉구 235.841429
10 동대문구 256.464438
11 동작구 596.800000
12 마포구 355.050794
13 서대문구 557.156633
14 서초구 557.379617
15 성동구 371.584524
16 성북구 307.876744
17 송파구 286.147768
18 양천구 361.187062
19 영등포구 380.809821
20 용산구 410.963143
21 은평구 392.635102
22 종로구 799.980451
23 중구 1105.794490
24 중랑구 250.935714
choro = folium.Choropleth(geo_data = geo,
                  name = 'choropleth',
                  data = 구별인구통계.reset_index(),
                  columns = ['구명','유동인구수'],
                  key_on = 'feature.properties.SIG_KOR_NM',
                  legend_name = '인구수',
                  fill_color = 'BuPu').add_to(map1)
map1

png

# 호버링 시 구이름 나타나게 하기
choro.geojson.add_child(folium.features.GeoJsonTooltip(['SIG_KOR_NM']))
<folium.features.GeoJson at 0x2b3c4918988>
map1

png

(실습 #3) 서울시 각 구별로 출퇴근/그 외 시간의 유동인구 분석 (출퇴근 시간은 7시-10시, 18시-21시로 가정함)

최종데이터.head()
ID유동인구조사 조사지점코드 조사구분 조사요일 시간대 유동인구수 년도 조사지점명 구코드 동코드 X좌표 Y좌표 구명 동명
0 3067 01-003 본조사 07시-08시 21.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동
1 3068 01-003 본조사 08시-09시 36.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동
2 3069 01-003 본조사 09시-10시 27.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동
3 3070 01-003 본조사 10시-11시 51.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동
4 3071 01-003 본조사 11시-12시 36.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동
최종데이터['출퇴근여부'] = 최종데이터.시간대.map({
    '07시-08시' : '출근',
    '08시-09시' : '출근',
    '09시-10시' : '출근',
    '10시-11시' : '일반',
    '11시-12시' : '일반',
    '12시-13시' : '일반',
    '13시-14시' : '일반',
    '14시-15시' : '일반',
    '15시-16시' : '일반',
    '16시-17시' : '일반',
    '17시-18시' : '일반',
    '18시-19시' : '퇴근',
    '19시-20시' : '퇴근',
    '20시-21시' : '퇴근'
})
최종데이터.head(10)
ID유동인구조사 조사지점코드 조사구분 조사요일 시간대 유동인구수 년도 조사지점명 구코드 동코드 X좌표 Y좌표 구명 동명 출퇴근여부
0 3067 01-003 본조사 07시-08시 21.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동 출근
1 3068 01-003 본조사 08시-09시 36.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동 출근
2 3069 01-003 본조사 09시-10시 27.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동 출근
3 3070 01-003 본조사 10시-11시 51.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동 일반
4 3071 01-003 본조사 11시-12시 36.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동 일반
5 3072 01-003 본조사 12시-13시 48.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동 일반
6 3073 01-003 본조사 13시-14시 30.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동 일반
7 3074 01-003 본조사 14시-15시 21.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동 일반
8 3075 01-003 본조사 15시-16시 30.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동 일반
9 3076 01-003 본조사 16시-17시 39.0 2015 신흥모피명품전문크리닝. 11010.0 1101055.0 196423.97707 455511.52968 종로구 부암동 일반
출퇴근시간대유동인구 = 최종데이터.pivot_table(index='구명', columns='출퇴근여부',
                       values='유동인구수', aggfunc='mean')
출퇴근시간대유동인구.plot(kind='barh', figsize=(5, 7))
<AxesSubplot:ylabel='구명'>

png

(실습 #4) 평일/주말 유동인구 분석 및 시각화

최종데이터.조사요일.value_counts()
금    16968
수    16968
월    16968
토    16968
화    16968
Name: 조사요일, dtype: int64
# 월, 화, 수 -> 평일(0)
# 금, 토 -> 주말(1)
def 주말여부(x):
    if x in ['월','화','수']:
        return 0
    elif x in ['금','토']:
        return 1
    else:
        return -1
최종데이터['주말여부'] = 최종데이터.조사요일.apply(주말여부)
# 다른방법
최종데이터['주말여부'] = 최종데이터.조사요일.map({
    '월':0,
    '화':0,
    '수':0,
    '금':1,
    '토':1
})
# 평일/주말별 구별 유동인구
ex4 = 최종데이터.pivot_table(index='구명', columns='주말여부',
                 values='유동인구수', aggfunc='mean')
ex4.columns = ['평일','주말']
ex4
평일 주말
구명
강남구 481.161607 473.319643
강동구 511.746032 541.752976
강북구 366.655844 374.935065
강서구 307.132653 299.954082
관악구 559.986063 549.378920
광진구 398.208791 417.664835
구로구 486.550152 477.084347
금천구 426.372671 384.218944
노원구 326.564386 329.046278
도봉구 237.211905 233.785714
동대문구 257.907295 254.300152
동작구 597.783550 595.324675
마포구 345.541005 369.315476
서대문구 571.933673 534.991071
서초구 551.793554 565.758711
성동구 377.960317 362.020833
성북구 306.342193 310.178571
송파구 279.435268 296.216518
양천구 364.090296 356.832210
영등포구 379.199777 383.224888
용산구 390.315714 441.934286
은평구 392.036735 393.532653
종로구 775.431391 836.804041
중구 1058.997959 1175.989286
중랑구 247.295918 256.395408
ex4['주말평일차이'] = ex4.주말 - ex4.평일
matplotlib.rcParams['axes.unicode_minus'] = False
# 마이너스 표시 깨짐 방지
ex4.주말평일차이.sort_values().plot(kind='barh')
<AxesSubplot:ylabel='구명'>

png

(실습 #5) 유동인구의 수와 보행환경 정보를 분석

  • 보행환경 정보는 유동인구상세로그_2015.xlsx의 보행환경 컬럼 참고. 매우불만족(1), 약간불만족(2), 보통(3), 약간만족(4), 매우만족(5)

    5.1 보행 환경 개선이 시급한 곳 10군데를 선정.

속성조사 = pd.read_excel('data/서울시유동인구/2_유동인구_속성조사_2015.xlsx',
                         skiprows = [0, 1, 3])
속성조사.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20000 entries, 0 to 19999
Data columns (total 16 columns):
 #   Column     Non-Null Count  Dtype  
---  ------     --------------  -----  
 0   ID속성조사     20000 non-null  int64  
 1   조사지점코드     20000 non-null  object 
 2   조사일자       20000 non-null  int64  
 3   조사요일       20000 non-null  object 
 4   남여구분       20000 non-null  object 
 5   조사시간대      0 non-null      float64
 6   조사시간대_텍스트  20000 non-null  object 
 7   연령대        20000 non-null  object 
 8   거주지        19118 non-null  object 
 9   통행주목적      20000 non-null  object 
 10  방문횟수       20000 non-null  object 
 11  동행자명       0 non-null      float64
 12  교통수단       0 non-null      float64
 13  보행환경       20000 non-null  object 
 14  직업명        18574 non-null  object 
 15  년도         20000 non-null  int64  
dtypes: float64(3), int64(3), object(10)
memory usage: 2.4+ MB
속성조사
ID속성조사 조사지점코드 조사일자 조사요일 남여구분 조사시간대 조사시간대_텍스트 연령대 거주지 통행주목적 방문횟수 동행자명 교통수단 보행환경 직업명 년도
0 1 01-003 1016 여자 NaN 오전11시~오후2시 50-54세 종로구 업무관련 주1~2회 NaN NaN 약간만족 전문/자유직 2015
1 2 01-003 1016 남자 NaN 오전11시~오후2시 60-64세 성북구 업무관련 주3~5회 NaN NaN 매우만족 일용/작업직 2015
2 3 01-003 1016 남자 NaN 오전11시~오후2시 65세이상 종로구 업무관련 주3~5회 NaN NaN 약간만족 일용/작업직 2015
3 4 01-003 1016 남자 NaN 오전7시30분~11시 45-49세 서대문구 출근 매일 NaN NaN 약간만족 사무/기술직 2015
4 5 01-003 1016 남자 NaN 오전7시30분~11시 35-39세 도봉구 출근 매일 NaN NaN 매우만족 사무/기술직 2015
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
19995 19996 25-463 1031 여자 NaN 오후5시~8시 20-24세 NaN 여가/오락/친교/모임/식사(회식,음료포함) 오늘처음 NaN NaN 보통 (대)학생 2015
19996 19997 25-463 1031 여자 NaN 오후5시~8시 45-49세 강동구 여가/오락/친교/모임/식사(회식,음료포함) 6개월1~3회 NaN NaN 보통 (전업)주부 2015
19997 19998 25-463 1031 여자 NaN 오전7시30분~11시 45-49세 강동구 회사또는근무지(사업장등)로복귀 6개월1~3회 NaN NaN 보통 판매/서비스직 2015
19998 19999 25-463 1031 여자 NaN 오전7시30분~11시 40-44세 강동구 출근 주1~2회 NaN NaN 약간불만족 일용/작업직 2015
19999 20000 25-463 1031 남자 NaN 오후5시~8시 45-49세 강동구 개인용무/집안일(병원,은행,관공서,종교활동,봉사활동등) 월1~2회 NaN NaN 매우불만족 사무/기술직 2015

20000 rows × 16 columns

최종데이터.조사지점코드.nunique()
1212
# 조사지점 개수 확인
속성조사.조사지점코드.nunique()
1000
# 전체 조사 다 한건 아님. 전체 1212개 중 딱 1000개만 조사함.
속성조사.조사지점코드.value_counts().value_counts()
# 모든 조사지점에서 20번의 속성조사가 이루어짐
20    1000
Name: 조사지점코드, dtype: int64
속성조사.조사요일.value_counts()
금    10000
토    10000
Name: 조사요일, dtype: int64
속성조사.pivot_table(index = '조사지점코드', columns = '조사요일',
                values = 'ID속성조사', aggfunc='count')
속성조사
ID속성조사 조사지점코드 조사일자 조사요일 남여구분 조사시간대 조사시간대_텍스트 연령대 거주지 통행주목적 방문횟수 동행자명 교통수단 보행환경 직업명 년도
0 1 01-003 1016 여자 NaN 오전11시~오후2시 50-54세 종로구 업무관련 주1~2회 NaN NaN 약간만족 전문/자유직 2015
1 2 01-003 1016 남자 NaN 오전11시~오후2시 60-64세 성북구 업무관련 주3~5회 NaN NaN 매우만족 일용/작업직 2015
2 3 01-003 1016 남자 NaN 오전11시~오후2시 65세이상 종로구 업무관련 주3~5회 NaN NaN 약간만족 일용/작업직 2015
3 4 01-003 1016 남자 NaN 오전7시30분~11시 45-49세 서대문구 출근 매일 NaN NaN 약간만족 사무/기술직 2015
4 5 01-003 1016 남자 NaN 오전7시30분~11시 35-39세 도봉구 출근 매일 NaN NaN 매우만족 사무/기술직 2015
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
19995 19996 25-463 1031 여자 NaN 오후5시~8시 20-24세 NaN 여가/오락/친교/모임/식사(회식,음료포함) 오늘처음 NaN NaN 보통 (대)학생 2015
19996 19997 25-463 1031 여자 NaN 오후5시~8시 45-49세 강동구 여가/오락/친교/모임/식사(회식,음료포함) 6개월1~3회 NaN NaN 보통 (전업)주부 2015
19997 19998 25-463 1031 여자 NaN 오전7시30분~11시 45-49세 강동구 회사또는근무지(사업장등)로복귀 6개월1~3회 NaN NaN 보통 판매/서비스직 2015
19998 19999 25-463 1031 여자 NaN 오전7시30분~11시 40-44세 강동구 출근 주1~2회 NaN NaN 약간불만족 일용/작업직 2015
19999 20000 25-463 1031 남자 NaN 오후5시~8시 45-49세 강동구 개인용무/집안일(병원,은행,관공서,종교활동,봉사활동등) 월1~2회 NaN NaN 매우불만족 사무/기술직 2015

20000 rows × 16 columns

속성조사.보행환경.value_counts().sort_index()
매우만족      772
매우불만족    2388
보통       9533
약간만족     2563
약간불만족    4744
Name: 보행환경, dtype: int64
속성조사['보행점수'] = 속성조사.보행환경.map({
    '매우불만족':1,
    '약간불만족':2,
    '보통':3,
    '약간만족':4,
    '매우만족':5
})
속성조사
ID속성조사 조사지점코드 조사일자 조사요일 남여구분 조사시간대 조사시간대_텍스트 연령대 거주지 통행주목적 방문횟수 동행자명 교통수단 보행환경 직업명 년도 보행점수
0 1 01-003 1016 여자 NaN 오전11시~오후2시 50-54세 종로구 업무관련 주1~2회 NaN NaN 약간만족 전문/자유직 2015 4
1 2 01-003 1016 남자 NaN 오전11시~오후2시 60-64세 성북구 업무관련 주3~5회 NaN NaN 매우만족 일용/작업직 2015 5
2 3 01-003 1016 남자 NaN 오전11시~오후2시 65세이상 종로구 업무관련 주3~5회 NaN NaN 약간만족 일용/작업직 2015 4
3 4 01-003 1016 남자 NaN 오전7시30분~11시 45-49세 서대문구 출근 매일 NaN NaN 약간만족 사무/기술직 2015 4
4 5 01-003 1016 남자 NaN 오전7시30분~11시 35-39세 도봉구 출근 매일 NaN NaN 매우만족 사무/기술직 2015 5
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
19995 19996 25-463 1031 여자 NaN 오후5시~8시 20-24세 NaN 여가/오락/친교/모임/식사(회식,음료포함) 오늘처음 NaN NaN 보통 (대)학생 2015 3
19996 19997 25-463 1031 여자 NaN 오후5시~8시 45-49세 강동구 여가/오락/친교/모임/식사(회식,음료포함) 6개월1~3회 NaN NaN 보통 (전업)주부 2015 3
19997 19998 25-463 1031 여자 NaN 오전7시30분~11시 45-49세 강동구 회사또는근무지(사업장등)로복귀 6개월1~3회 NaN NaN 보통 판매/서비스직 2015 3
19998 19999 25-463 1031 여자 NaN 오전7시30분~11시 40-44세 강동구 출근 주1~2회 NaN NaN 약간불만족 일용/작업직 2015 2
19999 20000 25-463 1031 남자 NaN 오후5시~8시 45-49세 강동구 개인용무/집안일(병원,은행,관공서,종교활동,봉사활동등) 월1~2회 NaN NaN 매우불만족 사무/기술직 2015 1

20000 rows × 17 columns

# 보행환경 개선이 시급한 지역
# 보행점수와 유동인구를 함께 고려
지점별보행점수 = 속성조사.pivot_table(index = '조사지점코드', values = '보행점수',
                aggfunc='mean')
지점별보행점수
보행점수
조사지점코드
01-003 3.90
01-005 2.35
01-008 2.60
01-009 3.05
01-016 2.60
... ...
25-242 2.75
25-408 2.75
25-430 1.70
25-434 3.80
25-463 2.60

1000 rows × 1 columns

지점별유동인구 = 최종데이터.pivot_table(index = '조사지점코드', values = '유동인구수',
                aggfunc='mean')
지점별유동인구
유동인구수
조사지점코드
01-003 41.357143
01-004 198.171429
01-005 150.128571
01-008 75.942857
01-009 80.528571
... ...
25-408 1548.042857
25-430 1371.985714
25-434 114.085714
25-463 218.100000
25-815 602.014286

1212 rows × 1 columns

ex5 = pd.concat([지점별보행점수, 지점별유동인구], axis=1, join='inner') # 기본값=없는값도Nan으로포함=outer
ex5                                                                     # merge는 기본값이 inner (반대)
보행점수 유동인구수
조사지점코드
01-003 3.90 41.357143
01-005 2.35 150.128571
01-008 2.60 75.942857
01-009 3.05 80.528571
01-016 2.60 889.542857
... ... ...
25-242 2.75 328.200000
25-408 2.75 1548.042857
25-430 1.70 1371.985714
25-434 3.80 114.085714
25-463 2.60 218.100000

992 rows × 2 columns

ex5.info()
<class 'pandas.core.frame.DataFrame'>
Index: 992 entries, 01-003 to 25-463
Data columns (total 2 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   보행점수    992 non-null    float64
 1   유동인구수   992 non-null    float64
dtypes: float64(2)
memory usage: 23.2+ KB
# 보행점수가 2점 미만인 지역 중 유동인구가 많은 10개 지역 선정
개선시급지역 = ex5[ex5.보행점수 < 2].nlargest(10, '유동인구수')
개선시급지역
보행점수 유동인구수
조사지점코드
01-213 1.95 3368.914286
17-2055 1.45 3242.828571
01-2095 1.20 2193.857143
23-076 1.90 1928.142857
01-084 1.85 1831.928571
02-260 1.70 1810.328571
17-082 1.70 1572.342857
01-082 1.90 1502.528571
23-034 1.35 1389.728571
25-430 1.70 1371.985714
# 개선시급지역을 지도에 시각화
개선시급지역= 개선시급지역.reset_index()
개선시급지역 = pd.merge(개선시급지역, 조사지점[['조사지점코드','조사지점명','X좌표','Y좌표']])
개선시급지역
조사지점코드 보행점수 유동인구수 조사지점명 X좌표 Y좌표
0 01-213 1.95 3368.914286 cafe nanuri 198528.06633 453727.84806
1 17-2055 1.45 3242.828571 디큐브시티 190016.40000 445484.99021
2 01-2095 1.20 2193.857143 동아일보 198026.60682 452265.43991
3 23-076 1.90 1928.142857 코끼리상가 202812.00572 447645.09507
4 01-084 1.85 1831.928571 광화문 빌딩 197893.71130 452255.02517
5 02-260 1.70 1810.328571 스타벅스 198571.61688 451327.19366
6 17-082 1.70 1572.342857 자전거 주차장 190463.23371 445313.73349
7 01-082 1.90 1502.528571 준코 뮤직타운 198721.18954 452307.83355
8 23-034 1.35 1389.728571 capital tower 앞 203045.16639 444620.39654
9 25-430 1.70 1371.985714 신동아생활용품DC마트 211375.75228 450168.33297
from pyproj import Proj, transform
def convert_loc(x,y):
    inProj = Proj(init='epsg:5181')
    outProj = Proj(init='epsg:4326')
    x2, y2 = transform(inProj, outProj, x, y)
    return [y2, x2]
import warnings
warnings.filterwarnings("ignore")
개선시급지역['위경도']=개선시급지역.apply(lambda 최종데이터: convert_loc(최종데이터.X좌표, 최종데이터.Y좌표), axis=1)
개선시급지역
조사지점코드 보행점수 유동인구수 조사지점명 X좌표 Y좌표 위경도
0 01-213 1.95 3368.914286 cafe nanuri 198528.06633 453727.84806 [37.58310456094619, 126.98333547159028]
1 17-2055 1.45 3242.828571 디큐브시티 190016.40000 445484.99021 [37.50878369212982, 126.8870826364644]
2 01-2095 1.20 2193.857143 동아일보 198026.60682 452265.43991 [37.56992740808484, 126.97766212376528]
3 23-076 1.90 1928.142857 코끼리상가 202812.00572 447645.09507 [37.528295993738254, 127.03181287428026]
4 01-084 1.85 1831.928571 광화문 빌딩 197893.71130 452255.02517 [37.569833277541, 126.9761578394427]
5 02-260 1.70 1810.328571 스타벅스 198571.61688 451327.19366 [37.56147485263183, 126.98383320628312]
6 17-082 1.70 1572.342857 자전거 주차장 190463.23371 445313.73349 [37.50724538640684, 126.89213866497093]
7 01-082 1.90 1502.528571 준코 뮤직타운 198721.18954 452307.83355 [37.57031059866805, 126.98552439699829]
8 23-034 1.35 1389.728571 capital tower 앞 203045.16639 444620.39654 [37.501042607485395, 127.03443814970538]
9 25-430 1.70 1371.985714 신동아생활용품DC마트 211375.75228 450168.33297 [37.55096447551782, 127.12873547021874]
for x, y in 개선시급지역.위경도:
    print(type(x))
<class 'float'>
<class 'float'>
<class 'float'>
<class 'float'>
<class 'float'>
<class 'float'>
<class 'float'>
<class 'float'>
<class 'float'>
<class 'float'>
for idx, val in 개선시급지역.iterrows():
    print(val.위경도)
[37.58310456094619, 126.98333547159028]
[37.50878369212982, 126.8870826364644]
[37.56992740808484, 126.97766212376528]
[37.528295993738254, 127.03181287428026]
[37.569833277541, 126.9761578394427]
[37.56147485263183, 126.98383320628312]
[37.50724538640684, 126.89213866497093]
[37.57031059866805, 126.98552439699829]
[37.501042607485395, 127.03443814970538]
[37.55096447551782, 127.12873547021874]
map4 = folium.Map(location=[37.566535, 126.9779691999996], zoom_start=10,
                  zoom_control=False, control_scale=True,
                  tiles='https://tiles.stadiamaps.com/tiles/outdoors/{z}/{x}/{y}{r}.png',
                  attr='&copy; <a href="https://stadiamaps.com/">Stadia Maps</a>, &copy; <a href="https://openmaptiles.org/">OpenMapTiles</a> &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors')
map4

png

from folium.plugins import MarkerCluster
marker_cluster = MarkerCluster().add_to(map4)
for idx, val in 개선시급지역.iterrows():
    #print(val)
    folium.Marker(location=val.위경도, popup=folium.Popup(val.조사지점명, max_width=300))\
    .add_to(marker_cluster)
map4

png

from folium.plugins import MarkerCluster
marker_cluster = MarkerCluster().add_to(map4)
for idx, val in 개선시급지역.iterrows():
    #print(val)
    msg = '조사지점명:{0}<br> 유동인구수:{1}<br> 보행점수:{2}'.format(val.조사지점명, val.유동인구수, val.보행점수)
    folium.Marker(location=val.위경도, popup=folium.Popup(msg, max_width=300))\
    .add_to(marker_cluster)
map4

png

5.2 구별 보행환경점수 지도 시각화

ex5 = ex5.reset_index()
ex5
조사지점코드 보행점수 유동인구수
0 01-003 3.90 41.357143
1 01-005 2.35 150.128571
2 01-008 2.60 75.942857
3 01-009 3.05 80.528571
4 01-016 2.60 889.542857
... ... ... ...
987 25-242 2.75 328.200000
988 25-408 2.75 1548.042857
989 25-430 1.70 1371.985714
990 25-434 3.80 114.085714
991 25-463 2.60 218.100000

992 rows × 3 columns

ex5 = pd.merge(ex5, 조사지점[['조사지점코드','구코드']], on='조사지점코드')
ex5 = pd.merge(ex5, 구코드[['구코드','구명']])
ex5
조사지점코드 보행점수 유동인구수 구코드 구명
0 01-003 3.90 41.357143 11010.0 종로구
1 01-005 2.35 150.128571 11010.0 종로구
2 01-008 2.60 75.942857 11010.0 종로구
3 01-009 3.05 80.528571 11010.0 종로구
4 01-016 2.60 889.542857 11010.0 종로구
... ... ... ... ... ...
987 25-242 2.75 328.200000 11250.0 강동구
988 25-408 2.75 1548.042857 11250.0 강동구
989 25-430 1.70 1371.985714 11250.0 강동구
990 25-434 3.80 114.085714 11250.0 강동구
991 25-463 2.60 218.100000 11250.0 강동구

992 rows × 5 columns

구별보행점수 = ex5.pivot_table(index='구명', values='보행점수', aggfunc='mean')
구별보행점수
보행점수
구명
강남구 2.455072
강동구 2.646774
강북구 2.646296
강서구 2.765517
관악구 2.843548
광진구 2.961765
구로구 2.250000
금천구 2.697368
노원구 2.920161
도봉구 3.002000
동대문구 2.853571
동작구 2.700000
마포구 2.750000
서대문구 2.900000
서초구 2.605797
성동구 2.571667
성북구 2.866667
송파구 2.733333
양천구 2.814286
영등포구 2.490566
용산구 2.867500
은평구 2.817241
종로구 2.600000
중구 2.802000
중랑구 3.145946
import json
with open('data/seoul_muncipalities_geo.json', encoding='utf-8') as file:
    geo=json.loads(file.read())
    file.close()
map2 = folium.Map(location=[37.566535, 126.9779691999996], zoom_start=10,
                  zoom_control=False, control_scale=True,
                  tiles='https://tiles.stadiamaps.com/tiles/outdoors/{z}/{x}/{y}{r}.png',
                  attr='&copy; <a href="https://stadiamaps.com/">Stadia Maps</a>, &copy; <a href="https://openmaptiles.org/">OpenMapTiles</a> &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors')
map2

png

구별보행점수 = 구별보행점수.reset_index()
folium.Choropleth(geo_data = geo,
                 name = 'choropleth',
                 data = 구별보행점수,
                 columns = ['구명','보행점수'],
                 key_on = 'feature.properties.SIG_KOR_NM',
                 legend_name = '보행점수',
                 fill_color = 'BuPu',
                 highlight = True
                 ).add_to(map2)
<folium.features.Choropleth at 0x2b3c6e29408>
choro.geojson.add_child(
    folium.features.GeoJsonTooltip(['SIG_KOR_NM'], labels=False))
<folium.features.GeoJson at 0x2b3c4918988>
map2

png