Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
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 31
Tags
more
Archives
Today
Total
관리 메뉴

왕초보 괴발자

(공공데이터) 파이썬 서울특별시 평균 월세 법정동 별 folium 시각화 본문

행정구역

(공공데이터) 파이썬 서울특별시 평균 월세 법정동 별 folium 시각화

왕초보괴발자 2024. 4. 2. 17:58

1. 이걸 할거에요

 

2. 순서에요

1) 공공데이터 "서울특별시_전월세가_2023.csv"파일을 준비해요.

2) 법정동별로 월세 평균을 구해요.

3) EPSG5179 에서 EPSG4326 으로 좌표변환을 해요.

4) folium으로 시각화해요.

 

 

3. 이렇게 해요

1) 공공데이터를 받아요. https://data.seoul.go.kr/dataList/OA-21276/S/1/datasetView.do

 

열린데이터광장 메인

데이터분류,데이터검색,데이터활용

data.seoul.go.kr

df_23 = pd.read_csv('서울특별시_전월세가_2023.csv', encoding='utf-8')
df_23.head()
접수년도	자치구코드	자치구명	법정동코드	법정동명	지번구분코드	지번구분	본번	부번	층	...	보증금(만원)	임대료(만원)	건물명	건축년도	건물용도	계약기간	신규계약구분	갱신청구권사용	종전보증금	종전임대료
0	2023	11440	마포구	11000	노고산동	1.0	대지	1.0	1.0	8.0	...	10000	40	\t(1-1)\t	1998.0	오피스텔	23.01~24.01	신규	NaN	0.0	0.0
1	2023	11440	마포구	11000	노고산동	1.0	대지	1.0	1.0	5.0	...	10000	55	\t(1-1)\t	1998.0	오피스텔	23.02~24.02	갱신	NaN	10000.0	55.0
2	2023	11440	마포구	11000	노고산동	1.0	대지	1.0	1.0	9.0	...	21000	0	\t(1-1)\t	1998.0	오피스텔	23.02~24.02	신규	NaN	0.0	NaN
3	2023	11170	용산구	11300	원효로2가	1.0	대지	1.0	0.0	9.0	...	23900	0	\t(1)\t	1990.0	오피스텔	23.09~25.09	신규	NaN	0.0	NaN
4	2023	11440	마포구	11000	노고산동	1.0	대지	1.0	1.0	9.0	...	22000	0	\t(1-1)\t	1998.0	오피스텔	NaN	NaN	NaN	NaN	NaN

 

 2) 데이터가 어떤지 조금 살펴볼게요.

len(df_23['자치구명'].unique())
25

 -  서울의 행정구 25개에 대한 정보가 전부 있어요.

 

df_23['자치구명'].value_counts(ascending=False).head(10)
송파구     46398
강남구     36943
강서구     36397
관악구     30862
강동구     30636
마포구     28013
서초구     27365
영등포구    25995
동작구     24368
광진구     23416
Name: 자치구명, dtype: int64

 - 행정구 단위로 본다면 강남송파의 거래량이 가장 많네요.

 

df_23['법정동명'].value_counts(ascending=False)
봉천동    15757
신림동    13886
화곡동    12016
상계동     9642
신정동     8379
       ...  
산림동        1
재동         1
궁정동        1
관훈동        1
북창동        1
Name: 법정동명, Length: 402, dtype: int64

 - 법정동 단위로는 관악구에 있는 봉천동과 신림동의 거래량이 가장 많아요. 왜 그럴까요?

- 서울의 법정동은 467개인데, 데이터가 있는 법정동이 402개네요. 조사가 안되었거나, 거래가 없다는 거겠죠.

 

df_bong = df_23[df_23['자치구명'] == '관악구']
df_bong['건물용도'].value_counts(ascending=False)
단독다가구    17944
아파트       5889
연립다세대     4708
오피스텔      2321
Name: 건물용도, dtype: int64

 - 관악구에는 저렴한 월세가 많아서 높은 거래량을 보이는 것으로 추측할 수 있겠네요.

 

df_bong = df_23[df_23['자치구명'] == '송파구']
df_bong['건물용도'].value_counts(ascending=False)
아파트      20968
연립다세대    15200
오피스텔      5602
단독다가구     4628
Name: 건물용도, dtype: int64

 - 반면 송파구에는 아파트 대단지들이 많이 있기 때문에 아파트의 거래량이 많은 것을 볼 수 있어요.

 

 

3) 필요한 데이터만 남겨요.

 - '건물용도'가 아파트면서, '전월세구분'이 월세인 경우도 있지만, 매우 비싸고 '월세방'과는 거리가 있기 때문에 제외해요.

df = df_23[['법정동명', '전월세구분', '임대료(만원)', '건물용도']]
df = df[df['건물용도'] != '아파트']
df = df[df['전월세구분'] == '월세']
df
법정동명	전월세구분	임대료(만원)	건물용도
0	노고산동	월세	40	오피스텔
1	노고산동	월세	55	오피스텔
5	방배동	월세	55	오피스텔
6	사당동	월세	22	오피스텔
7	노고산동	월세	30	오피스텔
...	...	...	...	...
545801	삼청동	월세	50	단독다가구
545802	대조동	월세	85	단독다가구
545807	자곡동	월세	50	단독다가구
545808	상도동	월세	100	단독다가구
545809	대치동	월세	150	단독다가구
df_avg = df.groupby('법정동명')['임대료(만원)'].mean().sort_values(ascending=False)
df_avg
법정동명
한남동      182.172691
신천동      173.900000
신문로2가    151.714286
압구정동     150.000000
의주로1가    149.461538
            ...    
양평동1가     36.435294
토정동       35.000000
하중동       32.000000
을지로6가     27.666667
문래동1가     21.545455
Name: 임대료(만원), Length: 386, dtype: float64

- 402개였던 행이 386개로 줄었어요. '전세' 또는 '아파트'만 거래했던 법정동들도 있었다는 거에요.

- df_avg 데이터프레임은 잠시 놔두도록 해요.

 

 

4) 법정동 경계 데이터를 불러와요.

 - 읍면동, 23년 7월 데이터를 다운받을게요.  http://www.gisdeveloper.co.kr/?p=2332

 

대한민국 최신 행정구역(SHP) 다운로드 – GIS Developer

 

www.gisdeveloper.co.kr

df_geo = gpd.read_file('emd.shp', encoding='CP949')
df_geo = df_geo[df_geo['EMD_CD'].str.startswith('11')]
df_geo = df_geo.rename(columns={'EMD_KOR_NM': '법정동명'})
df_geo

 - 다운 받은 파일은 dbf, shp, shx 라는 확장자인데, 변환하는 것은 shp파일이지만 셋 다 같은 폴더 경로에 있어야 해요.

 - 11로 시작하는 법정동코드만 남겨서, 서울특별시의 법정동만 남게 해요.

EMD_CD	EMD_ENG_NM	법정동명	geometry
0	11110101	Cheongun-dong	청운동	POLYGON ((953700.022 1954605.065, 953693.871 1...
1	11110102	Singyo-dong	신교동	POLYGON ((953233.465 1953996.984, 953235.183 1...
2	11110103	Gungjeong-dong	궁정동	POLYGON ((953560.228 1954257.466, 953561.190 1...
3	11110104	Hyoja-dong	효자동	POLYGON ((953519.843 1953890.785, 953518.489 1...
4	11110105	Changseong-dong	창성동	POLYGON ((953516.123 1953734.362, 953516.526 1...
...	...	...	...	...
462	11740106	Dunchon-dong	둔촌동	POLYGON ((969669.593 1948748.489, 969656.716 1...
463	11740107	Amsa-dong	암사동	POLYGON ((968514.203 1950677.234, 968505.336 1...
464	11740108	Seongnae-dong	성내동	POLYGON ((967686.073 1948534.011, 967685.029 1...
465	11740109	Cheonho-dong	천호동	POLYGON ((968336.280 1950222.697, 968337.437 1...
466	11740110	Gangil-dong	강일동	POLYGON ((970882.440 1951500.730, 970882.447 1...

 - 서울특별시 법정동 개수 467개가 잘 남았네요.

 

df_mg = pd.merge(df_geo, df_avg, on='법정동명', how='outer')
df_mg = df_mg[['법정동명', '임대료(만원)', 'geometry']]
df_mg.sample(10)

 - 아까 만들었던 df_avg와 병합을 하고, 필요한 컬럼만 남겨요. 데이터의 몇가지를 살펴볼게요.

법정동명	임대료(만원)	geometry
15	도렴동	NaN	POLYGON ((953680.961 1952885.050, 953683.242 1...
14	사직동	64.321429	POLYGON ((952745.849 1953334.909, 952758.791 1...
432	역삼동	77.668003	POLYGON ((960137.165 1945122.850, 960156.153 1...
0	청운동	147.823529	POLYGON ((953700.022 1954605.065, 953693.871 1...
315	봉원동	55.083333	POLYGON ((951341.687 1953070.323, 951347.456 1...
241	동소문동2가	57.375000	POLYGON ((956688.961 1954613.224, 956694.806 1...
391	당산동6가	54.898148	POLYGON ((947049.315 1948853.170, 947057.122 1...
13	내자동	NaN	POLYGON ((953411.883 1953152.129, 953414.609 1...
411	상도동	47.055121	MULTIPOLYGON (((951797.594 1945502.652, 951798...
345	성산동	58.003104	POLYGON ((946907.590 1953016.814, 946918.338 1...

 - 임대료가 집계되지 않은 법정동도 있고, 좌표가 '멀티폴리곤'인 데이터도 있어요. 멀티폴리곤의 예시는 다음과 같아요.

 - 멀티폴리곤은 법정동명은 하나지만, 폴리곤 개수가 2개 이상이에요.

 

 

5) pyproj 라이브러리를 이용해서 EPSG5179에서 EPSG4326으로 좌표변환해요.

 - 흔히 보던 우리나라의 좌표는 12X, 3X 인데, 지금 우리가 가진 데이터프레임은 90만, 190만이에요. 

 - 이는 우리나라에서 좌표를 측정할 때 EPSG5179 (UTM-K, GRS80)를 사용하지만, 세계지구좌표계는 EPSG4326 (WGS84)를 사용하기 때문이에요. 

import pyproj
from shapely.ops import transform

epsg5179 = pyproj.Proj(init='epsg:5179')
epsg4326 = pyproj.Proj(init='epsg:4326')

def epsg5179_to_epsg4326(geometry):
    return transform(lambda x, y: pyproj.transform(epsg5179, epsg4326, x, y), geometry)

df_mg['geometry'] = df_mg['geometry'].apply(epsg5179_to_epsg4326)
df_mg
법정동명	임대료(만원)	geometry
0	청운동	147.823529	POLYGON ((126.97556 37.58968, 126.97549 37.589...
1	신교동	60.342105	POLYGON ((126.97031 37.58418, 126.97033 37.584...
2	궁정동	47.000000	POLYGON ((126.97400 37.58654, 126.97401 37.586...
3	효자동	85.500000	POLYGON ((126.97356 37.58323, 126.97355 37.582...
4	창성동	62.200000	POLYGON ((126.97353 37.58182, 126.97354 37.581...
...	...	...	...
462	둔촌동	48.271144	POLYGON ((127.15669 37.53756, 127.15654 37.537...
463	암사동	45.594828	POLYGON ((127.14353 37.55490, 127.14343 37.554...
464	성내동	47.324013	POLYGON ((127.13424 37.53556, 127.13423 37.535...
465	천호동	51.026956	POLYGON ((127.14153 37.55080, 127.14154 37.550...
466	강일동	49.440860	POLYGON ((127.17030 37.56240, 127.17030 37.562...

 

df_mg['임대료(만원)'] = df_mg['임대료(만원)'].round(1)
df_mg['임대료(만원)'] = df_mg['임대료(만원)'].fillna(0).astype(int)
df_mg['임대료(만원)'] = df_mg['임대료(만원)'].astype(int)
df_mg

 - 추가로 임대료를 1의자리 숫자로 반올림하고, 시각화를 위해 NaN값을 0으로 바꾸고, 타입을 소수에서 정수로 바꿔줘요.

법정동명	임대료(만원)	geometry
0	청운동	147	POLYGON ((126.97556 37.58968, 126.97549 37.589...
1	신교동	60	POLYGON ((126.97031 37.58418, 126.97033 37.584...
2	궁정동	47	POLYGON ((126.97400 37.58654, 126.97401 37.586...
3	효자동	85	POLYGON ((126.97356 37.58323, 126.97355 37.582...
4	창성동	62	POLYGON ((126.97353 37.58182, 126.97354 37.581...
...	...	...	...
462	둔촌동	48	POLYGON ((127.15669 37.53756, 127.15654 37.537...
463	암사동	45	POLYGON ((127.14353 37.55490, 127.14343 37.554...
464	성내동	47	POLYGON ((127.13424 37.53556, 127.13423 37.535...
465	천호동	51	POLYGON ((127.14153 37.55080, 127.14154 37.550...
466	강일동	49	POLYGON ((127.17030 37.56240, 127.17030 37.562...

 

 

6) folium 라이브러리를 이용해 시각화해요.

import folium
import branca # folium에 쓰일 꾸밈용 라이브러리에요.
m = folium.Map(location=[37.5665, 126.9780], zoom_start=12)  # 서울을 중심으로 맵 생성
df_mg.crs = "EPSG:4326" # ValueError: Cannot transform naive geometries. Please set a crs on the object first. 오류 발생 시

# Define a linear colormap
colormap = branca.colormap.LinearColormap(
    vmin=df_mg["임대료(만원)"].quantile(0.0),
    vmax=df_mg["임대료(만원)"].quantile(1),
    colors=["white","red"],
    caption="임대료(만원)",
)

# Define a tooltip
tooltip = folium.GeoJsonTooltip(
    fields=["법정동명", "임대료(만원)"],
    aliases=["법정동명:", "임대료(만원):"],
    localize=True,
    sticky=False,
    labels=True,
    style="""
        background-color: #F0EFEF;
        border: 1px solid black;
        border-radius: 3px;
        box-shadow: 3px;
    """,
    max_width=800,
)

# Create a GeoJson layer
g = folium.GeoJson(
    df_mg,
    style_function=lambda x: {
        "fillColor": colormap(x["properties"]["임대료(만원)"])
        if x["properties"]["임대료(만원)"] is not None
        else "transparent",
        "color": "black",
        "weight": 1, # 경계 두께
        "fillOpacity": 0.6,
    },
    tooltip=tooltip,
).add_to(m)

# Add the colormap to the map
colormap.add_to(m)

m

 - 이제 마우스를 갖다대면 법정동명과 임대료를 볼 수 있을거에요. :)

 - 컬러맵 및 툴팁코드는 여기를 참조했어요. https://python-visualization.github.io/folium/latest/user_guide/geojson/geojson_popup_and_tooltip.html

 

GeoJSON popup and tooltip — Folium 0.1.dev1+ga7b4f17 documentation

Make this Notebook Trusted to load map: File -> Trust Notebook

python-visualization.github.io