Преобразовать список во фрейм данных

513

У меня есть вложенный список данных. Его длина составляет 132, а каждый элемент представляет собой список длиной 20. Существует ли быстрый способ преобразовать эту структуру во фрейм данных, содержащий 132 строки и 20 столбцов данных?

Вот некоторые примеры данных для работы:

l <- replicate(
  132,
  list(sample(letters, 20)),
  simplify = FALSE
)
Btibert3
источник
Итак, вы хотите, чтобы каждый элемент списка представлял собой строку данных в вашем data.frame?
Джошуа Ульрих
2
@RichieCotton Это не правильный пример. «каждый элемент представляет собой список длиной 20», и каждый элемент представляет собой список из одного элемента вектора длины 20.
Marek
1
Опоздал на вечеринку, но я не видел, чтобы кто-то упомянул об этом , что я подумал, было очень удобно (для того, что я хотел сделать).
mflo-ByeSE
1
упоминается в r-bloggers.com/converting-a-list-to-a-data-frame
郷 木 郷

Ответы:

390

Предполагая, что ваш список списков называется l:

df <- data.frame(matrix(unlist(l), nrow=length(l), byrow=T))

Выше приведено преобразование всех символьных столбцов в факторы, во избежание этого вы можете добавить параметр в вызов data.frame ():

df <- data.frame(matrix(unlist(l), nrow=132, byrow=T),stringsAsFactors=FALSE)
Nico
источник
109
Осторожно, если ваши данные не одного типа. Прохождение через матрицу означает, что все данные будут приведены к общему типу. Т.е., если у вас есть один столбец символьных данных и один столбец числовых данных, числовые данные будут приводиться к строке по матрице (), а затем к фактору с помощью data.frame ().
Ян Садбери
Каков наилучший способ сделать это, если в списке отсутствуют значения, или включить NA в кадр данных?
Дейв
1
@ Dave: Работы для меня ... смотрите здесь r-fiddle.org/#/fiddle?id=y8DW7lqL&version=3
Nico
4
Также будьте осторожны, если у вас есть символьный тип данных - data.frame преобразует его в факторы.
Алекс Браун
4
@nico Есть ли способ сохранить имена элементов списка в качестве имен столбцов или строк в df?
Н.Варела
473

С rbind

do.call(rbind.data.frame, your_list)

Edit: Предыдущая версия возвращение data.frameиз list«S вместо векторов (как @IanSudbery указано в комментариях).

Marek
источник
5
Почему это работает, но rbind(your_list)возвращает матрицу списка 1x32?
эйканал
26
@eykanal do.callпередать элементы в your_listкачестве аргументов rbind. Это эквивалент rbind(your_list[[1]], your_list[[2]], your_list[[3]], ....., your_list[[length of your_list]]).
Марек
2
Этот метод страдает от нулевой ситуации.
Фрэнк Ван
3
@FrankWANG Но этот метод не предназначен для нулевой ситуации. Требуется, чтобы они your_listсодержали векторы одинакового размера. NULLимеет длину 0, поэтому он должен потерпеть неудачу.
Марек
12
Кажется, этот метод возвращает правильный объект, но при осмотре объекта вы обнаружите, что столбцы являются списками, а не векторами, что может привести к проблемам в будущем, если вы этого не ожидаете.
Ян Садбери
134

Вы можете использовать plyrпакет. Например, вложенный список формы

l <- list(a = list(var.1 = 1, var.2 = 2, var.3 = 3)
      , b = list(var.1 = 4, var.2 = 5, var.3 = 6)
      , c = list(var.1 = 7, var.2 = 8, var.3 = 9)
      , d = list(var.1 = 10, var.2 = 11, var.3 = 12)
      )

теперь имеет длину 4, и каждый список lсодержит еще один список длины 3. Теперь вы можете запустить

  library (plyr)
  df <- ldply (l, data.frame)

и должен получить тот же результат, что и в ответе @Marek и @nico.

mropa
источник
8
Отличный ответ. Не могли бы вы немного объяснить, как это работает? Он просто возвращает фрейм данных для каждой записи списка?
Майкл Бартон
13
Имхо ЛУЧШИЙ ответ. Возвращает честный data.frame. Все типы данных (символьные, числовые и т. Д.) Корректно преобразуются. Если список имеет разные типы данных, все они будут преобразованы в символ с matrixподходом.
Роа
1
образец, представленный здесь, не тот, который предоставлен вопросом. результат этого ответа в исходном наборе данных неверен.
MySchizoBuddy
Прекрасно работает для меня! И имена столбцов в результирующем фрейме данных установлены! Tx
BAN
Является ли plyr многоядерным? Или есть версия для использования с mclapply?
Garglesoap
103

data.frame(t(sapply(mylistlist,c)))

sapplyпреобразует его в матрицу. data.frameпреобразует матрицу в кадр данных

Алекс Браун
источник
19
лучший ответ на сегодняшний день! Ни одно из других решений не дает правильных имен типов / столбцов. БЛАГОДАРЮ ВАС!
d_a_c321
1
Какую роль вы собираетесь cиграть здесь, один экземпляр данных списка? Ой, подождите, c для конкатенации, верно? Запутаться с использованием @ mnel c. Я также согласен с @dchandler, поэтому правильное использование имен столбцов было очень важно в моем случае использования. Гениальное решение.
jxramos
это право - стандартная функция c; От ?c:Combine Values into a Vector or List
Алекс Браун
1
не работает с
примерами
3
Разве это не генерирует data.frame списков?
Карл
69

Предположим, ваш список называется L,

data.frame(Reduce(rbind, L))
jdeng
источник
2
Хороший! Решение @Alex Brown отличается от вашего решения тем, что ваш маршрут по какой-то причине вызвал следующее предупреждающее сообщение: `Предупреждающее сообщение: в data.row.names (row.names, rowi, i): некоторые row.names дублированы : 3,4 -> row.names НЕ используются '
jxramos
Отлично!! Работал для меня здесь: stackoverflow.com/questions/32996321/…
Анастасия Пупынина
2
Работает хорошо, если в списке нет только одного элемента: data.frame(Reduce(rbind, list(c('col1','col2'))))создает фрейм данных с 2 строками, 1 столбцом (я ожидал 1 ряд и 2 столбца)
Red Pea
61

Пакет data.tableимеет функцию, rbindlistкоторая является сверхбыстрой реализациейdo.call(rbind, list(...)) .

Это может занять список lists, data.framesили в data.tables качестве входных данных.

library(data.table)
ll <- list(a = list(var.1 = 1, var.2 = 2, var.3 = 3)
  , b = list(var.1 = 4, var.2 = 5, var.3 = 6)
  , c = list(var.1 = 7, var.2 = 8, var.3 = 9)
  , d = list(var.1 = 10, var.2 = 11, var.3 = 12)
  )

DT <- rbindlist(ll)

Это возвращает data.tableнаследство от data.frame.

Если вы действительно хотите преобразовать обратно в data.frame, используйтеas.data.frame(DT)

mnel
источник
Что касается последней строки, setDFтеперь позволяет вернуться к data.frame по ссылке.
Фрэнк
1
Для моего списка с 30 тыс. Элементов rbindlist работал намного быстрее, чем ldply
tallharish
35

В tibbleпакете есть функция, enframe()которая решает эту проблему путем приведения вложенных listобъектов к вложенным tibble(«аккуратным» фреймам данных) объектам. Вот краткий пример от R для Data Science :

x <- list(
    a = 1:5,
    b = 3:4, 
    c = 5:6
) 

df <- enframe(x)
df
#> # A tibble: 3 × 2
#>    name     value
#>   <chr>    <list>
#>    1     a <int [5]>
#>    2     b <int [2]>
#>    3     c <int [2]>

Поскольку в вашем списке несколько вложений l, вы можете использовать их unlist(recursive = FALSE)для удаления ненужных вложений, чтобы получить только один иерархический список и затем перейти к enframe(). Я использую, tidyr::unnest()чтобы раскрутить вывод в одноуровневый «аккуратный» фрейм данных, в котором есть два столбца (один для группы nameи один для наблюдений с группами value). Если вы хотите, чтобы столбцы расширялись, вы можете добавить столбец, add_column()который повторяет порядок значений 132 раза. Тогда только spread()ценности.


library(tidyverse)

l <- replicate(
    132,
    list(sample(letters, 20)),
    simplify = FALSE
)

l_tib <- l %>% 
    unlist(recursive = FALSE) %>% 
    enframe() %>% 
    unnest()
l_tib
#> # A tibble: 2,640 x 2
#>     name value
#>    <int> <chr>
#> 1      1     d
#> 2      1     z
#> 3      1     l
#> 4      1     b
#> 5      1     i
#> 6      1     j
#> 7      1     g
#> 8      1     w
#> 9      1     r
#> 10     1     p
#> # ... with 2,630 more rows

l_tib_spread <- l_tib %>%
    add_column(index = rep(1:20, 132)) %>%
    spread(key = index, value = value)
l_tib_spread
#> # A tibble: 132 x 21
#>     name   `1`   `2`   `3`   `4`   `5`   `6`   `7`   `8`   `9`  `10`  `11`
#> *  <int> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1      1     d     z     l     b     i     j     g     w     r     p     y
#> 2      2     w     s     h     r     i     k     d     u     a     f     j
#> 3      3     r     v     q     s     m     u     j     p     f     a     i
#> 4      4     o     y     x     n     p     i     f     m     h     l     t
#> 5      5     p     w     v     d     k     a     l     r     j     q     n
#> 6      6     i     k     w     o     c     n     m     b     v     e     q
#> 7      7     c     d     m     i     u     o     e     z     v     g     p
#> 8      8     f     s     e     o     p     n     k     x     c     z     h
#> 9      9     d     g     o     h     x     i     c     y     t     f     j
#> 10    10     y     r     f     k     d     o     b     u     i     x     s
#> # ... with 122 more rows, and 9 more variables: `12` <chr>, `13` <chr>,
#> #   `14` <chr>, `15` <chr>, `16` <chr>, `17` <chr>, `18` <chr>,
#> #   `19` <chr>, `20` <chr>
Мэтт Данчо
источник
Цитируя ОП: «Есть ли быстрый способ преобразовать эту структуру во фрейм данных, содержащий 132 строки и 20 столбцов данных?» Поэтому, возможно, вам нужен шаг распространения или что-то в этом роде.
Франк
1
Ах да, просто должен быть столбец индекса, который можно распространять. Я скоро обновлю.
Мэтт Данчо
17

В зависимости от структуры ваших списков, есть несколько tidyverseопций, которые хорошо работают с списками неравной длины:

l <- list(a = list(var.1 = 1, var.2 = 2, var.3 = 3)
        , b = list(var.1 = 4, var.2 = 5)
        , c = list(var.1 = 7, var.3 = 9)
        , d = list(var.1 = 10, var.2 = 11, var.3 = NA))

df <- dplyr::bind_rows(l)
df <- purrr::map_df(l, dplyr::bind_rows)
df <- purrr::map_df(l, ~.x)

# all create the same data frame:
# A tibble: 4 x 3
  var.1 var.2 var.3
  <dbl> <dbl> <dbl>
1     1     2     3
2     4     5    NA
3     7    NA     9
4    10    11    NA

Вы также можете смешивать векторы и фреймы данных:

library(dplyr)
bind_rows(
  list(a = 1, b = 2),
  data_frame(a = 3:4, b = 5:6),
  c(a = 7)
)

# A tibble: 4 x 2
      a     b
  <dbl> <dbl>
1     1     2
2     3     5
3     4     6
4     7    NA
sbha
источник
Эта функция dplyr :: bind_rows хорошо работает, даже если трудно работать со списками, созданными как JSON. От JSON до удивительно чистого кадра данных. Ницца.
Г.Г.Андерсон
@sbha Я пытался использовать df <- purrr :: map_df (l, ~ .x), но кажется, что он не работает, у меня появляется сообщение об ошибке: Ошибка: столбец X2нельзя преобразовать из целого в символ
Jolin
15

Reshape2 выдает тот же результат, что и в примере с plyr:

library(reshape2)
l <- list(a = list(var.1 = 1, var.2 = 2, var.3 = 3)
          , b = list(var.1 = 4, var.2 = 5, var.3 = 6)
          , c = list(var.1 = 7, var.2 = 8, var.3 = 9)
          , d = list(var.1 = 10, var.2 = 11, var.3 = 12)
)
l <- melt(l)
dcast(l, L1 ~ L2)

выходы:

  L1 var.1 var.2 var.3
1  a     1     2     3
2  b     4     5     6
3  c     7     8     9
4  d    10    11    12

Если у вас почти не осталось пикселей, вы можете сделать все это в одну строку с помощью recast ().

Джек Райан
источник
Я думаю, что reshape2 устарела для dplyr, tidyr и т. Д.
csgillespie
12

Этот метод использует tidyverseпакет ( purrr ).

Список:

x <- as.list(mtcars)

Преобразование его во фрейм данных ( tibbleболее конкретно):

library(purrr)
map_df(x, ~.x)
SavedByJESUS
источник
10

Продолжая ответ @ Marek: если вы хотите избежать превращения строк в факторы, эффективность не является проблемой, попробуйте

do.call(rbind, lapply(your_list, data.frame, stringsAsFactors=FALSE))
laubbas
источник
9

Больше ответов, а также сроки в ответе на этот вопрос: Каков наиболее эффективный способ преобразования списка в фрейм данных?

Самый быстрый способ, который не создает информационный фрейм со списками, а не векторами для столбцов, выглядит так (из ответа Мартина Моргана):

l <- list(list(col1="a",col2=1),list(col1="b",col2=2))
f = function(x) function(i) unlist(lapply(x, `[[`, i), use.names=FALSE)
as.data.frame(Map(f(l), names(l[[1]])))
Ян Садбери
источник
9

Для общего случая глубоко вложенных списков с 3 или более уровнями, подобными тем, которые получены из вложенного JSON:

{
"2015": {
  "spain": {"population": 43, "GNP": 9},
  "sweden": {"population": 7, "GNP": 6}},
"2016": {
  "spain": {"population": 45, "GNP": 10},
  "sweden": {"population": 9, "GNP": 8}}
}

рассмотрим подход melt()к преобразованию вложенного списка в высокий формат:

myjson <- jsonlite:fromJSON(file("test.json"))
tall <- reshape2::melt(myjson)[, c("L1", "L2", "L3", "value")]
    L1     L2         L3 value
1 2015  spain population    43
2 2015  spain        GNP     9
3 2015 sweden population     7
4 2015 sweden        GNP     6
5 2016  spain population    45
6 2016  spain        GNP    10
7 2016 sweden population     9
8 2016 sweden        GNP     8

затем, dcast()затем, чтобы снова расшириться до аккуратного набора данных, где каждая переменная образует столбец, а каждое наблюдение образует строку:

wide <- reshape2::dcast(tall, L1+L2~L3) 
# left side of the formula defines the rows/observations and the 
# right side defines the variables/measurements
    L1     L2 GNP population
1 2015  spain   9         43
2 2015 sweden   6          7
3 2016  spain  10         45
4 2016 sweden   8          9
RubenLaguna
источник
7

Иногда ваши данные могут быть списком векторов одинаковой длины.

lolov = list(list(c(1,2,3),c(4,5,6)), list(c(7,8,9),c(10,11,12),c(13,14,15)) )

(Внутренние векторы также могут быть списками, но я упрощаю, чтобы их было легче читать).

Затем вы можете сделать следующую модификацию. Помните, что вы можете удалить один уровень за раз:

lov = unlist(lolov, recursive = FALSE )
> lov
[[1]]
[1] 1 2 3

[[2]]
[1] 4 5 6

[[3]]
[1] 7 8 9

[[4]]
[1] 10 11 12

[[5]]
[1] 13 14 15

Теперь используйте ваш любимый метод, упомянутый в других ответах:

library(plyr)
>ldply(lov)
  V1 V2 V3
1  1  2  3
2  4  5  6
3  7  8  9
4 10 11 12
5 13 14 15
user36302
источник
plyr считается устаревшим в пользу dplyr
csgillespie
3

Вот что наконец-то сработало для меня:

do.call("rbind", lapply(S1, as.data.frame))

Амит Кохли
источник
3
l <- replicate(10,list(sample(letters, 20)))
a <-lapply(l[1:10],data.frame)
do.call("cbind", a)
zhan2383
источник
3

Для параллельного (многоядерного, мультисессионного и т. Д.) Решения, использующего purrrсемейство решений, используйте:

library (furrr)
plan(multisession) # see below to see which other plan() is the more efficient
myTibble <- future_map_dfc(l, ~.x)

Где lсписок?

Для сравнения наиболее эффективных plan()вы можете использовать:

library(tictoc)
plan(sequential) # reference time
# plan(multisession) # benchamark plan() goes here. See ?plan().
tic()
myTibble <- future_map_dfc(l, ~.x)
toc()
Trevi
источник
3

У меня сработала следующая простая команда:

myDf <- as.data.frame(myList)

Ссылка ( Quora answer )

> myList <- list(a = c(1, 2, 3), b = c(4, 5, 6))
> myList
$a
[1] 1 2 3

$b
[1] 4 5 6

> myDf <- as.data.frame(myList)
  a b
1 1 4
2 2 5
3 3 6
> class(myDf)
[1] "data.frame"

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

> myList <- list(a = c(1, 2, 3), b = c(4, 5, 6, 7))
> myDf <- as.data.frame(myList)
Error in (function (..., row.names = NULL, check.rows = FALSE, check.names = TRUE,  : 
  arguments imply differing number of rows: 3, 4

Примечание : ответ идет к названию вопроса и может пропустить некоторые детали вопроса

Ahmad
источник
Обратите внимание, что на входе из вопроса это только вид работ. OP запрашивает 132 строки и 20 столбцов, но это дает 20 строк и 132 столбца.
Грегор Томас
Для вашего примера с вводом разной длины, где он терпит неудачу, не ясно, каким будет желаемый результат ...
Грегор Томас
@Gregor Верно, но заголовок вопроса "R - список к фрейму данных". У многих посетителей вопроса и тех, кто проголосовал за него, нет точной проблемы ОП. Основываясь на заголовке вопроса, они просто ищут способ конвертировать список во фрейм данных. У меня самой была та же проблема, и решение, которое я выложил, решило мою проблему
Ахмад
Да, просто заметил. Не понижение. Было бы неплохо отметить в ответе, что он делает что-то похожее - но заметно отличающееся от - почти всех остальных ответов.
Грегор Томас
1

Короткий (но, возможно, не самый быстрый) способ сделать это - использовать базу r, поскольку кадр данных - это просто список векторов равной длины . Таким образом, преобразование между вашим входным списком и размером 30 x 132 data.frame будет:

df <- data.frame(l)

Оттуда мы можем переместить его в матрицу 132 x 30 и преобразовать обратно в массив данных:

new_df <- data.frame(t(df))

Как однострочник:

new_df <- data.frame(t(data.frame(l)))

Имена строк будут довольно раздражающими, но вы всегда можете переименовать их с

rownames(new_df) <- 1:nrow(new_df)

Будет С
источник
2
Почему это было отвергнуто? Я хотел бы знать, чтобы я не продолжал распространять дезинформацию.
Будет ли C
Я определенно делал это раньше, используя комбинацию data.frame и t! Я думаю, что люди, которые проголосовали против, считают, что есть лучшие способы, особенно те, которые не путают имена.
Артур Ип
1
Это хороший момент, я думаю, это также неверно, если вы хотите сохранить имена в своем списке.
Будет ли C
-1

Как насчет использования map_функции вместе с forциклом? Вот мое решение:

list_to_df <- function(list_to_convert) {
  tmp_data_frame <- data.frame()
  for (i in 1:length(list_to_convert)) {
    tmp <- map_dfr(list_to_convert[[i]], data.frame)
    tmp_data_frame <- rbind(tmp_data_frame, tmp)
  }
  print(tmp_data_frame)
}

где map_dfrпреобразовать каждый элемент списка в data.frame, а затемrbind их вместе.

В вашем случае, я думаю, это будет:

converted_list <- list_to_df(l)
Бо Трон
источник