R - список к фрейму данных
У меня есть вложенный список данных. Его длина равна 132, а каждый элемент представляет собой список длиной 20. Существует ли быстрый способ преобразовать эту структуру во фрейм данных, содержащий 132 строки и 20 столбцов данных?
Вот некоторые примеры данных для работы:
l <- replicate(
132,
list(sample(letters, 20)),
simplify = FALSE
)
27 ответов
Предполагая, что ваш список списков называется 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)
С rbind
do.call(rbind.data.frame, your_list)
Изменить: возврат предыдущей версии data.frame
из list
вместо векторов (как указал @IanSudbery в комментариях).
Вы можете использовать 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.
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)
Это возвращает data.table
наследуется от data.frame
,
Если вы действительно хотите преобразовать обратно в data.frame, используйте as.data.frame(DT)
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>
В зависимости от структуры ваших списков есть несколько 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
Этот метод использует tidyverse
пакет (мурлыканье).
Список:
x <- as.list(mtcars)
Преобразование его в фрейм данных (tibble
конкретнее):
library(purrr)
map_df(x, ~.x)
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().
Продолжая ответ @Marek: если вы хотите избежать превращения строк в факторы, эффективность не является проблемой, попробуйте
do.call(rbind, lapply(your_list, data.frame, stringsAsFactors=FALSE))
Для общего случая глубоко вложенных списков с 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
У меня сработала следующая простая команда:
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
Больше ответов, а также сроки в ответе на этот вопрос: Каков наиболее эффективный способ преобразования списка в фрейм данных?
Самый быстрый способ, который не создает информационный кадр со списками, а не векторы для столбцов, выглядит так (из ответа Мартина Моргана):
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]])))
Если в вашем списке есть элементы с одинаковыми размерами, вы можете использовать метод
bind_rows
функция от tidyverse.
# Load the tidyverse
Library(tidyverse)
# make a list with elements having same dimensions
My_list <- list(a = c(1, 4, 5), b = c(9, 3, 8))
## Bind the rows
My_list %>% bind_rows()
Результатом является фрейм данных с двумя строками.
Иногда ваши данные могут быть списком списков векторов одинаковой длины.
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
Вот что наконец-то сработало для меня:
do.call("rbind", lapply(S1, as.data.frame))
Для параллельного (многоядерного, мультисессионного и т. Д.) Решения, использующего 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()
Короткий (но, возможно, не самый быстрый) способ сделать это - использовать базу 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)
l <- replicate(10,list(sample(letters, 20)))
a <-lapply(l[1:10],data.frame)
do.call("cbind", a)
Кажется, что каждое найденное мной решение применимо только тогда, когда каждый объект в list
имеет то же самое length
. Мне нужно было преобразоватьlist
к data.frame
когда length
объектов в list
были неравными length
. Ниже находится базаR
решение, которое я придумал. Без сомнения, это очень неэффективно, но похоже, что это работает.
x1 <- c(2, 13)
x2 <- c(2, 4, 6, 9, 11, 13)
x3 <- c(1, 1, 2, 3, 3, 4, 5, 5, 6, 7, 7, 8, 9, 9, 10, 11, 11, 12, 13, 13)
my.results <- list(x1, x2, x3)
# identify length of each list
my.lengths <- unlist(lapply(my.results, function (x) { length(unlist(x))}))
my.lengths
#[1] 2 6 20
# create a vector of values in all lists
my.values <- as.numeric(unlist(c(do.call(rbind, lapply(my.results, as.data.frame)))))
my.values
#[1] 2 13 2 4 6 9 11 13 1 1 2 3 3 4 5 5 6 7 7 8 9 9 10 11 11 12 13 13
my.matrix <- matrix(NA, nrow = max(my.lengths), ncol = length(my.lengths))
my.cumsum <- cumsum(my.lengths)
mm <- 1
for(i in 1:length(my.lengths)) {
my.matrix[1:my.lengths[i],i] <- my.values[mm:my.cumsum[i]]
mm <- my.cumsum[i]+1
}
my.df <- as.data.frame(my.matrix)
my.df
# V1 V2 V3
#1 2 2 1
#2 13 4 1
#3 NA 6 2
#4 NA 9 3
#5 NA 11 3
#6 NA 13 4
#7 NA NA 5
#8 NA NA 5
#9 NA NA 6
#10 NA NA 7
#11 NA NA 7
#12 NA NA 8
#13 NA NA 9
#14 NA NA 9
#15 NA NA 10
#16 NA NA 11
#17 NA NA 11
#18 NA NA 12
#19 NA NA 13
#20 NA NA 13
Пытаться collapse::unlist2d
(сокращение от 'unlist to data.frame'):
l <- replicate(
132,
list(sample(letters, 20)),
simplify = FALSE
)
library(collapse)
head(unlist2d(l))
.id.1 .id.2 V1 V2 V3 V4 V5 V6 V7 V8 V9 V10 V11 V12 V13 V14 V15 V16 V17 V18 V19 V20
1 1 1 e x b d s p a c k z q m u l h n r t o y
2 2 1 r t i k m b h n s e p f o c x l g v a j
3 3 1 t r v z a u c o w f m b d g p q y e n k
4 4 1 x i e p f d q k h b j s z a t v y l m n
5 5 1 d z k y a p b h c v f m u l n q e i w j
6 6 1 l f s u o v p z q e r c h n a t m k y x
head(unlist2d(l, idcols = FALSE))
V1 V2 V3 V4 V5 V6 V7 V8 V9 V10 V11 V12 V13 V14 V15 V16 V17 V18 V19 V20
1 e x b d s p a c k z q m u l h n r t o y
2 r t i k m b h n s e p f o c x l g v a j
3 t r v z a u c o w f m b d g p q y e n k
4 x i e p f d q k h b j s z a t v y l m n
5 d z k y a p b h c v f m u l n q e i w j
6 l f s u o v p z q e r c h n a t m k y x
Я тоже хочу предложить это решение. Хотя он похож на другие решения, он использует rbind.fill из пакета plyr. Это полезно в ситуациях, когда в списке отсутствуют столбцы или значения NA.
l <- replicate(10,as.list(sample(letters,10)),simplify = FALSE)
res<-data.frame()
for (i in 1:length(l))
res<-plyr::rbind.fill(res,data.frame(t(unlist(l[i]))))
res
И вот как это решение будет выглядеть как функция
ltodf<-function(l) {
res<-data.frame()
for (i in 1:length(l))
res<-plyr::rbind.fill(res,data.frame(t(unlist(l[i]))))
return(res)
}
ltodf(l)
Или вы можете использовать пакет tibble (от tidyverse):
#create examplelist
l <- replicate(
132,
as.list(sample(letters, 20)),
simplify = FALSE
)
#package tidyverse
library(tidyverse)
#make a dataframe (or use as_tibble)
df <- as_data_frame(l,.name_repair = "unique")
С другой точки зрения;
install.packages("smotefamily")
library(smotefamily)
library(dplyr)
data_example = sample_generator(5000,ratio = 0.80)
genData = BLSMOTE(data_example[,-3],data_example[,3])
#There are many lists in genData. If we want to convert one of them to dataframe.
sentetic=as.data.frame.array(genData$syn_data)
# as.data.frame.array seems to be working.
Как насчет использования 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)
}
return(tmp_data_frame)
}
где map_dfr
преобразовать каждый элемент списка в data.frame, а затем rbind
объединить их вместе.
В вашем случае, я думаю, это будет:
converted_list <- list_to_df(l)
test1<- список (c (a = 'a', b = 'b', c = 'c'), c (a = 'd', b = 'e', c = 'f')) as.data.frame (test1) abc 1 abc 2 def
test2 <- список (c ('a', 'b', 'c'), c (a = 'd', b = 'e', c = 'f'))
as.data.frame (test2) abc 1 abc 2 def
test3 <- список ('Row1' = c (a = 'a', b = 'b', c = 'c'), 'Row2' = c (a = 'd', var2 = 'e', var3 = 'е'))
as.data.frame (test3) abc var2 var3 Row1 abc
Row2 def