Archivi categoria: Data

In questa categoria troverai una serie di articoli che illustrano come creare visualizzazioni di  dati e infografiche utilizzando Processing e la programmazione.

Creiamo il nostro primo grafico a barre

Abbiamo imparato come leggere un file CSV in Processing e come cominciare a lavorare sui dati passando da una tabella a un array. Ora è arrivato il momento di creare il nostro primo grafico a barre.

Per fare un velocissimo recap: stiamo lavorando in Processing con un file CSV che rappresenta le statistiche metoclimatiche degli ultimi 10 anni (2008-2017) della regione Veneto, prese dal sito del Mipaaf.

Il file contiene diverse informazioni interessanti ma, per il momento, abbiamo creato un array in cui abbiamo salvato solo i valori delle temperature minime:

[0] 6.9
[1] 7.3
[2] 6.7
[3] 7.4
[4] 7.2
[5] 7.7
[6] 8.7
[7] 8.0
[8] 7.6
[9] 7.1

Il primo valore si riferisce all’anno 2008 mentre quello inserito alla posizione [9] è il dato del 2017.

Disegniamo il grafico a barre

Disegnare un grafico a barre non è difficile: devo creare dei rettangoli della stessa larghezza e la cui altezza sia legata al dato che voglio rappresentare.

Creiamo una finestra di 500×500 pixel e, siccome i valori sono 10 divido, ciascun rettangolo avrà una larghezza pari a 50 pixel.

Utilizzo un ciclo for per leggere dall’array i singoli valori e li assegno direttamente all’altezza dei rettangoli

/*
 * Creiamo il nostro primo grafico a barre
 * Federico Pepe, 22.04.2018
 * http://blog.federicopepe.com/processing
 */

Table csv;

float tempMin[];

void setup() {
  // Dimensione della finestra
  size(500, 500);
  
  csv = loadTable("data.csv", "header");

  println("Numero righe: " + csv.getRowCount());
  println("Numero colonne: " + csv.getColumnCount());
  
  // Creazione dell'array
  tempMin = new float[0];
  // Inserimento dei dati nell'array
  for(int i = 1; i < csv.getColumnCount(); i++) {
    tempMin = append(tempMin, csv.getFloat(0, i));
  }
  printArray(tempMin);
  // Disegno il grafico
  for(int j = 0; j < tempMin.length; j++) {
    rect(j * 50, 0, 50, tempMin[j]);
  }
  
  noLoop();
}

void draw() {
}

La riga di codice più importante è: rect(j * 50, 0, 50, tempMin[j]);. Ricordo che la funzione rect() accetta quattro parametri: posizione x, posizione y, larghezza e altezza del rettangolo. Il codice, quindi, dovrebbe essere chiaro e non dovrebbe necessitare di ulteriori spiegazioni.

Grafico a barre in Processing

Il risultato, come possiamo vedere nell’immagine, non è, però, molto soddisfacente: i grafici a barre generalmente vengono disegnati dal basso verso l’alto e, in questo caso, il valore dell’altezza dei rettangoli è troppo basso per essere comprensibile.

È sufficiente modificare una sola riga di codice:

rect(j * 50, height, 50, tempMin[j] * - 20);

per ottenere un risultato completamente differente:

Bar Chart in Processing

Aggiungiamo un po’ di margine dai bordi della finestra e tra le varie barre del nostro grafico:

// Disegno il grafico
  int x = 50;
  for(int j = 0; j < tempMin.length; j++) {
    rect(x, height - 50, 36, tempMin[j] * - 20);
    x += 40;
  }

e un po’ di colore fill(100, 190, 255);

Visualizzazione di dati in Processing (grafico a barre)

Ecco il codice completo:

/*
 * Creiamo il nostro primo grafico a barre
 * Federico Pepe, 22.04.2018
 * http://blog.federicopepe.com/processing
 */

Table csv;

float tempMin[];

void setup() {
  // Dimensione della finestra
  size(500, 500);
  background(255);
  csv = loadTable("data.csv", "header");

  println("Numero righe: " + csv.getRowCount());
  println("Numero colonne: " + csv.getColumnCount());
  
  // Creazione dell'array
  tempMin = new float[0];
  // Inserimento dei dati nell'array
  for(int i = 1; i < csv.getColumnCount(); i++) {
    tempMin = append(tempMin, csv.getFloat(0, i));
  }
  printArray(tempMin);
  // Disegno il grafico
  int x = 50;
  fill(100, 190, 255);
  for(int j = 0; j < tempMin.length; j++) {
    rect(x, height - 50, 36, tempMin[j] * - 20);
    x += 40;
  }
  
  noLoop();
}

void draw() {
}

Per il momento possiamo fermarci qui. Per chi volesse spingersi un po’ oltre, un buon esercizio potrebbe essere, partendo dal codice qui sopra, aggiungere al grafico anche le temperature massime con dei rettangoli colorati di rosso.

Da una tabella CSV agli array

Una volta capito come leggere con Processing i dati contenuti in un file CSV, il passaggio successivo è rendere questi dati leggibili e modificabili facilmente all’interno del programma convertendoli in variabili e array.

Ripartiamo dal nostro esempio precedente utilizzando sempre lo stesso data set:

/*
 * Leggere file CSV
 * Federico Pepe, 25.03.2018
 * http://blog.federicopepe.com/processing
 */

Table csv;

void setup() {
  csv = loadTable("data.csv", "header");

  println("Numero righe: " + csv.getRowCount());
  println("Numero colonne: " + csv.getColumnCount());
  
  for(int i = 0; i < csv.getRowCount(); i++) {
    println(csv.getFloat(i, 2));
  }
  
  noLoop();
}

void draw() {
}

Prima di procedere, assicuriamoci che il programma funzioni cliccando su run: nella console dovrebbero comparire i dati contenuti nella colonna relativa al 2009.

Utilizzare i metodi corretti per leggere i dati

Un aspetto importante da tenere presente è di utilizzare i metodi corretti per accedere ai dati. Spulciando nel reference di Table noterete che esistono diverse funzioni come .getFloat().getInt().getString().

Se sostituiamo nell’esempio precedente la riga println(csv.getFloat(i, 2)); con println(csv.getString(i, 2)); Processing non genererà nessun errore e continuerà a far girare il nostro programma ma, ora, quei valori sono considerati stringhe (quindi testo) e non più numeri.

Tutti questi metodi accettano due parametri: il primo indica la riga della tabella, il secondo la colonna. Per quest’ultimo possiamo usare sia un numero, partendo, come sempre a contare da 0, oppure una stringa contenente il nome della colonna.

Il codice, può essere sostituito con: println(csv.getFloat(i, "2009"));

Se sostituite sempre la stessa riga con println(csv.getFloat(i, "Descrizione")); la console vi restituirà tutti valori NaN ovvero Not a Number. Come dicevo, il programma continuerà a funzionare ma non in modo corretto.

Dal tabella CSV all’array

Passare tutti i dati in un array può essere molto comodo per utilizzare alcune funzioni specifiche di calcolo, come, ad esempio, min()max() che restituiscono, rispettivamente, il valore minimo e massimo di un array.

Anche se in questo momento siamo ancora lontani dall’idea di creare una visualizzazione di dati, dovremmo comunque cominciare a pensare a come utilizzeremo questi numeri.

Per come è stato strutturato il file CSV, ciascuna colonna rappresenta un anno con valori di vario tipo: temperatura minima, temperatura massima, eccetera; ma se noi volessimo rappresentare la variazione di uno stesso valore nel tempo dovremmo lavorare orizzontalmente e non verticalmente.

Sfruttiamo questo esempio per capire come passare i dati dal CSV a un array:

Creiamo un array di tipo float chiamato tempMin nel quale inseriremo tutti i valori di temperatura minima e modifichiamo il nostro ciclo for per girare non più sul numero di righe ma su quello delle colonne. Impostiamo l’inizio del ciclo for a 1 per saltare la prima colonna.

/*
 * Da una tabella CSV agli array
 * Federico Pepe, 01.04.2018
 * http://blog.federicopepe.com/processing
 */

Table csv;

float tempMin[];

void setup() {
  csv = loadTable("data.csv", "header");

  println("Numero righe: " + csv.getRowCount());
  println("Numero colonne: " + csv.getColumnCount());
  
  tempMin = new float[csv.getColumnCount()];
    
  for(int i = 1; i < csv.getColumnCount(); i++) {
    tempMin[i] = csv.getFloat(0, i);
  }
  
  printArray(tempMin);
  
  noLoop();
}

void draw() {
}

La dimensione dell’array è uguale al numero di colonne all’interno del file tempMin = new float[csv.getColumnCount()]; e inseriamo all’interno dell’array i valori float provenienti dalla riga 0, perché stiamo ignorando l’header, e di ciascuna colonna: tempMin[i] = csv.getFloat(0, i);

Dal risultato in console notiamo subito un problema: il primo valore dell’array è 0.0 perché, effettivamente, l’array contiene un valore in più, quello della colonna Descrizione.

Abbiamo due possibilità per risolvere il problema:

Modificare la grandezza dell’array sottraendo 1: tempMin = new float[csv.getColumnCount()-1]; e modificando l’inserimento dei valori nell’array sempre spostando l’indice indietro di 1 tempMin[i-1] = csv.getFloat(0, i);.

Questa soluzione funziona ma non è molto elegante, meglio cambiare il codice come segue: inizializziamo l’array con grandezza pari a 0: tempMin = new float[0]; e poi utilizziamo la funzione append() che espande l’array di un elemento e aggiunge il dato nella nuova posizione tempMin = append(tempMin, csv.getFloat(0, i));

Il codice completo

/*
 * Da una tabella CSV agli array
 * Federico Pepe, 01.04.2018
 * http://blog.federicopepe.com/processing
 */

Table csv;

float tempMin[];

void setup() {
  csv = loadTable("data.csv", "header");

  println("Numero righe: " + csv.getRowCount());
  println("Numero colonne: " + csv.getColumnCount());
  
  tempMin = new float[0];
    
  for(int i = 1; i < csv.getColumnCount(); i++) {
    tempMin = append(tempMin, csv.getFloat(0, i));
  }
  
  printArray(tempMin);
  
  noLoop();
}

void draw() {
}

Ora l’array è corretto e contiene esattamente tutti i valori previsti. Come dicevo, ora possiamo sfruttare l’array per ottenere il valore minimo e quello massimo molto semplicemente:

println("Il valore minimo è: " + min(tempMin));
println("Il valore massimo è: " + max(tempMin));

Leggere file CSV

Cominciamo il nostro percorso per imparare a lavorare con i dati: in questo post vedremo insieme come utilizzare i file di tipo CSV in Processing.

Per chi non conoscesse questo tipo di file o ci li avesse mai usati si tratta, in breve, di file di testo in cui i valori sono separati da virgole. CSV, infatti, sta per comma separated values. Nella maggior parte dei casi questi file vengono esportati da Microsoft Excel, uno dei programmi più diffusi (e odiati) per gestire tabelle di dati.

Per dare la possibilità a chiunque mi segua sul blog di seguire gli esercizi, utilizzerò Google Sheet, alternativa gratuita e accessibile via browser del blasonato programma di Microsoft.

Ecco, quindi, il nostro primo set di dati: le statistiche metoclimatiche degli ultimi 10 anni (2008-2017) della regione Veneto, prese dal sito del Mipaaf e portate su Google Sheet.

Scarica i dati

Per esportare il file come CSV cliccate su File > Scarica Come > Valori separati da virgola (.csv, foglio corrente). Di seguito le immagini di come si presenta il file prima e dopo l’esportazione:

Dati in Processing: File CSV in Google Sheet
I dati visti in Google Sheet
Dati in Processing: File CSV in Atom
Gli stessi dati, esportati in CSV

Dati in un file CSV: semplice testo

Come dicevamo all’inizio, il file CSV esportato non è altro che un file di testo contenente dei valori. Per cominciare a leggerne il contenuto è sufficiente utilizzare la funzione loadStrings(): tale funzione accetta in input un file di testo e restituisce un array di stringhe.

Prima di procedere, consiglio di rinominare il file scaricato in data.csv. Non dimenticate di trascinarlo all’interno della finestra di Processing per aggiungerlo al nostro sketch.


/*
 * Leggere file CSV
 * Federico Pepe, 25.03.2018
 * http://blog.federicopepe.com/processing
 */

String[] csv;

void setup() {
  csv = loadStrings("data.csv");
  printArray(csv);
  noLoop();
}

void draw() {
}

Con queste poche righe di codice nella console ogni riga del file viene mostrata come un nuovo elemento dell’array di stringhe.

[0] "Descrizione,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017"
[1] "Temp. minima (°C),6.9,7.3,6.7,7.4,7.2,7.7,8.7,8,7.6,7.1"

Fino a qui niente di difficile ma a noi interessa accedere ai singoli valori presenti in ciascuna colonna. A questo punto ci torna utile riprendere gli array bidimensionali: un sistema che ci permette di rappresentare facilmente una struttura formata da righe e colonne, proprio come un file excel/csv.

Modifichiamo, dunque, il nostro codice come segue:

/*
 * Leggere file CSV
 * Federico Pepe, 25.03.2018
 * http://blog.federicopepe.com/processing
 */

String[] csv;
String[][] dati;

void setup() {
  csv = loadStrings("data.csv");
  dati = new String[csv.length][10]; 
  
  for(int i = 0; i < csv.length; i++) {
    dati[i] = csv[i].split(",");
    printArray(dati[i]);
  }
  
  noLoop();
}

void draw() {
}

Abbiamo aggiunto un array bidimensionale chiamato dati la cui dimensione è determinata dal numero di righe [csv.length] e dal numero di colonne meno uno perché si conta sempre da zero [10].

Con un semplice ciclo for accediamo a tutte le righe del file e, utilizzando la funzione .split(",") separiamo tutti i valori che sono separati dalla virgola.

Affinché sia tutto il più chiaro possibile possiamo fare un po’ di esperimenti con println inserendo nel primo valore dell’array il numero della riga e nel secondo quello della colonna.

println(dati[3][2]); restituisce il valore 0.7 che corrisponde, infatti, alla cella nella quarta riga “Scarto dal clima” e nella terza colonna “2009”.

Leggere file CSV in modo più semplice: Table

Siamo riusciti nel nostro intento ma credo sia ovvio che il metodo che abbiamo usato non sia il più congeniale.

Per nostra fortuna i creatori di Processing avevano già pensato a questa evenienza e hanno creato un oggetto specifico chiamato Table che, come è facile intuire dal nome, rappresenta già una tabella completa di righe e colonne.

Grazie ai numerosi metodi disponibili per gli oggetti di tipo Table è possibile lavorare con i dati in modo semplice e intuitivo.

Aggiorniamo il nostro codice:


/*
 * Leggere file CSV
 * Federico Pepe, 25.03.2018
 * http://blog.federicopepe.com/processing
 */

Table csv;

void setup() {
  csv = loadTable("data.csv", "header");

  println("Numero righe: " + csv.getRowCount());
  println("Numero colonne: " + csv.getColumnCount());
  
  for(int i = 0; i < csv.getRowCount(); i++) {
    println(csv.getFloat(i, 2));
  }
  
  noLoop();
}

void draw() {
}

Abbiamo sostituito i due array di stringhe con un oggetto di tipo Table chiamato csv. Attraverso la funzione loadTable() carichiamo i dati all’interno della variabile. Passando il parametro “header” stiamo dicendo a Processing di ignorare la prima riga del file che contiene l’intestazione.

Attraverso le funzioni .getRowCount() .getColumnCount() accediamo al numero di righe e colonne del file e, infine, con il nostro ciclo for stampiamo in console i valori di tipo float contenuti nella terza colonna (quindi quelli relativi al 2009).

Conclusione

In questo post abbiamo messo molta carne al fuoco e abbiamo cominciato ad addentrarci nel mondo dei dati e dei file CSV. Assicuratevi di aver compreso bene tutte le funzioni e gli esempi inseriti in questo post prima di proseguire con la lettura del prossimo.

Lavorare con i dati

Nel mondo digitale tutto è un insieme di dati: che si tratti di un testo, una foto oppure un video, stiamo parlando di una sequenza di 1 e 0 che chiamiamo comunemente file.

Con questo post inauguro una nuovo capitolo su questo blog in cui scopriremo insieme come utilizzare Processing per lavorare con file e dati.

Quale potrebbe essere il vantaggio di utilizzare un linguaggio di programmazione per questo genere di attività? Perché non possiamo utilizzare strumenti per grafici o designer come Photoshop o Illustrator? La risposta è semplice: uno dei vantaggi dei computer rispetto alla mente umana è che sono in grado di processare velocemente numeri ed eseguire operazioni ripetitive e complesse molto velocemente.

La vista di un file di Excel pieno di righe e colonne può scatenare il panico nelle persone sane di mente ma per un software non servono altro che due cicli for per accedere al contenuto di ciascuna cella. Quello che impareremo a fare sarà, poi, assegnare quel contenuto a una (o più) variabili e il gioco è fatto.

Pensiamo alle visualizzazioni di dati che negli ultimi anni sono diventate onnipresenti su siti web, riviste e quotidiani. Il loro successo è dovuto al fatto che, grazie a un linguaggio di tipo visuale, rendono più accessibile il contenuto della storia, anche quando include una grande mole di dati. A differenza della carta stampata realizzando un’infografica con Processing si apre un ventaglio di possibilità: possiamo renderla interattiva, in 3D, può essere su uno schermo oppure proiettata su una parete.

Tipi di dato

Quali saranno i tipi di dati su cui lavoreremo? Abbiamo già avuto modo di parlare di immagini e pixel (tema che approfondiremo), ma in questa sezione ci concentreremo su:

  • File di testo (.txt)
  • Tabelle di dati di Excel (.csv, .tsv)
  • Pagine web (.html)
  • Feed (.xml)
  • API (.json)