#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. Возможно, это тоже возможно. Позвольте мне кое-что выяснить