Создать новый столбец в dplyr, добавив значения в список из других столбцов?

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

Предположим, кадр данных newdata:

col1 col2 col3 col4
dog  cat  NA   NA
NA   cat  foo  bar
dog  NA   NA   NA
NA   cat  NA   NA

Вот мой желаемый результат с новым столбцом newCol:

col1 col2 col3 col4 newCol
dog  cat  NA   NA   (dog, cat)
NA   cat  foo  bar  (cat, foo, bar)
dog  NA   NA   NA   (dog)
NA   cat  NA   bar  (cat, bar)

Я пробовал использовать ifelse в mutate и case_when в mutate, но оба не позволяют конкатенацию в список. Вот моя (неудачная) попытка с case_when:

newdata = newdata %>% mutate( 
    newCol = case_when(
        col1 == "dog" ~ c("dog"),
        col2 == "cat" ~ c(newCol, "cat"),
        col3 == "foo" ~ c(newCol, "foo"),
        col4 == "bar" ~ c(newcol, "dog")
        )
    )

Я попробовал аналогичный подход с оператором ifelse для каждого столбца, но также не смог добавить его в список.


person Sam    schedule 28.12.2017    source источник
comment
Возможный дубликат Как опустить NA при вставке значений нескольких столбцов вместе в R?   -  person Ronak Shah    schedule 29.12.2017


Ответы (3)


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

Мы показываем решения как для списка, так и для символьного столбца. Вопрос конкретно относится к списку, поэтому это предполагаемый желаемый результат, но если предполагалось, что newCol будет вектором символов, то мы также покажем это.

Это настолько легко сделать с помощью базовых функций, что мы сначала покажем это; тем не менее, мы повторяем это в tidyverse, хотя для этого требуется значительно больше кода.

1) base Мы можем использовать apply следующим образом:

reduce <- function(x) unname(x[!is.na(x)])
DF$newCol <- apply(DF, 1, reduce)

давая следующее, где newCol — это список, первым компонентом которого является c("dog", "cat") и т. д.

  col1 col2 col3 col4        newCol
1  dog  cat <NA> <NA>      dog, cat
2 <NA>  cat  foo  bar cat, foo, bar
3  dog <NA> <NA> <NA>           dog
4 <NA>  cat <NA> <NA>           cat
5 <NA> <NA> <NA> <NA>              

Последняя строка кода поочередно может быть:

DF$newCol <- lapply(split(DF, 1:nrow(DF)), reduce)

Вопрос относится к объединению в список, поэтому я предполагаю, что список требуется для newCol, но если требуется строка, вместо этого используйте это для уменьшения:

reduce_ch <- function(x) sprintf("(%s)", toString(x[!is.na(x)]))
apply(DF, 1, reduce_ch)

2) tidyverse или с помощью tpldyr/tidyr/tibble мы собираем его в длинную форму, удаляем NA, вкладываем, сортируем обратно в исходный порядок и связываем обратно с помощью DF.

library(dplyr)
library(tibble)
library(tidyr)

DF %>%
   rownames_to_column %>%
   gather(colName, Value, -rowname) %>%
   na.omit %>%
   select(-colName) %>%
   nest(Value, .key = newCol) %>%
   arrange(rowname) %>%
   left_join(cbind(DF %>% rownames_to_column), .) %>% 
   select(-rowname)

давая:

  col1 col2 col3 col4        newCol
1  dog  cat <NA> <NA>      dog, cat
2 <NA>  cat  foo  bar cat, foo, bar
3  dog <NA> <NA> <NA>           dog
4 <NA>  cat <NA> <NA>           cat
5 <NA> <NA> <NA> <NA>          NULL

Если требуется вывод символов, используйте это вместо этого:

DF %>%
   rownames_to_column %>%
   gather(colName, Value, -rowname) %>%
   select(-colName) %>%
   group_by(rowname) %>%
   summarize(newCol = sprintf("(%s)", toString(na.omit(Value)))) %>%
   ungroup %>%
   { cbind(DF, .) } %>%
   select(-rowname)

давая:

  col1 col2 col3 col4          newCol
1  dog  cat <NA> <NA>      (dog, cat)
2 <NA>  cat  foo  bar (cat, foo, bar)
3  dog <NA> <NA> <NA>           (dog)
4 <NA>  cat <NA> <NA>           (cat)
5 <NA> <NA> <NA> <NA>              ()

Примечание

Вход DF в воспроизводимой форме:

Lines <- "col1 col2 col3 col4
dog  cat  NA   NA
NA   cat  foo  bar
dog  NA   NA   NA
NA   cat  NA   NA
NA   NA   NA   NA"
DF <- read.table(text = Lines,  header = TRUE, as.is = TRUE)
person G. Grothendieck    schedule 29.12.2017
comment
Это именно то, что мне нужно. Благодарю вас! - person Sam; 29.12.2017

Решение с использованием na.omit() и paste() с аргументом collapse:

apply(newdata, 1, 
      function(x) paste0("(", paste(na.omit(x), collapse = ", "), ")"))
[1] "(dog, cat)" "(cat, foo, bar)" "(dog)" "(cat)"

Демо

person pogibas    schedule 28.12.2017

Это похоже на вариант использования для tidyr::unite. В конце вам все равно нужно будет выполнить очистку dplyr, но пока это должно работать.

library(tibble)
library(dplyr)
library(tidyr)

df <- tribble(~col1, ~col2, ~col3, ~col4,
              "dog", "cat", NA, NA,
              NA, "cat", "foo", "bar",
              "dog", NA, NA, NA,
              NA, "cat", NA, NA)

df %>%
  unite(newCol, col1, col2, col3, col4,
        remove = FALSE,
        sep = ', ') %>%
  # Replace NAs and "NA, "s with ''
  mutate(newCol = gsub('NA[, ]*', '', newCol)) %>%
  # Replace ', ' with '' if it is at the end of the line
  mutate(newCol = gsub(', $', '', newCol)) %>%
  # Add the parentheses on either side
  mutate(newCol = paste0('(', newCol, ')'))
#> # A tibble: 4 x 5
#>            newCol  col1  col2  col3  col4
#>             <chr> <chr> <chr> <chr> <chr>
#> 1      (dog, cat)   dog   cat  <NA>  <NA>
#> 2 (cat, foo, bar)  <NA>   cat   foo   bar
#> 3           (dog)   dog  <NA>  <NA>  <NA>
#> 4           (cat)  <NA>   cat  <NA>  <NA>

Кроме того, другие люди обсуждают эту проблему!

person zlipp    schedule 28.12.2017