2016-09-18 5 views
2

Dies kommt viel im Umgang mit APIs.Tidy verschachtelte JSON-Baum

Die meiste Zeit, um eine echte Analyse zu machen, möchte ich meine Datensätze sauber halten, aber in der Regel erfordert dies eine Lösung für jeden Baumtyp und nicht etwas allgemeineres.

Ich dachte, es wäre schön, eine Funktion zu haben, die ordentlich Daten (wenn auch mit einer Tonne von der NA mit vielen verschiedenen Faktorstufen in tief verschachtelten Bäume erzeugt.

ich eine hackish Lösung haben, die folgt, mit unlist(..., recursive = FALSE) + eine Namenskonvention,

Aber Ich mag würde, um zu sehen, ob jemand hier vielleicht eine bessere Lösung, um diese Arten von Listenstrukturen aufzuräumen hat.

##################### 
# Some Test Data 
aNestedTree = 
    list(a = 1, 
     b = 2, 
     c = list(
     a = list(1:5), 
     b = 2, 
     c = list(
      a = 1, 
      d = 3, 
      e = list())), 
     d = list(
     y = 3, 
     z = 2 
     )) 

############################################################ 
# Run through the list and rename all list elements, 
# We unlist once at time, adding "__" at each unlist step 
# until the object is no longer a list 

renameVars <- function(lst, sep = '__') { 
    if(is.list(lst)) { 
    names(lst) <- paste0(names(lst),sep) 
    renameVars(unlist(lst, recursive = FALSE),sep = sep) 
    } else { 
    lst 
    } 
} 

res <- renameVars(aNestedTree) 

können wir die Ausgabe überprüfen und sehen, dass wir eine seltsam genannt Objekt, Aber es gibt eine Methode zu diesem Wahnsinn.

> res 
    a________  b________ c__.a____1__ c__.a____2__ c__.a____3__ 
      1    2    1    2    3 
c__.a____4__ c__.a____5__ c__.b______ c__.c__.a____ c__.c__.d____ 
      4    5    2    1    3 
    d__.y______ d__.z______ 
      3    2 

Jetzt habe ich dies in einem data.table, also kann ich es gestalten.

library(data.table) 
dt <- data.table(values = res, name = names(res)) 

# Use some regex to split that name up, along with data.table's tstrsplit 
# function to separate them into as many columns as there are nests 

> dt[,paste0('V',seq_along(s <- tstrsplit(dt$name,'[__]+(\\.|)'))) := s] 
> dt 
    values   name V1 V2 V3 
1:  1  a________ a NA NA 
2:  2  b________ b NA NA 
3:  1 c__.a____1__ c a 1 
4:  2 c__.a____2__ c a 2 
5:  3 c__.a____3__ c a 3 
6:  4 c__.a____4__ c a 4 
7:  5 c__.a____5__ c a 5 
8:  2 c__.b______ c b NA 
9:  1 c__.c__.a____ c c a 
10:  3 c__.c__.d____ c c d 
11:  3 d__.y______ d y NA 
12:  2 d__.z______ d z NA 

Ich kann dann auswählen, für die Faktorkombinationen, die ich will (Oder dcast/spread). (Obwohl ich effektiv Tabellen auf der niedrigsten Ebene zerbricht, wenn sie existieren)

Ich dachte über Bind.c gehen und die do_unlist herausziehen, um eine Funktion mit einer flexiblen Namenskonvention über Rcpp zu machen, aber mein C++ ist rostig, also dachte ich, ich würde hier posten, bevor ich etwas drastisches mache.

+1

Haben Sie sich 'data.tree' angesehen? [data.tree-Einführung] (https://cran.r-project.org/web/packages/data.tree/vignettes/data.tree.html) [data.tree-Anwendung] (https: //cran.r -project.org/web/packages/data.tree/vignettes/applications.html) Und [diese Frage] (http://stackoverflow.com/questions/31339805/converting-json-format-to-csv-to-upload -data-table-in-r-to-produce-d3-bubble-cha) – dracodoc

+0

schaue es jetzt, das sieht wirklich vielversprechend aus – Shape

Antwort

0

Ich kämpfte in ähnlichen Situationen, aber das tidyjson Paket hat mich immer wieder gerettet, wenn es um geschachtelte JSON ging. Es ist eine beträchtliche Menge an Eingabe erforderlich, aber die tidyjson Funktionen geben ein sauberes Objekt zurück. Dokumentation hier: https://github.com/sailthru/tidyjson

+0

Kannst du mir ein Beispiel mit udyjson zeigen, das baum-agnostisch ist? (Sie brauchen die Namen der tief verschachtelten Ebenen nicht zu kennen? Ich habe schon vorher damit gespielt, aber ich dachte, dass es im Allgemeinen die Art von Wissen über die Struktur des Baumes erfordert, die ich gerne machen würde – Shape

+0

Hmm, Entschuldigung, aber ich bin nicht sicher, ob es ein solches Beispiel gibt.Nachdem ich den JSON erhalte, rufe ich 'jsonlite :: prettify()' auf, um die gesamte Struktur zu sehen, und dann mit 'tidyjson :: enter_object zu analysieren() ',' tidyjson :: spread_values ​​() 'usw. –

+0

Mit der Größe des JSON, mit dem ich arbeite, ist das nicht immer eine einfache Option.Ich suche nach einer Funktion, die wie die, die ich oben gepostet habe , arbeitet an jeder verschachtelten Struktur und verschiebt sie in eine saubere Struktur. – Shape

0

Wie dracodoc gezeigt hat, könnte data.tree helfen. Z.B. dies wie:

library(data.tree) 
aNestedTree = 
    list(a = 1, 
     b = 2, 
     c = list(
     a = list(1:5), 
     b = 2, 
     c = list(
      a = 1, 
      d = 3, 
      e = list())), 
     d = list(
     y = 3, 
     z = 2 
     )) 

tree <- FromListSimple(aNestedTree) 
print(tree) 

Dies gibt:

 levelName z 
1 Root   NA 
2 ¦--c   NA 
3 ¦ ¦--a  NA 
4 ¦ °--c  NA 
5 ¦  °--e NA 
6 °--d   2 

Und:

tree$fieldsAll 
[1] "a" "b" "1" "d" "y" "z" 

Randbemerkung:

do.call("print", c(tree, tree$fieldsAll)) 

Allerdings: In der Regel, Sie so etwas tun könnte Hier funktioniert das nicht, weil ein Knotenname existiert s sind die gleichen wie Feldnamen. Ich halte dies für einen Fehler und werde es bald beheben.

0

Ich neige dazu, sich auch in Richtung tidyjson zu neigen. In der tidyverse scheint das Verhalten, das Sie suchen, in der gather Familie zu sein.

Ich denke, die gather Familie von Funktionen in tidyjson könnte mit ein bisschen Verbesserung tun, die diese Helfer unnötig machen würde. Gerade jetzt sind sie sehr "typensensitiv" und Fehler oder werfen Typen aus, die nicht übereinstimmen.Auf jeden Fall ist die Problemumgehung nicht zu anspruchsvoll, obwohl sie definitiv nicht elegant ist. Beachten Sie, dass die bind_rows Variante derzeit aus meiner Entwicklungsversion stammt und noch nicht Mainstream ist. Hoffentlich illustriert dies jedoch die Idee.

Hinweise zum Ansatz:

  • , dass alle Werte numerisch sein würden (ich warf sie danach zum Charakter)
  • Helfer Elemente der unterschiedlichen Arten zu sammeln, und bind_rows stapelt die zusammen Datensätze.
  • Ebene ist Spur durch Niveau

Zunächst definieren die Helfer Rekursion gehalten:

recurse_gather <- function(.x,.level) { 
    .x <- tidyjson::bind_rows(
    gobj(.x,.level) 
    , garr(.x,.level) 
    , gpersist(.x,.level) 
) 

    if (any(as.character(json_types(.x,'type')$type) %in% c('object','array'))) { 
    .x <- recurse_gather(.x,.level+1) 
    } 

    return(.x) 
} 
gobj <- function(.x,.level) { 
    .x %>% json_types('type') %>% 
    filter(type=='object') %>% 
    gather_object(paste0('v',.level)) %>% 
    select(-type) 
} 

gpersist <- function(.x,.level) { 
    .x %>% json_types('type') %>% 
    filter(! type %in% c('object','array')) %>% 
    mutate_(.dots=setNames(
     paste0('as.character(NA)') 
     ,paste0('v',.level) 
    )) %>% 
    select(-type) 
} 

garr <- function(.x,.level) { 
    .x %>% json_types('type') %>% 
    filter(type=='array') %>% 
    gather_array('arridx') %>% 
    append_values_number(paste0('v',.level)) %>% 
    mutate_(.dots=setNames(
     paste0('as.character(v',.level,')') 
     ,paste0('v',.level) 
    )) %>% 
    select(-arridx,-type) 
} 

Dann ist die Helfer mit ziemlich geradlinig.

library(dplyr) 
library(tidyjson) 

j <- "{\"a\":[1],\"b\":[2],\"c\":{\"a\":[1,2,3,4,5],\"b\":[2],\"c\":{\"a\":[1],\"d\":[3],\"e\":[]}},\"d\":{\"y\":[3],\"z\":[2]}}" 
recurse_gather(j, 1) %>% arrange(v1, v2, v3, v4) %>% tbl_df() 
#> # A tibble: 12 x 5 
#> document.id v1 v2 v3 v4 
#> *  <int> <chr> <chr> <chr> <chr> 
#> 1   1  a  1 <NA> <NA> 
#> 2   1  b  2 <NA> <NA> 
#> 3   1  c  a  1 <NA> 
#> 4   1  c  a  2 <NA> 
#> 5   1  c  a  3 <NA> 
#> 6   1  c  a  4 <NA> 
#> 7   1  c  a  5 <NA> 
#> 8   1  c  b  2 <NA> 
#> 9   1  c  c  a  1 
#> 10   1  c  c  d  3 
#> 11   1  d  y  3 <NA> 
#> 12   1  d  z  2 <NA> 

hoffnungsvoll, dass die künftige Entwicklung auf das tidyjson Paket dies ein leichtes Problem machen zu bewältigen!