Перетворити список у кадр даних


513

У мене вкладений список даних. Її довжина - 132, і кожен елемент - це список довжиною 20. Чи існує швидкий спосіб перетворити цю структуру в кадр даних, який має 132 рядки та 20 стовпців даних?

Ось кілька зразкових даних, з якими можна працювати:

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

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

2
@RichieCotton Це неправильний приклад. "Кожен предмет - це список довжиною 20", і ви отримали кожен елемент - це один список елементів вектора довжиною 20.
Марек

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 ().
Ian Sudbery

Який найкращий спосіб зробити це, коли у списку відсутні пропущені значення, або включити NA у кадр даних?
Дейв

1
@ Dave: Роботи для мене ... дивіться тут r-fiddle.org/#/fiddle?id=y8DW7lqL&version=3
Nico

4
Також будьте уважні, якщо у вас є тип даних символів - data.frame перетворить їх у фактори.
Алекс Браун

4
@nico Чи є спосіб зберегти імена елементів списку як імена або рядків у df?
Н.Варела

472

З 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
Цей метод, здається, повертає правильний об'єкт, але, оглядаючи об’єкт, ви побачите, що стовпці - це списки, а не вектори, що може призвести до проблем внизу лінії, якщо ви цього не очікуєте.
Ian Sudbery

135

Ви можете використовувати 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.
Roah

1
наведена тут вибірка не є тією, яку надає питання. результат цієї відповіді на вихідному наборі даних невірний.
MySchizoBuddy

Для мене чудово працює! І назви стовпців у отриманому кадрі даних встановлюються! Tx
bAN

Чи є plyr багатоядерний? Або є версія lapply для використання з mclapply?
Garglesoap

103

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

sapplyперетворює його в матрицю. data.frameперетворює матрицю в кадр даних.


19
найкраща відповідь на сьогоднішній день! Жодне з інших рішень не відповідає правильності назв типів / стовпців. ДЯКУЮ ТОБІ!
d_a_c321

1
Яку роль ви збираєтеся cзіграти тут, один екземпляр даних списку? О, зачекайте, c для сполученої функції правильно? Заплутатися з використанням c @ mnel c. Я також погоджуюся з @dchandler, правильне назви стовпців було цінною потребою в моєму випадку використання. Блискуче рішення.
jxramos

це право - стандартна функція c; від ?c:Combine Values into a Vector or List
Алекс Браун

1
не працює зі зразковими даними, наведеними у запитанні
MySchizoBuddy,

3
Це не генерує фрейм даних із списків?
Карл

69

припустимо, ваш список називається L,

data.frame(Reduce(rbind, L))

2
Хороший! Є одна відмінність у рішенні @Alex Brown порівняно з вашим, коли ваш маршрут чомусь видав таке попереджувальне повідомлення: `Попереджувальне повідомлення: У data.row.names (row.name, rowsi, i): деякі імена row.name дублюються : 3,4 -> row.name NOT used '
jxramos

Дуже добре!! Працював для мене тут: stackoverflow.com/questions/32996321 / ...
Анастасія Pupynina

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.frameas.data.frame(DT)


Щодо останнього рядка, setDFтепер можна повернутися до data.frame за посиланням.
Френк

1
Для мого списку з предметами 30k, rbindlist працював набагато швидше ldply
талхаріш

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 до дивно чистого фрейму даних. Приємно.
GGAnderson

@sbha Я намагався використовувати df <- purrr :: map_df (l, ~ .x), але, здається, він не працює, повідомлення про помилку - помилка: стовпець X2не може бути перетворений з цілого числа в символ
Джолін

16

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

Якщо у вас майже не було пікселів, ви могли це зробити за один рядок w / recast ().


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

10

Для загального випадку глибоко вкладених списків з 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()розгортанням знову в охайний набір даних, де кожна змінна утворює стовпчик aa, а кожне спостереження утворює рядок:

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

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

8

Іноді ваші дані можуть бути списком переліків векторів однакової довжини.

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
l <- replicate(10,list(sample(letters, 20)))
a <-lapply(l[1:10],data.frame)
do.call("cbind", a)

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 )

> 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

Примітка . Відповідь спрямована до назви питання і може пропустити деякі деталі запитання


Зауважимо, що на вході в запитання працює лише такий вид. ОП запитує 132 рядки та 20 стовпців, але це дає 20 рядків та 132 стовпці.
Грегор Томас

Для вашого прикладу з різною довжиною введення, де він не вдається, не ясно, яким буде бажаний результат ...
Грегор Томас,

@Gregor Правда, але назва питання "R - список до кадру даних". Багато відвідувачів питання та ті, хто його проголосував, не мають точної проблеми ОП. Виходячи з назви питання, вони просто шукають спосіб перетворення списку в кадр даних. Я сам мав ту саму проблему, і рішення, яке я розмістив, вирішив свою проблему
Ахмад

Так, просто зауваживши. Не зволікає. У відповіді може бути приємно зазначити, що вона робить щось подібне - але виразно інше, ніж - майже всі інші відповіді.
Грегор Томас

1

Короткий (але, можливо, не найшвидший) спосіб зробити це було б використовувати базовий r, оскільки кадр даних - це лише список векторів однакової довжини . Таким чином, конверсія між вашим вхідним списком та діапазоном даних 30 x 132 буде такою:

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

0

Як щодо використання 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.