本日重點:了解特徵工程為何重要、認識特徵工程中常見的方法、如何在 R 中怎麼處理特徵工程
##設定環境
#setwd(dir) #設定working directory的存放位置
# MAC : setwd("/Users/rladiestaipei/R_DragonBall/")
# Windows : setwd("C://Users/rladiestaipei/Desktop/R_DragonBall/")
#安裝套件(僅需執行一次)
#install.packages(c("tidyverse","purrr","tidyr","plyr"), dependencies = TRUE)
#load packages
library(tidyverse)
library(purrr)
library(tidyr)
library(plyr)
options(dplyr.print_max=1e9)
在說明特徵工程前,不知道大家有沒有注意到,本次活動提供的資料集共有三個分別是
1. data_description.txt : 欄位敘述
2. train.csv : 訓練資料(用來做模型構建)
3. test.csv : 測試資料(用來檢測模型的表現)
不曉得大家是否已經了解什麼是訓練資料、測試資料了?
如果你還沒有什麼概念,那可以先從「人類是怎麼學習辨識貓跟狗的? 」這個問題來下手
在你小的時候,你的家人想要教你如何分辨狗跟貓,可能會給你看很多狗跟貓的圖片,並且告訴你哪些是狗哪些是貓,讓你慢慢去認識狗跟貓長什麼樣子,而在你學會辨認狗貓之前所看的這些圖片,就是訓練資料。而測試資料就像是你的家人為了測試你瞭不瞭解,通常會給你一些你沒看過的狗貓圖片,考考你是否學會辨識了。
講這麼多那到底訓練資料、測試資料跟特徵工程有什麼關係呢?那是因為要做出正確的辨識,我們需要要收集一些有用的特徵來輔組我們訓練,以剛剛辨認狗貓的例子,我們可能會從「是不是尖耳朵、嘴巴長短、有沒有鬍子、體型大小、毛色」等等來做辨識,這些就稱為特徵。如果沒有好的特徵,我們就沒辦法有好的訓練資料,可能就無法做出正確的預測。
特徵工程就是我們在收集到很多特徵之後,想辦法再從中找到更多更好的方式,來描述我們想要預測的標的。當我們有更好的方式去描述資料時,通常也能提升模型的表現。從剛剛辨認狗貓的例子,我們可能可以從體型、毛色、嘴巴長短等綜合出一些新的特徵,如體型小、卷毛、嘴巴長、咖啡色 => 貴賓狗。
再正式進入特徵工程之前,我們需要將資料讀進來,順便複習一下前兩天學到的東西
複習一下 Day 2 的 Code
訓練資料集中共有 81 個欄位,依照資料特性分割
train0 <- read.csv("train.csv", stringsAsFactors = FALSE)
# 分割 Numeric and Character 欄位
num_features <- names(which(sapply(train0, is.numeric)))
cat_features <- names(which(sapply(train0, is.character)))
train_numeric <- train0[, names(train0) %in% num_features]
train_categoric <- train0[, names(train0) %in% cat_features]
# Numerical 欄位
print(num_features)
[1] "Id" "MSSubClass" "LotFrontage" "LotArea" "OverallQual" "OverallCond"
[7] "YearBuilt" "YearRemodAdd" "MasVnrArea" "BsmtFinSF1" "BsmtFinSF2" "BsmtUnfSF"
[13] "TotalBsmtSF" "X1stFlrSF" "X2ndFlrSF" "LowQualFinSF" "GrLivArea" "BsmtFullBath"
[19] "BsmtHalfBath" "FullBath" "HalfBath" "BedroomAbvGr" "KitchenAbvGr" "TotRmsAbvGrd"
[25] "Fireplaces" "GarageYrBlt" "GarageCars" "GarageArea" "WoodDeckSF" "OpenPorchSF"
[31] "EnclosedPorch" "X3SsnPorch" "ScreenPorch" "PoolArea" "MiscVal" "MoSold"
[37] "YrSold" "SalePrice"
# Categorical 欄位
print(cat_features)
[1] "MSZoning" "Street" "Alley" "LotShape" "LandContour" "Utilities"
[7] "LotConfig" "LandSlope" "Neighborhood" "Condition1" "Condition2" "BldgType"
[13] "HouseStyle" "RoofStyle" "RoofMatl" "Exterior1st" "Exterior2nd" "MasVnrType"
[19] "ExterQual" "ExterCond" "Foundation" "BsmtQual" "BsmtCond" "BsmtExposure"
[25] "BsmtFinType1" "BsmtFinType2" "Heating" "HeatingQC" "CentralAir" "Electrical"
[31] "KitchenQual" "Functional" "FireplaceQu" "GarageType" "GarageFinish" "GarageQual"
[37] "GarageCond" "PavedDrive" "PoolQC" "Fence" "MiscFeature" "SaleType"
[43] "SaleCondition"
複習一下 Day 2 的 Code
有些欄位雖然是使用數字 1, 2, 3,...
做資料呈現,但從欄位定義及資料內容可以很明顯的看出,這些欄位是屬於類別型資料,因此將這部分資料作轉換
train0$OverallCond <- as.factor(train0$OverallCond)
train0$OverallQual <- as.factor(train0$OverallQual)
train0$MSSubClass <- as.factor(train0$MSSubClass)
處理缺失值最簡單的做法就是刪掉有缺失資料,但這麼做可能會誤刪掉一些有用的資訊,所以也可以使用一些進階的方法來填補,通常會依照資料特性不同有不同的處理方法。
複習一下 Day 2 的 Code
查看每個欄位的缺失值和比例missing_values <- sapply(train0, function(x) sum(is.na(x)))
null_count <- data.frame(Column =names(missing_values),
Count = missing_values,
Proportion = missing_values/nrow(train0)) %>%
filter(Count > 0) %>%
arrange(-Count)
Column | Count | Proportion |
---|---|---|
PoolQC | 1453 | 0.9952055 |
MiscFeature | 1406 | 0.9630137 |
Alley | 1369 | 0.9376712 |
Fence | 1179 | 0.8075342 |
FireplaceQu | 690 | 0.4726027 |
LotFrontage | 259 | 0.1773973 |
GarageType | 81 | 0.0554795 |
GarageYrBlt | 81 | 0.0554795 |
GarageFinish | 81 | 0.0554795 |
GarageQual | 81 | 0.0554795 |
GarageCond | 81 | 0.0554795 |
BsmtExposure | 38 | 0.0260274 |
BsmtFinType2 | 38 | 0.0260274 |
BsmtQual | 37 | 0.0253425 |
BsmtCond | 37 | 0.0253425 |
BsmtFinType1 | 37 | 0.0253425 |
MasVnrType | 8 | 0.0054795 |
MasVnrArea | 8 | 0.0054795 |
Electrical | 1 | 0.0006849 |
直接刪掉那些含有缺失值的 rows 或是不使用含有缺失值的 columns
#刪掉缺失超過 80% 的欄位
train0 <- train0 %>%
select(-c(as.vector(null_count$Column[null_count$Proportion>0.8])))
發現當車庫、地下室和外觀相關欄位缺失時,可以表示為房子本身就沒有這些設備,所以可以用 0 字串取代
#車庫相關欄位 (GarageCars, GarageArea)
#地下室相關欄位 (BsmtFullBath, BsmtHalfBath, BsmtFinSF1, BsmtFinSF2, BsmtUnfSF, TotalBsmtSF)
#外觀相關欄位 (MasVnrArea)
train0 <- train0 %>%
replace_na(list(GarageCars = 0, GarageArea = 0, BsmtFullBath = 0,
BsmtHalfBath = 0, BsmtFinSF1 = 0, BsmtFinSF2 = 0,
BsmtUnfSF = 0, TotalBsmtSF = 0, MasVnrArea = 0))
# LotFrontage(前面街道長度) 使用中位數取代
train0$LotFrontage[is.na(train0$LotFrontage)] <- median(train0$LotFrontage, na.rm = T)
# GarageYrBlt(車庫年份) 使用 YearBuilt(房子建造年份) 取代
train0$GarageYrBlt[is.na(train0$GarageYrBlt)] <- train0$YearBuilt[is.na(train0$GarageYrBlt)]
發現當車庫、地下室和外觀相關欄位缺失時,可以表示為房子本身就沒有這些設備且這幾個欄位的缺失值比較少,所以可以用
None
字串取代
#車庫相關欄位 (GarageFinish , GarageQual, GarageCond, GarageType)
#地下室相關欄位 (BsmtCond, BsmtExposure, BsmtQual, BsmtFinType2, BsmtFinType1)
#其他欄位 (MasVnrType, FireplaceQu)
train0 <- train0 %>%
replace_na(list(GarageFinish = "None", GarageQual = "None",
GarageCond = "None", GarageType = "None",
BsmtExposure = "None", BsmtQual = "None",
BsmtCond = "None", BsmtFinType1 = "None",
BsmtFinType2 = "None", MasVnrType = "None", FireplaceQu="None"))
# Electrical(電力系統) 使用眾數取代
#建立 Mode Function
Mode <- function(x) {
u_x <- unique(x)
u_x[which.max(tabulate(match(x, u_x)))]
}
train0$Electrical[is.na(train0$Electrical)] <- Mode(train0$Electrical)
為什麼要做特徵縮放?
從我們使用的例子來說,目前有各式各樣的房屋特徵,如房屋面積、前方街道距離、壁爐數量等等,這些資料因為收集的方式不同、單位不同,可能造成資料間範圍有很大的差異。舉例來說壁爐數量可能 1 - 10 以內,而房屋面積可能超過 100 以上。 資料間範圍相差過大,可能會導致模型更偏向於資料範圍較大的那個特徵。解決的辦法就是將各種不同 Scale 的特徵轉換成同樣的 Scale。
有些模型若放進去的資料沒有做 Scale 調整,模型的預測力會很差,如 SVM、神經網路、K-means 等
#將面積相關的欄位 (GrLivArea, TotalSF, MasVnrArea) 做標準化
train0$GrLivArea_stand <- scale(train0$GrLivArea)
train0$MasVnrArea_stand <- scale(train0$MasVnrArea)
為什麼要做離散化?
- 離散後的資料,可以克服資料中的缺陷,讓模型更穩定且可以降低 overfitting 的問題
- 特徵與目標變數間的關係變得更清楚,有利於對非線性關係的診斷與描述
#將 LotFrontage, SalePrice 做 log transformation
train0$LotFrontage_log <- log(train0$LotFrontage)
train0$SalePrice_log <- log(train0$SalePrice)
#新增一個欄位,判斷房子是否有壁爐
train0$is_Fireplace <- ifelse(train0$Fireplaces>0, "1", "0")
#檢查一下 LotFrontage, SalePrice 做 log transformation 前後比較
train0 %>%
select(LotFrontage, SalePrice, LotFrontage_log, SalePrice_log) %>%
gather() %>%
ggplot(aes(value)) +
facet_wrap(~ key, scales = "free") +
geom_histogram()
#將地下室等級(BsmtCond)轉換成數值
#Ex=Excellent,Gd=Good,TA=Typical,Fa=Fair,Po=Poor,None=No Basement
Qualities <- c('None' = 0, 'Po' = 1, 'Fa' = 2, 'TA' = 3, 'Gd' = 4, 'Ex' = 5)
train0$BsmtCond <- as.integer( revalue(train0$BsmtCond, Qualities) )
#建立一個總共 bathroom 數量
train0$TotalBathrooms <- train0$FullBath+(train0$HalfBath*0.5)+train0$BsmtFullBath+(train0$BsmtHalfBath*0.5)
請利用
TotalBsmtSF
、X1stFlrSF
、X2ndFlrSF
1. 建立出一個總房屋面積TotalSF
2. 將TotalSF
做標準化轉換
[Note] R 中變數的第一個字不能是數字,所以 1stFlrSF -> X1stFlrSF, 2ndFlrSF -> X2ndFlrSF