5.6. Exemple pràctic de tractament de dades
Introducció
Representar dades amb gràfics és tot un art. No és un tema fàcil, de fet, el Grau lI dedica una assignatura sencera, però vegem un petit exemple de com, amb p5.js, podem fer gràfics a partir de dades que recuperarem d’un arxiu extern.
En aquest exemple treballarem el capítol 12 «Data» del llibre.
Per a fer aquest exemple agafarem les dades d’un repositori públic, en aquest cas de l’Ajuntament de Madrid. Hem escollit les dades d’accidents de tràfic on hi ha implicades bicicletes. Aquestes dades estan disponibles en aquest enllaç:
Descarreguem l’arxiu de l’any que vulguem, però la versió CSV. Per a preparar aquest exemple hem agafat les dades de 2018, però per la data en què hem descarregat les dades (finals de desembre de 2018) només hi ha dades fins al novembre.
Per què cal agafar l’arxiu csv? Doncs perquè és el tipus d’arxiu que p5.js pot obrir per a recuperar les dades (pàgina 182 del llibre).
Problema
Aquest arxiu, tot i que és csv i els programes de full de càlcul l’obriran sense problemes, no fa servir com a separador cap de les dues opcions que ens permet loadTable() que són comes o tabuladors. Aquest csv fa servir com a separadors punts i comes, que és el format que es fa servir allà on fem servir la coma com a separador de decimals (més explicacions a la Viquipèdia: CSV). Com que els punts i coma no estan suportats per loadTable(), el primer que toca fer és substituir tots els punts i coma per comes. Això ho podem fer fàcilment amb el nostre editor habitual. Compte! Si tenim nombres amb decimals, la millor opció serà canviar els punt i comes per tabuladors. A més, caldrà tenir en compte el fet que p5.js fa servir el punt com a separador de decimals (però això es podrà arreglar un cop recuperades les dades).
Bé, ja tenim l’arxiu csv com li agrada a loadTable(). El podeu descarregar aquí: http://multimedia.uoc.edu/carlos/marques/Dades/datos/AccidentesBicicletas_2018.csv.
Per a poder treballar amb les dades, primer hem de saber quines dades tenim i, sobretot, com estan organitzades! Descarregueu-vos l’arxiu csv de la web de l’Ajuntament i obriu-lo amb el vostre programa de full de càlcul preferit. Se’ns obrirà un arxiu amb aquest aspecte:
Veiem que tenim una primera fila d’encapçalament. Això és important perquè aquesta informació també l’haurem de fer saber a la funció loadTable().
Després de l’encapçalament cada fila és un accident. I de cada accident tenim un munt de dades: data, hora aproximada, dia de la setmana, districte, etc. A la pràctica, per a l’exercici que farem, de totes les dades només farem servir la columna «DIA SEMANA». I una sola dada ja ens dona per fer una visualització!
El pas següent ja és fer el programa que mostri les gràfiques representatives d’aquestes dades. Per a simplificar-ho, farem un gràfic de barres on les barres representaran el nombre d’accidents amb bicicletes implicades, per dia de la setmana. El resultat amb aquestes dades serà aquest:
És molt senzill però el que ens interessa és veure com recuperem i treballem les dades.
Primer pas, recuperar les dades.
var bicis; function preload() { bicis = loadTable("datos/AccidentesBicicletas_2018.csv", "header"); }
Per a recuperar les dades, necessitem:
- Crear una variable que farem servir per a guardar les dades. Més endavant veurem com treballem amb aquesta variable.
- Crear una funció preload() que aturarà el nostre programa fins que no tinguem carregades totes les dades (millor no intentar fer res sense tenir totes les dades).
- Dins de la funció preload() cridem a la funció loadTable(). Li indiquem que, com hem vist, la taula té una capçalera.
Igual que ens passava quan carregàvem les imatges, aquests programes no els podrem executar en local, caldrà que estiguin a un servidor.
El pas següent serà treballar les dades. Com? Doncs comptarem, per a cada dia de la setmana, quants accidents hi ha hagut. Vegem el programa com va (creix força però no us espanteu, no és gens complicat). De moment, encara no posem la funció draw() perquè només estem recuperant i preparant les dades.
var bicis; var dia; var diasSemana = []; var maximo = 0; function preload() { bicis = loadTable("datos/AccidentesBicicletas_2018.csv", "header"); } function setup() { createCanvas(480, 240); noStroke(); preparacion(); } function preparacion(){ for (var i = 0; i < 7; i++) { diasSemana[i]=0; } for (var i = 0; i < bicis.getRowCount(); i++){ dia = bicis.getString(i,"DIA SEMANA"); switch (dia) { case "LUNES": diasSemana[0]++; break; case "MARTES": diasSemana[1]++; break; case "MIERCOLES": diasSemana[2]++; break; case "JUEVES": diasSemana[3]++; break; case "VIERNES": diasSemana[4]++; break; case "SABADO": diasSemana[5]++; break; case "DOMINGO": diasSemana[6]++; break; } } for (var i = 0; i < 7; i++) { if (maximo < diasSemana[i]) { maximo = diasSemana[i]; } } }
Com veiem, la gràcia està a la funció preparacion() que agafa les dades que hem carregat i calcula quants accidents hi ha hagut cada dia de la setmana.
Anem a poc a poc. Primer de tot, necessitem un array per a guardar el nombre d’accidents de cada dia de la setmana. A aquest array li hem dit diasSemana. També hem declarat una variable maximo on guardarem el valor més gran (el nombre d’accidents del dia que hi ha hagut més accidents). Ho farem servir a l’hora de dibuixar la gràfica al canvas.
Vegem què fa la funció preparacion(). El primer que fa és això:
for (var i = 0; i < 7; i++) { diasSemana[i]=0; }
Posa les 7 posicions de l’array que farem servir (del 0 al 6) a 0.
I és molt important! Si no ho fem, el nostre programa no funcionarà.
El pas següent és revisar totes les dades que hi ha a l’arxiu csv. Per a això, farem servir una instrucció for d’aquesta manera:
for (var i = 0; i < bicis.getRowCount(); i++){
Utilitzem una funció bicis.getRowCount() que ens diu quantes dades podem trobar a la variable bicis.
El pas següent és extreure, de cada fila, el dia de la setmana:
dia = bicis.getString(i,"DIA SEMANA");
Repassem una mica. El for ens ajudarà a recórrer, una a una, totes les fileres de l’arxiu. Per a cada accident (recordeu, un per fila) necessitem saber el dia de la setmana en què ha passat. La funció bicis.getString(i,”DIA SEMANA”) agafa, de la variable bicis (que recordeu és on hem guardat totes les dades de l’arxiu), la dada que hi ha a la fila i, columna DIA SEMANA.
El pas següent és sumar 1 al comptador d’aquest dia de la setmana. Això ho fem amb la instrucció següent, el switch:
switch (dia) { case "LUNES": diasSemana[0]++; break; case "MARTES": diasSemana[1]++; break; case "MIERCOLES": diasSemana[2]++; break; case "JUEVES": diasSemana[3]++; break; case "VIERNES": diasSemana[4]++; break; case "SABADO": diasSemana[5]++; break; case "DOMINGO": diasSemana[6]++; break; }
Així, si el dia és «LUNES» sumen 1 a diasSemana[0]. I si és diumenge li sumarem 1 a diasSemana[6].
Finalment, calculem quin és el nombre més gran d’accidents que hi ha hagut en un dia determinat de la setmana (com hem dit, farem servir aquesta dada per a dibuixar millor la gràfica).
for (var i = 0; i < 7; i++) { if (maximo < diasSemana[i]) { maximo = diasSemana[i]; } }
Ara ja tenim les dades que volem representar. Només cal representar-les. I dibuixar-ho és cosa de la funció draw().
function draw() { background(100); fill(255); for (var i = 0; i < 7; i++) { rect(40+(i*60), 210-map(diasSemana[i],0,maximo,0,200), 40, map(diasSemana[i],0,maximo,0,200)); } }
El que fem és, per a cada dia de la setmana, dibuixar un rectangle. El for ens serveix per a pintar els 7 dies. I el dibuix es fa amb una funció rect().
Dibuixar les barres és una mica complicat, així que anirem veient tots els paràmetres de la funció rect() un a un. Primer, però, recordem com és la funció rect():
rect(x, y, w, h) x Número: coordenada x del rectángulo. y Número: coordenada y del rectángulo. w Número: ancho del rectángulo. h Número: altura del rectángulo.
Recordem que comencem a pintar els rectangles a partir del seu vèrtex superior esquerre. Bé, veiem ara els diferents paràmetres. L’x:
40+(i*60)
El que estem fent, és que la primera barra la posarem a 40 píxels de l’esquerra del canvas. La segona 60 píxels més enllà i, així, successivament. Com que les barres les farem de 40 píxels, tindrem una separació de 20 píxels entre barra i barra.
Ara l’y.
210-map(diasSemana[i],0,maximo,0,200)
Això és més complicat. Les barres començaran (visualment) als 210 píxels. I tindran una alçada màxima de 200 píxels. Primer el que hem de fer és convertir el nombre d’accidents, que estarà entre 0 i maximo (recordeu d’on ve aquest valor?) a un valor entre 0 i 200. Això ho fem amb la funció map() que ja hem treballat. Amb la funció map() tenim l’alçada que tindrà la barra. Per a decidir a quina posició y cal començar, restem a la posició final (210) l’alçada de la barra. I ja sabem on hem de començar a pintar.
Ara ens falta determinar l’amplada:
40
i l’alçada
map(diasSemana[i],0,maximo,0,200)
que ja hem fet servir abans. El programa queda així:
var bicis; var dia; var diasSemana = []; var maximo = 0; function preload() { bicis = loadTable("datos/AccidentesBicicletas_2018.csv", "header"); } function setup() { createCanvas(480, 240); noStroke(); preparacion(); } function draw() { background(100); fill(255); for (var i = 0; i < 7; i++) { rect(40+(i*60), 210-map(diasSemana[i],0,maximo,0,200), 40, map(diasSemana[i],0,maximo,0,200)); } } function preparacion(){ for (var i = 0; i < 7; i++) { diasSemana[i]=0; } for (var i = 0; i < bicis.getRowCount(); i++){ dia = bicis.getString(i,"DIA SEMANA"); switch (dia) { case "LUNES": diasSemana[0]++; break; case "MARTES": diasSemana[1]++; break; case "MIERCOLES": diasSemana[2]++; break; case "JUEVES": diasSemana[3]++; break; case "VIERNES": diasSemana[4]++; break; case "SABADO": diasSemana[5]++; break; case "DOMINGO": diasSemana[6]++; break; } } for (var i = 0; i < 7; i++) { if (maximo < diasSemana[i]) { maximo = diasSemana[i]; } } }
I el podeu provar en l’adreça http://multimedia.uoc.edu/carlos/marques/Dades/index2.html.
Fins aquí, hem vist com recuperar les dades i després treballar amb aquestes.
Però la veritat és que manca informació. Ens aniria bé, almenys, indicar els dies de la setmana que és cada barra i el nombre real d’accidents.
Ho farem reduint la mida de les barres (a 100 píxels) i, quan el cursor passi per sobre d’una barra, pintant-la de color vermell i escrivint el dia de la setmana i el nombre d’accidents a sobre, com es pot veure en aquesta captura:
Els canvis que hem de fer afecten les funcions setup() i draw(). A més, he afegit una nova variable:
var nombresDias = ["Lunes","Martes","Miércoles","Jueves","Viernes","Sábado","Domingo"]; function setup() { createCanvas(480, 240); noStroke(); preparacion(); textFont("Arial"); textSize(24); } function draw() { background(100); for (var i = 0; i < 7; i++) { fill(255); rect(40+(i*60), 210-map(diasSemana[i],0,maximo,0,100), 40, map(diasSemana[i],0,maximo,0,100)); if ((mouseX > 40+(i*60)) && (mouseX < 40+(i*60)+40)) { fill(255,0,0); rect(40+(i*60), 210-map(diasSemana[i],0,maximo,0,100), 40, map(diasSemana[i],0,maximo,0,100)); fill("#CECEF6"); text(nombresDias[i] + ": "+diasSemana[i]+" accidentes.", 40, 70); } } }
La nova variable és un array on hem guardat els dies de la setmana.
A la funció setup() hem afegit la font i la mida del text que escriurem:
textFont("Arial"); textSize(24);
I a la funció draw() això:
if ((mouseX > 40+(i*60)) && (mouseX < 40+(i*60)+40)) { fill(255,0,0); rect(40+(i*60), 210-map(diasSemana[i],0,maximo,0,100), 40, map(diasSemana[i],0,maximo,0,100)); fill("#CECEF6"); text(nombresDias[i] + ": "+diasSemana[i]+" accidentes.", 40, 70); }
Per a cada barra preguntem si el ratolí està a sobre (per a simplificar-ho, fem com si les barres arribessin fins a dalt de tot). Si és així, tornem a dibuixar la barra però de color vermell (funcions fill() i rect()) i escrivim el text amb la funció text().
text(nombresDias[i] + ": "+diasSemana[i]+" accidentes.", 40, 70);
nombresDias[i] escriurà el nom del dia de la setmana de la barra sobre la qual està el ratolí i diasSemana[i] el nombre d’accidents que hi ha hagut aquest dia de la setmana.
Podeu veure el resultat en l’adreça http://multimedia.uoc.edu/carlos/marques/Dades/.