编辑:这个问题已经过时了。 jsonlite包会自动展平。
我正在处理具有基于记录的编码的在线数据流,通常采用JSON格式。从API文档中可以知道对象的结构(即JSON中的名称),但是,值大多是可选的,并不存在于每个记录中。列表可以包含新列表,结构有时非常深。以下是一些GPS数据的一个非常简单的示例:http://pastebin.com/raw.php?i=yz6z9t25。请注意,在较低的行中,由于没有GPS信号,"l"
对象丢失。
我正在寻找一种将这些对象展平为数据框的优雅方法。我目前正在使用这样的东西:
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)
答案 0 :(得分:5)
这是一个解决方案,可让您充分利用您之前对数据字段名称和类的了解。此外,通过避免重复调用as.data.frame
和单次调用plyr
的{{1}}(两者都是时间密集型),它的示例数据运行速度提高了约60倍。
rbind.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)
这是一种试图不对数据类型做出假设的尝试。它比@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
答案 2 :(得分: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"))