Created on 26 Mar 2020 ;    Modified on 27 Mar 2020 ;    Translationenglish

Come aggiorno gli articoli sul Coronavirus

Ho scritto un paio di articoli riguardo l'andamento dell'epidemia di Coronavirus COVID-19. Dopo di che, mi si è posto il problema di aggiornare quotidianamente i dati. Farlo manualmente era un notevole dispendio di tempo. Quindi: perché non far fare ai computer il loro lavoro?

Premessa

In questo periodo, qui in Italia, siamo ipnotizzati dai dati relativi all'epidemia di Coronavirus Covid-19. Dati che cambiano giorno dopo giorno.

Per chi, come il sottoscritto, ha avuto la ventura di stendere qualche articolo riguardo questa epidemia, si pone il problema dell'aggiornamento dei dati cui si fa riferimento negli articoli.

Farlo manualmente non è una strada perseguibile. Si tratta di un impegno di almeno mezz'ora, se non di più, da eseguire tutti i giorni per mesi. Una noia!

Di conseguenza, prendiamo monitor e tastiera e mettiamo al lavoro il nostro amato/odiato compagno di vita: il computer.

L'organizzazione generale

L'idea consiste nell'avere un articolo formato da un testo che non cambia nel tempo, inframezzato da alcune aree, che accoglieranno grafici e tabelle di dati il cui contenuto viene aggiornato automaticamente con la cadenza che desideriamo.

Nel seguito, chiamiamo template l'artefatto predetto.

Per raggiungere questo scopo utilizziamo:

  • il linguaggio reStructuredText per scrivere il testo del template;
  • il linguaggio Python, per creare l'algoritmo generale da seguire, per scaricare i dati aggiornati da Internet e per fondere i dati nel template;
  • la libreria Pandas di Python, per importare i dati aggiornati in una forma tabellare standard, elaborarli secondo necessità, formare le tabelle da visualizzare.

Certo, non si tratta di poca cosa: abbiamo un bel po' di roba da cuocere al fuoco. Ma, vi assicuro, utilizzando un altro linguaggio, ad esempio il C, più veloce in esecuzione ma anche molto più impegnativo da gestire in fase di progetto, sarebbe andata molto peggio. Come vedremo, con Python la cosa è meno impegnativa di quanto sembri a prima vista.

Il template

Cominciamo dall'inizio: il template.

Come detto si tratta di avere una struttura fissa entro cui calare i dati che possono variare. E questi dati possono essere di qualunque tipo: testo, immagini, tabelle.

Ricordate che usualmente, per scrivere i nostri articoli utilizziamo il reStructuredText? Bene, continuiamo così. Semplicemente, dove vogliamo scrivere qualcosa che cambia nel tempo, mettiamo una parola che comincia con il $.

Perché il $? Questo è il segnale che indica alla libreria Python che gestisce i template, dove fare una sostituzione e con che cosa. Ad esempio, prendiamo un estratto dal template per l'andamento dell'infezione nel mondo [1]:

... <cut> ...
Qui di seguito una sintesi tabellare della situazione per i venti paesi più colpiti
alla data di aggiornamento di questo documento. Sono riportati i casi totali,
i decessi, e il rapporto tra decessi e casi totali

.. csv-table:: situazione dei venti paesi più colpiti al $UPDATED

$DATA_TABLE

... <cut> ...

In questo esempio abbiamo i segnaposto $UPDATED e $DATA_TABLE. Come si intuisce, vogliamo sostituire in $UPDATED la data dell'aggiornamento, e in $DATA_TABLE una tabella con i dati aggiornati, in formato csv.

Come facciamo per effettuare la fusione tra template e dati? Supponiamo di avere:

  • in template_text il contenuto dell'esempio predetto;
  • in adate la data di aggiornamento, come stringa;
  • in data_table le linee csv che vogliamo inserire al posto di $DATA_TABLE;

possiamo procedere così:

from string   import Template

# ...<cut>... code to istantiate template_text, adate, data_table
d = {'UPDATED': adate,
    'DATA_TABLE': data_table,
}
tmpl = Template(template_text)
article = tmpl.safe_substitute(d)
# ...<cut>... code to write article to file

In pratica:

  • inseriamo i nostri dati nel dizionario d;
  • creiamo l'oggetto tmpl di tipo Template utilizzando template_text;
  • all'oggetto tmpl chiediamo di fondere i dati presenti in d.

Ce la siamo cavata con sei righe di codice, salvo gli accessori per preparare i dati all'elenco precedente e per salvare su file il lavoro fatto. Non male.

La tabella di dati in formato csv

Adesso consideriamo il motivo per cui stiamo facendo tutto questo. La tabella che vogliamo inserire al posto di $DATA_TABLE, andrà ricalcolata ogni giorno, al variare dei dati dell'epidemia.

Diciamo che vogliamo inserire una tabella di questo tipo:

situazione dei due paesi più colpiti al 2020-03-26
date cases country
2020-03-26 81968 China
2020-03-26 74386 Italy

si capisce che i dati nella tabella il 27 Marzo saranno diversi da quelli qui riportati, calcolati con i dati del 26 Marzo.

Ci servono:

  • una sorgente da cui scaricare i dati aggiornati, e
  • uno strumento che ci permetta facilmente di caricare, elaborare e rappresentare i dati in questione.

La sorgente da cui scaricare i dati aggiornati andrà individuata in ragione di cosa ci interessa. In questo caso, facciamo riferimento a questa URL dell'agenzia European Centre for Disease Prevention and Control.

Supponiamo di avere la URL predetta registrata nella variabile url e di voler salvare i dati in un file di nome memorizzato nella variabile fname. Usando la libreria standard urllib, per scaricare e salvare il file possiamo procedere così [2]:

import urllib.request

# ...<cut>... code to istantiate variables url and fname
with urllib.request.urlopen(url) as response:
   from_web = response.read()
with open(fname, 'wb') as f:
    f.write(from_web)

Salvato il file dati da Internet sul nostro computer, ora utilizziamo Pandas per importare i dati in forma tabellare e cominciare a elaborarli.

Importare i dati non è complesso. Se fname è il nome del file che contiene i dati:

import pandas as pd

# ...<cut>... code to istantiate variable fname
df = pd.read_csv(fname)

importa i dati nella variabile df.

df è l'acronimo di DataFrame. La struttura dati tabellare su cui tipicamente si lavora utilizzando Pandas.

In questo articolo non abbiamo intenzione di esplorare le possibilità di Pandas. Non lo conosciamo così bene, e sono veramente tante. Ci accontentiamo di capire rapidamente come passare da un DataFrame proveniente dal file csv, e strutturato così [3]:

date        ...  cases  ...                   country  id
2020-03-18  ...     33  ...                     China  CN
2020-03-17  ...    110  ...                     China  CN
2020-03-16  ...     25  ...                     China  CN
       ...  ...    ...  ...                       ...  ..
2020-01-01  ...      0  ...  United_States_of_America  US
2019-12-31  ...      0  ...  United_States_of_America  US

dove ogni singola riga riporta, tra gli altri, i nuovi casi positivi osservati nella giornata per la nazione citata, ad un DataFrame strutturato così:

date         cases   country
2020-03-25   81847     China
2020-03-25   69176     Italy
...

dove ogni riga riporta il totale dei casi positivi per la nazione e la data dell'ultima osservazione ricevuta.

In pratica possiamo eseguire questo codice:

import pandas as pd

# ...<cut>... code to istantiate variable df
l = []           # will be: [[date, sum, country], [date, sum, country], ...]
countries = df['country'].drop_duplicates()                 # get a copy of all countries
for country in countries:
    country_df = df.loc[df['country']==country].copy()      # get a copy of all records of a country
    the_cases = country_df['cases'].sum()                   # total of cases
    the_date = country_df['date'].max()                     # the greatest date for this country
    l.append((the_date, the_cases, country[:]))
df2 = pd.DataFrame(l, columns=['date', 'cases','country'])  # create a new dataframe with these values

Con otto righe di codice otteniamo il dataframe che ci interessa. E con il seguente lo assegnamo alla variabile data_table per portarlo nel template:

import pandas as pd

# ...<cut>... code to istantiate variable df
# a single big string with every line starting from 1st column on left side and ending with \n
data_table = df2.to_csv(index=False)
# we need a couple of spaces from 1st column on left side for every line; so:
lines = data_table.split('\n')              # splitting in lines
lines = ['  '+line for line in lines]       # two spaces on left side
data_table = '\n'.join(lines)               # rejoining lines

Qui ci siamo dovuti allargare: più quattro righe per aggiungere quei benedetti spazi bianchi a sinistra, che ci servono ad indentare il contenuto della tabella.

Conclusione

A questo punto abbiamo tutti i mattoni che ci servono per costruire il nostro sistema di aggiornamento automatico dei dati inseriti nell'articolo.

Se siete interessati a leggere tutto il codice del progetto, stay tuned: devo aggiungere un pò di commenti e poi lo publicherò open source su Github. Quando lo farò aggiornerò questo articolo con il relativo link.

Enjoy. ldfa


[1]

Per chi non conosce reStructuredText:

  • .. csv-table:: titolo è la direttiva che indica alla libreria reStructuredText di interpretare quel che segue come una tabella in formato csv.

Attenzione al fatto che il contenuto della tabella deve essere:

  • distanziato dalla direttiva con una riga vuota;
  • indentato rispetto la direttiva, ad esempio con un paio di spazio bianchi prima di ogni riga che forma il contenuto.

Invece l'indicatore ... <cut> ... nell'esempio vuole rimarcare il fatto che abbiamo omesso parte del template. Parte non significativa per il nostro esempio.

[2]Lo ammetto, quando leggo da programma qualcosa da Internet, rimango stupefatto dalla facilità con cui è possibile farlo quando si utilizza Python.
[3]Questa è una serie temporale: ogni nazione ha un gruppo di date, e per ogni data è riportato il numero di osservazioni nella giornata.