基于data.table统计学生考试科目数:处理双强度科目计数问题
解决data.table中考虑“双强度”修读规则的学生修读科目统计问题
我刚好处理过类似的场景,咱们一步步来搞定这个问题。首先得明确核心需求:原来的num_subjects是直接数非NA的列数,但双强度科目会用两个成绩列(比如Physics_1和Physics_2)来标识——这时候不管这两个列有没有成绩,只要属于同一门科目的双强度修读,都只能算1门,而不是2门。
先模拟一份测试数据
咱们先造个符合场景的data.table,方便后续演示:
library(data.table) # 模拟数据:StudentID是学生ID,Math/English是普通科目,Physics/Biology是双强度科目(带_1/_2后缀) dt <- data.table( StudentID = c(1, 2, 3), Math = c(85, NA, 90), Physics_1 = c(78, 82, NA), Physics_2 = c(81, 85, NA), English = c(92, 88, 79), Biology_1 = c(NA, 75, 80), Biology_2 = c(NA, 77, NA) )
方法一:行级处理(适合小数据集)
这种方法直接在原表上操作,对每行数据按科目组统计:
# 第一步:提取科目组名称(去掉双强度科目的后缀,比如把Physics_1/Physics_2统一为Physics) subject_groups <- gsub("_\\d+$", "", names(dt)[-1]) # 排除StudentID列,用正则去掉末尾的_1/_2 # 第二步:对每行计算正确的科目数 dt[, num_subjects_corrected := sapply(.SD, function(row) { # 把当前行的成绩按科目组分组 grouped_scores <- split(row, subject_groups) # 统计每个组里是否至少有一个非NA成绩,然后求和 sum(sapply(grouped_scores, function(group) any(!is.na(group)))) }), .SDcols = -"StudentID"]
方法二:转长格式聚合(适合大数据集,更高效)
如果你的数据集很大,行级循环效率不高,推荐用melt+dcast的方式:
# 1. 把宽表转长表,方便按科目组聚合 dt_long <- melt(dt, id.vars = "StudentID", variable.name = "Subject", value.name = "Score") # 2. 给每个科目分配对应的科目组 dt_long[, Subject_Group := gsub("_\\d+$", "", Subject)] # 3. 按学生+科目组,标记该学生是否修读了这门科目(只要有一个成绩就算) dt_long[, has_enrolled := any(!is.na(Score)), by = .(StudentID, Subject_Group)] # 4. 转回宽表,统计每个学生的修读科目数 dt_corrected <- dcast(dt_long, StudentID ~ ., fun.aggregate = sum, value.var = "has_enrolled") setnames(dt_corrected, ".", "num_subjects_corrected") # 5. 把统计结果合并回原表 dt <- dt[dt_corrected, on = "StudentID"]
自定义规则适配
如果你的双强度科目不是用_1/_2标识的(比如列名带_Double后缀),只需要调整正则表达式就行。比如如果是Physics_Double这种格式,就把gsub("_\\d+$", "", ...)改成gsub("_Double$", "", ...),根据实际的命名规则灵活调整。
运行完上面的代码,你就能得到修正后的num_subjects_corrected字段,准确统计每个学生实际修读的科目数啦!
内容的提问来源于stack exchange,提问作者newruser
相关产品推荐
相关产品推荐

