Список списков в dataframe в R
Мне нужно справиться с уродливым списком под названием ul
, который выглядит так:
[[1]]
[[1]]$param
name value
"Section" "1"
[[1]]$param
name value
"field" "1"
[[1]]$param
name value
"final answer" "1"
[[1]]$param
name value
"points" "-0.0"
[[2]]
[[2]]$param
name value
"Section" "1"
[[2]]$param
name value
"field" "2"
[[2]]$param
name value
"final answer" "1"
[[2]]$param
name value
"points" "1.0"
[[3]]
[[3]]$param
name value
"Section" "1"
[[3]]$param
name value
"field" "3"
[[3]]$param
name value
"final answer" "0.611"
[[3]]$param
name value
"points" "1.0"
Я хотел бы преобразовать список в простой фрейм данных, т.е.
Section field final answer points
1 1 1 -0.0
1 2 1 1.0
1 3 0.611 1.0
Есть ли простой способ достичь этого? или мне нужно сделать функцию доступа к каждому списку отдельно и привязать его к кадру данных?
Данные импортируются из более уродливого XML файла, поэтому, если кто-то хочет поиграть с ним, есть ссылка на файл RData. Извините за отсутствие воспроизводимого кода. Большое вам спасибо.
Ответы
Ответ 1
Возможно, лучшее решение, но это должно вас начать. Во-первых, мы загружаем некоторые библиотеки
R> library(plyr)
R> library(reshape2)
Затем обрабатывайте списки в двух частях.
##lapply applies ldply to each list element in turn
ul1 = lapply(ul, ldply)
##We then do the same again
dd = ldply(ul1)[,2:3]
Затем мы выводим вывод в соответствии со списком
R> dd$num = rep(1:3, each=4)
Затем мы конвертируем из длинного в широкоформатный
R> dcast(dd, num ~ name)
num field final answer points Section
1 1 1 1 -0.0 1
2 2 2 1 1.0 1
3 3 3 0.611 1.0 1
Ответ 2
Ответ на аналогичную проблему дал Марк Шварц по этой ссылке:
https://stat.ethz.ch/pipermail/r-help/2006-August/111368.html
Я копирую его, если ссылка удалена.
as.data.frame(sapply(a, rbind))
V1 V2 V3
1 a b c
2 1 3 5
3 2 4 6
или
as.data.frame(t(sapply(a, rbind)))
V1 V2 V3
1 a 1 2
2 b 3 4
3 c 5 6
Ответ 3
Поскольку структура ul
является последовательной, вы можете просто получить каждый столбец отдельно (используя только базу R):
section <- vapply(ul, function(x) as.numeric(x[[1]][2]), 0)
field <- vapply(ul, function(x) as.numeric(x[[2]][2]), 0)
final_answer <- vapply(ul, function(x) as.numeric(x[[3]][2]), 0)
points <- vapply(ul, function(x) as.numeric(x[[4]][2]), 0)
(Заметьте, я использую vapply
вместо sapply
, поскольку он быстрее и надежно возвращает вектор, который здесь необходим).
Затем вы можете просто собрать все это вместе:
> data.frame(section, field, final_answer, points)
section field final_answer points
1 1 1 1.000 0
2 1 2 1.000 1
3 1 3 0.611 1
Обратите внимание, что я превратил все в numeric
. Если вы хотите сохранить все как символы, удалите as.numeric
и обменивайтесь 0
с помощью ""
в каждом вызове vapply
.
Позднее обновление:
На самом деле есть приятный oneliner, который извлекает полные данные:
do.call("rbind", lapply(ul, function(x) as.numeric(vapply(x, "[", i = 2, ""))))
который дает:
[,1] [,2] [,3] [,4]
[1,] 1 1 1.000 0
[2,] 1 2 1.000 1
[3,] 1 3 0.611 1
чтобы использовать colnames
:
> vapply(ul[[1]], "[", i = 1, "")
param param param param
"Section" "field" "final answer" "points"
Ответ 4
Я не уверен, что вы подразумеваете под "функцией доступа к каждому списку по отдельности", но это довольно просто, используя "lapply" и "do.call(" rbind ",...)":
Я не мог загрузить ваш .RData файл, поэтому этот код работает для списка:
ul <- list(param = list(
c(name = "Section", value = "1"),
c(name = "field", value = "1"),
c(name = "final answer", value = "1"),
c(name = "points", value = "-0.0")),
param = list(
c(name = "Section", value = "1"),
c(name = "field", value = "2"),
c(name = "final answer", value = "1"),
c(name = "points", value = "1.0")))
Возможно, вам придется настроить данные, если ваш список отличается; общий принцип останется прежним. Просто, чтобы сохранить код в чистоте, определите функцию "extractitem", которая вытащит все имена или значения для ul [[1]], ul [[2]] и т.д. Эта функция немного более общая, чем вам нужно.
extractitem <- function(listelement, item)
unname(lapply(listelement, function(itemblock) itemblock[item]))
Теперь мы просто используем lapply для перехода через элемент ul по элементу; для каждого элемента мы извлекаем значения в кадр данных, а затем называем столбцы в соответствии с "именами".
rowlist <- lapply(ul, function(listelement) {
d <- data.frame(extractitem(listelement, "value"), stringsAsFactors = FALSE)
names(d) <- unlist(extractitem(listelement, "name"))
d
})
список строк теперь представляет собой список фреймов данных; мы можем объединить их в единый кадр данных с помощью "rbind". Хорошая вещь об использовании кадров данных на предыдущем шаге (в отличие от векторов или чего-то с более низкими служебными данными) заключается в том, что rbind будет изменять порядок столбцов, если это необходимо, поэтому, если порядок полей изменяется от элемента к элементу, мы все еще все вправо.
finaldf <- do.call("rbind", rowlist)
Нам по-прежнему необходимо изменить элементы fo finaldf от "character" до любого, что подходит для вашего приложения, например,
finaldf$points <- as.numeric(finaldf$points)
и т.д. Последний шаг очищает кадр данных, удаляя автоматически созданные имена строк:
rownames(finaldf) <- NULL
В случае, если вам нужно изменить настройки, общая идея состоит в том, чтобы написать функцию, которая будет форматировать каждую ul [[i]] в качестве фрейма данных с правильными именами столбцов; затем вызовите эту функцию на каждом элементе ul с лапкой; и, наконец, свернуть результирующий список с помощью do.call( "rbind",...).