R语言中apply结合scale函数丢失属性问题的排查与解决
为啥用
apply()调用scale()会丢属性?原因+解决办法 嘿,这个问题我之前也碰到过!其实本质是apply()的工作机制和scale()的属性存储逻辑不匹配导致的,咱们来捋清楚:
一、问题根源
- 直接调用
scale(trees)时,scale()是专门为矩阵/数据框设计的,处理完后会返回一个带全局属性的矩阵——把所有列的均值存在"scaled:center",标准差存在"scaled:scale",这些属性是绑定在整个矩阵上的。 - 但用
apply(trees, 2, scale)的时候,事情就不一样了:apply()会把数据框拆成一列列的向量,对每个向量单独跑scale(),这时候每个scale()返回的是带自身属性的向量(每列的均值和标准差存在这个向量的属性里)。但apply()最后会把这些向量重新拼成一个矩阵,在这个拼接过程中,R会自动丢弃每个向量的独立属性——因为矩阵的属性系统和向量不一样,apply()不会帮你把每列的属性汇总到最终矩阵上。
你可以跑这段代码验证一下:
# 单独处理一列,看属性 single_col <- scale(trees$Girth) attributes(single_col) # 能看到scaled:center和scaled:scale # 用apply处理后看属性 test2 <- apply(trees, 2, scale) attributes(test2) # 只有矩阵的维度属性,没了那些scale相关的
二、解决办法
这里有几个实用的方案,按需选就行:
方案1:用lapply()+do.call(cbind, ...)保留属性
lapply()会老老实实保留每列处理后的带属性向量,再用do.call(cbind, ...)把它们拼成矩阵,这样每列的属性都能保留下来:
test2_fixed <- do.call(cbind, lapply(trees, scale, center = TRUE, scale = TRUE)) # 检查属性,每列都有自己的scaled:center和scaled:scale attributes(test2_fixed[, "Girth"]) # 也可以给整个矩阵加全局属性(和直接scale的结果一致) attr(test2_fixed, "scaled:center") <- sapply(trees, mean) attr(test2_fixed, "scaled:scale") <- sapply(trees, sd)
方案2:手动给apply()的结果补属性
如果已经用apply()得到了结果,不想重跑的话,可以手动计算每列的均值和标准差,再给矩阵加上属性:
test2 <- apply(trees, 2, scale, center = TRUE, scale = TRUE) # 计算原始数据的均值和标准差 col_means <- sapply(trees, mean) col_sds <- sapply(trees, sd) # 给矩阵添加属性 attr(test2, "scaled:center") <- col_means attr(test2, "scaled:scale") <- col_sds # 现在再看属性就和直接scale的结果一样了 attributes(test2)
方案3:用tidyverse工具更省心
如果你平时用tidyverse的话,dplyr的mutate(across(everything(), ...))处理后会返回数据框,同时保留每列的scale属性:
library(dplyr) test2_tidy <- trees %>% mutate(across(everything(), scale, center = TRUE, scale = TRUE)) # 查看某列的属性 attributes(test2_tidy$Girth)
总结
核心就是apply()在拼接结果时会丢弃向量的属性,而scale()直接处理矩阵/数据框时会把属性存在矩阵层面。上面的方案要么绕过apply()的属性丢失问题,要么手动补全属性,都能解决你的需求。
内容的提问来源于stack exchange,提问作者Lampard
相关产品推荐
相关产品推荐

