데이터로그😎

[분류] 자전거대여 수요예측 본문

머신러닝/Kaggle

[분류] 자전거대여 수요예측

지연v'_'v 2023. 9. 5. 13:44

캐글 데이터: https://www.kaggle.com/competitions/bike-sharing-demand/data

 

Bike Sharing Demand | Kaggle

 

www.kaggle.com

 

Prerequisite

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
import warnings
warnings.filterwarnings('ignore', category=RuntimeWarning)
from sklearn.metrics import mean_squared_error, mean_absolute_error
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.linear_model import LinearRegression, Ridge, Lasso

bike_df = pd.read_csv('./data/bike_train.csv')

 

bike_df의 shape, null값 확인

# shape 확인
bike_df.shape

# null값 확인
bike_df.isnull().sum()

→ null값 없음

 특이점: datetime 컬럼이 object이며 '2011-01-01 00:00:00' 이와 같은 '년-월-일, 시분초' 형식을 띄고 있음.

 

데이터 전처리

  • datetime을 년, 월, 일, 시간 으로 쪼개겠다. 그 전에 우선 형식을 datetime으로 바꿈
# datetime -> 년,월,일,시간으로 분리하기
bike_df['datetime'] = bike_df['datetime'].apply(pd.to_datetime)
bike_df.info()

 

  • 년,월,일,시간으로 쪼개기
bike_df['year'] = bike_df['datetime'].apply(lambda x : x.year)
bike_df['month'] = bike_df['datetime'].apply(lambda x : x.month)
bike_df['day'] = bike_df['datetime'].apply(lambda x : x.day)
bike_df['hour'] = bike_df['datetime'].apply(lambda x : x.hour)

 

  • 필요없는 column drop
drop_col = ['datetime','casual','registered']
bike_df.drop(drop_col, axis=1, inplace=True)

 

자전거 대여수(count) 분포 시각화

# 자전거 대여 수(count)와 feature 간의 관계
fig, axs = plt.subplots(figsize=(20,10), ncols=4, nrows=2)
feature = ['season', 'holiday', 'workingday', 'weather','year', 'month', 'day', 'hour']
for i, j in enumerate(feature):
    row = int(i/4)
    col = i%4
    sns.barplot(ax= axs[row][col],x = bike_df[j], y = bike_df['count'])

<대여 수>
-  season: 여름(2), 가을(3) 높음
- holiday, workingday: 주중, 휴일여부는 차이 없음
- weather: 맑은(1), 안개(2)가 높음
- year: 2011 < 2012- month: 6~10월달에 높음
- hour: 8~9, 17~18시(출,퇴근시간)가 높음.

 

회귀

  • 필요한 함수 생성
# root mean squared log error 함수
def rmsle(y, pred):
    log_y = np.log1p(y)
    log_pred = np.log1p(pred)
    squared_error = (log_y - log_pred)**2
    rmsle = np.sqrt(np.mean(squared_error))
    return rmsle

def rmse(y, pred):
    return np.sqrt(mean_squared_error(y,pred))

# 평가 함수
def evaluate_regr(y, pred):
    rmsle_val = rmsle(y,pred)
    rmse_val = rmse(y,pred)
    mae_val = mean_absolute_error(y, pred)
    print(f'RMSLE: {round(rmsle_val,3)}, RMSE: {round(rmse_val,3)}, MAE: {round(mae_val,3)}')

# model 학습, 예측 함수
def get_predict(model, X_train, X_test, y_train):
    model.fit(X_train, y_train)
    pred = model.predict(X_test)
    return pred

 

  • 선형회귀
y_target = bike_df['count']
X_features = bike_df.drop(['count'],axis=1, inplace=False)

X_train, X_test, y_train, y_test = train_test_split(X_features, y_target, test_size=0.3, random_state=0)

lr_reg = LinearRegression()
pred =get_predict(lr_reg, X_train, X_test, y_train) 
evaluate_regr(y_test, pred)

RMSLE: 1.165, RMSE: 140.9, MAE: 105.924

-  대여 횟수가 105~140회정도 차이(오류)가 난다? 너무 큰 숫자.
회귀에서 이러한 큰 오류가 발생할 경우, 가장 먼저 살펴봐야 하는 것은 `Target 의 분포가 왜곡된 형태를 이루고 있는지`!

 

target 분포 확인

y_target.hist()

 

- 역시나 0~200사이에 값이 쏠려있다.

- 왜곡된 값을 정규분포로 바꾸기 위해 `로그 변환` 을 취하겠다.

 

target의 로그 변환

y_log_transform = np.log1p(y_target)
y_log_transform.hist()

- 로그변환을 하니 완벽한 정규분포는 아니지만 이전보다는 왜곡이 많이 개선됨.

 

 

y_target 로그변환 후 회귀 진행

# y_target값을 log변환한 값으로 대체
y_target = y_log_transform
X_features = bike_df.drop(['count'],axis=1, inplace=False)

X_train, X_test, y_train, y_test = train_test_split(X_features, y_target, test_size=0.3, random_state=0)

lr_reg = LinearRegression()
pred =get_predict(lr_reg, X_train, X_test, y_train) 

# 로그 변환된 상태인 y_test와 pred 모두 원상복구
y_test_exp = np.expm1(y_test)
pred_exp = np.expm1(pred)

# 평가
evaluate_regr(y_test_exp, pred_exp)

RMSLE: 1.017, RMSE: 162.594, MAE: 109.286

RMSLE는 이전보단 줄었지만.. RMSE, MAE는 오히려 늘어남.

 

Linear regression의 회귀 계수 확인

coef = pd.Series(lr_reg.coef_, index= X_features.columns)
coef_sort = coef.sort_values(ascending=False)
sns.barplot(x=coef_sort.values, y= coef_sort.index)

- year, hour, month, workingday, holiday, season 피처들의 회귀계수가 상대적으로 높음
- year -> 2011,2012 , month -> 1,2,3,4,...와 같이 숫자값의 형태로 의미를 담고 있음.
- 그러나 이들의 경우 개별 숫자값의 크기가 의미가 있지는 않다. 1월, 2월, 등 구분의 의미이지 1 -> 2 -> 3 -> 4.. 식으로 점점 커지는 것을 의미하는 것은 아니다.
- 따라서 year, hour, month는 숫자로 표현되었지만 카테고리형 피처이다.
- 그러므로 이들은 `원-핫 인코딩`을 적용해야 함.

 

원핫 인코딩

cat_col = ['year','hour','month','day','workingday','holiday','season','weather']
X_features_ohe = pd.get_dummies(X_features,columns = cat_col)
X_features_ohe.columns

 

평가

  • 평가 함수 수정
def get_model_predict(model, X_train, X_test, y_train, y_test, is_expm1 =False):
    model.fit(X_train, y_train)
    pred = model.predict(X_test)
    if is_expm1:
        y_test = np.expm1(y_test)
        pred = np.expm1(pred)
    print('-----',model.__class__.__name__,'-----')
    evaluate_regr(y_test, pred)
    return pred

 

from sklearn.linear_model import Ridge, Lasso

y_target = y_log_transform
X_features = bike_df.drop(['count'],axis=1, inplace=False)
X_train, X_test, y_train, y_test = train_test_split(X_features_ohe, y_log_transform, test_size=0.3, random_state=0)


lr_reg = LinearRegression()
ridge_reg = Ridge(alpha=10)
lasso_reg = Lasso(alpha=0.01)

for model in [lr_reg, ridge_reg, lasso_reg]:
    get_model_predict(model, X_train, X_test, y_train, y_test, True)

----- LinearRegression -----

RMSLE: 0.59, RMSE: 97.688, MAE: 63.382

----- Ridge -----

RMSLE: 0.59, RMSE: 98.529, MAE: 63.893

----- Lasso -----

RMSLE: 0.635, RMSE: 113.219, MAE: 72.803

 

로그변환만 했을 때보다 원핫인코딩 + 로그변환까지 적용한 경우에 오차가 훨씬 줄어듦.

 

회귀계수 확인

coef = pd.Series(lr_reg.coef_, index=X_features_ohe.columns)
coef_sort = coef.sort_values(ascending=False)[:40]
sns.barplot(x=coef_sort.values, y = coef_sort.index)

원핫+ 로그변환 전                                                                                                원핫 + 로그 변환 후

회귀계수의 중요도가 원핫+로그변환 적용 전과는 많이 변화했다.

month, day의 중요도가 많이 올라가고 year의 중요도는 낮아짐.

'머신러닝 > Kaggle' 카테고리의 다른 글

[분류] 신용카드 사기 검출  (0) 2023.09.04