Leer y escribir archivos XML en Python con Pandas

En este artículo, aprenderá cómo leer datos de un archivo XML y cargarlos en un DataFrame de Pandas. También exportará datos de un Pandas DataFrame a un archivo XML.

Introducción

XML (Extensible Markup Language) es un lenguaje de marcado utilizado para almacenar datos estructurados. La biblioteca de análisis de datos de Pandas proporciona funciones para leer/escribir datos para la mayoría de los tipos de archivos.

Por ejemplo, incluye read_csv() y to_csv() para interactuar con archivos CSV. Sin embargo, Pandas no incluye ningún método para leer y escribir archivos XML.

En este artículo, veremos cómo podemos usar otros módulos para leer datos de un archivo XML y cargarlos en un Pandas DataFrame. También tomaremos datos de un Pandas DataFrame y los escribiremos en un archivo XML.

Leer XML con Pandas

Echemos un vistazo a algunas formas de leer datos XML y colocarlos en un DataFrame de Pandas.

Para esta sección, usaremos un conjunto de datos de entrada para cada script. Guarde el siguiente XML en un archivo llamado properties.xml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<root>
    <bathrooms>
        <n35237 type="number">1.0</n35237>
        <n32238 type="number">3.0</n32238>
        <n44699 type="number">nan</n44699>
    </bathrooms>
    <price>
        <n35237 type="number">7020000.0</n35237>
        <n32238 type="number">10000000.0</n32238>
        <n44699 type="number">4128000.0</n44699>
    </price>
    <property_id>
        <n35237 type="number">35237.0</n35237>
        <n32238 type="number">32238.0</n32238>
        <n44699 type="number">44699.0</n44699>
    </property_id>
</root>

Lectura con xml.etree.ElementTree

El módulo xml.etree.ElementTree viene integrado con Python. Proporciona funcionalidad para analizar y crear documentos XML. ElementTree representa el documento XML como un árbol. Podemos movernos por el documento usando nodos que son elementos y subelementos del archivo XML.

En este enfoque, leemos el contenido del archivo en una variable y usamos ET.XML() para analizar el documento XML desde la cadena constante. Recorreremos cada hijo y subhijo manteniendo una lista de los datos que contienen. Mientras tanto, escribir etiquetas secundarias para la columna DataFrame. Luego escribimos estos datos en un DataFrame.

Nota: Al leer datos de XML, tenemos que transponer el DataFrame, ya que los subelementos de la lista de datos están escritos en columnas.

Veamos el código para demostrar el uso de xml.etree.ElementTree:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import xml.etree.ElementTree as ET
import pandas as pd

xml_data = open('properties.xml', 'r').read()  # Read file
root = ET.XML(xml_data)  # Parse XML

data = []
cols = []
for i, child in enumerate(root):
    data.append([subchild.text for subchild in child])
    cols.append(child.tag)

df = pd.DataFrame(data).T  # Write in DF and transpose it
df.columns = cols  # Update column names
print(df)

El código anterior producirá esta salida (varía según el archivo de entrada utilizado):

1
2
3
4
  bathrooms       price property_id
0       1.0   7020000.0     35237.0
1       3.0  10000000.0     32238.0
2       nan   4128000.0     44699.0

Lectura con lxml

La biblioteca lxml es un enlace de Python para las bibliotecas C libxml2 y libxslt. También amplía el módulo ElementTree nativo. Como se trata de un módulo de terceros, deberá instalarlo con pip de esta manera:

1
$ pip install lxml

A diferencia de ElementTree, no leemos los datos del archivo ni los analizamos. Podemos usar directamente objectify.parse() y darle la ruta al archivo XML. Para obtener el elemento raíz, usaremos getroot() en los datos XML analizados.

Ahora podemos recorrer los elementos secundarios del nodo raíz y escribirlos en una lista de Python. Como antes, crearemos un DataFrame usando la lista de datos y transpondremos el DataFrame.

Veamos el código para crear un Pandas DataFrame usando lxml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from lxml import objectify
import pandas as pd

xml_data = objectify.parse('properties.xml')  # Parse XML data
root = xml_data.getroot()  # Root element

data = []
cols = []
for i in range(len(root.getchildren())):
    child = root.getchildren()[i]
    data.append([subchild.text for subchild in child.getchildren()])
    cols.append(child.tag)

df = pd.DataFrame(data).T  # Create DataFrame and transpose it
df.columns = cols  # Update column names
print(df)

Si ejecutamos esto en el intérprete de Python, vemos el siguiente resultado:

1
2
3
4
  bathrooms       price property_id
0       1.0   7020000.0     35237.0
1       3.0  10000000.0     32238.0
2       nan   4128000.0     44699.0

Lectura con xmltodict

El módulo xmltodict convierte los datos XML en un diccionario de Python como sugiere el nombre. Al igual que lxml, este es un módulo de terceros que necesitamos instalar con pip:

1
$ pip install xmltodict

Como hemos hecho antes, leemos el contenido XML en una variable. Damos estos datos en el método parse() que devuelve un diccionario de los datos XML. Será un diccionario anidado que tiene elementos y subelementos del archivo XML. Podemos recorrer los elementos y escribirlos en una lista de datos que usamos para crear un DataFrame.

Echemos un vistazo al código para analizar datos XML para crear un DataFrame usando xmltodict:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import xmltodict
import pandas as pd

xml_data = open('properties.xml', 'r').read()  # Read data
xmlDict = xmltodict.parse(xml_data)  # Parse XML

cols = xmlDict['root'].keys()
data = []

for i in xmlDict['root']:
    child = xmlDict['root'][i]
    data.append([child[subchild]['#text'] for subchild in child])

df = pd.DataFrame(data).T  # Create DataFrame and transpose it.
df.columns = cols
print(df)

Si ejecutamos el código anterior, podemos ver el resultado como:

1
2
3
4
  bathrooms       price property_id
0       1.0   7020000.0     35237.0
1       3.0  10000000.0     32238.0
2       nan   4128000.0     44699.0

Nota: No se recomienda la biblioteca xmltodict para archivos XML de gran tamaño, ya que muchos desarrolladores han observado caídas en el rendimiento. La biblioteca lxml se considera la más rápida para trabajar con XML, incluso más rápida que la xml.etree.ElementTree incluida.

Use lo que sea mejor para su proyecto, y si el rendimiento es crítico, debe ejecutar pruebas con cada biblioteca.

Escribir XML con pandas

Veamos varias formas de escribir Pandas DataFrame en un archivo XML. Cada secuencia de comandos que usamos a continuación creará un nuevo archivo llamado coordinates.xml con los siguientes contenidos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<root>
  <A>
    <X>1.3</X>
    <Y>2.6</Y>
    <Z>2.1</Z>
  </A>
  <B>
    <X>1.4</X>
    <Y>1.4</Y>
    <Z>5.6</Z>
  </B>
  <C>
    <X>5.2</X>
    <Y>4.6</Y>
    <Z>4.6</Z>
  </C>
</root>

Escritura con la función write() integrada

Podemos usar la función write() incluida para archivos para escribir un DataFrame como un archivo XML. Para lograr esto, mantendremos una lista de los datos XML, de modo que cada elemento represente una línea en XML. Luego iteraremos sobre el DataFrame y escribiremos los datos con las etiquetas de apertura y cierre apropiadas de XML en la lista de datos.

Una vez que esté completo, iteramos sobre la lista una vez más para escribir los datos en el archivo XML. Aquí está el código que muestra el uso de write():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import pandas as pd

df = pd.DataFrame([[1.3, 1.4, 5.2],
                   [2.6, 1.4, 4.6],
                   [2.1, 5.6, 4.6]],
                  columns=['A', 'B', 'C'],
                  index=['X', 'Y', 'Z'])

xml_data = ['<root>']
for column in df.columns:
    xml_data.append('<{}>'.format(column))  # Opening element tag
    for field in df.index:
        # writing sub-elements
        xml_data.append('<{0}>{1}</{0}>'.format(field, df[column][field]))
    xml_data.append('</{}>'.format(column))  # Closing element tag
xml_data.append('</root>')

with open('coordinates.xml', 'w') as f:  # Writing in XML file
    for line in xml_data:
        f.write(line)

Ejecutar este código generará un archivo llamado coordinates.xml en el directorio actual.

Escribiendo archivos XML con xml.etree.ElementTree

El módulo predeterminado xml.etree.ElementTree se puede usar para almacenar datos como XML y convertirlos en una cadena para que se puedan escribir en un archivo.

Nuestro primer paso es crear el elemento raíz. Luego iteramos sobre las columnas y filas del DataFrame, agregándolos como elementos y subelementos en el ElementTree. Luego convertimos los datos del objeto ElementTree en una cadena binaria usando el método tostring().

Como los datos XML son una cadena binaria, los descodificamos en UTF-8 antes de escribirlos en el archivo.

El siguiente código usa xml.etree.ElementTree para escribir un DataFrame como un archivo XML:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import xml.etree.ElementTree as ET
import pandas as pd

df = pd.DataFrame([[1.3, 1.4, 5.2],
                   [2.6, 1.4, 4.6],
                   [2.1, 5.6, 4.6]],
                  columns=['A', 'B', 'C'],
                  index=['X', 'Y', 'Z'])
header = df.columns

root = ET.Element('root')  # Root element

for column in df.columns:
    entry = ET.SubElement(root, column)  # Adding element
    for row in df.index:
        schild = row
        child = ET.SubElement(entry, schild)  # Adding sub-element
        child.text = str(df[column][schild])

xml_data = ET.tostring(root)  # binary string
with open('coordinates.xml', 'w') as f:  # Write in file as utf-8
    f.write(xml_data.decode('utf-8'))

Al igual que antes, ejecutar este script creará un archivo coordinates.xml con el resultado esperado.

Escribiendo archivos XML con lxml

Usar lxml es similar a cómo usamos xml.etree.ElementTree. Comenzamos creando un objeto etree con el elemento raíz del archivo que estamos creando. Luego iteramos sobre el DataFrame, agregando columnas y filas como elementos y subelementos del árbol. Por último, usamos el método tostring() para obtener etree como una cadena binaria. Escribimos el archivo después de decodificar la cadena binaria en UTF-8.

Aquí está el código para escribir DataFrame como XML usando lxml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from lxml import etree as et
import pandas as pd

root = et.Element('root')  # Create root element
df = pd.DataFrame([[1.3, 1.4, 5.2],
                   [2.6, 1.4, 4.6],
                   [2.1, 5.6, 4.6]],
                  columns=['A', 'B', 'C'],
                  index=['X', 'Y', 'Z'])

for column in df.columns:
    entry = et.SubElement(root, column)  # Writing element
    for row in df.index:
        schild = row
        child = et.SubElement(entry, schild)  # Writing sub-elements
        child.text = str(df[column][schild])

xml_data = et.tostring(root)  # binary string
with open('coordinates.xml', 'w') as f:  # Write in XML file as utf-8
    f.write(xml_data.decode('utf-8'))

Al completar con éxito, verá coordinates.xml con las coordenadas XML.

Conclusión

Este tutorial muestra varias formas en que podemos leer y escribir datos XML con Pandas DataFrames. Puede leer datos con el módulo integrado xml.etree.ElementTree, así como con dos módulos de terceros: lxml y xmltodict.

Para escribir un DataFrame de Pandas en un archivo XML, hemos utilizado el archivo convencional write() con listas, el módulo xml.etree.ElementTree y lxml. Dado que manipular cadenas XML directamente para escribir un archivo es más propenso a errores humanos, xml.etree.ElementTree y lxml son las soluciones preferibles para exportar un DataFrame a XML. XML.