Конвертировать файл динамического c XML в pandas Dataframe - PullRequest
2 голосов
/ 24 января 2020

спасибо за ваше время и ваше внимание. Это первый раз, когда я отправил сообщение в stackoverflow, поэтому извините, если я неуклюжий.

По сути, я кодирую в Python, но это первый раз, когда я анализирую файл XML. Я работал над этим в течение нескольких недель, но я заблокирован в одной точке или более.

Мой пример:

<record date="2019-12-02" time="19:13:40.091913" id="ALARM:Ctrl">
  <field name="system_inst">run</field>
  <field name="system_name">run0</field>
  <field name="flags">AB2</field>
  <field name="alias">CO31</field>
  <field name="group">Clean</field>
  <field name="priority">126</field>
  <field name="text">Open feedback loss</field>
  <field name="trtext">ouvert perdu</field>
  <field name="end">2019-12-02  19:13:40.392992</field>
  <field name="duration">0.301</field>
  <field name="ackts">2016-12-02  20:39:25.615704</field>
  <field name="user">toto</field>
  <field name="acktext">tata</field>
  <field name="ivtext">IvTxt</field>
</record>
<record date="2019-10-02" time="20:05:06.661429" id="ALARM:SFCReset">
    <field name="system_inst">run</field>
    <field name="system_name">run</field>
    <field name="flags">AB1</field>
    <field name="alias">G7</field>
    <field name="group">MB</field>
    <field name="priority">5</field>
    <field name="text">X1 not ready</field>
    <field name="trtext">x1 pas prêt</field>
    <field name="end">2019-12-02  20:05:01.187379</field>
    <field name="duration">0.99</field>
</record>

Количество записей является динамическим c и название поля бализа может меняться для каждой записи. Здесь мой код разбирает этот xml файл на pandas фрейм данных:

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


with open('Alarm.xml') as f:
    it = itertools.chain('<root>', f, '</root>')
    root = et.fromstringlist(it)

    df_cols = ["date", "time", "id", "system_inst","system_name", "flags", 'alias', 'group',
    'priority', 'text', 'trtext', 'end', 'duration', 'ackts', 'user', 'acktext', 'ivtext']
    rows = []

    system_inst = []
    system_name = []
    flags = []
    alias = []
    group = []
    priority = []
    Text = []
    trtext = []
    end = []
    duration = []
    ackts = []
    user = []
    acktext = []
    ivtext = []

    for record in root.findall('record'):

      ListDate = record.get('date')
      ListTime = record.get('time')
      ListId   = record.get('id')

      system_inst = record.getchildren()[0].text
      system_name = record.getchildren()[1].text
      flags = record.getchildren()[2].text
      alias = record.getchildren()[3].text
      group = record.getchildren()[4].text
      priority = record.getchildren()[5].text
      Text = record.getchildren()[6].text
      trtext = record.getchildren()[7].text
      end = record.getchildren()[8].text
      duration = record.getchildren()[9].text
      ackts = record.getchildren()[10].text
      user = record.getchildren()[11].text
      acktext = record.getchildren()[12].text
      ivtext = record.getchildren()[13].text

      rows.append({"date": ListDate, "time": ListTime, "id": ListId, "system_inst" : system_inst,
                  "system_name" : system_name, "flags" : flags, "alias" : alias, "group" : group,
                  "priority" : priority, "text" : Text, "trtext" : trtext, "end" : end,
                  "duration" : duration, "ackts" : ackts, "user" : user, "acktext" : acktext,
                  "ivtext" : ivtext})

    out_df = pd.DataFrame(rows, columns = df_cols)
    print(out_df)

Наконец, мне нужен такой фрейм данных: введите описание изображения здесь

Но для каждой записи у меня могут отсутствовать различные поля или нет, в этом случае мне бы хотелось, чтобы в кадре данных было «Нет». Но я не могу найти решение в данный момент.

Опять же, спасибо за ваше время и за вашу помощь.

1 Ответ

0 голосов
/ 25 января 2020

Это был очень интересный вопрос - спасибо за это! То, что вы хотите сделать, можно сделать с помощью l xml и xpath. Я постараюсь объяснить, как мы go вдоль:

from lxml import etree
import pandas as pd
records = """[your xml above]"""

root = etree.fromstring(records)
num_recs = int(root.xpath('count(//record)')) #count the number of records; 2, in this case
rec_grid = [[] for __ in range(num_recs)] #intitalize a list of sublists (2 in this case, with each sublist holding the relevant fields
fields = ["date","time","id","system_inst", "system_name", "flags", "alias", "group", "priority", "text", "trtext", "end", "duration", "ackts", "user", "acktext", "ivtext"]

paths = root.xpath('//record') #this contains a list of the 2 locations of the records
counter = 0
for path in paths:    
    for fld in fields[:3]: #the first 3 fields are in a different sub-location than the other 14             
        target = f'(./@{fld})' #using f-strings to populate the full path
        if path.xpath(target):
                rec_grid[counter].append(path.xpath(target)[0]) #we start populating our current sublist with the relevant info            
        else:
                rec_grid[counter].append('NA')

    for fld in fields[3:]:  # and now for the rest of the fields            
        target = f'(./field[@name="{fld}"]/text())'
        if path.xpath(target):
            rec_grid[counter].append(path.xpath(target)[0]) 
        else:
            rec_grid[counter].append('NA')
    counter+=1

df = pd.DataFrame(rec_grid, columns=fields) #now that we have our lists, create a df
df

Вывод слишком длинный, чтобы воспроизвести его здесь, но он выглядит как связанное изображение в вашем вопросе.

Вы могли бы упростить это (немного) с помощью вспомогательной функции, например, так:

def proc_target(path,target,counter):
    if path.xpath(target):
        rec_grid[counter].append(path.xpath(target)[0])               
    else:
        rec_grid[counter].append('NA')

и измените for l oop на:

for path in paths:    
    for fld in fields[:3]:              
        target = f'(./@{fld})'
        proc_target(path,target,counter)
    for fld in fields[3:]:              
        target = f'(./field[@name="{fld}"]/text())'
        proc_target(path,target,counter)        
    counter+=1      
...