tidyr :: собрать na.rm с недостающими данными

Скажем, у меня есть несколько столбцов во фрейме данных, которые измеряют одну и ту же концепцию, но с использованием разных методов (например, существует несколько видов тестов IQ, и студенты могут иметь любой из них или вообще ни одного). Я хочу объединить различные методы в один столбец (очевидный вариант использования tidyr).

Если данные примерно такие:

mydata <- data.frame(ID = 55:64, 
                 age = c(12, 12, 14, 11, 20, 10, 13, 15, 18, 17),
                 Test1 = c(100, 90, 88, 115, NA, NA, NA, NA, NA, NA),
                 Test2 = c(NA, NA, NA, NA, 100, 120, NA, NA, NA, NA),
                 Test3 = c( NA, NA, NA, NA, NA, NA, 110, NA, 85, 150))

Я, естественно, хотел бы выполнить что-то вроде этого (обратите внимание, что я использую na.rm = TRUE, чтобы многие NA в моем наборе данных не получали свои собственные строки):

library(tidyr)
tests <- gather(mydata, key=IQSource, value=IQValue, c(Test1, Test2, Test3), na.rm = TRUE)
tests

Давая мне:

ID age IQSource IQValue 1 55 12 Test1 100 2 56 12 Test1 90 3 57 14 Test1 88 4 58 11 Test1 115 15 59 20 Test2 100 16 60 10 Test2 120 27 61 13 Test3 110 29 63 18 Test3 85 30 64 17 Test3 150

Проблема в том, что у меня есть студентка (ID = 62), у которой нет каких-либо оценок IQ ни по одному из трех, и я не хочу потерять другие ее данные (данные в Столбцы ID и возраста).

Есть ли способ отличить в tidyr, что да, я хочу удалить NA, где у меня есть данные, по крайней мере, в одном столбце, который я собираю, но в то же время хочу предотвратить потерю данных, когда все столбцы для сбора являются NA?)


person Joy    schedule 25.05.2017    source источник


Ответы (3)


Если каждый студент сможет пройти только один тест на IQ ...

library(tidyverse)

mydata %>%
  gather(key=IQSource, value=IQValue, Test1:Test3) %>%
  group_by(ID) %>%
  arrange(IQValue) %>%
  slice(1)
      ID   age IQSource IQValue
 1    55    12    Test1     100
 2    56    12    Test1      90
 3    57    14    Test1      88
 4    58    11    Test1     115
 5    59    20    Test2     100
 6    60    10    Test2     120
 7    61    13    Test3     110
 8    62    15    Test1      NA
 9    63    18    Test3      85
10    64    17    Test3     150

Если каждый студент сможет пройти несколько тестов на IQ ...

mydata %>%
  # Add an ID with multiple IQ tests
  bind_rows(data.frame(ID=65, age=13, Test1=100, Test2=100, Test3=NA)) %>%
  gather(key=IQSource, value=IQValue, Test1:Test3) %>%
  group_by(ID) %>%
  filter(!is.na(IQValue) | all(is.na(IQValue))) %>%
  filter(all(!is.na(IQValue)) | !duplicated(IQValue)) %>%
  arrange(ID, IQSource)
      ID   age IQSource IQValue
 1    55    12    Test1     100
 2    56    12    Test1      90
 3    57    14    Test1      88
 4    58    11    Test1     115
 5    59    20    Test2     100
 6    60    10    Test2     120
 7    61    13    Test3     110
 8    62    15    Test1      NA
 9    63    18    Test3      85
10    64    17    Test3     150
11    65    13    Test1     100
12    65    13    Test2     100
person eipi10    schedule 25.05.2017
comment
Я выбрал это как правильный ответ b / c простоты, соблюдения tidyverse и расширения за пределы исходного вопроса. Однако все полученные ответы были отличными и полезными! Спасибо всем! - person Joy; 26.05.2017

Я не нашел прямого решения, но вы могли right_join вернуть исходный data.frame, а затем отменить выбор всех столбцов, которые вам не нужны.

library(tidyr)
library(dplyr)

mydata %>% 
  gather(key, val, Test1:Test3, na.rm = T) %>%
  right_join(mydata) %>% 
  select(-contains("Test"))
#> Joining, by = c("ID", "age")
#>    ID age   key val
#> 1  55  12 Test1 100
#> 2  56  12 Test1  90
#> 3  57  14 Test1  88
#> 4  58  11 Test1 115
#> 5  59  20 Test2 100
#> 6  60  10 Test2 120
#> 7  61  13 Test3 110
#> 8  62  15  <NA>  NA
#> 9  63  18 Test3  85
#> 10 64  17 Test3 150

В качестве альтернативы вы, конечно, можете сначала создать data.frame со всеми переменными, которые хотите сохранить, а затем присоединиться к нему:

id_data <- select(mydata, ID, age)

mydata %>% 
  gather(key, val, Test1:Test3, na.rm = T) %>%
  right_join(id_data)
person Thomas K    schedule 25.05.2017
comment
Что ж, это намного лучше, чем у меня. Спасибо! - person svenhalvorson; 26.05.2017

Я думаю, это поможет вам:

    # make another data frame which has just ID and whether or not they missed all 3 tests
    missing = mydata %>% 
      mutate(allNA = is.na(Test1) & is.na(Test2) & is.na(Test3)) %>%
      select(ID, allNA)

    # Gather and keep NAs  
    tests <- gather(mydata, key=IQSource, value=IQValue, c(Test1, Test2, Test3), na.rm = FALSE)

    # Keep the rows that have a IQValue or missed all tests
    tests = left_join(tests, missing) %>% 
      filter(!is.na(IQValue) | allNA)
    # Remove duplicated rows of individuals who missed all exams
    tests = tests[!is.na(tests$IQValue) | !duplicated(tests[["ID"]]), ]
person svenhalvorson    schedule 25.05.2017