일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 29 | 30 |
- Python
- TiL
- 파이썬
- GIS
- 프로젝트
- 마이온컴퍼니
- 그리디
- parklab
- 멋사
- 멋쟁이사자처럼멋쟁이사자처럼
- likelion
- 멋쟁이사자처럼
- Plotly
- 마이온
- 시각화
- DP
- likelionlikelion
- 멋재이사자처럼
- folium
- BFS
- DFS
- pyhton
- seaborn
- intern10
- 인턴10
- GNN
- Join
- ux·ui디자인
- 알고리즘
- SQL
- Today
- Total
지금은마라톤중
Midproject_(1)_서울시 1인가구 거주지 추천 서비스 본문
2023.03.02
첫 미드 프로젝트를 하였다.
주제가 중간에 엎어지면서 주제를 다시정해서 밤새가며 진행했다.
서울시에 거주하는 20~30대 1인 가구원들에게 지역을 제안해주는 서비스를 만들려고 했다.
이번 포스팅에서는 내가 진행한 전처리에 대해서만 작성하려고 한다.
인구 수는 줄어 들고 있지만, 가구수는 늘어나는 서울의 인구.1인 가구의 연령분포는 주로 사회초년생 등 청년층이 구성하고 있으며 경제적인 자유도가 핵심 생산 연령층에 비해 떨어지기 때문에 서울에서 생활하기 위해 고려할 사항이 더 많다.서울에서 살아가기 위해 이들이 중요하게 보는 지표 등은 무엇이 있는지 세부적으로 분석하여 우선순위별 입지를 알아보고자 한다. |
거주지 선택 시 주요사항
- 치안
- 녹지
- 전월세
- 대중교통
- 편의시설
팀원별로 하나의 주요사항을 정해 전처리를 진행하였다.
나는 편의시설을 맡아 서울시 열린데이터 광장에서
- 서울시 세탁업 인허가 정보,
- 서울시 약국 인허가 정보
- 서울시 휴게음식점 인허가 정보
3가지 데이터를 다운받아 활용하였다.
라이브러리와 데이터 불러오기
import pandas as pd
import numpy as np
from glob import glob
from urllib.request import urlopen
import json
# 지도 시각화를 위한 라이브러리
import folium
# 위도경도 변환을 위한 라이브러리
import pyproj
# 세탁소 정보 불러오기
coin = pd.read_csv("서울시세탁소정보.csv", encoding = "cp949")
coin.head()
coin.shape
# 약국 정보 불러오기
pharm = pd.read_csv("서울시약국정보.csv", encoding = "cp949")
pharm.shape
# 편의점 정보 불러오기
store = pd.read_csv("seoul_store.csv")
store.shape
1) 코인세탁소 정보 전처리
# 사업장명 unique값 확인
# 유니크값이 너무 많아서 확인 불가
coin["사업장명"].unique()
# 사업자명 컬럼으로 변경
# 무인|24|코인|빨래방|세탁방|셀프|유니룩스|크린|워시|월드씨앤에프|월드크리닝
coin = coin.loc[coin["사업장명"].str.contains("무인|24|코인|빨래방|세탁방|셀프|유니룩스|크린|워시|월드씨앤에프|월드크리닝")]
coin
# 필요한 컬럼만 추출
coin = coin[["사업장명", "지번주소","좌표정보(X)","좌표정보(Y)"]].reset_index(drop = True)
coin
# 널값 제거
coin = coin.dropna()
coin
# 자치구 컬럼 만들기
coin_ad_list = coin["지번주소"]
coin_gu = [i.split(" ")[1].strip() for i in list(coin_ad_list)]
coin["자치구"] =coin_gu
# 동 컬럼 만들기
coin_ad_list = coin["지번주소"]
coin_dong = [i.split(" ")[2].strip() for i in list(coin_ad_list)]
coin["동"] =coin_dong
# 컬럼명 통일을 위한 컬럼명 재설정
coin = coin[["사업장명","자치구", "동", "좌표정보(X)","좌표정보(Y)"]]
coin.columns = ["사업장명","자치구", "동", "X","Y"]
coin["업태구분명"] = "코인세탁소"
coin
2) 약국 정보 전처리
# 페업처리 사업자 제거
pharm = pharm.loc[~pharm["영업상태명"].str.contains("폐업"), :]
pharm.head(2)
# 사업장명 unique값 확인
# 유니크값이 너무 많아서 확인 불가
pharm["사업장명"].unique()
# 필요한 컬럼만 추출
pharm = pharm[["사업장명","지번주소","좌표정보(X)","좌표정보(Y)"]].reset_index(drop = True)
pharm
# 널값 제거
pharm = pharm.dropna()
pharm
# 자치구 컬럼 만들기
pharm_ad_list = pharm["지번주소"]
pharm_gu = [i.split(" ")[1].strip() for i in list(pharm_ad_list)]
pharm["자치구"] =pharm_gu
# 동 컬럼 만들기
lst = []
pharm_ad_list = list(pharm["지번주소"])
for i in pharm_ad_list :
if len(i.split(" ")) < 3:
i = i + " -"
lst.append(i)
elif len(i.split(" ")) >= 3:
lst.append(i)
pharm_dong = [i.split(" ")[2] for i in lst ]
pharm["동"] = pharm_dong
# 컬럼명 통일을 위한 컬럼명 재설정
pharm = pharm[["사업장명","자치구", "동","좌표정보(X)","좌표정보(Y)"]]
pharm.columns = ["사업장명","자치구", "동", "X","Y"]
pharm["업태구분명"] = "약국"
pharm
3) 편의점 정보 전처리
# 페업처리 사업자 제거
store = store.loc[~store["영업상태명"].str.contains("폐업"), :]
store.head(2)
# 업태구분명 편의점만 추출
store = store.loc[store["업태구분명"].str.contains("편의점"), :]
store
# 필요한 컬럼만 추출
store = store[["사업장명","지번주소","좌표정보(X)","좌표정보(Y)","업태구분명"]].reset_index(drop = True)
store
# 널값 제거
store = store.dropna()
store
# 자치구 컬럼 만들기
store_ad_list = store["지번주소"]
store_gu = [i.split(" ")[1].strip() for i in list(store_ad_list)]
store["자치구"] =store_gu
# 동 컬럼 만들기
store_ad_list = store["지번주소"]
store_dong = [i.split(" ")[2].strip() for i in list(store_ad_list)]
store["동"] =store_dong
# 컬럼명 통일을 위한 컬럼명 재설정
store = store[['사업장명', '자치구', "동", '좌표정보(X)', '좌표정보(Y)', '업태구분명']]
store.columns = ["사업장명",'자치구', "동", "X","Y", "업태구분명"]
4) 3개 데이터프레임 합치기 : df
df = pd.concat([coin, pharm, store], ignore_index = True )
df.columns = ["사업장명","자치구", "동", "X","Y","업태구분명"]
df.head()
5) TM좌표계 -> 위경도좌표로 변경
팀원들의 데이터는 모두 위경도 좌표계로 되어있는데 편의시설 데이터만 TM 좌표계로 되어있어 위경도 좌표로 변환하였다.
또한 시각화에 쓸 라이브러리인 folium에서도 위경도 좌표를 사용한다.
## df의 좌표를 위경도로 바꾼다.
def project_array(coord, p1_type, p2_type):
"""
좌표계 변환 함수
- coord: x, y 좌표 정보가 담긴 NumPy Array
- p1_type: 입력 좌표계 정보 ex) epsg:5179
- p2_type: 출력 좌표계 정보 ex) epsg:4326
"""
p1 = pyproj.Proj(init=p1_type)
p2 = pyproj.Proj(init=p2_type)
fx, fy = pyproj.transform(p1, p2, coord[:, 0], coord[:, 1])
return np.dstack([fx, fy])[0]
# DataFrame -> NumPy Array 변환
coord = np.array(df[["X", "Y"]])
coord
# 좌표계 정보 설정
p1_type = "epsg:2097"
p2_type = "epsg:4326"
# project_array() 함수 실행
result = project_array(coord, p1_type, p2_type)
result
df['X'] = result[:, 0]
df['Y'] = result[:, 1]
df.head()
6) 주소 재설정 : 카카오 API 활용
데이터가 주소가 제대로 되어있지 않은 부분들이 많았다. 그래서 팀원들과 합의하에 카카오 API를 활용하여 위경도좌표로 주소를 다시 받아 자치구와 동 컬럼에 할당해주기로 하였다.
# library import
import pandas as pd
import requests
import sys
import json
import datetime
from tqdm import trange
# kakao API 내부 함수
def json_request(url='', encoding='utf-8', success=None, error=lambda e: print('%s : %s' % (e, datetime.now()), file=sys.stderr)):
headers = {'Authorization': 'KakaoAK {}'.format(APP_KEY)}
resp = requests.get(url, headers=headers)
# print('%s : success for request [%s]' % (datetime.now(), url))
return resp.text
def reverse_geocode(longitude, latitude):
# 파라미터 최적화하여 url 생성
url = '%s?x=%s&y=%s' %(URL, longitude, latitude)
# json request
try:
# print('try')
json_req = json_request(url=url)
json_data = json.loads(json_req)
json_doc = json_data.get('documents')[0]
json_gu = json_doc.get('region_2depth_name') #구단위
json_dong = json_doc.get('region_3depth_name') #동단위(법정동)
json_region = json_doc.get('region_type')
except:
# print('nan')
json_gu = 'NaN'
json_dong = 'NaN'
return json_gu, json_dong, json_region
def get_address(x,y):
gu = []
json_gu, json_dong, json_region = reverse_geocode(x, y)
gu.append(json_gu)
return gu # 전처리 함수에서 구단위 리스트 받아서 데이터프레임에 추가
def get_code(x,y):
dong = []
json_gu, json_dong, json_region = reverse_geocode(x, y)
dong.append(json_dong)
return dong # 전처리 함수에서 동단위(법정동) 리스트 받아서 데이터프레임에 추가
def get_region(x,y):
region = []
json_gu, json_dong, json_region = reverse_geocode(x, y)
region.append(json_region)
return region
# APP_KEY에 본인 REST API KEY 넣기
APP_KEY = '개별 REST API KEY'
URL = 'https://dapi.kakao.com/v2/local/geo/coord2regioncode.json'
# 아래의 df 에는 본인이 적용할 dataframe명 변수로 넣으면 됨
# 위도, 경도 매칭 중요 : X값(100대), Y값(30대)
# 함수 적용 전, X, Y 컬럼의 값들 string으로 변환부터 해줘야 적용됨
def get_district(df):
for i in trange(len(df)):
x_crd = float(df.loc[i, ['X']])
y_crd = float(df.loc[i, ['Y']])
gu = get_address(x_crd, y_crd)
dong = get_code(x_crd, y_crd)
region = get_region(x_crd,y_crd)
df.loc[i, ['자치구']] = gu
df.loc[i, ['동']] = dong
df.loc[i, ['동분류']] = region
return df
API를 활용하는데 1시간정도 걸렸다. 동분류는 동이 행정동과 법정동으로 나누어져있는데 법정도기준으로 동을 나누어 표현하기로 하여 변환한 동이 법정동(B)가 맞는지 확인하기 위해 같이 불러왔다.
df = get_district(df)
df
* 코드가 길어져 코인세탁소 기준으로 코드만 작성하겠다.
7) 데이터프레임 업태구분명 별로 다시 분리
- df_coin, df_pharm, df_store로 변수 지정
- 각 업태구분명별로 동별 합계를 구하였다.
- 여기서 동별로 나누었을 때 다른 구에 동명이 같은 것이 존재하여 구분해주기 위해 "주소" = "자치구" + "동" 파생 컬럼 만들었다.
- 주소별 합계를 구하였다.
# 코인세탁소 합계
df_s_coin = df.loc[df["업태구분명"] == "코인세탁소", :]
df_s_coin = df_s_coin.groupby(["주소"])["업태구분명"].count()
df_s_coin = pd.DataFrame(df_s_coin).reset_index()
df_s_coin = df_s_coin.rename(columns = {"업태구분명" : "합계"})
df_s_coin
8) 법정코드 붙이기
- 위에서 언급한 같은 오류로 지도 시각화할 때 동명을 사용할 수가 없어 법정동 코드를 사용하여 시각화를 하였다.
- 법정동 코드를 불러와서 각 데이터프레임에 병합하였다.
df_c_coin = pd.merge(df_coin, code, how = "left", on = "주소")
df_c_coin.isnull().sum()
9) 동당 면적 불러오기
- 회의를 통해 동마다 면적이 다르고 갯수의 차이가 있어 같은 갯수여도 면적이 다르면 같게 보면 안된다는 의견을 수렴하여 동 면적을 불러와 합계를 나누어 좀 더 객관적인 점수를 표현하기로 했다.
# 법정코드 불러온 파일
aa = pd.read_excel('code_new.xlsx', index_col =0, header = None)
aa = aa.iloc[3:, 0:3]
aa["주소"] = aa[1] + aa[2]
aa = aa.loc[~aa[2].str.contains("소계"), :]
aa = aa[["주소",3]]
aa.columns = ["주소", "면적"]
aa = aa.reset_index(drop =True)
aa
# df_a_coin
df_a_coin = pd.merge(df_c_coin, aa, how = "left", on = "주소")
10) 종합 데이터프레임 만들기
- 주소별 합계를 구한 데이터프레임과, 법정코드와 면적을 추가해준 데이터 프레임을 병합하였다.
- 주소를 기준으로 동별 합계와 동별 점수가 구해야하니 left로 병합해주었다.
- 그리고 컬럼명을 팀원간 상의를 통해 최종 재설정하였다.
# 코인세탁소 종합 병합용 데이터프레임
df_a_coin = df_a_coin[["업태구분명", "법정동코드", "자치구", "동", "면적", "주소"]]
df_t_coin = pd.merge(df_s_coin, df_a_coin, how = "left", on ="주소")
df_t_coin = df_t_coin[["업태구분명", "법정동코드", "자치구", "동", "합계", "면적"]]
df_t_coin.columns = ["구분", "법정동코드", "자치구", "동", "합계" , "면적" ]
df_t_coin = df_t_coin.drop_duplicates().reset_index(drop=True)
df_t_coin
df_t = pd.concat([df_t_coin, df_t_pharm, df_t_store], ignore_index = True)
df_t
11) 합계당 면적 컬럼 추가
- 원래는 면적당 합계를 구하는 것이 목적이었지만 면적당 합계를 숫자가 너무 작아져 반대인 합계당 면적을 구하기로 하였다.
- 합계당 면적을 구하고 점수를 10개 구간으로 나누어 작은 순부터 높은 점수를 부여하여 면적당 합계와 같은 결과를 같게 하였다.
df_t["합계"] = df_t["합계"].astype(float)
df_t["면적"] = df_t["면적"].astype(float)
df_t["합계당면적"] = df_t["면적"] / df_t["합계"]
df_t = df_t.dropna()
df_t
12) 점수 매기기
- 합계당 면적을 이용하여 10개 구간으로 나누어 100점 만점으로 10점씩 차등 점수를 매겼다.
- 각 구분별 점수를 매기기위해 통합한 데이터프레임을 다시 나누어 주고 점수를 매기고 다시 합했다.
## 데이터프레임 구분
df_fac_coin =df_t.loc[df_t["구분"].str.contains("코인세탁소"), :].reset_index(drop =True)
df_fac_coin
# 코인세탁 점수 매기기
grades = []
for row in df_fac_coin["합계당면적"] :
if row > df_fac_coin['합계당면적'].quantile(0.9):
grades.append(10)
elif row > df_fac_coin['합계당면적'].quantile(0.8):
grades.append(20)
elif row > df_fac_coin['합계당면적'].quantile(0.7):
grades.append(30)
elif row > df_fac_coin['합계당면적'].quantile(0.6):
grades.append(40)
elif row > df_fac_coin['합계당면적'].quantile(0.5):
grades.append(50)
elif row > df_fac_coin['합계당면적'].quantile(0.4):
grades.append(60)
elif row > df_fac_coin['합계당면적'].quantile(0.3):
grades.append(70)
elif row > df_fac_coin['합계당면적'].quantile(0.2):
grades.append(80)
elif row > df_fac_coin['합계당면적'].quantile(0.1):
grades.append(90)
else :
grades.append(100)
df_fac_coin['점수'] = grades
df_fac_coin = df_fac_coin.reset_index(drop=True)
df_fac_coin = df_fac_coin[["구분", "법정동코드", "자치구", "동", "합계", "점수"]]
df_fac_coin.to_csv("df_fac_coin.csv")
df_fac_coin
# 데이터프레임 다시 합치기
df_fac = pd.concat([df_fac_coin,df_fac_pharm,df_fac_store ], ignore_index =True)
df_fac.to_csv("df_fac.csv")
df_fac
12) 시각화
- GeoJSON을 활용하여 folium으로 지도 시각화를 하였다.
- 각 구분별 법정동코드별 점수로 지도 시각화를 진행하였다.
- 각 구분을 넣어주면 시각화해주는 함수를 구현했다.
- 추가로 3개의 구분을 우선순위순으로 넣어주면 불투명도로 차등으로 두어 종합 시각화해주는 함수를 구현했다.
seoul_geo_url = "https://raw.githubusercontent.com/choiimingue/seoul_map/main/EMD_SEOUL.json"
print(seoul_geo_url)
# 서울 시도 GeoJSON
with urlopen(seoul_geo_url) as response:
seoul_geojson = json.load(response)
seoul_geojson["features"][0]["properties"]
- 구분별 시각화 함수
# 구분 데이터 시각화
def get_geo(a):
# 개별 데이터 지도 시각화 함수
# 파라미터를 입력
# a에 세부구분 넣어 지도에 시각화
f_map_fac = folium.Map([37.541, 126.986], zoom_start=12)
lst = [a]
for i in lst :
name = { "코인세탁소" : df_fac_coin, "약국" : df_fac_pharm, "편의점" : df_fac_store,
}
sub = { a : 0.7,}
# 코인세탁
folium.Choropleth(
geo_data=seoul_geojson,
name='choropleth',
data=name[i],
columns=["법정동코드",'점수'],
key_on='feature.properties.EMD_CD',
fill_color='Greens',
fill_opacity=sub[i],
line_opacity=0.7,
legend_name= f'동별 {i} 수'
).add_to(f_map_fac)
return f_map_fac
get_geo("코인세탁소")
- 우선순위 차등 시각화 함수
def get_geo_op(a,b,c):
# 편의시설 우선순위에 따른 지도 시각화 함수
# 파라미터를 우선순위순으로 입력
# 불투명도에 차이를 두어 지도에 시각화
lst = [a, b, c]
f_map_fac = folium.Map([37.541, 126.986], zoom_start=12)
for i in lst :
name = { "코인세탁소" : df_fac_coin, "약국" : df_fac_pharm, "편의점" : df_fac_store,
}
sub = { a : 0.5, b : 0.3, c : 0.1}
# 코인세탁
folium.Choropleth(
geo_data=seoul_geojson,
name='choropleth',
data=name[i],
columns=["법정동코드",'합계'],
key_on='feature.properties.EMD_CD',
fill_color='Greens',
fill_opacity=sub[i],
line_opacity=0.5,
legend_name= f'동별 {i} 수'
).add_to(f_map_fac)
return f_map_fac
get_geo_op("약국", "편의점", "코인세탁소")
13) 바 그래프 시각화
- 코인세탁 상위 10개 동네
a =df_fac_coin.sort_values("합계", ascending = False).head(10)
sns.barplot(data = a, x = "동", y ="합계",palette = "Reds_r")
'멋쟁이사자처럼 > Project' 카테고리의 다른 글
Miniproject_(3)_boxoffice (0) | 2023.03.02 |
---|---|
Miniproject_(2)_국민청원_scrapping (0) | 2023.03.01 |
Miniproject_(1)_starbucks_API (0) | 2023.03.01 |