8

編集:この質問は時代遅れです。jsonliteパッケージは自動的にフラット化されます

私は、通常はJSONで、レコードベースのエンコーディングを持つオンラインデータストリームを扱っています。オブジェクトの構造(つまり、JSON内の名前)はAPIドキュメントからわかりますが、値はほとんどオプションであり、すべてのレコードに存在するわけではありません。リストには新しいリストを含めることができ、構造が非常に深い場合があります。これはいくつかのGPSデータの非常に簡単な例です:http://pastebin.com/raw.php?i=yz6z9t25"l"下の行では、 GPS信号がないため、オブジェクトが欠落していることに注意してください。

これらのオブジェクトをデータフレームにフラット化するためのエレガントな方法を探しています。私は現在次のようなものを使用しています:

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)

これでうまくいきますが、速度が遅く、エラーが発生しやすくなります。このアプローチの問題は、データの構造(オブジェクト名)に関する知識を使用していないことです。代わりに、データから推測されます。これは、特定のプロパティがすべてのレコードに存在しない場合に問題を引き起こします。この場合、NA値の列ではなく、データフレームにまったく表示されません。これは、ダウンストリームの問題につながる可能性があります。たとえば、場所のタイムスタンプを処理する必要があります。

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

l$tただし、オブジェクトが存在しないデータセットの場合、これによりエラーが発生します。さらに、as.data.frameとの両方がrbind.fill物事をかなり遅くします。サンプルデータセットは比較的小さいものです。より良い実装のための提案はありますか?堅牢なソリューションでは、常に同じ列が同じ順序であり、行数のみが変化するデータフレームが生成されます。

編集:より多くのメタデータを含むデータセットの下。サイズが大きく、ネストが深くなっています。

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

3 に答える 3

5

これは、データフィールド名とクラスに関する事前の知識を活用できるソリューションです。また、への繰り返しの呼び出しとのas.data.frame単一の呼び出し(両方とも時間のかかる)を回避することにより、サンプルデータで約60倍高速に実行されます。plyrrbind.fill()

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]])))

編集:私のアプローチで元の質問と同じ結果が得られることを確認するために、次のテストを実行しました。stringsAsFactors=FALSE(どちらの場合も、因子レベルの順序に意味のない違いがないように設定していることに注意してください。)

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

さらに編集:

念のため、上記の代替バージョンを自動的に追加します。

  1. すべてのデータフィールドの名前を検索します
  2. クラス/タイプを決定します
  3. 最終的なdata.frameの列を正しいクラスに強制します

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]))
于 2012-06-25T21:16:24.173 に答える
2

これは、データのタイプについて何も仮定しないことを試みる試みです。@JoshOBrienよりも少し遅いですが、OPの元のソリューションよりは高速です。

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
于 2012-06-25T23:20:48.717 に答える
1

わかりやすくするために、これまでに思いついた中で最高のJoshとJoshuaのソリューションの組み合わせを追加します。

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)
}

関数は適度に高速です。私はまだこれをスピードアップできるはずだと思います:

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

また、特定の列を「強制」することもできますが、それほど高速化されることはありません。

flatdata <- records2df(obj$data, columns=c("m", "doesnotexist"))
于 2012-06-26T22:28:05.560 に答える