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.framerbind.fill都让事情变得很慢。示例数据集是一个相对较小的数据集。有什么更好的实施建议吗?一个健壮的解决方案总是会产生一个具有相同列的相同顺序的数据框,并且只有行数不同。

编辑:在具有更多元数据的数据集下方。它的尺寸更大,嵌套更深:

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

3 回答 3

5

这是一个解决方案,可让您利用您对数据字段名称和类的先验知识。此外,通过避免对's 的重复调用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 回答