do.call(rbind, ...)在R语言循环中表现异常,求原因及解决方案
问题描述
尝试通过R代码批量下载自行车出行数据,代码如下:
library(RJSONIO) first.day <- as.Date(paste0("202201","01"), "%Y%m%d") today <- Sys.Date() months <- format(seq(first.day, today,by="month"), "%Y%m") length(months) months.df.list <- vector("list", length = length(months)) for (m in months){ print(m) filename <- paste0("./monthly_bike_data/", m, ".csv") query <- paste0("https://data.urbansharing.com/bergenbysykkel.no/trips/v1/", substr(m,0,4),"/", substr(m,5,6), ".json") month.df.raw <- fromJSON(query) #retrieve data month.df <- do.call(rbind,month.df.raw) # convert to a proper dataframe write.csv(month.df, filename) }
运行class(month.df)得到结果:
[1] "list"
但在循环外单独运行以下代码时:
twentytwenty.raw <- fromJSON("https://data.urbansharing.com/bergenbysykkel.no/trips/v1/2020/01.json") twentytwenty <- do.call(rbind, twentytwenty.raw) class(twentytwenty)
得到的结果是:
[1] "matrix" "array"
仅有的差异是do.call(rbind, ...)运行在循环内还是外,但输出类型却不同,请问原因是什么,该如何修复?
原因分析
核心问题并非循环本身,而是不同月份的JSON返回结构存在差异:
- 2020年1月的JSON数据返回的是包含多个长度一致子列表的列表,
do.call(rbind, ...)可以将这些子列表按行绑定为矩阵。 - 循环中部分月份的JSON数据结构不同:可能是该月份只有单条记录,或JSON返回的是单个嵌套列表而非列表的列表。此时
do.call(rbind, 单个列表)不会执行行绑定操作,而是直接返回原列表。
另外,RJSONIO包对JSON的解析行为稳定性不足,面对不同结构的JSON时返回结果一致性较差,进一步放大了问题。
修复建议
1. 替换解析包为jsonlite(更稳定的JSON处理工具)
jsonlite对不同结构的JSON解析一致性更好,默认会将嵌套JSON转换为数据框:
library(jsonlite) first.day <- as.Date(paste0("202201","01"), "%Y%m%d") today <- Sys.Date() months <- format(seq(first.day, today,by="month"), "%Y%m") for (m in months){ print(m) filename <- paste0("./monthly_bike_data/", m, ".csv") query <- paste0("https://data.urbansharing.com/bergenbysykkel.no/trips/v1/", substr(m,1,4),"/", substr(m,5,6), ".json") # 直接解析为数据框,无需手动rbind month.df <- fromJSON(query) # 确保最终为数据框类型 if (!is.data.frame(month.df)) { month.df <- as.data.frame(month.df) } write.csv(month.df, filename, row.names = FALSE) }
2. 对RJSONIO的兼容修复
如果必须使用RJSONIO,可先检查解析后的数据结构再做绑定处理:
library(RJSONIO) first.day <- as.Date(paste0("202201","01"), "%Y%m%d") today <- Sys.Date() months <- format(seq(first.day, today,by="month"), "%Y%m") for (m in months){ print(m) filename <- paste0("./monthly_bike_data/", m, ".csv") query <- paste0("https://data.urbansharing.com/bergenbysykkel.no/trips/v1/", substr(m,1,4),"/", substr(m,5,6), ".json") month.df.raw <- fromJSON(query) # 判断是否为列表的列表,再选择绑定方式 if (all(sapply(month.df.raw, is.list))) { month.df <- do.call(rbind, lapply(month.df.raw, as.data.frame)) } else { month.df <- as.data.frame(month.df.raw) } write.csv(month.df, filename, row.names = FALSE) }
3. 使用更鲁棒的绑定函数
用dplyr::bind_rows替代do.call(rbind),它能自动处理不同结构的输入,统一返回数据框:
library(RJSONIO) library(dplyr) first.day <- as.Date(paste0("202201","01"), "%Y%m%d") today <- Sys.Date() months <- format(seq(first.day, today,by="month"), "%Y%m") for (m in months){ print(m) filename <- paste0("./monthly_bike_data/", m, ".csv") query <- paste0("https://data.urbansharing.com/bergenbysykkel.no/trips/v1/", substr(m,1,4),"/", substr(m,5,6), ".json") month.df.raw <- fromJSON(query) month.df <- bind_rows(month.df.raw) write.csv(month.df, filename, row.names = FALSE) }
内容的提问来源于stack exchange,提问作者Illimar Rekand
相关产品推荐
相关产品推荐

