Una introducción práctica a SDMX y a la automatización de la extracción de datos de organismos internacionales

dic. 7, 2025·
Roger Mario López Justiniano
Roger Mario López Justiniano
· 50 min de lectura

Esta es una entrada que quería escribir hace algún tiempo y que había prometido en otro post similar hace algunas semanas.

Cuando uno comienza a realizar análisis macroeconómicos -tanto para análisis como para presentaciones- el flujo de trabajo usual implica ir a la fuente de información -usualmente algún organismo internacional que agrega la información-, descargarse el conjunto de datos y luego importarlo en la herramienta de preferencia para proceder con el análisis.

Este flujo de trabajo, sin embargo, tiene bastantes inconvenientes. En primer lugar, no es replicable: existen varios pasos manuales que no quedan estrictamente documentados. Además, no es escalable, ya que si hubiese alguna nueva variable necesaria para el análisis y no tomada en cuenta en la primera extracción de datos el proceso debe repetirse nuevamente desde el inicio.

Por tanto, para el analista aplicado, se hace imprescindible automatizar el proceso de extracción de datos, de tal manera que la mayor parte del trabajo esté centrada en el análisis y menos en la plomería para contar con la información necesaria.

En este sentido, lo óptimo es contar con una API (del inglés, application programming interfase) que permita encontrar, entender y extraer la información rápidamente, de tal manera que el análisis sea totalmente reproducible y escalable. Por ejemplo, en entradas anteriores mostré cómo, de manera simplificada, acceder a la API de la CEPAL y a la del World Economic Outlook que publica el Fondo Monetario Internacional (FMI) para automatizar el flujo.

En esta entrada, sin embargo, quiero expandir las fuentes de información y ofrecer, al menos conceptualmente, un método general que permita acceder a información de organismos internacionales de forma sistemática. Esto es posible gracias a un modelo de información o protocolo estandarizado denominado SDMX y que permite acceder a los datos de forma más o menos homogénea.

En lo que sigue de esta entrada se revisará conceptualmente el modelo SDMX, se mostrará un flujo relativamente simplificado para extraer información y se mostrarán algunos ejemplos aplicados. Finalmente, para el caso del FMI, se ha creado una librería denominda imfapi que facilita la extracción de datos en base a la API SDMX que tiene actualmente el FMI y que se explora a continuación.

Qué es SDMX

Como se indica en su página web oficial:

SDMX, que significa Statistical Data and Metadata eXchange (Intercambio de Datos y Metadatos Estadísticos), es un estándar ISO diseñado para describir datos y metadatos estadísticos, normalizar su intercambio y mejorar su distribución eficiente entre organizaciones estadísticas y similares.

SDMX está patrocinado por ocho organizaciones internacionales:

  1. Banco de Pagos Internacionales (BIS),
  2. Banco Central Europeo (BCE),
  3. Oficina Estadística de la Unión Europea (Eurostat),
  4. Organización Internacional del Trabajo (OIT),
  5. Fondo Monetario Internacional (FMI),
  6. Organización para la Cooperación y el Desarrollo Económicos (OCDE),
  7. División de Estadística de las Naciones Unidas (UNSD) y
  8. Banco Mundial (WB)

Cada una de estas organizaciones ha implementado SDMX en sus sistemas de información estadística y provee APIs con esta estructura para su consulta. Esto se puede ver en la tabla a continuación:

ORGAPI SDMXDocumentación
BIShttps://stats.bis.org/api/v1BIS SDMX Tech Spec
BCEhttps://data-api.ecb.europa.eu/serviceECB API Overview
Eurostathttps://ec.europa.eu/eurostat/api/disseminationEurostat API
OIThttps://sdmx.ilo.org/restILOSTAT SDMX User Guide
FMIhttps://sdmxcentral.imf.org/sdmx/v2/IMF Data APIs
OCDEhttps://sdmx.oecd.org/public/rest/OECD Data API Explainer
UNSDhttp://data.un.org/WS/rest/UN SDMX / SDG API Manual
WBhttps://api.worldbank.org/v2/sdmx/rest/World Bank SDMX API Queries

Así, el analista puede acceder a una amplia gama de datos macroeconómicos y sociales de diversas fuentes internacionales utilizando un enfoque estandarizado.

Flujo de trabajo general para extraer datos SDMX

Aunque cada API puede tener sus particularidades y el modelo SDMX es bastante amplio, el flujo de trabajo general para extraer datos utilizando APIs SDMX puede resumirse en cuatro pasos. Cada uno corresponde a un concepto clave dentro de la arquitectura SDMX y permite entender cómo están organizados y expuestos los datos por los organismos estadísticos.

  1. Identificación del dataflow. El primer paso consiste en identificar el dataflow. Un dataflow es, conceptualmente, el contenedor de un conjunto de datos temático. Funciona como la “base de datos” o el dominio estadístico que publica una institución (por ejemplo, estadísticas monetarias, balances bancarios, tipos de cambio o indicadores macroeconómicos). Identificar el dataflow adecuado implica revisar la lista de dataflows disponibles en la API y seleccionar aquel que contiene la información relevante para el análisis que se desea realizar. Sin este paso, no es posible avanzar hacia una consulta válida, ya que todo dataset SDMX parte de un dataflow específico.
  2. Obtención de la estructura de datos. Una vez seleccionado el dataflow, el siguiente paso es obtener la estructura de datos asociada, conocida como Data Structure Definition (DSD). Conceptualmente, la DSD es el esquema lógico del conjunto de datos: describe cómo están organizadas las observaciones y qué elementos intervienen en su identificación. La DSD especifica las dimensiones (como país, indicador, frecuencia o moneda), que son variables clave que clasifican los datos y determinan cómo se combinan para formar series u observaciones. También define la medida principal, que es el valor numérico del dato (por ejemplo, el índice de precios o un saldo monetario), así como los atributos adicionales que proporcionan información contextual (como la unidad, el método de ajuste estacional o el estatus de la observación). Entender esta estructura es esencial para saber qué filtros se pueden aplicar y cómo debe construirse la consulta. 3.Consulta del codelist. El tercer paso consiste en consultar las codelists. Cada dimensión definida en la DSD tiene asociada un codelist, que es el conjunto autorizado de valores posibles para esa dimensión. Las codelists funcionan como un diccionario estándar de códigos que permiten garantizar la interoperabilidad entre instituciones y sistemas. Por ejemplo, una codelist puede contener todos los códigos de países, los tipos de frecuencia temporal (anual, trimestral, mensual), o los distintos indicadores dentro de un dominio estadístico. Consultarlas es fundamental para saber qué valores se pueden usar al filtrar una consulta y cómo deben codificarse correctamente dentro de la URL o del cuerpo de la petición. Como se verá más adelante, muchas veces los codelists muestran lo posible pero no lo que es válido para un dataflow determinado. Por tanto, se tendrá que investigar si el DSD contiene restricciones en los valores y, si no, se obtendrán por fuerza bruta descargando una muestra de los datos y extrayendo los valores únicos. En cualquier caso, lo importante es que el usuario esté familiarizado con los datos.
  3. Extracción de datos. Finalmente, el cuarto paso es extraer los datos. Una vez que se conoce el dataflow, su estructura y los códigos permitidos para cada dimensión, es posible construir una consulta válida a la API SDMX. Esto implica seleccionar las combinaciones de códigos relevantes (por ejemplo, país, indicador y frecuencia) y aplicar filtros temporales o de detalle según lo permita la API. La respuesta suele recibirse en un formato estandarizado, como SDMX-JSON o SDMX-XML, que posteriormente puede procesarse en herramientas como R o Python para análisis, visualización o integración en un pipeline reproducible. Este paso materializa todo el trabajo previo, ya que convierte la comprensión conceptual de la estructura SDMX en una solicitud concreta de datos.

Algunos ejemplos

En esta sección se realizará un ejemplo con las APIs del FMI y, posteriormente, con las APIs del Banco Central Europeo (BCE). Aunque se explicará paso a paso, se debe tener en mente que lo que vamos a hacer es:

  1. Identificar los dataflows disponibles (“base de datos”)
  2. Obtener la estructura de datos del dataflow de interés (“las variables”)
  3. Consultar las codelists asociadas a las dimensiones de interés (“los códigos válidos”)
  4. Extraer los datos (“la consulta final”)

SDMX en el FMI

El FMI tiene una colección de APIs SDMX 3.0 bastante completa que permite acceder a una amplia gama de datos macroeconómicos. Estas pueden encontrarse, luego de registrarse, aquí:

IMF Data API

Las APIs SDMX del FMI —y en realidad cualquier API SDMX 3.0— están diseñadas siguiendo una estructura jerárquica, donde la URL funciona como un camino que va de lo más general a lo más específico. Esto permite que un usuario explore de forma progresiva el sistema estadístico, empezando por los catálogos completos y avanzando hacia un dataset concreto o incluso una sola serie temporal.

La idea fundamental es que cada segmento de la ruta añade una restricción, un filtro, o un nivel de precisión. Así, una llamada puede significar “mostrar todo”, o “dame exactamente esta estructura de datos”, dependiendo de cuántos parámetros se incluyan. Vamos a verlo por partes.

Datos disponibles: el dataflow

En primer lugar, el punto de partida es la URL base que, en este caso, es https://api.imf.org/external/sdmx/3.0. A partir de este punto podemos decir que la API del FMI se divide en dos grandes familias: una de estructura y otra de datos. La primera permite explorar los catálogos y estructuras, mientras que la segunda permite extraer datos concretos.

Por ejemplo, si a la URL base le añadimos /structure/dataflow, obtenemos el catálogo completo de dataflows disponibles en la API, es decir, estamos pidiendo “dame todos los dataflows que tiene el FMI”. Si, por otro lado, añadimos /structure/datastructure básicamente le estamos pidiendo “dame todas las DSD que tiene el FMI”. Este nivel es más bien bastante general, pues no aplica ningún filtro a la extracción, lo que es útil cuando no sabemos qué es lo que necesitamos.

Para ilustrar esto vamos a realizar el primer paso del flujo de trabajo: obtener todos los dataflows disponibles en la API del FMI y localizar el dataflow correspondiente al World Economic Outlook (WEO). Esto se puede hacer con el siguiente código en R:

# 1. Base URL SDMX 3.0 del FMI
base_url <- "https://api.imf.org/external/sdmx/3.0"


# 2. Obtener todos los dataflows
flows_resp <- httr2::request(paste0(base_url, "/structure/dataflow")) |>
  httr2::req_perform()

flows_json <- flows_resp |>
  httr2::resp_body_string() |>
  jsonlite::fromJSON(flatten = TRUE)

flows <- flows_json$data$dataflows

# Tabla de dataflows en formato tibble
df_flows <- tibble::tibble(
  id      = flows$id,
  name    = flows$names.en,
  version = flows$version,
  agency  = flows$agencyID,
  dsd_urn = flows$structure
)

df_flows |> 
  dplyr::select(id, name, version, agency) |>
  dplyr::arrange(id) |> 
  kableExtra::kable() 
idnameversionagency
AFRREOSub-Saharan Africa Regional Economic Outlook (AFRREO)6.0.1IMF.AFR
ANEANational Economic Accounts (NEA), Annual Data6.0.1IMF.STA
APDREOAsia and Pacific Regional Economic Outlook (APDREO)6.0.0IMF.APD
BOPBalance of Payments (BOP)21.0.0IMF.STA
BOP_AGGBalance of Payments and International Investment Position Statistics (BOP/IIP), World and Country Group Aggregates9.0.1IMF.STA
COFERCurrency Composition of Official Foreign Exchange Reserves (COFER)7.0.0IMF.STA
CPIConsumer Price Index (CPI)5.0.0IMF.STA
CPI_WCAConsumer Price Index (CPI), World and Country Aggregates (CPI_WCA)2.0.2IMF.STA
CTOTCommodity Terms of Trade (CTOT)5.0.1IMF.RES
DIPDirect Investment Positions by Counterpart Economy (formerly CDIS)12.0.0IMF.STA
EDExport Diversification (ED)1.0.0IMF.RES
EEREffective Exchange Rate (EER)6.0.0IMF.STA
EQExport Quality (EQ)2.0.0IMF.RES
ERExchange Rates (ER)4.0.1IMF.STA
FAFund Accounts (FA)8.0.0IMF.STA
FASFinancial Access Survey (FAS)4.0.0IMF.STA
FDFiscal Decentralization (FD)6.0.0IMF.STA
FDIFinancial Development Index (FDI)1.0.0IMF.MCM
FMFiscal Monitor (FM)5.0.0IMF.FAD
FSIBSISFinancial Soundness Indicators (FSI), Balance Sheet, Income Statement and Memorandum Series18.0.0IMF.STA
FSICFinancial Soundness Indicators (FSI), Core and Additional Indicators13.0.1IMF.STA
FSICDMFinancial Soundness Indicators (FSI), Concentration and Distribution Measures7.0.0IMF.STA
GDDGlobal Debt Database (GDD)2.0.0IMF.FAD
GFS_BSGFS Balance Sheet12.0.0IMF.STA
GFS_COFOGGFS Government Expenditures by Function11.0.0IMF.STA
GFS_SFCPGFS Stocks and Flows by Counterparty10.0.0IMF.STA
GFS_SOEFGFS Statement of Other Economic Flows11.0.0IMF.STA
GFS_SOOGFS Statement of Operations12.0.0IMF.STA
GFS_SSUCGFS Statement of Sources and Uses of Cash10.0.0IMF.STA
HPDHistorical Public Debt (HPD)1.0.0IMF.FAD
ICSDInvestment and Capital Stock Dataset (ICSD)1.0.0IMF.FAD
IIPInternational Investment Position (IIP)13.0.0IMF.STA
IIPCCCurrency Composition of the International Investment Position (IIPCC)13.0.0IMF.STA
ILInternational Liquidity (IL)13.0.1IMF.STA
IMTSInternational Trade in Goods (by partner country) (IMTS)1.0.0IMF.STA
IRFCLInternational Reserves and Foreign Currency Liquidity (IRFCL)11.0.0IMF.STA
ISORA_2016_DATA_PUBISORA 2016 Data2.0.0ISORA
ISORA_2018_DATA_PUBISORA 2018 Data2.0.0ISORA
ISORA_LATEST_DATA_PUBISORA Latest Data4.0.0ISORA
ITGInternational Trade in Goods (ITG)4.0.0IMF.STA
ITG_WCAInternational Trade in Goods, World and Country Aggregates2.0.4IMF.STA
ITSInternational Trade in Services (ITS)3.0.1IMF.RES
LSLabor Statistics (LS)9.0.0IMF.STA
MCDREOMiddle East and Central Asia Regional Economic Outlook (MCDREO) 8.0.0IMF.MCD
MFS_CBSMonetary and Financial Statistics (MFS), Central Bank Data24.0.0IMF.STA
MFS_DCMonetary and Financial Statistics (MFS), Depository Corporations8.0.0IMF.STA
MFS_FCMonetary and Financial Statistics (MFS), Financial Corporations9.0.0IMF.STA
MFS_FMPMonetary and Financial Statistics (MFS): Financial Market Prices3.0.0IMF.STA
MFS_IRMonetary and Financial Statistics (MFS), Interest Rate8.0.1IMF.STA
MFS_MAMonetary and Financial Statistics (MFS), Monetary Aggregates10.0.1IMF.STA
MFS_NSRFMonetary and Financial Statistics (MFS), Non-Standard Data1.0.3IMF.STA
MFS_ODCMonetary and Financial Statistics (MFS), Other Depository Corporations10.0.0IMF.STA
MFS_OFCMonetary and Financial Statistics (MFS), Other Financial Corporations7.0.0IMF.STA
MPFTMonetary Policy Frameworks Toolkit (MPFT)7.0.1IMF.RES
NSDPNational Summary Data Page (NSDP)7.0.0IMF.STA
PCPSPrimary Commodity Price System (PCPS)9.0.0IMF.RES
PIProduction Indexes (PI)2.0.0IMF.STA
PIPPortfolio Investment Positions by Counterpart Economy (formerly CPIS)4.0.0IMF.STA
PI_WCAProduction Indexes, World and Country Group Aggregates1.0.0IMF.STA
PPIProducer Price Index (PPI)3.0.0IMF.STA
PSBSPublic Sector Balance Sheet (PSBS)2.0.0IMF.FAD
QGDP_WCAQuarterly Gross Domestic Product (GDP), World and Country Aggregates3.0.0IMF.STA
QGFSQuarterly Government Finance Statistics (QGFS)12.0.0IMF.STA
QNEANational Economic Accounts (NEA), Quarterly Data7.0.0IMF.STA
SDGIMF Reported SDG Data2.0.1IMF.STA
SPESpecial Purpose Entities (SPEs)13.0.0IMF.STA
SRDStructural Reform Database (SRD)1.0.0IMF.RES
TAXFITTax and Benefits Analysis Tool (TAXFIT)2.0.3IMF.RES
TEGTrade in Low Carbon Technology Goods (TEG)3.0.2IMF.STA
WEOWorld Economic Outlook (WEO)9.0.0IMF.RES
WHDREOWestern Hemisphere Regional Economic Outlook (WHDREO)5.0.0IMF.WHD
WPCPERCrypto-based Parallel Exchange Rates (Working Paper dataset WP-CPER)6.0.0IMF.STA

En la tabla anterior podemos ver todos los dataflows disponibles en la API del FMI. Cada dataflow tiene un identificador único (id), un nombre descriptivo (name), una versión (version), una agencia responsable (agency) y un URN (del inglés, Uniform Resource Name) que no se reproduce por motivos de espacio pero que apunta a su estructura de datos asociada (dsd_urn).

En nuestro caso nos interesa el dataflow WEO. Podemos filtrar el dataflow de interés de la siguiente manera:

# Nos quedamos con el dataflow WEO
weo <- df_flows |> 
  dplyr::filter(id == "WEO")

weo |> 
  kableExtra::kable()
idnameversionagencydsd_urn
WEOWorld Economic Outlook (WEO)9.0.0IMF.RESurn:sdmx:org.sdmx.infomodel.datastructure.DataStructure=IMF.RES:DSD_WEO(9.0+.0)

Estructura de datos: las dimensiones

Ahora que tenemos el dataflow de interés, el siguiente paso es entender qué varialbles (dimensions) están disponibles en su estructura de datos. Para ello, el API que debemos consultar tiene la forma de: /structure/datastructure/{agency}/{dsd_id}/{version}, donde {agency}, {dsd_id} y {version} son los componentes que debemos extraer del URN del dataflow WEO. Nótese que, en el caso de no indicar estos tres campos se corre el riesgo de obtener información desactualizada.

¿Cómo obtenemos esta información? Lo bueno es que, al momento de consultar el dataflow esta información se provee en el URN asociada:

# Parsear el URN del datastructure de WEO
urn <- weo$dsd_urn
urn
## [1] "urn:sdmx:org.sdmx.infomodel.datastructure.DataStructure=IMF.RES:DSD_WEO(9.0+.0)"

Nótese que aquí tenemos las 3 piezas esenciales:

  1. La agencia responsable del DSD: IMF.RES
  2. El identificador del DSD: DSD_WEO
  3. La versión del DSD: 9.0+.0

Para extraer estas piezas de forma sistemática podemos usar expresiones regulares en R de la siguiente manera:

m     <- base::regexec("DataStructure=([^:]+):([^()]+)\\(([^)]+)\\)", urn)
parts <- base::regmatches(urn, m)[[1]]

agency_dsd  <- parts[2]   # p.ej. "IMF.RES"
dsd_id      <- parts[3]   # p.ej. "DSD_WEO"
version_dsd <- parts[4]   # p.ej. "9.0+.0"


# Descargar el datastructure de WEO y ver dimensiones

dsd_url <- paste0(
  base_url,
  "/structure/datastructure/",
  agency_dsd, "/", dsd_id, "/", version_dsd
)
dsd_url
## [1] "https://api.imf.org/external/sdmx/3.0/structure/datastructure/IMF.RES/DSD_WEO/9.0+.0"

Ahora que tenemos la URL del datastructure de WEO, podemos descargarlo y ver las dimensiones disponibles:

dsd_resp <- httr2::request(dsd_url) |>
  httr2::req_perform()

dsd_json <- dsd_resp |>
  httr2::resp_body_string() |>
  jsonlite::fromJSON(flatten = TRUE)

# Dimensiones "normales": COUNTRY, INDICATOR, FREQUENCY
dims_main <- dsd_json$data$dataStructures$dataStructureComponents.dimensionList.dimensions[[1]] |>
  tibble::as_tibble() |>
  dplyr::select(position, id, type)

# Dimensión de tiempo: TIME_PERIOD
time_dim <- dsd_json$data$dataStructures$dataStructureComponents.dimensionList.timeDimensions[[1]] |>
  tibble::as_tibble() |>
  dplyr::select(position, id, type)

# Orden completo de dimensiones
dims_clean <- dplyr::bind_rows(dims_main, time_dim) |>
  dplyr::arrange(position)

dims_clean |> 
  kableExtra::kable()
positionidtype
0COUNTRYDimension
1INDICATORDimension
2FREQUENCYDimension
3TIME_PERIODTimeDimension

En la tabla anterior podemos ver las dimensiones disponibles en el dataflow WEO (COUNTRY, INDICATOR, FREQUENCY y TIME_PERIOD). Estas dimensiones son esenciales para entender cómo se organizan los datos y qué filtros podemos aplicar al momento de extraer información específica.

Nótese también que, aunque la consulta se ha obtenido sin errores, previamente se exploró dsd_json para entender dónde y cómo estaba almacenando la información.

Codelists: los códigos válidos

Como tercer paso, y antes de extraer los datos, es importante conocer los códigos válidos para cada dimensión del dataset. Esto es fundamental porque, al construir la consulta final, debemos asegurarnos de usar los valores exactos que la API reconoce. Caso contrario nos devolverá un error. Por ejemplo, si queremos obtener el crecimiento del PIB real para un país específico, necesitamos saber cómo codifica el FMI el país, el indicador y la frecuencia. No basta con escribir “Bolivia” o “Real GDP growth”: hay que usar los códigos oficiales definidos por la estructura del dataset.

Cada una de las dimensiones del WEO —COUNTRY, INDICATOR, FREQUENCY y TIME_PERIOD— tiene un conjunto autorizado de valores posibles: el codelist. Por ejemplo, los códigos de país pueden ser BOL, ARG o USA; los indicadores pueden ser NGDP_RPCH, LUR u otros; y las frecuencias válidas suelen ser A (anual), Q (trimestral), etc. Un detalle importante es que cada dataset puede tener codelists distintos, aun para conceptos relativamente similares. El FMI podría usar una lista de países para WEO, otra para BOP (Balance of Payments), y otra para estadísticas monetarias. Esto significa que no necesariamente los códigos que funcionan para un dataflow van a funcionar para otro.

Por eso, si queremos consultar datos del WEO, no podemos inventarnos los códigos ni confiar en listas antiguas: se debe consultar directamente al FMI las codelists oficiales asociadas. La única manera segura de hacerlo es comenzar por el dataflow, obtener su estructura (la DSD) y, desde allí, recuperar las listas de códigos correctas para cada dimensión. Este procedimiento asegura que la consulta sea reproducible, exacta y totalmente compatible con la definición que el FMI usa internamente.

Para obtener estas codelists asociadas al dataflow WEO, utilizamos una URL específica de la API SDMX del FMI. El patrón general es: /structure/dataflow/{agency}/{dataflow_id}/+, donde {agency} y {dataflow_id} provienen del propio dataflow (en nuestro caso, IMF.RES y WEO, respectivamente). El símbolo + indica que queremos la versión más reciente disponible. Pero lo realmente importante son los parámetros que añadimos al final: ?detail=full&references=descendants1.

Para nuestro caso, la URL completa para obtener las codelists del WEO es la siguiente:

weo_struct_url <- paste0(
  base_url,
  "/structure/dataflow/",
  weo$agency, "/", weo$id, "/+",
  "?detail=full&references=descendants"
)
weo_struct_url
## [1] "https://api.imf.org/external/sdmx/3.0/structure/dataflow/IMF.RES/WEO/+?detail=full&references=descendants"

Al realizar la petición a esta URL, obtenemos una respuesta que incluye todas las codelists asociadas al dataflow WEO. Podemos procesar esta respuesta para extraer las codelists específicas que necesitamos para cada dimensión:

weo_struct_resp <- httr2::request(weo_struct_url) |>
  httr2::req_perform()

weo_struct_json <- weo_struct_resp |>
  httr2::resp_body_string() |>
  jsonlite::fromJSON(flatten = TRUE)

# Tabla de codelists
codelists_tbl <- weo_struct_json$data$codelists

codelists_tbl |> 
  dplyr::select(id, name, version, agencyID) |> 
  head() |> 
  kableExtra::kable()
idnameversionagencyID
CL_DERIVATION_TYPEDerivation Type1.2.1IMF
CL_VALUATIONValuation2.4.0IMF
CL_GFS_STOGFS Stocks, Transactions, Other Flows2.9.0IMF
CL_ACCOUNTING_ENTRYAccounting Entry2.2.1IMF
CL_UNITUnit of Measure2.11.0IMF
CL_ACCESS_SHARING_LEVELAccess and Sharing Level1.0.2IMF

Nótese que en la tabla anterior se ha extraído bastante información sobre las codelists disponibles para el dataflow WEO. En el código a continuación vamos a resumir un poco lo extraído:

# Vista resumida de las codelists
weo_codelists_overview <- tibble::tibble(
  cl_id   = codelists_tbl$id,
  cl_name = ifelse(is.na(codelists_tbl$names.en), 
                   yes = codelists_tbl$name,
                   no = codelists_tbl$names.en
                   ),
  n_codes = sapply(codelists_tbl$codes, nrow)
)

weo_codelists_overview |> 
  dplyr::arrange(desc(cl_id)) |> 
  kableExtra::kable()
cl_idcl_namen_codes
CL_WEO_INDICATORWorld Economic Outlook (WEO) Indicator145
CL_WEO_COUNTRYWorld Economic Outlook (WEO) Country338
CL_VALUATIONValuation26
CL_UNIT_MULTUnit Multiplier31
CL_UNITUnit of Measure270
CL_TRANSFORMATIONTransformation487
CL_TRADE_FLOWTrade flow50
CL_TOPICTopic118
CL_S_ADJUSTMENTSeasonal Adjustment17
CL_STATISTICAL_MEASURESStatistical Measures23
CL_SOC_CONCEPTSSocial Concepts31
CL_SEXSex9
CL_SEC_CLASSIFICATIONSecurity Classification6
CL_SECTORInstitutional Sector288
CL_REPORTING_PERIOD_TYPEREPORTING_PERIOD_TYPE6
CL_PRICESPrices15
CL_OVERLAPIMF Data Overlap1
CL_ORGANIZATIONOrganization1588
CL_OBS_STATUSObservation Status22
CL_NA_STONA Stocks, Transactions, Other Flows636
CL_MFS_INSTRMonetary and Financial Instruments18
CL_METHODOLOGYMethodology69
CL_LANGUAGELanguage93
CL_INT_TTCInterest Rate types, terms and conditions101
CL_INT_ACC_ITEMInternational accounts item507
CL_INSTR_ASSETInstrument and assets classification342
CL_INDEX_TYPEIndex type49
CL_GFS_STOGFS Stocks, Transactions, Other Flows389
CL_GENDERGender15
CL_FUNCTIONAL_CATFunctional category72
CL_FSENTRYFlow or stock entry43
CL_FREQFrequency6
CL_FI_MATURITYMaturity of Financial Instrument32
CL_EXRATEExchange Rate37
CL_DERIVATION_TYPEDerivation Type12
CL_DEPARTMENTDepartment34
CL_DECIMALSDecimals16
CL_CURRENCYCurrency177
CL_COUNTRYCountry337
CL_CONF_STATUSConfidentiality Status12
CL_COMMODITYCommodity135
CL_COICOP_2018COICOP 201816
CL_COICOP_1999COICOP 199915
CL_COFOGCOFOG190
CL_CIVIL_STATUSCivil (or Marital) Status8
CL_ACCOUNTSMacroeconomic and financial accounts40
CL_ACCOUNTING_ENTRYAccounting Entry82
CL_ACCESS_SHARING_LEVELAccess and Sharing Level8

En la tabla anterior podemos ver un resumen de las codelists disponibles para el dataflow WEO. Cada codelist tiene un identificador (cl_id), un nombre descriptivo (cl_name) y el número de códigos que contiene (n_codes). En nuestro caso, nos interesan particularmente las codelists asociadas a las dimensiones COUNTRY, INDICATOR y FREQUENCY: CL_WEO_COUNTRY, CL_WEO_INDICATOR y CL_FREQUENCY.

Por ejemplo, en el caso de CL_WEO_COUNTRY:

weo_country_codes <- codelists_tbl$codes[[which(codelists_tbl$id == "CL_WEO_COUNTRY")]] |>
  tibble::as_tibble() |>
  dplyr::transmute(
    code    = id,
    name_en = names.en  # en CL_WEO_COUNTRY viene como names.en1
  )
weo_country_codes |>
  head(20) |> 
  kableExtra::kable()
codename_en
GX123Other Advanced Economies (Advanced Economies excluding G7 and Euro Area countries)
AFGAfghanistan, Islamic Republic of
ALBAlbania
DZAAlgeria
ASMAmerican Samoa
ANDAndorra, Principality of
AGOAngola
AIAAnguilla, United Kingdom-British Overseas Territory
ATGAntigua and Barbuda
ARGArgentina
ARMArmenia, Republic of
ABWAruba, Kingdom of the Netherlands
AUSAustralia
AUTAustria
AZEAzerbaijan, Republic of
BHSBahamas, The
BHRBahrain, Kingdom of
BGDBangladesh
BRBBarbados
BLRBelarus, Republic of

En la tabla anterior podemos ver los códigos de país válidos para el dataflow WEO. Cada país tiene un código (code) y un nombre descriptivo en inglés (name_en). Estos códigos son esenciales para construir consultas precisas al API del FMI.

Ahora, procedemos a buscar los INDICATOR disponibles en el WEO:

weo_indicator_codes <- codelists_tbl$codes[[which(codelists_tbl$id == "CL_WEO_INDICATOR")]] |>
  tibble::as_tibble() |>
  dplyr::transmute(
    code    = id,
    name_en = names.en
  )

weo_indicator_codes |>
  kableExtra::kable()
codename_en
LURUnemployment rate
PCOALSACoal, South Africa, Export price, US dollars per metric tonne
DSPExternal debt: total debt service, amortization, US dollar
DSIExternal debt: total debt service, interest, US dollar
DSP_NGDPDExternal debt: total debt service, amortization, Percent of GDP
BXExports of goods and services, US dollar
BMImports of goods and services, US dollar
DSExternal debt: total debt service, US dollar
TTPCHTerms of trade of goods and services, percent change
DSI_NGDPDExternal debt: total debt service, interest, Percent of GDP
NGDP_RPCHMKGross domestic product (GDP), Constant prices, Percent
NGDPDGross domestic product (GDP), Current prices, US dollar
PTEATea, Kenyan, Unit prices, US cents per kilogram
DS_NGDPDExternal debt: total debt service, Percent of GDP
DExternal debt, US dollar
NGDP_DGross domestic product (GDP), Price deflator, Index
NGDP_RPCHGross domestic product (GDP), Constant prices, Percent change
TTTPCHTerms of trade of goods, percent change
NGDP_RGross domestic product (GDP), Constant prices, Domestic currency
NGDPGross domestic product (GDP), Current prices, Domestic currency
NGSD_NGDPGross national savings, Percent of GDP
PSUNOSunflower oil, Export price, US dollars per metric tonne
PNGASEUNatural gas, EU, Unit prices, US dollars per million metric British thermal units of gas
PZINCZinc, Unit prices, US dollars per metric tonne
PPPSHGross domestic product (GDP), Purchasing power parity (PPP) international dollar, percent of world GDP, Percent, ICP benchmarks 2017-2021
D_NGDPDExternal debt, Percent of GDP
PPPPCGross domestic product (GDP), Per capita, purchasing power parity (PPP) international dollar, ICP benchmarks 2017-2021
PPPGDPGross domestic product (GDP), Current prices, Purchasing power parity (PPP) international dollar, ICP benchmarks 2017-2021
DSP_BXExternal debt: total debt service, amortization, Percent of exports of goods and services
PPORKSwine, Unit prices, US cents per pound
PSAWMALHard sawnwood, Dark Red Meranti, Unit prices, US dollars per cubic meter
PORANGOrange, Import price, US dollars per metric tonne
PTINTin, Unit prices, US dollars per metric tonne
PPOULTPoultry, Unit prices, US cents per pound
DSI_BXExternal debt: total debt service, interest, Percent of exports of goods and services
NGDPDPCGross domestic product (GDP), Current prices, Per capita, US dollar
PSAWORESoft sawnwood, Export price, US dollars per cubic meter
PROILRapeseed oil, Unit prices, US dollars per metric tonne
PLAMBLamb, Unit prices, US cents per pound
PBARLBarley, Unit prices, US dollars per metric tonne
PLOGORESoft logs, Export price, US dollars per cubic meter
GGR_NGDPRevenue, General government, Percent of GDP
TRADEPCHTrade of goods and services, Volume, Percent change
PALUMAluminum, Unit prices, US dollars per metric tonne
PLOGSKHard logs, import price Japan, Import price, US dollars per cubic meter
PWOOLCWool, coarse, Unit prices, US cents per kilogram
GGXWDN_NGDPNet debt, General government, Percent of GDP
PFISHFishmeal, Unit prices, US dollars per metric tonne
NGDPRPCGross domestic product (GDP), Constant prices, Per capita, Domestic currency
PNGASJPLNG, Asia, Unit prices, US dollars per million metric British thermal units of gas
LEEmployed persons, Persons for countries / Index for country groups
NGDPPCGross domestic product (GDP), Current prices, Per capita, Domestic currency
GGRRevenue, General government, Domestic currency
PWOOLFWool, fine, Unit prices, US cents per kilogram
PCOFFOTMCoffee, other mild Arabica, Unit prices, US cents per pound
PNICKNickel, Unit prices, US dollars per metric tonne
BCACurrent account balance (credit less debit), US dollar
PBANSOPBananas, Unit prices, US dollars per metric tonne
PRICENPQRice, Thailand, Unit prices, US dollars per metric tonne
DS_BXExternal debt: total debt service, Percent of exports of goods and services
PCOFFROBCoffee, Robustas, Unit prices, US cents per pound
PSALMFish, Export price, US dollars per kilogram
PSUGAUSASugar, No. 16, US, Import price, US cents per pound
GGXWDG_NGDPGross debt, General government, Percent of GDP
PCOPPCopper, Unit prices, US dollars per metric tonne
PLEADLead, Unit prices, US dollars per metric tonne
PBEEFBeef, Import price, US cents per pound
BFFinancial account balance (assets less liabilities), US dollar
PURANUranium, Unit prices, US dollars per pound
BCA_NGDPDCurrent account balance (credit less debit), Percent of GDP
PHIDEHides, Unit prices, US cents per pound
PGNUTSGroundnuts, Unit prices, US dollars per metric tonne
GGSBStructural balance, General government, Domestic currency
BFPPortfolio investment, Net (assets minus liabilities), US dollar
GGX_NGDPExpenditure, General government, Percent of GDP
GGXWDGGross debt, General government, Domestic currency
GGXWDNNet debt, General government, Domestic currency
NGDP_FYGross domestic product (GDP), Current prices, Fiscal year, Domestic currency
GGXExpenditure, General government, Domestic currency
PRUBBRubber, Unit prices, US cents per pound
PWHEAMTWheat, Unit prices, US dollars per metric tonne
PMAIZMTCorn, Unit prices, US dollars per metric tonne
GGSB_NPGDPStructural balance, General government, Percent
POLVOILOlive oil, Unit prices, US dollars per metric tonne
PIORECRIron ore, Unit prices, US dollars per metric tonne
D_BXExternal debt, Percent of exports of goods and services
BFDDirect investment, Net (assets minus liabilities), US dollar
TX_RPCHExports of goods and services, Volume, Free on board (FOB), Percent change
PCOCOCocoa, Unit prices, US dollars per metric tonne
BFOOther investment, Net (assets minus liabilities), US dollar
PPOILPalm oil, Unit prices, US dollars per metric tonne
NGDPRPPPPCGross domestic product (GDP), Constant prices, Per capita, purchasing power parity (PPP) international dollar, ICP benchmark 2021
PRAWMWAgricultural raw materials, Commodity price index
PSMEASoybean meal, Unit prices, US dollars per metric tonne
PCOTTINDCotton, Unit prices, US cents per pound
PCOALAUCoal, Australia, Unit prices, US dollars per metric tonne
PNGASUSNatural Gas, US Henry Hub Gas, Unit prices, US dollars per metric tonne
TXGM_DExports of manufactures, Price deflator, Free on board (FOB), Index
TM_RPCHImports of goods and services, Volume, Cost insurance freight (CIF), Percent change
PSOILSoybeans oil, Unit prices, US dollars per metric tonne
PSUGAISASugar, No. 11, World, Unit prices, US cents per pound
BFFFinancial derivatives and employee stock options, Net (assets minus liabilities), US dollar
NGAP_NPGDPOutput gap, Percent of potential GDP
PSOYBSoybeans, Unit prices, US dollars per metric tonne
PINDUWIndustrial materials, Commodity price index
GGXCNLNet lending (+) / net borrowing (-), General government, Domestic currency
BFRAChange in reserve assets, Net (assets minus liabilities), US dollar
TXGM_DPCHExports of manufactures, Price deflator, Free on board (FOB), Percent change
POILBREBrent crude, Unit prices, US dollars per barrel
PSHRIShrimp, Unit prices, US dollars per kilogram
GGXCNL_NGDPNet lending (+) / net borrowing (-), General government, Percent of GDP
PCOALWCoal, Commodity price index
PBEVEWBeverages, Commodity price index
PWOOLWWool, Commodity price index
POILDUBDubai crude, Unit prices, US dollars per barrel
POILWTIWTI crude, Unit prices, US dollars per barrel
PCOFFWCoffee, Commodity price index
PCPIAll Items, Consumer price index (CPI), Period average
PSEAFWSeafood, Commodity price index
PTIMBWTimber, Commodity price index
GGXONLB_NGDPPrimary net lending (+) / net borrowing (-), General government, Percent of GDP
PSUGAWSugar, Commodity price index
POILAPSPAPSP crude oil, Unit prices, US dollars per barrel
PCEREWCereal, Commodity price index
PALLFNFWAll commodities, Commodity price index
PCPIEAll Items, Consumer price index (CPI), End-of-period (EoP)
GGXONLBPrimary net lending (+) / net borrowing (-), General government, Domestic currency
TXG_RPCHExports of goods, Volume, Free on board (FOB), Percent change
TMG_RPCHImports of goods, Volume, Cost insurance freight (CIF), Percent change
PSOFTWSoftwood, Commodity price index
PHARDWHardwood, Commodity price index
PCPIPCHAll Items, Consumer price index (CPI), Period average, percent change
PMEATWMeat, Commodity price index
PFANDBWFood and beverage, Commodity price index
PCPIEPCHAll Items, Consumer price index (CPI), End-of-period (EoP), percent change
PNFUELWNon-fuel, Commodity price index
PNRGWEnergy, Commodity price index
LPPopulation, Persons for countries / Index for country groups
PNGASWNatural gas, Commodity price index
PPPEXRate, Domestic currency per international dollar in PPP terms, ICP benchmarks 2017-2021
PFOODWFood, Commodity price index
PMETAWMetal, Commodity price index
POILAPSPWAPSP crude oil, Commodity price index
NID_NGDPGross capital formation, Percent of GDP
PVOILWVegetable oil, Commodity price index

Finalmente, para la frecuencia:

weo_frequency_codes <- codelists_tbl$codes[[which(codelists_tbl$id =="CL_FREQ")]] |>
  tibble::as_tibble() |>
  dplyr::transmute(
    code    = id,
    name_en = names.en
  )
weo_frequency_codes |>
  kableExtra::kable()
codename_en
AAnnual
DDaily
MMonthly
QQuarterly
SHalf-yearly, semester
WWeekly

Una vez que tenemos los códigos válidos para las dimensiones de interés, estamos listos para proceder a la extracción de datos específicos del dataflow WEO. Este es el último paso del flujo de trabajo SDMX y nos permitirá obtener las series temporales que necesitamos para nuestro análisis macroeconómico.

Extracción de datos: la consulta final

Con los códigos válidos para las dimensiones COUNTRY, INDICATOR y FREQUENCY en mano, ya estamos en condiciones de construir la consulta final para extraer datos específicos del dataflow WEO. Desde el punto de vista de la API del FMI, el endpoint general de datos tiene la forma:

/data/{context}/{agencyID}/{resourceID}/{version}/{key}[?c]

En nuestro caso, {context} será dataflow, {agencyID} será IMF.RES, {resourceID} será WEO y {version} la última versión estable (+). Es decir, todo lo que está antes de {key} ya lo conocemos a partir de los pasos anteriores. Lo que nos falta definir es precisamente el key y, opcionalmente, el parámetro de filtro c.

El parámetro key es la pieza central de la consulta: representa la combinación de valores de dimensiones que identifica una serie o un subconjunto del “cubo” de datos. La API lo define como “the combination of dimension values identifying series or slices of the cube”. En la práctica, esto se traduce en una cadena donde los valores de las dimensiones se concatenan siguiendo el orden definido en la DSD.

En el caso del WEO, vimos que el orden es COUNTRY.INDICATOR.FREQUENCY, así que un key como BOL.NGDP_RPCH.A significa: país Bolivia (BOL), indicador “Real GDP, percent change” (NGDP_RPCH) y frecuencia anual (A). La API permite también trabajar con comodines (*), por ejemplo BOL.*.A (todas las series anuales de Bolivia) o *.NGDP_RPCH.A (la variación del PIB real anual, para todos los países). Esto hace del key una forma muy compacta de expresar qué combinaciones de dimensiones queremos.

El parámetro c, por su parte, permite filtrar por componentes adicionales mediante una sintaxis de tipo c[DIM]=valor. La documentación lo presenta como “Filter data by component value (e.g. c[FREQ]=A)” y, además, permite añadir operadores lógicos. En nuestro ejemplo sencillo, no usamos c porque ya restringimos país, indicador y frecuencia directamente en el key, y dejamos que la API devuelva toda la historia disponible de la serie. Sin embargo, c es muy útil cuando se quieren aplicar filtros adicionales (por ejemplo, acotar el rango de tiempo, seleccionar solo ciertas monedas, métodos o estados de observación) sin tener que complicar el key o cuando se trabaja con estructuras más ricas en dimensiones.

Supongamos que queremos obtener el crecimiento del PIB real (percent change) para Bolivia con frecuencia anual.

# Código de Bolivia (según la codelist WEO)
bol_code <- "BOL"

# Código del indicador "Real GDP, percent change"
rgdp_code <- "NGDP_RPCH"

# Frecuencia anual (A) según la dimensión FREQUENCY
freq_code <- "A"

# Clave SDMX para WEO: COUNTRY.INDICATOR.FREQUENCY
weo_key <- paste(bol_code, rgdp_code, freq_code, sep = ".")
weo_key
## [1] "BOL.NGDP_RPCH.A"

Ahora que tenemos el key procedemos a realizar la consulta:

# URL de extracción de datos SDMX 3.0 (data query correcta)
weo_data_url <- paste0(
  base_url,
  "/data/dataflow/",
  weo$agency, "/", weo$id, "/+/",
  weo_key
)


weo_data_resp <- httr2::request(weo_data_url) |>
  httr2::req_url_query(
    dimensionAtObservation = "TIME_PERIOD",
    attributes             = "dsd",
    measures               = "all",
    includeHistory         = "false"
  ) |>
  httr2::req_perform()

weo_data_json <- weo_data_resp |>
  httr2::resp_body_string() |>
  jsonlite::fromJSON(flatten = FALSE)

En weo_data_json tenemos la respuesta completa de la API con los datos solicitados. La estructura de este objeto es más bien compleja, pues es una lista de listas con varios y distintos niveles de profundidad.

str(weo_data_json, max.level = 4)
## List of 2
##  $ meta: Named list()
##  $ data:List of 2
##   ..$ dataSets  :'data.frame':	1 obs. of  4 variables:
##   .. ..$ structure               : int 0
##   .. ..$ action                  : chr "Replace"
##   .. ..$ series                  :'data.frame':	1 obs. of  1 variable:
##   .. .. ..$ 0:0:0:'data.frame':	1 obs. of  2 variables:
##   .. ..$ dimensionGroupAttributes:'data.frame':	1 obs. of  1 variable:
##   .. .. ..$ :0:::List of 1
##   ..$ structures:'data.frame':	1 obs. of  6 variables:
##   .. ..$ dataSets   :List of 1
##   .. .. ..$ : int 0
##   .. ..$ links      :List of 1
##   .. .. ..$ :'data.frame':	2 obs. of  2 variables:
##   .. ..$ dimensions :'data.frame':	1 obs. of  2 variables:
##   .. .. ..$ series     :List of 1
##   .. .. ..$ observation:List of 1
##   .. ..$ measures   :'data.frame':	1 obs. of  1 variable:
##   .. .. ..$ observation:List of 1
##   .. ..$ attributes :'data.frame':	1 obs. of  3 variables:
##   .. .. ..$ dimensionGroup:List of 1
##   .. .. ..$ series        :List of 1
##   .. .. ..$ observation   :List of 1
##   .. ..$ annotations:List of 1
##   .. .. ..$ : list()

El siguiente paso, por tanto, es transformar esta respuesta en un formato más manejable, como un tibble en R, que contenga las observaciones de tiempo y sus valores correspondientes. En primer lugar vamos a extraer el objeto de la lista anterior denominado dataSets y todo lo que contiene:

# Serie 0:0:0 (la única que pedimos en el WEO)
obs_df <- weo_data_json$data$dataSets$series$`0:0:0`$observations

obs_values <- obs_df[1, ] |>
  unlist(use.names = FALSE) |>
  as.numeric()

# TIME_PERIOD desde la estructura
time_dim    <- weo_data_json$data$structures$dimensions$observation[[1]]$values |> 
  unlist(use.names = FALSE)

# Tibble final (año, valor)
weo_bol_rgdp <- tibble::tibble(
  time  = as.integer(time_dim),
  value = obs_values
) |>
  dplyr::arrange(time)

weo_bol_rgdp |> 
  kableExtra::kable()
timevalue
19800.610
19810.300
1982-3.939
1983-4.042
1984-0.201
1985-1.676
1986-2.574
19872.463
19882.910
19893.790
19904.636
19915.267
19921.646
19934.269
19944.667
19954.678
19964.361
19974.954
19985.029
19990.427
20002.508
20011.684
20022.486
20032.711
20044.173
20054.421
20064.797
20074.564
20086.148
20093.357
20104.127
20115.204
20125.122
20136.796
20145.461
20154.857
20164.264
20174.195
20184.224
20192.217
2020-8.738
20216.111
20223.606
20233.082
20240.729
20250.600

Finalmente, podemos también hacer la consulta más rica. Por ejemplo, extraemos el crecimiento anual del PIB real y el déficit primario como porcentaje del PIB para Bolivia, España y Estados Unidos entre el 2015 y 2025:

# 1. Parámetros de consulta

countries  <- c("BOL", "ESP", "USA")
indicators <- c("NGDP_RPCH", "GGXONLB_NGDP")  # Real GDP % y déficit % PIB
freq_code  <- "A"                             # Frecuencia anual

country_key   <- paste(countries,  collapse = "+")
indicator_key <- paste(indicators, collapse = "+")
weo_key_multi <- paste(country_key, indicator_key, freq_code, sep = ".")

# URL de datos SDMX 3.0 (IMF WEO)
weo_data_url_multi <- paste0(
  base_url,
  "/data/dataflow/",
  weo$agency, "/", weo$id, "/+/",
  weo_key_multi
)


# 2. Llamada a la API (1980–2030)

weo_data_resp_multi <- httr2::request(weo_data_url_multi) |>
  httr2::req_url_query(
    dimensionAtObservation = "TIME_PERIOD",
    attributes             = "dsd",
    measures               = "all",
    includeHistory         = "false",
    startPeriod            = "1980",
    endPeriod              = "2030"
  ) |>
  httr2::req_perform()

weo_data_json_multi <- weo_data_resp_multi |>
  httr2::resp_body_string() |>
  jsonlite::fromJSON(flatten = FALSE)


# 3. Estructuras de dimensiones

series_dims_resp <- weo_data_json_multi$data$structures$dimensions$series[[1]]
obs_dim_resp     <- weo_data_json_multi$data$structures$dimensions$observation[[1]]

time_values <- obs_dim_resp$values |>
  unlist(use.names = FALSE)


# 4. Extraer las series

series_df <- weo_data_json_multi$data$dataSets$series

# Cada columna es un data.frame con $attributes y $observations
series_list <- lapply(series_df, function(col) col$observations)
names(series_list) <- names(series_df)

# Función para desanidar una serie
extract_series_tbl <- function(obs_df, series_name) {
  # Índices de dimensión a partir de "0:0:0"
  idx <- as.integer(strsplit(series_name, ":", fixed = TRUE)[[1]]) + 1L
  
  # Mapear índices a códigos de COUNTRY / INDICATOR / FREQUENCY
  dim_values <- purrr::map2_chr(
    seq_along(series_dims_resp$id),
    idx,
    ~ series_dims_resp$values[[.x]]$id[.y]
  )
  names(dim_values) <- series_dims_resp$id
  
  # Número de observaciones
  n_obs <- ncol(obs_df)
  if (is.null(n_obs) || n_obs == 0) {
    return(tibble::tibble(
      COUNTRY     = character(0),
      INDICATOR   = character(0),
      FREQ        = character(0),
      TIME_PERIOD = integer(0),
      value       = numeric(0)
    ))
  }
  
  # Mapear columnas "0,1,2,...,41,..." a índices de time_values
  pos_idx     <- as.integer(colnames(obs_df)) + 1L
  these_times <- as.integer(time_values[pos_idx])
  
  values_num <- obs_df[1, ] |>
    unlist(use.names = FALSE) |>
    as.numeric()
  
  tibble::tibble(
    COUNTRY     = dim_values[["COUNTRY"]],
    INDICATOR   = dim_values[["INDICATOR"]],
    FREQ        = dim_values[["FREQUENCY"]],
    TIME_PERIOD = these_times,
    value       = values_num
  )
}

# Aplicar a todas las series
weo_multi_tidy_raw <- purrr::imap_dfr(
  series_list,
  extract_series_tbl
)


# 5. Filtrar 2015–2025

weo_multi_tidy <- weo_multi_tidy_raw |>
  dplyr::filter(TIME_PERIOD >= 2015, TIME_PERIOD <= 2025) |>
  dplyr::arrange(COUNTRY, INDICATOR, TIME_PERIOD)

# Versión ancha (una columna por indicador)
weo_multi_wide <- weo_multi_tidy |>
  tidyr::pivot_wider(
    id_cols    = c(COUNTRY, TIME_PERIOD),
    names_from  = INDICATOR,
    values_from = value
  )

weo_multi_wide |> 
  kableExtra::kable()
COUNTRYTIME_PERIODGGXONLB_NGDPNGDP_RPCH
BOL2015-5.9344.857
BOL2016-6.2534.264
BOL2017-6.7434.195
BOL2018-6.9794.224
BOL2019-5.8752.217
BOL2020-11.228-8.738
BOL2021-7.9796.111
BOL2022-5.4993.606
BOL2023-8.6713.082
BOL2024-7.5060.729
BOL2025-9.9340.600
ESP2015-2.6754.061
ESP2016-1.8852.915
ESP2017-0.8662.896
ESP2018-0.3992.395
ESP2019-1.0011.961
ESP2020-7.999-10.940
ESP2021-4.7236.683
ESP2022-2.5366.370
ESP2023-1.7312.461
ESP2024-1.3353.455
ESP2025-0.5782.908
USA2015-1.6922.946
USA2016-2.3741.820
USA2017-2.7812.458
USA2018-3.1012.967
USA2019-3.5282.584
USA2020-12.072-2.081
USA2021-9.1646.152
USA2022-0.9872.524
USA2023-4.6692.935
USA2024-4.6142.793
USA2025-3.8002.017

SDMX en el ECB

El Banco Central Europeo (BCE) también ofrece una API SDMX 3.0 bastante completa para acceder a sus datos estadísticos. Adicionalmente, el BCE tiene información en su web dedicada a explicar cada uno de sus dataflows con lo que la exploración es más sencilla2.

El flujo de trabajo para extraer datos del BCE es similar al del FMI, pero con algunas diferencias en la estructura de las URLs y los parámetros específicos. Adicionalmente, la respuesta de las consultas a las APIs vienen en formato .xml y los datos se pueden descargar en formato .csv lo cual se tendrá que tomar en cuenta a la hora de procesar la información.

Datos disponibles: el dataflow

En primer lugar, buscamos los dataflows disponibles en la API del BCE.

# 1. Base URL
base_url_ecb <- "https://data-api.ecb.europa.eu/service/"

# 2. Extraer todos los dataflows
flows_resp_ecb <- httr2::request(
  paste0(base_url_ecb, "dataflow")
) |>
  httr2::req_perform()

xml <- xml2::read_xml(
  httr2::resp_body_string(flows_resp_ecb)
)

# 3. Extraer los nodos del Dataflow
df_nodes <- xml2::xml_find_all(
  xml,
  ".//*[local-name()='Dataflow']"
)

# 4. Extraer campos básicos de cada dataflow
flows_tbl <- tibble::tibble(
  id      = purrr::map_chr(df_nodes, ~ xml2::xml_attr(.x, "id")),
  agency  = purrr::map_chr(df_nodes, ~ xml2::xml_attr(.x, "agencyID")),
  version = purrr::map_chr(df_nodes, ~ xml2::xml_attr(.x, "version")),
  
  # extraer la referencia al DSD
  dsd_id = purrr::map_chr(
    df_nodes,
    ~ xml2::xml_attr(
        xml2::xml_find_first(.x, ".//*[local-name()='Structure']/*[local-name()='Ref']"),
        "id"
      )
  ),
  
  dsd_agency = purrr::map_chr(
    df_nodes,
    ~ xml2::xml_attr(
        xml2::xml_find_first(.x, ".//*[local-name()='Structure']/*[local-name()='Ref']"),
        "agencyID"
      )
  ),
  
  dsd_version = purrr::map_chr(
    df_nodes,
    ~ xml2::xml_attr(
        xml2::xml_find_first(.x, ".//*[local-name()='Structure']/*[local-name()='Ref']"),
        "version"
      )
  ),
  
  # Extraer nombre con xpath independiente del namespace
  name    = purrr::map_chr(
    df_nodes,
    ~ xml2::xml_text(
        xml2::xml_find_first(.x, ".//*[local-name()='Name']")
      )
  )
)

# 5. Mostrar tabla ordenada
flows_tbl |>
  dplyr::arrange(id) |>
  dplyr::select(agency, version, dsd_id, name) |>
  kableExtra::kable()
agencyversiondsd_idname
ECB1.0ECB_BCS1AGR
ECB1.0ECB_AME1AMECO
ECB1.0ECB_BKN1Banknotes statistics
ECB.DISS1.0ECB_BKN1Banknotes statistics - Published series
ECB1.0ECB_BLS1Bank Lending Survey Statistics
ECB1.0ECB_BOP_BNTShipments of Euro Banknotes Statistics (ESCB)
ECB1.0ECB_BOP1Euro Area Balance of Payments and International Investment Position Statistics
IMF1.0BOP1_15Balance of Payments and International Investment Position (BPM6)
ECB.DISS1.0BOP1_15Balance of Payments and International Investment Position (BPM6) - Published series
IMF1.0BOPBalance of Payments and International Investment Position
ECB.DISS1.0BOPBalance of Payments and International Investment Position - Published series
ECB1.0ECB_BSI1Balance Sheet Items
ECB.DISS1.0ECB_BSI1Balance Sheet Items - Published series
ECB1.0ECB_BSI1Balance Sheet Items Statistics (tables 2 to 5 of the Blue Book)
ECB1.0ECB_CAR1CAR
ECB1.0ECB_CBD1Statistics on Consolidated Banking Data
ECB1.0ECB_CBD2Consolidated Banking data
ECB1.0ECB_CCP1Central Counterparty Clearing Statistics
ECB1.0ECB_CES1Consumer Expectations Survey
ECB1.0ECB_FMD2Composite Indicator of Systemic Stress
ECB1.0ECB_FMD2Country-Level Index of Financial Stress (CLIFS)
ECB1.0ECB_CPP3Commercial Property Price Statistics
ECB.DISS1.0ECB_CPP3Commercial Property Price Statistics - Published series
ECB1.0NA_SECCSEC
ECB.DISS1.0NA_SECCSEC - Published series
ECB1.0ECB_DCM1Dealogic DCM analytics data
ECB1.0ECB_DD1Derived Data
ECB1.0ECB_DWA1DWA
ESTAT1.0NA_SECGovernment Tax and Social Contributions Receipts Statistics (Eurostat ESA2010 TP, table 9)
ESTAT1.0NA_SECClassification of the Functions of Government Statistics (Eurostat ESA2010 TP, table 11)
ECB1.0ECB_SUR2ECS
ESTAT1.0NA_SECEDP tables
ECB.DISS1.0NA_SECEDP tables - Published series
ESTAT1.0NA_MAINNational accounts, Employment (Eurostat ESA2010 TP, table 0110, 0111)
ECB.DISS1.0NA_MAINNational accounts, Employment (Eurostat ESA2010 TP, table 0110, 0111) - Published series
ECB1.0ECB_EON1EONIA: Euro Interbank Offered Rate
ECB1.0ECB_ESA1ESA95 National Accounts
ECB1.0EUROSTAT_BOP_01European Union Balance of Payments (Source Eurostat)
ECB1.0ECB_EST1Euro Short-Term Rate
ECB1.0ECB_EWT1ECB wage tracker
ECB1.0ECB_EXR1Exchange Rates
ECB.DISS1.0ECB_EXR1Exchange Rates - Published series
ECB1.0ECB_FMD2Financial market data
ECB.DISS1.0ECB_FMD2Financial market data - Published series
ECB1.0ECB_FVC1Financial Vehicle Corporation
ECB.DISS1.0ECB_FVC1Financial Vehicle Corporation - Published series
ECB1.0ECB_FXS1Foreign Exchange Statistics
ESTAT1.0NA_SECGovernment Finance Statistics
ECB.DISS1.0NA_SECGovernment Finance Statistics - Published series
ECB1.0ECB_GST1Government Statistics
ECB1.0ECB_ICPF1Insurance Corporations Assets and Liabilities
ECB.DISS1.0ECB_ICPF1Insurance Corporations Assets and Liabilities - Published series
ECB1.0ECB_ICO1Insurance Corporations Operations
ECB.DISS1.0ECB_ICO1Insurance Corporations Operations - Published series
ECB1.0ECB_ICP1Indices of Consumer prices
EUROSTAT1.0ESTAT_ESAIEAInsurance Corporations & Pension Funds Statistics
ECB.DISS1.0ESTAT_ESAIEAInsurance Corporations & Pension Funds Statistics - Published series
ECB.DISS1.0ECB_ICP1Indices of Consumer prices - Published series
ESTAT1.0NA_MAINNational accounts, Main aggregates in the International Data Cooperation TF context
ECB.DISS1.0NA_MAINNational accounts, Main aggregates in the International Data Cooperation TF context - Published series
ESTAT1.0NA_SECNational accounts, Sector Accounts in the International Data Cooperation TF context
EUROSTAT1.0ESTAT_ESAIEAQuarterly non-financial accounts, QSA by country
EUROSTAT1.0ESTAT_ESAIEAQuarterly Euro Area Accounts
ECB.DISS1.0ESTAT_ESAIEAQuarterly Euro Area Accounts - Published series
EUROSTAT1.0EUROSTAT_LFS1Labour Force Survey Indicators - IESS definition
ECB.DISS1.0EUROSTAT_LFS1Labour Force Survey Indicators - IESS definition - Published series
ECB1.0ECB_IFI1Indicators of Financial Integration
ECB1.0ECB_ILM1Internal Liquidity Management
ECB.DISS1.0ECB_ILM1Internal Liquidity Management - Published series
ECB1.0ECB_IRS1Interest rate statistics
ECB.DISS1.0ECB_IRS1Interest rate statistics - Published series
ECB1.0ECB_IVF1Investment Funds Balance Sheet Statistics
ECB.DISS1.0ECB_IVF1Investment Funds Balance Sheet Statistics - Published series
ECB.DISS1.0ECB_BSI1Aggregated balance sheet of euro area monetary financial institutions, excluding the Eurosystem (millions of euro)
ECB.DISS1.0ECB_BSI1Domestic and cross-border positions of euro area monetary financial institutions, excluding the Eurosystem (EUR billions, outstanding amounts at end of period)
ECB.DISS1.0ECB_BSI1Growth rates for the national contributions to the aggregated balance sheet of euro area monetary financial institutions(annual growth rates at end of period)
ECB.DISS1.0ECB_EXR1Harmonised competitiveness indicators based on consumer price indices (period averages - index 1999 Q1=100)
ECB.DISS1.0ECB_EXR1Harmonised competitiveness indicators based on GDP deflators (period averages - index 1999 Q1=100)
ECB.DISS1.0ECB_EXR1Harmonised competitiveness indicators based on unit labour costs indices for the total economy (period averages - index 1999 Q1=100)
ECB.DISS1.0ESTAT_ESAIEAAggregated balance sheet of euro area insurance corporations and pension funds(EUR millions, outstanding amounts at the end of the period)
ECB.DISS1.0ECB_ICP1HICP - Annual percentage changes, breakdown by purpose of consumption(percentage change)
ECB.DISS1.0ECB_ICP1HICP - Expenditure weights, breakdown by purpose of consumption(Parts per 1000. HICP total = 1000)
ECB.DISS1.0ECB_ICP1HICP - Indices, breakdown by purpose of consumption(2015=100)
ECB.DISS1.0ECB_ICP1HICP - Annual percentage changes, breakdown by type of product (mainly used by the ECB)(percentage change)
ECB.DISS1.0ECB_ICP1HICP - Expenditure weights, breakdown by type of product (mainly used by the ECB)(Parts per 1000. HICP total = 1000)
ECB.DISS1.0ECB_ICP1HICP - Indices, breakdown by type of product (mainly used by the ECB)(2015=100)
ECB.DISS1.0ECB_IVF1Euro area and national investment fund statistics - assets and liabilities(EUR billions; amounts outstanding at the end of the period, transactions during the period)
ECB.DISS1.0ECB_IVF1Euro area and national investment fund statistics - by investment policy and type of fund(EUR billions; amounts outstanding at the end of the period, transactions during the period)
ECB.DISS1.0ECB_MFI1Number of monetary financial institutions (MFIs) in the euro area (pure number)
ECB.DISS1.0ECB_MFI1Number of monetary financial institutions (MFIs) in the non-participating Member States(pure number)
ECB.DISS1.0ECB_MIR1Euro area and national MFI interest rates (MIR)(percentages per annum ; rates on new business as average of the period ; rates on outstanding amounts as end-of-period, unless otherwise indicated)
ECB.DISS1.0NA_MAINQuarter-on-quarter volume growth of GDP and expenditure components(quarter-on-quarter percentage changes)
ECB.DISS1.0NA_MAINYear-on-year volume growth of GDP and expenditure components(annual percentage changes)
ECB.DISS1.0NA_MAINContributions to quarter-on-quarter volume growth of GDP and expenditure components(contributions to quarter-on-quarter percentage changes of GDP in percentage points)
ECB.DISS1.0NA_MAINContributions to year-on-year volume growth of GDP and expenditure components(contributions to annual percentage changes of GDP in percentage points)
ECB.DISS1.0ECB_PSS1Total number of transactions in the euro area(millions; total for the period)
ECB.DISS1.0ECB_PSS1Total number of transactions in the non-participating Member States(millions; total for the period)
ECB.DISS1.0ECB_PSS1Relative importance of payment services in the euro area(percentage of total number of national transactions)
ECB.DISS1.0ECB_PSS1Relative importance of payment services in the non-participating Member States(percentage of total number of national transactions)
ECB.DISS1.0ECB_PSS1Total value of transactions in the euro area(EUR millions; total for the period)
ECB.DISS1.0ECB_PSS1Total value of transactions in the non-participating Member States(EUR millions; total for the period)
ECB.DISS1.0ECB_BSI1JDF_PUB_BSI_CROSS_BORDER_POSITIONS - Published series
ECB.DISS1.0ECB_BSI1JDF_PUB_BSI_MFI_BALANCE_SHEET - Published series
ECB.DISS1.0ECB_IVF1JDF_PUB_IVF_INVESTMENT_FUNDS - Published series
ECB.DISS1.0ECB_MIR1JDF_PUB_MIR_BANK_INTEREST_RATES - Published series
ECB.DISS1.0BOPOfficial reserve assets, other foreign currency assets and related short-term liabilities(EUR millions)
ECB.DISS1.0ECB_SEC1Outstanding amounts and transactions of euro-denominated debt securities by country of residence, sector of the issuer and original maturity(EUR millions; nominal values)
ECB.DISS1.0ECB_SEC1Outstanding amounts and transactions of listed shares by country of residence and sector of the issuer(EUR millions; nominal values)
EUROSTAT1.0EUROSTAT_JVC2Eurostat Job Vacancy Statistics
ECB.DISS1.0EUROSTAT_JVC2Eurostat Job Vacancy Statistics - Published series
ESTAT1.0JVSJob Vacancy Statistics
ECB.DISS1.0JVSJob Vacancy Statistics - Published series
ECB1.0ECB_CBD1EBA Key Risk Indicators
ESTAT1.0LCILabour Cost Indices
ECB.DISS1.0LCILabour Cost Indices - Published series
EUROSTAT1.0EUROSTAT_LFS1Labour Force Survey
ECB.DISS1.0EUROSTAT_LFS1Labour Force Survey - Published series
ECB1.0ECB_LIG1Large Insurance Groups Statistics
ECB1.0ECB_MFI1List of MFIs
ECB1.0ECB_MIR1MFI Interest Rate Statistics
ECB.DISS1.0ECB_MIR1MFI Interest Rate Statistics - Published series
ECB1.0ECB_MMS1Money Market Survey
ECB1.0ECB_MMSR1Money Market Statistical Reporting
ESTAT1.0NA_MAINNational accounts, Main aggregates (Eurostat ESA2010 TP, table 1)
ECB.DISS1.0NA_MAINNational accounts, Main aggregates (Eurostat ESA2010 TP, table 1) - Published series
ECB.DISS1.0ECB_BSI1Euro area monetary aggregates
ECB.DISS1.0ECB_EXR1Exchange rates
ECB.DISS1.0NA_SECGovernment finance
ECB.DISS1.0ECB_ICP1Inflation
ECB.DISS1.0ECB_ICP1Key euro area indicators (ICP)
ECB.DISS1.0ECB_BSI1Key euro area indicators (BSI)
ECB.DISS1.0NA_MAINKey euro area indicators (ESA)
ECB.DISS1.0ECB_DD1Key euro area indicators (DD)
ECB.DISS1.0ECB_STS1Key euro area indicators (STS)
ECB.DISS1.0ECB_EXR1Key euro area indicators (EXR)
ECB.DISS1.0NA_SECKey euro area indicators (GST)
ECB.DISS1.0ECB_FMD2Key euro area indicators (FM)
ECB.DISS1.0ECB_MIR1Bank interest rates
ECB1.0ECB_MPD1Macroeconomic Projection Database
ECB1.0ECB_BCS1NEC
ECB1.0ECB_OFI1Other Financial Intermediaries
ECB1.0ECB_OMO1Open market operations
ECB1.0ECB_PAY1PAY
ECB1.0ECB_PAY11PCN
ECB1.0ECB_PAY5PCP
ECB1.0ECB_PAY2PCT
ECB1.0ECB_PAY3PDD
ECB1.0ECB_PAY4PEM
ECB1.0ECB_ICPF1Pension Fund Assets and Liabilities
ECB1.0ECB_PFM1Pension funds number of members
ECB1.0ECB_ICPF1Pension funds Regulation
ECB.DISS1.0ECB_ICPF1Pension funds Regulation - Published series
ECB.DISS1.0ECB_ICPF1Pension Fund Assets and Liabilities - Published series
ECB1.0ECB_PAY6PIS
ECB1.0ECB_PAY7Losses due to fraud by liability bearer
ECB1.0ECB_PAY10PMC
ECB1.0ECB_PAY9PPC
ECB1.0ECB_PAY13PSN
ECB1.0ECB_PSS1Payments and Settlement Systems Statistics
ECB1.0ECB_PAY14PST
ECB1.0ECB_PAY12PTN
ECB1.0ECB_PAY8PTT
ESTAT1.0NA_SECQuarterly Sector Accounts (MUFA and NFA Eurostat ESA2010 TP, table 801)
ECB.DISS1.0NA_SECQuarterly Sector Accounts (MUFA and NFA Eurostat ESA2010 TP, table 801) - Published series
ECB1.0ECB_BOP1International Reserves of the Eurosystem
IMF1.0BOP1_15International Reserves of the Eurosystem (BPM6)
ECB.DISS1.0BOP1_15International Reserves of the Eurosystem (BPM6) - Published series
ECB1.0ECB_RAI1Risk Assessment Indicators
IMF1.0BOPInternational Reserves of the Eurosystem
ECB.DISS1.0BOPInternational Reserves of the Eurosystem - Published series
ECB1.0ECB_FMD2Risk Dashboard data
ECB1.0ECB_FMD2Risk Dashboard data
ECB1.0ECB_RES1Commercial Property Prices
ECB.DISS1.0ECB_RES1Commercial Property Prices - Published series
ECB1.0ECB_RES1Structural Housing Indicators
ECB1.0ECB_RES1Real Estate Statistics
ECB.DISS1.0ECB_RES1Real Estate Statistics - Published series
ECB1.0ECB_RES1Residential Property Valuation
ECB1.0ECB_RIR2Retail Interest Rates
ECB1.0ECB_RPP1Residential Property Price Index Statistics
ECB.DISS1.0ECB_RPP1Residential Property Price Index Statistics - Published series
ECB1.0ECB_RPP1Residential Property Valuation
ECB1.0ECB_RTD1Real Time Database (research database)
ECB1.0ECB_SAFESurvey on the Access to Finance of SMEs
ECB1.0ECB_SEC1Securities
ECB.DISS1.0ECB_SEC1Securities - Published series
ECB1.0ECB_SEE1Securities exchange - Trading Statistics
ECB1.0ECB_SESFODSurvey on credit terms and conditions in euro-denominated securities financing and over-the-counter derivatives markets
ECB1.0ECB_SHI1Structural Housing Indicators Statistics
ECB1.0ECB_SHS6Securities Holding Statistics
ECB1.0NA_SECSHSS
ECB1.0ECB_FCT1Survey of Professional Forecasters
ECB1.0ECB_SSI1Banking structural statistical indicators
ECB1.0ECB_SSI1Structural Financial Indicators for Payments
ECB1.0ECB_SSS1Securities Settlement Statistics
ECB1.0ECB_BOP1Balance of Payments statistics, national data
ECB1.0ECB_BOP1Euro Area Balance of Payments and International Investment Position Statistics, Geographical Breakdown
ECB1.0ECB_BCS1Short-Term Business Statistics
ECB1.0ECB_STP1STEP data
ECB1.0ECB_STS1Short-Term Statistics
ECB.DISS1.0ECB_STS1Short-Term Statistics - Published series
ECB1.0ECB_SUP1Supervisory Banking Statistics
ECB1.0ECB_SUR1Opinion Surveys
ECB.DISS1.0ECB_SUR1Opinion Surveys - Published series
ECB1.0ECB_TGB1Target Balances
ECB1.0ECB_TRD1External Trade
ECB.DISS1.0ECB_TRD1External Trade - Published series
ECB1.0ECB_WTS1Trade weights
ECB1.0ECB_FMD2Financial market data - yield curve
ECB.DISS1.0ECB_FMD2Financial market data - yield curve - Published series

En la tabla anterior podemos ver todos los dataflows disponibles en la API del BCE. Cada dataflow tiene un identificador único (id), una agencia responsable (agency), una versión (version), el dsd_id que se utilizará para identificar el dataflow y un nombre descriptivo (name).

Supongamos que nos interesa el dataflow EST, que muestra el tipo de interés a corto plazo del euro (€STR) y refleja los costos de financiación mayorista, en euros y sin garantía, de los bancos ubicados en la zona del euro para préstamos a un día.

# Nos quedamos con el dataflow CBD2
est <- flows_tbl |> 
  dplyr::filter(id == "EST")

est |> 
  kableExtra::kable()
idagencyversiondsd_iddsd_agencydsd_versionname
ESTECB1.0ECB_EST1ECB1.0Euro Short-Term Rate

Estructura de datos: las dimensiones

Como se indicó anteriormente, el siguiente paso es entender qué variables (dimensiones) están disponibles en la estructura de datos del dataflow EST. En este caso particular, el BCE tiene información detallada en su web sobre lo que contiene este dataflow. Sin embargo, vamos a verificar qué dimensiones tiene:

# 1. Definir parámetros del DSD

base_url_ecb <- "https://data-api.ecb.europa.eu/service/"
dsd_id       <- "ECB_EST1"
dsd_agency   <- "ECB"
dsd_version  <- "latest"   # o "1.0" si se quisiera una versión específica


# 2. Descargar el datastructure del dataflow EST

url_dsd <- paste0(
  base_url_ecb,
  "datastructure/", dsd_agency, "/", dsd_id, "/", dsd_version,
  "?references=children"
)

dsd_resp <- httr2::request(url_dsd) |>
  httr2::req_perform()

xml_dsd <- xml2::read_xml(
  httr2::resp_body_string(dsd_resp)
)


# 3. Extraer series de dimensiones

series_dims <- xml2::xml_find_all(
  xml_dsd,
  ".//*[local-name()='DataStructure']
     /*[local-name()='DataStructureComponents']
     /*[local-name()='DimensionList']
     /*[local-name()='Dimension']"
)


# 4. Extraer el ID de la dimensión y el ConceptID ref

dimensions_tbl <- tibble::tibble(
  id = purrr::map_chr(
    series_dims,
    ~ xml2::xml_attr(.x, "id")
  ),
  
  concept_ref = purrr::map_chr(
    series_dims,
    ~ {
      concept_node <- xml2::xml_find_first(
        .x,
        ".//*[local-name()='ConceptIdentity']/*[local-name()='Ref']"
      )
      xml2::xml_attr(concept_node, "id")
    }
  ),
  
  position = seq_along(series_dims)
)

dimensions_tbl |> 
  kableExtra::kable()
idconcept_refposition
FREQFREQ1
BENCHMARK_ITEMBENCHMARK_ITEM2
DATA_TYPE_ESTDATA_TYPE_EST3

Codelists: los códigos válidos

Una vez se conocen las dimensiones del dataflow EST, el siguiente paso es extraer las codelists asociadas a cada dimensión. En algunos casos, la DSD contiene información adicional sobre los codelist como, por ejemplo, restricciones que nos permiten entender qué códigos son válidos. En otros casos, no.

Supongamos que el DSD no mostrase las restricciones de los codelists. En este caso tendríamos que utilizar una aproximación más pragmática y deberíamos extraer una muestra de datos para entender qué valores toman nuestras dimensiones. Esto es relativamente sencillo con el BCE porque la API devuelve datos estructurados en formato .csv. Además, se utilizará el parámetro lastNObservations para controlar el tamaño de la muestra:

est_sample <- httr2::request(
  "https://data-api.ecb.europa.eu/service/data/EST"
) |>
  httr2::req_url_query(
    format = "csvdata",
    lastNObservations = 100
  ) |>
  httr2::req_perform() |>
  httr2::resp_body_string() |>
  readr::read_csv(show_col_types = FALSE)

est_sample |> 
  dplyr::select(KEY, FREQ, BENCHMARK_ITEM, DATA_TYPE_EST, TIME_PERIOD, OBS_VALUE) |>
  head() |> 
  kableExtra::kable()
KEYFREQBENCHMARK_ITEMDATA_TYPE_ESTTIME_PERIODOBS_VALUE
EST.B.EU000A2QQF08.CIBEU000A2QQF08CI2025-07-22107.2684
EST.B.EU000A2QQF08.CIBEU000A2QQF08CI2025-07-23107.2742
EST.B.EU000A2QQF08.CIBEU000A2QQF08CI2025-07-24107.2799
EST.B.EU000A2QQF08.CIBEU000A2QQF08CI2025-07-25107.2856
EST.B.EU000A2QQF08.CIBEU000A2QQF08CI2025-07-28107.3028
EST.B.EU000A2QQF08.CIBEU000A2QQF08CI2025-07-29107.3086

Para verificar los valores úinicos realizamos la siguiente consulta sobre las dimensiones del dataflow:

est_sample |> 
  dplyr::distinct(FREQ, BENCHMARK_ITEM, DATA_TYPE_EST,TITLE) |> 
  kableExtra::kable()
FREQBENCHMARK_ITEMDATA_TYPE_ESTTITLE
BEU000A2QQF08CICompounded euro short-term rate index (1 Oct 2019 = 100)
BEU000A2QQF16CRCompounded euro short-term rate average rate, 1 week tenor
BEU000A2QQF24CRCompounded euro short-term rate average rate, 1 month tenor
BEU000A2QQF32CRCompounded euro short-term rate average rate, 3 months tenor
BEU000A2QQF40CRCompounded euro short-term rate average rate, 6 months tenor
BEU000A2QQF57CRCompounded euro short-term rate average rate, 12 months tenor
BEU000A2X2A25CMEuro short-term rate - Calculation method
BEU000A2X2A25NBEuro short-term rate - Number of active banks
BEU000A2X2A25NTEuro short-term rate - Number of transactions
BEU000A2X2A25R25Euro short-term rate - Rate at 25th percentile of volume
BEU000A2X2A25R75Euro short-term rate - Rate at 75th percentile of volume
BEU000A2X2A25RPEuro short-term rate - Publication type
BEU000A2X2A25TTEuro short-term rate - Total volume
BEU000A2X2A25VLEuro short-term rate - Share of volume of the 5 largest active banks
BEU000A2X2A25WTEuro short-term rate - Volume-weighted trimmed mean rate

Extracción de datos: la consulta final

Entonces, supongamos que queremos extraer la tasa de interés compuesta del euro a corto plazo (€STR) con un plazo de 12 meses. Según la muestra anterior, los códigos relevantes son:

est_all <- httr2::request(
  "https://data-api.ecb.europa.eu/service/data/EST/B.EU000A2QQF57.CR"
) |>
  httr2::req_url_query(
    format = "csvdata",
    startPeriod = "2025-01-01",
    endPeriod   = "2025-12-06"
  ) |>
  httr2::req_perform() |>
  httr2::resp_body_string() |>
  readr::read_csv(show_col_types = FALSE)

est_all |> 
  dplyr::select(KEY, FREQ, BENCHMARK_ITEM, DATA_TYPE_EST, TIME_PERIOD, OBS_VALUE) |>
  head() |> 
  kableExtra::kable() 
KEYFREQBENCHMARK_ITEMDATA_TYPE_ESTTIME_PERIODOBS_VALUE
EST.B.EU000A2QQF57.CRBEU000A2QQF57CR2025-01-023.70759
EST.B.EU000A2QQF57.CRBEU000A2QQF57CR2025-01-033.70480
EST.B.EU000A2QQF57.CRBEU000A2QQF57CR2025-01-063.69739
EST.B.EU000A2QQF57.CRBEU000A2QQF57CR2025-01-073.69557
EST.B.EU000A2QQF57.CRBEU000A2QQF57CR2025-01-083.69083
EST.B.EU000A2QQF57.CRBEU000A2QQF57CR2025-01-093.68803

Graficamos:

library(ggplot2)

est_all |> 
ggplot( aes(x = TIME_PERIOD, y = OBS_VALUE)) +
  geom_line(color = "steelblue", linewidth = 1.2) +
  labs(
    title = "Tasa de interés compuesta del euro a corto plazo (€STR) - 12 meses",
    x = "Fecha",
    y = "Tasa de interés (%)"
  ) +
  theme_bw()

Una nueva librería: imfapi

Hace unos meses alguien de mi red en LinkedIn compartió el post de Christopher Smith donde anunciaba la creación de una nueva librería que “Proporciona funciones fáciles de usar para acceder de forma programática a datos macroeconómicos del ‘SDMX 3.0 IMF Data API’ del Fondo Monetario Internacional.”. La librería en cuestión está disponible en CRAN y funciona establemente. Además, esta librería forma parte del EconDataverse, que recomiendo explorar. A continuación voy a mostrar, de forma simplificada, como acceder a los mismos datos del WEO que vimos anteriormente.

Usando imfapi para extraer datos del WEO

En primer lugar, cargamos la librería:

library(imfapi)

Luego, extraemos los dataflows disponibles en la API del FMI:

dataflows <- imfapi::imf_get_dataflows()

dataflows |> 
  dplyr::glimpse() 
## Rows: 72
## Columns: 6
## $ id           <chr> "GFS_SSUC", "QGFS", "QNEA", "MFS_IR", "WHDREO", "IRFCL", …
## $ name         <chr> "GFS Statement of Sources and Uses of Cash", "Quarterly G…
## $ description  <chr> "The Government Finance Statistics (GFS) includes a cash‐…
## $ version      <chr> "10.0.0", "12.0.0", "7.0.0", "8.0.1", "5.0.0", "11.0.0", …
## $ agency       <chr> "IMF.STA", "IMF.STA", "IMF.STA", "IMF.STA", "IMF.WHD", "I…
## $ last_updated <chr> "2025-06-06T01:50:52.945047Z", "2025-09-03T16:39:57.78654…

Ahora, filtramos el dataflow WEO:

weo_flow <- dataflows |> 
  dplyr::filter(id == "WEO")

weo_flow |> 
  kableExtra::kable()
idnamedescriptionversionagencylast_updated
WEOWorld Economic Outlook (WEO)The World Economic Outlook (WEO) database is created during the biannual WEO exercise, which begins in January and June of each year and results in the April and September/October WEO publication. Selected series from the publication are available in a database format.9.0.0IMF.RES2025-10-08T18:00:33.590181Z

Y obtenemos las dimensiones del dataflow WEO:

dsd_weo <- imfapi::imf_get_datastructure(dataflow_id = "WEO")
dsd_weo |> 
  kableExtra::kable()
dimension_idtypeposition
COUNTRYDimension0
INDICATORDimension1
FREQUENCYDimension2

A continuación, extraemos los codelists asociados al dataflow WEO:

dim_list <- list()

for(dim_id in dsd_weo$dimension_id) {
  dim_list[[dim_id]] <- imfapi::imf_get_codelists(dimension_ids = dim_id,
                                                  dataflow_id = "WEO")
}

dim_list |> 
  dplyr::bind_rows() |> 
  dplyr::group_by(dimension_id) |> 
  dplyr::summarise(n_codes = dplyr::n()) |> 
  kableExtra::kable()
dimension_idn_codes
COUNTRY338
FREQUENCY6
INDICATOR145

En este caso tenemos 338 códigos para la dimensión COUNTRY, 6 códigos para la dimensión FREQUENCY y 145 códigos válidos para INDICATOR.

Finalmente, extraemos los datos del WEO para Bolivia y el crecimiento del PIB real:

data <- imfapi::imf_get(dataflow_id = "WEO",
                        dimensions = list(COUNTRY = "BOL",
                                          INDICATOR = "NGDP_RPCH",
                                          FREQUENCY = "A")
                        )

data |>
  kableExtra::kable()
COUNTRYINDICATORFREQUENCYTIME_PERIODOBS_VALUE
BOLNGDP_RPCHA19800.610
BOLNGDP_RPCHA19810.300
BOLNGDP_RPCHA1982-3.939
BOLNGDP_RPCHA1983-4.042
BOLNGDP_RPCHA1984-0.201
BOLNGDP_RPCHA1985-1.676
BOLNGDP_RPCHA1986-2.574
BOLNGDP_RPCHA19872.463
BOLNGDP_RPCHA19882.910
BOLNGDP_RPCHA19893.790
BOLNGDP_RPCHA19904.636
BOLNGDP_RPCHA19915.267
BOLNGDP_RPCHA19921.646
BOLNGDP_RPCHA19934.269
BOLNGDP_RPCHA19944.667
BOLNGDP_RPCHA19954.678
BOLNGDP_RPCHA19964.361
BOLNGDP_RPCHA19974.954
BOLNGDP_RPCHA19985.029
BOLNGDP_RPCHA19990.427
BOLNGDP_RPCHA20002.508
BOLNGDP_RPCHA20011.684
BOLNGDP_RPCHA20022.486
BOLNGDP_RPCHA20032.711
BOLNGDP_RPCHA20044.173
BOLNGDP_RPCHA20054.421
BOLNGDP_RPCHA20064.797
BOLNGDP_RPCHA20074.564
BOLNGDP_RPCHA20086.148
BOLNGDP_RPCHA20093.357
BOLNGDP_RPCHA20104.127
BOLNGDP_RPCHA20115.204
BOLNGDP_RPCHA20125.122
BOLNGDP_RPCHA20136.796
BOLNGDP_RPCHA20145.461
BOLNGDP_RPCHA20154.857
BOLNGDP_RPCHA20164.264
BOLNGDP_RPCHA20174.195
BOLNGDP_RPCHA20184.224
BOLNGDP_RPCHA20192.217
BOLNGDP_RPCHA2020-8.738
BOLNGDP_RPCHA20216.111
BOLNGDP_RPCHA20223.606
BOLNGDP_RPCHA20233.082
BOLNGDP_RPCHA20240.729
BOLNGDP_RPCHA20250.600

En la tabla anterior podemos ver los datos extraídos del WEO para Bolivia y el crecimiento del PIB real con frecuencia anual. La librería imfapi simplifica considerablemente el proceso de extracción de datos desde la API del FMI, permitiendo a los usuarios centrarse en el análisis de los datos en lugar de en los detalles técnicos de la API SDMX.

Conclusiones

El objetivo de esta entrada fue mostrar que, para el analista aplicado, trabajar con APIs SDMX no es un fin en sí mismo, sino una forma de construir flujos de trabajo más limpios, reproducibles y escalables. En lugar de depender de descargas manuales y pasos poco documentados, SDMX permite encontrar, entender y extraer datos macroeconómicos de distintos organismos bajo una lógica común.

A través del FMI y el BCE vimos que el proceso general —identificar el dataflow, revisar su estructura, consultar las codelists y finalmente extraer los datos— se repite de manera consistente, lo que facilita automatizarlo y aplicarlo a otras bases. Además, herramientas como imfapi demuestran cómo este enfoque puede simplificarse aún más, haciendo que la carga técnica sea mínima y que la mayor parte del esfuerzo se concentre en el análisis.


  1. Estos dos modificadores le indican al servidor que queremos toda la información estructural del dataflow y todos los elementos relacionados, es decir: la DSD, los conceptos y, crucialmente, todas las codelists que dependen del dataflow. Esto nos evita tener que buscar manualmente qué codelists existen, cómo se llaman o en qué versión están. En lugar de ello, obtenemos toda la estructura completa del WEO en una sola llamada, de forma limpia y consistente. ↩︎

  2. Por ejemplo, el dataflow EST que corresponde a la tasa de interés de corto plazo €STR y que se investigará más adelante se puede encontrar aquí↩︎