R: Общее сглаживание JSON в data.frame

Этот вопрос касается универсального механизма преобразования любого набора нециклических однородных или гетерогенных структур данных в кадр данных. Это может быть особенно полезно при работе с многими документами JSON или с большим документом JSON, который представляет собой набор словарей.

Есть несколько вопросов SO, которые касаются манипулирования глубоко вложенными структурами JSON и превращения их в dataframes с использованием таких функций, как plyr, lapply и т.д. Все вопросы и ответы, которые я нашел, касаются конкретных случаев, в отличие от предлагая общий подход к работе с коллекциями сложных структур данных JSON.

В Python и Ruby мне хорошо удалось реализовать универсальную утилиту для выравнивания структуры данных, которая использует путь к листу node в структуре данных как имя значения в этом node в сплющенном структура данных. Например, значение my_data[['x']][[2]][['y']] будет выглядеть как result[['x.2.y']].

Если у вас есть набор этих структур данных, которые могут быть не совсем однородными, ключом к успешному сглаживанию к фреймворку данных будет поиск имен всех возможных столбцов данных, например, путем объединения всех ключей/имена значений в индивидуально сплющенных структурах данных.

Это похоже на общий шаблон, и поэтому мне интересно, кто-то уже построил это для R. Если нет, я его построю, но, учитывая R уникальных структур данных, основанных на обещаниях, я был бы признателен за советы по который минимизирует переполнение кучи.

Ответ 1

Привет @Sim У меня появилась причина подумать о вашей проблеме вчера:

flatten<-function(x) {
    dumnames<-unlist(getnames(x,T))
    dumnames<-gsub("(*.)\\.1","\\1",dumnames)
    repeat {
        x <- do.call(.Primitive("c"), x)
        if(!any(vapply(x, is.list, logical(1)))){
           names(x)<-dumnames
           return(x)
        }
    }
}
getnames<-function(x,recursive){

    nametree <- function(x, parent_name, depth) {
        if (length(x) == 0) 
            return(character(0))
        x_names <- names(x)
        if (is.null(x_names)){ 
            x_names <- seq_along(x)
            x_names <- paste(parent_name, x_names, sep = "")
        }else{ 
            x_names[x_names==""] <- seq_along(x)[x_names==""]
            x_names <- paste(parent_name, x_names, sep = "")
        }
        if (!is.list(x) || (!recursive && depth >= 1L)) 
            return(x_names)
        x_names <- paste(x_names, ".", sep = "")
        lapply(seq_len(length(x)), function(i) nametree(x[[i]], 
            x_names[i], depth + 1L))
    }
    nametree(x, "", 0L)
}

(getnames адаптирован из AnnotationDbi: make.name.tree)

(flattenадаптирован из обсуждения здесь Как сгладить список в список без принуждения?)

как простой пример

my_data<-list(x=list(1,list(1,2,y='e'),3))

> my_data[['x']][[2]][['y']]
[1] "e"

> out<-flatten(my_data)
> out
$x.1
[1] 1

$x.2.1
[1] 1

$x.2.2
[1] 2

$x.2.y
[1] "e"

$x.3
[1] 3

> out[['x.2.y']]
[1] "e"

поэтому результат будет сплюснутым списком с примерно той структурой, которую вы предлагаете. Также избегается принуждение, которое является плюсом.

Более сложный пример

library(RJSONIO)
library(RCurl)
json.data<-getURL("http://www.reddit.com/r/leagueoflegends/.json")
dumdata<-fromJSON(json.data)
out<-flatten(dumdata)

ОБНОВЛЕНИЕ

наивный способ удаления трейлинга .1

my_data<-list(x=list(1,list(1,2,y='e'),3))
gsub("(*.)\\.1","\\1",unlist(getnames(my_data,T)))

> gsub("(*.)\\.1","\\1",unlist(getnames(my_data,T)))
[1] "x.1"   "x.2.1" "x.2.2" "x.2.y" "x.3"  

Ответ 2

R имеет два пакета для работы с входом JSON: rjson и RJSONIO. Если я правильно понимаю, что вы подразумеваете под "сборкой нециклических однородных или гетерогенных структур данных", я думаю, что любой из этих пакетов импортирует такую ​​структуру как list.

Затем вы можете сгладить этот список (в вектор) с помощью функции unlist.

Если список правильно структурирован (не вложенный список, где каждый элемент имеет одинаковую длину), то as.data.frame предлагает альтернативу преобразованию списка в кадр данных.

Пример:

(my_data <- list(x = list('1' = 1, '2' = list(y = 2))))
unlist(my_data)

Ответ 3

Пакет jsonlite является вилкой RJSONIO, специально разработанной для упрощения преобразования между JSON и кадрами данных. Вы не предоставляете никаких примеров json данных, но я думаю, что это может быть то, что вы ищете. Посмотрите на это сообщение в блоге или виньетка.

Ответ 4

Отличный ответ с функциями flatten и getnames. Потребовалось несколько минут, чтобы выяснить все параметры, необходимые для перехода от вектора строк JSON к data.frame, поэтому я решил записать это здесь. Пусть jsonvec - вектор строк JSON. Далее строятся data.frame(data.table), где есть одна строка в строке, и каждый столбец соответствует другому возможному листу node дерева JSON. Любая строка, пропускающая определенный лист node, заполняется NA.

library(data.table)
library(jsonlite)
parsed = lapply(jsonvec, fromJSON, simplifyVector=FALSE)
flattened = lapply(parsed, flatten) #using flatten from accepted answer
d = rbindlist(flattened, fill=TRUE)