Web scraping: extracción de Exportaciones FOB del Banco Central de Nicaragua¶
En esta entrada comparto una manera, de las tantas que puedan existir, de extraer una tabla de una página web. En esta ocasión me centraré en el cuadro de exportaciones publicado por el Banco Central de Nicaragua (BCN). Este es el enlace de la tabla con la que desarrollaré el ejemplo. La herramienta seleccionada para este trabajo es Python.
Antes de desarrollar el ejemplo, se debe tener en cuenta que las tablas publicadas por el BCN sufren de varios problemas, entre ellos:
- Desactualización de información (el más grave de todos!).
- Algunos cuadros padecen de serias deficiencias en la escritura html.
- Constantes problemas con el certificado SSL que dificultan la conexión con la web del BCN
- Tablas presentadas en formatos visualmente agradables, sin embargo, no cuentan con un servicio de despacho de información estructurada lista para el el análisis. Esto es algo que el BCN podría copiar de la Superintendencia de Bancos y Otras Instituciones Financiera (SIBOIF) el servicio web.
Teniendo en cuenta estos problemas, este post muestra cómo superar los puntos 2 y 3 (el 1 y 4 son parte de la volutad y compromiso del BCN con la ciudadanía).
En resumen, en este post presnto:
- Importar tablas directamente de la web (web scraping) del BCN superando el persistente problema del SSL
- Limpieza y ordenamiento de la base, esto implica:
- Crear las variables de Año y Mes correctamente para que permitan ser usadas como filtros
- Eliminar celdas combinadas (uso de celdas combinadas es una pésima decisión para presentación de datos)
- Nombrar correctamente al producto 'Camarón' como 'Camarón de Cultivo' y 'Camarón Marino' (no involucraría un esfuerzo olímpico que el BCN lo publicara así, de esta manera evitaría el uso de celdas combianas)
- Se eliminan filas con totales anuales
- Se normaliza la tabla dándole el esquema de diseño + semántica que caracteriza a los tidy data
- Para finalizar, se presentan dos visualizaciones sencillas: un treemap que muesta la composicón de la cesta exportable según los productos más importanes y un gráfico de evolución temporal del top 3 de productos de exportación
Ahora, manos a la obra!
# Importando los datos
import pandas as pd
import ssl
import re
import numpy as np
ssl._create_default_https_context = ssl._create_unverified_context
fob = pd.read_html("https://bcn.gob.ni/estadisticas/sector_externo/comercio_exterior/exportaciones/6-7.htm")
Una vez leído los datos y almacenados en la lista fob
se proce con la limpieza la base y creación de nuevas variables.
# Preparación de datos
fob2 = fob[0].loc[21:191, fob[0].columns != 1]
fob2.columns = fob[0].loc[3:3, fob[0].columns != 1].iloc[0]
fob2.reset_index(drop=True)
fob2['Año y mes'] = fob2['Año y mes'].astype(str)
fob2.insert(0, 'Año', fob2[['Año y mes']]) # creando la variable Año
fob2['Año'] = fob2['Año'].str.replace(r'[^0-9]+', '').str.replace(r'(\d{4}).*', r'\1')
fob2=fob2.replace('', np.NaN)
fob2['Año']=fob2.Año.fillna(method='ffill')
fob2 = fob2[~fob2['Año y mes'].str.contains("nan|\d+", regex=True)]
fob2 = fob2.rename(columns={'Año y mes': 'Mes'})
fob2 = fob2.rename(columns={'Camarón': 'Camarón Cultivo', 'Camarón': 'Camarón Marino'})
fob2 # Los datos ya casi están listos para el proceso de análisis.
Ahora, un poco de estructuración y visualización¶
Dado que se trata de valores exportados en millones de dólares, podría interesarnos saber la composición o importancia relativa de cada ítem sobre el total de las exportaciones durante los meses reportados de 2019 (enero-abril, les dije en el listado de problemas, la desactualización es el principal problema de las estadísticas económicas de Nicaragua). Para ello, se debe aplicar un poco de tratamiento a esta tabla y normalizarla, esto significa convertirla en una tabla tidy este concepto trató en un post anterior. Así que al darle diseño + semántica a esta tabla obtenemos lo siguiente:
fob3 = pd.melt(fob2, id_vars=['Año', 'Mes'], var_name='Producto', value_name="Monto US$")
fob3 # Ahora sí ya está lista para el proceso de análisis.
# asignando tipo apropiado a cada variable...
fob3['Año'] = fob3['Año'].astype(int)
fob3['Monto US$'] = fob3['Monto US$'].astype(float)
fob4 = fob3.loc[fob3['Año']==2019, ['Producto', 'Monto US$']] .groupby(['Producto'], as_index=False).sum().sort_values(by=['Monto US$'], ascending=False)
fob4['Porcentaje'] = round(100*fob4['Monto US$']/sum(fob4['Monto US$']), 1)
fob4['Label'] = fob4.Producto.astype('str') + '\n (' + fob4.Porcentaje.astype('str') + '%)'
fob4 = fob4[fob4.Porcentaje >1]
fob4
Composición de las exportaciones: el treemap¶
El treemap es un gráfico usado para demostrar cómo un todo está compuesto por sus diversas partes dando un orden jerárquico a cada parte según su peso en el todo. Es un conjunto de rectángulos con el tamaño de cada uno proporcional a la variable de interés, en nuestro caso, esto sería el rectángulo representa cada producto de exportación y el tamaño se asocial a su peso dentro del conjunto de toda la cesta exportada. Para más información sobre el treemap vea este sitio.
# ahora el treemap
import matplotlib
import squarify
import matplotlib.pyplot as plt
colors = ["#248af1", "#eb5d50", "#8bc4f6", "#8c5c94", "#a170e8", "#fba521"]
plt.figure(figsize=(15,9))
plt.rc('font', size=14)
squarify.plot(sizes=fob4['Monto US$'], label=fob4['Label'], alpha= .6, color=colors)
plt.title("Principales productos de exportación", fontsize = 20, fontweight="bold")
plt.annotate('Fuente: elaborado por Jilber Urbina con datos BCN \nNota 1: se omitieron todos los productos cuya participación sea menor o igual a 1% \nNota 2: entre paréntesis participación en el saldo total de exportación.',
(0,0), (0, -20), xycoords='axes fraction', textcoords='offset points', va='top')
plt.axis('off')
plt.show()
Comportamiento de los tres principales productos de exportación: plot de series de tiempo¶
top3 = fob3[fob3.Producto.isin(['Carne vacuna', 'Café', 'Oro']) ].copy()
# Meses en orden crononólogico, no alfabético
fob3['Mes'] = fob3['Mes'].astype('category')
top3['Mes'] = top3.Mes.cat.reorder_categories(['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'])
top3['mes'] = top3.Mes.cat.codes.astype('int')+1
top3['Fecha'] = top3.Año.astype('str') + '-' + top3.mes.astype('str').str.replace(r'(.{3}).*', r'\1')
from pandas.tseries.offsets import MonthEnd
top3.Fecha = pd.to_datetime(top3.Fecha)- MonthEnd(0) # creando fecha con fin de mes.
top3.set_index('Fecha', inplace=True)
top3.head(12)
top3[top3['Producto']=='Café']['Monto US$'].plot(figsize=(12,6), fontsize=14)
plt.xlabel('Año', fontsize=20)
plt.ylabel('Millones de dólares')
plt.title('Evolución temporal de las exportaciones de café Enero 2008 - Abril 2019')
plt.annotate('Fuente: elaborado por Jilber Urbina con datos BCN.',
(0,0), (0, 0), xycoords='axes fraction', textcoords='offset points', va='bottom')
plt.show()
top3.groupby('Producto', observed=True)['Monto US$'].plot( legend = 'True', figsize=(12,6), fontsize=14)
plt.xlabel('Año', fontsize=20)
plt.ylabel('Millones de dólares')
plt.title('Evolución temporal de las exportaciones Enero 2008 - Abril 2019')
plt.annotate('Fuente: elaborado por Jilber Urbina con datos BCN.',
(0,0), (0, 0), xycoords='axes fraction', textcoords='offset points', va='bottom')
plt.show()
Aquí finaliza esta entrega.