keep()、discard() 与列表筛选
使用谓词函数筛选列表元素,构建整洁的数据处理管道
keep()、discard() 与列表筛选 是 CoddyKit 上的免费 R Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 R Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 R Academy 课程共包含 4 节课。
使用 purrr 过滤列表
purrr 提供了一系列函数,用于根据谓词对列表进行过滤、搜索和测试。谓词是一个返回 TRUE 或 FALSE 的函数。这些函数可以替代冗长的 Filter() 调用和条件循环。
library(purrr)
# A mixed list to filter
mixed <- list(1L, 'hello', 3.14, NULL, TRUE, 42L, 'world', NULL)
cat('Total elements:', length(mixed), '\n')
cat('Non-NULL elements:', length(compact(mixed)))keep() — 保留符合谓词的元素
keep(.x, .p) 只保留 .x 中谓词 .p 返回 TRUE 的元素。它与 discard() 的作用相反。谓词可以是函数名称、公式或逻辑向量。
library(purrr)
mixed <- list(1L, 'hello', 3.14, TRUE, 42L, 'world', 2.7)
# Keep only numeric values
numeric_only <- keep(mixed, is.numeric)
print(numeric_only)
# Keep values greater than 5
keep(mixed, ~is.numeric(.x) && .x > 5)discard() — 移除符合谓词的元素
discard(.x, .p) 会移除谓词返回 TRUE 的元素,保留其余元素。它与 keep() 相反。您可以使用它从列表中过滤掉空值、NA 或不需要的类型。
library(purrr)
results <- list(
model_a = list(rmse=2.5, converged=TRUE),
model_b = list(rmse=NA, converged=FALSE),
model_c = list(rmse=1.8, converged=TRUE),
model_d = list(rmse=NA, converged=FALSE)
)
# Discard models that did not converge
converged_models <- discard(results, ~!.x$converged)
map_dbl(converged_models, ~.x$rmse)compact() — 移除 NULL 元素
compact(.x) 会从列表中移除所有 NULL 元素,相当于 discard(.x, is.null),但写法更加简洁。在 map() 调用可能对某些输入返回 NULL 时,这个函数尤其有用。
library(purrr)
# Simulate fetching data that might return NULL
ids <- 1:6
fetch_data <- function(id) {
if (id %% 2 == 0) NULL else list(id=id, value=id*10)
}
raw_results <- map(ids, fetch_data)
cat('With NULLs:', length(raw_results), '\n')
clean_results <- compact(raw_results)
cat('Without NULLs:', length(clean_results))detect() — 查找第一个匹配项
detect(.x, .p) 返回满足谓词的第一个元素。如果没有元素匹配,则返回 NULL。使用 detect(.right=TRUE) 可以从右侧开始搜索并找到最后一个匹配项。
library(purrr)
prices <- list(15.99, 8.50, 23.99, 5.25, 19.99, 4.75)
# Find first price under $10
first_cheap <- detect(prices, ~.x < 10)
cat('First price under $10:', first_cheap, '\n')
# Find last price under $10
last_cheap <- detect(prices, ~.x < 10, .right=TRUE)
cat('Last price under $10:', last_cheap)detect_index() — 查找第一个匹配项的位置
detect_index(.x, .p) 返回第一个满足谓词的元素的索引(如果没有找到,则返回 0L)。当您需要的是位置而不是值时,这很有用,例如更新或提取该元素。
library(purrr)
models <- list(
list(name='lm', rmse=3.5),
list(name='rf', rmse=2.1),
list(name='xgb', rmse=1.8),
list(name='svm', rmse=2.4)
)
# Find the index of the best model (lowest RMSE)
best_idx <- detect_index(models, function(m) m$rmse < 2.0)
cat('Best model index:', best_idx, '\n')
cat('Best model name:', models[[best_idx]]$name)every() — 所有元素都匹配吗?
如果每个元素都满足谓词,every(.x, .p) 就返回 TRUE。遇到第一个 FALSE 时会立即停止计算。它等价于 all(map_lgl(.x, .p)),但采用惰性求值。
library(purrr)
batch_results <- list(
list(status='success', rows=150),
list(status='success', rows=200),
list(status='success', rows=175)
)
failed_batch <- list(
list(status='success', rows=150),
list(status='error', rows=0),
list(status='success', rows=175)
)
cat('All success (good):', every(batch_results, ~.x$status == 'success'), '\n')
cat('All success (bad):', every(failed_batch, ~.x$status == 'success'))some() — 是否有元素匹配?
如果至少一个元素满足谓词,some(.x, .p) 就返回 TRUE。遇到第一个 TRUE 时会立即停止计算。它等价于 any(map_lgl(.x, .p)),但采用惰性求值。
library(purrr)
datasets <- list(
data.frame(x=1:3, y=c(1,NA,3)),
data.frame(x=4:6, y=7:9),
data.frame(x=7:9, y=c(NA,NA,NA))
)
# Does any dataset contain NAs?
cat('Any NAs:', some(datasets, anyNA), '\n')
# Is every dataset NA-free?
cat('All clean:', every(datasets, ~!anyNA(.x)))none() — 没有元素匹配吗?
如果没有任何元素满足谓词,none(.x, .p) 就返回 TRUE。它等价于 !some(.x, .p),但在需要断言某个条件不存在的验证场景中,可读性更好。
library(purrr)
api_responses <- list(
list(code=200, data='OK'),
list(code=200, data='OK'),
list(code=201, data='Created')
)
# Ensure no errors occurred
no_errors <- none(api_responses, ~.x$code >= 400)
cat('No errors:', no_errors, '\n')
# Check another batch
bad_batch <- c(api_responses, list(list(code=500, data='Error')))
none(bad_batch, ~.x$code >= 400)将 keep() 与 map() 结合使用
将 keep()(或 discard())与 map() 链式结合是一种常见模式:先筛选出列表中相关的元素,再应用转换。这样可以清晰地将筛选逻辑与处理逻辑分开。
library(purrr)
model_results <- list(
list(name='A', converged=TRUE, rmse=2.5),
list(name='B', converged=FALSE, rmse=NA),
list(name='C', converged=TRUE, rmse=1.8),
list(name='D', converged=TRUE, rmse=3.1)
)
# Keep converged, extract RMSE
model_results %>%
keep(~.x$converged) %>%
map_dbl(~.x$rmse)将 keep() 与 is.* 函数结合使用
R 内置的 is.* 谓词函数(is.numeric、is.character、is.null、is.na、is.finite)可以直接作为 keep() 和 discard() 的谓词使用,无需用 ~ 包装。
library(purrr)
mixed_env <- list(
count = 42L,
name = 'Alice',
ratio = 3.14,
flag = TRUE,
missing = NULL,
data = data.frame(x=1:3)
)
# Extract different types
numeric_items <- keep(mixed_env, is.numeric)
cat('Numeric items:', length(numeric_items), '\n')
# Remove NULL items
cleaned <- discard(mixed_env, is.null)
cat('After removing NULL:', length(cleaned))快速检查
当 .x 中没有任何元素满足谓词 .p 时,detect(.x, .p) 会返回什么?
回顾:列表筛选函数
purrr 列表筛选工具的要点:
keep(.x, .p)— 保留谓词为 TRUE 的元素discard(.x, .p)— 删除谓词为 TRUE 的元素compact(.x)— 删除所有 NULL 元素detect(.x, .p)— 返回第一个匹配谓词的元素(或 NULL)detect_index(.x, .p)— 返回第一个匹配项的索引(或 0L)every(.x, .p)— 如果所有元素都匹配,则为 TRUEsome(.x, .p)— 如果任意元素匹配,则为 TRUEnone(.x, .p)— 如果没有元素匹配,则为 TRUE
library(purrr)
scores <- list(45, 72, 88, 53, 91, 67, 39, 85)
passing <- keep(scores, ~.x >= 60)
failing <- discard(scores, ~.x >= 60)
cat('Passing:', length(passing), 'students\n')
cat('Failing:', length(failing), 'students\n')
cat('All pass:', every(scores, ~.x >= 60), '\n')
cat('Any fail:', some(scores, ~.x < 60), '\n')
cat('First pass score:', detect(scores, ~.x >= 60))常见问题解答
「keep()、discard() 与列表筛选」课时是免费的吗?
是的 — 「keep()、discard() 与列表筛选」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 R Academy 课程的其余内容,请升级到 CoddyKit PRO。 R Academy 课程共包含 4 节课。
「keep()、discard() 与列表筛选」这节课中我会学到什么?
使用谓词函数筛选列表元素,构建整洁的数据处理管道 你通过在浏览器中直接运行的动手代码来练习 R Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 R Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 R Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「keep()、discard() 与列表筛选」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 R Academy 课中编写并运行代码吗?
能。每节 R Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- map() 与类型化变体
- 使用 map2() 和 pmap() 处理多个输入
- reduce()、accumulate() 与 walk()
- keep()、discard() 与列表筛选