将SDMX-XML文件读入R中的数据框架

6
我想知道是否有人成功将SDMX-XML文件读入数据框中。我要读取的文件是https://www.ecb.europa.eu/stats/sdmx/icpf/1/data/pension_funds.xml(1mb)。 我将文件保存为“pensions_funds.xml”并尝试使用XML包读取它:
fileName <- system.file("pensions", "pensions_funds.xml", package="XML")
parsed<-xmlTreeParse("pension_funds.xml",getDTD=F)
r<-xmlRoot(parsed)
tmp = xmlSApply(r, function(x) xmlSApply(x, xmlValue))

上面几行基本上是按照这里的例子 http://www.omegahat.org/RSXML/gettingStarted.html 进行的,但我认为我首先需要忽略头部(我已经粘贴了我要读取的文件的前几页)。因此,我认为上述方法可能有效,但对于我的目的来说,它从错误的节点开始。我想通过时间段和参考区域索引 obs_values。

首先要做的是找到正确的节点并从那里开始,但我怀疑我可能在做一件愚蠢的事情,因为我对数据格式的知识有限,而且我不确定 XML 包是否可用于 SDMX-XML 文件。更聪明的人似乎已经尝试过这样做 http://opensdmxdevelopers.wikispaces.com/RSDMX 我在这里找不到这个软件包的下载链接 https://r-forge.r-project.org/projects/rsdmx/ (我看不到任何链接/下载部分,但也许我眼瞎),而且它似乎还处于早期阶段。rsdmx 的存在表明使用 xml 包读取 sdmx 可能不容易,因此我准备在这个阶段放弃,除非有人已经成功实现了这一点。实际上,我主要是想读取这个文件 http://www.ecb.europa.eu/stats/sdmx/bsi/1/data/outstanding_amounts.xml 但这是一个 10mb 的文件,所以我从小的文件开始。

编辑3 尝试使用 Mischa 的评论中的更改在大文件上运行 sgibb 的答案 library("XML")

url <- "http://www.ecb.europa.eu/stats/sdmx/bsi/1/data/outstanding_amounts.xml"

    sdmxHandler <- function() {
  ## data.frame which stores results
  data <- data.frame(stringsAsFactors=FALSE)
  ## counter to store current row
  i <- 1
  ## temp value to store current REF_AREA
  ## temp value to store current REF_AREA
  refArea <- NA
  bsItem <- NA
  bsCountSector <- NA

  ## handler subroutine for Obs tag
  Obs <- function(name, attr) {
    ## found an Obs tag and now fill data.frame
    data[i, "refArea"] <<- refArea
    data[i, "timePeriod"] <<- as.numeric(attr["TIME_PERIOD"])
    data[i, "obsValue"] <<- as.numeric(attr["OBS_VALUE"])
    data[i, "bsItem"] <<- bsItem
    data[i, "bsCountSector"] <<- bsCountSector
    i <<- i + 1
  }

  ## handler subroutine for Series tag
  Series <- function(name, attr) {
    refArea <<- attr["REF_AREA"]
    bsItem <<- as.character(attr["BS_ITEM"])
    bsCountSector <<- as.numeric(attr["BS_ITEM"])
  }
  return(list(getData=function() {return(data)},
              Obs=Obs, Series=Series))
}

## run parser
df <- xmlEventParse(file(url), handlers=sdmxHandler())$getData()
Specification mandate value for attribute OBS_VALUE
attributes construct error
Couldn't find end of Start Tag Obs line 15108
Premature end of data in tag Series line 15041
Premature end of data in tag DataSet line 91
Premature end of data in tag CompactData line 2
Error: 1: Specification mandate value for attribute OBS_VALUE
2: attributes construct error
3: Couldn't find end of Start Tag Obs line 15108
4: Premature end of data in tag Series line 15041
5: Premature end of data in tag DataSet line 91
6: Premature end of data in tag CompactData line 2
In addition: There were 50 or more warnings (use warnings() to see the first 50)

编辑2: sgibb的答案看起来很理想,在较小的文件上完全可以运行。我试图在上面运行它,但出现了一些问题。

url <- http://www.ecb.europa.eu/stats/sdmx/bsi/1/data/outstanding_amounts.xml

(10mb文件,原始链接已更正),唯一的修改是添加了两行代码:
data[i, "bsItem"] <<- as.character(attr["BS_ITEM"])

data[i, "bsCountSector"] <<- as.numeric(attr["BS_COUNT_SECTOR"])

这些是额外的ID变量,需要用于识别较大数据集中的行。它运行了几分钟,然后出现了以下错误:

错误:1:属性TIME_PE的规范命令值
2: 属性构造错误
3: 找不到Start Tag Obs的结束行20743
4: Series标签的数据过早结束行20689
5: DataSet标签的数据过早结束行91 6: CompactData标签的数据过早结束行2

此外:有50个以上的警告(使用warnings()函数查看前50个警告)
数据的基本格式似乎非常相似,所以我认为这可能有效。 10MB文件的基本格式如下:
    <Series FREQ="M" REF_AREA="AT" ADJUSTMENT="N" BS_REP_SECTOR="A" BS_ITEM="A20" MATURITY_ORIG="A" DATA_TYPE="1" COUNT_AREA="U2" BS_COUNT_SECTOR="0000" CURRENCY_TRANS="Z01" BS_SUFFIX="E" TIME_FORMAT="P1M" COLLECTION="E">
        <Obs TIME_PERIOD="1997-09" OBS_VALUE="275.3" OBS_STATUS="A" OBS_CONF="F"/>
        <Obs TIME_PERIOD="1997-10" OBS_VALUE="275.9" OBS_STATUS="A" OBS_CONF="F"/>
        <Obs TIME_PERIOD="1997-11" OBS_VALUE="276.6" OBS_STATUS="A" OBS_CONF="F"/>

编辑1:

期望的数据格式:

Ref_area    time_period obs_value

At  2006    118    
At  2007    119    
…    
Be  2006    101

这是第一部分数据。

    </Header>
    DataSet xsi:schemaLocation="https://www.ecb.europa.eu/vocabulary/stats/icpf/1 https://www.ecb.europa.eu/stats/sdmx/icpf/1/structure/2011-08-11/sdmx-compact.xsd" xmlns="https://www.ecb.europa.eu/vocabulary/stats/icpf/1"> 
<Group DECIMALS="0" TITLE_COMPL="Austria, reporting institutional sector Insurance corporations and pension funds - Closing balance sheet - All financial assets and liabilities - counterpart area World (all entities), counterpart institutional sector Total economy including Rest of the World (all sectors) - Credit (resources/liabilities) - Non-consolidated, Current prices - Euro, Neither seasonally nor working day adjusted - ESA95 TP table Not applicable" UNIT_MULT="9" UNIT="EUR" ESA95TP_SUFFIX="Z" ESA95TP_DENOM="E" ESA95TP_CONS="N" ESA95TP_DC_AL="2" ESA95TP_CPSECTOR="S" ESA95TP_CPAREA="A1" ESA95TP_SECTOR="S125" ESA95TP_ASSET="F" ESA95TP_TRANS="LE" ESA95TP_PRICE="V" ADJUSTMENT="N" REF_AREA="AT"/><Series ESA95TP_SUFFIX="Z" ESA95TP_DENOM="E" ESA95TP_CONS="N" ESA95TP_DC_AL="2" ESA95TP_CPSECTOR="S" ESA95TP_CPAREA="A1" ESA95TP_SECTOR="S125" ESA95TP_ASSET="F" ESA95TP_TRANS="LE" ESA95TP_PRICE="V" ADJUSTMENT="N" REF_AREA="AT" COLLECTION="E" TIME_FORMAT="P1Y" FREQ="A"><Obs OBS_CONF="F" OBS_STATUS="E" OBS_VALUE="112" TIME_PERIOD="2008"/><Obs OBS_CONF="F" OBS_STATUS="E" OBS_VALUE="119" TIME_PERIOD="2009"/><Obs OBS_CONF="F" OBS_STATUS="E" OBS_VALUE="125" TIME_PERIOD="2010"/><Obs OBS_CONF="F" OBS_STATUS="E" OBS_VALUE="127" TIME_PERIOD="2011"/></Series><Group D

忘记粘贴文件的初始行了。首先有一个我不需要的长标题,以这样开始: - Aidan
你需要哪些数据?你是想要每个系列或每个组的观测数据?能否举个例子说明一下你需要的数据和你想要保留关系的方式? - Mischa Vreeburg
这三个项目将识别一行: REF_AREA="AT" TIME_PERIOD="2008" OBS_VALUE="119" 因此,像下面这样的格式是理想的。 Ref_area time_period obs_value At 2006 118 At 2007 119 … Be 2006 101 … XML文件具有给定ref_area的所有数据,然后移动到下一个ref_area。该文件仅具有一个变量。我在末尾包含的第二个链接具有多个变量。在这种情况下,我需要一个带有变量ID的额外列,然后在每个变量中重复上述格式。 - Aidan
抱歉,那个格式不清晰,我已经编辑了原始帖子。 - Aidan
3个回答

5

RSDMX 似乎处于早期开发阶段。在我看来,目前还没有可用的软件包。但是,您可以使用 XML 软件包轻松实现它。我建议使用 xmlEventParse(有关详细信息,请参见 ?xmlEventParse):

编辑:适应 outstanding_amounts.xml 的更改要求
编辑2:添加 download.file

library("XML")

#url <- "http://www.ecb.europa.eu/stats/sdmx/icpf/1/data/pension_funds.xml"
url <- "http://www.ecb.europa.eu/stats/sdmx/bsi/1/data/outstanding_amounts.xml"

## download xml file to avoid download errors disturbing xmlEventParse
tmp <- tempfile()
download.file(url, tmp) 

sdmxHandler <- function() {
  ## data.frame which stores results
  data <- data.frame(stringsAsFactors=FALSE)
  ## counter to store current row
  i <- 1
  ## temp value to store current REF_AREA, BS_ITEM and BS_COUNT_SECTOR
  refArea <- NA
  bsItem <- NA
  bsCountSector <- NA

  ## handler subroutine for Obs tag
  Obs <- function(name, attr) {
    ## found an Obs tag and now fill data.frame
    data[i, "refArea"] <<- refArea
    data[i, "bsItem"] <<- bsItem
    data[i, "bsCountSector"] <<- bsCountSector
    data[i, "timePeriod"] <<- as.Date(paste(attr["TIME_PERIOD"], "-01", sep=""), format="%Y-%m-%d")
    data[i, "obsValue"] <<- as.double(attr["OBS_VALUE"])
    ## update current row
    i <<- i + 1
  }

  ## handler subroutine for Series tag
  Series <- function(name, attr) {
    refArea <<- attr["REF_AREA"]
    bsItem <<- attr["BS_ITEM"]
    bsCountSector <<- as.numeric(attr["BS_COUNT_SECTOR"])
  }

  return(list(getData=function() {return(data)},
              Obs=Obs, Series=Series))
}

## run parser
df <- xmlEventParse(tmp, handlers=sdmxHandler())$getData()

head(df)
#  refArea bsItem bsCountSector timePeriod obsValue
#1      DE    A20          2210      12053     39.6
#2      DE    A20          2210      12084     46.1
#3      DE    A20          2210      12112     50.2
#4      DE    A20          2210      12143     52.0
#5      DE    A20          2210      12173     52.3
#6      DE    A20          2210      12204     47.3

1
使用以下更改

临时变量以存储当前REF_AREA

refArea <- NA bsItem <- NA bsCountSector <- NASeries <- function(name, attr) { refArea <<- attr["REF_AREA"] bsItem <<- as.character(attr["BS_ITEM"])
bsCountSector <<- as.numeric(attr["BS_ITEM"])
} Obs <- function(name, attr) {

找到一个Obs标签,现在填充数据框

data[i, "refArea"] <<- refArea data[i, "timePeriod"] <<- as.numeric(attr["TIME_PERIOD"])
data[i, "obsValue"] <<- as.numeric(attr["OBS_VALUE"]) data[i, "bsItem"] <<- bsItem data[i, "bsCountSector"] <<- bsCountSector
- Mischa Vreeburg
谢谢Mischa。我已经使用您的更改编辑了帖子,但是我仍然遇到错误。也许是因为文件太大了? - Aidan
感谢@sgibb为我扩展您的代码。在完整文件上尝试后,我仍然会遇到像上面帖子中Edit3中的错误,尽管每次略有不同。大约处理两分钟后,我会收到错误消息。它说...规范要求属性O的值 属性构造错误 找不到Obs行14137的结束标记... 或者再次运行后,消息不完全相同:...规范要求属性OBS_的值 属性构造错误 找不到Obs行15549的结束标记。 - Aidan
这个程序运行缓慢的主要原因不是因为它逐行迭代地构建“data”对象,而是没有先分配足够大的对象,然后再填充它吗? - Peter Ellis
@PeterEllis:当然,但是您知道如何在读取完整个文件之前获取行数吗? - sgibb
显示剩余6条评论

3

软件包rsdmx允许您读取SDMX-ML文件并将其强制转换为data.frame。它现在托管在Github上,并且目前可在CRAN上使用,但如果您希望轻松地从GitHub安装它,请执行以下操作:

require("devtools")
install_github("rsdmx", "opensdmx")

应用于您的数据,您可以执行以下操作:
sdmx <- readSDMX("http://www.ecb.europa.eu/stats/sdmx/bsi/1/data/outstanding_amounts.xml")
df <- as.data.frame(sdmx)

更多示例可在 rsdmx wiki 中找到。
请注意,rsdmx的功能目前将xml对象加载到R中,作为由rsdmx实例化的SDMX R对象的一部分。在未来,我们希望研究如何使用xmlEventParse(如@sgibb上面建议的)来读取非常大的数据集。

0
library(XML)

xmlparsed <- xmlParse(file(url))

## obtain dataset node::
series_data <- getNodeSet(xmlparsed, "//Series")

if(length(series_data)==0){

datasetnode <- xmlChildren( xmlChildren(xmlparsed)[[1]])[[2]]

series_data<-xmlChildren(datasetnode)[ names(xmlChildren(datasetnode))=="Series"]

}

## prepare dataset

dataset.frame <- data.frame(matrix(ncol=3))
colnames(dataset.frame) <- c('REF_AREA', 'TIME_PERIOD', 'OBS_VALUE')
## loop over data

counter=1
for (i in 1: length(series_data)){
  if('Obs'%in%names(xmlChildren(series_data[[i]])) ){ ## To ignore empty //Series nodes
    for (j in 1: length(xmlChildren(series_data[[i]]))){
      dataset.frame[counter,1] <- xmlAttrs(series_data[[i]])['REF_AREA']
      dataset.frame[counter,2] <- xmlAttrs(series_data[[i]][[j]])['TIME_PERIOD']
      dataset.frame[counter,3] <- xmlAttrs(series_data[[i]][[j]])['OBS_VALUE']
      counter=counter+1
    }
  }
}


head(dataset.frame,5)

谢谢Mischa!请原谅我的无知,但是我该如何将url参数传递给第二行的文件呢?它似乎不接受 url <- "http://www.ecb.europa.eu/stats/sdmx/icpf/1/data/pension_funds.xml" 或者 url <- "http://www.ecb.europa.eu/stats/sdmx/bsi/1/data/outstanding_amounts.xml" (Error in file.exists(file) : invalid 'file' argument) - Aidan
抱歉,忘记了这一行代码的URL地址:url <- "http://www.ecb.europa.eu/stats/sdmx/icpf/1/data/pension_funds.xml" - Mischa Vreeburg
在counter=counter+1之前添加以下2行代码: dataset.frame[counter,4] <- xmlAttrs(series_data[[i]])['BS_ITEM'] dataset.frame[counter,5] <- xmlAttrs(series_data[[i]])['BS_ITEM']同时将ncol=3改为ncol=5,然后您的文件就可以正常工作了。 - Mischa Vreeburg
我可能错过了一些非常明显和基础的东西,但是在执行以下代码后仍然会出现错误: url <- "ecb.europa.eu/stats/sdmx/icpf/1/data/pension_funds.xml" 或者 url <- "http://www.ecb.europa.eu/stats/sdmx/icpf/1/data/pension_funds.xml" 并且 xmlparsed <- xmlParse(file(url)) file.exists(file)中的错误:无效的'file'参数。 - Aidan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接