R - список к фрейму данных

431

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

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

l <- replicate(
  132,
  list(sample(letters, 20)),
  simplify = FALSE
)
  • 0
    Итак, вы хотите, чтобы каждый элемент списка представлял собой строку данных в вашем data.frame?
  • 2
    @RichieCotton Это не правильный пример. «каждый элемент представляет собой список длиной 20», и вы получили, что каждый элемент представляет собой список из одного элемента вектора длины 20.
Показать ещё 4 комментария
Теги:
dataframe
list

18 ответов

289
Лучший ответ

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

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

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

df <- data.frame(matrix(unlist(l), nrow=132, byrow=T),stringsAsFactors=FALSE)
  • 0
    @ Джошуа Ульрих: Упс! Я не знаю почему, но я думал, что он просит матрицу :)
  • 7
    unlist сделал свое дело. После этого я мог манипулировать / менять то, что мне было нужно. Спасибо!
Показать ещё 14 комментариев
403

С rbind

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

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

  • 3
    Почему это работает, но rbind(your_list) возвращает матрицу списка 1x32?
  • 23
    @eykanal do.call передает элементы your_list качестве аргументов для rbind . Это эквивалент rbind(your_list[[1]], your_list[[2]], your_list[[3]], ....., your_list[[length of your_list]]) .
Показать ещё 10 комментариев
108

Вы можете использовать пакет 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.

  • 7
    Отличный ответ. Не могли бы вы немного объяснить, как это работает? Он просто возвращает фрейм данных для каждой записи списка?
  • 11
    Имхо ЛУЧШИЙ ответ. Возвращает честный data.frame. Все типы данных (символьные, числовые и т. Д.) Корректно преобразуются. Если список имеет разные типы данных, они будут преобразованы в символьно- matrix .
Показать ещё 2 комментария
82

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

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

  • 0
    обновлен, чтобы принимать внутренние списки в виде строк.
  • 14
    лучший ответ на сегодняшний день! Ни одно из других решений не дает правильных имен типов / столбцов. БЛАГОДАРЮ ВАС!
Показать ещё 7 комментариев
52

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

data.frame(Reduce(rbind, L))
  • 2
    Хороший! Решение @Alex Brown отличается от вашего решения тем, что по вашему маршруту по какой-то причине появилось следующее предупреждающее сообщение: `Предупреждающее сообщение: в data.row.names (row.names, rowi, i): некоторые row.names дублированы : 3,4 -> row.names НЕ используется '
  • 0
    Отлично!! Работал для меня здесь: stackoverflow.com/questions/32996321/…
Показать ещё 1 комментарий
48

В пакете 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)

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

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

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

Пакет tibble имеет функцию enframe(), которая решает эту проблему, принуждая вложенные объекты list к вложенным объектам tibble ( "tidy" data frame). Вот краткий пример из 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>
  • 0
    Цитируя ОП: «Есть ли быстрый способ преобразовать эту структуру во фрейм данных, содержащий 132 строки и 20 столбцов данных?» Так что, может быть, вам нужен шаг распространения или что-то.
  • 1
    Ах да, просто должен быть столбец индекса, который можно распространять. Я обновлю в ближайшее время.
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

Если вы были почти из пикселей, вы можете сделать это все в 1 строке w/recast().

8

Для общего случая глубоко вложенных списков с 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
8

Расширение ответа @Marek: если вы хотите избежать превращения строк в факторы и эффективность, это не проблема.

do.call(rbind, lapply(your_list, data.frame, stringsAsFactors=FALSE))
  • 0
    отлично, у меня тоже сработало :)
8

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

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

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

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

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
4

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

Список:

x <- as.list(mtcars)

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

library(purrr)
map_df(x, ~.x)
4

Вот что, наконец, помогло мне:

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

3

В зависимости от структуры ваших списков есть несколько опций 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
  • 0
    Эта функция dplyr :: bind_rows хорошо работает, даже если трудно работать со списками, начинающимися как JSON. От JSON до удивительно чистого кадра данных. Ницца.
2
l <- replicate(10,list(sample(letters, 20)))
a <-lapply(l[1:10],data.frame)
do.call("cbind", a)
0

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

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

Как однострочник: new_df <- data.frame(t(data.frame(l)))

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

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

  • 1
    Почему это было отвергнуто? Я хотел бы знать, чтобы я не продолжал распространять дезинформацию.
  • 0
    Я определенно делал это раньше, используя комбинацию data.frame и t! Я думаю, что люди, которые проголосовали против, считают, что есть лучшие способы, особенно те, которые не путают имена.
Показать ещё 1 комментарий
-4

test1 < - list (c (a = 'a', b = 'b', c = 'c'), c (a = 'd', b = 'e', ​​c = 'f') ) as.data.frame(test1)   a b c 1 a b c 2 d e f

test2 < - list (c ('a', 'b', 'c'), c (a = 'd', b = 'e', ​​c = 'f'))

as.data.frame(test2)   a b c 1 a b c 2 d e f

test3 < - list ('Row1' = c (a = 'a', b = 'b', c = 'c'), 'Row2' = c (a = 'd', var2 = 'e ", var3 = 'е'))

as.data.frame(test3)      a b c var2 var3 Row1 a b c
Row2 d e f

Ещё вопросы

Сообщество Overcoder
Наверх
Меню