您每次都明确调用 1 个随机数:runif(1, ...)。相反,请使用runif(n(), ...)。意识到它不是为每一行调用一次,而是为满足该条件的所有行运行一次。在下面的示例中,5 月份有三行,但 runif 被称为 runif(1,..),并且该单个数字应用于所有三行。
样本数据:
set.seed(42)
df <- data.frame(day = as.Date("2022-01-01") + sample(364, size=10)) %>%
arrange(day) %>%
mutate(month = as.POSIXlt(day)$mon + 1L)
df
# day month
# 1 2022-02-19 2
# 2 2022-03-16 3
# 3 2022-05-03 5
# 4 2022-05-09 5
# 5 2022-05-27 5
# 6 2022-06-03 6
# 7 2022-08-17 8
# 8 2022-10-31 10
# 9 2022-11-18 11
# 10 2022-12-31 12
破碎:
library(dplyr)
set.seed(42)
df %>%
mutate(
new_day = case_when(
month == 2 ~ floor(runif(1, 1, 28)),
month %in% c(9, 4, 6, 11) ~ floor(runif(1, 1, 30)),
TRUE ~ floor(runif(1, 1, 31))
)
)
# day month new_day
# 1 2022-02-19 2 25
# 2 2022-03-16 3 9
# 3 2022-05-03 5 9
# 4 2022-05-09 5 9
# 5 2022-05-27 5 9
# 6 2022-06-03 6 28
# 7 2022-08-17 8 9
# 8 2022-10-31 10 9
# 9 2022-11-18 11 28
# 10 2022-12-31 12 9
为了证明runif 被调用一次 对于满足每个条件的所有行,我将添加message 到每个。如果我们可以依赖runif(1,..),那么我们应该会看到"30d" 打印到控制台7 次和"31d" 两次,但我们没有。
set.seed(42)
df %>%
mutate(
new_day = case_when(
month == 2 ~ { message("Feb: ", length(month)); floor(runif(1, 1, 28)); },
month %in% c(9, 4, 6, 11) ~ { message("30d: ", length(month)); floor(runif(1, 1, 30)); },
TRUE ~ { message("31d: ", length(month)); floor(runif(1, 1, 31)); }
)
)
# Feb: 10
# 30d: 10
# 31d: 10
# day month new_day
# 1 2022-02-19 2 25
# 2 2022-03-16 3 9
# 3 2022-05-03 5 9
# 4 2022-05-09 5 9
# 5 2022-05-27 5 9
# 6 2022-06-03 6 28
# 7 2022-08-17 8 9
# 8 2022-10-31 10 9
# 9 2022-11-18 11 28
# 10 2022-12-31 12 9
这表明,当我们在其中一个条件的 RHS 内时,它是对框架所有行的调用。请注意,每次我们调用runif,它都会看到month 的所有 值(df 中有 10 行)。
改为使用n()(每次调用的行数):
set.seed(42)
df %>%
mutate(
new_day = case_when(
month == 2 ~ floor(runif(n(), 1, 28)),
month %in% c(9, 4, 6, 11) ~ floor(runif(n(), 1, 30)),
TRUE ~ floor(runif(n(), 1, 31))
)
)
# day month new_day
# 1 2022-02-19 2 25
# 2 2022-03-16 3 5
# 3 2022-05-03 5 30
# 4 2022-05-09 5 29
# 5 2022-05-27 5 3
# 6 2022-06-03 6 28
# 7 2022-08-17 8 12
# 8 2022-10-31 10 28
# 9 2022-11-18 11 14
# 10 2022-12-31 12 26
这意味着我们在case_when 中抽取 30 个随机数,每个条件抽取 10 个。虽然这不是问题这里(大量提取熵可能会很慢),但您可以通过预先提取随机数据然后相应地进行缩放来缓解。
set.seed(42)
df %>%
mutate(
rand = runif(n(), 0, 1),
new_day = case_when(
month == 2 ~ ceiling(rand*28),
month %in% c(9, 4, 6, 11) ~ ceiling(rand*30),
TRUE ~ ceiling(rand*31)
)
)
# day month rand new_day
# 1 2022-02-19 2 0.9148060 26
# 2 2022-03-16 3 0.9370754 30
# 3 2022-05-03 5 0.2861395 9
# 4 2022-05-09 5 0.8304476 26
# 5 2022-05-27 5 0.6417455 20
# 6 2022-06-03 6 0.5190959 16
# 7 2022-08-17 8 0.7365883 23
# 8 2022-10-31 10 0.1346666 5
# 9 2022-11-18 11 0.6569923 20
# 10 2022-12-31 12 0.7050648 22
(注意从floor 到ceiling 的转变)。代码可以通过其他方式进行重构,但我认为这通常已经足够了。