본 장에서는 수요 예측을 진행함에 있어 성능 비교의 기준이 되는 베이스라인 모델을 구축하고자 한다.
1. Library Import
import pandas as pd
from sklearn.preprocessing import OneHotEncoder
from sklearn.linear_model import LinearRegression
import seaborn as sns
import numpy as np
import warnings
import matplotlib.pyplot as plt
from ipywidgets import interact
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import mean_squared_error
plt.style.use('ggplot')
warnings.filterwarnings('ignore')
%config InlineBackend.figure_format = 'retina'
PROJECT_ID='manifest-module-318307' # 본인의 PJT ID를 넣어주시면 됩니다.
2. Data Pre-processing
데이터 전처리는 지난 게시글에 올렸던 전처리를 그대로 옮겨왔다.
%%time
base_query = """
WITH base_data AS
(
SELECT nyc_taxi.*, gis.* EXCEPT (zip_code_geom)
FROM (
SELECT *
FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2015`
WHERE
EXTRACT(MONTH from pickup_datetime) = 1
and pickup_latitude <= 90 and pickup_latitude >= -90
) AS nyc_taxi
JOIN (
SELECT zip_code, state_code, state_name, city, county, zip_code_geom
FROM `bigquery-public-data.geo_us_boundaries.zip_codes`
WHERE state_code='NY'
) AS gis
ON ST_CONTAINS(zip_code_geom, st_geogpoint(pickup_longitude, pickup_latitude))
)
SELECT
zip_code,
DATETIME_TRUNC(pickup_datetime, hour) as pickup_hour,
EXTRACT(MONTH FROM pickup_datetime) AS month,
EXTRACT(DAY FROM pickup_datetime) AS day,
CAST(format_datetime('%u', pickup_datetime) AS INT64) -1 AS weekday,
EXTRACT(HOUR FROM pickup_datetime) AS hour,
CASE WHEN CAST(FORMAT_DATETIME('%u', pickup_datetime) AS INT64) IN (6, 7) THEN 1 ELSE 0 END AS is_weekend,
COUNT(*) AS cnt
FROM base_data
GROUP BY zip_code, pickup_hour, month, day, weekday, hour, is_weekend
ORDER BY pickup_hour
"""
base_df = pd.read_gbq(query=base_query, dialect='standard', project_id='manifest-module-318307') # 본인의 PJT ID를 넣어주시면 됩니다.
enc = OneHotEncoder(handle_unknown='ignore')
enc.fit(base_df[['zip_code']])
ohe_output = enc.transform(base_df[['zip_code']]).toarray()
ohe_df = pd.concat([base_df, pd.DataFrame(ohe_output, columns='zip_code_'+ enc.categories_[0])], axis=1)
def split_train_and_test(df, date):
"""
Dataframe에서 train_df, test_df로 나눠주는 함수
df : 시계열 데이터 프레임
date : 기준점 날짜
"""
train_df = df[df['pickup_hour'] < date]
test_df = df[df['pickup_hour'] >= date]
return train_df, test_df
3. Baseline modeling
베이스라인 모델은 작업한 모델 중 가장 성능이 낮을 모델로 타 모델들의 비교 대상이 된다. 본 프로젝트에서는 Linear regression 을 베이스라인 모델로 사용한다.
3-1) Target 분포 확인
base_df.head()
# 전체 분포
sns.distplot(base_df['cnt']);
전체 분포를 보면 특정 cnt에 많은 데이터가 모여 있는 것을 확인할 수 있다. 몰려있는 데이터를 펼쳐 보기 위해서는 log를 취하면 된다.
# 전체 분포 log화
sns.distplot(np.log10(base_df['cnt']));
3-2) Widget 적용
각 zip code에 대해서 분포를 세부적으로 확인하기 위해 함수를 만들어서 분포를 확인해보려고 한다.
각 분포에 대해 log를 True로 설정하면 log가 취해진 분포, False면 일반적인 그래프를 보여준다.
def visualize_dist_by_zipcode(df, log=False):
def view_images(zip_code):
if log:
data = np.log10(df.loc[df['zip_code'] == str(zip_code)]['cnt'])
else:
data = df.loc[df['zip_code'] == str(zip_code)]['cnt']
ax = sns.distplot(data);
ax.set_title(f'log is {log}, zip_code : {zip_code}')
interact(view_images, zip_code=(10001, 10200))
visualize_dist_by_zipcode(base_df)
visualize_dist_by_zipcode(base_df, log=True)
위의 그래프들을 한번에 보려면 아래와 같은 함수를 만들면 된다.
def visualize_dist_by_zipcode_at_the_same_time(df):
def view_images(zip_code):
fig, axs = plt.subplots(ncols=2, figsize=(15,5))
raw_data = df.loc[df['zip_code'] == str(zip_code)]['cnt']
log_data = np.log10(raw_data)
ax1 = sns.distplot(raw_data, ax=axs[0]);
ax2 = sns.distplot(log_data, ax=axs[1]);
ax1.set_title(f'log is False, zip_code : {zip_code}')
ax2.set_title(f'log is True, zip_code : {zip_code}')
interact(view_images, zip_code=(10001, 10200))
visualize_dist_by_zipcode_at_the_same_time(base_df)
위의 분포를 통해 데이터에 log를 취하면 조금 더 용이할 것이라고 판단하여 log_cnt 데이터를 생성하였다.
base_df['log_cnt'] = np.log10(base_df['cnt'])
4. Train Test Data Split
train_df, test_df = split_train_and_test(base_df, '2015-01-24')
del train_df['pickup_hour']
del test_df['pickup_hour']
2015년 1월 24일 기준으로 train data와 test data를 나눴으며 결과는 아래와 같으며, pickup_hour은 사용하지 않을 열이라 삭제하였다.
train_df.tail()
test_df.tail()
y_train_raw = train_df.pop('cnt')
y_train_log = train_df.pop('log_cnt')
y_test_raw = test_df.pop('cnt')
y_test_log = test_df.pop('log_cnt')
x_train = train_df.copy()
x_test = test_df.copy()
위의 pop에 대한 부분은 이전 포스팅인 [NYC 택시 수요 예측 PJT] 6. 데이터 전처리를 참고하면 된다.
5. Modeling
5-1) Simple regression without one hot encoding
#모델 정의, train data fitting, 예측 실행
lr_reg = LinearRegression()
lr_reg.fit(x_train, y_train_log)
pred = lr_reg.predict(x_test)
test_df['pred_log']= pred
test_df['pred_reverse'] = 10**pred #log를 취한 값들을 원래의 값으로 복구
test_df['real_log'] = y_test_log
test_df['real_raw'] = y_test_raw
#성능을 평가하는 evaluation 함수, 성능평가는 mape, mae, mse로 평가
def evaluation(y_true, y_pred):
y_true, y_pred = np.array(y_true), np.array(y_pred)
mape = np.mean(np.abs((y_true - y_pred) / y_true)) * 100
mae = mean_absolute_error(y_true, y_pred)
mse = mean_squared_error(y_true, y_pred)
score = pd.DataFrame([mape, mae, mse], index=['mape', 'mae', 'mse'], columns=['score']).T
return score
evaluation(test_df['real_raw'], test_df['pred_reverse'])
#Feature Importance
coef = pd.Series(lr_reg.coef_ , index=x_train.columns)
coef_sort = coef.sort_values(ascending=False)[:10]
sns.barplot(x=coef_sort.values , y=coef_sort.index);
5-2) Simple regression with one hot encoding
#One Hot Encoding and delete columns not using
ohe_df['log_cnt'] = np.log10(ohe_df['cnt'])
train_df, test_df = split_train_and_test(ohe_df, '2015-01-24')
del train_df['zip_code']
del train_df['pickup_hour']
del test_df['zip_code']
del test_df['pickup_hour']
y_train_raw = train_df.pop('cnt')
y_train_log = train_df.pop('log_cnt')
y_test_raw = test_df.pop('cnt')
y_test_log = test_df.pop('log_cnt')
x_train = train_df.copy()
x_test = test_df.copy()
lr_reg = LinearRegression()
lr_reg.fit(x_train, y_train_log)
pred = lr_reg.predict(x_test)
test_df['pred_log']= pred
test_df['pred_reverse'] = 10**pred
test_df['real_log'] = y_test_log
test_df['real_raw'] = y_test_raw
# inf를 제외하기 위한 전처리
test_df = test_df[np.isfinite(test_df).all(1)]
infinite 값을 제외하기 위한 전처리라고 하는데 이 부분은 한번에 이해가 안되어서 조금 더 공부해봐야겠다.
evaluation(test_df['real_raw'], test_df['pred_reverse'])
One Hot Encoding 없이 진행했던 예측 결고와 비교해보면 더 좋은 결과를 얻은 것을 확인할 수 있다.
Feature Importance는 결과가 조금 이상하게 나와 재확인중이며 향후 업데이트 하도록 하겠다.
'Programming > Project' 카테고리의 다른 글
[NYC 택시 수요 예측 PJT] 9. XGBoost Regressor, LightGBM Regressor, Random Forest (0) | 2021.08.15 |
---|---|
[NYC 택시 수요 예측 PJT] 8. 베이스라인 모델 구축 - 반복 실험(Sacred 사용) (0) | 2021.08.02 |
[NYC 택시 수요 예측 PJT] 6. 데이터 전처리 (2) | 2021.07.27 |
[NYC 택시 수요 예측 PJT] 4. 데이터 EDA - 데이터 시각화 (Time Domain) - 2 (0) | 2021.07.14 |
[NYC 택시 수요 예측 PJT] 4. 데이터 EDA - 데이터 시각화 (Time Domain) - 1 (0) | 2021.07.11 |