У меня есть вложенный список данных. Его длина составляет 132, а каждый элемент представляет собой список длиной 20. Существует ли быстрый способ преобразовать эту структуру во фрейм данных, содержащий 132 строки и 20 столбцов данных?
Вот некоторые примеры данных для работы:
l <- replicate(
132,
list(sample(letters, 20)),
simplify = FALSE
)
Предполагая, что ваш список списков называется 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)
С rbind
do.call(rbind.data.frame, your_list)
Изменить: Предыдущая версия return data.frame
of list
вместо векторов (как указано в комментариях @IanSudbery).
rbind(your_list)
возвращает матрицу списка 1x32?
do.call
передает элементы your_list
качестве аргументов для rbind
. Это эквивалент rbind(your_list[[1]], your_list[[2]], your_list[[3]], ....., your_list[[length of your_list]])
.
Вы можете использовать пакет 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.
matrix
.
data.frame(t(sapply(mylistlist,c)))
sapply
преобразует его в матрицу.
data.frame
преобразует матрицу в кадр данных.
предположим, что ваш список называется L
,
data.frame(Reduce(rbind, L))
В пакете 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)
setDF
теперь позволяет вернуться к data.frame по ссылке.
Пакет 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>
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().
Для общего случая глубоко вложенных списков с 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
Расширение ответа @Marek: если вы хотите избежать превращения строк в факторы и эффективность, это не проблема.
do.call(rbind, lapply(your_list, data.frame, stringsAsFactors=FALSE))
Дополнительные ответы, а также тайминги в ответе на этот вопрос: Каков наиболее эффективный способ создания списка в виде фрейма данных?
Самый быстрый способ, который не создает блок данных со списками, а не векторами для столбцов (из ответа Мартина Моргана):
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]])))
Иногда ваши данные могут быть списком списков векторов одинаковой длины.
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
Этот метод использует tidyverse
пакет (purrr).
Список:
x <- as.list(mtcars)
Преобразование его во фрейм данных (более конкретно, tibble
):
library(purrr)
map_df(x, ~.x)
Вот что, наконец, помогло мне:
do.call("rbind", lapply(S1, as.data.frame))
В зависимости от структуры ваших списков есть несколько опций 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
l <- replicate(10,list(sample(letters, 20)))
a <-lapply(l[1:10],data.frame)
do.call("cbind", a)
Короткий (но, возможно, не самый быстрый) способ сделать это - использовать базу 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)
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