House Price Prediction(EDA)

데이터화 되지 않은 정보들로 집값 형성을 이루기 때문에 사회초년생이나 집값에 대해 잘 알지 못하는 사람들은 집값을 제대로 파악하지 못해 본인이 원하는 집을 구하지 못하는 경우가 많다. 특히, 20~30대들이 집을 살 때, 공인중개사를 만날 때나, 집을 구할 때 미리 집값을 알고 갈 수 있게 데이터를 제공하기 위해 미국 seattle지역의 2014년 5월부터 2015년 5월까지의 1년간의 데이터를 이용하여 여러 변수나 항목들로 집값을 예측하는 어플을 만들고자 한다. 다른 활용방안으로는 은행 및 정부에게 데이터를 제공하여 은행쪽에선 대출 상한, 하한선을 정할 수 있고, 정부쪽에서는 집값의 상한선을 잡을 수 있을 때 활용할 수 있다.

1. 데이터 확인 및 변수 설명

사용 라이브러리 및 모듈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
#%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import plotly.offline as py
import folium
from folium import plugins
from folium.plugins import HeatMap
from folium.plugins import FastMarkerCluster
from folium.plugins import MarkerCluster
cs

데이터 불러오기

1
2
3
data = pd.read_csv('C:/Users/CPB06GameN/apart/train.csv')
print(data.shape)
data.head(10)
cs










데이터는 15035개의 데이터와, 21개의 변수로 이루어져 있다.

변수설명

타겟변수 : price

  • ID : 집을 구분하는 번호
  • date : 집을 구매한 날짜
  • price : 집의 가격(Target variable)
  • bedrooms : 침실의 수
  • bathrooms : 화장실의 수
  • sqft_living : 주거 공간의 평방 피트(면적)
  • sqft_lot : 부지의 평방 피트(면적)
  • floors : 집의 층 수
  • waterfront : 집의 전방에 강이 흐르는지 유무
  • view : 뷰가 얼마나 좋은지 0~4단계
  • condition : 집의 전반적인 상태
  • grade : King County grading 시스템 기준으로 매긴 집의 등급
  • sqft_above : 지하실을 제외한 평방 피트(면적)
  • sqft_basement : 지하실의 평방 피트(면적)
  • yr_built : 지어진 년도
  • yr_renovated : 집을 재건축한 년도
  • zipcode : 우편번호
  • lat : 위도
  • long : 경도
  • sqft_living15 : 2015년 기준 주거 공간의 평방 피트
  • sqft_lot15 : 2015년 기준 주차 공간의 평방 피트

데이터 타입확인 및 결측치 확인

1
data.info()
cs

















타켓변수인 price는 float형태로 되어있고, date를 제외한 모든 독립변수는 int형과 float형태로 이루어져있다.

1
pd.isnull(data).sum()
cs





























데이터 전체의 요약된 통계 결과

1
data.describe()
cs








전체적으로 변수의 요약값을 확인하여 보니, 수치형 데이터는 분산이 대부분 큰 것으로 확인

목적변수 확인

1
2
3
plt.rcParams["figure.figsize"] = (10,5)
plt.rcParams["axes.grid"] = True 
sns.boxplot(data['price'])
cs









타겟변수인 price는 전체적으로 0~100000사이에 위치하고 있으며 분산이 큰 것으로 알 수 있음

2. 수치형 데이터 정규화

목적변수 히스토그램

1
2
f, ax = plt.subplots(figsize=(10, 10))
sns.distplot(data['price'])
cs













히스토그램으로 확인한 결과 오른쪽 꼬리가 길고, 한쪽으로 몰려있어 정규화가 필요하다고 판단

목적변수 정규화

1
2
f, ax = plt.subplots(figsize=(1010))
sns.distplot(np.log1p(data['price']))
cs















위의 목적변수의 경우 첨도, 왜도도 굉장히 정규분포를 따르지 않고, 분산 또한 매우 큰 것을 확인

나중에 회귀모델을 사용할 수도 있기 때문에 Nomalize를 통해 price의 분포가 정규분포를 가지도록 만들고, 그 결과 위의 그래프를 비교해보면 price에 log를 취해주면 기존보다 더 정규분포에 가까워진 것을 확인 할 수 있음.

수치형 데이터 히스토그램

1
2
3
4
5
6
7
8
9
10
11
fig, ax = plt.subplots(32, figsize=(20,10))
 
count = 0
columns = ['sqft_living''sqft_lot''sqft_above''sqft_basement''sqft_living15''sqft_lot15']
for row in range(3):
    for col in range(2):
        sns.kdeplot(data[columns[count]], ax=ax[row][col])
        ax[row][col].set_title(columns[count], fontsize=15)
        count+=1
        
plt.tight_layout()
cs











위의 수치형 데이터인 설명변수들도 정규분포를 따르지 않아, 목적변수와 같은 이유로 정규분포를 가지도록 만들어 주겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
data_log = data.copy()
skew_columns = ['sqft_living''sqft_lot''sqft_above''sqft_basement''sqft_living15''sqft_lot15']
 
for c in skew_columns:
    data_log[c] = np.log1p(data_log[c].values)
 
fig, ax = plt.subplots(32, figsize=(2010))
 
count = 0
for row in range(3):
    for col in range(2):
        sns.kdeplot(data_log[skew_columns[count]], ax=ax[row][col])
        ax[row][col].set_title(skew_columns[count], fontsize=15)
        count+=1
        
plt.tight_layout()
cs










1
2
data_log['price'= np.log1p(data['price'])
data_log
cs











정규화 데이터를 하나 더 만들어 정규화 한 것과 안한 것을 비교하며 분석

3. 목적변수와 설명변수의 상관관계 파악

목적변수와 설명변수간의 상관관계 확인

1
data.corr()
cs














상관관계 시각화

1
2
3
plt.figure(figsize=(15,15))
sns.heatmap(data = data.corr(), annot=True
fmt = '.2f', linewidths=.5, cmap='Blues')
cs















  • 상관 분석 또는 '상관관계' 또는 '상관'은 확률론과 통계학에서 두 변수간에 어떤 선형적 또는 비선형적 관계를 갖고 있는 지를 분석하는 방법이다. 두변수는 서로 독립적인 관계이거나 상관된 관계일 수 있으며 이때 두 변수간의 관계의 강도를 상관관계(Correlation, Correlation coefficient)라 한다. 상관분석에서는 상관관계의 정도를 나타내는 단위로 모상관계수로 ρ를 사용하며 표본 상관 계수로 r 을 사용한다.
  • 결과의 해석
    • r이 -1.0과 -0.7 사이이면, 강한 음적 선형관계,
    • r이 -0.7과 -0.3 사이이면, 뚜렷한 음적 선형관계,
    • r이 -0.3과 -0.1 사이이면, 약한 음적 선형관계,
    • r이 -0.1과 +0.1 사이이면, 거의 무시될 수 있는 선형관계,
    • r이 +0.1과 +0.3 사이이면, 약한 양적 선형관계,
    • r이 +0.3과 +0.7 사이이면, 뚜렷한 양적 선형관계,
    • r이 +0.7과 +1.0 사이이면, 강한 양적 선형관계

price와 상관성이 높은 top 6가지 변수

  • 1위 : sqft_living 0.7

      (주거 공간의 평방 피트(면적))
  • 2위 : grade 0.67

      (King County grading 시스템 기준으로 집의 등급)
  • 3위 : sqft_above 0.61

      (지하실을 제외한 평방 피트(면적))
  • 4위 : sqft_living15 0.59 ##1위의 변수와 비슷하므로 제외

      (2015년 기준 주거 공간의 피트)
  • 5위 : bathrooms 0.53

      (화장실의 수)
  • 6위 : view 0.40

      (뷰가 좋은 정도 4단계)
  • 7위 : bedrooms 0.32

      (침실의 수)

4. 변수 수정 및 변수 생성

변수 수정

1
2
data['date'= data['date'].apply(lambda x: x[0:8]) ## 날짜 8자리 추출
data_log['date'= data_log['date'].apply(lambda x: x[0:8])
cs

날짜 데이터를 보면 20151204T0000000형태로 되어있어 분석에 필요한 데이터만 추출

1
2
3
4
5
6
7
data['date_year'] = data['date'].apply(lambda x: x[0:4]) ## 년도 추출
data['date_month'] = data['date'].apply(lambda x: x[4:6]) ##월 추출
data
data_log['date_year'] = data_log['date'].apply(lambda x: x[0:4])
data_log['date_month'] = data_log['date'].apply(lambda x: x[4:6])


cs

날짜와 년도를 월별로 추출하여 변수생성

변수생성

total_room

  • 침실의 수(bedrooms) + 화장실의 수(bathrooms)

sqft_total_size

  • 지하실을 제외한 평방 피트(sqft_above) + 지하실의 평방 피트(sqft_basement)

per_price

  • 집의 가격(per_price) / sqft_total_size

1
2
3
4
data_raw=data.copy()
data_raw['total_rooms'] = data_raw['bedrooms'] + data_raw['bathrooms']
data_raw['sqft_total_size'] = data_raw['sqft_above'] + data_raw['sqft_basement']
data_raw['per_price'] = data_raw["price"] / data_raw['sqft_total_size']
cs

is_renovated(집의 재건축 여부)

  • 재건축 되지 않은 경우 : 1
  • 재건축 된 경우 : 0

house_age

  • 재건축 되지 않은 경우 : 거래년도(yyyy) - 건축년도(yr_built)

  • 재건축 된 경우 : 거래년도(yyyy) - 재건축년도(yr_renovated)

1
2
3
4
5
6
7
8
years = data['date'].map(lambda x: int(x[:4]))
data['house_age'= years - data['yr_renovated'].astype('int')
data['yr_renovated'= data['yr_renovated'].apply(lambda x: np.nan if x == 0 else x)
data['yr_renovated'= data['yr_renovated'].fillna(data['yr_built'])
data['is_renovated'= data['yr_renovated'- data['yr_built']
data['is_renovated'= data['is_renovated'].apply(lambda x: 0 if x == 0 else 1)
boolvec = data['is_renovated'== 0
data['house_age'][boolvec] = years - data['yr_built']
cs

5. 목적변수와 설명변수의 데이터 시각화 및 통계량 파악

Price와 sqft_living간의 관계

1
2
3
4
raw_data = pd.concat([data['price'], data['sqft_living']], axis=1)
f, ax = plt.subplots(figsize=(158))
plt.title("Price <==> sqft_living")
fig = sns.regplot(x='sqft_living', y="price", data=raw_data, color="g")
cs









대체적으로 6000이하에 머물러 있지만, 면적이 높아질수록 가격이 높게 형성되어지는 것을 볼 수 있다. 상관관계분석에서 본 것처럼 가장 타겟변수와 상관성이 높은 변수라 할 수 있습니다. 하지만 12000이상에서 이상치를 발견 할 수 있고, 이는 나중에 제거할지 안할지 모델링을 할 때 고려해봐야 될 것 같습니다.

Price와 sqft_living간의 관계(정규화)









정규화를 하여 대체적인 선형관계를 이루어지지만 여전히 분산이 큰 것을 알 수 있습니다.

Price와 grade의 관계

1
2
3
4
raw_data = pd.concat([data['price'], data['grade']], axis=1)
f, ax = plt.subplots(figsize=(158))
plt.title("Price <==> Grade")
fig = sns.boxplot(x='grade', y="price", data=raw_data)
cs









grade가 높아지면 높아질수록 가격대가 높게 형성되는 것을 볼 수 있습니다. 하지만 이 그래프에서 유심히 봐야 할 것은 6~11단계까지 이상치가 많이 존재하고, 특히 11단계에선 확실한 이상치를 확인 하였습니다. 또한 13단계는 단계별 상승세와는 다르게 급격히 높아진 것을 확인 할 수 있었습니다.

Price와 grade의 관계(정규화)









정규화를 한 그래프를 보면, 전체적인 양의 상관관계를 이루고 있으며 특히 중요하게 봐야할 점은 3단계가 다른 등급과는 다른 형태를 지니는 것을 확인하였고, 이는 후에 모델링을 할 때에 삭제를 할 지 안할지는 고려해봐야겠습니다.

Price와 sqft_above의 관계

1
2
3
4
raw_data = pd.concat([data['price'], data['sqft_above']], axis=1)
f, ax = plt.subplots(figsize=(158))
plt.title("Price <==> sqft_above")
fig = sns.regplot(x='sqft_above', y="price", data=raw_data, color="b")
cs







sqft_above 변수는 그래프의 형태가 sqft_living와 비슷하게 보이며, sqft_living와 마찬가지로 같은 위치에 이상치가 있는 것을 확인 할 수 있음

Price와 sqft_above의 관계(정규화)










정규화를 통해 전체적인 선형관계를 이루고 있으며, 정규화를 했지만 분산이 큰 것을 확인할 수 있음.

Price와 bathrooms의 관계

1
2
3
4
raw_data = pd.concat([data['price'], data['bathrooms']], axis=1)
f, ax = plt.subplots(figsize=(185))
plt.title("Price <==> bathrooms")
fig = sns.boxplot(x='bathrooms', y="price", data=raw_data)
cs







대체적으로 양의 상관관계를 보이지만, 대체적으로 분산이 높고, 4.5에서 특히평균가격이 낮아지고 이상치가 보임

4.75이상부터는 상관성이 있다고 보기 어려움

Price와 bathrooms의 관계(정규화)








Price와 view의 관계

1
2
3
4
raw_data = pd.concat([data['price'], data['view']], axis=1)
f, ax = plt.subplots(figsize=(156))
plt.title("Price <==> view")
fig = sns.stripplot(x='view', y="price", data=raw_data, jitter=True)
cs








view에 따른 price의 관계가 이 그래프를 보면 아주 조금의 차이만 있는 것으로 판단됨

Price와 view의 관계(정규화)








Price와 view의 평균 비교









하지만 평균을 비교해서 보면 0 <-> 1,2,3 <-> 4처럼 평균 가격 등급이 나누어 질 수 있음

Price와 bedrooms의 관계

1
2
3
4
raw_data = pd.concat([data['price'], data['bedrooms']], axis=1)
f, ax = plt.subplots(figsize=(186))
plt.title("Price <==> bedrooms")
fig = sns.boxplot(x='bedrooms', y="price", data=raw_data)
cs







Price와 bedrooms의 관계(정규화)







방의 개수가 2~6이 이상치가 많이 나타남

방의 경우 median 값을 중심으로 살펴보면 어느정도 선형관계를 띄는것을 볼 수 있습니다.

그리고 방의 갯수가 2~6의 경우 방이 적음에도 값이 큰 값들이 많습니다.

이는 우리나라의 경우처럼 방의 크기는 작아도 강남처럼 비싼 지역일 가능성이 높습니다.

Price와 total_rooms의 관계

1
2
3
4
data['total_rooms'= data['bedrooms'+ data['bathrooms']
raw_data = pd.concat([data['price'], data['total_rooms']], axis=1)
f, ax = plt.subplots(figsize=(17,12))
fig = sns.boxplot(x='total_rooms', y="price", data=raw_data)
cs











변수를 새로 생성하고 그래프를 그려보았고, 나중에 모델링을 할 때, 모델의 복잡성을 낮추고 정확도를 높을 수 있으면 변수 사용

연도별 구매횟수

1
data.groupby("date_year").size()
cs







월별 평균 집값

1
2
3
4
5
ax = plt.subplots() 
ax = sns.barplot(x='date_month', y='price', data=data) 
ax.set_title('data_month<==>price') 
ax.set_xlabel('data_month') 
ax.set_ylabel('price')
cs










월별로 집값의 차이가 나타났고, 4월(봄)이 가장 높은 가격대를 형성하였으며 점차 계절이 지나면서 가격이 내려가는 형상을 띄고 있습니다. 2월이 가장 최저점찍고 다시 봄이 오면서 올라가는 형태를 가지고 있습니다.

1
data.groupby("date_month")["price"].mean()
cs











Price와 waterfront의 관계

1
2
3
4
5
ax = plt.subplots() 
ax = sns.barplot(x='waterfront', y='price', data=data) 
ax.set_title('waterfront<==>price') 
ax.set_xlabel('waterfront') 
ax.set_ylabel('price')
cs









1
data.groupby("waterfront").count()
cs







리버뷰의 차이가 상당히 크게 나타남

강이 보이는 곳이 안보이는 곳에 비해 확연히 적기 때문에 비쌀 수 밖에 없음

Price와 condition의 관계

1
2
3
4
5
ax = plt.subplots() 
ax = sns.barplot(x='condition', y='price', data=data) 
ax.set_title('condition<==>price') 
ax.set_xlabel('condition') 
ax.set_ylabel('price')
cs










집의 전반적인 상태는 1,2와 3,4,5로 나누어 진 것처럼 3,4,5단계는 집값의 차이가 많이 나지 않음

Price와 house_age간의 관계

1
2
f, ax = plt.subplots(figsize=(10,10))
fig = sns.regplot(x='house_age', y="price", data=data)
cs















house_age는 집값과 -0.19의 음의 상관관계를 가진다.

house_age값이 -1인 경우는 집계약을 먼저 하고 그 다음년도에 건축이 완료된 경우라고 판단된다.

Price와 long의 관계

1
2
3
4
raw_data = pd.concat([data['price'], data['long']], axis=1)
f, ax = plt.subplots(figsize=(158))
plt.title("Price <==> long")
fig = sns.regplot(x='long', y="price", data=raw_data, color="y")
cs









경도는 집값과 전혀 관계가 없어보인다.

Price와 lat의 관계

1
2
3
4
raw_data = pd.concat([data['price'], data['lat']], axis=1)
f, ax = plt.subplots(figsize=(158))
plt.title("Price <==> lat")
fig = sns.regplot(x='lat', y="price", data=raw_data, color="k")
cs









반대로 위도는 위도가 높아질수록 집값이 높게 형성되는 것을 볼 수 있음

위의 지역은 시애틀인데 시애틀은 북쪽으로 갈수록 살기 좋은 집들이 많다.

전체 데이터의 위치 시각화

1
2
3
4
5
6
7
8
9
10
11
12
13
= folium.Map(
    location=[data.lat.mean(), data.long.mean()], #위도의 평균, 경도의 평균으로 맵을 그림
    tiles='Cartodb Positron'
    zoom_start=13   
)
for i in range(0,len(data)):
    folium.Circle(
        location = data.loc[i, ['lat''long']],
        tooltip = data.loc[i, 'price'],
        radius = 200,
        color ='green'
    ).add_to(m)
m
cs










집값 상위 100개, 하위 100개 지도 시각화

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
min_price=data.sort_values("price").head(100)
max_price=data.sort_values("price").tail(100)
max_price.reset_index(inplace=True)
min_price.reset_index(inplace=True)
 
= folium.Map(
    location=[data.lat.mean(), data.long.mean()], #위도의 평균, 경도의 평균으로 맵을 그림
    tiles='Cartodb Positron'
    zoom_start=13   
)
for i in max_price.index[:100]:
    folium.Circle(
        location = max_price.loc[i, ['lat''long']],
        tooltip = max_price.loc[i, 'price'],
        radius = 200,
        color ='red'
    ).add_to(m)
    
for k in min_price.index[:100]:
    folium.Circle(
        location = min_price.loc[k, ['lat''long']],
        tooltip = min_price.loc[k, 'price'],
        radius = 200,
        color ='blue'
    ).add_to(m)
m
cs














1
2
data_raw['sqft_total_size'= data_raw['sqft_above'+ data_raw['sqft_basement']
data_raw['per_price'= data_raw["price"/ data_raw['sqft_total_size']
cs

데이터를 우편번호(zipcode)로 그룹화하여 우편번호의 개수, 각 우편번호별 데이터의 개수

1
data_raw.groupby("zipcode").size()
cs








1
2
3
4
zipcode_min5=data_raw.groupby("zipcode")["per_price"].mean().sort_values().head(5)
zipcode_max5=data_raw.groupby("zipcode")["per_price"].mean().sort_values().tail(5)
print(zipcode_max5)
print(zipcode_min5)
cs









1
2
perprcie_min5=data_raw[(data_raw["zipcode"]==98023) | (data_raw["zipcode"]==98032) | (data_raw["zipcode"]==98002) |(data_raw["zipcode"]==98001) |(data_raw["zipcode"]==98030)].reset_index()
perprcie_max5=data_raw[(data_raw["zipcode"]==98119) | (data_raw["zipcode"]==98109) | (data_raw["zipcode"]==98112) |(data_raw["zipcode"]==98004) |(data_raw["zipcode"]==98039)].reset_index()
cs

면적당 집값 상위 지역 5개, 하위 지역 5개 시각화

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
= folium.Map(
    location=[data.lat.mean(), data.long.mean()], #위도의 평균, 경도의 평균으로 맵을 그림
    tiles='Cartodb Positron'
    zoom_start=13   
)
for i in range(0,len(perprcie_min5)):
    folium.Circle(
        location = perprcie_min5.loc[i, ['lat''long']],
        tooltip = perprcie_min5.loc[i, 'price'],
        radius = 200,
        color ='green'
    ).add_to(m)
    
for k in range(0,len(perprcie_max5)):
    folium.Circle(
        location = perprcie_max5.loc[k, ['lat''long']],
        tooltip = perprcie_max5.loc[k, 'price'],
        radius = 200,
        color ='yellow'
    ).add_to(m)
m
cs





댓글

이 블로그의 인기 게시물

SQL(9)(performance tuning queries, pivoting data)

데이터 크롤링(3주차)

SQL(8)(writing subqueries, window functions)