library(tidyverse)16 数据转换与特征工程
17 数据转换与特征工程
数据清洗解决了”数据有没有问题”,特征工程解决的是”数据能不能用好”。本章介绍常见的数据转换方法和特征构造技巧。
17.1 示例数据
延续上一章的土壤调查场景,使用一个已清洗的数据集:
set.seed(2027)
soil <- tibble(
site_id = rep(paste0("S", sprintf("%02d", 1:20)), each = 3),
depth = factor(rep(c("0-10", "10-20", "20-30"), 20),
levels = c("0-10", "10-20", "20-30"), ordered = TRUE),
ph = round(rnorm(60, 6.2, 0.8), 2),
organic_carbon = round(rlnorm(60, log(25), 0.4), 2), # 右偏分布
nitrogen = round(abs(rnorm(60, 2.1, 0.6)), 2), # 确保为正值
clay_pct = round(runif(60, 10, 45), 1),
sand_pct = round(runif(60, 15, 40), 1),
elevation = rep(round(runif(20, 200, 800)), each = 3),
slope = rep(round(runif(20, 5, 35), 1), each = 3),
aspect = rep(sample(c("N", "S", "E", "W", "NE", "NW", "SE", "SW"), 20, replace = TRUE), each = 3),
vegetation = rep(sample(c("针叶林", "阔叶林", "混交林", "灌丛", "草地"), 20, replace = TRUE), each = 3)
)
head(soil)17.2 数据标准化与归一化
不同变量的量纲和范围差异很大(如 pH 范围 4-8,有机碳范围 10-60),在某些分析(如 PCA、聚类)中需要先统一尺度。
17.2.1 Z-score 标准化
将数据转换为均值为 0、标准差为 1 的分布:
\[z = \frac{x - \bar{x}}{s}\]
soil_scaled <- soil |>
mutate(
ph_z = scale(ph)[,1],
oc_z = scale(organic_carbon)[,1],
nitrogen_z = scale(nitrogen)[,1]
)
# 验证
soil_scaled |>
summarise(
ph_z_mean = round(mean(ph_z), 10),
ph_z_sd = round(sd(ph_z), 10)
)17.2.2 Min-Max 归一化
将数据缩放到 [0, 1] 区间:
\[x_{norm} = \frac{x - x_{min}}{x_{max} - x_{min}}\]
min_max <- function(x) (x - min(x, na.rm = TRUE)) / (max(x, na.rm = TRUE) - min(x, na.rm = TRUE))
soil_norm <- soil |>
mutate(
ph_norm = min_max(ph),
oc_norm = min_max(organic_carbon),
nitrogen_norm = min_max(nitrogen)
)
# 验证范围
soil_norm |>
summarise(across(ends_with("_norm"), list(min = min, max = max)))
Tip什么时候用哪种?
- Z-score:适合大多数统计分析(PCA、回归),保留了分布形状
- Min-Max:适合需要固定范围的场景(如神经网络输入)
- 不需要标准化:基于树的模型(随机森林、决策树)对尺度不敏感
17.3 变量变换
17.3.1 对数变换
生态学数据中很多变量呈右偏分布(如生物量、物种丰度、有机碳含量),对数变换可以使其接近正态分布:
# 对比变换前后的分布
p1 <- ggplot(soil, aes(x = organic_carbon)) +
geom_histogram(bins = 15, fill = "steelblue", alpha = 0.7) +
labs(title = "原始分布", x = "有机碳 (g/kg)") +
theme_minimal()
p2 <- ggplot(soil, aes(x = log(organic_carbon))) +
geom_histogram(bins = 15, fill = "coral", alpha = 0.7) +
labs(title = "对数变换后", x = "ln(有机碳)") +
theme_minimal()
library(patchwork)
p1 + p2# 添加对数变换列
soil <- soil |>
mutate(
log_oc = log(organic_carbon),
log_nitrogen = log(nitrogen)
)17.3.2 正态性检验
# Shapiro-Wilk 检验
shapiro.test(soil$organic_carbon)
shapiro.test(soil$log_oc)
Note对数变换的注意事项
- 数据中不能有 0 或负值(可以用
log(x + 1)处理含 0 的数据) - 变换后的结果需要反变换才能解释原始尺度
- 在论文中要说明使用了什么变换
17.4 分类变量编码
17.4.1 因子排序
# 按频次排序
soil |>
mutate(vegetation = fct_infreq(vegetation)) |>
ggplot(aes(x = vegetation)) +
geom_bar(fill = "steelblue") +
labs(title = "植被类型分布", x = "", y = "样本数") +
theme_minimal()17.4.2 虚拟变量(独热编码)
某些统计模型需要将分类变量转换为数值型的虚拟变量:
# 使用 model.matrix 创建虚拟变量
dummy <- model.matrix(~ vegetation - 1, data = soil) |>
as_tibble()
head(dummy)17.4.3 坡向的数值编码
坡向是一个特殊的分类变量——它本质上是角度,可以用三角函数转换为连续变量:
# 坡向转角度
aspect_to_degree <- function(asp) {
mapping <- c(N = 0, NE = 45, E = 90, SE = 135, S = 180, SW = 225, W = 270, NW = 315)
mapping[asp]
}
soil <- soil |>
mutate(
aspect_deg = aspect_to_degree(aspect),
aspect_cos = cos(aspect_deg * pi / 180), # 南北分量
aspect_sin = sin(aspect_deg * pi / 180) # 东西分量
)17.5 数据合并与重塑
17.5.1 合并多个数据源
# 气象数据
climate <- tibble(
site_id = paste0("S", sprintf("%02d", 1:20)),
mean_temp = round(rnorm(20, 18, 3), 1),
annual_precip = round(rnorm(20, 1200, 200))
)
# 合并
soil_full <- soil |>
left_join(climate, by = "site_id")
head(soil_full)17.5.2 长宽格式转换
# 宽格式:每个深度一列
soil_wide <- soil |>
select(site_id, depth, ph) |>
pivot_wider(
names_from = depth,
values_from = ph,
names_prefix = "ph_"
)
head(soil_wide)
# 转回长格式
soil_wide |>
pivot_longer(
cols = starts_with("ph_"),
names_to = "depth",
values_to = "ph",
names_prefix = "ph_"
) |>
head()17.6 构造新特征
根据领域知识,从已有变量中构造有意义的新特征:
soil_features <- soil_full |>
mutate(
# 碳氮比(C/N ratio)——重要的土壤质量指标
cn_ratio = organic_carbon / nitrogen,
# 粉粒含量(假设三者之和为100%)
silt_pct = 100 - clay_pct - sand_pct,
# 海拔分级
elev_class = cut(elevation, breaks = c(0, 300, 500, 800, Inf),
labels = c("低海拔", "中低海拔", "中高海拔", "高海拔")),
# 坡度分级
slope_class = cut(slope, breaks = c(0, 10, 20, 30, Inf),
labels = c("平缓", "中等", "较陡", "陡峭"))
)
head(soil_features |> select(site_id, cn_ratio, silt_pct, elev_class, slope_class))17.7 交互特征与生态学指标
在生态学中,变量之间的交互效应往往比单个变量更有意义。
17.7.1 交互特征
soil_features <- soil_features |>
mutate(
# 温度-降水交互:反映水热条件组合
temp_precip = mean_temp * annual_precip / 1000,
# 坡度-海拔交互:反映地形复杂度
topo_index = slope * log(elevation)
)17.7.2 生态学常用派生指标
soil_features <- soil_features |>
mutate(
# 土壤质地类型(基于粘粒和砂粒含量简化判断)
texture = case_when(
clay_pct > 40 ~ "粘土",
sand_pct > 40 ~ "砂土",
TRUE ~ "壤土"
),
# 有机碳密度(考虑深度层厚度,假设容重 1.3 g/cm³,层厚 10 cm)
oc_density = organic_carbon * 1.3 * 10 / 100 # 单位:kg/m²
)
head(soil_features |> select(site_id, depth, texture, oc_density, temp_precip))
Note特征工程的原则
- 领域知识优先:碳氮比、土壤质地等指标来自土壤学知识,比盲目组合变量更有效
- 避免数据泄露:特征构造只能使用训练集信息,不能使用目标变量
- 检查合理性:新特征的取值范围和分布是否符合物理意义
17.8 课后练习
- 对
iris数据集的 4 个数值变量进行 Z-score 标准化 - 检验
iris$Sepal.Width是否服从正态分布,如果不是,尝试对数变换 - 将
iris$Species转换为虚拟变量 - 构造一个新特征:花瓣面积 = Petal.Length × Petal.Width
- 将 iris 数据从宽格式转为长格式(4 个测量值变成一列)