keep(), discard() и фильтрация списков
Фильтруйте элементы списков функциями-предикатами для чистых конвейеров обработки данных.
«keep(), discard() и фильтрация списков» — бесплатный урок R Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения 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(). Используйте её, чтобы отфильтровать из списка значения NULL, 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))Быстрая проверка
Что возвращает detect(.x, .p), если ни один элемент в .x не удовлетворяет предикату .p?
Итоги: функции фильтрации списков
Главное о средствах purrr для фильтрации списков:
keep(.x, .p)— оставляет элементы, для которых предикат равен TRUEdiscard(.x, .p)— удаляет элементы, для которых предикат равен TRUEcompact(.x)— удаляет все элементы NULLdetect(.x, .p)— возвращает первый элемент, соответствующий предикату (или NULL)detect_index(.x, .p)— возвращает индекс первого совпадения (или 0L)every(.x, .p)— возвращает TRUE, если соответствуют все элементыsome(.x, .p)— возвращает TRUE, если соответствует хотя бы один элементnone(.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() и фильтрация списков» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс R Academy, подпишись на CoddyKit PRO. Курс R Academy содержит 4 уроков всего.
Чему я научусь в уроке «keep(), discard() и фильтрация списков»?
Фильтруйте элементы списков функциями-предикатами для чистых конвейеров обработки данных. Ты практикуешь R Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать R Academy?
Предыдущий опыт не требуется. R Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «keep(), discard() и фильтрация списков»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке R Academy?
Да. Каждый урок R Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- map() и типизированные варианты
- map2() и pmap() для нескольких входных данных
- reduce(), accumulate() и walk()
- keep(), discard() и фильтрация списков