2012-07-19 22 views
10

To pytanie dotyczy ogólnego mechanizmu przekształcania dowolnego zbioru niecyklicznych jednorodnych lub heterogenicznych struktur danych w ramkę danych. Może to być szczególnie przydatne w przypadku przetwarzania wielu dokumentów JSON lub dużego dokumentu JSON, który jest zestawem słowników.R: Ogólne spłaszczanie JSON do data.frame

Istnieje kilka SO pytania, które zajmują się manipulacji głęboko zagnieżdżone struktury JSON i zamieniając je w dataframes użyciu funkcji takich jak plyr, lapply itp Wszystkie pytania i odpowiedzi znalazłem są o konkretnych przypadkach, w przeciwieństwie do oferowania ogólny podejście do radzenia sobie z kolekcjami złożonych struktur danych JSON.

W języku Python i Ruby zostałem dobrze obsłużony przez implementację ogólnego narzędzia do spłaszczania danych, które wykorzystuje ścieżkę do węzła liścia w strukturze danych jako nazwę wartości w tym węźle w spłaszczonej strukturze danych. Na przykład wartość my_data[['x']][[2]][['y']] pojawi się jako result[['x.2.y']].

Jeśli ma się kolekcję tych struktur danych, które mogą nie być całkowicie jednorodne, kluczem do skutecznego spłaszczenia ramki danych będzie wykrycie nazw wszystkich możliwych kolumn ramek danych, np. Poprzez połączenie wszystkich kluczy/nazwy wartości w indywidualnie spłaszczonych strukturach danych.

Wydaje się, że to powszechny wzorzec, więc zastanawiam się, czy ktoś już zbudował to dla R. Jeśli nie, zbuduję to, ale biorąc pod uwagę wyjątkowe struktury danych oparte na obietnicach R, będę wdzięczny za porady podejście implementacyjne, które minimalizuje efekt sterty.

+0

Huh? Za dużo angielskiego dla mnie (w każdym razie) do zrozumienia. Zasugeruj dostarczenie niektórych powtarzalnych danych wejściowych za pomocą jakiegoś (prawdopodobnie) wolnego kodu, który generuje dane wyjściowe, i od tego miejsca. Może po prostu nie znam JSON. Czy możesz podać coś możliwego do wklejenia w nową sesję R, która pobiera gdzieś dane JSON, aby zademonstrować swoje pytanie? [Jak zrobić świetny powtarzalny przykład] (http://stackoverflow.com/questions/5963269/how-to-make-a-great-r-reproducible-example) –

Odpowiedz

7

Hi @Sim miałem powód do refleksji na temat swojego problemu wczoraj określić:

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 jest adaptacją AnnotationDbi ::: make.name.tree)

(flatten został zaadaptowany z omawianych tutaj How to flatten a list to a list without coercion?)

jako prosty przykład

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" 

więc wynik jest spłaszczony lista z grubsza struktury nazewnictwa sugerujesz. Unika się także przymusu, co jest plusem.

Bardziej skomplikowane przykład

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

UPDATE

naiwny sposób, aby usunąć końcowe .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

Wygląda obiecująco. Jak zasugerowałbyś, że pozbędziemy się końcowych '.1'? – Sim

+0

Powinieneś być w stanie ponownie przypisać 'imiona (spłaszczone_struktury)', prawda? – Sim

+0

Zgadzam się. Sprzątaczka teraz. Moje pytanie dotyczyło konwersji dużego dokumentu JSON, który jest zestawem słowników/skrótów do data.frame. W tym celu musisz zbudować zestaw kolumn jako połączenie wszystkich spłaszczonych nazw list, prawda? – Sim

4

R ma dwa pakiety do obsługi wejścia JSON: rjson i RJSONIO. Jeśli dobrze rozumiem, co masz na myśli przez "gromadzenie niecyklicznych jednorodnych lub heterogenicznych struktur danych", myślę, że każdy z tych pakietów zaimportuje taką strukturę jako list.

Następnie można spłaszczyć tę listę (do wektora) za pomocą funkcji unlist.

Jeśli lista jest odpowiednio uporządkowana (lista nie-zagnieżdżona, gdzie każdy element ma tę samą długość), wówczas as.data.frame stanowi alternatywę dla przekształcenia listy w ramkę danych.

Przykład:

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

Co ze scenariuszem? 'unlist' wygląda dokładnie tak, jak" ogólne narzędzie do spłaszczania struktury danych ", którego @Sim chce. W rzeczywistości podobne pytanie związane z @ttmaccer zawiera odpowiedzi, które w dużym stopniu korzystają z "unlist". –

+0

@ttmaccer: Tak, nie możesz mieć tego na dwa sposoby w R. Ty masz płaską (wektor) strukturę danych z pojedynczym typem danych lub strukturą zagnieżdżoną (list) o różnych typach. Sądzę, że w R jest wystarczająco dużo narzędzi, aby dowolny JSON mógł się przekształcić w to, co chcesz. –

+0

@RichieCotton @ttmaccer Zgadzam się, że 'unlist' nie będzie działać w sposób ogólny. Jeśli jest to najlepsze, co R ma po wyjęciu z pudełka, powiem, że napiszę spłaszczony opór powrotny, którego używałem w innych językach. – Sim

1

Pakiet jsonlite jest rozwidlenie RJSONIO specjalnie zaprojektowany do konwersji między JSON i ramki danych łatwiejsze. Nie podajesz żadnego przykładu, ale myślę, że to może być to, czego szukasz. Spójrz na to blog post lub the vignette.

0

Świetna odpowiedź dzięki funkcjom spłaszczania i getnames. Zajęło mi kilka minut, aby znaleźć wszystkie opcje potrzebne do przejścia od wektora ciągów JSON do data.frame, więc pomyślałem, że to nagrałem tutaj. Załóżmy, że jsonvec jest wektorem ciągów JSON. Poniższy tekst tworzy dane.frame (data.table), w którym znajduje się jeden wiersz na łańcuch, a każda kolumna odpowiada innemu możliwemu węzłowi liści drzewa JSON. Dowolny łańcuch, który nie ma określonego węzła liścia, jest wypełniany 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)