專欄 | 基於 Jupyter 的特徵工程手冊:資料預處理(四)

紅色石頭發表於2020-04-14

基於 Jupyter 的特徵工程手冊:資料預處理的上一篇:

專欄 | 基於 Jupyter 的特徵工程手冊:資料預處理(一)

專欄 | 基於 Jupyter 的特徵工程手冊:資料預處理(二)

專欄 | 基於 Jupyter 的特徵工程手冊:資料預處理(三)

專案地址:

https://github.com/YC-Coder-Chen/feature-engineering-handbook

本專案將探討資料預處理部分:介紹瞭如何利用 scikit-learn 處理靜態的連續變數,利用 Category Encoders 處理靜態的類別變數以及利用 Featuretools 處理常見的時間序列變數。

目錄

特徵工程的資料預處理我們將分為三大部分來介紹:

  • 靜態連續變數
  • 靜態類別變數
  • 時間序列變數

本文將介紹 1.3 時間序列變數的資料預處理。下面將結合 Jupyter,使用 sklearn,進行詳解。

1.3 Time Series Variables 時間序列變數

有時我們的資料集或者所研究的問題可能與時間有關。在這種情況下,我們可以利用python中的Featuretools包來實現自動化特徵工程。具體來說,我們可以跨時間“積累”資訊。

# 合成一些樣本資料,記錄使用者在每一次登陸網站後的行為,比如使用者的停留時間,購買的產品等
import numpy as np
import pandas as pd
import featuretools as ft
import datetime

raw_dataset = np.array([['001',100, 'Apple', '2020/01/01', 'male', 35, 1],
                        ['002',20, np.nan, '2020/01/01', 'female', 30, 0],
                        ['003',50, 'Orange','2020/01/01', 'male', 20, 1],
                        ['001', 13, np.nan, '2020/01/03', 'male', 35, 0],
                        ['002', 30, 'Apple', '2020/01/03','female', 30, 1], 
                        ['001', 90, 'Orange', '2020/01/06', 'male', 35, 1], 
                        ['003', 5, 'Orange', '2020/01/07', 'male', 20, 1]])

dataset = pd.DataFrame(raw_dataset, columns = ['Customer ID', 'Seconds Stay', 'Item Purchase',
                                               'Time', 'Sex', 'Age', 'Target'])
dataset['Seconds Stay'] = dataset['Seconds Stay'].astype(int)
dataset['Target'] = dataset['Target'].astype(int)
dataset['Age'] = dataset['Age'].astype(int)
dataset['Time'] = pd.to_datetime(dataset['Time'])
dataset['Item Purchase'] = dataset['Item Purchase'].replace("nan", np.nan)

dataset # 在Item Purchase中,NaN即該客戶沒有購買

1.3.1 Time Series Categorical Features 時間序列類別變數

一個可能的資料科學問題是:在上述的合成資料中,我們如何預測客戶的購買行為。具體而言,我們可能想要預測客戶002在2020-01-08登陸的時候是否會購買。

上述資料中的類別變數有Item Purchase。基於這個類別變數我們可以進行特徵工程併合成以下新變數:每一次登陸前他有過幾次購買行為,每一次登陸前他最喜歡的產品是什麼,每一次登陸前他購買過多少獨特的商品等等。

而這一些變數,都可以透過Featuretools包簡單實現。

Featuretools包提供了以下一些有用的特徵變換:

  • COUNT:在給定時間之前的變數計數值,不包括缺失值
  • Mode: 在給定時間之前的變數眾數
  • NumUnique:在給定時間之前的唯一值計數,不包括缺失值
  • Entropy: 在給定時間之前類別變數的熵
  • First: 在給定時間之前變數出現的第一個值
  • Last: 在給定時間之前出現的變數最後一個值
  • Featuretools包還提供了很多其他的變換,具體可見官方網站:

https://docs.featuretools.com/en/stable/api_reference.html#aggregation-primitives

1.3.2 Time Series Continuous Features 時間序列連續變數

與前述問題一致,我們可能想要預測客戶002在2020-01-08登陸的時候是否會購買。

上述資料中的連續變數有Seconds Stay,即客戶每次登陸後停留了多久。基於這個連續變數我們可以進行特徵工程併合成以下新變數:每一次登陸前他的平均停留時間,每一次登陸前他的停留時間的標準差,每一次登陸前他的停留時間的滑動平均等。

而這一些變數,同樣可以透過Featuretools包簡單實現。

Featuretools包提供了以下一些有用的特徵變換:

  • COUNT: 在給定時間之前的變數計數值,不包括缺失值
  • First: 在給定時間之前變數出現的第一個值
  • Last: 在給定時間之前變數出現的最後一個值
  • Mean: 在給定時間之前該變數的平均值,不包括缺失值
  • Sum: 在給定時間之前該變數的求和,不包括缺失值
  • Min: 在給定時間之前該變數的最小值,不包括缺失值
  • Max: 在給定時間之前該變數的最大值,不包括缺失值
  • Std: 在給定時間之前該變數的標準差,不包括缺失值
  • Median: 在給定時間之前該變數的中位數,不包括缺失值
  • Trend: 在給定時間之前該變數的趨勢,即線性斜率
  • Featuretools包還提供了很多其他的變換,具體可見官方網站:

https://docs.featuretools.com/en/stable/api_reference.html#aggregation-primitives

1.3.3 Implementation 程式碼實現

# 原始資料集
dataset

1.3.3.1 Create EntitySet 生成實體集

# 首先,我們需要建立EntitySet即實體集
# 它是資料集中實體的集合,包含實體之間的關係
# 它們的建立能幫助Featuretool瞭解資料的結構,從而實現自動時間序列特徵工程

es = ft.EntitySet(id="customer_data") # 首先生成一個空白的實體集

# 資料集中一個實體即每一個客戶,我們有客戶層面的資料,例如客戶的性別,客戶的年齡
df_customer = dataset[['Customer ID', 'Sex', 'Age']].drop_duplicates()
df_customer

# 現在我們將這個實體加入到實體集中
es = es.entity_from_dataframe(entity_id="Customer",
                              dataframe=df_customer,
                              index= 'Customer ID') 
# 在這個實體中,Customer ID是將每一個顧客區別開的索引

es['Customer']

Entity: Customer
Variables:
Customer ID (dtype: index)
Sex (dtype: categorical)
Age (dtype: numeric)
Shape:
(Rows: 3, Columns: 3)

# 第二個實體即每一次發生的交易,我們也有交易層面的資料,即每一次的停留時間,購買的產品名稱
es = es.entity_from_dataframe(entity_id="Transaction",
                              dataframe=dataset[["Customer ID","Seconds Stay", 
                                                 "Item Purchase", 
                                                 "Time","Target"]].reset_index(),
                              index="index", 
                              # 在這個實體中,索引為‘index’
                              time_index="Time", # 時間索引為‘Time’
                              variable_types={"Item Purchase": ft.variable_types.Categorical, 
                                              "Seconds Stay": ft.variable_types.Numeric,
                                              "Target": ft.variable_types.Numeric})
es['Transaction']

Entity: Transaction
Variables:
index (dtype: index)
Customer ID (dtype: categorical)
Time (dtype: datetime_time_index)
Item Purchase (dtype: categorical)
Seconds Stay (dtype: numeric)
Target (dtype: numeric)
Shape:
(Rows: 7, Columns: 6)

# 現在,我們新增實體之間的關係
relationship = ft.Relationship(es["Customer"]["Customer ID"],
                               es["Transaction"]["Customer ID"])
# 每一個使用者都有一些交易與之關聯
# 故我們稱使用者為母實體
# 每一次交易為子實體

es = es.add_relationship(relationship)
es

Entityset: customer_data
Entities:
Customer [Rows: 3, Columns: 3]
Transaction [Rows: 7, Columns: 6]
Relationships:
Transaction.Customer ID -> Customer.Customer ID

1.3.3.2 Set up cut-time 設定時間截斷

關於時間截斷的更多資訊可以在Featuretools的官網網站中獲得:

https://docs.featuretools.com/en/stable/automated_feature_engineering/handling_time.html

# 建立時間切割
# 通常在時間序列的資料集中
# 其可能包含很多時間節點
# 如果處理不慎就可能導致未來的資訊洩漏

# 故我們需要設定Cut-time 時間切割
# 即讓Featuretool明白其需要考慮可能的資訊洩漏問題
# 我們可以在Featuretools中加入一個用於指示對應切割時間的資料集
# 其指定了每一行可使用資訊的最後時間點
# 即對每一行資料,在進行對應的特徵工程時,我們僅僅會考慮這一行時間切割點之前的資訊

ct = dataset[['Customer ID','Time']].copy() # the cut-off dataframe
ct

# 但是在我們的這一問題中
# 每一行的截止時間應在交易發生時間之前
# 此次交易的資訊是不應該被涵蓋在特徵工程中
# 因為我們沒有當前行資訊來預測當前行的購買
# 例如,直到顧客完成購買後,我們才知道他購買了哪種產品

ct['Time'] = ct['Time'] + datetime.timedelta(seconds = -1) 
# 將時間截斷設定為每一次登陸時間的前1秒

ct

1.3.3.3 Auto Feature Engineering 自動特徵工程

一種使用Featuretools的策略是讓其自動生成所有可能的特徵。這種策略中我們無需指定我們要為每個原始特徵進行的轉換。隨後我們可以從這些自動生成的特徵中篩選我們想要的特徵。但這種策略往往會比較佔用記憶體和執行時間。

# 開始自動特徵工程,我們無需指定我們要為每個原始特徵進行的轉換
fm, features = ft.dfs(entityset=es,
                      target_entity='Customer', 
                      # 我們想要在每一個客戶層面累計資訊
                      # 因為我們想要預測的是未來每個客戶的可能購買行為
                      max_depth=2, # 設定深度為 2, 即交叉項最後包含兩種原始變數
                      cutoff_time=ct,
                      cutoff_time_in_index=True)

# 我們甚至可以指定訓練視窗來實現滑動平均的效果,此處不進行展示
fm; # 特徵工程後的結果
fm.columns

Index([‘Sex’, ‘Age’, ‘SUM(Transaction.Target)’,
‘SUM(Transaction.Seconds Stay)’, ‘STD(Transaction.Target)’,
‘STD(Transaction.Seconds Stay)’, ‘MAX(Transaction.Target)’,
‘MAX(Transaction.Seconds Stay)’, ‘SKEW(Transaction.Target)’,
‘SKEW(Transaction.Seconds Stay)’, ‘MIN(Transaction.Target)’,
‘MIN(Transaction.Seconds Stay)’, ‘MEAN(Transaction.Target)’,
‘MEAN(Transaction.Seconds Stay)’, ‘COUNT(Transaction)’,
‘NUM_UNIQUE(Transaction.Item Purchase)’,
‘MODE(Transaction.Item Purchase)’, ‘NUM_UNIQUE(Transaction.YEAR(Time))’,
‘NUM_UNIQUE(Transaction.WEEKDAY(Time))’,
‘NUM_UNIQUE(Transaction.MONTH(Time))’,
‘NUM_UNIQUE(Transaction.DAY(Time))’, ‘MODE(Transaction.YEAR(Time))’,
‘MODE(Transaction.WEEKDAY(Time))’, ‘MODE(Transaction.MONTH(Time))’,
‘MODE(Transaction.DAY(Time))’],
dtype=’object’)

features
# 特徵工程生成的新變數名稱與對應的合成方式
# 自動功能工程可能會生成一些毫無意義的變數
# 這需要我們的人工篩選

[,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
]

另一種策略即指定每一個原始變數我們需要什麼對應的變換。在這種策略下,我們可以更好地控制特徵工程的結果並節省時間與記憶體。

from featuretools.primitives import TimeSinceLast
# import TimeSinceLast
# 這樣我們可以控制時間維度的單位

operation_dict = {("count", TimeSinceLast(unit = "hours")): {"include_variables": {"Transaction": ["index"]}},
                  ("entropy", 
                   "num_unique",
                   "mode"): {"include_variables": {"Transaction": ["Item Purchase"]}},
                  ("mean", 
                   "max",
                   "median",
                   "skew",
                   "std"): {"include_variables": {"Transaction": ["Seconds Stay","Target"]}},
                   "last": {"include_variables": {"Transaction": ["Item Purchase","Seconds Stay",
                                                                  "Target"]}}
                 }

# 新增一個計數變換來顯示每次交易之前共有多少次登陸次數
# 新增一個TimeSinceLast變換,以顯示此次登陸距離上一次登陸的時間間隔
# 新增對類別變數Item Purchase的熵,唯一值計數,眾數及最近一次登陸購買的產品的統計
# 新增數值變數Seconds Stay 及目標變數(滯後項)的均值,最大值,中位數,偏度,標準差和最近值
fm, features = ft.dfs(entityset=es,
                      target_entity='Customer',
                      max_depth=2,
                      cutoff_time=ct,
                      cutoff_time_in_index=True, 
                      agg_primitives = ['count','entropy', 'num_unique','mode','last',
                                        'mean','max','median','skew','std',
                                        TimeSinceLast(unit = "hours")],
                      # 為簡單起見,我們不包含transform primative
                      # 即將實體中的一個或多個變數作為輸入
                      # 併為該實體輸出一個新變數的變換

                      # aggregation primitive & transform primitive的區別可見:
                      # https://docs.featuretools.com/en/latest/automated_feature_engineering/primitives.html

                      primitive_options= operation_dict # 指明對每一個原始變數我們想要的變換
                      )
features # 新變數名稱及對應的變換

[,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
]

fm # 特徵工程的結果

好了,以上就是關於時間序列變數的資料預處理介紹。建議讀者結合程式碼,在 Jupyter 中實操一遍。

特徵工程:資料預處理完成!

目前該專案完整中文版已製作完成!

中文版 Jupyter 地址:

https://github.com/YC-Coder-Chen/feature-engineering-handbook/tree/master/%E4%B8%AD%E6%96%87%E7%89%88


本文首發於公眾號:AI有道(ID: redstonewill),歡迎關注!

相關文章