当子节点发生变化时,将 XML 节点提取到数据帧中

Extracting XML nodes into a dataframe when child nodes vary

提问人:msl 提问时间:10/20/2023 最后编辑:msl 更新时间:10/21/2023 访问量:80

问:

我正在 R 中构建一个代码,以从 XML 格式的IRS990PF文件中提取授权数据。我构建了以下代码,该代码可以很好地为仅包含美国受助者的文件构建数据帧。但是,当有外国受助者时,代码不起作用,因为子节点的名称不同 - 导致列表大小不同。

我的示例XML文件是这个,用于亿滋国际基金会:https://projects.propublica.org/nonprofits/download-xml?object_id=202043219349103104

这是我的 R 代码:

grant_test <- XML::xmlParse(paste0(getwd(),"/xml_all/Mondelez-2019.xml")) %>%
  XML::xmlToList()

grants_df <- grant_test[["ReturnData"]][["IRS990PF"]][["SupplementaryInformationGrp"]] %>%    
  lapply(function(x) unlist(x)) %>% 
  as.data.frame() %>% 
  ## Choose the values to keep
  dplyr::filter(row.names(.) %in% c("RecipientBusinessName.BusinessNameLine1Txt","RecipientFoundationStatusTxt", "GrantOrContributionPurposeTxt","Amt")) %>%
  dplyr::select(-OnlyContriToPreselectedInd, -TotalGrantOrContriPdDurYrAmt,- TotalGrantOrContriApprvFutAmt) %>% 
  ## Transpose dataframe
  tibble::rownames_to_column(var="rowname") %>%
  data.table::transpose(make.names="rowname")

它运行良好,并给了我一个数据框,其中每行都是赠款,并且有 4 列,分别是受赠人姓名、受赠人状态、项目描述和金额。

问题是,当我将相同的代码应用于不同的 XML 时,包括美国以外的受助者(例如这个:https://projects.propublica.org/nonprofits/download-xml?object_id=202033179349101943),我在第三行(“as.data.frame”)出现此错误:

错误 (函数 (..., row.names = NULL, check.rows = FALSE, check.names = TRUE, : 参数表示不同的行数: 9, 8, 7, 10, 1

我知道这个错误是由于国际受赠方的赠款与国内赠款没有相同的子节点造成的。美国受助者将始终如下所示:

<GrantOrContributionPdDurYrGrp>
  <RecipientBusinessName>
    <BusinessNameLine1Txt>IMMUNARTES LLC</BusinessNameLine1Txt>
  </RecipientBusinessName>
  <RecipientUSAddress>
    <AddressLine1Txt>1463 E 53RD STREET FL 2</AddressLine1Txt>
    <CityNm>CHICAGO</CityNm>
    <StateAbbreviationCd>IL</StateAbbreviationCd>
    <ZIPCd>60615</ZIPCd>
  </RecipientUSAddress>
  <RecipientFoundationStatusTxt>NC: US NON-EXEMPT</RecipientFoundationStatusTxt>
  <GrantOrContributionPurposeTxt>PNEUMONIA</GrantOrContributionPurposeTxt>
  <Amt>198000</Amt>
<GrantOrContributionPdDurYrGrp>

而国际被授权者的条目通常如下例所示(地址节点的名称不同,国家/地区代码的附加子节点),但在“RecipientForeignAddress”下也可以有不同的子节点(例如,有些缺少省/州编号)。

<GrantOrContributionPdDurYrGrp>
  <RecipientBusinessName>
    <BusinessNameLine1Txt>IMMUNIMED INC</BusinessNameLine1Txt>
  </RecipientBusinessName>
  <RecipientForeignAddress>
    <AddressLine1Txt>62 SCURFIELD BLVD</AddressLine1Txt>
    <CityNm>WINNIPEG</CityNm>
    <ProvinceOrStateNm>MB</ProvinceOrStateNm>
    <CountryCd>CA</CountryCd>
    <ForeignPostalCd>R3Y1M5</ForeignPostalCd>
  </RecipientForeignAddress>
  <RecipientFoundationStatusTxt>NC: FOREIGN NON-EXEM</RecipientFoundationStatusTxt>
  <GrantOrContributionPurposeTxt>DISCOVERY AND TRANSLATIONAL SCIENCES</GrantOrContributionPurposeTxt>
  <Amt>50000</Amt>
<GrantOrContributionPdDurYrGrp>

所以,我的问题是:我怎样才能重新设计我的代码来解释“GrantOrContributionPdDurYrGrp”的不同实例之间列表长度的差异?我不需要数据框中的地址,但我想保留国际受助人的国家/地区代码,并将美国添加到国内受助人的该变量中。

R 数据帧 XPath XML 解析

评论

1赞 margusl 10/20/2023
欢迎来到 SO!请注意,这些链接不会持续太久,截至目前,这些链接已经过期。也许制作一个最小但有效的 XML 并将其包含在您的问题中,以便我们有一些可以测试的东西?完整和独立的问题对于有类似问题的其他人来说也更有价值。谢谢!
0赞 msl 10/20/2023
谢谢!我修复了链接,它们现在应该可以工作了。

答:

0赞 margusl 10/20/2023 #1

下面是 . 我们首先创建一个 ,每个授权一个节点,并将其转换为常规嵌套列表;当迭代列表中的授权时,我们将提取特定项目,对于不存在的项目最终具有值,生成的命名列表可以转换为 Data.frame / Tibble。xml2xml_nodesetNA

这里的一个假设是,缺少节点自动意味着存在且有效,因此一旦列表变成 ,列中的所有值都被视为非外部记录指标,并且这些值设置为.//RecipientForeignAddress/CountryCdRecipientUSAddressbind_rows()NACountryCdCountryCdUS

library(xml2)

grant_test_int  <- read_xml("https://projects.propublica.org/nonprofits/download-xml?object_id=202033179349101943")
xml_ns_strip(grant_test_int)

grants_df <- xml_find_all(grant_test_int, "/Return/ReturnData/IRS990PF/SupplementaryInformationGrp/GrantOrContributionPdDurYrGrp") |>
  as_list() |>
  lapply(\(grant_node) list(
    BusinessName     = grant_node$RecipientBusinessName$BusinessNameLine1Txt[[1]],
    FoundationStatus = grant_node$RecipientFoundationStatusTxt[[1]],
    Purpose          = grant_node$GrantOrContributionPurposeTxt[[1]],
    Amt              = as.numeric(grant_node$Amt[[1]]),
    CountryCd        = grant_node$RecipientForeignAddress$CountryCd[[1]]
    )) |> 
  dplyr::bind_rows() |>
  tidyr::replace_na(list(CountryCd = "US"))
grants_df
#> # A tibble: 3,712 × 5
#>    BusinessName                        FoundationStatus Purpose    Amt CountryCd
#>    <chr>                               <chr>            <chr>    <dbl> <chr>    
#>  1 1 FOR THE PLANET INC                PC: 509(A)(1)    GLOBAL… 5.07e4 US       
#>  2 1000 DAYS                           PC: 509(A)(1)    NUTRIT… 1   e6 US       
#>  3 100 BLACK MEN OF AMERICA INC        PC: 509(A)(2)    GLOBAL… 4   e5 US       
#>  4 2164 INC                            PC: 509(A)(2)    GLOBAL… 5   e4 US       
#>  5 4POINT0 SCHOOLS                     PC: 509(A)(1)    K-12 E… 1   e6 US       
#>  6 501 COMMONS                         PC: 509(A)(1)    PACIFI… 3   e5 US       
#>  7 A-ALPHA BIO INC                     NC: US NON-EXEM… SUPPOR… 6.87e4 US       
#>  8 AARON DIAMOND AIDS RESEARCH CENTER… PC: 509(A)(1)    HIV     7.55e5 US       
#>  9 ABT ASSOCIATES INC                  NC: US NON-EXEM… FAMILY… 1.41e6 US       
#> 10 ACADEMISCH MEDISCH CENTRUM          GOV: FOREIGN GO… HIV     1.53e6 NL       
#> # ℹ 3,702 more rows

第一次尝试 - https://stackoverflow.com/revisions/77332323/1 - 试图通过反复传递授权节点来避免并仅从 XML 树中提取特定节点;它慢了大约 20 倍。xml2::to_list()xml2::xml_find_first()

评论

1赞 msl 10/24/2023
这很有效,谢谢!