R语言:求更优雅的分类变量对应权重加权交叉表实现方案
问题
我有一个包含80列的data frame:前40列是带有6个factor水平("A", "I", "M", "P", "Q", "R")的分类变量,后40列是对应每个分类变量的权重。我需要生成一个结果矩阵,按每个分类变量的各水平汇总对应的权重。目前用循环实现:
for(i in 1:40){ WKC[i,]<- xtabs(VaW[,i+40]~VaW[,i])}
这个方法能得到结果,但不够优雅,而且如果权重列的顺序发生变化,代码就会失效。想问问有没有更鲁棒、更简洁的实现方式?
附数据结构(截取前几行):
dput(head(VaW)) structure(list(V1 = structure(c(4L, NA, NA, NA, NA, NA), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V2 = structure(c(NA, NA, NA, NA, 5L, NA), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V3 = structure(c(NA, NA, NA, 2L, NA, NA), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V4 = structure(c(NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_ ), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V5 = structure(c(NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V6 = structure(c(NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_ ), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V7 = structure(c(NA, NA, 5L, NA, NA, NA), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V8 = structure(c(NA, NA, NA, NA, NA, 2L), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V9 = structure(c(2L, 1L, NA, NA, NA, 5L), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V10 = structure(c(NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V11 = structure(c(NA, 1L, NA, NA, NA, NA), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V12 = structure(c(NA, 2L, NA, NA, NA, NA), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V13 = structure(c(NA, NA, NA, NA, 5L, NA), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V14 = structure(c(NA, NA, NA, NA, 5L, NA), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V15 = structure(c(NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V16 = structure(c(NA, 4L, NA, NA, NA, 5L), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V17 = structure(c(NA, NA, 4L, NA, NA, NA), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V18 = structure(c(NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V19 = structure(c(NA, NA, NA, NA, NA, 5L), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V20 = structure(c(NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V21 = structure(c(NA, 1L, 4L, NA, NA, NA), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V22 = structure(c(NA, NA, NA, 2L, 1L, NA), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V23 = structure(c(NA, NA, NA, NA, 5L, NA), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V24 = structure(c(NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_ ), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V25 = structure(c(4L, NA, NA, NA, NA, NA), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V26 = structure(c(NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_ ), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V27 = structure(c(NA, NA, NA, 1L, NA, NA), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V28 = structure(c(4L, NA, NA, NA, NA, NA), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V29 = structure(c(4L, NA, 4L, NA, NA, NA), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V30 = structure(c(NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V31 = structure(c(NA, NA, NA, 5L, NA, NA), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V32 = structure(c(NA, NA, NA, 3L, NA, NA), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), V33 = structure(c(NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_), .Label = c("A", "I", "M", "P", "Q", "R"), class = "factor"), ...))
解决方案
你的循环思路没问题,但确实可以优化得更优雅、更鲁棒——核心是避免依赖列的位置索引,而是通过明确的配对关系来处理分类变量和权重列。下面给你几种不同风格的实现方式:
方法1:用purrr::map2(tidyverse简洁风格)
如果你的分类列是V1-V40,权重列是V41-V80,可以先把它们拆分成两个列表,再用map2逐个配对计算:
library(tidyverse) # 拆分分类变量列和权重列(如果列名有规则,比如分类列是V开头,权重列是W开头,用select更稳妥) cat_cols <- VaW[, 1:40] weight_cols <- VaW[, 41:80] # 逐个计算每个分类变量的权重汇总,转成一行后合并 result_list <- map2(cat_cols, weight_cols, function(cat_col, weight_col) { # 用xtabs计算汇总,转置成一行方便合并 xtabs(weight_col ~ cat_col) %>% as.data.frame.matrix() %>% t() }) # 合并成结果矩阵,设置行名为分类列的名称 WKC <- do.call(rbind, result_list) rownames(WKC) <- names(cat_cols)
如果后续列顺序变化,只要你能正确筛选出分类列和权重列(比如用select(VaW, starts_with("V"))和select(VaW, starts_with("W"))),代码就不会出错。
方法2:Base R的mapply(无需加载额外包)
不想用tidyverse的话,base R的mapply也能实现类似效果,还能确保所有factor水平都被包含:
# 拆分列 cat_cols <- VaW[, 1:40] weight_cols <- VaW[, 41:80] # 配对计算,确保每个分类的6个水平都存在(避免某些水平无数据导致列缺失) result_list <- mapply(function(cat_col, weight_col) { tab <- xtabs(weight_col ~ cat_col) # 初始化全0的向量,包含所有factor水平 full_tab <- setNames(rep(0, length(levels(cat_col))), levels(cat_col)) # 填充有数据的水平值 full_tab[names(tab)] <- tab full_tab }, cat_cols, weight_cols, SIMPLIFY = FALSE) # 转成矩阵并设置行名 WKC <- do.call(rbind, result_list) rownames(WKC) <- names(cat_cols)
方法3:数据重塑(更清晰的tidy思路)
另一种思路是把数据转成长格式,分组汇总后再转宽格式,完全不依赖列的位置——只要你能通过列名规则匹配分类和权重列:
library(tidyverse) # 转成长格式,匹配分类变量和对应的权重列 long_data <- VaW %>% mutate(row_id = row_number()) %>% # 添加行ID确保配对正确 # 拆分分类变量列 pivot_longer(V1:V40, names_to = "cat_var", values_to = "cat_level") %>% # 拆分权重列 pivot_longer(V41:V80, names_to = "weight_var", values_to = "weight") %>% # 提取列名中的数字,匹配分类列和权重列(V1对应V41=1+40) mutate( cat_num = parse_number(cat_var), weight_num = parse_number(weight_var) ) %>% filter(cat_num == weight_num - 40) # 分组汇总权重 summary_data <- long_data %>% group_by(cat_var, cat_level) %>% summarise(total_weight = sum(weight, na.rm = TRUE), .groups = "drop") %>% # 确保所有factor水平都被包含,无数据的填0 complete(cat_var, cat_level = levels(.$cat_level), fill = list(total_weight = 0)) # 转成宽格式得到目标矩阵 WKC <- summary_data %>% pivot_wider(names_from = cat_level, values_from = total_weight) %>% column_to_rownames("cat_var") %>% as.matrix()
如果权重列的命名规则不同(比如叫W1-W40),只需要把filter条件改成cat_num == weight_num即可,非常灵活。
内容的提问来源于stack exchange,提问作者Boo
相关产品推荐
相关产品推荐

