Unnesting Lenex XML Files

Viewed 82

I'm looking to extract data from swimming results using python by un-nesting Lenex files (a form of xml).

The Lenex tree can be found here.
An example of a full file that I want to unnest can be found here.

More specifically, I would like to extract pieces of these files and convert them into dataframes - a table for each element (Athlete, Club, Result, etc.) where the columns are the attributes listed in the technical document linked above.

For example, the following code can get me a dataframe for Athletes with athleteid, lastname, firstname, gender, and birthdate as the columns:

    tree = ET.parse(filepath)
root = tree.getroot()

athletes = pd.DataFrame()
for ele in root.findall(".//ATHLETE"):
    df_dictionary = pd.DataFrame([ele.attrib])
    athletes = pd.concat([athletes, df_dictionary], ignore_index=True)

The result looks like this (I've swapped names for initials):

athleteid lastname firstname gender birthdate
0 145 E J M 2004-08-25
1 147 M C M 2003-12-16
2 146 H C F 2005-10-26
3 112 P J M 2004-11-22
4 111 F A F 2004-08-22

This is a good start, but each athlete is nested within a club. Also, within each athlete, is a collection of entries and results. Within each result, is a collection of splits. i.e. (Split < Result < Athlete < Club < Meet). I want to keep these associations when I turn this into dataframes.

My assumption is that the best way to do this is for the 'Split' dataframe to have an associated 'Resultid', for the 'Result' dataframe to have an associated 'Athleteid, and for the 'Athlete' dataframe to have an associated 'Clubid,' etc. Within any given meet, these relationships are true 'subsets,' i.e., it's a one-to-many relationship between results to splits, athletes to results, clubs to athletes, etc.

In terms of database design - Is this actually the most sensible way to do this?

More importantly, how would I add a column to my Athletes dataframe that includes their associated club name?

As far as a 'representative' example, I'm not sure if this is accurate or helpful, but I did my best to put something together:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<LENEX version="3.0">
  <CONSTRUCTOR name="Swiss Timing Swimming Data Handling" version="60.0.0.0">
    <CONTACT name="Swiss Timing Ltd." zip="CH-2606" city="Corgemont" email="info@swisstiming.com" internet="http://www.swisstiming.com" />
  </CONSTRUCTOR>
  <MEETS>
    <MEET name="Phillips 66 International Team Trials" city="Greensboro, NC" nation="USA" course="LCM" timing="AUTOMATIC">
      <SESSIONS>
        <SESSION number="1" date="2022-04-26" daytime="09:00">
          <EVENTS>
            <EVENT eventid="112" number="12" preveventid="12" gender="M" round="FIN" daytime="18:57">
              <SWIMSTYLE distance="200" relaycount="1" stroke="BACK" />
              <HEATS>
                <HEAT daytime="18:57" final="B" heatid="20112" number="2" />
                <HEAT daytime="19:02" final="A" heatid="10112" number="1" />
                <HEAT daytime="19:40" final="C" heatid="30112" number="3" />
              </HEATS>
    </EVENT>
    </EVENTS>
    </SESSION>
    </SESSIONS>
    <CLUBS>
    <CLUB name=“An Aquatic Club” shortname="AAC" code="AAC" nation="USA" type="">
        <ATHLETES>
            <ATHLETE athleteid="145" lastname=“SMITH” firstname="John” gender="M" birthdate="2000-07-25">
              <ENTRIES>
                <ENTRY entrytime="00:01:55.31" eventid="12" heat="1" lane="4" />
              </ENTRIES>
              <RESULTS>
                <RESULT eventid="112" place="12" lane="3" heat="2" heatid="20112" swimtime="00:02:00.20" reactiontime="+51">
                  <SPLITS>
                    <SPLIT distance="50" swimtime="00:00:28.31" />
                    <SPLIT distance="100" swimtime="00:00:58.62" />
                    <SPLIT distance="150" swimtime="00:01:29.61" />
                    <SPLIT distance="200" swimtime="00:02:00.20" />
                  </SPLITS>
                </RESULT>
    </RESULTS>  
    </ATHLETE>
    </ATHLETES>
    </CLUB>
    </CLUBS>
    </MEET>
</MEETS>
</LENEX>

The end goal should hopefully look something like this by modifying the code I'm currently using to generate the dataframe listed above:

athleteid lastname firstname gender birthdate club name
0 145 Smith John M 2004-07-25 An Aquatic Club

Thank you!

1 Answers

If I understand you correctly, you are looking for something like the below. In this case, I'm using lxml instead of ElementTree, because of the former's better xpath support:

from lxml import etree
tree = etree.parse(filepath)
root = tree.getroot()
rows = []
cols = ["athleteid","lastname","firstname","gender","birthdate","club name"]
for athlete in root.xpath('//ATHLETE'):
    club = athlete.xpath('./ancestor::CLUB/@name')
    row = athlete.xpath('./@*')
    row.append(club[0])
    rows.append(row)
df = pd.DataFrame(rows,columns=cols)
df

The output is

    athleteid   lastname    firstname   gender  birthdate   club name
0   164222     ANWARI       Fahim       M     1999-05-05      Afghanistan
1   160991     KADIU        Kledi       M     2003-10-28      Albania
2   110360     MERIZAJ      Nikol       F     1998-08-07      Albania

etc., etc.

Related