当子节点变化时将 XML 节点提取到数据框中

问题描述 投票:0回答:1

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

我的示例 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”):

(function (..., 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 xml dataframe xpath xml-parsing
1个回答
0
投票

这是一种使用

xml2
的可能方法。 我们首先创建一个
xml_nodeset
,每个授权一个节点,并将其转换为常规嵌套列表;当迭代列表中的拨款时,我们将提取特定项目,对于不存在的项目最终具有
NA
值,然后可以将生成的命名列表转换为 data.frame / tibble。

这里的假设之一是,缺少

.//RecipientForeignAddress/CountryCd
节点自动意味着现有且有效的
RecipientUSAddress
,因此一旦列表变成带有
bind_rows()
的小标题,
NA
列中的所有
CountryCd
值都将被视为非- 外国记录指标和
CountryCd
设置为
US

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 - 试图避免

xml2::to_list()
并通过反复传递授权节点来从 XML 树中提取特定节点
xml2::xml_find_first()
;速度慢了大约 20 倍。

© www.soinside.com 2019 - 2024. All rights reserved.