-
(DACON) 아파트 실거래가 예측 튜토리얼 대회IT 지식 창고 2019. 7. 7. 16:47
데이콘에서 주최한 제 2회 튜토리얼 대회입니다.
https://dacon.io/tutorial_comp/116748
1회대회에 아쉽게 실격 처리가 되어 2번째 대회에 다시 도전하여 총 14팀중 5등으로 수상하게 되었습니다.
약 150만개의 데이터를 처음 다뤄보면서 컴퓨터의 한계에 부딪히기도 하였고, colab을 이용하여 데이터를 학습하는 방법을 배웠습니다.
아파트 실거래가 예측-최종정리 In [1]:#주피터 노트북 블로그게시용 함수 from IPython.core.display import display, HTML display(HTML("<style> .container{width:100% !important;}</style>"))
In [1]:# For example, here's several helpful packages to load in import numpy as np # linear algebra import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv) import matplotlib.pyplot as plt #data visualization import seaborn as sns import timeit # time measure import sklearn # modeling import warnings warnings.filterwarnings("ignore") import sys print ('Python version ->', sys.version) print ('Numpy version ->', np.__version__) print ('Pandas version ->', pd.__version__) print ('Sklearn version ->', sklearn.__version__)
1. Data set 불러오기¶
In [2]:train_df = pd.read_csv('train.csv') # training dataframe test_df = pd.read_csv('test.csv') # testing dataframe
In [3]:#원본데이터는 보존하기 위함 train = train_df.copy() test = test_df.copy()
In [4]:print("train.csv. Shape: ",train.shape) print("test.csv. Shape: ",test.shape)
In [5]:train.info()
In [6]:test.info()
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)
In [7]:train_null = train.drop('transaction_real_price', axis = 1).isnull().sum()/len(train)*100 test_null = test.isnull().sum()/len(test)*100 pd.DataFrame({'train_null_count' : train_null, 'test_null_count' : test_null})
Out[7]:In [8]:train.head()
Out[8]:2. Exploratory Data Analysis AND Processing¶
2.1 Target Variable (Dependent Variable)¶
transaction_real_price : 실거래가¶
In [10]:train['transaction_real_price'].describe()
Out[10]:- 총 1216553개의 데이터, 약 120만개
- 평균 : 38227.69
- 표준편차 : 31048.98
- min : 100
- max : 820000, 82만
In [11]:f, ax = plt.subplots(figsize = (8,6)) sns.distplot(train['transaction_real_price']) print("%s -> Skewness: %f, Kurtosis: %f" % ('transaction_real_price',train['transaction_real_price'].skew(), train['transaction_real_price'].kurt()))
- 왜도(Skewness) : 왼쪽으로 치우쳐져 있을수록 값이크고, 오른쪽으로 치우쳐져 있을 수록 값이 작아진다. 즉, 0에 가까울수록 좋은 형태
- 첨도(Kurtosis) : 첨도 값이 3에 가까울 경우 정규분포에 가까우며, 첨도 값이 클수록 뾰족하고 값이 작을 수록 완만해진다
In [12]:train['transaction_real_price'] = np.log1p(train['transaction_real_price']) print("%s -> Skewness: %f, Kurtosis: %f" % ('transaction_real_price',train['transaction_real_price'].skew(), train['transaction_real_price'].kurt()))
In [13]:f, ax = plt.subplots(figsize = (8,6)) sns.distplot(train['transaction_real_price'])
Out[13]:columns 상관관계¶
In [14]:#상관관계 확인 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=(8, 6)) sns.heatmap(data = cm, annot=True, square=True, fmt = '.2f', linewidths=.5, cmap='Reds', yticklabels = cols.values, xticklabels = cols.values)
Out[14]:2.2 Numeric Variable¶
transaction_id & apartment_id : 거래 id & 아파트 id¶
In [15]:len(train['apartment_id'].unique())
Out[15]:transaction_id는 예측을 하는 데 필요없고, apartment_id는 unique로 갯수를 확인해 보니 12533개로 겹치는 부분이 상당히 많은 것을 알 수 있습니다.
transaction_id가 순서가 섞여있지만, 나중에 문제될 것이 없습니다.
따라서, transaction_id는 삭제를 하고, apartment_id는 그대로 놔두겠습니다.
In [16]:train_id = train['transaction_id'] train = train.drop('transaction_id', axis=1) test_id = test['transaction_id'] test = test.drop('transaction_id', axis=1)
floor : 층¶
In [17]:#약 100만개의 데이터라 sns.regplot 사용 시 엄청 오래걸림... 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()
In [19]:f, ax = plt.subplots(figsize = (8,6)) sns.distplot(train['floor']) print("%s -> Skewness: %f, Kurtosis: %f" % ('floor',train['floor'].skew(), train['floor'].kurt()))
대부분의 데이터는 다른데이터와 비교하기 위해 왜도, 첨도 조정은 마지막에 한다.
exclusive_use_area : 전용 면적¶
In [20]:train['exclusive_use_area'].describe()
Out[20]:- 평균 : 78.16549
- 표준편차 : 29.15113
- min : 9.26
- max : 424.32
float형식으로 나타나있음. 면적의 단위는 m^2
In [21]:#약 100만개의 데이터라 sns.regplot 사용 시 엄청 오래걸림... 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()
많이 벗어나 보이는 이상치들을 탐색해보겠습니다.
In [22]:train[train['exclusive_use_area']>400]
Out[22]:In [23]:train[(train['exclusive_use_area']<150)&(train['transaction_real_price']<6)]
Out[23]:딱히 이상이 없어 보임
In [24]: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 : 거래 년, 월¶
In [25]:train_test_data = [train, test] for dataset in train_test_data: #date -> 년, 월 단위로 새로운 칼럼 만듦 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)
In [26]:f, ax = plt.subplots(figsize = (8,6)) sns.boxplot(train['year'], train['transaction_real_price']) plt.show()
In [27]:train[(train['year']==2016) & (train['transaction_real_price']<6)]
Out[27]:서면아파트가 왜이렇게 값이 낮을까 다른 아파트는 어떨지..
In [28]:train[train['apt']=='서면']
Out[28]:floor가 -1인 경우 가격이 현저히 낮음, 다른 -1층은?
In [29]:train[train['floor']==-1].sort_values('transaction_real_price')
Out[29]:-1 치고 가격이 높은 경우도 있음
같은 아파트에서는 floor가 -1인 경우의 영향을 확인해봄
In [30]:train[train['apt']=='근영빌라1동'].sort_values('floor')
Out[30]:대충확인했을 때 아파트에 따라 다름.
따라서, 이상치라고 판단하기 어려워 삭제하지 않는다.
In [31]:f, ax = plt.subplots(figsize = (8,6)) sns.boxplot(train['month'], train['transaction_real_price']) plt.show()
year_of_completion : 설립일자¶
In [32]:f, ax = plt.subplots(figsize = (22,6)) sns.boxplot(train['year_of_completion'], train['transaction_real_price']) plt.show()
이상치를 찾을 수 ㅇ벗..
2.3 Catergorical Variable¶
city : 도시¶
In [33]:replace_name = {'서울특별시' : 0, '부산광역시' : 1 } train = train.replace({'city' : replace_name}) test = test.replace({'city' : replace_name})
In [34]:f, ax = plt.subplots(figsize = (8,6)) sns.boxplot(train['city'], train['transaction_real_price']) plt.show()
In [35]:f, ax = plt.subplots(figsize = (8,6)) sns.countplot(train['city']) plt.show()
addr_kr : 주소¶
동, 지번, 아파트단지이름을 합친 것을 addr_kr로 하였으므로 그냥 삭제 하겠습니다.
In [36]:train = train.drop('addr_kr', axis=1) test = test.drop('addr_kr', axis=1)
dong¶
데이콘 튜토리얼 샘플코드에서 한강의 유무에 따른 feature를 하나 생성 해준다.
In [39]:train['hangang']=train['dong'].isin(['성수동1가','삼성동','이촌동','공덕동','서교동','한강로3가','목동']).astype(int) test['hangang']=test['dong'].isin(['성수동1가','삼성동','이촌동','공덕동','서교동','한강로3가','목동']).astype(int)
In [37]:len(train['dong'].unique())
Out[37]:dong은 그 지역을 그룹화 한 것으로 지역에 따라 가격차이를 보여줄 수 있으므로,
아파트 실거래가의 평균순으로 데이터를 labeling 한다.
In [38]:train_dong = train[['transaction_real_price','dong']].groupby('dong').mean().sort_values('transaction_real_price').reset_index() train_dong.head()
Out[38]:In [40]:dong_num = {} for i in range(len(train_dong)): dong = train_dong['dong'].iloc[i] dong_num[dong] = i dong_num
Out[40]:In [41]:train = train.replace({'dong' : dong_num}) test = test.replace({'dong' : dong_num}) train.head()
Out[41]:Jibun¶
In [62]:len(train['jibun'].unique())
Out[62]:In [63]:train_jibun = train[['transaction_real_price','jibun']].groupby('jibun').mean().sort_values('transaction_real_price').reset_index() train_jibun.head()
Out[63]:apt¶
In [42]:len(train['apt'].unique())
Out[42]:In [43]:train_apt = train[['transaction_real_price','apt']].groupby('apt').mean().sort_values('transaction_real_price').reset_index() train_apt.head()
Out[43]:지번과 아파트 데이터는 unique가 많으므로 따로 labeling을 하지 않는다.
transaction_date¶
transaction_date는 거래기간으로 처음 거래시작한 day와 거래가 끝난 day의 차이를 컬럼으로 만들어 주겠습니다.
In [44]: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)
In [45]:len(train['transaction_date'].unique())
Out[45]:거래기간 feature도 unique 갯수가 몇개 없기 때문에 labeling을 하겠습니다.
In [46]:train_date = train[['transaction_real_price','transaction_date']].groupby('transaction_date').mean().sort_values('transaction_real_price').reset_index() train_date.head()
Out[46]:In [47]:date_num = {} for i in range(len(train_date)): date = train_date['transaction_date'].iloc[i] date_num[date] = i date_num
Out[47]:In [48]:train = train.replace({'transaction_date' : date_num}) test = test.replace({'transaction_date' : date_num}) train.head()
Out[48]:2.4 Preprocessing¶
floor 같은 경우 -값이 존재하기 때문에 log를 취하기 전에 각 값에 +5를 하면서 모든 값을 양수로 만들어줍니다.
In [49]: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()))
In [50]:drop_columns = ['apt', 'jibun', 'transaction_year_month'] train = train.drop(drop_columns, axis=1) test = test.drop(drop_columns, axis=1)
In [51]:train.head()
Out[51]:3. Feature Engineering¶
In [52]:train_test_data = [train, test] for dataset in train_test_data: # 거래하는 기간까지의 아파트 나이를 feature 생성 dataset['age'] = dataset['year'] - dataset['year_of_completion'] # 샘플코드에 있는 아파트의 재건추 유무를 판단하는 feature 생성 dataset['is_rebuild']=(dataset['age']>=30).astype(int)
In [53]: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)
In [54]:#정규분포모형을 가질 수 있도록 첨도와 왜도를 조정 #조정하는 방법에는 square root, quarter root, log 등이 있다. #log에서 0의 값이 들어왔을 때 무한으로 가는 것을 방지하도록 1 더해주는 log1p를 사용 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()))
In [55]:#상관관계 확인 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)
Out[55]:4. Modeling¶
In [56]: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
In [57]:target = train['transaction_real_price'] del train['transaction_real_price']
In [58]:#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))
In [59]: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'}]
In [60]:start = timeit.default_timer() cv_score(models) stop = timeit.default_timer() print('불러오는데 걸린 시간 : {}초'.format(stop - start))
In [62]:#여러개의 모델로 만들어진 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
xgboost, lightgbm, forest 총 3개의 모델을 stacking하여 활용
In [63]: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))
In [64]:sub = pd.read_csv('submission.csv')
In [66]:sub['transaction_real_price'] = np.expm1(predictions)
In [67]:sub.to_csv('submission.csv', index=False)
'IT 지식 창고' 카테고리의 다른 글
영상 등급 분류 보조 시스템 프로젝트 (0) 2019.10.22 주가등락예측 프로젝트(캡스톤 디자인) (0) 2019.07.07 (DACON) 영화 관객수 예측 튜토리얼 대회 (4) 2019.07.07 (Kaggle) 2019 2nd ML month KaKR - House Price (0) 2019.04.21 주가 예측 딥 러닝을 위한 자료들 (0) 2019.04.02 댓글