PAYATHON 2026

R 中如何使用 left_join 按累积概率随机分配职业?

数据阿明

可以用 left_join() 配合 join_by() 中的 closest():先按 homeworld 和 species 匹配,再找到大于或等于当前随机数的最小累积概率上限。分配规则可以放在独立表中,无需逐条编写 case_when()。

可直接使用的代码

这段代码需要 dplyr 1.1.0 或更高版本。为区分抽样值和规则阈值,规则表中的累积概率列命名为 cumulative_prob。

library(dplyr)

occupations <- tribble(
  ~homeworld, ~species, ~occupation,      ~cumulative_prob,
  "Tatooine", "Human", "moisture farmer", 0.5,
  "Tatooine", "Human", "pilot",          0.8,
  "Tatooine", "Human", "mechanic",       1.0
)

set.seed(123)

result <- starwars %>%
  mutate(random_draw = runif(n())) %>%
  left_join(
    occupations,
    by = join_by(
      homeworld,
      species,
      closest(random_draw <= cumulative_prob)
    ),
    na_matches = "never"
  )

runif(n()) 会为每行生成一个随机数,这里不需要 rowwise()。set.seed() 用于复现抽样:输入数据和行顺序保持一致时,重新运行代码会得到相同的分配结果。

为什么需要 closest()

连接条件中的这一行决定了如何选择职业:

closest(random_draw <= cumulative_prob)

它先找出满足 random_draw <= cumulative_prob 的规则,再从中选择最接近随机数的上限,也就是满足条件的最小上限。

对于 Tatooine 和 Human 这一组,结果与原来的 case_when() 一致:

随机数范围匹配上限occupation
0 <= random_draw <= 0.50.5moisture farmer
0.5 < random_draw <= 0.80.8pilot
0.8 < random_draw < 11.0mechanic

如果只写 random_draw <= cumulative_prob,当随机数为 0.4 时,三个上限都满足条件,连接结果就会出现多行。加上 closest() 后,会选择上限为 0.5 的规则,前提是该组没有重复记录这个上限。

规则表需要满足的条件

cumulative_prob 保存的是累积概率。职业概率为 0.5、0.3、0.2 时,对应的上限应为 0.5、0.8、1.0,不能直接把三项职业概率作为连接阈值。

每个 homeworld、species 组合内的累积概率应位于 [0, 1],按职业分配顺序严格递增,最后一个上限为 1。同一组合下如果存在重复上限,closest() 仍可能返回多条匹配记录;它不会从并列规则中随机选一条。

没有对应规则的输入行会由 left_join() 保留,新增的 occupation 和 cumulative_prob 为 NA。代码中的 na_matches = "never" 表示缺失的连接键不相互匹配,避免把未知的 homeworld 或 species 当成有效规则。

如果最终只需要职业,可以在连接后删除辅助列:

result <- result %>%
  select(-random_draw, -cumulative_prob)

备注:内容仅供参考。