https://github.com/BambooStreet/MachineLearning/blob/main/따릉이.ipynb
↑↑↑깃헙이 더 보기 편해요
## 라이브러리 로딩
import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV
from sklearn import metrics
## 데이터 로딩 및 확인
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')
submission = pd.read_csv('submission.csv')
train.info()
test.info()
train.head()
test.head()
## 결측치 확인 및 처리
train.isnull().sum()
test.isnull().sum()
train.describe()
결측치 처리를 위해 데이터를 합쳐준다.
data = pd.concat([train,test])
data
합쳐진 데이터의 결측치를 평균값으로 채워준다.
이진 분류된 피처는 제외한다.
def fill_nan(df,column) :
df[column] = df[column].fillna(value = df[column].mean())
fill_nan(data,'hour_bef_temperature')
fill_nan(data,'hour_bef_windspeed')
fill_nan(data,'hour_bef_humidity')
fill_nan(data,'hour_bef_visibility')
fill_nan(data,'hour_bef_ozone')
fill_nan(data,'hour_bef_pm10')
fill_nan(data,'hour_bef_pm2.5')
이진 분류된 피처인 hour_bef_precipitation은 평균값으로 결측치를 채우면 안된다.
따라서 상태를 살펴보자
data[data['hour_bef_precipitation'].isnull()]
습도가 52도이고, 대부분의 비 정보는 비가 안오는 경우가 대부분이다.
hour_bef_precipitation 결측치는 0으로 처리해도 무방할 것 같다.
data['hour_bef_precipitation'] = data['hour_bef_precipitation'].fillna(value=0)
data.isnull().sum()
결측치 처리가 완료되었다. 학습을 위해 다시 데이터를 분리해준다.
train = data[~pd.isnull(data['count'])]
test = data[pd.isnull(data['count'])]
## 상관관계 분석
import matplotlib.pyplot as plt
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
train.corr()
plt.figure(figsize = (12, 12))
sns.heatmap(train.corr(), annot = True)
1에 가까울수록 관계가 깊다, 0에 가까울수록 관계가 적다.
상관성이 있다고 생각되는 데이터를 중점으로 살펴보았다.
train.corr()>=0.3
hour_bef_pm10, hour_bef_pm2.5(1시간 전 미세먼지 데이터)는 자전거 대여량과 큰 상관관계를 보이지 않는 것 같다.
hour_bef_visibility(1시간 전 시정, 시계) 또한 0.3으로 상관관계가 약한 것으로 파악된다.
추후 학습 데이터에서 제거한다.
## 분포도
수치형 데이터의 집계 값을 나타낸다,
plt.rc('font',size=15)
sns.displot(train['count'])
타겟 값의 분포가 0 근처에 몰려있고 분포가 왼쪽으로 많이 편향되어 있다.
회귀모델이 좋은 성능을 내려면 정규분포를 따라야 하는데, 현재 타깃값 count는 정규분포를 따르지 않는다.
로그변환을 통해 왼쪽으로 쏠린 데이터를 바꿔준다.
sns.displot(np.log(train['count']))
추후에 지수변환을 통해 타깃값인 count로 복원해야 한다.
### 막대그래프
sns.displot(train['hour_bef_precipitation'])
rain = train[train['hour_bef_precipitation']==1]
rain.describe()['count']
train.describe()['count']
## 회귀선을 포함한 산점도 그래프
수치형 데이터가 대부분이므로 산점도 그래프로 시각화 해보았다.
#figure 준비
plt.rc('font',size=9)
figure, axes = plt.subplots(nrows=4,ncols=2)
plt.tight_layout()
figure.set_size_inches(10,20)
#figure 서브플롯에 할당
#기온, 풍속, 습도, 시정, 오존, 미세먼지10, 미세먼지 2.5 산점도 그래프
sns.regplot(x='hour_bef_temperature', y = 'count', data = train, ax = axes[0,0],
scatter_kws={'alpha':0.3},line_kws={'color':'g'})
sns.regplot(x='hour_bef_windspeed', y = 'count', data = train, ax = axes[0,1],
scatter_kws={'alpha':0.3},line_kws={'color':'g'})
sns.regplot(x='hour_bef_humidity', y = 'count', data = train, ax = axes[1,0],
scatter_kws={'alpha':0.3},line_kws={'color':'g'})
sns.regplot(x='hour_bef_visibility', y = 'count', data = train, ax = axes[1,1],
scatter_kws={'alpha':0.3},line_kws={'color':'g'})
sns.regplot(x='hour_bef_ozone', y = 'count', data = train, ax = axes[2,0],
scatter_kws={'alpha':0.3},line_kws={'color':'g'})
sns.regplot(x='hour_bef_pm10', y = 'count', data = train, ax = axes[2,1],
scatter_kws={'alpha':0.3},line_kws={'color':'g'})
sns.regplot(x='hour_bef_pm2.5', y = 'count', data = train, ax = axes[3,0],
scatter_kws={'alpha':0.3},line_kws={'color':'g'})
결측치가 많이 존재한 이유도 있겠지만 미세먼지의 회귀 그래프는 상관관계가 적어보인다, 추후 제거할 것을 생각해보자
## 정리
1. 타깃값 변환
* 분포도 확인 결과 타깃값 count가 0근처로 치우쳐 있으므로 로그변환해 정규분포에 가깝게 만들어야 한다. 타깃값을 count가 아닌 log(count)로 변환해 사용할 것이므로 마지막에 다시 지수변환해 count로 복원해야 한다.
2. 피처 제거
* id 피처는 학습에 관계 없으므로 제거한다.
* 미세먼지 피처와 시야 피처는 타겟값과 큰 상관관계가 없고 결측치 비율도 높으므로 제거한다.
## 모델 정의 및 학습
* 모델 : RandomForestRegressor
* 성능 개선
* 피처 엔지니어링 : 앞의 분석 수준에서 모든 모델에서 동일하게 수행
* 하이퍼파라미터 최적화 : grid search
* 기타: 타깃값이 count가 아닌 log(count)임
#### 데이터 합치기
data = pd.concat([train,test])
data
#### 필요없는 피처 제거
#drop_features = ['id','hour_bef_pm10','hour_bef_pm2.5']
drop_features = ['id','hour_bef_pm10','hour_bef_pm2.5','hour_bef_visibility']
data = data.drop(drop_features, axis=1)
data
#### 데이터 나누기
#훈련, 테스트 데이터 나누기
X_train = data[~pd.isnull(data['count'])]
X_test = data[pd.isnull(data['count'])]
#카운트 제거
X_train = X_train.drop(['count'],axis=1)
X_test = X_test.drop(['count'],axis=1)
y = train['count'] #타깃값
#### 데이터 스케일링
from sklearn.preprocessing import MinMaxScaler
#ord = ['hour','hour_bef_temperature','hour_bef_precipitation','hour_bef_windspeed',
# 'hour_bef_humidity','hour_bef_visibility','hour_bef_ozone']
ord = ['hour','hour_bef_temperature','hour_bef_precipitation','hour_bef_windspeed',
'hour_bef_humidity','hour_bef_ozone']
scaler = MinMaxScaler()
train_scaler = scaler.fit(X_train[ord])
X_train[ord] = scaler.transform(X_train[ord])
X_test[ord] = scaler.transform(X_test[ord])
#X_train_scaled = minmax_scaler.transform(X_train)
#X_test_scaled = minmax_scaler.transform(X_test)
X_train.head()
X_test.head()
## 모델 훈련 및 저장
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV
from sklearn import metrics
#모델 생성
randomforest_model = RandomForestRegressor()
#그리드서치 객체 생성
rf_params = {'random_state':[42],
'n_estimators':[100,120],
'max_depth':[32,64],
'min_samples_leaf':[1,6],
'min_samples_split':[2,8]}
gridsearch_random_forest_model = GridSearchCV(estimator=randomforest_model,
param_grid = rf_params,
cv=5)
#그리드서치 수행
log_y = np.log(y)
gridsearch_random_forest_model.fit(X_train,log_y)
print('최적의 하이퍼 파라미터',gridsearch_random_forest_model.best_params_)
print('GridSearchCV 최고 정확도:{0:.4f}'.format(gridsearch_random_forest_model.best_score_))
randomforest_preds = gridsearch_random_forest_model.best_estimator_.predict(X_test)
submission['count'] = np.exp(randomforest_preds) #지수변환
submission
submission.to_csv('0926.csv',index = False)
#46.453