【问题标题】:Convert (possibly malformed) xml into Data Frame in R将(可能格式错误的)xml转换为R中的数据框
【发布时间】:2013-12-29 22:40:30
【问题描述】:

我正在尝试将美国联邦公报存档中的 xml 文件转换为数据框,其中每一行对应于特定操作(例如,通知、规则、提议的规则),并且每一列都包含与该操作相关的属性(例如,机构类型、主题等)。我尝试了以下方法:

> setwd("C:/Users/mwilliamson/Desktop/FedReg/2000/01/")
> url = "FR-2000-01-18.xml"
> doc <- xmlInternalTreeParse("FR-2000-01-18.xml")
> doc_list <- xmlToList(doc)
> library(plyr)
> j <- ldply(doc_list, data.frame)

但是,它返回一个错误:

Error in data.frame(SECTNO = "§ 831.502", SUBJECT = "Automatic separation;  
exemption.",  : 
arguments imply differing number of rows: 1, 0

似乎空白值的数量和变量长度的差异在 R 处理 XML 时产生了一个问题(我在这里可能错了,对 xml 包没有太多经验)。我认为可以使用模式 (.xsd) 文件来避免这种情况,但目前尚不清楚我如何将模式与 xmlToList 一起使用。本质上,我正在寻找将 xml 处理为我描述的数据框并用 NA 填充任何空白单元格的“最佳”方法。我已将架构和示例文件上传到:

https://www.dropbox.com/sh/pluje12t185w1v2/ys1xHzilQO

您能提供的任何帮助都会很棒!

更新:我也试过了:

xmlToDataFrame(doc, colClasses = character, homogeneous = NA)

但收到以下内容:

Error: duplicate subscripts for columns

再次感谢您提供的任何帮助。

更新:似乎 /AGENCY 节点是数据开始实际适合我尝试创建的格式的位置;但是,我似乎无法提取所有其余数据(即,我可以获得包含 115 条标识代理机构的记录的单列,但无法获得与这 115 条记录相关的其余信息)。我尝试了以下方法:

out <- getNodeSet(doc, "//*", fun=xmlToList)
df <- data.frame(do.call(rbind, out))
head(df)

但它似乎会导致 R 崩溃。我希望我的持续更新将激励某人伸出援助之手。再次感谢您提供的任何帮助。

【问题讨论】:

    标签: xml r xml-parsing


    【解决方案1】:

    这个 XML 是一团糟,我猜你需要分别解析每个动作。

    table(xpathSApply(doc, "//FEDREG/child::node()", xmlName))
        DATE  NEWPART       NO  NOTICES PRESDOCS PRORULES    RULES UNITNAME      VOL 
          12        6       12        1        3        1        1       12       12 
    
    table(xpathSApply(doc, "//NOTICES/child::node()", xmlName))
       NOTICE 
           92 
    

    使用 getNodeSet 获取通知

    z <- getNodeSet(doc, "//NOTICE")
    z[[1]]
    # check node names
    sapply(z, xmlSApply, xmlName)
    x <- xmlToDataFrame(z)
    dim(x)
    [1] 92  4
    

    所以这是从 PREAMB 和 SUPLINFO 中混杂了很多细节,所以您可能需要分别解析这些节点。

    如果只拿PREAMB,那也是一团糟……

    z2 <- getNodeSet(doc, "//NOTICE/PREAMB")
    # check node names and notice different formats
    sapply(z2, xmlSApply, xmlName)
    ## and count
    sort( table(unlist(sapply(z2, xmlSApply, xmlName))) )
    AUTH   BILCOD     NOTE GPOTABLE    STARS  PRTPAGE     DATE     FTNT      GPH  EFFDATE      ADD    DATES       FP      SIG   DEPDOC  EXTRACT      SUM 
       2        3        3        5        5        8       15       15       15       16       19       24       32       37       45       47       52 
     AGY   FURINF   SUBAGY      ACT   AGENCY  SUBJECT       HD        P 
      54       54       55       57       92       92      103      663 
    

    我在这里看到了三种不同的格式,所以 xmlToDataFrame 将适用于一些节点,但不是全部

    x <- xmlToDataFrame(z2[1:4])
    

    将这 10 列与代码中 ldply 的结果进行比较

    doc_list <-  getNodeSet(doc, "//NOTICE/PREAMB", fun=xmlToList)
    ## this returns 31 columns since it grabs every child node...
    j <- ldply(doc_list[1:4], data.frame)
    names(j)
    

    我认为有时最好只遍历 getNodeSet 结果并解析您需要的内容,如果节点不存在,请确保添加 NA(在此处使用 xp 函数)。请参阅 ?getNodeSet 创建子文档和使用免费修复内存泄漏,但对于最常见的格式可能是这样的。您可以为具有大量 HD、EXTRACT 和 P 标签的通知添加检查并获取其他列。

    xp <- function (doc, tag){
       n <- xpathSApply(doc, tag, xmlValue)
       if (length(n) > 0) 
          # paste multiple values?  BILCOD and probably others..
          paste0(n, collapse="; ") 
       else NA
    }
    
    
      z <- getNodeSet(doc, "//NOTICE")
      n <-length(z)
      notices <-vector("list",n)
      for(i in 1:n)
      {
         z2<-xmlDoc(z[[i]])
         notices[[i]] <- data.frame(
          AGENCY = xp(z2, "//AGENCY"),
          SUBAGY = xp(z2, "//SUBAGY"),
          SUBJECT = xp(z2, "//PREAMB/SUBJECT"),    ##  SUBJECT node in SECTION too, so it helps to be as specific as possible
          ACT= xp(z2, "//ACT"),
          SUM = xp(z2, "//SUM"),
          DATES = xp(z2, "//DATES"),
          ADD = xp(z2, "//ADD"),
          FURINF = xp(z2, "//FURINF"),
          SIG = xp(z2, "//PREAMB/SIG"),     ## SIG in SUPLINF too
          SUPLINF = xp(z2, "//SUPLINF"),
          FRDOC = xp(z2, "//FRDOC"),
          BILCOD = xp(z2, "//BILCOD"),
          DEPDOC = xp(z2, "//DEPDOC"),
          PRTPAGE = xp(z2, "//PRTPAGE"),
           stringsAsFactors=FALSE)
         free(z2)  
      }
      x <- do.call("rbind", notices)
      head(x)
      table(is.na(x$ACT) )
      FALSE  TRUE 
         57    35 
    

    您仍然有像 SUPLINF 这样的列,其中包含大量结构化数据 - 如果需要,您可以将其分解...

    table(xpathSApply(doc, "//NOTICE/SUPLINF/child::node()", xmlName))
    
    AMDPAR APPENDIX     AUTH   BILCOD     DATE  EXTRACT       FP     FTNT      GPH GPOTABLE       HD   LSTSUB        P  PRTPAGE      SIG     text 
         1        1       10        1        4       10       23       31       10       12      186        1      783        4       52        1 
    
    xpathSApply(doc, "//NOTICE/SUPLINF/GPH", xmlValue)
    [1] "EN18JA00.000" "EN18JA00.001" "EN18JA00.002" "EN18JA00.003" "EN18JA00.004" "EN18JA00.005" "EN18JA00.006" "EN18JA00.007" "EN18JA00.008" "EN18JA00.009"
     ## since SIG is in PREAMB and SUPLINF, you may want to parse that separately
     xpathSApply(doc, "//NOTICE/SUPLINF/SIG", xmlValue) 
    

    【讨论】:

    • 这很有帮助。谢谢你。我不清楚如何使用最后一段代码来分解 SUPLINFO。我统计了表格中有多少“NA”,但我不知道接下来的编程步骤是什么。感谢您提供任何其他信息。
    • 仔细观察,您可能对 suplinf 文本 blob 没问题。我在答案末尾添加了一些额外的代码来检查子节点,因此如果需要,您也可以获取其中的一个或多个,仅此而已。
    • 完美!这与循环相结合将满足我的需要。非常感谢
    猜你喜欢
    • 2019-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-28
    • 2021-11-30
    • 2015-01-05
    • 1970-01-01
    相关资源
    最近更新 更多