0Pricing
R Academy · Урок

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) — оставляет элементы, для которых предикат равен TRUE
  • discard(.x, .p) — удаляет элементы, для которых предикат равен TRUE
  • compact(.x) — удаляет все элементы NULL
  • detect(.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 — локальная установка не требуется.

Все уроки этого курса

  1. map() и типизированные варианты
  2. map2() и pmap() для нескольких входных данных
  3. reduce(), accumulate() и walk()
  4. keep(), discard() и фильтрация списков
← Назад к R Academy