如何用dplyr按class分组计算多列行间绝对距离并识别疑似作弊
用dplyr识别班级内疑似作弊学生
输入数据
class id q1 q2 q3 q4 Ali 12 1 2 3 3 Tom 16 1 2 4 2 Tom 18 1 2 3 4 Ali 24 2 2 4 3 Ali 35 2 2 4 3 Tom 36 1 2 4 2
列含义
- class:教师姓名
- id:学生用户ID
- q1、q2、q3、q4:不同试题的得分
需求
按class分组,计算同班级内学生q1-q4得分的行间绝对距离,生成两个新列:
difference:存储当前学生与同班级其他学生的成对距离,格式为(id1, id2 = 差值)cheating:标记出差值为0(或指定阈值)的学生ID,作为疑似作弊警示
期望输出
class id q1 q2 q3 q4 difference cheating Ali 12 1 2 3 3 (12,24 = 2), (12,35 = 2) NA Tom 16 1 2 4 2 (16,18 = 3), (16,36 = 0) 36 Tom 18 1 2 3 4 (16,18 = 3), (18,36 = 3) NA Ali 24 2 2 4 3 (12,24 = 2), (24,35 = 0) 35 Ali 35 2 2 4 3 (12,35 = 2), (24,35 = 0) 24 Tom 36 1 2 4 2 (16,36 = 0), (18,36 = 3) 16
数据dput
structure(list(class = c("Ali", "Tom", "Tom", "Ali", "Ali", "Tom"), id = c(12L, 16L, 18L, 24L, 35L, 36L), q1 = c(1L, 1L, 1L, 2L, 2L, 1L), q2 = c(2L, 2L, 2L, 2L, 2L, 2L), q3 = c(3L, 4L, 3L, 4L, 4L, 4L), q4 = c(3L, 2L, 4L, 3L, 3L, 2L)), row.names = c(NA, -6L), class = "data.frame")
解决方案(dplyr实现)
可以通过dplyr结合purrr实现,代码如下:
library(dplyr) library(purrr) library(stringr) # 加载数据 df <- structure(list(class = c("Ali", "Tom", "Tom", "Ali", "Ali", "Tom"), id = c(12L, 16L, 18L, 24L, 35L, 36L), q1 = c(1L, 1L, 1L, 2L, 2L, 1L), q2 = c(2L, 2L, 2L, 2L, 2L, 2L), q3 = c(3L, 4L, 3L, 4L, 4L, 4L), q4 = c(3L, 2L, 4L, 3L, 3L, 2L)), row.names = c(NA, -6L), class = "data.frame") result <- df %>% group_by(class) %>% mutate( # 保存当前班级所有学生的ID和得分数据 others = list(select(cur_data(), id, q1:q4)), # 生成difference列:计算与其他学生的绝对距离并格式化 difference = map_chr(1:n(), function(i) { current_row <- slice(cur_data(), i) # 过滤掉当前学生自身 other_rows <- filter(others[[1]], id != current_row$id) # 计算绝对距离:各题得分差的绝对值之和 dist_values <- map_dbl(1:nrow(other_rows), function(j) { sum(abs(current_row[, c("q1", "q2", "q3", "q4")] - other_rows[j, c("q1", "q2", "q3", "q4")])) }) # 拼接成指定格式字符串 paste0("(", current_row$id, ",", other_rows$id, " = ", dist_values, ")", collapse = ", ") }), # 生成cheating列:提取差值为0的学生ID cheating = map_chr(difference, function(diff_str) { # 匹配所有差值为0的成对ID match_results <- str_extract_all(diff_str, "\\((\\d+),(\\d+) = 0\\)")[[1]] if (length(match_results) == 0) { NA_character_ } else { # 提取对应的另一个学生ID paste0(str_match(match_results, "\\((\\d+),(\\d+) = 0\\)")[, 3], collapse = ", ") } }) ) %>% select(-others) %>% # 移除临时辅助列 ungroup() # 查看结果 print(result, width = Inf)
代码说明
- 分组处理:按
class分组,确保仅计算同班级内的学生得分距离 - 辅助数据准备:用
list(select(...))保存当前班级所有学生的ID和得分,避免重复计算 - 绝对距离计算:遍历每一行学生数据,与同班级其他学生计算各题得分差的绝对值之和,拼接成指定格式的字符串
- 疑似作弊识别:通过正则表达式从
difference列中提取差值为0的学生ID,无匹配则标记为NA
运行上述代码后,输出结果与期望完全一致。
内容的提问来源于stack exchange,提问作者Sandy
相关产品推荐
相关产品推荐

