Преобразовать вложенный список с разными именами в data.frame, заполняющий NA и добавляющий столбец

Мне нужно базовое решение R для преобразования вложенного списка с разными именами в data.frame

mylist <- list(list(a=1,b=2), list(a=3), list(b=5), list(a=9, z=list('k'))

convert(mylist)
## returns a data.frame:
##
##     a     b    z           
##     1     2    <NULL>   
##     3    NA    <NULL>   
##    NA     5    <NULL>   
##     9    NA    <chr [1]>

Я знаю, что это можно легко сделать с помощью dplyr::bind_rows или data.table::rbindlist с fill = TRUE (хотя и не идеально, поскольку он заполняет столбец символов с помощью NULL, а не NA), но мне действительно нужно решение в базе R. Чтобы упростить проблему, это также хорошо с двухуровневый вложенный список, в котором нет списков третьего уровня, таких как

mylist <- list(list(a=1,b=2), list(a=3), list(b=5), list(a=9, z='k'))

convert(mylist)
## returns a data.frame:
##
##     a     b    z           
##     1     2    NA   
##     3    NA    NA   
##    NA     5    NA   
##     9    NA    k  

Я пробовал что-то вроде

convert <- function(L) as.data.frame(do.call(rbind, L))

Это не заполняет NA и добавляет дополнительный столбец z

Обновлять

mylist это просто упрощенный пример. На самом деле я не мог предположить ни названия элементов подсписка (a, b и z в примере), ни длины подсписков (2, 1, 1, 2 в примере).

Вот предположения для ожидаемого data.frame и ввода mylist:

  1. Номер столбца ожидаемого data.frame определяется максимальной длиной подсписков, которая может варьироваться от 1 до нескольких сотен. Нет явного источника информации о длине каждого подсписка (неизвестно, какие имена появятся или исчезнут в каком подсписке). max(sapply(mylist, length)) <= 1000 ## ==> TRUE
  2. Номер строки ожидаемого data.frame определяется длиной mylist, которая может варьироваться от 1 до нескольких тысяч. dplyr::between(length(mylist), 0, 10000) ## ==> TRUE
  3. Нет явной информации для имен элементов подсписка и их порядка, поэтому имена столбцов и порядок ожидаемых data.frame могут быть определены только по сути из mylist
  4. Каждый подсписок содержит элементы типов numeric, character или list. Чтобы упростить задачу, рассмотрим только numeric и character.
Стоит ли изучать 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
0
85
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Вы можете сделать что-то вроде следующего:

mylist <- list(list(a=1,b=2), list(a=3), list(b=5), list(a=9, z='k'))

convert <- function(mylist){
  col_names <- NULL
  # get all the unique names and create the df
  for(i in 1:length(mylist)){
    col_names <- c(col_names, names(mylist[[i]]))
  }
  col_names <- unique(col_names)
  df <- data.frame(matrix(ncol=length(col_names),
                          nrow=length(mylist)))
  colnames(df) <- col_names
  
  # join data to row in df
  for(i in 1:length(mylist)){
    for(j in 1:length(mylist[[i]])){
      df[i, names(mylist[[i]])[j]] <- mylist[[i]][names(mylist[[i]])[j]]
    }
  }
  return(df)
}

df <- convert(mylist)
> df
   a  b    z
1  1  2 <NA>
2  3 NA <NA>
3 NA  5 <NA>
4  9 NA    k

У меня есть решение. Обратите внимание, что здесь используется только труба, и ее можно заменить на родную трубу и т. д.

mylist %>% 
  #' first, ensure that the 2nd level is flat,
  lapply(. %>% lapply(FUN = unlist, recursive = FALSE)) %>%
  #' replace missing vars with `NA`
  lapply(function(x, vars) {
    x[vars[!vars %in% names(x)]]<-NA
    x
  }, vars = {.} %>% unlist() %>% names() %>% unique()) %>%
  do.call(what = rbind) %>%
  #' do nothing
  identity()

В {.} он предназначен для определения и оценки функции, образованной unlist, за которой следует names. В противном случае . %>% unlist() %>% names() просто определяет функцию, а не оценивает входные данные ..

Это решение пропускает 5 в 3-й строке в столбце b (вместо этого назначается NA) и ошибочно ставит "k" в 4-й строке в столбце b. "k" должен быть на 4 месте в z столбце. Также не могли бы вы объяснить использование . в первых lapply и var = {.}?

englealuze 11.05.2022 10:12
Ответ принят как подходящий

Более короткое решение в базе R будет

make_df <- function(a = NA, b = NA, z = NA) {
  data.frame(a = unlist(a), b = unlist(b), z = unlist(z))
}

do.call(rbind, lapply(mylist, function(x) do.call(make_df, x)))
#>    a  b    z
#> 1  1  2 <NA>
#> 2  3 NA <NA>
#> 3 NA  5 <NA>
#> 4  9 NA    k

Обновлять

Более общее решение, использующее тот же метод, но не требующее конкретных имен, будет следующим:

build_data_frame <- function(obj) {
  nms     <- unique(unlist(lapply(obj, names)))
  frmls   <- as.list(setNames(rep(NA, length(nms)), nms))
  dflst   <- setNames(lapply(nms, function(x) call("unlist", as.symbol(x))), nms)
  make_df <- as.function(c(frmls, call("do.call", "data.frame", dflst)))
  
  do.call(rbind, lapply(mylist, function(x) do.call(make_df, x)))
}

Это позволяет

build_data_frame(mylist)
#>    a  b    z
#> 1  1  2 <NA>
#> 2  3 NA <NA>
#> 3 NA  5 <NA>
#> 4  9 NA    k

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

englealuze 11.05.2022 09:30

@englealuze а, понятно. Проверьте мое обновление, которое содержит более общее решение.

Allan Cameron 11.05.2022 10:23

Два вопроса: 1. как оцениваются объекты вызова unlist(*) внутри dflst? Я мог понять, что касается внешнего call("do.call", "data.frame", dflst)), вы конвертируете его в функцию и применяете к фактическим параметрам. Но я не мог понять, как оценивается внутренний вызов unlist(*). 2. В примере на справочной странице as.function используется alist. Когда я пытаюсь as.function(alist(a =, call('abs', as.symbol("a")))), он не генерирует истинную функцию. Я должен использовать as.function(c(a = NA, call('abs', as.symbol("a")))), как ты. Почему?

englealuze 11.05.2022 11:35

Объекты вызова внутри dflist не оцениваются до тех пор, пока do.call("data.frame", dflist) не будет запущен. Но так как это само находится в теле make_df, оно вызывается только тогда, когда вызывается make_df. В этот момент функция make_df эффективно function(a = NA, b = NA, z = NA) data.frame(a = unlist(a), b = unlist(b), z = unlist(z)) . Ни один из аргументов или вызовов в функции не оценивается до тех пор, пока make_df не будет вызван в последнем вызове lapply в последней строке.

Allan Cameron 11.05.2022 11:57

Аргументы в alist не оцениваются, поэтому as.function(alist(a =, {abs(a)})), вероятно, делает то, что вы намеревались.

Allan Cameron 11.05.2022 12:00

Спасибо. lapply используется чаще, чем sapply в nms <- unique(unlist(sapply(obj, names))). Если все подсписки имеют одинаковые имена и длину, sapply без упрощения возвращает матрицу и приводит к ошибке. Итак, lapply является более общим

englealuze 11.05.2022 12:48

@englealuze ты прав. Я обновлю.

Allan Cameron 11.05.2022 13:10

Мы можем попробовать базовый код R ниже

subset(
    Reduce(
        function(...) {
            merge(..., all = TRUE)
        },
        Map(
            function(k, x) cbind(id = k, list2DF(x)),
            seq_along(mylist), mylist
        )
    ),
    select = -id
)

который дает

   a  b  z
1  1  2 NA
2  3 NA NA
3 NA  5 NA
4  9 NA  k

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

englealuze 11.05.2022 18:25

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