5. Reto 5. Sistemas

5.6. Ejemplo práctico de tratamiento de datos

Introducción

Representar datos con gráficos es todo un arte. No es un tema fácil, de hecho, al grado se le dedica una asignatura entera, pero vamos a ver un pequeño ejemplo de cómo, con p5.js, podemos hacer gráficos a partir de unos datos que recuperaremos de un archivo externo.

En este ejemplo trabajaremos el capítulo 12 «Data», del libro.

Para hacer este ejemplo, cogeremos los datos de un repositorio público, en este caso del Ayuntamiento de Madrid. Hemos escogido los datos de accidentes de tráfico donde hay implicadas bicicletas. Estos datos están disponibles en este enlace:

https://datos.madrid.es/portal/site/egob/menuitem.c05c1f754a33a9fbe4b2e4b284f1a5a0/?vgnextoid=20f4a87ebb65b510VgnVCM1000001d4a900aRCRD&vgnextchannel=374512b9ace9f310VgnVCM100000171f5a0aRCRD&vgnextfmt=default

Descargamos el archivo del año que queramos, pero la versión CSV. Para preparar este ejemplo se han cogido los datos de 2018, pero por la fecha en que hemos descargado los datos (finales de diciembre de 2018) solo están los datos hasta noviembre.

¿Por qué cogemos el archivo csv? Pues porque es el tipo de archivo que p5.js puede abrir para recuperar los datos (página 182 del libro).

Bien, ya tenemos el archivo csv como le gusta a loadTable(). Lo podéis descargar de aquí: http://multimedia.uoc.edu/carlos/marques/Dades/datos/AccidentesBicicletas_2018.csv.

Para poder trabajar con los datos, primero tenemos que saber qué datos tenemos y, sobre todo, ¡cómo están organizados! Descargaos el archivo csv de la web del ayuntamiento y abridlo con vuestro programa de hoja de cálculo preferida. Se nos abrirá un archivo con este aspecto:

Vemos que tenemos una primera fila de encabezamiento. Esto es importante porque esta información también la tendremos que llamar en la función loadTable().

Después del encabezamiento cada fila es un accidente. Y de cada accidente tenemos un montón de datos: fecha, hora aproximada, día de la semana, distrito, etc. En la práctica, por el ejercicio que haremos, de todos los datos solo usaremos la columna «DÍA SEMANA». ¡Y un solo dato ya nos da para hacer una visualización!

El siguiente paso ya es hacer el programa que muestre las gráficas representativas de estos datos. Para simplificar, haremos un gráfico de barras donde las barras representarán el número de accidentes con bicicletas implicadas, por día de la semana. El resultado con estos datos será este:

Es muy sencillo, pero lo que nos interesa es ver cómo recuperamos y trabajamos los datos.

Primer paso, recuperar los datos.

var bicis;

function preload() {
 bicis = loadTable("datos/AccidentesBicicletas_2018.csv", "header");
}

Para recuperar los datos necesitamos:

  • Crear una variable que usaremos para guardar los datos. Más adelante veremos cómo trabajamos con esta variable.
  • Crear una función preload() que parará nuestro programa hasta que no tengamos cargados todos los datos (mejor no intentar hacer nada sin tener todos los datos).
  • Dentro de la función preload() llamamos a la función loadTable(). Le indicamos que, como hemos visto, la tabla tiene cabecera.

Igual que nos pasaba al cargar las imágenes, estos programas no los podremos ejecutar en local, hará falta que estén en un servidor.

El siguiente paso será trabajar los datos. ¿Cómo? Pues contaremos, para cada día de la semana, cuántos accidentes ha habido. Vemos el programa cómo va (crece bastante, pero no os asustéis, no es nada complicado). De momento no ponemos todavía la función draw() porque solo estamos recuperando y preparando los datos.

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];
  }
 }
}

Como vemos, la gracia está en la función preparacion() que coge los datos que hemos cargado y calcula cuántos accidentes ha habido cada día de la semana.

Vamos poco a poco. Antes que nada, necesitamos un array para guardar el número de accidentes de cada día de la semana. A este array lo hemos llamado diasSemana. También hemos declarado una variable maximo donde guardaremos el valor más grande (el número de accidentes del día que ha habido más accidentes). Lo usaremos a la hora de dibujar la gráfica en el canvas.

Vamos ahora a ver qué hace la función preparacion(). Lo primero que hace es esto:

for (var i = 0; i < 7; i++) {
 diasSemana[i]=0;
}

Pone las 7 posiciones del array que usaremos (del 0 al 6) a 0.

El siguiente paso es revisar todos los datos que hay en el archivo csv. Para lo cual usaremos una instrucción for de este modo:

for (var i = 0; i < bicis.getRowCount(); i++){

Utilizamos una función bicis.getRowCount() que nos dice cuántos datos podemos encontrar en la variable bicis.

El siguiente paso es extraer, de cada fila, el día de la semana:

dia = bicis.getString(i,"DIA SEMANA");

Repasemos un poco. El for nos ayudará a recorrer, una a una, todas las filas del archivo. Para cada accidente (recordad, uno por fila) necesitamos saber el día de la semana en que ha pasado. La función bicis.getString(i,”DIA SEMANA”) coge, de la variable bicis (que, recordad, es donde hemos guardado todos los datos del archivo), el dato que hay en la fila i, columna DIA SEMANA.

El siguiente paso es sumar 1 al contador de este día de la semana. Esto lo hacemos con la instrucción siguiente, 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;
}

Así, si el día es «LUNES» sumamos 1 a diasSemana[0]. Y si es domingo le sumaremos 1 a diasSemana[6].

Al final, se calcula cuál es el número más grande de accidentes que ha habido en un día determinado de la semana (como hemos dicho, usaremos este dato para dibujar mejor la gráfica).

for (var i = 0; i < 7; i++) {
 if (maximo < diasSemana[i]) {
   maximo = diasSemana[i];
 }
}

Ahora ya tenemos los datos que queremos representar. Solo hay que representarlos. Y dibujar es cosa de la función 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));
   }
}

Lo que hacemos es, para cada día de la semana, dibujar un rectángulo. El for nos sirve para pintar los 7 días. Y el dibujo se hace con una función rect().

Dibujar las barras es un poco complicado, así que iremos viendo todos los parámetros de la función rect() uno a uno. Primero, sin embargo, recordemos cómo es la función 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.

Recordemos que empezamos a pintar los rectángulos a partir de su vértice superior izquierdo. Bien, veamos ahora los diferentes parámetros. La x:

40+(i*60)

Lo que estamos haciendo es que la primera barra la pondremos a 40 píxeles de la izquierda del canvas. La segunda 60 píxeles más allá, y así sucesivamente. Como las barras las haremos de 40 píxeles, tendremos una separación de 20 píxeles entre barra y barra.

Ahora la y.

210-map(diasSemana[i],0,maximo,0,200)

Esto es más complicado. Las barras empezarán (visualmente) en los 210 píxeles. Y tendrán una altura máxima de 200 píxeles. Primero, lo que tenemos que hacer es convertir el número de accidentes, que estará entre 0 y maximo (¿recordáis de dónde viene este valor?), en un valor entre 0 y 200. Esto lo hacemos con la función map() que ya habíamos trabajado. Con la función map() tenemos la altura que tendrá la barra. Para decidir en qué posición y hay que empezar, restamos a la posición final (210) la altura de la barra. Y ya sabemos dónde tenemos que empezar a pintar.

Ahora nos falta determinar la anchura:

40

y la altura

map(diasSemana[i],0,maximo,0,200)

Que ya habíamos usado antes. El programa queda así:

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];
  }
 }
}

Y lo podéis probar en esta dirección http://multimedia.uoc.edu/carlos/marques/Dades/index2.html.

Hasta aquí hemos visto cómo recuperar los datos y trabajar después con ellos.

Sin embargo, es verdad que falta información. Nos iría bien al menos indicar los días de la semana que es cada barra y el número real de accidentes.

Lo haremos reduciendo el tamaño de las barras (a 100 píxeles) y, cuando el cursor pase por encima de una barra, pintándola de color rojo y escribiendo el día de la semana y el número de accidentes encima, como se puede ver en esta captura:

Los cambios que tenemos que hacer afectan a las funciones setup() y draw(). Además, he añadido una nueva 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 nueva variable es un array donde hemos guardado los días de la semana.

En la función setup() hemos añadido la fuente y el tamaño del texto que escribiremos:

textFont("Arial");
textSize(24);

Y en la función draw() esto:

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);
  }

Para cada barra preguntamos si el ratón está encima (para simplificar, hacemos como si las barras llegaran hasta arriba de todo). Si es así, volvemos a dibujar la barra pero en color rojo (funciones fill() y rect()) y escribimos el texto con la función text().

text(nombresDias[i] + ": "+diasSemana[i]+" accidentes.", 40, 70);

nombresDias[i] escribirá el nombre del día de la semana de la barra sobre la cual está el ratón y diasSemana[i] el número de accidentes que ha habido este día de la semana.

Podéis ver el resultado en esta dirección http://multimedia.uoc.edu/carlos/marques/Dades/.