Расширение фрейма данных, содержащего список в R

У меня есть фрейм данных, внутри которого содержится список.

df <- data.frame(
      id=c(1:4),
      a=I(list(c(1,"a1"),2,c("a31","a32","a33"),"a4")),
      b=I(list(2,c("b1","b2",3),c("b3","b4"),4))
    ); print(df)

  id            a         b
1  1        1, a1         2
2  2            2 b1, b2, 3
3  3 a31, a32....    b3, b4
4  4           a4         4

Теперь мне нужно unnest список, чтобы получить такой фрейм данных:

df2 <- data.frame(
      id=c(1,1,2,2,2,3,3,3,3,3,3,4),
      a=c(1,"a1",2,2,2,"a31","a31","a32","a32","a33","a33","a4"),
      b=c(2,2,"b1","b2",3,"b3","b3","b3","b4","b4","b4",4)
    ) ; print(df2)

   id   a  b
1   1   1  2
2   1  a1  2
3   2   2 b1
4   2   2 b2
5   2   2  3
6   3 a31 b3
7   3 a31 b3
8   3 a32 b3
9   3 a32 b4
10  3 a33 b4
11  3 a33 b4
12  4  a4  4

Раньше я использовал unnest() для тех, которые содержат одинаковое количество элементов списка в некоторых строках/столбцах, но текущий фрейм данных содержит разное количество элементов в некоторых строках и столбцах. В настоящее время я столкнулся со следующей ошибкой.

> target <- c("id","a","b")
> df %>% unnest(cols=target)
Error in `unnest()`:
! In row 3, can't recycle input of size 3 to size 2.
Run `rlang::last_trace()` to see where the error occurred.

Из-за непредсказуемости того, где это происходит (по строкам/столбцам) и сколько элементов оно будет содержать, я не могу найти подходящий подход для решения этой проблемы. Количество столбцов и их названия не могут быть определены заранее.

Я ценю ваше предложение, особенно простые, которые можно интегрировать в текущую работу трубопровода в dplyr. Base R и другие подходы также приветствуются.

====

Невоспроизводимые данные примера

Позвольте мне поделиться воспроизводимым примером реального фрейма данных, над которым я работаю. С решениями от @ThomasIsCoding это не сработало. Я подозреваю, что это вызвано NULL, но это не было причиной. Пустое/Нулевое значение не обязательно реплицируется.

structure(list(cluster = c("1", "2", "3", "4", "5", "6"), st_sub_main_th = list(
    "hira", NULL, "tsuma", "tsuma", NULL, c("other", "hira")), 
    roo_main = list("2", "4", "3", "2", c("1", "3"), c("6", "7", 
    "2", "1")), st_con_rt = list("sub-room", "main-room", "sub-room", 
        "sub-room", "main-room", "sub-room"), st_con_tr = list(
        "terrace", c("terrace", "direct"), "terrace", "terrace", 
        "terrace", "direct"), st_adsb = list("add", "add", "add", 
        "sub", "add", "sub"), st_th = list(NULL, "tsuma", NULL, 
        NULL, "hira", NULL), st_sub2_main_th = list(NULL, NULL, 
        NULL, "hira", "hira", "tsuma"), isstilt = list(NULL, 
        NULL, NULL, NULL, NULL, "0")), class = "data.frame", row.names = c(NA, 
-6L))

У вас больше проблем, чем размер элементов в списках: в этих списках смешаны элементы numeric и character, их нелегко «преобразовать» в простой вектор/столбец.

Zé Loff 20.02.2024 13:42
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
108
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

(Обновление) Если ваш вложенный фрейм данных имеет NULLs

Поскольку у вас могут быть NULL, вы можете проделать следующий трюк с новыми данными в вашем вопросе.

d <- list2DF(lapply(df, \(u) replace(u, lengths(u) == 0, "")))
do.call(
    rbind,
    do.call(Map, c(expand.grid, d, stringsAsFactors = FALSE))
)

который дает

    cluster st_sub_main_th roo_main st_con_rt st_con_tr st_adsb st_th
1         1           hira        2  sub-room   terrace     add
2.1       2                       4 main-room   terrace     add tsuma
2.2       2                       4 main-room    direct     add tsuma
3         3          tsuma        3  sub-room   terrace     add      
4         4          tsuma        2  sub-room   terrace     sub
5.1       5                       1 main-room   terrace     add  hira
5.2       5                       3 main-room   terrace     add  hira
6.1       6          other        6  sub-room    direct     sub
6.2       6           hira        6  sub-room    direct     sub
6.3       6          other        7  sub-room    direct     sub
6.4       6           hira        7  sub-room    direct     sub
6.5       6          other        2  sub-room    direct     sub
6.6       6           hira        2  sub-room    direct     sub
6.7       6          other        1  sub-room    direct     sub
6.8       6           hira        1  sub-room    direct     sub
    st_sub2_main_th isstilt
1
2.1
2.2
3
4              hira
5.1            hira
5.2            hira
6.1           tsuma       0
6.2           tsuma       0
6.3           tsuma       0
6.4           tsuma       0
6.5           tsuma       0
6.6           tsuma       0
6.7           tsuma       0
6.8           tsuma       0

Если в вашем вложенном фрейме данных НЕТ NULL

Вы можете попробовать expand.grid с Map, как показано ниже.

do.call(
    rbind,
    do.call(Map, c(expand.grid, df, stringsAsFactors = FALSE))
)

который дает

   id   a  b
1   1   1  2
2   1  a1  2
3   2   2 b1
4   2   2 b2
5   2   2  3
6   3 a31 b3
7   3 a32 b3
8   3 a33 b3
9   3 a31 b4
10  3 a32 b4
11  3 a33 b4
12  4  a4  4

Спасибо. В реальном фрейме данных столбцы не могут быть указаны динамически. Я заменил Map(expand.grid, id, a, b, stringsAsFactors = FALSE) на Map(expand.grid, !!sym(target), stringsAsFactors = FALSE), но столкнулся с ошибкой Can't convert a character vector to a symbol. Я тоже просто ставлю target без !!sym(), но оно возвращается 'names' attribute [3] must be the same length as the vector [1]. Ничего плохого?

HSJ 20.02.2024 13:26

@HSJ Вы можете попробовать мое обновленное решение

ThomasIsCoding 20.02.2024 13:32

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

HSJ 20.02.2024 13:44

@HSJ да, NULL вызывает проблему, но вы можете попробовать заменить ее на "" или NA, прежде чем продолжить expand.grid. Смотрите мое обновление

ThomasIsCoding 20.02.2024 14:01

Вы можете сделать это, unnestпросматривая каждый столбец за раз. Однако сначала вам нужно преобразовать элементы в character, иначе отмена вложения не сработает, поскольку вы будете пытаться создать вектор/столбец как с numeric, так и с character элементами.

library(purrr) # For map()
library(tidyr) # For unnest()
library(dplyr) # For mutate()

df %>%
mutate(a = map(a, as.character),
       b = map(b, as.character)) %>%
unnest(cols = a) %>%
unnest(cols = b)

Обновление с примерами данных:

В вашем примере данных вам также необходимо заменить NULL на что-то другое, иначе они исчезнут при промежуточном разложении:

df2 <- structure(list(
  cluster = c("1", "2", "3", "4", "5", "6"),
  st_sub_main_th = list("hira", NULL, "tsuma", "tsuma", NULL, c("other", "hira")),
  roo_main = list("2", "4", "3", "2", c("1", "3"), c("6", "7", "2", "1")),
  st_con_rt = list("sub-room", "main-room", "sub-room", "sub-room", "main-room", "sub-room"),
  st_con_tr = list("terrace", c("terrace", "direct"), "terrace", "terrace", "terrace", "direct"),
  st_adsb = list("add", "add", "add", "sub", "add", "sub"),
  st_th = list(NULL, "tsuma", NULL, NULL, "hira", NULL),
  st_sub2_main_th = list(NULL, NULL, NULL, "hira", "hira", "tsuma"),
  isstilt = list(NULL, NULL, NULL, NULL, NULL, "0")),
class = "data.frame", row.names = c(NA, -6L))

df2 %>%
mutate_if (is.list, map, coalesce, NA_character_) %>%
mutate_if (is.list, map, as.character) %>%
unnest(st_sub_main_th) %>%
unnest(roo_main) %>%
unnest(st_con_rt) %>%
unnest(st_con_tr) %>%
unnest(st_adsb) %>%
unnest(st_th) %>%
unnest(st_sub2_main_th) %>%
unnest(isstilt)

Спасибо, одна проблема в том, что я не могу заранее исправить количество и имя столбцов...

HSJ 20.02.2024 13:49

Возможно, используйте ... %>% Reduce(unnest, which(sapply(., is.list)), init = .), чтобы не нужно было указывать каждое невложение отдельно.

lotus 20.02.2024 14:04
Ответ принят как подходящий

Используйте tidyverse:

library(tidyverse)
exec(pmap_df, df, expand_grid)

# A tibble: 15 × 9
   cluster st_sub_main_th roo_main st_con_rt st_con_tr st_adsb st_th st_sub2_main_th isstilt
   <chr>   <chr>          <chr>    <chr>     <chr>     <chr>   <chr> <chr>           <chr>  
 1 1       hira           2        sub-room  terrace   add     NA    NA              NA     
 2 2       NA             4        main-room terrace   add     tsuma NA              NA     
 3 2       NA             4        main-room direct    add     tsuma NA              NA     
 4 3       tsuma          3        sub-room  terrace   add     NA    NA              NA     
 5 4       tsuma          2        sub-room  terrace   sub     NA    hira            NA     
 6 5       NA             1        main-room terrace   add     hira  hira            NA     
 7 5       NA             3        main-room terrace   add     hira  hira            NA     
 8 6       other          6        sub-room  direct    sub     NA    tsuma           0      
 9 6       other          7        sub-room  direct    sub     NA    tsuma           0      
10 6       other          2        sub-room  direct    sub     NA    tsuma           0      
11 6       other          1        sub-room  direct    sub     NA    tsuma           0      
12 6       hira           6        sub-room  direct    sub     NA    tsuma           0      
13 6       hira           7        sub-room  direct    sub     NA    tsuma           0      
14 6       hira           2        sub-room  direct    sub     NA    tsuma           0      
15 6       hira           1        sub-room  direct    sub     NA    tsuma           0  

Обратите внимание, что приведенное выше эквивалентно:

bind_rows(exec(pmap, df, expand_grid))

Если у вас смешанные типы данных, учтите следующее:

exec(rbind, !!!exec(pmap, df, expand_grid))

Это очень удобно выполнять в рамках операции с каналом. Спасибо!

HSJ 21.02.2024 03:52

Другие вопросы по теме