Archivi tag: data visualization

Grafico a linee

Partiamo sempre dall’esempio su cui stiamo lavorando da un po’ di tempo a questa parte e creiamo un grafico a linee in Processing.

L’unica modifica al codice che dobbiamo fare è quella relativa alla grafica: lasciamo, dunque, inalterata la prima parte, quella relativa alla lettura dei dati dal file .csv e l’inserimento degli stessi in un array e passiamo direttamente alla grafica.

Ripensare la posizione della x

La prima differenza rispetto al grafico a barre sarà determinare correttamente la posizione x del valore estratto dall’array. Se, infatti, il ciascuna barra del grafico aveva una sua larghezza, ora dobbiamo rappresentare solo un punto.

Per risolvere il problema, pur mantenendo il risultato finale che avevamo realizzato nell’ultimo post, creo una nuova variabile chiamata x1 che mi servirà come nuovo riferimento e aggiungo un nuovo ciclo for solo per disegnare i punti di riferimento nel grafico.


int x1 = 68;
  
for(int i = 0; i < tempMin.length; i++) {
  ellipse(x1, height - 50 + tempMin[i] * - 20, 10, 10);
  ellipse(x1, height - 50 + tempMax[i] * - 20, 10, 10);
  x1 += 40;
}

Ecco il risultato:

Grafico a linee in Processing

Creiamo il grafico a linee

Creare un grafico a linee potrebbe essere più complesso di quanto ipotizzato: dovremmo, infatti, creare manualmente una linea per congiungere tra loro i punti disegnati in precedenza.

Per fortuna ci vengono in aiuto alcune funzione di Processing: beginShape()vertex() ed endShape().

Con beginShape() diciamo al programma di iniziare a creare una forma, aggiungiamo tutti i vertici che la compongono con vertex() e, una volta richiamata la funzione endShape(), Processing si preoccuperà di collegare insieme tutti i punti.

Come al solito, consiglio di verificare sul reference i parametri opzionali che possiamo usare per ciascuna funzione. Per questo esempio il comportamento di default è quello che fa al caso nostro ma in futuro potrebbero tornarvi utili anche le altre possibilità.

Dovendo creare due grafici a barre, dovremo utilizzare due cicli for:


int x1 = 68;

beginShape();
for (int i = 0; i < tempMin.length; i++) {
  vertex(x1, height - 50 + tempMin[i] * - 20);
  ellipse(x1, height - 50 + tempMin[i] * - 20, 10, 10);
  x1 += 40;
}
endShape();

x1 = 68;
beginShape();
for (int i = 0; i < tempMax.length; i++) {
  vertex(x1, height - 50 + tempMax[i] * - 20);
  ellipse(x1, height - 50 + tempMax[i] * - 20, 10, 10);
  x1 += 40;
}
endShape();

Grafico a linee beginShape, vertex, endShape

A questo punto possiamo eliminare i grafici a barre che avevamo tenuto come riferimento e andare a sistemare i colori per ottenere un risultato finale soddisfacente:

Grafico a linee finale in Processing

Codice completo

Guardando il codice vi sembrerà un po’ verboso, in particolare per quanto riguarda le assegnazioni dei colori. Facendo qualche esperimento vi renderete conto di quanto sia importante inserire le istruzioni giuste per evitare problemi di visualizzazione.


/*
 * Grafico a linee
 * Federico Pepe, 20.05.2018
 * http://blog.federicopepe.com/processing
 */

Table csv;

float tempMin[], tempMax[];

void setup() {
  size(500, 500);
  background(255);
  noStroke();
  csv = loadTable("data.csv", "header");

  println("Numero righe: " + csv.getRowCount());
  println("Numero colonne: " + csv.getColumnCount());

  tempMin = new float[0];
  tempMax = new float[0];

  for (int i = 1; i < csv.getColumnCount(); i++) {
    tempMin = append(tempMin, csv.getFloat(0, i));
    tempMax = append(tempMax, csv.getFloat(3, i));
  }

  printArray(tempMin);

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

  noLoop();

  // Scritte relative all'anno
  int x = 50;
  int year = 2008;
  
  for (int i = 0; i < tempMin.length; i++) {
    fill(0, 127);
    text(year, x + 2, height - 30);
    x += 40;
    year++;
  }
  
  // Temperature minime
  int x1 = 68;
  beginShape();
  for (int i = 0; i < tempMin.length; i++) {
    fill(100, 190, 255);
    vertex(x1, height - 50 + tempMin[i] * - 20);
    noStroke();
    ellipse(x1, height - 50 + tempMin[i] * - 20, 10, 10);
    x1 += 40;
    stroke(100, 190, 255);
    noFill();
  }
  endShape();
  
  // Temperature massime
  x1 = 68;
  beginShape();
  for (int i = 0; i < tempMax.length; i++) {
    fill(255, 100, 100);
    vertex(x1, height - 50 + tempMax[i] * - 20);
    noStroke();
    ellipse(x1, height - 50 + tempMax[i] * - 20, 10, 10);
    x1 += 40;
    stroke(255, 100, 100);
    noFill();
  }
  endShape();

  // Griglia di riferimento
  textAlign(RIGHT, CENTER);
  for (int j = 0; j <= 20; j++) {
    if (j % 5 == 0) {
      fill(0, 127);
      text(j + "°", 25, height - 52 + (j * - 20));
      stroke(0, 30);
    } else {
      stroke(0, 15);
    }
    line(30, height - 50 + (j * - 20), 470, height - 50 + (j * - 20));
  }
}

void draw() {
}

Grafico a barre II

Nell’articolo precedente abbiamo cominciato a lavorare al nostro primo grafico a barre utilizzando come fonte i dati presenti all’interno di un file CSV.

In questo articolo andremo a completare la visualizzazione inserendo anche le temperature massime e dei riferimenti.

Array delle temperature massime

I dati relativi alle temperature massime sono già presenti dentro al file CSV. Come abbiamo già visto, per comodità inseriamo questi dati in un array.

Dove abbiamo dichiarato le variabili, aggiungiamo:

float tempMin[], tempMax[];

Inizializziamo l’array:

tempMax = new float[0];

Inseriamo i dati utilizzando lo stesso ciclo for per non appesantire troppo il programma

for (int i = 1; i < csv.getColumnCount(); i++) {
  tempMin = append(tempMin, csv.getFloat(0, i));
  tempMax = append(tempMax, csv.getFloat(3, i));
}

Grafico a barre delle temperature massime

Utilizzando sempre il codice che abbiamo già scritto, aggiungiamo nel ciclo for in cui andavamo a disegnare i rettangoli delle temperature minime, quelli relativi alle massime. Per differenziarle a livello visivo utilizzeremo il colore rosso.

fill(255, 100, 100);
rect(x, height - 50, 36, tempMax[i] * - 20);

Siccome i rettangoli rossi saranno più alti rispetto a quelli azzurri, assicuriamoci di disegnarli per primi.

Nel mio codice ho aggiunto anche un noStroke() per eliminare il bordo.

Grafico a barre con temperature massime

Aggiungiamo dei riferimenti

Quando si creano delle visualizzazioni di dati può essere utile dare dei riferimenti a chi sta osservando il nostro lavoro per aiutarli nella comprensione. L’immagine qui sopra presa singolarmente potrebbe rappresentare migliaia di cose differenti.

Aggiungere delle linee di riferimento è questione di un semplice ciclo for:

for (int j = 0; j <= 20; j++) {
  stroke(0, 30);
  line(30, height - 50 + (j * - 20), 470, height - 50 + (j * - 20));
}

Grafico a barre con riferimenti in Processing

Ora non ci resta che aggiungere il testo (aggiungiamo un paio di linee di codice al precedente ciclo for):

textAlign(RIGHT, CENTER);
for (int j = 0; j <= 20; j++) {
  fill(0, 127);
  text(j + "°", 25, height - 52 + (j * - 20));
  stroke(0, 30);
  line(30, height - 50 + (j * - 20), 470, height - 50 + (j * - 20));
}

Grafico a barre in Processing

Per rendere il grafico più leggibile facciamo dei piccoli miglioramenti: scriviamo il valore numerico ogni cinque gradi e schiariamo leggermente le altre linee:

textAlign(RIGHT, CENTER);

for (int j = 0; j <= 20; j++) {
  if (j % 5 == 0) {
    fill(0, 127);
    text(j + "°", 25, height - 52 + (j * - 20));
    stroke(0, 30);
  } else {
    stroke(0, 15);
  }
  line(30, height - 50 + (j * - 20), 470, height - 50 + (j * - 20));
}

Realizzare un grafico a barre in Processing

Non ci resta che aggiungere l'anno di riferimento in fondo al grafico: avendo aggiunto l'opzione header nella lettura del file CSV viene saltata completamente la prima riga che include proprio questo dato. Potremmo riscrivere il codice ma, per questa volta, adotteremo una soluzione più semplice anche se non proprio elegante. Per completezza includo tutto il codice del programma:

/*
 * Grafico a barre, 2
 * Federico Pepe, 29.04.2018
 * http://blog.federicopepe.com/processing
 */

Table csv;

float tempMin[], tempMax[];

void setup() {
  size(500, 500);
  background(255);
  noStroke();
  csv = loadTable("data.csv", "header");

  println("Numero righe: " + csv.getRowCount());
  println("Numero colonne: " + csv.getColumnCount());

  tempMin = new float[0];
  tempMax = new float[0];

  for (int i = 1; i < csv.getColumnCount(); i++) {
    tempMin = append(tempMin, csv.getFloat(0, i));
    tempMax = append(tempMax, csv.getFloat(3, i));
  }

  printArray(tempMin);

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

  noLoop();

  int x = 50;
  int year = 2008;

  for (int i = 0; i < tempMin.length; i++) {
    fill(255, 100, 100);
    rect(x, height - 50, 36, tempMax[i] * - 20);
    fill(100, 190, 255);
    rect(x, height - 50, 36, tempMin[i] * - 20);
    fill(0, 127);
    text(year, x + 2, height - 30);
    x += 40;
    year++;
  }

  textAlign(RIGHT, CENTER);

  for (int j = 0; j <= 20; j++) {
    if (j % 5 == 0) {
      fill(0, 127);
      text(j + "°", 25, height - 52 + (j * - 20));
      stroke(0, 30);
    } else {
      stroke(0, 15);
    }
    line(30, height - 50 + (j * - 20), 470, height - 50 + (j * - 20));
  }
}

void draw() {
}

Bar Graph in Processing

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.

Quick, Draw! + Processing: lavorare con il dataset di Google con più di 50 milioni di disegni

Quick, Draw! in Processing

A fine 2016 Google ha messo on-line Quick, Draw! uno dei suoi esperimenti di intelligenza artificiale e machine learning. Il sito chiede alle persone di disegnare delle forme: un maiale, un tubo per annaffiare il prato, una padella, ecc… e in meno di 20 secondi, attraverso una rete neurale, il computer prova a indovinare cosa è stato disegnato.

Più di 15 milioni di persone hanno partecipato al gioco e, così facendo, Google è riuscita a raccogliere un dataset di più di 50 milioni di disegni che, qualche giorno fa, è stato rilasciato pubblicamente.

Il dataset si compone di 345 categorie e, oltre ai dati vettoriali delle immagini sono inclusi anche una serie di metadati.

L’obiettivo del rilascio di questi dati è:

We’re sharing them here for developers, researchers, and artists to explore, study, and learn from.

Nel giro di poche ore sul mio feed twitter sono comparsi i primi esperimenti (i più interessanti sono quelli di @frauzufall che trovate qui) la maggior parte dei quali, però, generati con OpenFrameworks.

Non trovando nessuno che stesse utilizzando Processing, ho deciso di scrivere un piccolo programma in questo linguaggio per dare la possibilità a tutti di utilizzare questo dataset

Struttura dei dati

I file che si scaricano dal repository rilasciato da Google sono file di tipo .ndjson ovvero file con un oggetto di tipo JSON per ogni riga di file.

Ciascun oggetto è composto dai seguenti dati:

  • key_id (integer): numero univoco che identifica il disegno
  • word (string): la categoria che era stata richiesta
  • recognized (boolean): se il disegno era stato riconosciuto (true) oppure no (false)
  • timestamp (datetime): quando il disegno è stato creato
  • countrycode (string): due lettere per identificare la nazione del giocatore
  • drawing (string): un array JSON contenente i dati vettoriali del disegno
{ 
    "key_id":"5891796615823360",
    "word":"nose",
    "countrycode":"AE",
    "timestamp":"2017-03-01 20:41:36.70725 UTC",
    "recognized":true,
    "drawing":[[[129,128,129,129,130,130,131,132,132,133,133,133,133,...]]]
  }

Per semplificarmi la vita, mi sono concentrato solo sull’ultimo tipo di dato: il mio obiettivo era caricare i dati del dataset e ridisegnare le forme con Processing.

L’array contenente i dati del disegno è a sua volta strutturato in questo modo:

[ 
  [  // First stroke 
    [x0, x1, x2, x3, ...],
    [y0, y1, y2, y3, ...],
    [t0, t1, t2, t3, ...]
  ],
  [  // Second stroke
    [x0, x1, x2, x3, ...],
    [y0, y1, y2, y3, ...],
    [t0, t1, t2, t3, ...]
  ],
  ... // Additional strokes
]

Per fortuna la documentazione di Google è molto chiara: x e y sono le coordinate e t è il tempo trascorso in millisecondi dal primo punto.

Dati preprocessati

I dati contenuti nel dataset sono molti e, infatti, ciascun file pesa diverse centinaia di megabyte. Per fortuna il team di Google ha messo a disposizione anche dei file preprocessati: sono state rimosse le informazioni temporali e tutti i dati sono stati allineati e ridimensionali in un quadrato di 256×256 pixel.

I file .ndjson prepocessati sono disponibili a questo link e, come potete notare, la dimensione è notevolmente ridotta anche se si tratta, pur sempre, di almeno 40-50 megabyte per file.

Importare i dati in Processing

Primo problema: in Processing non esistono funzioni che ci permettono di lavorare su oggetti di tipo JSON differenti a meno che non siano inclusi in un array. All’inizio avevo, dunque, optato per modificare il file dei dati ma, grazie al suggerimento di un utente su Facebook, ho scoperto l’esistenza dell’oggetto BufferedReader che si usa per leggere un file una riga alla volta.

Una volta risolto questo inghippo, tutto è diventato più semplice: le linee restituite dall’oggetto sono di tipo String. Con la funzione parseJSONObject() ho convertito la stringa in un oggetto JSON.

Quick, Draw!

Quick, Draw: il dataset di Google in Processing
Vorrei conoscere chi ha disegnato questa “incudine”

Avendo accesso all’oggetto JSON, l’unico passaggio da fare era estrarre dall’array drawing i dati x e y e assegnarli ai vertici di una forma per poterla disegnare a schermo.

Per farli ho usato un’insieme di funzioni di cui parleremo in futuro.

Per i più curiosi, il codice è disponibile su questo repository su GitHub.