Extracting subset of data from multilevel XML when variables have the same name

Viewed 96

I have a large amount of xml data which looks like this (only a fraction of data shown):

<weatherdata xmlns:xsi="http://www.website.com" xsi:noNamespaceSchemaLocation="www.website.com" created="2020-07-06T14:53:48Z">
  <meta>
    <model name="xxxxxx" termin="2020-07-06T06:00:00Z" runended="2020-07-06T09:48:31Z" nextrun="2020-07-06T16:00:00Z" from="2020-07-06T15:00:00Z" to="2020-07-08T12:00:00Z"/>
    <model name="xxxxxx" termin="2020-07-06T00:00:00Z" runended="2020-07-06T09:48:31Z" nextrun="2020-07-06T18:00:00Z" from="2020-07-08T13:00:00Z" to="2020-07-09T18:00:00Z"/>
    <model name="xxxxxx" termin="2020-07-06T00:00:00Z" runended="2020-07-06T09:48:31Z" nextrun="2020-07-06T18:00:00Z" from="2020-07-09T21:00:00Z" to="2020-07-12T00:00:00Z"/>
    <model name="xxxxxx" termin="2020-07-06T00:00:00Z" runended="2020-07-06T09:48:31Z" nextrun="2020-07-06T18:00:00Z" from="2020-07-12T06:00:00Z" to="2020-07-16T00:00:00Z"/>
  </meta>
  <product class="pointData">
    <time datatype="forecast" from="2020-07-06T15:00:00Z" to="2020-07-06T15:00:00Z">
     <location altitude="10" latitude="123" longitude="123">
      <temperature id="TTT" unit="celsius" value="18.8"/>
      <windDirection id="dd" deg="296.5" name="NW"/>
      <windSpeed id="ff" mps="5.8" beaufort="4" name="Laber bris"/>
      <globalRadiation value="524.2" unit="W/m^2"/>
      <humidity value="59.0" unit="percent"/>
      <pressure id="pr" unit="hPa" value="1022.9"/>
      <cloudiness id="NN" percent="22.7"/>
      <lowClouds id="LOW" percent="22.7"/>
      <mediumClouds id="MEDIUM" percent="0.0"/>
      <highClouds id="HIGH" percent="0.0"/>
      <dewpointTemperature id="TD" unit="celsius" value="10.6"/>
     </location>
    </time>
    <time datatype="forecast" from="2020-07-06T14:00:00Z" to="2020-07-06T15:00:00Z">
     <location altitude="10" latitude="123" longitude="123">
      <precipitation unit="mm" value="0.0" minvalue="0.0" maxvalue="0.0" probability="2.0"/>
      <symbol id="LightCloud" number="2"/>
     </location>
    </time>
    <time datatype="forecast" from="2020-07-06T16:00:00Z" to="2020-07-06T16:00:00Z">
     <location altitude="10" latitude="123" longitude="123">
      <temperature id="TTT" unit="celsius" value="19.4"/>
      <windDirection id="dd" deg="291.6" name="W"/>
      <windSpeed id="ff" mps="6.3" beaufort="4" name="Laber bris"/>
      <globalRadiation value="645.3" unit="W/m^2"/>
      <humidity value="55.7" unit="percent"/>
      <pressure id="pr" unit="hPa" value="1022.8"/>
      <cloudiness id="NN" percent="47.5"/>
      <lowClouds id="LOW" percent="47.5"/>
      <mediumClouds id="MEDIUM" percent="0.0"/>
      <highClouds id="HIGH" percent="0.1"/>
      <dewpointTemperature id="TD" unit="celsius" value="10.3"/>
     </location>
    </time>
    <time datatype="forecast" from="2020-07-06T15:00:00Z" to="2020-07-06T16:00:00Z">
     <location altitude="10" latitude="123" longitude="123">
      <precipitation unit="mm" value="0.0" minvalue="0.0" maxvalue="0.0" probability="2.2"/>
      <symbol id="PartlyCloud" number="3"/>
     </location>
    </time>

I want to extract the environmental data and place it in a pandas dataframe. I can do this using the following method:

import xml.etree.ElementTree as et
import pandas as pd

tree = et.parse(data.xml) #load in the data
root = tree.getroot() # get the element tree root

celsius = []
for x in root.iter('temperature'):
    value = x.attrib.get('value')
    celsius.append(value)

tempdf = pd.DataFrame(celsius,columns=['Temperature (C)'])        
tempdf

This gives me the following dataframe with 114 columns:

enter image description here

I can then repeat this for all the other interesting variables, and use pd.concat to join them together. The problem is that there are two 'time' variables for each of the 114 blocks of data, as 'precipitation' has a separate timestamp. When I try to parse the time data like so:

time = []
for x in root.iter('time'):
    value = x.attrib.get('to')
    time.append(value)

timedf = pd.DataFrame(time,columns=['Date & Time'])        
timedf

This gives double the amount of rows of data (228 instead of 114):

enter image description here

I can't join the time dataframe together with the others as there are double the amount of time rows than other variables. I would like to only select the first time variable from each of the 114 instances i.e. I want to keep time datatype="forecast" from="2020-07-06T15:00:00Z" to="2020-07-06T15:00:00Z" and skip the second one which is for precipitation time datatype="forecast" from="2020-07-06T14:00:00Z" to="2020-07-06T15:00:00Z". I have tried:

time = []
for x in root.iter('time')[0]:
    value = x.attrib.get('to')
    time.append(value)

But this doesn't work and I am not sure how I can do this when the variable names are identical within each hour of data. I would greatly appreciate any help with this.

1 Answers

Consider separately building a temperature data frame and precipitation data frame with concat and then merge concatenated versions together by common values in time and location nodes. And consider binding all attribute values together using list/dict comprehension.

import xml.etree.ElementTree as et
import pandas as pd

tree = et.parse('Input.xml')     # load in the data
root = tree.getroot()            # get the element tree root

temp_list = []; precip_list = []

for n, x in enumerate(root.iter('time')):
    # GET LIST OF DICTIONARIES OF ALL ATTRIBUTES
    x_list = [{i.tag+'_'+k:v for k,v in i.attrib.items()} for i in x.iter('*')] 

    # COMBINE INTO SINGLE DICTIONARY    
    x_dict = {k:v for d in x_list for k,v in d.items()}

    # BUILD DATA FRAME
    df = pd.DataFrame(x_dict, index=[0])
    
    # SEPARATELY SAVE TO LIST OF DATA FRAMES
    if 'temperature_unit' in df.columns: temp_list.append(df)
    if 'precipitation_unit' in df.columns: precip_list.append(df)
    

# MERGE CONCATENATED SETS BY COMMON VARS
df = pd.merge(pd.concat(temp_list),
              pd.concat(precip_list),
              on=['time_to', 'time_datatype',
                  'location_altitude', 'location_latitude',
                  'location_longitude'],
              suffixes=['_t','_p'])
Related