0Pricing
R Academy · 课时

使用 map2() 和 pmap() 处理多个输入

使用 map2() 和 pmap() 同时遍历两个或更多列表

使用 map2() 和 pmap() 处理多个输入 是 CoddyKit 上的免费 R Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 R Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 R Academy 课程共包含 4 节课。

遍历多个输入

map() 一次遍历一个输入。但许多函数需要两个或更多个并行输入。purrr 的 map2() 处理两个输入,pmap() 则可以处理任意数量的输入。两者都保证返回值类型安全。

library(purrr)

# Simulate: add corresponding elements from two vectors
x <- c(1, 2, 3, 4)
y <- c(10, 20, 30, 40)

# map2 applies fn(x[i], y[i]) for each i
result <- map2(x, y, function(a, b) a + b)
print(result)

map2() — 两个并行输入

map2(.x, .y, .f) 会对 .x 和 .y 中对应的元素应用 .f(x_i, y_i)。两个向量的长度必须相同(或者其中一个长度为 1,并进行循环复用)。该函数返回列表。

library(purrr)

names_vec <- c('Alice','Bob','Carol')
scores_vec <- c(85, 92, 78)

# Build a personalized message for each student
map2_chr(names_vec, scores_vec,
         ~paste(.x, 'scored', .y, 'points'))

使用 ~ 简写调用 map2()

公式简写 ~expr 同样适用于 map2():.x 表示第一个输入,.y 表示第二个输入。这样可以简洁地编写接收两个参数的匿名函数。

library(purrr)

lower_bounds <- c(0, 10, 20, 50)
upper_bounds <- c(9, 19, 49, 100)

# Create labels for each range
map2_chr(lower_bounds, upper_bounds,
         ~paste0('[', .x, '-', .y, ']'))

map2() 的类型变体

与 map() 类似,map2() 也提供了保证返回类型安全的类型变体:map2_dbl()、map2_chr()、map2_lgl()、map2_int() 和 map2_df()。请使用与预期输出类型相匹配的变体。

library(purrr)

actual <- c(100, 150, 200, 120)
target <- c(110, 140, 195, 130)

# Numeric: percentage achievement
map2_dbl(actual, target, ~round(100 * .x / .y, 1))

# Logical: did they meet their target?
map2_lgl(actual, target, ~.x >= .y)

pmap() — 任意数量的输入

pmap(.l, .f) 的第一个参数是一个向量列表。每个向量都是一个独立的输入流。该函数会同时接收所有输入流中相互对应的元素。

library(purrr)

# Three parallel inputs
params <- list(
  name = c('Alice','Bob','Carol'),
  score = c(85, 92, 78),
  grade = c('B','A','C')
)

pmap_chr(params, function(name, score, grade) {
  paste(name, ':', score, '(', grade, ')')
})

将 pmap() 与数据框结合使用

数据框本质上是一个列的列表,因此可以直接将数据框作为 pmap() 的第一个参数。该函数会将一行中的各个值作为带名称的参数传入,并将列名称与参数名称对应起来。

library(purrr)

params_df <- data.frame(
  mean_val = c(0, 5, 10),
  sd_val = c(1, 2, 3),
  n = c(100, 50, 200)
)

# Generate samples from different normal distributions
set.seed(42)
results <- pmap(params_df, function(mean_val, sd_val, n) {
  rnorm(n, mean=mean_val, sd=sd_val)
})

map_dbl(results, mean)

遍历参数网格

pmap() 的一个常见用途是遍历模型参数网格。使用 expand.grid() 或 tidyr::expand_grid() 创建参数组合,然后使用 pmap() 为每一行运行一次实验。

library(purrr)

# Create a parameter grid
grid <- expand.grid(
  learning_rate = c(0.01, 0.1),
  epochs = c(10, 50),
  batch_size = c(32, 64)
)

# Simulate model training results
set.seed(42)
grid$accuracy <- pmap_dbl(grid, function(learning_rate, epochs, batch_size) {
  base <- 0.5 + 0.3 * log10(epochs) + 0.1 * learning_rate
  round(min(base + rnorm(1, 0, 0.02), 0.99), 3)
})

print(grid[order(-grid$accuracy),])

map2_df() — 构建数据框

map2_df()(或 map2_dfr())会对两个并行输入应用函数,并将生成的数据框按行绑定。这对于比较两组项目或合并并行数据集非常有用。

library(purrr)

q1_sales <- list(East=100, West=200, North=80)
q2_sales <- list(East=120, West=190, North=95)

map2_df(q1_sales, q2_sales, function(q1, q2) {
  data.frame(
    q1 = q1,
    q2 = q2,
    change = q2 - q1,
    pct = round(100*(q2-q1)/q1, 1)
  )
}, .id = 'region')

将 pmap() 与 nest() 结合

将 pmap() 与嵌套数据框及模型参数结合,可以实现一种强大的模式:将超参数和数据存储在同一个数据框中,然后使用 pmap() 遍历各行,使用每组参数拟合模型。

library(purrr)
library(dplyr)

# Simulate polynomial regression with different degrees
set.seed(42)
x <- 1:20
y <- 2*x + 0.5*x^2 + rnorm(20, 0, 10)
df <- data.frame(x=x, y=y)

configs <- data.frame(degree = 1:3)
configs$r2 <- map_dbl(configs$degree, function(d) {
  m <- lm(y ~ poly(x, d), data=df)
  summary(m)$r.squared
})

print(configs)

使用 possibly() 处理错误

在 map2()/pmap() 工作流中,一个有问题的输入就可能导致整个操作失败。请使用 possibly(fn, otherwise=NA) 包装函数,以捕获错误并返回默认值,即使部分输入失败,也能让处理流程继续运行。

library(purrr)

safe_log <- possibly(log, otherwise=NA_real_)

values <- list(10, -5, 100, 0, 50)

# Without possibly(): log() warns for negative numbers
# With possibly(): errors return NA, computation continues
map_dbl(values, safe_log)

实践:使用 pmap() 进行交叉验证

pmap() 的一个实际应用是通过遍历折叠分配来执行 k 折交叉验证。pmap() 会将每一折的训练数据和测试数据传递给拟合函数,并为每一折返回一个性能指标。

library(purrr)

set.seed(42)
df <- data.frame(x=1:20, y=2*(1:20)+rnorm(20,0,3))
df$fold <- sample(rep(1:5, 4))

# Compute RMSE for each held-out fold
folds <- 1:5
rmse_vals <- map_dbl(folds, function(k) {
  train <- df[df$fold != k, ]
  test <- df[df$fold == k, ]
  m <- lm(y ~ x, data=train)
  preds <- predict(m, newdata=test)
  sqrt(mean((test$y - preds)^2))
})

cat('Per-fold RMSE:', round(rmse_vals, 2), '\n')
cat('Mean RMSE:', round(mean(rmse_vals), 2))

快速检查

如果要同时遍历两个以上的并行输入向量,应该使用哪个 purrr 函数?

回顾:map2() 与 pmap()

关于多输入映射的要点:

  • map2(.x, .y, .f) — 遍历两个并行输入;在 ~ 简写中使用 .x 和 .y
  • map2_dbl/chr/lgl/int/df() — 保证返回类型安全的类型变体
  • pmap(.l, .f) — 同时遍历包含任意数量向量的列表
  • 将数据框传递给 pmap() — 列名称会成为函数参数名称
  • 使用 expand.grid() + pmap() 搜索参数网格
  • 使用 possibly(fn, NA) 包装函数,以安全地处理错误
library(purrr)

# pmap with a data frame: each row = one function call
params <- data.frame(
  mean_val = c(0, 10, 100),
  sd_val = c(1, 5, 20),
  label = c('control','mid','high')
)

pmap_chr(params, function(mean_val, sd_val, label) {
  sprintf('%s: mean=%.0f, sd=%.0f', label, mean_val, sd_val)
})

常见问题解答

「使用 map2() 和 pmap() 处理多个输入」课时是免费的吗?

是的 — 「使用 map2() 和 pmap() 处理多个输入」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 R Academy 课程的其余内容,请升级到 CoddyKit PRO。 R Academy 课程共包含 4 节课。

「使用 map2() 和 pmap() 处理多个输入」这节课中我会学到什么?

使用 map2() 和 pmap() 同时遍历两个或更多列表 你通过在浏览器中直接运行的动手代码来练习 R Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 R Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 R Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「使用 map2() 和 pmap() 处理多个输入」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 R Academy 课中编写并运行代码吗?

能。每节 R Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. map() 与类型化变体
  2. 使用 map2() 和 pmap() 处理多个输入
  3. reduce()、accumulate() 与 walk()
  4. keep()、discard() 与列表筛选
← 返回 R Academy