如何在R中高效将长格式data.frame转为含0/1的独立data.frame
问题描述
首先定义原始数据集:
mydata <- data.frame(id = c(1,1,1,2,2,3,4), hobby = c("music", "sports", "science", "science", "lifestyle", "party", "sports"), x = c(10, 10, 10, 23, 23, 11, 0), y = c(78, 78, 78, 55, 55, 22, 9))
查看数据结构:
> mydata id hobby x y 1 1 music 10 78 2 1 sports 10 78 3 1 science 10 78 4 2 science 23 55 5 2 lifestyle 23 55 6 3 party 11 22 7 4 sports 0 9
需求说明:
- 数据集为长格式,包含5种独特爱好:
music、sports、science、lifestyle、party - 需要为每个爱好生成独立数据集,其中
hobby列以0/1标记(1表示该id拥有此爱好),且每个数据集每行对应唯一id - 最终针对每个数据集运行Poisson回归:
glm(y ~ hobby + offset(x), family = "poisson", data = dat_xxx) - 实际场景存在5万个独特爱好,需高效实现
高效解决方案
针对5万个爱好的大规模场景,绝对不建议生成5万个独立的data.frame——这会造成严重的内存浪费且运行效率极低。推荐以下两种高效思路:
思路1:构建宽格式数据集,批量运行模型
先将数据转换为宽格式(每个爱好对应一列0/1值),同时保留每个id的唯一x、y值(同一id的x、y值一致):
library(dplyr) library(tidyr) # 获取每个id的唯一x、y基准数据 id_base <- mydata %>% distinct(id, x, y) # 生成每个id的爱好标记,转换为宽格式 hobby_wide <- mydata %>% distinct(id, hobby) %>% mutate(hobby_flag = 1) %>% pivot_wider(names_from = hobby, values_from = hobby_flag, values_fill = 0) # 合并得到宽格式数据集 wide_data <- id_base %>% left_join(hobby_wide, by = "id")
生成的wide_data结构如下:
id x y music sports science lifestyle party 1 1 10 78 1 1 1 0 0 2 2 23 55 0 0 1 1 0 3 3 11 22 0 0 0 0 1 4 4 0 9 0 1 0 0 0
接下来批量遍历所有爱好列,运行模型并将结果存储在列表中:
# 提取所有爱好名称 hobby_names <- setdiff(colnames(wide_data), c("id", "x", "y")) # 批量拟合模型 model_list <- lapply(hobby_names, function(hobby) { formula <- as.formula(paste0("y ~ ", hobby, " + offset(x)")) glm(formula, family = "poisson", data = wide_data) }) # 为模型列表命名,方便索引 names(model_list) <- hobby_names
之后可通过model_list$music、model_list$sports直接调用对应爱好的模型结果,无需生成大量独立数据集。
思路2:分组操作直接拟合模型
如果不需要保留每个爱好的独立数据集,可通过笛卡尔积生成全量标记数据,再按爱好分组拟合模型:
# 生成id与爱好的笛卡尔积,标记是否拥有该爱好 full_data <- expand.grid(id = unique(mydata$id), hobby = unique(mydata$hobby)) %>% left_join(mydata %>% distinct(id, hobby), by = c("id", "hobby")) %>% mutate(hobby_flag = ifelse(!is.na(hobby.y), 1, 0)) %>% left_join(id_base, by = "id") %>% select(id, hobby = hobby.x, hobby_flag, x, y) # 按爱好分组拟合模型 model_list2 <- full_data %>% group_by(hobby) %>% group_map(~ glm(y ~ hobby_flag + offset(x), family = "poisson", data = .x)) # 为模型列表命名 names(model_list2) <- unique(full_data$hobby)
这种方法全程无需生成多个独立数据集,内存占用远低于生成5万个data.frame的方案。
小数据场景可选:生成数据集列表
若爱好数量较少,可将各爱好数据集存储在列表中(而非全局环境中的独立对象):
# 获取id基准数据 id_base <- mydata %>% distinct(id, x, y) # 生成每个爱好的数据集列表 dat_list <- lapply(unique(mydata$hobby), function(hobby) { id_base %>% mutate(hobby = as.integer(id %in% mydata$id[mydata$hobby == hobby])) }) # 命名列表元素 names(dat_list) <- paste0("dat_", unique(mydata$hobby))
此时可通过dat_list$dat_music获取对应数据集,但该方案在5万爱好的场景下会引发内存溢出,不推荐使用。
内容的提问来源于stack exchange,提问作者Adrian
相关产品推荐
相关产品推荐

