【问题标题】:R: Converting a list of lists into a data frame (Census data)R:将列表列表转换为数据框(人口普查数据)
【发布时间】:2016-11-20 10:09:39
【问题描述】:

我正在尝试使用 Census API 下载特定表格并将其保存在数据框中。我已经成功下载了数据。我为调用组装了适当的 URL,然后使用包 'rjson' 将 URL 读入列表中。例如:

library(rjson)    

get <- c("B19081_002M")                                      # create vector of vars
datafile <- "http://api.census.gov/data/2009/acs5?"          # ACS 05-09
get <- paste0("get=NAME,", paste(get, collapse = ','))       # variables
geo <- "for=county:*"                                        # all counties
api_key <- "key=KEYHERE"                                     # API key
url <- paste0(datafile, paste(get, geo, api_key, sep = "&")) # creates url
data <- fromJSON(file = url)                                 # read into R

# To see an example of a problematic observation
# (this should return "Hinsdale County, Colorado")

data[[273]]

但是,我很难将其转换为数据框。 fromJSON() 函数创建一个列表对象。在大多数情况下,列表对象的元素是每个空间单元(例如上例中的县)的 chr 向量,并且该向量包含表信息和关联的元数据。在这种情况下,我使用以下工作示例中的方法将列表转换为数据框,其中每一行是不同的空间单位,每一列是不同的变量。

# Create fake data
x1 <- seq(1:5)
x2 <- rep(5,5)
l1 <- list(x1,x2)

# Convert to df
cols_per_row <- length(unlist(l1[1]))
test1 <- data.frame(matrix(unlist(l1), byrow = TRUE, ncol = cols_per_row))

print(test1) # success!

X1 X2 X3 X4 X5
1  1  2  3  4  5
2  5  5  5  5  5

但是当我对 list-in-list 对象使用相同的方法时(这是因为我从 API 中包含了不同的表而出现),我收到一个错误:

# Create fake data
x1 <- seq(1:5)
x2 <- rep(5,5)
x3 <- list(1,2,3,4,NULL)
l2 <- list(x1,x2,x3)

# Produces an error
cols_per_row <- length(unlist(l2[1]))
test2 <- data.frame(matrix(unlist(l2), byrow = TRUE, ncol = cols_per_row))

Warning message:
In matrix(unlist(l2), byrow = TRUE, ncol = cols_per_row) :
data length [14] is not a sub-multiple or multiple of the number of columns [5]

有人对此有解决方案吗?

  • 我注意到子列表仅在变量之一具有 NULL 值的情况下出现。
  • 在主列表的元素也是列表的情况下,子列表的长度等于作为向量的主列表元素的向量长度。

备注

  • 我不需要使用 fromJSON 并且欢迎使用可能使这更容易的替代方法。
  • 我不想使用“acs”包来完成这个,所以请不要建议使用它。我正在努力学习如何处理这个问题。

【问题讨论】:

  • as.data.frame(do.call(cbind, l2)) 将是一种典型的(虽然不是很好;你会丢失类型)方式。 purrr 对于处理列表很有用;你可以做类似l2 %&gt;% setNames(make.names(seq_along(.))) %&gt;% at_depth(2, ~.x %||% NA) %&gt;% map_df(unlist)的事情,虽然这可能不是最优雅的版本。

标签: json r list dataframe census


【解决方案1】:

我给你一个使用真实查询的技巧:

tmp <- data.frame(matrix(ncol=4))

for(i in 1:length(data)){
  if(length(t(unlist(data[i]))) == 4){
  tmp[i,] <- t(unlist(data[i]))
  } else{
    cat("Row number ", i, "has an abnormal length \n")
  }
}
Row number  273 has an abnormal length 
Row number  550 has an abnormal length 
Row number  1900 has an abnormal length 
Row number  2733 has an abnormal length 
Row number  2737 has an abnormal length 
Row number  2753 has an abnormal length
head(tmp)
1                               NAME B19081_002M state county
2     Aleutians East Borough, Alaska        8469    02    013
3 Aleutians West Census Area, Alaska        7691    02    016
4     Anchorage Municipality, Alaska         920    02    020
5         Bethel Census Area, Alaska        2414    02    050
6        Bristol Bay Borough, Alaska        9635    02    060

> 3,000 行中只有 6 行长度异常,但如果您想挽救这些行,可以通过添加另一行来使用占位符填充缺失值。

最后,别忘了第一行是标题,所以你可以把它写到你的data.frame的colnames

【讨论】:

  • 这是一个有用的技巧,可以识别列表中的有问题的元素。这本身不是问题的答案,但我会赞成,因为它很有帮助。注意:当返回对象时,为什么变量名出现在第一行也有点令人费解(在我自己的应用程序中,我已经自动修复了这个问题)。
【解决方案2】:

也许这就是你所追求的:

simplify2array(l2)

编辑:

上述解决方案无效。作为替代方案,我会用 NA 替换 NULL 值:

# Function to replace NULL values to NA values inside a list
listNull2Na <- function(l) sapply(l, function(x) ifelse(is.null(x), NA, x))

# Substitute NULL values in your list and get matrix:
l2 <- sapply(l2, listNull2Na)

【讨论】:

  • 我认为它不适用于真实数据。至少它在我的测试中没有(导致奇怪的形状——切断一个维度并给出 3,222 列)。如果我错了,请纠正我,OP。
  • @Hack-R 遗憾的是我没有真实数据,所以无法测试。我正在处理 OP 提供的玩具数据...@user3614648 这个函数由sapply() 在内部使用,关于它的信息不多。但是我们可以通过输入simplify2array 来检查它的代码——它与你所做的有点相似——取消列出数据,然后将其排列成一个合适的数组。适用于您的情况,因为每个子列表的长度相同。请参阅:lengths(l2)(即使为 NULL)。如果长度不同,可能无法正常工作。
  • @user3614648 我明白了。也许您可以详细说明什么是不正确的,或者添加一个不适合您的简单案例?
  • 经过反思,这并不完全正确。我开发了一种获取我想要的数据框的黑客方法,其中每列都是一个向量(而不是列表)。为了把它放到一个“整洁”的数据框中,我首先使用了:as.data.frame(t(simplify2array(data))),从 NxK 转置为 KxN。我意识到数据框中的每一列都是一个列表,这不是我想要的。因此,我通过以下方式将所有内容转换为字符类型:as.data.frame(sapply(as.data.frame(t(simplify2array(data)), stringsAsFactors = FALSE),as.character), stringsAsFactors = FALSE)。这做得很糟糕,我相信还有更有效的方法。
  • 感谢您的详细说明。问题是simplified2array(和do.call(cbind,l2))将列留在“列表”类中。所以你最终会得到一个 data.frame 列表。正在考虑一个简单的解决方案...
猜你喜欢
  • 2015-11-14
  • 2015-04-16
  • 1970-01-01
  • 1970-01-01
  • 2020-07-29
  • 1970-01-01
  • 1970-01-01
  • 2022-06-27
  • 2020-04-17
相关资源
最近更新 更多