R: Общее сведение JSON к data.frame

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

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

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

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

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


person Sim    schedule 19.07.2012    source источник
comment
Хм? Слишком много английского для меня (во всяком случае), чтобы понять. Предложите предоставить воспроизводимый ввод с некоторым (предположительно) медленным кодом, который производит желаемый вывод, и продолжайте оттуда. Может быть, я просто не знаю JSON. Можете ли вы предоставить что-то вставляемое в новый сеанс R, который откуда-то загружает некоторые данные JSON, чтобы продемонстрировать ваш вопрос? Как сделать отличный воспроизводимый пример   -  person Matt Dowle    schedule 19.07.2012


Ответы (4)


Привет @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"  
person shhhhimhuntingrabbits    schedule 12.08.2012
comment
Выглядит многообещающе. Как бы вы предложили избавиться от конечных .1? - person Sim; 13.08.2012
comment
Вы должны иметь возможность переназначить names(flattened_structure), верно? - person Sim; 13.08.2012
comment
Я согласен. Теперь чище. Мой вопрос был конкретно о преобразовании большого документа JSON, который представляет собой массив словарей/хэшей, в data.frame. Для этого вам нужно построить набор столбцов как объединение всех имен сплющенных списков, верно? - person Sim; 13.08.2012
comment
Спасибо за пример кода. Однако я получаю, что атрибут 'names' [71556] должен быть той же длины, что и вектор [66648] - в большом вложенном наборе. - person EarlyPoster; 15.08.2013
comment
Отличный ответ - я просто хочу предложить одно изменение в flatten. В настоящее время вы выполняете сглаживание (.Primitive(c)) перед проверкой того, что список уже сглажен. Если вы предоставляете список, который уже сглажен, кажется, что он преобразует атомарный вектор, который теряет всю информацию о типе. Я предлагаю переместить сглаживание в конец повторяющегося цикла. - person DavidR; 22.05.2017

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

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

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

Пример:

(my_data <- list(x = list('1' = 1, '2' = list(y = 2))))
unlist(my_data)
person Richie Cotton    schedule 19.07.2012
comment
Что с минусом? unlist выглядит точно так же, как универсальная утилита для выравнивания структуры данных, которую хочет @Sim. Фактически, аналогичный вопрос, на который ссылается @ttmaccer, включает ответы, в которых широко используется unlist. - person Richie Cotton; 19.07.2012
comment
@ttmaccer: Да, в R не может быть и того и другого. У вас либо плоская (векторная) структура данных с одним типом данных, либо вложенная (списковая) структура со смешанными типами. Я думаю, что в R достаточно инструментов, чтобы любой JSON можно было преобразовать во что угодно. - person Richie Cotton; 19.07.2012
comment
@RichieCotton @ttmaccer Я согласен, что unlist не будет работать в обычном режиме. Если это лучшее, что есть в R из коробки, я продолжу и напишу рекурсивное сглаживание спуска, которое я использовал в других языках. - person Sim; 28.07.2012

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

person Jeroen    schedule 22.02.2014
comment
fromJSON(path_to_file, flatten=F) -- затем проверьте вывод. Обычно это вполне логично, хотя вам может понадобиться несколько rbindlist() и других действий, чтобы полностью развернуть его. - person Ufos; 28.05.2019

Отличный ответ с функциями flatten и getnames. Потребовалось несколько минут, чтобы выяснить все параметры, необходимые для перехода от вектора строк JSON к data.frame, поэтому я решил записать это здесь. Предположим, что jsonvec — это вектор строк JSON. Далее строится data.frame (data.table), где каждая строка содержит одну строку, а каждый столбец соответствует разным возможным конечным узлам дерева JSON. Любая строка, в которой отсутствует определенный конечный узел, заполняется 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)
person DavidR    schedule 22.05.2017