如何为R数据框按列子集生成非NA计数列与均值列?
在R数据框中按前缀分组添加非NA计数与行均值列
问题背景
现有如下示例数据框,需要按列名前缀(如var1、var2)生成两类新列:
- 对应前缀列子集的非NA值计数(如
var1_count、var2_count) - 对应前缀列子集的行均值(如
avg_var1、avg_var2)
示例数据生成代码:
set.seed(123) df <- data.frame( id = 1:5, var1_a = runif(1:5), var1_b = runif(1:5), var2_a = runif(1:5), var2_b = runif(1:5)) df[, "var1_a"][ df[, "var1_a"] < 0.5 ] <- NA df[, "var2_b"][ df[, "var2_b"] > 0.5 ] <- NA
处理后的数据框:
id var1_a var1_b var2_a var2_b 1 1 NA 0.0455565 0.9568333 NA 2 2 0.7883051 0.5281055 0.4533342 0.24608773 3 3 NA 0.8924190 0.6775706 0.04205953 4 4 0.8830174 0.5514350 0.5726334 0.32792072 5 5 0.9404673 0.4566147 0.1029247 NA
之前尝试的代码因不理解正则\\..*导致匹配错误,同时实际列名存在大小写差异(如brand_m1_A和Brand_m1_B),需要解决这些问题。
关键概念解释:\\..*正则表达式
\\..*是正则表达式的写法:
\\.:匹配点号(正则中点号是通配符,需用反斜杠转义,R里要写两个反斜杠).*:匹配点号之后的任意长度字符- 所以
gsub("\\..*", "", names(df))的作用是:把列名中第一个点及后面的所有内容删除,提取前缀(比如var1_a变成var1,brand_m1_A变成brand)
解决大小写差异问题
如果列名前缀存在大小写不同(如brand和Brand),默认分组会把它们当成不同组,需要先统一前缀的大小写:
- 转成小写:
tolower(gsub("\\..*", "", names(df))) - 转成大写:
toupper(gsub("\\..*", "", names(df)))
具体解决方案
方法1:Base R实现
直接基于分组计算计数和均值,再合并到原数据框:
# 提取分组前缀(统一转小写处理大小写差异) groups <- tolower(gsub("\\..*", "", names(df))) # 排除id列,只处理数值列 num_cols <- df[, !names(df) %in% "id"] grouped_cols <- split.default(num_cols, groups) # 计算非NA计数 count_cols <- lapply(grouped_cols, function(x) rowSums(!is.na(x))) names(count_cols) <- paste0(names(count_cols), "_count") # 计算行均值(忽略NA) mean_cols <- lapply(grouped_cols, function(x) rowMeans(x, na.rm = TRUE)) names(mean_cols) <- paste0("avg_", names(mean_cols)) # 合并所有列 df_result <- cbind(df, count_cols, mean_cols)
运行后得到的结果:
id var1_a var1_b var2_a var2_b var1_count var2_count avg_var1 avg_var2 1 1 NA 0.0455565 0.9568333 NA 1 1 0.04555650 0.95683330 2 2 0.7883051 0.5281055 0.4533342 0.24608773 2 2 0.65820530 0.34971097 3 3 NA 0.8924190 0.6775706 0.04205953 1 2 0.89241900 0.35981507 4 4 0.8830174 0.5514350 0.5726334 0.32792072 2 2 0.71722620 0.45027707 5 5 0.9404673 0.4566147 0.1029247 NA 2 1 0.69854100 0.10292470
方法2:tidyverse(dplyr + stringr)实现
更直观的管道式操作,适合复杂数据处理:
library(dplyr) library(stringr) df_result <- df %>% rowwise() %>% mutate( var1_count = sum(!is.na(c_across(starts_with("var1")))), var2_count = sum(!is.na(c_across(starts_with("var2")))), avg_var1 = mean(c_across(starts_with("var1")), na.rm = TRUE), avg_var2 = mean(c_across(starts_with("var2")), na.rm = TRUE) ) %>% ungroup()
如果实际列名是brand_m1_A这类,只需把starts_with("var1")改成starts_with(str_to_lower("Brand"))(统一大小写匹配)即可。
内容的提问来源于stack exchange,提问作者Victor Shin
相关产品推荐
相关产品推荐

