Проблема с использованием rowwise () для подсчета количества NA в каждой строке кадра данных

У меня проблемы с использованием rowwise () для подсчета количества NA в каждой строке. Мой минимальный пример:

df <- data.frame(Q1 = c(rep(1, 1), rep(NA, 9)),
                 Q2 = c(rep(2, 2), rep(NA, 8)),
                 Q3 = c(rep(3, 3), rep(NA, 7))
)
df
   Q1 Q2 Q3
1   1  2  3
2  NA  2  3
3  NA NA  3
4  NA NA NA
5  NA NA NA
6  NA NA NA
7  NA NA NA
8  NA NA NA
9  NA NA NA
10 NA NA NA

Я хотел бы создать новый столбец, в котором будет подсчитано количество НП в каждой строке. Я могу сделать это очень просто, написав

 df$Count_NA <- rowSums(is.na(df))
 df
   Q1 Q2 Q3 Count_NA
1   1  2  3        0
2  NA  2  3        1
3  NA NA  3        2
4  NA NA NA        3
5  NA NA NA        3
6  NA NA NA        3
7  NA NA NA        3
8  NA NA NA        3
9  NA NA NA        3
10 NA NA NA        3

Но если я попытаюсь сделать это через dplyr с помощью rowwise (), я получу неправильный ответ - столбец Count_NA имеет одинаковый номер в каждой строке:

df %>%
   rowwise() %>%
   mutate(Count_NA = sum(is.na(.)))
# A tibble: 10 x 4
# Rowwise: 
      Q1    Q2    Q3 Count_NA
   <dbl> <dbl> <dbl>    <int>
 1     1     2     3       24
 2    NA     2     3       24
 3    NA    NA     3       24
 4    NA    NA    NA       24
 5    NA    NA    NA       24
 6    NA    NA    NA       24
 7    NA    NA    NA       24
 8    NA    NA    NA       24
 9    NA    NA    NA       24
10    NA    NA    NA       24

что я делаю не так и как это исправить?

Спасибо заранее

Томас Филипс


person Thomas Philips    schedule 18.04.2021    source источник
comment
Отвечает ли это на ваш вопрос? Подсчитывать NA на строку во фрейме данных   -  person TarJae    schedule 18.04.2021
comment
Вероятный дубликат: Что такое rowwise и c_across   -  person Ian Campbell    schedule 18.04.2021


Ответы (6)


Одна проблема заключается в том, что . здесь разрешается для всего кадра, а не только для всей строки. Другой метод dplyr, использующий c_across:

df %>%
    rowwise() %>%
    mutate(a=sum(is.na(c_across(everything()))))
# # A tibble: 10 x 4
# # Rowwise: 
#       Q1    Q2    Q3     a
#    <dbl> <dbl> <dbl> <int>
#  1     1     2     3     0
#  2    NA     2     3     1
#  3    NA    NA     3     2
#  4    NA    NA    NA     3
#  5    NA    NA    NA     3
#  6    NA    NA    NA     3
#  7    NA    NA    NA     3
#  8    NA    NA    NA     3
#  9    NA    NA    NA     3
# 10    NA    NA    NA     3

Самая большая разница, которую я вижу между использованием этого и cur_data(), заключается в том, что c_across позволяет более напрямую выбирать переменную, как в c_across(starts_with("Q")). Конечно, всегда можно select(cur_data(),...), так что это слабый аргумент.

person r2evans    schedule 18.04.2021
comment
Спасибо - как определить, что это за файл. разрешает? - person Thomas Philips; 18.04.2021
comment
Честно говоря, я сделал func <- function(z, ...) {browser();0;}, а затем df %>% rowwise() %>% mutate(Count_NA = func(.)), потом посмотрел на z, чтобы увидеть. Это не помогает решить проблему, просто чтобы ее идентифицировать. - person r2evans; 18.04.2021
comment
Ответ @Grothendieck является каноническим в том смысле, что в этом ответе используется cur_data(). Этот ответ является каноническим в том смысле, что в нем используется c_across. Тщательно выбирать. Я склонен полагать, что c_across был предназначен для такого рода вещей, но я уверен, что есть и контрпримеры. - person r2evans; 18.04.2021
comment
@ r2evans, к сведению, сравнение скорости - person Waldi; 18.04.2021
comment
@Waldi, я не сказал, что мне нравится c_across, просто это канонически то, что (я думаю) является рекомендацией для таких задач. Я действительно стараюсь избегать построчных операций в dplyr глаголах: большинство моих данных, как правило, имеют 100 КБ или более строк, поэтому меня заставили избегать этого :-) - person r2evans; 18.04.2021
comment
@ r2evans, мне действительно нравится ваш ответ, но меня удивило сравнение скорости. Я также работаю с ›100 тыс. Таблиц и оставил dplyr на data.table. - person Waldi; 18.04.2021

baseR ответ

df$Count_NA <- apply(df, 1, function(x) sum(is.na(x)))                 

df
   Q1 Q2 Q3 Count_NA
1   1  2  3        0
2  NA  2  3        1
3  NA NA  3        2
4  NA NA NA        3
5  NA NA NA        3
6  NA NA NA        3
7  NA NA NA        3
8  NA NA NA        3
9  NA NA NA        3
10 NA NA NA        3

Так может быть интегрирован в трубу dplyr

df %>% mutate(count_NA = apply(., 1, function(x) sum(is.na(x))))

   Q1 Q2 Q3 count_NA
1   1  2  3        0
2  NA  2  3        1
3  NA NA  3        2
4  NA NA NA        3
5  NA NA NA        3
6  NA NA NA        3
7  NA NA NA        3
8  NA NA NA        3
9  NA NA NA        3
10 NA NA NA        3
person AnilGoyal    schedule 18.04.2021
comment
К вашему сведению, сравнение скорости - person Waldi; 18.04.2021
comment
Спасибо, что отметили меня. Отличная информация. TFS - person AnilGoyal; 18.04.2021

Используйте cur_data() вместо точки. .[cur_group_id(), ], c(Q1, Q2, Q3), across() или c_across() (или c_across с аргументом в соответствии с другим ответом) также будут работать.

Обратите внимание, что лучше всего использовать ungroup после этого, иначе он сохранит память построчно, и вы можете получить неожиданные результаты позже.

df %>%
   rowwise() %>%
   mutate(Count_NA = sum(is.na(cur_data()))) %>%
   ungroup

давая:

# A tibble: 10 x 4
      Q1    Q2    Q3 Count_NA
   <dbl> <dbl> <dbl>    <int>
 1     1     2     3        0
 2    NA     2     3        1
 3    NA    NA     3        2
 4    NA    NA    NA        3
 5    NA    NA    NA        3
 6    NA    NA    NA        3
 7    NA    NA    NA        3
 8    NA    NA    NA        3
 9    NA    NA    NA        3
10    NA    NA    NA        3
person G. Grothendieck    schedule 18.04.2021
comment
Гротендик, к сведению, сравнение скорости - person Waldi; 18.04.2021

На случай, если в будущем вас заинтересует построчное решение с purrr пакетными функциями:

library(purrr)

df %>%
  mutate(Count_NA = pmap(., ~ sum(is.na(c(...)))))


   Q1 Q2 Q3 Count_NA
1   1  2  3        0
2  NA  2  3        1
3  NA NA  3        2
4  NA NA NA        3
5  NA NA NA        3
6  NA NA NA        3
7  NA NA NA        3
8  NA NA NA        3
9  NA NA NA        3
10 NA NA NA        3

person Anoushiravan R    schedule 18.04.2021
comment
Вы можете использовать pmap для гораздо более сложных строковых операций с несколькими переменными: если вам интересно, вы можете проверить это: stackoverflow.com/questions/66935005/ - person Anoushiravan R; 18.04.2021
comment
К вашему сведению, сравнение скорости - person Waldi; 18.04.2021
comment
@ Вальди, это интересное сравнение. Но обычно я предпочитаю использовать pmap для более сложных построчных операций. Нельзя отрицать, что rowSums проще и быстрее всех. - person Anoushiravan R; 18.04.2021
comment
Мне нравится ваше решение, и я также люблю purrr, но microbenchmark всегда полезно найти наиболее эффективный способ. - person Waldi; 18.04.2021
comment
@Waldi Большое спасибо. Ты прав. Поскольку я начал изучать R всего лишь год, я уделял больше внимания сначала получению желаемого результата, а затем опробованию альтернативных решений. Однако в этом случае я пытался предложить решение, отличное от уже опубликованных. Также интересно, что есть пакет под названием bench, который делает то же самое, что и microbenchmark, и я пока не знаю, чем он отличается от последнего. - person Anoushiravan R; 18.04.2021

rowSums работает напрямую с mutate без rowwise:

df %>% mutate(count_NA = rowSums(is.na(.)))

   Q1 Q2 Q3 count_NA
1   1  2  3        0
2  NA  2  3        1
3  NA NA  3        2
4  NA NA NA        3
5  NA NA NA        3
6  NA NA NA        3
7  NA NA NA        3
8  NA NA NA        3
9  NA NA NA        3
10 NA NA NA        3

Обратите внимание, что ваше первоначальное решение на сегодняшний день является самым быстрым:

microbenchmark::microbenchmark(
  df$Count_NA <- rowSums(is.na(df)),
  df$Count_NA <- apply(df, 1, function(x) sum(is.na(x))),
  df %>% mutate(count_NA = rowSums(is.na(.))),
  df %>%
    mutate(Count_NA = purrr::pmap(., ~ sum(is.na(c(...))))),
  df %>%
    rowwise() %>%
    mutate(a=sum(is.na(c_across(everything())))),
  df %>%
  rowwise() %>%
  mutate(Count_NA = sum(is.na(cur_data()))) %>%
  ungroup
)

Unit: microseconds
                                                                            expr     min       lq
                                               df$Count_NA <- rowSums(is.na(df))    39.8    64.30
                          df$Count_NA <- apply(df, 1, function(x) sum(is.na(x)))  1661.6  1868.40
                                     df %>% mutate(count_NA = rowSums(is.na(.)))  1181.7  1572.80
                   df %>% mutate(Count_NA = purrr::pmap(., ~sum(is.na(c(...)))))  4749.9  5190.35
             df %>% rowwise() %>% mutate(a = sum(is.na(c_across(everything())))) 29124.1 31148.50
 df %>% rowwise() %>% mutate(Count_NA = sum(is.na(cur_data()))) %>%      ungroup 70473.0 73659.70
      mean   median       uq     max neval   cld
    79.033    76.25    88.75   174.0   100 a    
  2082.960  1966.50  2075.75  8777.3   100  b   
  1722.178  1676.20  1791.60  3112.9   100  b   
  5726.549  5396.40  5745.25 28592.1   100   c  
 33567.825 31983.05 33637.00 54676.9   100    d 
 77902.342 76492.85 81199.15 98942.1   100     e
Unit: microseconds
                                                                            expr     min       lq
                                               df$Count_NA <- rowSums(is.na(df))    38.2    44.95
                          df$Count_NA <- apply(df, 1, function(x) sum(is.na(x)))  1584.8  1765.30
                                     df %>% mutate(count_NA = rowSums(is.na(.)))  1247.9  1496.95
                   df %>% mutate(Count_NA = purrr::pmap(., ~sum(is.na(c(...)))))  4614.0  5110.50
 df %>% rowwise() %>% mutate(Count_NA = sum(is.na(cur_data()))) %>%      ungroup 67413.5 70865.45
      mean   median       uq      max neval cld
    71.159    65.85    84.40    162.2   100 a  
  1967.629  1894.45  2093.30   3436.6   100 ab 
  1814.193  1666.25  1895.35   9031.0   100 a  
  5796.483  5380.70  5665.10  15309.7   100  b 
 78309.807 75275.30 79776.40 286964.3   100   c
person Waldi    schedule 18.04.2021
comment
Думаю, меня больше всего удивляет неэффективность cur_data(). - person Ian Campbell; 18.04.2021
comment
Я начал задавать этот вопрос, потому что у меня хорошо работали решения rowSums и apply (), и я пытался переместить свое решение с базового R на dplyr. Теперь, когда я вижу ваши сравнения скорости, я придерживаюсь базового R! Тем не менее, я многому научился из этого обсуждения. Всем спасибо. - person Thomas Philips; 19.04.2021

Использование dapply

library(collapse)
dapply(df, function(x) sum(is.na(x)), MARGIN = 1)
#[1] 0 1 2 3 3 3 3 3 3 3
person akrun    schedule 18.04.2021