如何在R中将含多组学生的宽格式数据集转换为长格式?
在R中将无统一列名的宽格式学生数据重构为长格式
我有一个包含22位讲师的数据集:
- 每位讲师对应9个属性列(FirstName、LastName、School、Email等)
- 每位讲师对应4组学生,每组学生的数据分布在64列中
- 学生列的列名没有统一命名规则,但存在区间规律:"Student1 age"到"Student2 age"之间的63列均属于Student1,Student2的列从"Student2 age"到"Student3 age"前一列,以此类推,每组固定64列
我需要把这个宽格式数据转换成长格式,让每位学生的每条信息单独占一行。
简化示例数据
以下是简化版的数据集(仅保留2位讲师、1个讲师属性列,每组学生2列数据):
| 讲师 | 讲师所属院校 | 学生1年龄 | 学生1性别 | 学生2年龄 | 学生2性别 | 学生3年龄 | 学生3性别 | 学生4年龄 | 学生4性别 |
|---|---|---|---|---|---|---|---|---|---|
| John | X | 18 | male | 18 | female | 16 | female | 17 | male |
| Jane | Y | 16 | female | 15 | male | 16 | male | 18 | female |
期望的长格式结果
| 讲师 | 讲师所属院校 | 学生编号 | 信息内容 |
|---|---|---|---|
| John | X | 1 | 18 |
| John | X | 1 | male |
| John | X | 2 | 18 |
| John | X | 2 | female |
| John | X | 3 | 16 |
| John | X | 3 | female |
| John | X | 4 | 17 |
| John | X | 4 | male |
| Jane | Y | 1 | 16 |
| Jane | Y | 1 | female |
| Jane | Y | 2 | 15 |
| Jane | Y | 2 | male |
| Jane | Y | 3 | 16 |
| Jane | Y | 3 | male |
| Jane | Y | 4 | 18 |
| Jane | Y | 4 | female |
我尝试的代码(仅适用于简化示例)
针对简化数据,我用以下代码实现了类似结果,但排序有差异:
library(tidyverse) original_data <- data.frame( Instructor = c("John", "Jane"), Instructor_school = c("X", "Y"), Student1_age = c(18, 16), Student1_gender = c("male", "female"), Student2_age = c(18, 15), Student2_gender = c("female", "male"), Student3_age = c(16, 16), Student3_gender = c("female", "male"), Student4_age = c(17, 18), Student4_gender = c("male", "female") ) # Reshape the data to long format long_data <- original_data %>% gather(key = "key", value = "value", -Instructor, -Instructor_school) %>% separate(col = key, into = c("Student", "Info"), sep = "_") %>% mutate(Student = gsub("Student", "", Student)) View(long_data)
但这个方法无法应用到实际数据中,因为学生列没有统一的命名规则,无法通过分隔列名来区分学生组。恳请提供解决方案。
解决方案
针对实际数据的区间规律,我们可以按列的位置分组来处理,不需要依赖列名:
步骤说明
- 拆分数据集:前9列是讲师属性列,剩余列按每64列一组,分成4组(对应Student1到Student4)
- 对每组学生列单独转换为长格式,并标记对应的学生编号
- 合并所有组的结果,得到最终长格式数据
代码实现
library(tidyverse) # 假设你的实际数据框名为`actual_data` # 1. 定义讲师属性列数量和每组学生列数 instructor_cols_num <- 9 student_cols_per_group <- 64 # 2. 按位置拆分学生列组 student_col_indices <- seq(instructor_cols_num + 1, ncol(actual_data)) student_groups <- split(student_col_indices, ceiling(seq_along(student_col_indices)/student_cols_per_group)) # 3. 循环处理每个学生组 long_result_list <- map2(student_groups, 1:4, function(cols, student_id) { # 提取当前组的列,结合讲师属性列 group_data <- actual_data %>% select(all_of(c(1:instructor_cols_num, cols))) # 转换为长格式 group_data %>% pivot_longer(cols = -all_of(1:instructor_cols_num), values_to = "信息内容") %>% mutate(学生编号 = student_id) %>% # 保留讲师属性列、学生编号、信息内容 select(all_of(1:instructor_cols_num), 学生编号, 信息内容) }) # 4. 合并所有组结果并排序 final_long_data <- bind_rows(long_result_list) %>% arrange(FirstName, LastName, 学生编号) # 替换为你的讲师唯一标识列
精准分组优化(可选)
如果你的数据中"Student1 age"是每组学生的第一列,可以用它来定位分组起始位置,避免讲师列数量变化导致的错误:
# 找到Student1 age的列索引 student1_start <- which(colnames(actual_data) == "Student1 age") # 手动定义4组学生的列范围 student_groups <- list( student1_start:(student1_start + student_cols_per_group - 1), (student1_start + student_cols_per_group):(student1_start + 2*student_cols_per_group - 1), (student1_start + 2*student_cols_per_group):(student1_start + 3*student_cols_per_group - 1), (student1_start + 3*student_cols_per_group):(student1_start + 4*student_cols_per_group - 1) ) # 后续处理同上述代码...
内容的提问来源于stack exchange,提问作者beth_9
相关产品推荐
相关产品推荐

