如何解决从嵌套XML数据的子集创建Pandas DataFrame?
我有一堆大型XML文件,其中包含成千上万条如下所示的记录:
XML示例:
<Report:Report xmlns:Report ="http://someplace.com">
<Id root="1234567890"/>
<Records value="10"/>
<ReportDate>2020-06-20</ReportDate>
<Record>
<Id root="001"/>
<Site>
<SiteData>
<SiteDataInfo1>
<Name code="12345"/>
<Status code="1"/>
</SiteDataInfo1>
<SiteDataInfo2>
<Primary code="A"/>
<Secondary code="B"/>
</SiteDataInfo2>
</SiteData>
</Site>
</Record>
<Record>
<Id root="002"/>
<Site>
<SiteData>
<SiteDataInfo1>
<Name code="789AB"/>
<Status code="2"/>
</SiteDataInfo1>
<SiteDataInfo2>
<Secondary code="D"/>
</SiteDataInfo2>
</SiteData>
</Site>
</Record>
<Record>
<Id root="003"/>
<Site>
<SiteData>
<SiteDataInfo1>
<Name code="CDEFG"/>
</SiteDataInfo1>
<SiteDataInfo2>
<Primary code="E"/>
</SiteDataInfo2>
</SiteData>
</Site>
</Record>
</Report:Report>
原始文件在每个记录元素下的各个深度都有数百个子元素-因此,在这里我对其进行了一些简化,同时保留了核心问题。我的目的是将XML读取到pandas数据框,以便可以使用类似的东西:
Record Id | Number | Status | Primary | Secondary
-------------------------------------------------
001 | 12345 | 1 | A | B
-------------------------------------------------
002 | 789AB | 2 | | D
-------------------------------------------------
003 | CDEFG | | E |
如您所见,大多数数据深达五个级别,并且每条记录中都不存在每个元素-但我需要能够处理上表所示的缺失元素。
我已经开始使用lxml了,但是我根本不知道自己在做什么!我知道我可以(非常笨拙)通过遍历树如下来提取属性或文本:
from lxml import etree as et
xtree = et.parse('file1.xml')
xroot = xtree.getroot()
for n in xroot.iter('Primary'):
print(n.attrib['code'])
但是...之后,我已经精疲力尽了。我不确定如何继续构建代码,以便可以确保所有翻译后的数据实际上都与其原始记录相对应。
任何善良的灵魂都可以提供任何指导,将我带离XML的黑暗山谷,进入阳光明媚的熊猫山吗?
任何帮助将不胜感激。
解决方法
见下文
import xml.etree.ElementTree as ET
xml = '''<Report:Report xmlns:Report ="http://someplace.com">
<Id root="1234567890"/>
<Records value="10"/>
<ReportDate>2020-06-20</ReportDate>
<Record>
<Id root="001"/>
<Site>
<SiteData>
<SiteDataInfo1>
<Name code="12345"/>
<Status code="1"/>
</SiteDataInfo1>
<SiteDataInfo2>
<Primary code="A"/>
<Secondary code="B"/>
</SiteDataInfo2>
</SiteData>
</Site>
</Record>
<Record>
<Id root="002"/>
<Site>
<SiteData>
<SiteDataInfo1>
<Name code="789AB"/>
<Status code="2"/>
</SiteDataInfo1>
<SiteDataInfo2>
<Secondary code="D"/>
</SiteDataInfo2>
</SiteData>
</Site>
</Record>
<Record>
<Id root="003"/>
<Site>
<SiteData>
<SiteDataInfo1>
<Name code="CDEFG"/>
</SiteDataInfo1>
<SiteDataInfo2>
<Primary code="E"/>
</SiteDataInfo2>
</SiteData>
</Site>
</Record>
</Report:Report>'''
data = []
root = ET.fromstring(xml)
records = root.findall('.//Record')
for record in records:
entry = {'id': record.find('./Id').attrib['root']}
entry['Number'] = record.find('./Site/SiteData/SiteDataInfo1/Name').attrib['code']
status = record.find('./Site/SiteData/SiteDataInfo1/Status')
entry['Status'] = status.attrib['code'] if status is not None else ''
primary = record.find('.//Primary')
entry['Primary'] = primary.attrib['code'] if primary is not None else ''
secondary = record.find('.//Secondary')
entry['Secondary'] = secondary.attrib['code'] if secondary is not None else ''
data.append(entry)
for entry in data:
print(entry)
,
我通常的方法是先使用xmlplain,然后使用json_normalize
so.xml 只是您的示例XML保存到文件中。
import pandas as pd
import xmlplain
from collections import OrderedDict
with open("so.xml") as f: js = xmlplain.xml_to_obj(f,strip_space=True,fold_dict=True)
df = pd.json_normalize(js['Report:Report'])
# work out columns that are info that do not form records
rootcols = [k for r in js['Report:Report'] for k in r.keys() for v in [r[k]] if not isinstance(v,OrderedDict)]
rootcols = [c for c in df.columns if c.split(".")[0] in rootcols]
# fill the columns that are "info" columns"
df.loc[:,rootcols] = df.loc[:,rootcols].fillna(method="ffill").fillna(method="bfill")
# drop rows that don't hold records
df = (df.dropna(how="all",subset=[c for c in df.columns if c not in rootcols])
.reset_index(drop=True)
# cleanup column names
.rename(columns={c:c.replace("Record.Site.SiteData.","") for c in df.columns})
)
print(df.to_string(index=False))
输出
@xmlns:Report Id.@root Records.@value ReportDate Record.Id.@root SiteDataInfo1.Name.@code SiteDataInfo1.Status.@code SiteDataInfo2.Primary.@code SiteDataInfo2.Secondary.@code
http://someplace.com 1234567890 10 2020-06-20 001 12345 1 A B
http://someplace.com 1234567890 10 2020-06-20 002 789AB 2 NaN D
http://someplace.com 1234567890 10 2020-06-20 003 CDEFG NaN E NaN
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。