UP | HOME |

Diagrama de barras e información geográfica con R

Diagrama de barras e información geográfica con R

¡Qué noche tan dichosa!
Sólo ella conoció el momento
en que Cristo resucitó del abismo. - Pregón Pascual 2021.

He aquí un ejemplo de información espacial y diagramas de barras que transmiten fácilmente la información más relevante de una base de datos geolocalizados.

grmapabarrasmultas.png

Carlos Gil Bellosta acuñó el término huertograma para calificar los gráficos espaciales que confunden al lector. Aquí presentamos un gráfico que creemos muy informativo y sencillo de entender por parte de los lectores.

¿Qué información transmitir? Los datos no informan

Paula Casado actualiza en El Arte del Dato las enseñanzas de Edward Tufte. A ellos remitimos para quien desee aprender las técnicas básicas de cómo transmitir información estadística de forma visual.

Destaco que la labor de un especialista en datos consiste en condensar, en resumir, la información. Y los gráficos, cuanto más sencillos y comprensibles sean, mucho mejor. Desafortunadamente, percibo una marabunta de aplicaciones gráficas que delegan en el usuario todos los datos y éste es quién debe esforzarse por entenderlos.

Por ejemplo, las Shiny Apps, mediante un cuadro de mandos, permiten al usuario conocer los microdatos. En ese tipo de aplicaciones, es el usuario quien extrae las conclusiones. Ha de dedicar tiempo a manipular y extraer lo más relevante de la base de datos. Un ejemplo de este enfoque se muestra en el mapa interactivo de Antonio Díaz Muñiz sobre multas de tráfico en la ciudad de Gijón.

Desde el atrevimiento de mi creciente ignorancia, creo que la labor de un estadístico consiste en analizar los datos y convertirlos en conocimiento, incluyendo la incertidumbre si fuera necesario. Es decir, requiere un estudio previo que parece que se obvia en las visualizaciones actuales.

Inspirado por este gráfico sobre atascos de tráfico, creamos uno similar con R utilizando la base de datos del Ayuntamiento de Gijón y contamo de una forma distinta cómo se multa en esta ciudad. londontrafficjams.jpeg

Grid de R

El ecosistema de gráficos de R parece constreñirse al universo ggplot2. Muchos especialistas en gráficos desconocen que este popular paquete se basa en el sistema grid. Y cuando llega el momento de diseñar gráficos no estándar, el paquete gráfico lattice permite una interacción directa con el sistema grid que con ggplot2 no se consigue -o al menos este enano ignorante no ha descubierto cómo hacerlo de una forma sencilla-.

library(grid) # Grid
library(lattice) # Top level functions for grid objects
suppressPackageStartupMessages(library(data.table))
library(colorspace) # Palette of colors

Preprocesado de los datos

La parte tediosa consiste en preprocesar los datos. De una forma resumida, leemos los datos, observamos las diez calles con más multas y creamos una tabla con el resumen de esas localizaciones. Así dispondremos de dos bases de datos, que darán lugar a dos elementos gráficos, y que enlazaremos mediante segmentos superpuestos.

La primera base contiene los microdatos con las multas y sus ubicaciones geográficas. Su representación formará el contorno urbanístico de la ciudad de Gijón. Para que la escala del mapa sea manejable, eliminamos las ubicaciones muy lejanas.

datos <- fread(file.path("graphics","datawhour.csv"),dec=",")
names(datos) <- tolower(names(datos))
## Hour. Convert hour POSIXct and numeric variables.
datos$hora <- as.POSIXct(datos$hour,format="%H:%M")
datos$Hour <- as.numeric(datos$hora)
## Map
## To plot the map, we remove the outliers: In this way, the map shows with  more precision the
## places where traffic violations happen.
## Since the asymmetry of the map, we remove the percentiles 5 in latitude, and 15 and 95 in longitude.
datos$Latitude <- datos$latitude
datos$Longitude <- datos$longitude
limitslat <- quantile(datos$latitude,c(0.03,1),na.rm=TRUE)
limitslong <- quantile(datos$longitude,c(0.01,0.999),na.rm=TRUE)
f <-  datos$latitude > limitslat[1] & datos$latitude < limitslat[2] &
    datos$longitude > limitslong[1] & datos$longitude < limitslong[2]
datosgr <- na.omit(datos[f,]) ## Data set to plot the map
datosgr[,.(place,latitude,longitude,fee)]
                           place latitude longitude fee
    1: AVDA PRINCIPE DE ASTURIAS 43.53894 -5.692616 200
    2: AVDA PRINCIPE DE ASTURIAS 43.53894 -5.692616 200
    3: AVDA PRINCIPE DE ASTURIAS 43.53894 -5.692616 200
    4: AVDA PRINCIPE DE ASTURIAS 43.53894 -5.692616 200
    5: AVDA PRINCIPE DE ASTURIAS 43.53894 -5.692616 200
   ---                                                 
96001:               AVDA OVIEDO 43.52270 -5.682149 100
96002:               AVDA OVIEDO 43.52270 -5.682149 100
96003:               AVDA OVIEDO 43.52270 -5.682149 100
96004:               AVDA OVIEDO 43.52270 -5.682149 100
96005:               AVDA OVIEDO 43.52270 -5.682149 100

Seleccionamos las diez calles donde se realizan más multas y las etiquetamos adecuadamente. Estos son los datos que se representarán como barras. Cada rectángulo se coloreará de forma distinta.

datoscalles <- datos[,.(fee=round(sum(fee)/(365*3))), by=place]
datosdd <- datoscalles[order(-fee),][1:10]
datosdd$place2 <- c("Avda. Oviedo",
                    "Ctra. Siero",
                    "Avda. Princ.",
                    "Juan Carlos I",
                    "Justo Castillo",
                    "Ctra. Somio",
                    "Gaspar Laviana",
                    "Cimadevilla",
                    "Avda. Llano",
                    "Ctra. Carbonera")
datosdd[,.(place2,fee)]
             place2  fee
 1:    Avda. Oviedo 2699
 2:     Ctra. Siero 1350
 3:    Avda. Princ. 1223
 4:   Juan Carlos I 1166
 5:  Justo Castillo  915
 6:     Ctra. Somio  844
 7:  Gaspar Laviana  464
 8:     Cimadevilla  435
 9:     Avda. Llano  409
10: Ctra. Carbonera  286

Armonización de escalas

Queremos superponer dos gráficos. El primero representa un gráfico de barras y el segundo grafica puntos mediante la latitud y longitud. Ajustamos las medidas de este último a las dimensiones del primer gráfico mediante una escala lineal. Ese ajuste se ha realizado a mano, reiterando el proceso hasta obtener un diseño conjunto aceptable.

maxfee <- max(datosdd$fee)
minfee <- min(datosdd$fee)
minlat <- min(datosgr$latitude,na.rm=TRUE)
maxlat <- max(datosgr$latitude,na.rm=TRUE)
minlong <- min(datosgr$longitude,na.rm=TRUE)
maxlong <- max(datosgr$longitude,na.rm=TRUE)
datosgrdd <- datosgr[, .(latitude,longitude,place,fee)]
datosgrdd$lat <- 7*(datosgrdd$latitude - min(datosgrdd$latitude)) / diff(range(datosgrdd$latitude)) + 1.5
datosgrdd$long <- (maxfee-minfee)*(datosgrdd$longitude - min(datosgrdd$longitude)) / diff(range(datosgrdd$longitude)) +500
datosgrddresumen <- datosgrdd[, .(latcenter=median(lat,na.rm=TRUE),longcenter=median(long,na.rm=TRUE)),by=place]
datosgrddresumen <- datosgrddresumen[place %in%  datosdd$place,]
datosdd <- merge(datosdd,datosgrddresumen,all.x=TRUE)
datosdd <- datosdd[order(-fee)]
## Cimadevilla data is missing. So we add the coordinates
datosdd[8,latcenter:=7.2]
datosdd[8,longcenter:=1600]
datosdd$pos <- 10:1
datosdd[,.(place2,fee,latcenter,longcenter,pos)]

             place2  fee latcenter longcenter pos
 1:    Avda. Oviedo 2699  1.788799  1004.2576  10
 2:     Ctra. Siero 1350  2.232996  1848.5991   9
 3:    Avda. Princ. 1223  5.252121   732.6217   8
 4:   Juan Carlos I 1166  5.676419  1098.6869   7
 5:  Justo Castillo  915  4.360528  2525.4773   6
 6:     Ctra. Somio  844  6.216697  2609.8363   5
 7:  Gaspar Laviana  464  2.549424  1510.5530   4
 8:     Cimadevilla  435  7.200000  1600.0000   3
 9:     Avda. Llano  409  2.884444  1643.4182   2
10: Ctra. Carbonera  286  1.734828  1447.3211   1

Color de cada barra

Como queremos que cada barra tenga un color distinto, modificamos la función lattice::panel.barchart para que asigne el color de la barra en función del valor de y. Se ha añadido un bucle for que dibuja cada barra panel.rect según la paleta de colores que se escoja, y se etiqueta cada barra para usarla si fuera necesario mediante Grob.

## Modifiy panel.barchart to get a name for each bar, and color. Just horizontal case.
panel.barchart2 <- function (x, y, box.ratio = 1, box.width = box.ratio/(1 + box.ratio),
                             horizontal = TRUE, origin = NULL, reference = TRUE, stack = FALSE,
                             groups = NULL, col = if (is.null(groups)) plot.polygon$col else superpose.polygon$col,
                             border = if (is.null(groups)) plot.polygon$border else superpose.polygon$border,
                             lty = if (is.null(groups)) plot.polygon$lty else superpose.polygon$lty,
                             lwd = if (is.null(groups)) plot.polygon$lwd else superpose.polygon$lwd,
                             ..., identifier = "barchart")
{
    plot.polygon <- trellis.par.get("plot.polygon")
    superpose.polygon <- trellis.par.get("superpose.polygon")
    reference.line <- trellis.par.get("reference.line")
    keep <- (function(x, y, groups, subscripts, ...) {
        !is.na(x) & !is.na(y) & if (is.null(groups))
                                    TRUE
                                else !is.na(groups[subscripts])
    })(x = x, y = y, groups = groups, ...)
    if (!any(keep))
        return()
    x <- as.numeric(x[keep])
    y <- as.numeric(y[keep])
    if (!is.null(groups)) {
        groupSub <- function(groups, subscripts, ...) groups[subscripts[keep]]
        if (!is.factor(groups))
            groups <- factor(groups)
        nvals <- nlevels(groups)
        groups <- as.numeric(groupSub(groups, ...))
    }
    if (horizontal) {
        if (is.null(groups)) {
            if (is.null(origin)) {
                origin <- current.panel.limits()$xlim[1]
                reference <- FALSE
            }
            height <- box.width
            if (reference)
                panel.abline(v = origin, col = reference.line$col,
                             lty = reference.line$lty, lwd = reference.line$lwd,
                             identifier = paste(identifier, "abline", sep = "."))
            for( i in y){
                ##   print(i)
                ##  print(x)
                panel.rect(x = origin, y = length(y)-i + 1, height = height, width = x[i] - origin, border = border,
                           col = col[i], lty = lty, lwd = lwd, just = c("left",
                                                                        "centre"), identifier = paste0(identifier,".",i))
            }
        }
    }
}


La paleta de color que usamos viene definida en R:

MyPalette <- sequential_hcl(10,"Heat") # used in barchat

Dibujando los gráficos

Ahora se dibujan bastantes elementos de una tacada: el gráfico de barras (panel.barchart2), el número en cada barra (panel.text), los puntos geolocalizados en color gris (panel.points) y los segmentos que unen las barras con los centros de las ubicaciones (panel.segments). Estos segmentos se han diseñado de tal forma que las líneas se corten lo menos posible.

graficobarras <- barchart(reorder(place2,fee)~fee,data=datosdd,
                          xlab=list(label=""),
                          main="Recaudación de 21 000 euros diarios en multas de tráfico",
                          xlab.top =list(label=paste("Las 10 principales calles donde se multa"), just="center",font=2, x = grid::unit(55, "mm")),
                          scales = list(x = list(draw = FALSE),
                                        y=list(font=2),
                                        cex=c(1.4,1.4)),
                          par.settings = list(axis.line = list(col = "transparent"),
                                              par.main.text = list(font = 2, # make it bold
                                                                   just = "left",
                                                                   cex=2.,
                                                                   x = grid::unit(5, "mm")),
                                              par.sub.text = list(font = 2,
                                                                  cex=1.3,
                                                                  just = "left",
                                                                  x = grid::unit(5, "mm"))),
                          panel=function(...) {
                              args <- list(...)
                              panel.barchart2(...,border=NULL,col=MyPalette)
                              panel.points(x=datosgrdd[["long"]],y=datosgrdd[["lat"]],pch=".",size=0.03,alpha=0.3,col="grey")
                              panel.text(args$x / 1.5, args$y, paste0(args$x),col="white", offset=0,font=2,cex=1.4)


                              ## Oviedo
                              i <- 1
                              x1 <- datosdd[["fee"]][i]
                              y1 <- datosdd[["pos"]][i]
                              x2 <- x1+100
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- x1
                              y2 <- datosdd[["latcenter"]][i]
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- datosdd[["longcenter"]][i]
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              panel.points(x2,y2,col=MyPalette[i], pch=16,cex=2)
                                      # Carbonera
                              i <- 10
                              x1 <- datosdd[["fee"]][i]
                              y1 <- datosdd[["pos"]][i]
                              x2 <- datosdd[["longcenter"]][i]
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- x1
                              y2 <- datosdd[["latcenter"]][i]
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- datosdd[["longcenter"]][i]
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              panel.points(x2,y2,col=MyPalette[i], pch=16,cex=2)

                                      # Llano
                              i <- 9
                              x1 <- datosdd[["fee"]][i]
                              y1 <- datosdd[["pos"]][i]
                              x2 <- datosdd[["longcenter"]][i]
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- x1
                              y2 <- datosdd[["latcenter"]][i]
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- datosdd[["longcenter"]][i]
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              panel.points(x2,y2,col=MyPalette[i], pch=16,cex=2)
                              ## panel.segments(x1,y1,x2,y2)

                                      # Cimadevilla
                              i <- 8
                              x1 <- datosdd[["fee"]][i]
                              y1 <- datosdd[["pos"]][i]
                              x2 <- datosdd[["longcenter"]][i]
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- x1
                              y2 <- datosdd[["latcenter"]][i]
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- datosdd[["longcenter"]][i]
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              panel.points(x2,y2,col=MyPalette[i], pch=16,cex=2)

                                      # Gaspar
                              i <- 7
                              x1 <- datosdd[["fee"]][i]
                              y1 <- datosdd[["pos"]][i]
                              x2 <- x1 + 100
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- x1
                              y2 <- datosdd[["latcenter"]][i]
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- datosdd[["longcenter"]][i]
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              panel.points(x2,y2,col=MyPalette[i], pch=16,cex=2)


                                      # Somio
                              i <- 6
                              x1 <- datosdd[["fee"]][i]
                              y1 <- datosdd[["pos"]][i]
                              x2 <- datosdd[["longcenter"]][i]
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- x1
                              y2 <- datosdd[["latcenter"]][i]
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- datosdd[["longcenter"]][i]
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              panel.points(x2,y2,col=MyPalette[i], pch=16,cex=2)
                              ## panel.segments(x1,y1,x2,y2)

                                      # Gaspar
                              i <- 5
                              x1 <- datosdd[["fee"]][i]
                              y1 <- datosdd[["pos"]][i]
                              x2 <- x1 + 100
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- x1
                              y2 <- datosdd[["latcenter"]][i]
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- datosdd[["longcenter"]][i]
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              panel.points(x2,y2,col=MyPalette[i], pch=16,cex=2)

                              ## panel.segments(x1,y1,x2,y2)


                                      # Juan Carlos
                              i <- 4
                              x1 <- datosdd[["fee"]][i]
                              y1 <- datosdd[["pos"]][i]
                              x2 <- x1 + 100
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- x1
                              y2 <- datosdd[["latcenter"]][i]
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- datosdd[["longcenter"]][i]
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              panel.points(x2,y2,col=MyPalette[i], pch=16,cex=2)


                                      # Av prin
                              i <- 3
                              x1 <- datosdd[["fee"]][i]
                              y1 <- datosdd[["pos"]][i]
                              x2 <- x1 + 100
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- x1
                              y2 <- datosdd[["latcenter"]][i]
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- datosdd[["longcenter"]][i]
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              panel.points(x2,y2,col=MyPalette[i], pch=16,cex=2)

                              i <- 2
                              x1 <- datosdd[["fee"]][i]
                              y1 <- datosdd[["pos"]][i]
                              x2 <- 2750
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- x1
                              y2 <- datosdd[["latcenter"]][i]
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              x1 <- x2
                              y1 <- y2
                              x2 <- datosdd[["longcenter"]][i]
                              y2 <- y1
                              panel.segments(x1,y1,x2,y2,col=MyPalette[i])
                              panel.points(x2,y2,col=MyPalette[i], pch=16,cex=2)

                          }
                          )

Salvamos el gráfico, respetando la proporción de ancho y alto del mapa.

png(file.path("graphics","grmapabarrasmultas.png"),width=830,height=580)
print(graficobarras)
dev.off()
grmapabarrasmultas.png