准备
数据预处理和工程技术(engineering techniques)通常是指数据的添加、删除或转换。
# 加载包
library(dplyr) # 用于数据操作
library(ggplot2) # 绘图
#install.packages("visdat")
library(visdat)
# 功能工程包
library(caret) # 用于各种 ML 任务
library(recipes) # 用于特征工程任务
#加载数据##############
ames <- AmesHousing::make_ames()
# 使用分层抽样加载和分割Ames住房数据
set.seed(123)
split <- rsample::initial_split(ames, prop = 0.7, strata = "Sale_Price")
ames_train <- rsample::training(split)
ames_test <- rsample::testing(split)
从图中可以看出Ames 住房数据( Sale_Price) 的响应变量是右偏,一个简单的线性模型,比如说Sale_Price = 𝛃0+𝛃1Year_Built+𝛜,其中误差项𝛜是正态分布的响应的log转换通常可以帮助解决这种问题。

log转换后基本符合正态分布
目标工程
使用对数转换进行规范化
使用对数转换进行规范化。这会将大多数右偏分布转换为近似正态分布。一种方法是以手动、单步方式简单地对训练和测试集进行日志转。
transformed_response <- log(ames_train$Sale_Price)
# log transformation
library("tidymodels")
ames_recipe <- recipe(Sale_Price ~ ., data = ames_train) %>%
step_log(all_outcomes())
ames_recipe
使用Box Cox 变换
Box Cox 变换比(但也作为一种特殊情况)对数变换更灵活,并且会从幂变换族中找到合适的变换,将变量变换为尽可能接近正态分布。Box Cox 变换的核心是指数-lambda。所有值λ被考虑并从训练数据中估计给定数据的最优值;“最佳值”是导致最佳转换为近似正态分布的值。
# Log transformation
train_log_y <- log(ames_train$Sale_Price)
test_log_y <- log(ames_train$Sale_Price)
# Box Cox transformation
lambda <- forecast::BoxCox.lambda(ames_train$Sale_Price)
train_bc_y <- forecast::BoxCox(ames_train$Sale_Price, lambda)
test_bc_y <- forecast::BoxCox(ames_test$Sale_Price, lambda)
# Plot differences
levs <- c("Normal", "Log_Transform", "BoxCox_Transform")
data.frame(
Normal = ames_train$Sale_Price,
Log_Transform = train_log_y,
BoxCox_Transform = train_bc_y
) %>%
gather(Transform, Value) %>%
mutate(Transform = factor(Transform, levels = levs)) %>%
ggplot(aes(Value, fill = Transform)) +
geom_histogram(show.legend = FALSE, bins = 40) +
facet_wrap(~ Transform, scales = "free_x")

处理缺失值
可视化缺失值
sum(is.na(AmesHousing::ames_raw))
AmesHousing::ames_raw %>%
is.na() %>%
reshape2::melt() %>%
ggplot(aes(Var2, Var1, fill=value)) +
geom_raster() +
coord_flip() +
scale_y_continuous(NULL, expand = c(0, 0)) +
scale_fill_grey(name = "",
labels = c("Present",
"Missing")) +
xlab("Observation") +
theme(axis.text.y = element_text(size = 4))

插补
插补是用替代的“最佳猜测”值替换缺失值的过程。插补应该是首先采取的特征工程步骤之一,因为它会影响任何下游预处理。
为特征估算缺失值的一种基本方法是计算描述性统计数据,例如均值、中位数或众数(对于分类),并使用该值替换NAs。
另一种方法是建模插补,它可以为您自动执行此过程,两种最常见的方法包括K 最近邻和基于树的插补。
K最近邻(KNN) 通过识别具有缺失值的观察值来估算值,然后根据其他可用特征识别最相似的其他观察值,并使用来自这些最近邻观察值的值来估算缺失值。
用KNN对ames_recipe估算变量的所有缺失值进行填
用基于树的方法对ames_recipe估算变量的所有缺失值进行填补。与KNN 插补类似,识别具有缺失值的观测值,并将包含缺失值的特征视为目标并使用装袋决策树进行预测。
ames_recipe %>% step_impute_median(Gr_Liv_Area) #中位数填补
ames_recipe %>% step_impute_knn(all_predictors(), neighbors = 6) #knn填补
ames_recipe %>% step_bagimpute(all_predictors()) #基于树填补
三种不同插补方法的比较。红点代表被删除和缺失的实际值,蓝点代表估算值。估计统计插补方法(即平均数、中位数)仅预测每次观察的相同值,并且可以减少特征和响应之间的信号;而KNN 和基于树的程序倾向于保持特征分布和关系。

特征过滤
在许多数据分析和建模项目中,我们最终会收集到数百甚至数千个特征。从实际的角度来看,具有更多特征的模型通常变得更难解释并且计算成本更高。如图所示,一些模型比其他模型更能抵抗非信息预测器(例如,套索和基于树的方法)。

去除零或者接近零的变量
caret::nearZeroVar(ames_train, saveMetrics = TRUE) %>%
tibble::rownames_to_column() %>%
filter(nzv)
目前认为如果特征列内容符合以下两个条件,就可以认为是一个low variance feature
(1)独特值占样本量的比例较低 (例如≤10%)
(2)最普遍值的频率与次普遍值的频率之比很大(例如≥ 20%)
如果这两个条件都成立,那么从模型中删除变量通常是有利的。
数值特征工程
当某些模型的分布偏斜、包含异常值或幅度范围很大时,数字特征可能会给某些模型带来许多问题。基于树的模型完全不受特征空间中这些类型问题的影响,但许多其他模型(例如,GLM、正则化回归、KNN、支持向量机、神经网络)可能会受到这些问题的极大阻碍。对严重偏斜的特征进行规范化和标准化有助于最大限度地减少这些担忧。
偏度
偏度是一个重要的统计概念,我们至少可以从三个方面来衡量:
- 实际数据很少遵循正态分布。而偏度衡量了数据分布的不对称性,对于了解数据分布的形状来说至关重要。
- 偏度告诉我们离群值的方向。正偏度表示存在较大的极值,负偏度表示存在较小的极值。
-
偏度可以揭示大多数值集中在哪里,以及反应了均值、中位数以及众数间的大小关系。
这里所谓的左偏和右偏的叫法,是根据尾部的方向来说的对于左图,因为尾部在左侧,所以它是左偏(负偏);而右图的尾部是在右侧,所以它是右偏(正偏)。
标准化
对原始数据按照一定的比例缩放
set.seed(123)
x1 <- tibble(
variable = "x1",
`Real value` = runif(25, min = -30, max = 5),
`Standardized value` = scale(`Real value`) %>% as.numeric()
)
set.seed(456)
x2 <- tibble(
variable = "x2",
`Real value` = rlnorm(25, log(25)),
`Standardized value` = scale(`Real value`) %>% as.numeric()
)
set.seed(789)
x3 <- tibble(
variable = "x3",
`Real value` = rnorm(25, 150, 15),
`Standardized value` = scale(`Real value`) %>% as.numeric()
)
x1 %>%
bind_rows(x2) %>%
bind_rows(x3) %>%
gather(key, value, -variable) %>%
mutate(variable = factor(variable, levels = c("x3", "x2", "x1"))) %>%
ggplot(aes(value, variable)) +
geom_point(alpha = .6) +
facet_wrap(~ key, scales = "free_x") +
ylab("Feature") +
xlab("Value")

标准化特征允许在一个共同的价值尺度上比较所有特征,而不管它们的实际价值差异如何
应用
是适用于大多数问题的潜在步骤的建议顺序:
-
过滤掉零或接近零的方差特征;
-
如果需要,执行插补;
-
归一化以解决数字特征偏斜;
-
标准化(居中和缩放)数字特征;
-
对数字特征执行降维(例如,PCA);
-
单热或虚拟编码分类特征。
整合流程
使用recipe()创建和应用特征工程有三个主要步骤:
-
recipe:定义特征工程步骤以创建蓝图的位置;
-
prep是:根据训练数据估计特征工程参数;
-
bake:将蓝图应用于新数据。
#################################
blueprint <- recipe(Sale_Price ~ ., data = ames_train) %>%
step_nzv(all_nominal()) %>%
step_integer(matches("Qual|Cond|QC|Qu")) %>%
step_center(all_numeric(), -all_outcomes()) %>%
step_scale(all_numeric(), -all_outcomes()) %>%
step_pca(all_numeric(), -all_outcomes())
blueprint
prepare <- prep(blueprint, training = ames_train)
prepare
baked_train <- bake(prepare, new_data = ames_train)
baked_test <- bake(prepare, new_data = ames_test)
baked_train
###################
# 指定重采样计划
cv <- trainControl(
method = "repeatedcv",
number = 10,
repeats = 5
)
# 构建超参数值网格
hyper_grid <- expand.grid(k = seq(2, 25, by = 1))
# 利用网格搜索调整 knn 模型
knn_fit2 <- train(
blueprint,
data = ames_train,
method = "knn",
trControl = cv,
tuneGrid = hyper_grid,
metric = "RMSE"
)
knn_fit2
ggplot(knn_fit2)
网友评论