You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于R语言计算学生连续两次挂科后下次挂科的概率

问题描述与代码修正需求

数据集构造

现有学生多次考试的数据集,考试结果为通过("1")或挂科("0"),数据集构造代码如下:

id = sample.int(10000, 100000, replace = TRUE)
res = c(1,0)
results = sample(res, 100000, replace = TRUE)
date_exam_taken = sample(seq(as.Date('1999/01/01'), as.Date('2020/01/01'), by="day"), 100000, replace = TRUE)

my_data = data.frame(id, results, date_exam_taken)
my_data <- my_data[order(my_data$id, my_data$date_exam_taken),]

my_data$general_id = 1:nrow(my_data)
my_data$exam_number = ave(my_data$general_id, my_data$id, FUN = seq_along)
my_data$general_id = NULL

数据集结构示例:

id results date_exam_taken exam_number
7992   1       1      2004-04-23           1
24837  1       0      2004-12-10           2
12331  1       1      2007-01-19           3
34396  1       0      2007-02-21           4
85250  1       0      2007-09-26           5
11254  1       1      2009-12-20           6

已实现的一级分析

已完成单次考试结果后下次考试的概率计算,代码及结果如下:

my_list = list()

for (i in 1:length(unique(my_data$id)))
{ 
    {tryCatch({ 
        start_i = my_data[my_data$id == i,]
        pairs_i =  data.frame(first = head(start_i$results, -1), second = tail(start_i$results, -1))
        frame_i =  as.data.frame(table(pairs_i))
        frame_i$id = i
        my_list[[i]] = frame_i
    }, error = function(e){})
    }}

final = do.call(rbind.data.frame, my_list)

library(dplyr)
total_1 = final %>% group_by(first, second) %>% summarise(totals = n())
total_2 = total_1 %>% group_by(first) %>% summarise(sum = sum(totals))

join = merge(x = total_1, y = total_2, by = "first", all = TRUE)
join$probs = join$totals/join$sum

结果示例:

first second totals   sum     probs
1     0      0   9817 19557 0.5019686
2     0      1   9740 19557 0.4980314
3     1      0   9727 19498 0.4988717
4     1      1   9771 19498 0.5011283

二级分析需求

需要计算连续两次考试结果后,第三次考试的概率(例如:学生第3、4次考试挂科,求第5次挂科的概率),预期结果格式:

first second third totals sums probs
1     1      1     1    ...  ...   ...
2     0      0     0    ...  ...   ...
3     1      0     1    ...  ...   ...
4     0      1     0    ...  ...   ...
5     0      0     1    ...  ...   ...
6     1      1     0    ...  ...   ...
7     0      1     1    ...  ...   ...
8     1      0     0    ...  ...   ...

错误的尝试代码及问题

尝试的代码得到了完全均匀的概率结果(所有probs为0.5),明显不符合预期,代码如下:

library(stringr)
my_list = list()

for (i in 1:length(unique(my_data$id)))
{ 
    {tryCatch({
start_i = my_data[my_data$id == i,]

vals_i = as.numeric(paste(start_i$results, collapse = ""))
L_1_i = lengths(gregexpr("111", vals_i))
L_2_i = lengths(gregexpr("000", vals_i))
L_3_i = lengths(gregexpr("101", vals_i))
L_4_i = lengths(gregexpr("010", vals_i))
L_5_i = lengths(gregexpr("001", vals_i))
L_6_i = lengths(gregexpr("110", vals_i))
L_7_i = lengths(gregexpr("011", vals_i))
L_8_i = lengths(gregexpr("100", vals_i))

frame_i = data.frame(class = c("111","000","101","010","001","110","011","100"), values = c(L_1_i, L_2_i, L_3_i, L_4_i, L_5_i, L_6_i, L_7_i, L_8_i))
frame_i$id = i

        my_list[[i]] = frame_i
    }, error = function(e){})
    }}

final = do.call(rbind.data.frame, my_list)

final$first = substr(final$class, 1,1)
final$second = substr(final$class, 2,2)
final$third = substr(final$class, 3,3)

total_1 = final %>% group_by(first, second, third) %>% summarise(totals = n())
total_2 = total_1 %>% group_by(first, second) %>% summarise(sum = sum(totals))

join = merge(x = total_1, y = total_2, by = c("first", "second"), all = TRUE)
join$probs = join$totals/join$sum

错误结果示例:

first second third totals   sum probs
1     0      0     0  10000 20000   0.5
2     0      0     1  10000 20000   0.5
3     0      1     0  10000 20000   0.5
4     0      1     1  10000 20000   0.5
5     1      0     0  10000 20000   0.5
6     1      0     1  10000 20000   0.5
7     1      1     0  10000 20000   0.5
8     1      1     1  10000 20000   0.5

原代码错误分析

  1. 字符串拼接损坏数据:as.numeric(paste(start_i$results, collapse = ""))会将长序列转换为科学计数法,导致原始考试结果字符串完全失真,正则匹配完全失效。
  2. 正则匹配计数错误:gregexpr默认非重叠匹配,例如"1111"中"111"只会被统计1次,但实际存在2个连续三元组(位置1-3、2-4),计数严重偏低。
  3. 统计逻辑错误:summarise(totals = n())统计的是出现该三元组的学生数量,而非所有学生的三元组总次数,这是导致结果完全均匀的核心原因。

修正后的代码实现

方法一:使用dplyr分组滑动窗口(高效简洁)

library(dplyr)

# 按学生分组,生成连续三元组(前两次结果 + 当前结果)
triples_data <- my_data %>%
  group_by(id) %>%
  # 仅保留考试次数>=3的学生数据
  filter(n() >= 3) %>%
  mutate(
    first = lag(results, 2),  # 前两次考试结果
    second = lag(results, 1), # 前一次考试结果
    third = results           # 当前考试结果
  ) %>%
  # 过滤掉没有足够前序数据的行
  filter(!is.na(first)) %>%
  ungroup()

# 统计各三元组的总出现次数
total_triples <- triples_data %>%
  group_by(first, second, third) %>%
  summarise(totals = n(), .groups = "drop")

# 统计每个(first, second)组合的总次数
total_pairs <- total_triples %>%
  group_by(first, second) %>%
  summarise(sum = sum(totals), .groups = "drop")

# 合并计算概率
final_result <- merge(total_triples, total_pairs, by = c("first", "second")) %>%
  mutate(probs = totals / sum) %>%
  arrange(first, second, third)

# 查看结果
print(final_result)

方法二:修正循环逻辑(保留原循环思路)

my_list <- list()

# 遍历每个唯一学生ID
for (i in unique(my_data$id)) {
  tryCatch({
    start_i <- my_data[my_data$id == i, ]
    # 仅处理考试次数>=3的学生
    if (nrow(start_i) >= 3) {
      # 直接按位置截取生成三元组
      triples_i <- data.frame(
        first = start_i$results[1:(nrow(start_i)-2)],
        second = start_i$results[2:(nrow(start_i)-1)],
        third = start_i$results[3:nrow(start_i)]
      )
      # 统计当前学生的三元组次数
      frame_i <- as.data.frame(table(triples_i))
      frame_i$id <- i
      my_list[[as.character(i)]] <- frame_i
    }
  }, error = function(e){})
}

final <- do.call(rbind.data.frame, my_list)

# 统计所有学生的三元组总次数
total_1 <- final %>% 
  group_by(first, second, third) %>% 
  summarise(totals = sum(Freq), .groups = "drop")

# 统计每个(first, second)组合的总次数
total_2 <- total_1 %>% 
  group_by(first, second) %>% 
  summarise(sum = sum(totals), .groups = "drop")

# 合并计算概率
join <- merge(total_1, total_2, by = c("first", "second")) %>%
  mutate(probs = totals / sum) %>%
  arrange(first, second, third)

# 查看结果
print(join)

内容的提问来源于stack exchange,提问作者stats_noob

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 14:25:16