지금은마라톤중

Midproject_(1)_서울시 1인가구 거주지 추천 서비스 본문

멋쟁이사자처럼/Project

Midproject_(1)_서울시 1인가구 거주지 추천 서비스

달리는중 2023. 3. 2. 17:50

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
Comments