Web scraping: extracción de Exportaciones FOB del Banco Central de Nicaragua

Scraping exportaciones fob

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:

  1. Desactualización de información (el más grave de todos!).
  2. Algunos cuadros padecen de serias deficiencias en la escritura html.
  3. Constantes problemas con el certificado SSL que dificultan la conexión con la web del BCN
  4. 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:

  1. Importar tablas directamente de la web (web scraping) del BCN superando el persistente problema del SSL
  2. 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!

In [332]:
# 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 fobse proce con la limpieza la base y creación de nuevas variables.

In [329]:
# 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.
Out[329]:
3 Año Mes Café Carne vacuna Langosta Oro Maní Ganado bovino Azúcar Queso Frijol Camarón Marino Camarón Marino Banano Harina de trigo Café instantáneo Tabaco Galletas Bebidas y rones
23 2008 Enero 18.5 16.2 7.1 7.1 9.1 2.8 0.7 4.6 5.0 1.5 1.8 0.6 0.9 1.9 0.3 0.9 3.1
24 2008 Febrero 23.6 16.4 3.4 6.4 9.5 2.8 8.2 4.9 4.1 0.4 0.6 0.7 0.5 1.9 0.6 0.7 3.7
25 2008 Marzo 34.9 15.6 4.0 6.9 8.4 1.9 17.7 5.6 10.1 0.1 1.2 0.9 0.7 1.8 0.5 0.6 3.0
26 2008 Abril 37.9 19.3 2.3 7.7 9.5 3.1 0.0 5.2 12.2 0.5 1.1 0.9 0.7 1.5 0.4 0.7 3.3
27 2008 Mayo 28.3 13.1 0.1 6.3 8.8 2.7 1.5 4.4 8.5 0.1 0.2 0.7 0.6 2.0 0.4 0.8 3.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
184 2018 Diciembre 9.0 39.1 4.0 28.7 5.2 0.9 4.1 9.3 5.0 3.7 0.7 0.8 0.3 1.0 0.2 0.0 4.8
188 2019 Enero 25.1 42.0 3.6 32.2 9.6 1.0 21.0 11.4 7.4 2.9 0.3 0.7 0.3 1.9 0.6 0.0 3.4
189 2019 Febrero 46.0 38.4 4.5 31.1 10.7 1.1 18.5 9.9 4.3 0.8 0.2 0.7 0.4 1.6 0.6 0.0 3.3
190 2019 Marzo 58.3 41.6 5.0 38.1 9.8 0.9 20.0 8.9 12.8 1.3 0.4 0.8 0.4 1.7 0.7 0.0 4.6
191 2019 Abril 61.3 37.2 0.7 32.1 8.1 0.6 25.8 8.0 10.8 0.4 0.0 0.6 0.3 1.5 0.8 0.1 5.4

136 rows × 19 columns

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:

In [44]:
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.
Out[44]:
Año Mes Producto Monto US$
0 2008 Enero Café 18.5
1 2008 Febrero Café 23.6
2 2008 Marzo Café 34.9
3 2008 Abril Café 37.9
4 2008 Mayo Café 28.3
... ... ... ... ...
2307 2018 Diciembre Bebidas y rones 4.8
2308 2019 Enero Bebidas y rones 3.4
2309 2019 Febrero Bebidas y rones 3.3
2310 2019 Marzo Bebidas y rones 4.6
2311 2019 Abril Bebidas y rones 5.4

2312 rows × 4 columns

In [335]:
# 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
Out[335]:
Producto Monto US$ Porcentaje Label
3 Café 190.7 26.0 Café\n (26.0%)
6 Carne vacuna 159.2 21.7 Carne vacuna\n (21.7%)
13 Oro 133.5 18.2 Oro\n (18.2%)
0 Azúcar 85.3 11.6 Azúcar\n (11.6%)
14 Queso 38.2 5.2 Queso\n (5.2%)
12 Maní 38.2 5.2 Maní\n (5.2%)
7 Frijol 35.3 4.8 Frijol\n (4.8%)
2 Bebidas y rones 16.7 2.3 Bebidas y rones\n (2.3%)
11 Langosta 13.8 1.9 Langosta\n (1.9%)

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.

In [342]:
# 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

In [336]:
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)
Out[336]:
Año Mes Producto Monto US$ mes
Fecha
2008-01-31 2008 Enero Café 18.5 1
2008-02-29 2008 Febrero Café 23.6 2
2008-03-31 2008 Marzo Café 34.9 3
2008-04-30 2008 Abril Café 37.9 4
2008-05-31 2008 Mayo Café 28.3 5
2008-06-30 2008 Junio Café 27.3 6
2008-07-31 2008 Julio Café 28.8 7
2008-08-31 2008 Agosto Café 26.4 8
2008-09-30 2008 Septiembre Café 17.2 9
2008-10-31 2008 Octubre Café 17.6 10
2008-11-30 2008 Noviembre Café 8.7 11
2008-12-31 2008 Diciembre Café 9.1 12
In [348]:
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()
In [350]:
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.