从含重复节点名的XML提取数据转为R data.frame的方法
问题:将嵌套XML数据导入R的data.frame
我正尝试将XML文件中的数据导入到R的data.frame中。我对R较为熟悉,但从未接触过XML,因此对此领域感到陌生。
XML示例:
<ArchivedIncident ID="100"> <attributes> <entry> <key>TEST1</key> <value> <type>S</type> <value/> </value> </entry> <entry> <key>TEST2</key> <value> <type>S</type> <value>12</value> </value> </entry> <entry> <key>TEST3</key> <value> <type>T</type> <value>A</value> </value> </entry> <entry> <key>TEST4</key> <value> <type>S</type> <value/> </value> </entry> </attributes> </ArchivedIncident> <ArchivedIncident ID="101"> <attributes> <entry> <key>TEST1</key> <value> <type>S</type> <value>BLAH</value> </value> </entry> <entry> <key>TEST2</key> <value> <type>S</type> <value/> </value> </entry> <entry> <key>TEST3</key> <value> <type>T</type> <value/> </value> </entry> <entry> <key>TEST4</key> <value> <type>S</type> <value/> </value> </entry> </attributes> </ArchivedIncident>
期望的data.frame结构:
| ID | TEST1 | TEST2 | TEST3 | TEST4 |
|---|---|---|---|---|
| 100 | NA | 12 | A | NA |
| 101 | BLAH | NA | NA | NA |
已完成的操作:
我已经用xml2包读取了ID,代码如下:
require(xml2) doc <- read_xml("./data/file.xml") df <- data.frame( ID = xml_attr(xml_find_all(doc, ".//ArchivedIncident"), "ID") )
但我不清楚如何提取剩余数据。XML中存在多个名为entry、value、type的重复节点,我需要提取<key>的文本作为列名,提取对应<key>下的<value>子节点的文本作为值,同时将空值替换为NA。我曾在其他场景使用自定义函数,但不确定是否适用于此,现有自定义函数代码如下:
L <- xml_find_all(doc, ".//ArchivedIncident") FindAllValues <- function(node){ tmp <- lapply(L, xml_find_all, paste0(".//", node)) tmp <- lapply(tmp, xml_text) tmp[!sapply(tmp, function(y) length(y == 0))] <- NA return(tmp) }
希望能得到解决方法。
解决方案
我来帮你搞定这个问题,这里有两种实用的方法可以实现你想要的效果,都是基于你已经在使用的xml2包:
方法一:用purrr遍历节点生成数据框
这种方法更简洁,适合处理结构统一的XML,通过遍历每个ArchivedIncident节点,把每个节点转换成一行数据,最后合并成完整的data.frame:
library(xml2) library(purrr) library(dplyr) # 读取XML文档 doc <- read_xml("./data/file.xml") # 获取所有Incident节点 incidents <- xml_find_all(doc, ".//ArchivedIncident") # 定义函数处理单个Incident节点 process_incident <- function(incident_node) { # 提取ID属性 id <- xml_attr(incident_node, "ID") # 获取当前节点下的所有entry entries <- xml_find_all(incident_node, "./attributes/entry") # 提取每个entry的key和对应的value文本 keys <- xml_text(xml_find_all(entries, "./key")) values <- xml_text(xml_find_all(entries, "./value/value")) # 把空字符串替换成NA(xml_text对空节点会返回空字符串) values[values == ""] <- NA # 组合成一行数据框 row <- data.frame(ID = id, stringsAsFactors = FALSE) row[keys] <- values return(row) } # 处理所有节点并合并成最终数据框 result_df <- map_dfr(incidents, process_incident) # 查看结果 print(result_df)
运行后就能得到你期望的结构,每个Incident的ID和对应的键值对都正确映射,空值自动转为NA。
方法二:改进自定义函数提取指定key的值
如果你更习惯用循环或者需要单独处理某些key,可以改进你原有的函数,针对性提取每个key对应的值:
library(xml2) doc <- read_xml("./data/file.xml") # 获取所有Incident节点 L <- xml_find_all(doc, ".//ArchivedIncident") # 定义函数:从单个节点中提取指定key的value get_key_value <- function(node, target_key) { # 定位到对应key的entry下的value节点 value_node <- xml_find_first(node, paste0("./attributes/entry[key='", target_key, "']/value/value")) val <- xml_text(value_node) # 空值转NA if(val == "") NA else val } # 获取所有唯一的key,作为数据框的列名 all_keys <- unique(xml_text(xml_find_all(doc, ".//key"))) # 初始化数据框,先放入ID列 df <- data.frame( ID = xml_attr(L, "ID"), stringsAsFactors = FALSE ) # 遍历每个key,提取所有节点对应的值并添加为新列 for(key in all_keys) { df[[key]] <- sapply(L, get_key_value, target_key = key) } # 查看结果 print(df)
这个方法先确定所有需要的列名(唯一的key),然后逐个提取每个key在所有节点中的值,最终组合成完整的数据框,逻辑清晰,也能满足你的需求。
两种方法都能完美实现你想要的效果,你可以根据自己的习惯选择使用~
内容的提问来源于stack exchange,提问作者Wimpel
相关产品推荐
相关产品推荐

