Разложить или удалить фрейм данных, содержащий списки разной длины

У меня есть фрейм данных с несколькими столбцами, содержащими столбцы списка, которые я хочу unnest (или unchop). НО, они разной длины, поэтому результирующая ошибка Error: No common size for...

Вот представление, показывающее, что работает, а что нет.

library(tidyr)
library(vctrs)

# This works as expected
df_A <- tibble(
  ID = 1:3,
  A = as_list_of(list(c(9, 8, 5), c(7,6), c(6, 9)))
)

unchop(df_A, cols = c(A))
# A tibble: 7 x 2
     ID     A
  <int> <dbl>
1     1     9
2     1     8
3     1     5
4     2     7
5     2     6
6     3     6
7     3     9

# This works as expected as the lists are the same lengths

df_AB_1 <- tibble(
  ID = 1:3,
  A = as_list_of(list(c(9, 8, 5), c(7,6), c(6, 9))),
  B = as_list_of(list(c(1, 2, 3), c(4, 5), c(7, 8)))
)

unchop(df_AB_1, cols = c(A, B))

# A tibble: 7 x 3
     ID     A     B
  <int> <dbl> <dbl>
1     1     9     1
2     1     8     2
3     1     5     3
4     2     7     4
5     2     6     5
6     3     6     7
7     3     9     8

# This does NOT work as the lists are different lengths

df_AB_2 <- tibble(
  ID = 1:3,
  A = as_list_of(list(c(9, 8, 5), c(7,6), c(6, 9))),
  B = as_list_of(list(c(1, 2), c(4, 5, 6), c(7, 8, 9, 0)))
)

unchop(df_AB_2, cols = c(A, B))

# Error: No common size for `A`, size 3, and `B`, size 2.

Результат, которого я хотел бы достичь для df_AB_2 выше, выглядит следующим образом, где каждый список не вырезан, а отсутствующие значения заполнены NA:

# A tibble: 10 x 3
      ID     A     B
   <dbl> <dbl> <dbl>
 1     1     9     1
 2     1     8     2
 3     1     5    NA
 4     2     7     4
 5     2     6     5
 6     2    NA     6
 7     3     6     7
 8     3     9     8
 9     3    NA     9
10     3    NA     0

Я упоминал об этой проблеме на Github и StackOverflow здесь.

Есть идеи, как добиться указанного выше результата?

Версии

> packageVersion("tidyr")
[1] ‘1.0.0’
> packageVersion("vctrs")
[1] ‘0.2.0.9001’

person Megan Beckett    schedule 05.12.2019    source источник


Ответы (2)


Вот идея с помощью dplyr, которую вы можете обобщить на любое количество столбцов,

library(tidyverse)

df_AB_2 %>% 
 pivot_longer(c(A, B)) %>% 
 mutate(value = lapply(value, `length<-`, max(lengths(value)))) %>% 
 pivot_wider(names_from = name, values_from = value) %>% 
 unnest() %>% 
 filter(rowSums(is.na(.[-1])) != 2)

который дает,

# A tibble: 10 x 3
      ID     A     B
   <int> <dbl> <dbl>
 1     1     9     1
 2     1     8     2
 3     1     5    NA
 4     2     7     4
 5     2     6     5
 6     2    NA     6
 7     3     6     7
 8     3     9     8
 9     3    NA     9
10     3    NA     0
person Sotos    schedule 05.12.2019
comment
Ах, супер аккуратно! Хорошая идея - вращаться вперед и назад и создавать все списки одинаковой длины. Спасибо! - person Megan Beckett; 05.12.2019
comment
Без проблем. Рад помочь. Просто убедитесь, что вы настроили окончательный фильтр в зависимости от количества столбцов, которые вам нужно pivot_longer / unnest. Если у вас есть 3 столбца со списками, которые нужно разложить, тогда is.na ()! = 3 и т. Д. - person Sotos; 05.12.2019

Определение вспомогательной функции для обновления длины элемента и выполнение dplyr:

foo <- function(x, len_vec) {
  lapply(
    seq_len(length(x)), 
    function(i) {
      length(x[[i]]) <- len_vec[i]
      x[[i]]
    } 
  )
}

df_AB_2 %>% 
  mutate(maxl = pmax(lengths(A), lengths(B))) %>% 
  mutate(A = foo(A, maxl), B = foo(B, maxl)) %>% 
  unchop(cols = c(A, B)) %>% 
  select(-maxl)

# A tibble: 10 x 3
      ID     A     B
   <int> <dbl> <dbl>
 1     1     9     1
 2     1     8     2
 3     1     5    NA
 4     2     7     4
 5     2     6     5
 6     2    NA     6
 7     3     6     7
 8     3     9     8
 9     3    NA     9
10     3    NA     0

Использование data.table:

library(data.table)
setDT(df_AB_2)
df_AB_2[, maxl := pmax(lengths(A), lengths(B))]
df_AB_2[, .(unlist(A)[seq_len(maxl)], unlist(B)[seq_len(maxl)]), by = ID]
person sindri_baldur    schedule 05.12.2019
comment
Большой! Спасибо, очень нравится вспомогательная функция. - person Megan Beckett; 05.12.2019