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


513

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

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

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

Итак, вы хотите, чтобы каждый элемент списка представлял собой строку данных в вашем data.frame?
Джошуа Ульрих

2
@RichieCotton Это не правильный пример. «каждый элемент представляет собой список длиной 20», и каждый элемент представляет собой список из одного элемента вектора длины 20.
Marek

1
Опоздал на вечеринку, но я не видел, чтобы кто-то упомянул об этом , что я подумал, было очень удобно (для того, что я хотел сделать).
mflo-ByeSE


Ответы:


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)

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 указано в комментариях).


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.


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))

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)


Что касается последней строки, 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

Эта функция 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)

10

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

do.call(rbind, lapply(your_list, data.frame, stringsAsFactors=FALSE))

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

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

plyr считается устаревшим в пользу dplyr
csgillespie



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()

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

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


Обратите внимание, что на входе из вопроса это только вид работ. 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)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.