tidyverse сделайте pivot_wider с двумя различными стратегиями изменения формы (создание категориальных и двоичных столбцов)

#r #pivot #tidyverse

#r #сводная #tidyverse

Вопрос:

Используя следующие данные:

 df <- data.frame(id = c("A", "B", "C", "A", "B", "A"),
                 value = c(1, 2, 3, 4, 5, 6))
 

Я хочу изменить pivot_wider эти данные так, чтобы при изменении формы создавались два разных набора столбцов:

  • Один набор, в котором я создаю набор двоичных столбцов, которые берут имена столбцов из столбцов значений (например, bin_1, bin_2 и так далее) и которые кодируются как 0/1.
  • Дополнительный набор, в котором я создаю столько необходимых столбцов, чтобы хранить значения «категориальным» способом. Здесь идентификатор «A» имеет три значения, поэтому я хочу создать три столбца cat_1, cat_2, cat_3, а для идентификаторов B и C я хочу заполнить их NAs, если там нет значения.

Теперь я знаю, как создать эти две вещи отдельно друг от друга, а затем объединить их с помощью left_join .

Однако мой вопрос: можно ли это сделать в одном конвейере, где я делаю два последующих pivot_widers? Я пытался, но это не работает (очевидно, потому, что мой способ копирования столбца значений, а затем попытки использовать один для двоичного изменения формы и один для категориального изменения формы неверен).

Есть идеи?


Код, который пока работает:

 df1 <- df %>%
  group_by(id) %>%
  mutate(group_id = 1:n()) %>%
  ungroup() %>%
  pivot_wider(names_from = group_id,
              names_prefix = "cat_",
              values_from = value)

df2 <- df %>%
  mutate(dummy = 1) %>%
  arrange(value) %>%
  pivot_wider(names_from = value,
              names_prefix = "bin_",
              values_from = dummy,
              values_fill = list(dummy = 0),
              values_fn = list(dummy = length))

df <- df1 %>%
  left_join(., df2, by = "id)
 

Ожидаемый результат:

 # A tibble: 3 x 10
  id    cat_1 cat_2 cat_3 bin_1 bin_2 bin_3 bin_4 bin_5 bin_6
  <chr> <dbl> <dbl> <dbl> <int> <int> <int> <int> <int> <int>
1 A         1     4     6     1     0     0     1     0     1
2 B         2     5    NA     0     1     0     0     1     0
3 C         3    NA    NA     0     0     1     0     0     0
 

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

1. Не совсем. Связанный вопрос на самом деле касается только создания двоичных версий (что я знаю, как я могу сделать). Мой вопрос на самом деле касается того, как сделать и то, и другое, категориальное и двоичное, за один шаг.

Ответ №1:

С добавлением purrr , вы могли бы сделать:

 map(.x = reduce(range(df$value), `:`),
    ~ df %>%
     group_by(id) %>%
     mutate(!!paste0("bin_", .x) := as.numeric(.x %in% value))) %>%
 reduce(full_join) %>%
 mutate(cats = paste0("cat_", row_number())) %>%
 pivot_wider(names_from = "cats",
             values_from = "value")

  id    bin_1 bin_2 bin_3 bin_4 bin_5 bin_6 cat_1 cat_2 cat_3
  <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 A         1     0     0     1     0     1     1     4     6
2 B         0     1     0     0     1     0     2     5    NA
3 C         0     0     1     0     0     0     3    NA    NA
 

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

1. Интересный подход, но, глядя на него, он также сводится к тому, что в него вводится какое-то соединение. Или другими словами. Вероятно, нет способа достичь того, чего я хочу, используя только сводные точки. Но в моем текущем подходе определенно есть альтернативы.

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

Ответ №2:

В base вы можете попробовать:

 tt <- unstack(df[2:1])
x <- cbind(t(sapply(tt, "[", seq_len(max(lengths(tt))))),
           t( sapply(names(tt), "%in%", x=df$id)))
colnames(x) <- c(paste0("cat_", seq_len(max(lengths(tt)))),
              paste0("bin_", seq_len(nrow(df))))
x
#  cat_1 cat_2 cat_3 bin_1 bin_2 bin_3 bin_4 bin_5 bin_6
#A     1     4     6     1     0     0     1     0     1
#B     2     5    NA     0     1     0     0     1     0
#C     3    NA    NA     0     0     1     0     0     0
 

Ответ №3:

Немного измените свой подход, сократив df2 код и включив все это в один канал, воспользовавшись list . трюком и, который позволяет вам работать с двумя версиями df в одном вызове.

Это не сильно улучшило то, что вы сделали, но теперь все это в одном вызове. Я не могу придумать, как ты можешь сделать это без а merge/join .

 library(tidyverse)
df %>%
  list(
    pivot_wider(., id_cols = id, 
                names_from = value,
                names_prefix = "bin_") %>% 
      mutate_if(is.numeric, ~  (!is.na(.))), #convert to binary
    
    group_by(., id) %>%
      mutate(group_id = 1:n()) %>%
      ungroup() %>%
      pivot_wider(names_from = group_id,
                  names_prefix = "cat_",
                  values_from = value)    
  ) %>%
  .[c(2:3)] %>%
  reduce(left_join)

#   id    bin_1 bin_2 bin_3 bin_4 bin_5 bin_6 cat_1 cat_2 cat_3
#   <chr> <int> <int> <int> <int> <int> <int> <dbl> <dbl> <dbl>
# 1 A         1     0     0     1     0     1     1     4     6
# 2 B         0     1     0     0     1     0     2     5    NA
# 3 C         0     0     1     0     0     0     3    NA    NA
 

Ответ №4:

Даже вы можете объединить оба ваших синтаксиса в один без создания какого-либо промежуточного объекта

 df %>%
  group_by(id) %>%
  mutate(group_id = row_number()) %>%
  pivot_wider(names_from = group_id,
              names_prefix = "cat_",
              values_from = value) %>% left_join(df %>% mutate(dummy = 1) %>% arrange(value) %>% pivot_wider(names_from = value, 
                                                                                                             names_prefix = "bin_", 
                                                                                                             values_from = dummy, 
                                                                                                             values_fill = list(dummy = 0), 
                                                                                                             values_fn = list(dummy = length)), by = "id")

# A tibble: 3 x 10
# Groups:   id [3]
  id    cat_1 cat_2 cat_3 bin_1 bin_2 bin_3 bin_4 bin_5 bin_6
  <chr> <dbl> <dbl> <dbl> <int> <int> <int> <int> <int> <int>
1 A         1     4     6     1     0     0     1     0     1
2 B         2     5    NA     0     1     0     0     1     0
3 C         3    NA    NA     0     0     1     0     0     0
 

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

1. Спасибо. Да, строго говоря, это был бы один конвейер. Возможно, я был недостаточно точен: я хотел знать, могу ли я просто выполнить два pivot_wider, один за другим. Но я понимаю, что это кажется невозможным.

2. Возможно, это тоже возможно. Позвольте мне кое-что выяснить