이번 프로젝트는 아파트 실거래가를 예측하는 것이다.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import timeit
import sklearn
import warnings
warnings.filterwarnings('ignore')
import sys

train_data=pd.read_csv('d:/data/real_estate/train.csv')
test_data=pd.read_csv('d:/data/real_estate/test.csv')

print('train.csv. Shape: ',train_data.shape)
print('test.csv. Shape: ', test_data.shape)

train.csv. Shape:  (1216553, 13)
test.csv. Shape:  (5463, 12)

훈련데이터는 총 120만개정도이고 , 테스트 데이터는 5400여개가 존재한다.

각 데이터의 정보를 간략하게 살펴보자.

 

train_data.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1216553 entries, 0 to 1216552
Data columns (total 13 columns):
 #   Column                  Non-Null Count    Dtype  
---  ------                  --------------    -----  
 0   transaction_id          1216553 non-null  int64  
 1   apartment_id            1216553 non-null  int64  
 2   city                    1216553 non-null  object 
 3   dong                    1216553 non-null  object 
 4   jibun                   1216553 non-null  object 
 5   apt                     1216553 non-null  object 
 6   addr_kr                 1216553 non-null  object 
 7   exclusive_use_area      1216553 non-null  float64
 8   year_of_completion      1216553 non-null  int64  
 9   transaction_year_month  1216553 non-null  int64  
 10  transaction_date        1216553 non-null  object 
 11  floor                   1216553 non-null  int64  
 12  transaction_real_price  1216553 non-null  int64  
dtypes: float64(1), int64(6), object(6)
memory usage: 120.7+ MB
test_data.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5463 entries, 0 to 5462
Data columns (total 12 columns):
 #   Column                  Non-Null Count  Dtype  
---  ------                  --------------  -----  
 0   transaction_id          5463 non-null   int64  
 1   apartment_id            5463 non-null   int64  
 2   city                    5463 non-null   object 
 3   dong                    5463 non-null   object 
 4   jibun                   5463 non-null   object 
 5   apt                     5463 non-null   object 
 6   addr_kr                 5463 non-null   object 
 7   exclusive_use_area      5463 non-null   float64
 8   year_of_completion      5463 non-null   int64  
 9   transaction_year_month  5463 non-null   int64  
 10  transaction_date        5463 non-null   object 
 11  floor                   5463 non-null   int64  
dtypes: float64(1), int64(5), object(6)
memory usage: 512.3+ KB

훈련데이터와 테스트데이터의 레이블을 살펴보면 훈련데이터에만 transaction_real_price가 있는것을 확인할 수 있다. 이는 결국 훈련 데이터를 통해 가격을 예측해보기 위한점이다.

 

[Train Column name Description]

  • transaction_id : 아파트 거래에 대한 유니크한 아이디
  • apartment_id : 아파트 아이디
  • city : 도시
  • dong : 동
  • jibun : 지번
  • apt : 아파트단지 이름
  • addr_kr : 주소
  • exclusive_use_area : 전용면적
  • year_of_completion : 설립일자
  • transaction_year_month : 거래년월
  • transaction_date : 거래날짜
  • floor : 층
  • transaction_real_price : 실거래가 (target variable)

이제 결측값이 있는지 확인해보자. 

train_null=train_data.drop('transaction_real_price',axis=1).isnull().sum()/len(train_data)*100
test_null=test_data.isnull().sum()/len(test_data)*100
pd.DataFrame({'train_null_count': train_null,'test_null_count':test_null})

결측치 확인

딱히, 결측치는 존재하지 않는다.

 

간단하게 훈련 데이터를 살펴보자.

train_data.head()


transaction_id	apartment_id	city	dong	jibun	apt	addr_kr	exclusive_use_area	year_of_completion	transaction_year_month	transaction_date	floor	transaction_real_price
0	0	7622	서울특별시	신교동	6-13	신현(101동)	신교동 6-13 신현(101동)	84.82	2002	200801	21~31	2	37500
1	1	5399	서울특별시	필운동	142	사직파크맨션	필운동 142 사직파크맨션	99.17	1973	200801	1~10	6	20000
2	2	3578	서울특별시	필운동	174-1	두레엘리시안	필운동 174-1 두레엘리시안	84.74	2007	200801	1~10	6	38500
3	3	10957	서울특별시	내수동	95	파크팰리스	내수동 95 파크팰리스	146.39	2003	200801	11~20	15	118000
4	4	10639	서울특별시	내수동	110-15	킹스매너	내수동 110-15 킹스매너	194.43	2004	200801	21~31	3	120000

탐색적 데이터 분석 및 처리

훈련데이터 ['transaction_real_price']레이블의 통계적 요약을 살펴보면 다음과 같다.

train_data['transaction_real_price'].describe()

count    1.216553e+06
mean     3.822769e+04
std      3.104898e+04
min      1.000000e+02
25%      1.900000e+04
50%      3.090000e+04
75%      4.700000e+04
max      8.200000e+05
Name: transaction_real_price, dtype: float64

평균 38827 Min: 100 MAX: 820000 표준편차:31048

 

이를 그래프로 막대그래프와 왜도 첨도를 통해 그래프를 살펴보자. 

왜도(Skewness): 왼쪽으로 치우쳐져 있을수록 값이 크다.

첨도(Kurtosis): 첨도 값이 3에 가까울 경우 정규분포에 근사한다. 첨도 값이 클수록 뾰족하고 값이 작을수록 완만해진다.

f,ax=plt.subplots(figsize=(8,6))
sns.distplot(train_data['transaction_real_price'])
print("%s->Skewness: %f, Kurtosis: %f" % ('transaction_real_price',train_data['transaction_real_price'].skew(),train_data['transaction_real_price'].kurt()))

transaction_real_price->Skewness: 3.407169, Kurtosis: 24.839821

일반 분포

이 그래프를 정규화에 근사시키기 위해 log1p 함수를 이용해 로그를씌워 다시 한번확인해보자.

train_data['transaction_real_price'] = np.log1p(train_data['transaction_real_price'])
print("%s -> Skewness: %f, Kurtosis: %f" %  ('transaction_real_price',train_data['transaction_real_price'].skew(),train_data['transaction_real_price'].kurt()))

transaction_real_price -> Skewness: -0.094932, Kurtosis: 0.248866

f,ax=plt.subplots(figsize=(8,6))
sns.distplot(train_data['transaction_real_price'])

log를 씌운 그래프 분포

칼럼들의 상관관계

각 칼럼들간의 상관관계를 확인해보자. 

k=train_data.shape[1]
corrmat=train_data.corr()
cols=corrmat.nlargest(k,'transaction_real_price')['transaction_real_price'].index
cm=np.corrcoef(train_data[cols].values.T)
f,ax=plt.subplots(figsize=(8,6))
sns.heatmap(data=cm,annot=True,square=True,fmt='.2f',linewidths=5,cmap='Reds',yticklabels=cols.values,xticklabels=cols.values)

각 칼럼들간의 상관관계

훈련데이터의 'apartment_id' 칼럼의 unique_value의 갯수를 확인해보자.

 

len(train_data['apartment_id'].unique())

12533

 

이후 'transaction_id'를 따로 변수로 지정한 후, 본래의 데이터프레임에서는 없애주자.

train_id=train_data['transaction_id']
train=train_data.drop('transaction_id',axis=1)
test_id=test_data['transaction_id']
test=test_data.drop('transaction_id',axis=1)

Floor(층)

가설) 층수가 높아질수록 집값도 높아진다.

 

'floor' 과 'transaction_real_price'의 연관성을 확인하기 위해 산점도표에 케스팅해보자. 

 

 

f,ax=plt.subplots(figsize=(8,6))
plt.scatter(train['floor'],train['transaction_real_price'])
plt.xlabel('floor')
plt.ylabel('transaction_real_price')
plt.show()

집값과 층수의 연관성

위 그래프를 확인해보면 층수가 높아질수록 집값도 높아진다라는 본인의 생각은 틀린것을 알 수 있다.

 

그렇다면,

floor(층수)칼럼에 대해서 막대그래프를 통한 분포표도 확인해보자.

 

f,ax=plt.subplots(figsize=(8,6))
sns.distplot(train['floor'])
print('%s->Skewness: %f, Kurtosis: %f'% ('floor',train['floor'].skew(),train['floor'].kurt()))

floor->Skewness: 1.324710, Kurtosis: 3.796603

exclusive_use_area(전용면적)

가설) 전용면적이 넓어지면 넓어질수록 집값은 비싸다

우선 훈련데이터의 'exclusive_use_area'를 간단하게 통계적 요약으로 살펴보자.

train['exclusive_use_area'].describe()

count    1.216553e+06
mean     7.816549e+01
std      2.915113e+01
min      9.260000e+00
25%      5.976000e+01
50%      8.241000e+01
75%      8.497000e+01
max      4.243200e+02
Name: exclusive_use_area, dtype: float64

Mean: 78.1 Min: 9.2 Max: 424.3

이를 산점도표에 케스팅하여 전용면적과 집값의 연관성을 확인해보자.

 

f,ax=plt.subplots(figsize=(8,6))
plt.scatter(train['exclusive_use_area'],train['transaction_real_price'])
plt.xlabel('exclusive_use_area')
plt.ylabel('transaction_real_price')
plt.show()

집값과 전용면적의 연관성

대략 위 산점도표를 살펴보면, 전용면적이 넓어질수록 집값도 올라가는 경향을 보인다. 그러므로 가설은 맞다고 가정하자.

 

이상치들을 확인해보자.

 

train[train['exclusive_use_area']>400]

	apartment_id	city	dong	jibun	apt	addr_kr	exclusive_use_area	year_of_completion	transaction_year_month	transaction_date	floor	transaction_real_price
563870	12633	서울특별시	도곡동	193-1	힐데스하임빌라	도곡동 193-1 힐데스하임빌라	424.32	1998	201604	11~20	10	498000
train[(train['exclusive_use_area']<150)&(train['transaction_real_price']<200)]

	apartment_id	city	dong	jibun	apt	addr_kr	exclusive_use_area	year_of_completion	transaction_year_month	transaction_date	floor	transaction_real_price
722888	6225	부산광역시	범전동	263-5	서면	범전동 263-5 서면	138.25	1974	201606	21~30	-1	100

크게 이상치라고 판단할수 있는 건 없는것 같다.

이를 한번더 막대 그래프로 살펴보자.

 

f,ax=plt.subplots(figsize=(8,6))
sns.distplot(train['exclusive_use_area'])
print("%s->Skewness: %f, Kurtosis= %f"% ('exclusive_use_area',train['exclusive_use_area'].skew(),train['exclusive_use_area'].kurt()))

transaction_year_month(거래 년,월)

훈련데이터와 테스트데이터에 있는 년월 칼럼을 따로 뽑아와 새로운 칼럼으로 만들어보자.

train_test_data=[train,test]
for dataset in train_test_data:
    dataset['transaction_year_month']=dataset['transaction_year_month'].astype(str)
    dataset['year']=dataset['transaction_year_month'].str[:4].astype(int)
    dataset['month']=dataset['transaction_year_month'].str[4:6].astype(int)
    dataset['transaction_year_month']=dataset['transaction_year_month'].astype(int)

년도 별로 그래프를 확인해보자.

f,ax=plt.subplots(figsize=(8,6))
sns.boxplot(train['year'],train['transaction_real_price'])
plt.show()

보면 2016년도에 이상치같이 보이는 것이 하나 생겨있다. 이를 좀더 확인해보자.

train[(train['year']==2016)&(train['transaction_real_price']<150)]

apartment_id	city	dong	jibun	apt	addr_kr	exclusive_use_area	year_of_completion	transaction_year_month	transaction_date	floor	transaction_real_price	year	month
722888	6225	부산광역시	범전동	263-5	서면	범전동 263-5 서면	138.25	1974	201606	21~30	-1	100	2016	6

서면 아파트가 값이 낮다. 

그렇다면 서면 전체에 대해서 한번 확인해보자.

 

train[train['apt']=='서면']
	apartment_id	city	dong	jibun	apt	addr_kr	exclusive_use_area	year_of_completion	transaction_year_month	transaction_date	floor	transaction_real_price	year	month
695725	6238	부산광역시	전포동	산99-46	서면	전포동 산99-46 서면	57.59	1976	201105	21~31	3	5900	2011	5
696834	6238	부산광역시	전포동	산99-46	서면	전포동 산99-46 서면	57.59	1976	201108	21~31	1	7500	2011	8
700884	6225	부산광역시	범전동	263-5	서면	범전동 263-5 서면	88.93	1974	201206	1~10	1	25000	2012	6
702589	6238	부산광역시	전포동	산99-46	서면	전포동 산99-46 서면	57.59	1976	201212	21~31	5	6000	2012	12
702876	6238	부산광역시	전포동	산99-46	서면	전포동 산99-46 서면	57.59	1976	201301	11~20	4	7400	2013	1
703979	6238	부산광역시	전포동	산99-46	서면	전포동 산99-46 서면	57.59	1976	201304	1~10	1	7000	2013	4
711024	6238	부산광역시	전포동	산99-46	서면	전포동 산99-46 서면	57.59	1976	201407	21~31	5	7000	2014	7
712441	6238	부산광역시	전포동	산99-46	서면	전포동 산99-46 서면	57.59	1976	201410	21~31	3	8800	2014	10
713864	6238	부산광역시	전포동	산99-46	서면	전포동 산99-46 서면	57.59	1976	201501	1~10	4	7750	2015	1
721684	6238	부산광역시	전포동	산99-46	서면	전포동 산99-46 서면	57.59	1976	201604	1~10	1	8600	2016	4
722888	6225	부산광역시	범전동	263-5	서면	범전동 263-5 서면	138.25	1974	201606	21~30	-1	100	2016	6
723957	6225	부산광역시	범전동	263-5	서면	범전동 263-5 서면	55.04	1974	201608	21~31	2	23200	2016	8
726597	6238	부산광역시	전포동	산99-46	서면	전포동 산99-46 서면	57.59	1976	201612	11~20	4	6650	2016	12
1186983	6225	부산광역시	범전동	263-5	서면	범전동 263-5 서면	34.08	1974	201702	11~20	1	16400	2017	2
1189112	6238	부산광역시	전포동	산99-46	서면	전포동 산99-46 서면	57.59	1976	201706	21~30	3	9300	2017	6
1189212	6225	부산광역시	범전동	263-5	서면	범전동 263-5 서면	54.15	1974	201706	1~10	5	26200	2017	6

서면 아파트 값이 전체적으로 싼것은아니다.. 그럼 뭐때문에 이렇게 값이 떨어진걸까? 확인해보니 floor=-1 인것을 볼수있다.

그렇다면 다른 -1층은 어떨까?

train[train['floor']==-1].sort_values('transaction_real_price')
	apartment_id	city	dong	jibun	apt	addr_kr	exclusive_use_area	year_of_completion	transaction_year_month	transaction_date	floor	transaction_real_price	year	month
722888	6225	부산광역시	범전동	263-5	서면	범전동 263-5 서면	138.250	1974	201606	21~30	-1	100	2016	6
651052	1514	부산광역시	대청동4가	75-176	근영빌라2동	대청동4가 75-176 근영빌라2동	47.170	1996	200809	21~30	-1	1300	2008	9
651087	1513	부산광역시	대청동4가	75-181	근영빌라1동	대청동4가 75-181 근영빌라1동	59.890	1996	200810	11~20	-1	2500	2008	10
651641	1514	부산광역시	대청동4가	75-176	근영빌라2동	대청동4가 75-176 근영빌라2동	47.170	1996	201101	21~31	-1	3200	2011	1
674249	9764	부산광역시	동삼동	213-19	조은아크로빌	동삼동 213-19 조은아크로빌	46.800	2001	201506	11~20	-1	3500	2015	6
...	...	...	...	...	...	...	...	...	...	...	...	...	...	...
537384	10310	서울특별시	청담동	102-13	청담파라곤Ⅱ 2단지	청담동 102-13 청담파라곤Ⅱ 2단지	241.880	2010	201012	11~20	-1	290000	2010	12
1095417	11320	서울특별시	한남동	810	한남더힐	한남동 810 한남더힐	212.524	2011	201705	21~31	-1	480000	2017	5
1095416	11320	서울특별시	한남동	810	한남더힐	한남동 810 한남더힐	212.524	2011	201705	21~31	-1	490000	2017	5
23585	11320	서울특별시	한남동	810	한남더힐	한남동 810 한남더힐	240.230	2011	201612	1~10	-1	543000	2016	12
23376	11320	서울특별시	한남동	810	한남더힐	한남동 810 한남더힐	240.230	2011	201610	11~20	-1	575000	2016	10

지하 1층 치고 비싼곳도 있는것이 확인된다. 그러므로 지하1층은 값이 싸다라는 가정도 패스.

 

그러면 transaction_real_price가 100인 위치를 한번 싹다 확인해보자.

train[train['jibun']=='263-5'].sort_values('floor')

 

	apartment_id	city	dong	jibun	apt	addr_kr	exclusive_use_area	year_of_completion	transaction_year_month	transaction_date	floor	transaction_real_price	year	month
722888	6225	부산광역시	범전동	263-5	서면	범전동 263-5 서면	138.25	1974	201606	21~30	-1	100	2016	6
700884	6225	부산광역시	범전동	263-5	서면	범전동 263-5 서면	88.93	1974	201206	1~10	1	25000	2012	6
1186983	6225	부산광역시	범전동	263-5	서면	범전동 263-5 서면	34.08	1974	201702	11~20	1	16400	2017	2
723957	6225	부산광역시	범전동	263-5	서면	범전동 263-5 서면	55.04	1974	201608	21~31	2	23200	2016	8
1189212	6225	부산광역시	범전동	263-5	서면	범전동 263-5 서면	54.15	1974	201706	1~10	5	26200	2017	6

확인해보니 같은 위치의 가격에 비해 price가 100인 값만 현저하게 작다..

이것을 이상치라고 판단하고 넘어가야하는가... 고민된다 통계적 계산을 통해 이상치 판단 근거를 만들 수 있지만 이는 다음에 해보는 것으로하고 우선 넘어가자.

 

그 다음은 월별과 가격의 연관성을 확인해보자.

위 그림을 보면 6월에 이상치같은 것이 있는데 이는 위에서 얘기한 price:100인 집이다.

 

year_of_completion(설립일자)

f,ax=plt.subplots(figsize=(22,6))
sns.boxplot(train['year_of_completion'],train['transaction_real_price'])
plt.show()

다음 설립일자에서... 너무많지만 가장 이상치같이 보이는 점을 확대해서 확인해보자..

1974년도 설립일자

1974년도에 가장 낮은 값이 있었는데 이는 무엇인지 확인해보자.

train[train['year_of_completion']==1974].sort_values('transaction_real_price',ascending=True)
	apartment_id	city	dong	jibun	apt	addr_kr	exclusive_use_area	year_of_completion	transaction_year_month	transaction_date	floor	transaction_real_price	year	month
722888	6225	부산광역시	범전동	263-5	서면	범전동 263-5 서면	138.25	1974	201606	21~30	-1	4.615121	2016	6
350513	12414	서울특별시	화곡동	354-54	화곡동복지	화곡동 354-54 화곡동복지	53.94	1974	201206	21~30	1	7.601402	2012	6
689192	7162	부산광역시	가야동	385-2	시영	가야동 385-2 시영	39.67	1974	201004	21~30	6	7.696667	2010	4
679153	7162	부산광역시	가야동	385-2	시영	가야동 385-2 시영	39.67	1974	200807	11~20	4	7.824446	2008	7
678316	7162	부산광역시	가야동	385-2	시영	가야동 385-2 시영	39.67	1974	200805	11~20	5	7.863651	2008	5
...	...	...	...	...	...	...	...	...	...	...	...	...	...	...
1163472	4695	서울특별시	반포동	757	반포 주공1단지	반포동 757 반포 주공1단지	140.33	1974	201706	11~20	2	12.706851	2017	6
1164677	4695	서울특별시	반포동	757	반포 주공1단지	반포동 757 반포 주공1단지	140.33	1974	201709	11~20	3	12.736704	2017	9
1164022	4695	서울특별시	반포동	757	반포 주공1단지	반포동 757 반포 주공1단지	140.33	1974	201707	21~31	5	12.765691	2017	7
1164377	4695	서울특별시	반포동	757	반포 주공1단지	반포동 757 반포 주공1단지	140.33	1974	201708	1~10	3	12.765691	2017	8
1164370	4695	서울특별시	반포동	757	반포 주공1단지	반포동 757 반포 주공1단지	140.33	1974	201708	1~10	4	12.779876	2017	8

이또한 똑같은 데이터였다.. 

 

변수 카테고리화

city(도시)

우선 서울특별시와 부산광역시를 레이블 0,1로 구분시켜 다시 데이터프레임을 구성하자.

 

replace_name={'서울특별시':0,'부산광역시':1}
train=train.replace({'city':replace_name})
test=test.replace({'city':replace_name})
f,ax=plt.subplots(figsize=(8,6))
sns.boxplot(train['city'],train['transaction_real_price'])
plt.show()

지역별 집값 추세

보니 서울이 부산보다 집값이 높은것을 확인할 수 있다. 거래량도 확인해보자

f,ax=plt.subplots(figsize=(8,6))
sns.countplot(train['city'])
plt.show()

지역별 거래량

이를 통해 집값은 서울이 더 높고, 거래량도 서울이 더 높은것을 확인할 수 있다.

 

addr_kr(주소)

동, 지번, 아파트단지이름을 합친 것을 addr_kr로 하였는데 이는 그냥 삭제해버리자.

train=train.drop('addr_kr',axis=1)
test=test.drop('addr_kr',axis=1)

dong(동)

데이콘 튜토리얼 샘플코드에서는 한강의 유무에 따른 feature를 하나 생성해준다. 그러므로 본인도 만들어놓아본다.

train['hangang']=train['dong'].isin(['성수동1가','삼성동','이촌동','공덕동','서교동','한강로3가','목동']).astype(int)
test['hangang']=train['dong'].isin(['성수동1가','삼성동','이촌동','공덕동','서교동','한강로3가','목동']).astype(int)
len(train['dong'].unique())

473

dong은 그 지역을 그룹핑한 것이다. 그러므로 지역에 따라 가격차이를 확인해 줄 수 있다.

아파트 거래가격의 평균순으로 데이터를 레이블링해보자.

 

train_dong=train[['transaction_real_price','dong']].groupby('dong').mean().sort_values('transaction_real_price').reset_index()
train_dong.head()
dong	transaction_real_price
0	신선동3가	5500.000000
1	장안읍 명례리	5508.333333
2	신창동2가	5531.250000
3	봉래동5가	5732.546012
4	중앙동4가	6312.500000
dong_num={}
for i in range(len(train_dong)):
    dong=train_dong['dong'].iloc[i]
    dong_num[dong]=i
dong_num

{'신선동3가': 0,
 '장안읍 명례리': 1,
 '신창동2가': 2,
 '봉래동5가': 3,
 '중앙동4가': 4,
 '장충동2가': 5,
 '신선동2가': 6,
 '부평동2가': 7,
 '필동1가': 8,
 '아미동2가': 9,
 '초장동': 10,
 '수정동': 11,
 '기장읍 대변리': 12,
 '동광동5가': 13,
 '보수동1가': 14,
 '남항동3가': 15,
 '대청동1가': 16,
 '누상동': 17,
 '반송동': 18,
 '기장읍 서부리': 19,
 '동삼동': 20,
 '영등포동3가': 21,
 '일광면 이천리': 22,
 '봉래동4가': 23,
 '서동': 24,
 '일광면 삼성리': 25,
 '대청동4가': 26,
 '영등포동2가': 27,
 '덕천동': 28,
 '모라동': 29,
 '감만동': 30,
 '미근동': 31,
 '장림동': 32,
 '학장동': 33,
 '동대신동3가': 34,
 '구평동': 35,
 '보수동2가': 36,
 ...
 '대치동': 464,
 '서빙고동': 465,
 '한남동': 466,
 '반포동': 467,
 '회현동2가': 468,
 '용산동5가': 469,
 '청암동': 470,
 '압구정동': 471,
 '장충동1가': 472}
train=train.replace({'dong': dong_num})
test=test.replace({'dong':dong_num})
train.head()
apartment_id	city	dong	jibun	apt	exclusive_use_area	year_of_completion	transaction_year_month	transaction_date	floor	transaction_real_price	year	month	hangang
0	7622	0	335	6-13	신현(101동)	84.82	2002	200801	21~31	2	37500	2008	1	0
1	5399	0	408	142	사직파크맨션	99.17	1973	200801	1~10	6	20000	2008	1	0
2	3578	0	408	174-1	두레엘리시안	84.74	2007	200801	1~10	6	38500	2008	1	0
3	10957	0	459	95	파크팰리스	146.39	2003	200801	11~20	15	118000	2008	1	0
4	10639	0	459	110-15	킹스매너	194.43	2004	200801	21~31	3	120000	2008	1	0

Jibun(지번)

지번의 unique_value를 확인해보자.

len(train['jibun'].unique())

8961
train_jibun=train[['transaction_real_price','jibun']].groupby('jibun').mean().sort_values('transaction_real_price').reset_index()
train_jibun.head()
	jibun	transaction_real_price
0	산3-148	1428.263889
1	737-1	1852.413793
2	741-1	1855.584416
3	11-174	2000.000000
4	1181-4	2043.055556

apt(아파트)

아파트의 unique_value를 확인해보자

len(train['apt'].unique())

10440
train_apt=train[['transaction_real_price','apt']].groupby('apt').mean().sort_values('transaction_real_price').reset_index()
train_apt.head()
	apt	transaction_real_price
0	좌천시민(737-1)	1852.413793
1	좌천시민(741-1)	1855.584416
2	수정(1181-4)	2043.055556
3	수정(1175-1)	2111.785714
4	수정(1186-1)	2292.333333

확인해보니 지번과 아파트 데이터는 unique_value가 너무 많다.. 따로 레이블링은 하지말자..

 

transaction_date(거래 기간)

위 데이터는 처음 거래시작한 day와 거래가 끝난 day의 차이를 칼럼으로 변경시켜주자.

train['day_diff']=train['transaction_date'].str.extract('(~\d+)')[0].str[1:].astype(int)-train['transaction_date'].str.extract('(\d+~)')[0].str[:-1].astype(int)
test['day_diff']=test['transaction_date'].str.extract('(~\d+)')[0].str[1:].astype(int)-test['transaction_date'].str.extract('(\d+~)')[0].str[:-1].astype(int)

이것에 대해 unique_value를 확인해보자.

len(train['transaction_date'].unique())

6

거래기간은 unique_value가 6개이므로 이는 레이블링 처리를 해주자.

 

train_date=train[['transaction_real_price','transaction_date']].groupby('transaction_date').mean().sort_values('transaction_real_price').reset_index()
train_date.head()
transaction_date	transaction_real_price
0	21~29	33055.585346
1	21~28	37182.344295
2	1~10	37732.015054
3	11~20	38217.678678
4	21~31	38879.439698
date_num={}
for i in range(len(train_date)):
    date=train_date['transaction_date'].iloc[i]
    date_num[date]=i
date_num

{'21~29': 0, '21~28': 1, '1~10': 2, '11~20': 3, '21~31': 4, '21~30': 5}
train=train.replace({'transaction_date':date_num})
test=test.replace({'transaction_date':date_num})
train.head()
apartment_id	city	dong	jibun	apt	exclusive_use_area	year_of_completion	transaction_year_month	transaction_date	floor	transaction_real_price	year	month	hangang	day_diff
0	7622	0	335	6-13	신현(101동)	84.82	2002	200801	4	2	37500	2008	1	0	10
1	5399	0	408	142	사직파크맨션	99.17	1973	200801	2	6	20000	2008	1	0	9
2	3578	0	408	174-1	두레엘리시안	84.74	2007	200801	2	6	38500	2008	1	0	9
3	10957	0	459	95	파크팰리스	146.39	2003	200801	3	15	118000	2008	1	0	9
4	10639	0	459	110-15	킹스매너	194.43	2004	200801	4	3	120000	2008	1	0	10

전처리

floor 같은 경우 음수값이 존재하기 때문에 log를 취하기 전에 각 값에 +5를 해주어 모든 값을 양수로 만들어주자.

train['floor']=np.log(train['floor']+5)
test['floor']=np.log(test['floor']+5)
f,ax=plt.subplots(figsize=(8,6))
sns.distplot(train['floor'])
print("%s->Skewness: %f, Kurtosis: %f"%('floor',train['floor'].skew(),train['floor'].kurt()))

floor->Skewness: 0.089636, Kurtosis: -0.640885

이제 아파트와 지번 그리고 거래년월을 없애버리자.

drop_columns=['apt','jibun','transaction_year_month']

train=train.drop(drop_columns,axis=1)
test=test.drop(drop_columns,axis=1)
train.head()
	apartment_id	city	dong	exclusive_use_area	year_of_completion	transaction_date	floor	transaction_real_price	year	month	hangang	day_diff
0	7622	0	335	84.82	2002	4	1.945910	37500	2008	1	0	10
1	5399	0	408	99.17	1973	2	2.397895	20000	2008	1	0	9
2	3578	0	408	84.74	2007	2	2.397895	38500	2008	1	0	9
3	10957	0	459	146.39	2003	3	2.995732	118000	2008	1	0	9
4	10639	0	459	194.43	2004	4	2.079442	120000	2008	1	0	10

Feature Engineering

train_test_data=[train,test]

for dataset in train_test_data:
    dataset['age']=dataset['year']-dataset['year_of_completion']
    dataset['is_rebuild']=(dataset['age']>=30).astype(int)
    

거래하는 기간까지의 아파트 나이를 특징으로 생성해주고, 샘플코드에 있는 아파트의 재건축 유무를 판단하는 특징을 만들어준다.

train_columns=[]
for column in train.columns[:]:
    if train[column].skew()>=1:
        print('%s -> Skewness: %f, Kurtosis: %f'%(column,train[column].skew(),train[column].kurt()))
        train_columns.append(column)
    elif train[column].kurt()>=3:
        print('%s -->Skewness: %f, Kurtosis: %f'%(column,train[column].skew(),train[column].kurt()))
        train_columns.append(column)
        
exclusive_use_area -> Skewness: 1.227509, Kurtosis: 3.100517
transaction_real_price -> Skewness: 3.407169, Kurtosis: 24.839821
hangang -> Skewness: 6.358349, Kurtosis: 38.428662
day_diff -->Skewness: -0.589751, Kurtosis: 4.573129
is_rebuild -> Skewness: 3.324832, Kurtosis: 9.054522

정규분포 근사화를 위해 첨도와 왜도를 조정해준다.

for column in train_columns :
    train[column] = np.log1p(train[column])
    test[column] = np.log1p(test[column])
    print("%s -> Skewness: %f, Kurtosis: %f" %  (column,train[column].skew(), 
                                                 train[column].kurt()))
                                                 
                                                 

exclusive_use_area -> Skewness: -0.438156, Kurtosis: 1.744119
hangang -> Skewness: 6.358349, Kurtosis: 38.428662
day_diff -> Skewness: -1.128137, Kurtosis: 6.788569
is_rebuild -> Skewness: 3.324832, Kurtosis: 9.054522

이제 상관관계를 다시 확인해보자.

#상관관계 확인
k=train.shape[1] #히트맵 변수 갯수
corrmat = train.corr() #변수간의 상관관계
cols = corrmat.nlargest(k, 'transaction_real_price')['transaction_real_price'].index #price기준으로 제일 큰순서대로 20개를 뽑아냄
cm = np.corrcoef(train[cols].values.T)
f, ax = plt.subplots(figsize=(20, 6))
sns.heatmap(data = cm, annot=True, square=True, fmt = '.2f', linewidths=.5, cmap='Reds', 
            yticklabels = cols.values, xticklabels = cols.values)

 

모델링

from sklearn.linear_model import ElasticNet, Lasso
from sklearn.ensemble import GradientBoostingRegressor, RandomForestRegressor
from sklearn.kernel_ridge import KernelRidge
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import RobustScaler
from sklearn.model_selection import KFold, cross_val_score, train_test_split
from sklearn.metrics import mean_squared_error
import xgboost as xgb
import lightgbm as lgb
target = train['transaction_real_price']
del train['transaction_real_price']
#cross validation score
n_folds = 2

def cv_score(models):
    kfold = KFold(n_splits=n_folds, shuffle=True ,random_state=42).get_n_splits(train.values)
    for m in models:
        cvs = np.mean(cross_val_score(m['model'], train.values, target, cv=kfold))
        rmse = np.mean(np.sqrt(-cross_val_score(m['model'], train.values, np.expm1(target), scoring = "neg_mean_squared_error", cv = kfold)))
        print("Model {} CV score : {:.4f}".format(m['name'], cvs))
        print("RMSE : {:.4f}".format(rmse))
lasso = make_pipeline(RobustScaler(), Lasso(alpha = 0.0005, random_state=42))
ENet = make_pipeline(RobustScaler(), ElasticNet(alpha=0.0005, l1_ratio=.9, random_state=42))
gboost = GradientBoostingRegressor(random_state=42)
forest = RandomForestRegressor(n_estimators = 100, n_jobs = -1, random_state=42)
xgboost = xgb.XGBRegressor(random_state=42)
lightgbm = lgb.LGBMRegressor(random_state=42, num_leaves = 100, min_data_in_leaf = 15, max_depth=6,
                            learning_rate = 0.1, min_child_samples = 30, feature_fraction=0.9, bagging_freq= 1,
                            bagging_fraction = 0.9, bagging_seed = 11, lambda_l1 = 0.1, verbosity = -1 )

models = [{'model': gboost, 'name':'GradientBoosting'}, {'model': xgboost, 'name':'XGBoost'},
          {'model': lightgbm, 'name':'LightGBM'}, {'model' : lasso, 'name' : 'LASSO Regression'}, 
          {'model' : ENet, 'name' : 'Elastic Net Regression'}, {'model' : forest, 'name' : 'RandomForset'}]
start = timeit.default_timer()
cv_score(models)
stop = timeit.default_timer()
print('불러오는데 걸린 시간 : {}초'.format(stop - start))
Model GradientBoosting CV score : 0.5706
RMSE : 0.0108
Model XGBoost CV score : 0.5788
RMSE : 0.0108
Model LightGBM CV score : 0.5681
RMSE : 0.0108
Model LASSO Regression CV score : 0.5526
RMSE : 0.0107
Model Elastic Net Regression CV score : 0.5602
RMSE : 0.0106
Model RandomForset CV score : 0.4911
RMSE : 0.0118
불러오는데 걸린 시간 : 910.3417148000003초
#여러개의 모델로 만들어진 predict 데이터들의 평균을 구한다.

models = [{'model':xgboost, 'name':'XGBoost'},
          {'model':lightgbm, 'name':'LightGBM'},
         {'model':forest, 'name' : 'RandomForest'}]

def AveragingBlending(models, x, y, sub_x):
    for m in models : 
        m['model'].fit(x.values, y)
    
    predictions = np.column_stack([m['model'].predict(sub_x.values) for m in models])
    return predictions
start = timeit.default_timer()

y_test_pred = AveragingBlending(models, train, target, test)
y_test_pred = (y_test_pred[:, 0]*0.05 + y_test_pred[:, 1]*0.1 + y_test_pred[:, 2]*0.85)
predictions = y_test_pred

stop = timeit.default_timer()
print('불러오는데 걸린 시간 : {}초'.format(stop - start))
[LightGBM] [Warning] feature_fraction is set=0.9, colsample_bytree=1.0 will be ignored. Current value: feature_fraction=0.9
[LightGBM] [Warning] min_data_in_leaf is set=15, min_child_samples=30 will be ignored. Current value: min_data_in_leaf=15
[LightGBM] [Warning] lambda_l1 is set=0.1, reg_alpha=0.0 will be ignored. Current value: lambda_l1=0.1
[LightGBM] [Warning] bagging_fraction is set=0.9, subsample=1.0 will be ignored. Current value: bagging_fraction=0.9
[LightGBM] [Warning] bagging_freq is set=1, subsample_freq=0 will be ignored. Current value: bagging_freq=1
불러오는데 걸린 시간 : 260.0541379999995초
sub = pd.read_csv('d:/data/real_estate/submission.csv')
sub['transaction_real_price'] = np.expm1(predictions)
sub.to_csv('d:/data/real_estate/submission.csv', index=False)

 

 

+ Recent posts