#r #tidyverse
#r #tidyverse
Вопрос:
У меня есть запрос к базе данных, выполняемый из R, в котором может быть несколько столбцов, заполненных NA, некоторые из этих столбцов мне нужны дальше по строке, но один конкретный столбец может быть удален, если все значения NA.
Я обычно использую purrr::discard(~все (is.na (.))) чтобы удалить столбцы, которые все являются NA, но поскольку этот фрейм данных может содержать несколько столбцов с NA, где я хочу удалить только один, я изо всех сил пытаюсь сделать это специфичным для столбца в решении tidyverse.
В настоящее время у меня есть этот обходной путь:
if(sum(is.na(Orders$Originator)) == nrow(Orders)) {
Orders <- Orders %>%
select(-Originator)
}
Но это улучшило бы читаемость, если бы я мог использовать это в решении tidyverse. Надеюсь, кто-нибудь может помочь!
Спасибо!
Ответ №1:
Канонический способ tidyverse решить эту проблему — использовать функцию предиката, используемую внутри select(where(...))
, и объединить это с выбором по имени переменной.
Сначала мы могли бы написать пользовательскую функцию предиката для использования, в where
которой выбираются только столбцы, содержащие только NA
s.
# custom predicate function
all_na <- function(x) {
all(is.na(x))
}
Мы можем использовать эту функцию вместе с логическим выражением, говорящим, что мы не хотим select
y
, если (читать И amp;
) это all_na
:
library(dplyr)
df <- data.frame(
x = c(1,2,NA),
y = NA,
z = c(3,4,5)
)
df %>%
select(!(y amp; where(all_na)))
#> x z
#> 1 1 3
#> 2 2 4
#> 3 NA 5
Чтобы проверить, действительно ли это работает, давайте переопределим y
, чтобы он содержал не только NA
s, и мы увидим, что на этот раз он не отменен:
df2 <- data.frame(
x = c(1,2,NA),
y = c(1,2,NA),
z = c(3,4,5)
)
df2 %>%
select(!(y amp; where(all_na)))
#> x y z
#> 1 1 1 3
#> 2 2 2 4
#> 3 NA NA 5
Вместо пользовательской функции мы можем использовать лямбда-функцию внутри where
:
df %>%
select(!(y amp; where(~ all(is.na(.x)))))
Создано 2021-12-07 пакетом reprex (версия 0.3.0)
В большем tidyverse мы могли бы также использовать purrr::lmap_at
и выбрать y
с .at
аргументом, а затем создать лямбда-функцию, в которой говорится, что если all(is.na(.x))
затем использовать пустой list()
(= удалить столбец), в противном случае сохранить столбец .x
:
library(purrr)
library(dplyr)
df %>%
lmap_at("y", ~ if(all(is.na(.x))) list() else .x)
#> # A tibble: 3 x 2
#> x z
#> <dbl> <dbl>
#> 1 1 3
#> 2 2 4
#> 3 NA 5
Создано 2021-12-07 пакетом reprex (v2.0.1)
Ответ №2:
Использование данных примера:
df <- data.frame(
x = c(1,2,NA),
y = NA,
z = c(3,4,5)
)
Здесь столбец y
является целевым столбцом для проверки all
is.na
. Ваш if
и else
будет содержаться в фигурных скобках. Фигурные скобки не позволят каналу использовать первый аргумент в функции. Обратите else
внимание, что ваш фрейм данных будет сохранен в канале, если условие равно false.
library(tidyverse)
df %>%
{ if (all(is.na(.$y))) select(., -y) else . }
Вывод
x z
1 1 3
2 2 4
3 NA 5
Комментарии:
1. Не знал, что мы можем интегрировать подобные функции. Работает как шарм! Большое спасибо!
Ответ №3:
Кажется, вы пытаетесь смешать выделение по имени (т. Е. Только По Определенному столбцу) и по логическому. Оба могут быть выполнены очень хорошо отдельно в tidyverse (используйте аккуратные селекторы или where
), но я не уверен, как их объединить!
Итак, вот грязное решение, которое не использует ни:
library(dplyr, warn.conflicts = FALSE)
df <- data.frame(
x = c(1,2,NA),
y = NA,
y2 = NA,
z = c(3,4,5)
)
df %>%
select(-which(colnames(.)=="y" amp; sapply(., (x) all(is.na(x)))))
#> x y2 z
#> 1 1 NA 3
#> 2 2 NA 4
#> 3 NA NA 5
Создано 2021-12-07 пакетом reprex (v2.0.1)
Комментарии:
1. Проблема с этим решением заключается в том, что если вы добавите столбец, например a со всеми значениями NA, который также будет удален. У меня есть еще столбцы, которые могут содержать полные NA, но я хочу удалить только один.
2. хорошо, я обновил его.