본문 바로가기
Programming/Project

[NYC 택시 수요 예측 PJT] 6. 데이터 전처리

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

데이터 전처리란 모델링하고자 하는 목적에 맞고 분석하기 좋게 데이터를 다듬고 이상값을 제거하는 과정을 말한다.

데이터 전처리는 SQL(BigQuery)와 Python 둘 다에서 진행이 가능하며, SQL에서 가능한 부분은 SQL에서, SQL에서 하기 힘든 부분은 Python의 Label Encoding, One Hot Encoding을 통해 하고자 한다.

 

코딩에 앞서 전처리할 부분은 아래와 같다.

  • Time data pre-processing 
  • Reverse Geocoding via BigQiery GIS
  • Categorical data pre-processing 
  • Train-Test Split

1. Library Import

import pandas as pd
from sklearn.preprocessing import OneHotEncoder, LabelEncoder
import warnings
import seaborn as sns
from sklearn.linear_model import LinearRegression
warnings.filterwarnings('ignore')

PROJECT_ID='manifest-module-318307'

PROJECT_ID부분에 사용자의 ID를 넣으면 된다.

 

2. 시간 관련 전처리 (Time Data Pre-processing)

2-1) Pre-processing on BigQuery side

  • DATETIME_TRUNC : 데이터 자르기 (e.g. Hour, Minute 단위로 자르기 가능)
  • EXTRACT 함수를 사용하여 MONTH, WEEK, DAY, HOUR 등 추출
  • FORMAT_DATETIME 함수를 사용하여 요일을 추출 (c.f. Pandas에선 0이 월요일이나 BigQuery에서는 1일 월요일)
%%time
extract_query = """
SELECT
    *,
    EXTRACT(MONTH FROM pickup_hour) AS month,
    EXTRACT(DAY FROM pickup_hour) AS day,
    CAST(format_datetime('%u', pickup_hour) AS INT64) -1 AS weekday, # Python과 맞추기 위해 -1
    EXTRACT(HOUR FROM pickup_hour) AS hour,
    CASE WHEN CAST(FORMAT_DATETIME('%u', pickup_hour) AS INT64) IN (6, 7) THEN 1 ELSE 0 END AS is_weekend
FROM (
    SELECT 
        DATETIME_TRUNC(pickup_datetime, hour) AS pickup_hour,
        count(*) AS cnt
    FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2015` 
    WHERE EXTRACT(MONTH from pickup_datetime) = 1
    GROUP BY pickup_hour
)
ORDER BY pickup_hour
"""

df = pd.read_gbq(query=extract_query, dialect='standard', project_id='manifest-module-318307')

전체 데이터(*)에서 pickup_hour, cnt, month, day, weekday, hour, is_weekend 항목을 추출하였고 DATETIEME_TRUNC를 통해 pickup_hour은 시간 단위까지만 불러왔다.

 

df.tail()

2-2) Pre-processing on Python side

  • Pandas datetime으로 처리
  • pd.to_Datetime으로 datetime 변환 후, df[col].dt.xxx로 추출
extract_query = """
SELECT 
    DATETIME_TRUNC(pickup_datetime, hour) AS pickup_hour,
    count(*) AS cnt
FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2015` 
WHERE EXTRACT(MONTH from pickup_datetime) = 1
GROUP BY pickup_hour
ORDER BY pickup_hour
"""

df = pd.read_gbq(query=extract_query, dialect='standard', project_id='manifest-module-318307')
df.head()

df.info()

pickup_hour가 datetime64라 to_datetime을 하지 않아도 날짜 관련 데이터 추출 가능하며, 만약 다른 형태로 되어 있다면 pd.to_datetime(df['pickup_hour])로 변환해야 한다.

df['month'] = df['pickup_hour'].dt.month
df['day'] = df['pickup_hour'].dt.day
df['weekday'] = df['pickup_hour'].dt.weekday
df['hour'] = df['pickup_hour'].dt.hour
df['is_weekend'] = (df['pickup_hour'].dt.weekday // 5 == 1).astype(int)
df.tail()

3. Reverse Geocoding via BigQuery GIS

  • 좌표를 zip_code로 변환
  • BigQuery의 bigquery-public-data.geo_us_boundaries.zip_codes 참고
  • ST_CONTAINS 함수로 추출 가능
%%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')

지난번과 마찬가지로 BigQuery를 통해 데이터를 불러오고 zip_code 별로 feature를 뽑았다.

 

base_df.tail()

4. Categorical Data Pre-processing

sklearn의 머신러닝 알고리즘은 문자열 값을 입력값으로 허용하지 않기때문에 문자열 값들을 숫자 형으로 변환하는 전처리 작업이 필요하며, 인코딩 방식에는 One Hot EncodingLabel Encoding이 있다.

본 예측 프로젝트에서 Linear Regression에서는 One Hot Encoding, Boosting에서는 Label Encoding을 사용하겠다.

4-1) One Hot Encoding

enc = OneHotEncoder(handle_unknown='ignore')
enc.fit(base_df[['zip_code']])

객체 생성 후 fitting을 하고 transform 한 후 원본 데이터에서 join을 하면 된다.

ohe_output = enc.transform(base_df[['zip_code']]).toarray()
oh_feature_df = pd.concat([base_df, pd.DataFrame(ohe_output, columns='zip_code_'+enc.categories_[0])], axis=1)
oh_feature_df.head(3)

4-2) Label Encoding

le = LabelEncoder()
le.fit(base_df['zip_code'])
base_df['zip_code_le'] = le.transform(base_df['zip_code'])
base_df.head(3)

5. Train and Test Data Split

  • 시계열 데이터는 Random Sampling을 하면 안됨
  • Train엔 과거 데이터, Test엔 Train 대비 미래 데이터가 있어야 함
  • Random Sampling을 사용하여 Train Data에 미래 데이터가 들어간다면 미래데이터를 기반으로 과거를 예측한 무의미한 예측이 되며, 실제로 사용할 땐 과거 데이터를 사용하여 미래 데이터를 예측
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

또는

def split_train_and_test_period(df, period):
    """
    Dataframe에서 train_df, test_df로 나눠주는 함수
    
    df : 시계열 데이터 프레임
    period : train/test 기준 일
    """
    criteria = (max(df['pickup_hour']) - pd.Timedelta(days=period)).date()
    train_df = df[df['pickup_hour'] < criteria]
    test_df = df[df['pickup_hour'] >= criteria]
    return train_df, test_df

1번 방법이 명시적으로 date를 지정하는 방법이라, Dateframe의 시간 데이터를 마지막 n주치만 test로 지정할 경우 아래 방법을 쓰면 된다. 본 프로젝트에서는 먼저 작성한 코드를 사용할 것이다.

 

train_df, test_df = split_train_and_test(oh_feature_df, '2015-01-24')
train_df.head()

test_df.head()

del train_df['zip_code']
del train_df['pickup_hour']
del test_df['zip_code']
del test_df['pickup_hour']
# del train_df['zip_code_le']
# del test_df['zip_code_le']

중복되는 Feature 삭제

train_df.head(2)

y_train = train_df.pop('cnt')
x_train = train_df.copy()
y_test = test_df.pop('cnt')
x_test = test_df.copy()

pop이란 데이터프레임에서 특정 열을 불러오는 명령어이다. train_df의 cnt를 y_train, test_df의 cnt를 y_test로 지정하였다.

 

다음번에는 전처리한 데이터를 통해 예측을 진행해보겠다.

 

본인은 태양광 발전 출력에 관해 3건의 논문을 썼는데 이때 예측은 모두 파이썬 머신러닝을 활용해서 하였다. 파이썬이 익숙치 않아 데이터 전처리를 csv 파일에서 manually 진행하였는데 이번 강의를 먼저 들었으면 더 효율적으로 진행할 수 있었을텐데 아쉽다..

 

아래는 내가 작성한 논문이다. 

 

1. An Ensemble Learner-Based Bagging Model Using Past Output Data for Photovoltaic Forecasting

https://www.mdpi.com/1996-1073/13/6/1438

 

An Ensemble Learner-Based Bagging Model Using Past Output Data for Photovoltaic Forecasting

As the world is aware, the trend of generating energy sources has been changing from conventional fossil fuels to sustainable energy. In order to reduce greenhouse gas emissions, the ratio of renewable energy sources should be increased, and solar and wind

www.mdpi.com

2. Optimized-XGBoost Learner Based Bagging Model for Photovoltaic Power Forecasting

https://www.dbpia.co.kr/journal/articleDetail?nodeId=NODE09364782&language=ko_KR

 

최적화 하이퍼 파라미터의 XGBoost 학습자 기반 배깅 모델을 활용한 태양광 출력 예측

논문, 학술저널 검색 플랫폼 서비스

www.dbpia.co.kr

3. A Study on Solar Power Forecasting Ensemble Model for Increasing Flexibility in Power System

http://www.riss.kr/search/detail/DetailView.do?p_mat_type=be54d9b8bc7cdb09&control_no=3907b06c4fca9b4fffe0bdc3ef48d419&outLink=K

300x250