1
Fork 0
mirror of https://github.com/Steffo99/sdmx-sandbox.git synced 2025-01-05 05:29:43 +00:00
Esperimenti con pandaSDMX per il tirocinio Unimore
Find a file
2021-03-28 04:58:27 +02:00
.idea First commit 2021-03-12 12:07:11 +01:00
.gitignore First commit 2021-03-12 12:07:11 +01:00
README.ipynb Rename pandasdmx.ipynb to README.ipynb 2021-03-28 04:58:27 +02:00
sdmx-sandbox.iml First commit 2021-03-12 12:07:11 +01:00

{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "# PandaSDMX\n",
    "\n",
    "- [Documentazione aggiornata (v1.4.1)](https://pandasdmx.readthedocs.io/en/latest/)\n",
    "- [Esempio breve (con poche spiegazioni)](https://pandasdmx.readthedocs.io/en/master/example.html)\n",
    "- [Esempio approfondito (ma non troppo aggiornato)](https://pandasdmx.readthedocs.io/en/latest/walkthrough.html)"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Installazione\n",
    "\n",
    "- L'ultima versione non funziona con Pydantic 1.8.1 ma richiede 1.7 ([dr-leo/pandaSDMX#204](https://github.com/dr-leo/pandaSDMX/issues/204))"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "!pip install pandasdmx pydantic==1.7"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Esempio"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "import pandas\n",
    "import pandasdmx\n",
    "\n",
    "# Per type annotations\n",
    "import pandasdmx.message\n",
    "import pandasdmx.model\n",
    "import pandasdmx.source\n",
    "import pandasdmx.source.estat"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "È possibile selezionare tra più fonti di dati, tra i quali Eurostat (`ESTAT`)."
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "> __Request__: client di comunicazione tra `pandasdmx` e un server di dati come Eurostat\n",
    "\n",
    "Come prima cosa, è necessario creare un'istanza di `pandasdmx.Request`:"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "eurostat: pandasdmx.Request = pandasdmx.Request(\"ESTAT\")\n",
    "eurostat"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "> __Dataflow__: set di metadati relativi a una misura effettuata (ad esempio, `educ_enrl1ad - Students by ISCED level, study intensity and sex`)\n",
    "\n",
    "> __Message__: risposta HTTPS ricevuta in seguito a una richiesta effettuata ad un server di dati\n",
    "\n",
    "Poi, scarichiamo _tutti_ i dataflow disponibili usando `.dataflow()` sul client creato in precedenza per effettuare una richiesta al server Eurostat, creando un `pandasdmx.message.Message`:"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "all_flows_msg: pandasdmx.message.Message = eurostat.dataflow()\n",
    "all_flows_msg"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "> __Series__: una specie di `dict` più veloce e avanzato implementato da `pandas`\n",
    "\n",
    "PandaSDMX ha la funzionalità che cercavamo di cercare dataset per keyword!\n",
    "\n",
    "Per effettuare la ricerca, usiamo il metodo `.to_pandas()` per convertire il `Message` in oggetti Python e/o `pandas`, poi usiamo i metodi \"nativi\" per trovare quello che ci serve:"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Converte i risultati in due Series di pandas, una con i dataflow e una con la loro relativa struttura\n",
    "_dict: dict[str, pandas.Series] = all_flows_msg.to_pandas()\n",
    "all_flows: pandas.Series = _dict[\"dataflow\"]\n",
    "all_structs: pandas.Series = _dict[\"structure\"]\n",
    "all_flows, all_structs"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Cerchiamo nella Series i allflows la cui descrizione contiene \"student\"\n",
    "# https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.str.contains.html\n",
    "student_flows: pandas.Series = all_flows[all_flows.str.contains(\"student\", case=False)]\n",
    "student_flows"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Per continuare gli esperimenti, prendiamo il primo dataflow tra quelli contenenti `\"student\"` nel label:"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "my_flow_label = student_flows.index[0]\n",
    "my_flow_label"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Usiamo il label per chiamare di nuovo `.dataflow()`, specificando però stavolta il dataflow di cui ci interessano i dettagli:"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "my_flow_msg: pandasdmx.message.Message = eurostat.dataflow(my_flow_label)\n",
    "my_flow: pandasdmx.model.DataflowDefinition = my_flow_msg.dataflow[my_flow_label]\n",
    "my_flow"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "> __Structure__: metadati su come sono strutturate le misure di un dataflow (cosa è stato misurato, quali filtri è possibile applicare, note, etc)\n",
    "\n",
    "_Particolarità di Eurostat: la structure va richiesta separatamente dal dataflow, in quanto tutti i campi a parte `id` di `dataflow.structure` sono sempre vuoti._\n",
    "\n",
    "Scopriamo prima il label della structure, poi scarichiamo da Eurostat la structure del dataflow che ci interessa con il metodo `.datastructure()`:"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "my_struct_label: pandasdmx.source.DataStructureDefinition = my_flow.structure.id\n",
    "my_struct_msg: pandasdmx.message.Message = eurostat.datastructure(my_struct_label)\n",
    "my_struct: pandasdmx.source.DataStructureDefinition = my_struct_msg.structure[my_struct_label]\n",
    "my_struct"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Ispezioniamo la structure che abbiamo scaricato, visualizzandola contemporaneamente [sul Data Explorer di Eurostat](https://ec.europa.eu/eurostat/databrowser/view/educ_enrl1ad/default/table?lang=en)\n",
    "\n",
    "> __Measures__: valori aggregati relativi alle misure effettuate, simili a `COUNT(*)` dell'SQL\n",
    "\n",
    "> __Dimensions__: filtri applicabili ai dati raccolti in modo simile all'`HAVING` dell'SQL\n",
    "\n",
    "> __Attributes__: ???\n",
    "\n",
    "> __Annotations__: commenti che possono essere aggiunti al dataflow"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "my_struct.annotations, my_struct.measures, my_struct.attributes, my_struct.dimensions"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Infine, richiediamo i dati da Eurostat, limitandoli a quelli dell'`IT`alia dal 2010 in poi e selezionando solo il `WORKTIME` `TOTAL`, e convertiamoli in una Series multi-chiave:"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "my_data_msg: pandasdmx.message.Message = eurostat.data(my_flow_label, key={\"GEO\": \"IT\", \"WORKTIME\": \"TOTAL\"}, params={\"startPeriod\": \"2010\"})\n",
    "my_data_series: pandas.Series = my_data_msg.to_pandas()\n",
    "my_data_series"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "> __DataFrame__: Tabella di dati di `pandas`, implementata come array di Series\n",
    "\n",
    "Per avere una rappresentazione migliore dei dati sul notebook, convertiamo la Series a un DataFrame:"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "my_data: pandas.DataFrame = my_data_series.to_frame()\n",
    "my_data"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Inoltre, per semplificarne le query, \"appiattiamo\" il [MultiIndex](https://pandas.pydata.org/pandas-docs/stable/user_guide/advanced.html) trasformandolo in normalissime colonne:"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "my_data.reset_index(inplace=True)\n",
    "my_data"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Abbiamo finalmente i dati, e possiamo manipolarli come un qualsiasi DataFrame di `pandas`, in modo molto simile a una tabella SQL:"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Il numero di studenti [M]aschi, [F]emmine e [T]otali in Italia nel [2010], [2011] e [2012]\n",
    "my_data.groupby([\"FREQ\", \"TIME_PERIOD\", \"SEX\"]).first()"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Sorgenti dati\n",
    "\n",
    "Tra le sorgenti di dati di cui abbiamo parlato, sono [completamente supportate](https://pandasdmx.readthedocs.io/en/latest/sources.html):\n",
    "\n",
    "- `ESTAT` - Eurostat\n",
    "- `ISTAT` - ISTAT\n",
    "\n",
    "Queste sorgenti non supportano lo standard `SDMX-MD` ma solo lo standard `SDMX-JSON`, che [non supporta query di metadati e struttura](https://pandasdmx.readthedocs.io/en/latest/sources.html#data-source-limitations):\n",
    "\n",
    "- `OECD` - Organisation for Economic Cooperation and Development"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Archiviazione dati\n",
    "\n",
    "Se si vogliono replicare dati provenienti da queste fonti, si potrebbe usare tranquillamente un database **relazionale** (SQL) le cui tabelle sono generate a runtime in base alla struttura del dataflow desiderato.\n",
    "\n",
    "[SQLAlchemy](https://www.sqlalchemy.org/) potrebbe essere utile in questo caso; non sono particolarmente familiare con l'[ORM di Django](https://docs.djangoproject.com/en/3.1/topics/db/models/), ma sembrano molto simili (anche se [si direbbe che SQLAlchemy supporti query più complesse](https://stackoverflow.com/questions/18199053/example-of-what-sqlalchemy-can-do-and-django-orm-cannot))."
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Filtraggio in base a `TIME_PERIOD`\n",
    "\n",
    "È possibile capire se un DataFrame ha una colonna `TIME_PERIOD` in questo modo:"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "\"TIME_PERIOD\" in list(my_data.columns)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "I `TIME_PERIOD` possono essere misurati in modi diversi: anni, quadrimestri, giorni, etc...\n",
    "\n",
    "I valori possibili sono:"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "list(my_struct.dimensions.get(\"FREQ\").local_representation.enumerated)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Per capire quali sono disponibili, si può effettuare una query aggregata:"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "list(my_data.groupby([\"FREQ\"]).any().index)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "In questo caso, è disponibile solo `A`, il che significa che le misurazioni sono **eseguite solo annualmente**.\n",
    "\n",
    "Possiamo trovare il \"periodo\" più recente con una query sulla tabella:"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "latest_period = my_data[\"TIME_PERIOD\"].max()\n",
    "latest_period"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Possiamo filtrare i dati in modo da avere solo quelli del periodo desiderato:"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "my_data.loc[my_data[\"TIME_PERIOD\"] == latest_period]"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "In generale, possiamo applicare ulteriori filtri effettuando accessi agli elementi (`__getitem__`) della proprietà `loc` del dataframe:"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "my_data.loc[my_data[\"SEX\"] == \"M\"]"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "outputs": [
    {
     "data": {
      "text/plain": "  FREQ UNIT ISCED97 SEX WORKTIME GEO TIME_PERIOD     value\n5    A   NR     ED0   M    TOTAL  IT        2012  879256.0",
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>FREQ</th>\n      <th>UNIT</th>\n      <th>ISCED97</th>\n      <th>SEX</th>\n      <th>WORKTIME</th>\n      <th>GEO</th>\n      <th>TIME_PERIOD</th>\n      <th>value</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>5</th>\n      <td>A</td>\n      <td>NR</td>\n      <td>ED0</td>\n      <td>M</td>\n      <td>TOTAL</td>\n      <td>IT</td>\n      <td>2012</td>\n      <td>879256.0</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(\n",
    "    my_data\n",
    "        .loc[my_data[\"TIME_PERIOD\"] == latest_period]\n",
    "        .loc[my_data[\"SEX\"] == \"M\"]\n",
    "        .loc[my_data[\"ISCED97\"] == \"ED0\"]\n",
    ")"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}