PAYATHON 2026

R dplyr:如何在分组汇总中使用自定义函数计算升学率?

数据阿明

可以把 progressionby19() 改为接收列向量并返回单个数值的函数,再在 summarise() 中传入各列。dplyr 会将当前组的数据传给函数,算出每个 Type 对应的升学率,无需在函数内部使用 do()。

原代码的问题在哪里?

在下面的写法中:

example %>%
  group_by(Type) %>%
  summarise(
    learner_count = n_distinct(Student.ID),
    progression_rate = progressionby19(.)
  )

Student.ID 是 summarise() 当前组中的列,而 %>% 的占位符 . 指向传入管道的整个分组数据对象,不会自动变成当前组的数据。两个表达式使用的数据范围因此不同。

原函数中的 total 和 progressed 都是 summarise() 返回的表格,(progressed / total) * 100 的结果也是表格,而非单个数值。如果传入的数据仍带有分组属性,内部汇总也会受这些分组影响。

新增的 do() 分支会再次对整个传入对象执行分组计算,返回一张结果表,无法让结果自然对应外层 summarise() 当前正在计算的组。由于没有具体报错信息,无法确定原先触发的是哪一种错误,但数据范围和返回类型都需要调整。

改成接收列向量的函数

以下代码使用问题中已定义的 example:

library(dplyr)

progressionby19 <- function(student_id, attended, entry) {
  participated <- attended %in% "Yes"

  total <- n_distinct(
    student_id[participated],
    na.rm = TRUE
  )

  progressed <- n_distinct(
    student_id[participated & (entry %in% "Yes")],
    na.rm = TRUE
  )

  if (total == 0L) {
    return(NA_real_)
  }

  100 * progressed / total
}

result <- example %>%
  group_by(Type) %>%
  summarise(
    learner_count = n_distinct(
      Student.ID[Student.Attended %in% "Yes"],
      na.rm = TRUE
    ),
    progression_rate = progressionby19(
      Student.ID,
      Student.Attended,
      entryby19
    )
  ) %>%
  ungroup()

result

函数的计算口径是:

  • 分母:当前组中,存在 Student.Attended == "Yes" 记录的不同学生人数。
  • 分子:当前组中,存在同一条记录同时满足 Student.Attended == "Yes" 和 entryby19 == "Yes" 的不同学生人数。
  • 升学率:分子除以分母,再乘以 100。

n_distinct() 避免同一名学生的重复记录增加人数。na.rm = TRUE 明确排除缺失的学生 ID;%in% "Yes" 则只把明确标记为 "Yes" 的记录纳入相应条件。

示例应该得到什么结果?

根据给定数据,结果应为:

Typelearner_countprogression_rate
Exhibition2100
Mentoring250

Exhibition 中有两名参加者,两人均进入 HE,升学率为 100%。

Mentoring 中,#003 没有参加活动,需要排除。参加者为 #004 和 #005,其中只有 #004 进入 HE,升学率为 50%。

如果需要计算整体升学率,可以直接传入完整列:

progressionby19(
  example$Student.ID,
  example$Student.Attended,
  example$entryby19
)
# 按给定数据计算,结果应为 75

人数口径和边界情况

上面的 learner_count 统计参加活动的学生人数,与升学率的分母一致。原代码中的 n_distinct(Student.ID) 会统计组内所有学生,按原始写法,Mentoring 的人数是 3。如果需要保留这个口径,可以将该字段改回原表达式,并用名称区分总人数与参加人数。

当某组没有有效参加者时,函数返回 NA_real_,表示该组没有可用的升学率,避免把没有分母的情况解释成 0%。由于没有提前删除未参加记录,这类组仍会出现在结果中。

如果同一名学生属于多个 Type,他会在每个符合条件的组中各计一次。如果同一学生的多条记录存在状态冲突,当前函数沿用原代码的筛选逻辑。若业务要求按最终状态或最新记录计算,需要先明确记录选择规则,再进行汇总。

备注:内容仅供参考。