2012-06-25 36 views
8

Düzenleme: bu soru güncel değil. jsonlite paketi otomatik olarak düzleşir.veri tabanına kayıt tabanlı liste/nesne basma

Genellikle JSON'da, kayıt tabanlı kodlamaya sahip çevrimiçi veri akışlarıyla uğraşıyorum. Nesnenin yapısı (yani JSON'daki isimler) API belgelerinden bilinmektedir, ancak değerler çoğunlukla isteğe bağlıdır ve her kayıtta mevcut değildir. Listeler yeni listeler içerebilir ve yapı bazen oldukça derindir. İşte bazı GPS verilerinin oldukça basit bir örneği: http://pastebin.com/raw.php?i=yz6z9t25. Alt satırlarda, hiçbir GPS sinyali olmadığından "l" nesnesinin eksik olduğunu unutmayın.

Bu nesneleri bir veri çerçevesine yerleştirmenin zarif bir yolunu arıyorum. Şu anda böyle bir şey kullanıyorum:

library(RJSONIO) 
library(plyr) 

obj <- fromJSON("http://pastebin.com/raw.php?i=yz6z9t25", simplifyWithNames=FALSE, simplify=FALSE) 
flatdata <- lapply(obj$data, as.data.frame); 
mydf <- rbind.fill(flatdata) 

Bu işi yapar, ancak yavaş ve biraz hata eğilimli. Bu yaklaşımdaki bir problem, verilerdeki yapı (nesne adları) hakkındaki bilgimi kullanmamamdır; bunun yerine verilerden anlaşılmaktadır. Bu, her kayıtta belirli bir özellik olmadığı zaman sorunlara yol açar. Bu durumda, NA değerlerine sahip bir sütun yerine, veri çerçevesinde hiç görünmez. Bu, aşağı yönde sorunlara yol açabilir. Örneğin, ben yeri damgası işlemek gerekir:

mydf$l.t <- structure(mydf$l.t/1000, class="POSIXct") 

Ancak bu l$t nesne yoktur olduğu bir veri kümesi halinde bir hata ile sonuçlanır. Ayrıca, as.data.frame ve rbind.fill her ikisi de işleri oldukça yavaşlatır. Örnek veri kümesi nispeten küçüktür. Daha iyi uygulama için herhangi bir öneriniz var mı? Sağlam bir çözüm, her zaman aynı sırayla aynı sütunlara sahip olan ve yalnızca satır sayısının değiştiği bir veri çerçevesine sahip olur.

Düzenleme: daha fazla meta veri içeren bir veri kümesinin altında. Boyutları daha büyüktür ve daha derinlemesine yuvalanır:

obj <- fromJSON("http://www.stat.ucla.edu/~jeroen/files/output.json", simplifyWithNames=FALSE, simplify=FALSE) 
+1

: Bir speedup çok fazla yol açmaz, ancak

obj <- fromJSON("http://www.stat.ucla.edu/~jeroen/files/output.json", simplifyWithNames=FALSE, simplify=FALSE) flatdata <- records2df(obj$data) 

Ayrıca 'gücün' belirli sütunlar sağlar: Hala bile bu hızlandırmak mümkün olmalıdır düşünüyorum python ;-) kullanarak deneyebilirsiniz ;-) – John

+1

Peki python'da bunu nasıl yaparsınız? :-) – Jeroen

cevap

1

Sadece açıklık sağlamak için, Josh ve Joshua'nın çözümünü, şimdiye kadar ortaya çıkan en iyi çözümün bir bileşimini ekliyorum.

flatlist <- function(mylist){ 
    lapply(rapply(mylist, enquote, how="unlist"), eval) 
} 

records2df <- function(recordlist, columns) { 
    if(length(recordlist)==0 && !missing(columns)){ 
     return(as.data.frame(matrix(ncol=length(columns), nrow=0, dimnames=list(NULL,columns)))) 
    } 
    un <- lapply(recordlist, flatlist) 
    if(!missing(columns)){ 
     ns <- columns; 
    } else { 
     ns <- unique(unlist(lapply(un, names))) 
    } 
    un <- lapply(un, function(x) { 
     y <- as.list(x)[ns] 
     names(y) <- ns 
     lapply(y, function(z) if(is.null(z)) NA else z)}) 
    s <- lapply(ns, function(x) sapply(un, "[[", x)) 
    names(s) <- ns 
    data.frame(s, stringsAsFactors=FALSE) 
} 

Bu işlev oldukça hızlıdır.

flatdata <- records2df(obj$data, columns=c("m", "doesnotexist")) 
5

Önceden veri alanı adları ve sınıfları bilginizden yararlanmanızı sağlayan bir çözüm. Ayrıca, as.data.frame numaralı çağrılara ve plyr numaralı telefonun rbind.fill() numaralı telefon numaralarına (hem zaman açısından yoğun) yapılan çağrılardan kaçınarak, örnek verilerinizde yaklaşık 60 kat daha hızlı çalışır.

cols <- c("id", "ls", "ts", "l.lo","l.tz", "l.t", "l.ac", "l.la", "l.pr", "m") 
numcols <- c("l.lo", "l.t", "l.ac", "l.la") 

## Flatten each top-level list element, converting it to a character vector. 
x <- lapply(obj$data, unlist) 
## Extract fields that might be present in each record (returning NA if absent). 
y <- sapply(x, function(X) X[cols]) 
## Convert to a data.frame with columns of desired classes. 
z <- as.data.frame(t(y), stringsAsFactors=FALSE) 
z[numcols] <- lapply(numcols, function(X) as.numeric(as.character(z[[X]]))) 

Düzenleme: yaklaşımım orijinal söz konusu edilenlerle aynı sonuçları verdiğini doğrulamak için, aşağıdaki testi yaptı. (Her iki durumda da ben faktör seviyelerinin siparişlerinde anlamsız farklılıkları önlemek için stringsAsFactors=FALSE set dikkat edin.)

flatdata <- lapply(obj$data, as.data.frame, stringsAsFactors=FALSE) 
mydf <- rbind.fill(flatdata) 
identical(z, mydf) 
# [1] TRUE 

Daha Edit: Sadece kayıt için

, burada alternatif bir versiyonu yukarıdaki otomatik ek olarak:

  1. tüm veri alanlarının adlarını bulur
  2. doğru sınıf

son data.frame sütun coerces sınıf/türü belirler.

dat <- obj$data 

## Find the names and classes of all fields 
fields <- unlist(lapply(xx, function(X) rapply(X, class, how="unlist"))) 
fields <- fields[unique(names(fields))] 
cols <- names(fields) 

## Flatten each top-level list element, converting it to a character vector. 
x <- lapply(dat, unlist) 
## Extract fields that might be present in each record (returning NA if absent). 
y <- sapply(x, function(X) X[cols]) 
## Convert to a data.frame with columns of desired classes. 
z <- as.data.frame(t(y), stringsAsFactors=FALSE) 

## Coerce columns of z (all currently character) back to their original type 
z[] <- lapply(seq_along(fields), function(i) as(z[[cols[i]]], fields[i])) 
+0

Teşekkürler. Yine de türleri değiştirmeyen bir çözümü tercih ederim. As.numeric/as.character potansiyel olarak hassasiyeti kaybedebilir, vb. – Jeroen

+0

Aynı endişeye sahiptim ama bahse girerim (özellikle JSON'un metin tabanlı bir standart olduğu göz önüne alındığında). Olası düzeltmelere hızlı bir şekilde bakacağım (özellikle de JOSON() 'dan seçenekler için), eğer bir şansım olsun diye anlamazsan. –

+0

Eh JSON, dizeleri ve sayıları açıkça ayırt eder. Sorunun JSON ayrıştırmada olduğunu sanmıyorum. İhtiyacımız olan şey, listeyi, yalnızca 1 seviye derinlikteki bir listeye daraltan bir liste (liste dışı) gibi bir şeydir (her şeyi dizelere dönüştüren bir vektör yerine). – Jeroen

2

Verilerin türleri hakkında hiçbir varsayımda bulunmaya çalışan bir girişim aşağıda verilmiştir. Bu, JoshOBrien'den biraz daha yavaş, ancak OP'nin orijinal çözümünden daha hızlı.

Joshua <- function(x) { 
    un <- lapply(x, unlist, recursive=FALSE) 
    ns <- unique(unlist(lapply(un, names))) 
    un <- lapply(un, function(x) { 
    y <- as.list(x)[ns] 
    names(y) <- ns 
    lapply(y, function(z) if(is.null(z)) NA else z)}) 
    s <- lapply(ns, function(x) sapply(un, "[[", x)) 
    names(s) <- ns 
    data.frame(s, stringsAsFactors=FALSE) 
} 

Josh <- function(x) { 
    cols <- c("id", "ls", "ts", "l.lo","l.tz", "l.t", "l.ac", "l.la", "l.pr", "m") 
    numcols <- c("l.lo", "l.t", "l.ac", "l.la") 
    ## Flatten each top-level list element, converting it to a character vector. 
    x <- lapply(obj$data, unlist) 
    ## Extract fields that might be present in each record (returning NA if absent). 
    y <- sapply(x, function(X) X[cols]) 
    ## Convert to a data.frame with columns of desired classes. 
    z <- as.data.frame(t(y)) 
    z[numcols] <- lapply(numcols, function(X) as.numeric(as.character(z[[X]]))) 
    z 
} 

Jeroen <- function(x) { 
    flatdata <- lapply(x, as.data.frame) 
    rbind.fill(flatdata) 
} 

library(rbenchmark) 
benchmark(Josh=Josh(obj$data), Joshua=Joshua(obj$data), 
    Jeroen=Jeroen(obj$data), replications=5, order="relative") 
#  test replications elapsed relative user.self sys.self user.child sys.child 
# 1 Josh   5 0.24 1.000000  0.24  0   NA  NA 
# 2 Joshua   5 0.31 1.291667  0.32  0   NA  NA 
# 3 Jeroen   5 12.97 54.041667  12.87  0   NA  NA 
+0

Teşekkür ederiz.'Recursive = FALSE' ile daha fazla iç içe geçmiş nesneler için işe yaramayacak olan bir sorun var mı? – Jeroen

+0

Ayrıca, kodunuzda 2 yazım hatası vardır; n '' '' '' '(bence) olmalıdır – Jeroen

+0

@Jeroen: yazım hatalarını işaretlediğiniz için teşekkür ederiz. Daha derinlemesine iç içe nesnelerin bir örneğiniz var mı? –

İlgili konular