Удаление строк, содержащих NA в определенном столбце, только если строки дублируются (на основе двух других столбцов)

#r #na

#r #na

Вопрос:

У меня есть фрейм данных, который является результатом левого соединения. Примерные данные приведены ниже:

    P.I.D..     Status List.year CURRENT_LAND_VALUE CURRENT_IMPROVEMENT_VALUE
   <chr>       <chr>      <dbl>              <dbl>                     <dbl>
 1 003-913-627 X           2000                 NA                        NA
 2 003-913-627 T           2010            1578000                   1201000
 3 003-913-627 S           2018                 NA                        NA
 4 003-913-627 S           2018            2814000                    901000
 5 003-913-627 S           2002                 NA                        NA
 6 003-913-627 T           2007             390000                    282000
 7 003-913-627 T           2007             295000                    180000
 8 003-913-627 S           2008             464000                    391000
 9 003-913-627 S           2008             339000                    246000
10 003-913-627 X           2009             339000                    246000
11 003-913-627 X           2009             464000                    391000
 

Извините, я пытался использовать dput для генерации кода для данных, но когда я попытался, это дало мне какой-то несвязанный результат, который не представляет таблицу, показанную выше

Как видно для 2018 года и PID 003-913-627, показаны две строки. У одного есть номер для CURRENT_LAND_VALUE и CURRENT_IMPROVEMENT_VALUE, а одна строка включает NA . Что я хочу сделать, так это удалить строку, которая имеет значение NA, только если строка дублируется (что означает, что у нас есть другая строка с тем же PID и List.year. В некоторых случаях, таких как первая строка, поскольку нет такой же строки с PID 003-913-627 и List.Year 2000, NA не следует удалять. Ожидаемый результат для вышеуказанного фрейма данных:

    P.I.D..     Status List.year CURRENT_LAND_VALUE CURRENT_IMPROVEMENT_VALUE
   <chr>       <chr>      <dbl>              <dbl>                     <dbl>
 1 003-913-627 X           2000                 NA                        NA
 2 003-913-627 T           2010            1578000                   1201000
 4 003-913-627 S           2018            2814000                    901000
 5 003-913-627 S           2002                 NA                        NA
 6 003-913-627 T           2007             390000                    282000
 7 003-913-627 T           2007             295000                    180000
 8 003-913-627 S           2008             464000                    391000
 9 003-913-627 S           2008             339000                    246000
10 003-913-627 X           2009             339000                    246000
11 003-913-627 X           2009             464000                    391000
 

В заключение: я хочу удалить строки, содержащие NA в «CURRENT_LAND_VALUE» и «CURRENT_IMPROVEMENT_VALUE», только если уже есть строка с такими же «PID» и «List.Year», которая имеет фактическое значение для «CURRENT_IMPROVEMENT_VALUE» или «CURRENT_LAND_VALUE»

как я могу это сделать?

Ответ №1:

summarise Здесь можно использовать группировку after, поскольку dplyr version >= 1.0 может возвращать более одной строки на группу. Здесь мы можем использовать столбцы группировки, а затем выполнить summarise across числовые столбцы, чтобы вернуть элементы, отличные от NA, если хотя бы один из них не является NA, или вернуть NA

 library(dplyr)
df1 %>%
   group_by(`P.I.D..`, Status, List.year) %>%
   summarise(across(where(is.numeric),  
     ~ if(all(is.na(.))) NA_real_ else .[complete.cases(.)]), .groups = 'drop')
 

-вывод

 # A tibble: 10 x 5
#   P.I.D..     Status List.year CURRENT_LAND_VALUE CURRENT_IMPROVEMENT_VALUE
#   <chr>       <chr>      <int>              <dbl>                     <dbl>
# 1 003-913-627 S           2002                 NA                        NA
# 2 003-913-627 S           2008             464000                    391000
# 3 003-913-627 S           2008             339000                    246000
# 4 003-913-627 S           2018            2814000                    901000
# 5 003-913-627 T           2007             390000                    282000
# 6 003-913-627 T           2007             295000                    180000
# 7 003-913-627 T           2010            1578000                   1201000
# 8 003-913-627 X           2000                 NA                        NA
# 9 003-913-627 X           2009             339000                    246000
#10 003-913-627 X           2009             464000                    391000
 

Подробности — across циклы по нескольким столбцам. Внутри него первым выражением могут быть интересующие столбцы. Мы могли бы использовать everything() , если все остальные столбцы должны быть циклическими или выражение для проверки type столбца, и если оно соответствует только циклу ( where(is.numeric) ) , тогда мы создаем выражение lamdba ( ~ эквивалент функции (x)) и используем некоторое условие if/else . Возможно, это не требуется, но это просто вариант предотвращения сбоя, когда в некоторых столбцах есть только NA . В else , мы подмножествуем столбец элементами, отличными от NA ( .[complete.cases(.)] ) .

Предполагается, что он вернет одинаковую длину для каждого столбца, или же может быть заключен в list

данные

 df1 <- structure(list(P.I.D.. = c("003-913-627", "003-913-627", "003-913-627", 
"003-913-627", "003-913-627", "003-913-627", "003-913-627", "003-913-627", 
"003-913-627", "003-913-627", "003-913-627"), Status = c("X", 
"T", "S", "S", "S", "T", "T", "S", "S", "X", "X"), List.year = c(2000L, 
2010L, 2018L, 2018L, 2002L, 2007L, 2007L, 2008L, 2008L, 2009L, 
2009L), CURRENT_LAND_VALUE = c(NA, 1578000L, NA, 2814000L, NA, 
390000L, 295000L, 464000L, 339000L, 339000L, 464000L), 
CURRENT_IMPROVEMENT_VALUE = c(NA, 
1201000L, NA, 901000L, NA, 282000L, 180000L, 391000L, 246000L, 
246000L, 391000L)), class = "data.frame", row.names = c("1", 
"2", "3", "4", "5", "6", "7", "8", "9", "10", "11"))
 

Комментарии:

1. Я пытаюсь понять это замечательное выражение для summarise ; не могли бы вы объяснить его поведение? Я никогда не видел ничего подобного.

2. Спасибо, акрун. Я скопировал ваш код, но получил ошибку, касающуюся круглых скобок. Поскольку ваш код намного сложнее, чем я знаю, я не был уверен, где мне следует добавить круглые скобки. Не могли бы вы, пожалуйста, помочь с этим? Кроме того, я был бы признателен, если бы вы могли объяснить подробнее, чтобы я мог понять, как работает этот код

3. @Roozbeh_you Я проверил еще раз, скопировав / вставив и запустив код. Это работает на меня. Можете ли вы показать свой packageVersion('dplyr') . Я бы использовал >= 1.0 для dplyr

4. @акрун. [1] ‘1.0.2’ так что, я думаю, с этой стороны все в порядке. Не уверен, что когда я копирую и вставляю его, он отображается как unmatched opening brackets

5. @Roozbeh_you Я пробовал пару раз, но это работает

Ответ №2:

Я подозреваю, что это связано с тем, как вы соединяете два фрейма данных, но, не имея возможности их создать, моя первая мысль заключается в том, что вы могли бы сделать что-то вроде:

 df %>% 
  distinct(`P.I.D.`, CURRENT_LAND_VALUE, .keep_all = TRUE) %>% 
  filter(!is.na(CURRENT_LAND_VALUE))
 

Это distinct() даст вам различные входные данные с комбинациями P.I.D столбца и CURRENT_LAND_VALUE столбца (таким образом, если он дублируется, вы получите NA и фактическое значение в виде отдельных строк), а затем оттуда вы можете безопасно удалить любые NA значения (предполагая NA , что s только из дублирования, и это не где-то еще в данные).

Если NA s встречаются естественным образом, это будет сложнее, и я бы сосредоточился на преобразовании full_join в другой тип соединения.

РЕДАКТИРОВАТЬ: я только что заметил, что естественные NA s существуют. Тогда мое решение менее полезно.

Ответ №3:

Базовое решение R:

 subset(within(df, {
  pk <- as.integer(as.factor(paste0(P.I.D.., List.year)))
  pk_cnt <- ave(pk, pk, FUN = length)
}), pk_cnt == 1 | !(is.na(CURRENT_LAND_VALUE) amp; is.na(CURRENT_IMPROVEMENT_VALUE)),
select = names(df))
 

Данные:

    df <-  structure(list(P.I.D.. = c("003-913-627", "003-913-627", "003-913-627", 
    "003-913-627", "003-913-627", "003-913-627", "003-913-627", "003-913-627", 
    "003-913-627", "003-913-627", "003-913-627"), Status = c("X", 
    "T", "S", "S", "S", "T", "T", "S", "S", "X", "X"), List.year = c(2000L, 
    2010L, 2018L, 2018L, 2002L, 2007L, 2007L, 2008L, 2008L, 2009L, 
    2009L), CURRENT_LAND_VALUE = c(NA, 1578000L, NA, 2814000L, NA, 
    390000L, 295000L, 464000L, 339000L, 339000L, 464000L), CURRENT_IMPROVEMENT_VALUE = c(NA, 
    1201000L, NA, 901000L, NA, 282000L, 180000L, 391000L, 246000L, 
    246000L, 391000L)), row.names = c(NA, -11L), class = "data.frame")
 

Ответ №4:

Вы можете сохранить те строки, которые имеют только одну строку в группе или не имеют NA значения в обоих CURRENT_LAND_VALUE и CURRENT_IMPROVEMENT_VALUE .

 library(dplyr)

df %>%
  group_by(P.I.D.., List.year) %>%
  filter(n() == 1 | !(is.na(CURRENT_LAND_VALUE) amp; is.na(CURRENT_IMPROVEMENT_VALUE)))

#    P.I.D..     Status List.year CURRENT_LAND_VALUE CURRENT_IMPROVEMENT_VALUE
#   <chr>       <chr>      <int>              <int>                     <int>
# 1 003-913-627 X           2000                 NA                        NA
# 2 003-913-627 T           2010            1578000                   1201000
# 3 003-913-627 S           2018            2814000                    901000
# 4 003-913-627 S           2002                 NA                        NA
# 5 003-913-627 T           2007             390000                    282000
# 6 003-913-627 T           2007             295000                    180000
# 7 003-913-627 S           2008             464000                    391000
# 8 003-913-627 S           2008             339000                    246000
# 9 003-913-627 X           2009             339000                    246000
#10 003-913-627 X           2009             464000                    391000