2012-07-19 12 views
10

Esta pregunta es sobre un mecanismo genérico para convertir cualquier colección de estructuras de datos heterogéneas o heterogéneas no cíclicas en un marco de datos. Esto puede ser particularmente útil cuando se trata de la ingestión de muchos documentos JSON o con un documento JSON grande que es una matriz de diccionarios.R: Aplanamiento genérico de JSON a data.frame

Hay varios SO preguntas que tienen que ver con la manipulación de estructuras anidadas JSON y convertirlas en tramas de datos utilizando la funcionalidad como plyr, lapply, etc. Todas las preguntas y respuestas que he encontrado son sobre casos específicos en lugar de ofrecer un general enfoque para tratar con colecciones de estructuras de datos JSON complejas.

En Python y Ruby Me ha servido mucho implementar una herramienta de aplanamiento de estructura de datos genérica que utiliza la ruta a un nodo hoja en una estructura de datos como el nombre del valor en ese nodo en la estructura de datos aplanada. Por ejemplo, el valor my_data[['x']][[2]][['y']] aparecería como result[['x.2.y']].

Si uno tiene una colección de estas estructuras de datos que pueden no ser completamente homogéneas, la clave para hacer un aplanamiento exitoso a un marco de datos sería descubrir los nombres de todas las columnas de dataframe posibles, por ejemplo, tomando la unión de todas las claves/nombres de los valores en las estructuras de datos planas individualmente.

Esto parece ser un patrón común, por lo que me pregunto si alguien ya ha creado esto para R. Si no, lo construiré pero, dadas las estructuras de datos basadas en promesas únicas de R, agradecería consejos sobre un enfoque de implementación que minimiza la agitación de montón.

+0

¿Huh? Demasiado inglés para mí (de todos modos) para entender. Sugiera proporcionar alguna entrada reproducible con algún código (presumiblemente) lento que produzca el resultado que desea, e ir desde allí. Quizás es solo que no sé JSON. ¿Puede proporcionar algo fácil de preparar en una nueva sesión R que descargue datos JSON de algún lugar para demostrar su pregunta? [Cómo hacer un gran ejemplo reproducible] (http://stackoverflow.com/questions/5963269/how-to-make-a-great-r-reproducible-example) –

Respuesta

7

Hola @Sim que ha tenido que reflexionar sobre su problema ayer definen:

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 es una adaptación de AnnotationDbi ::: make.name.tree)

(flatten está adaptado a partir de la discusión aquí How to flatten a list to a list without coercion?)

como un ejemplo sencillo

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" 

lo que el resultado es una lista lineal con aproximadamente la estructura de nombres que usted sugiere. La coacción también se evita, lo cual es un plus.

Un ejemplo más complicado

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

ACTUALIZACIÓN

manera ingenua para eliminar la zaga .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" 
+0

Parece prometedor. ¿Cómo sugerirías que nos deshagamos de los '.1's finales? – Sim

+0

Debería poder reasignar 'names (flattened_structure)', ¿no? – Sim

+0

Estoy de acuerdo. Más limpio ahora. Mi pregunta era específicamente sobre la conversión de un gran documento JSON que es una matriz de diccionarios/hashes a data.frame. Para eso tendrías que construir el conjunto de columnas como la unión de todos los nombres de la lista aplanada, ¿verdad? – Sim

4

R tiene dos paquetes para tratar con la entrada JSON: rjson y RJSONIO. Si entiendo correctamente lo que quiere decir con "recopilación de estructuras de datos heterogéneas o heterogéneas no cíclicas", creo que cualquiera de estos paquetes importará ese tipo de estructura como list.

Puede aplanar esa lista (en un vector) utilizando la función unlist.

Si la lista está estructurada adecuadamente (una lista no anidada donde cada elemento tiene la misma longitud), entonces as.data.frame ofrece una alternativa para convertir la lista en un marco de datos.

Un ejemplo:

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

¿Qué pasa con el voto a favor? 'unlist' parece exactamente como la" utilidad de aplanamiento de la estructura de datos genéricos "que @Sim quiere. De hecho, la pregunta similar vinculada por @ttmaccer incluye respuestas que hacen un uso extenso de 'unlist'. –

+0

@ttmaccer: Sí, no puede tener ambas formas en R. Puede ser una estructura de datos plana (vector) con un solo tipo de datos o una estructura anidada (lista) con tipos mixtos. Creo que hay suficientes herramientas en R para que cualquier JSON sea transformable en lo que quieras. –

+0

@RichieCotton @ttmaccer Acepto que 'unlist' no funcionará de forma genérica. Si esta es la mejor R que tiene de fábrica, voy a seguir adelante y escribir el acoplador de descenso recursivo que he usado en otros idiomas. – Sim

1

El paquete jsonlite es un tenedor de RJSONIO diseñado específicamente para hacer la conversión entre JSON y marcos de datos más fáciles. No proporciona ningún ejemplo de json de datos, pero creo que esto podría ser lo que está buscando. Eche un vistazo a este blog post o the vignette.

0

Gran respuesta con las funciones aplanar y obtener nombres. Tardó unos minutos en descubrir todas las opciones necesarias para pasar de un vector de cadenas JSON a un data.frame, así que pensé en registrarlo aquí. Supongamos que jsonvec es un vector de cadenas JSON. A continuación, se crea un data.frame (data.table) donde hay una fila por cadena, y cada columna corresponde a un nodo de hoja posible diferente del árbol JSON. Cualquier cadena que falta un nodo de hoja particular se llena con 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) 
Cuestiones relacionadas