본문 바로가기
Programming/Project

[NYC 택시 수요 예측 PJT] 7. 베이스라인 모델 구축

by 최성현 2021. 7. 28.
320x100

본 장에서는 수요 예측을 진행함에 있어 성능 비교의 기준이 되는 베이스라인 모델을 구축하고자 한다.

 

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는 결과가 조금 이상하게 나와 재확인중이며 향후 업데이트 하도록 하겠다.

300x250