Imaginemos por un momento que trabajamos en un área del Gobierno de la Ciudad encargada de analizar los reclamos y solicitudes que realizan los vecinos. Para abordar el tema, algunas preguntas que de inmediato vienen a la mente son ¿Qué tipo de solicitudes hacen los ciudadanos, en qué cantidad, y en dónde?
Disponiendo de los datos necesarios, podemos usar R para encontrar las respuestas.
Vamos a cargar datos provenientes de los registros del Sistema Único de Atención Ciudadana (o SUACI), la plataforma del Gobierno de la Ciudad que administra los contactos iniciados por ciudadanos. En el portal de datos abiertos de la Ciudad se publica cada uno de los contactos recibidos por SUACI año a año. El tipo de archivo con el que se publican es “.csv” (o “comma separated values”) un formato muy popular en el mundo de la ciencia de datos, ya que es muy fácil de manipular y compartir entre sistemas. Es posible abrir un archivo .csv hasta con el humilde block de notas. Al igual que los archivos .xls, los .csv se utilizan para guardar información tabular: un rectángulo con filas y columnas. R incluye una función que lee archivos .csv, que se llama read.csv. La usamos así:
suaci2018 <- read.csv('https://bitsandbricks.github.io/data/gcba_suaci_2018.csv')
Obsérvese que los datos están alojados en un servidor de internet (accesibles vía https://bitsandbricks…). Eso no es problema para la función read.csv, que con la misma soltura lee archivos guardados en nuestra PC o publicados en un sitio web. Para ver el contenido de la variable donde guardamos el resultado de leer la data, suaci2018, sólo hace falta escribir su nombre:
suaci2018
## BARRIO CONTACTOS
## 1 AGRONOMIA 7378
## 2 ALMAGRO 31420
## 3 BALVANERA 28616
## 4 BARRACAS 23106
## 5 BELGRANO 46936
## 6 BOCA 11495
## 7 BOEDO 12926
## 8 CABALLITO 50301
## 9 CHACARITA 11076
## 10 COGHLAN 8920
## 11 COLEGIALES 18637
## 12 CONSTITUCION 7569
## 13 FLORES 38462
## 14 FLORESTA 13589
## 15 LINIERS 17606
## 16 MATADEROS 19771
## 17 MONSERRAT 10716
## 18 MONTE CASTRO 15459
## 19 NUEVA POMPEYA 11762
## 20 NUÑEZ 26556
## 21 PALERMO 75068
## 22 PARQUE AVELLANEDA 14914
## 23 PARQUE CHACABUCO 16560
## 24 PARQUE CHAS 8948
## 25 PARQUE PATRICIOS 13559
## 26 PATERNAL 7255
## 27 PUERTO MADERO 1939
## 28 RECOLETA 30339
## 29 RETIRO 10646
## 30 SAAVEDRA 18232
## 31 SAN CRISTOBAL 11050
## 32 SAN NICOLAS 12526
## 33 SAN TELMO 5281
## 34 VELEZ SARSFIELD 11586
## 35 VERSALLES 5835
## 36 VILLA CRESPO 25748
## 37 VILLA DEL PARQUE 25224
## 38 VILLA DEVOTO 34361
## 39 VILLA GRAL. MITRE 11417
## 40 VILLA LUGANO 24853
## 41 VILLA LURO 11485
## 42 VILLA ORTUZAR 11150
## 43 VILLA PUEYRREDON 18985
## 44 VILLA REAL 5496
## 45 VILLA RIACHUELO 5311
## 46 VILLA SANTA RITA 13265
## 47 VILLA SOLDATI 9811
## 48 VILLA URQUIZA 40146
Vemos que la tabla tiene 48 filas (una por cada barrio de la ciudad) y 2 columnas (una con el nombre del barrio, y otra con la cantidad total de contactos registrados en 2018).
En R, las tablas son llamadas dataframes.
La función head() nos permite echar un vistazo rápido al contenido, mostrando sólo las seis primeras filas:
head(suaci2018)
## BARRIO CONTACTOS
## 1 AGRONOMIA 7378
## 2 ALMAGRO 31420
## 3 BALVANERA 28616
## 4 BARRACAS 23106
## 5 BELGRANO 46936
## 6 BOCA 11495
R es un entorno modular que permite instalarle diferentes “paquetes” con funcionalidades. Nosotros vamos a cargar uno de los paquetes más usados, tidyverse. Tidyverse incluye una gran cantidad de funciones diseñadas por y para practicantes de la ciencia de datos. El valor que aportan es que, sin dudas, ayudan a realizar de manera más fácil las tareas típicas de la ciencia de datos: importar, limpiar, comprender y comunicar datos.
Para instalarlo, usamos la función install.packages() y le pasamos el nombre del paquete deseado, “tidyverse”, entre comillas.
install.packages("tidyverse")
De aquí en más, podremos activar el conjunto de funciones que provee tidyverse cada vez que queramos. Para eso, lo invocamos con la función library():
library(tidyverse)
## ── Attaching packages ────────────────────────────────────────────── tidyverse 1.2.1 ──
## ✔ ggplot2 3.1.1 ✔ purrr 0.3.2
## ✔ tibble 2.1.1 ✔ dplyr 0.8.0.1
## ✔ tidyr 0.8.3 ✔ stringr 1.4.0
## ✔ readr 1.3.1 ✔ forcats 0.4.0
## ── Conflicts ───────────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
… y listo para usar. La razón por la cual activamos tidyverse es que en este momento nos vienen bien dos de sus funciones: mutate() para modificar valores, y ggplot() para hacer gráficos.
Para presentar la información en forma visual vamos a llamar a la función ggplot().
Por ejemplo, veamos cuántos contactos sumó cada barrio durante 2018:
ggplot(suaci2018) +
geom_col(aes(x = BARRIO, y = CONTACTOS)) +
coord_flip()
Para realizar una visualización con ésta herramienta, siempre se comienza con la función ggplot(), que crea un eje de coordenadas sobre el cual se pueden agregar capas. El primer parámetro que recibe ggplot() es el dataset que queremos usar para el gráfico; en nuestro caso, ggplot(suaci2018). Ejecutar sólo ggplot(suaci2018) nos devuelve un gráfico vacío; la gracia está en agregar una o más capas especificando cómo queremos mostrar los datos. Estas capas se agregan con un signo +.
En nuestro ejemplo, geom_col() crea columnas cuya posición en el eje vertical depende de la variable “BARRIO”, mientas que la extensión (posición en el eje horizontal) depende del valor de la variable “CONTACTOS”. Existen muchas funciones del tipo “geom_XXX”, que agregan distintas clases de capas al gráfico: geom_point, geom_polygon, geom_text y muchos más.
En cuanto al gráfico que hemos creado, podemos observar que entre las 48 barrios de la ciudad la cantidad de contactos en 2018 varió bastante. Si escudriñamos las líneas podemos arriesgar que van de de un par de miles en Puerto Madero a unos 75000 en Palermo. Lo que no nos muestra son patrones espaciales: ¿Cómo es la distribución geográfica de las solicitudes? ¿Los barrios con mayor demanda son los de una región de la ciudad en particular? Para responde esas preguntas, necesitamos un mapa.
Vamos a presentar un paquete más: sf. En R, el paquete sf brinda herramientas que permiten realizar tares similares a los de los sistemas ArcGIS.
Nuestro objetivo es obtener un mapa de la ciudad de Buenos Aires con sus comunas.
Primero, instalamos sf en caso de que aún no lo hayamos hecho.
install.packages("sf")
Ahora le pedimos a R que active el paquete así:
library(sf)
## Linking to GEOS 3.5.1, GDAL 2.1.3, PROJ 4.9.2
Luego, cargamos un archivo georeferenciado con las comunas de la Ciudad Autónoma de Buenos Aires, disponible online en formato geojson, un estándar de representación de datos geográficos que es fácil de usar:
barrios <- st_read('https://bitsandbricks.github.io/data/CABA_barrios.geojson')
## Reading layer `OGRGeoJSON' from data source `https://bitsandbricks.github.io/data/CABA_barrios.geojson' using driver `GeoJSON'
## Simple feature collection with 48 features and 4 fields
## geometry type: POLYGON
## dimension: XY
## bbox: xmin: -58.53152 ymin: -34.70529 xmax: -58.33514 ymax: -34.52754
## epsg (SRID): 4326
## proj4string: +proj=longlat +datum=WGS84 +no_defs
Al igual que cuando usamos read.csv() para leer un archivo .csv y cargarlo como un dataframe, el comando st_read() hace lo propio con archivos de información geográfica, conocidos en la jerga como “shapefiles”. El resultado también es un dataframe.
head(barrios)
## Simple feature collection with 6 features and 4 fields
## geometry type: POLYGON
## dimension: XY
## bbox: xmin: -58.50617 ymin: -34.63064 xmax: -58.41192 ymax: -34.57829
## epsg (SRID): 4326
## proj4string: +proj=longlat +datum=WGS84 +no_defs
## BARRIO COMUNA PERIMETRO AREA geometry
## 1 CHACARITA 15 7725.695 3118101 POLYGON ((-58.45282 -34.595...
## 2 PATERNAL 15 7087.513 2229829 POLYGON ((-58.46558 -34.596...
## 3 VILLA CRESPO 15 8132.699 3613584 POLYGON ((-58.42375 -34.597...
## 4 VILLA DEL PARQUE 11 7705.390 3399596 POLYGON ((-58.49461 -34.614...
## 5 ALMAGRO 5 8537.901 4050752 POLYGON ((-58.41287 -34.614...
## 6 CABALLITO 6 10990.964 6851029 POLYGON ((-58.43061 -34.607...
Podemos ver que el dataframe contiene 48 filas y 5 columnas. Una fila por barrio, y una columnas por cada variable disponible: “BARRIO”, “COMUNA”, “PERIMETRO”, “AREA” y “geometry”. Nuestro vistazo mediante head() permite asumir que “BARRIO” contiene los nombres, COMUNA indica la unidad administrativa, y PERIMETRO y AREA informan sobre las dimensiones del polígono cubierto por cada barrio. La columna “geometry” aparece en todos los dataframes de tipo espacial, y es la que contiene los datos con sus coordenadas geográficas.
Y hablando de coordenadas, generar un mapa a partir de un dataframe espacial creado por sf es muy fácil con la ayuda de ggplot():
ggplot(barrios) +
geom_sf()
Si queremos agregar una leyenda al mapa que identifique la comuna a la que pertenece cada barrio, usamos:
ggplot(barrios) +
geom_sf(aes(fill = factor(COMUNA)))
Dentro de “aes()” usé el parámetro “fill” (relleno en inglés) para pedirle a ggplot() que llene cada polígono con un color distinto de acuerdo al campo “COMUNA”. Lo mismo podríamos hacer para ver la distribución de solicitudes registradas en SUACI, pintando los barrios con una escala de colores que indique el nivel de solicitudes que les corresponde. Pero antes de hacer eso, pensemos: es de esperarse que los barrios más poblados representen un mayor número de solicitudes que los más pequeños, ya que al fin y al cabo tienen más gente viviendo dentro. Nos interesa la cantiad de contactos per cápita, lo cuál debería darnos una mejor idea acerca de las diferencias entre barrios en lo que respecta a cuánta interacción hay entre vecinos y gobierno.
Sumemos pues datos de población.
Cuando tenemos una identificador en común, R hace muy fácil cruzar datos de fuentes distintas. Traigamos los datos de población en cada barrio de la Ciudad de Buenos Aires, de acuerdo al censo nacional de 2010:
poblacion <- read.csv("https://bitsandbricks.github.io/data/caba_pob_barrios_2010.csv")
Veamos el contenido del dataframe:
head(poblacion)
## BARRIO POBLACION
## 1 AGRONOMIA 13912
## 2 ALMAGRO 131699
## 3 BALVANERA 138926
## 4 BARRACAS 89452
## 5 BELGRANO 126267
## 6 BOCA 45113
Bien, tenemos el nombre de cada barrio y su población. Como nuestro dataframe de contactos a SUACI tiene una columna con el mismo nombre que contiene las mismas categorías, sumarle la información de población es tan fácil como:
suaci2018 <- left_join(suaci2018, poblacion)
## Joining, by = "BARRIO"
Veamos entonces el resultado:
head(suaci2018)
## BARRIO CONTACTOS POBLACION
## 1 AGRONOMIA 7378 13912
## 2 ALMAGRO 31420 131699
## 3 BALVANERA 28616 138926
## 4 BARRACAS 23106 89452
## 5 BELGRANO 46936 126267
## 6 BOCA 11495 45113
Todo en orden. Ahora usemos una operación similar para sumar los datos de SUACI y de pobación al dataframe con información espacial, el que nos permite dibujar los barrios. Teniendo toda la información allí, podremos visualizar el mapa que queremos.
barrios <- left_join(barrios, suaci2018)
## Joining, by = "BARRIO"
Habrán notado que llegar hasta aquí tomó una buena cantidad de operaciones. En contraste, lo que estamos a punto de hacer -generar un mapa con los barrios de la ciudad que muestre la cantidad de solicitudes de la ciudadanía per cápita- va a ser mucho más breve. Esa vendría a ser la lección central de éste capítulo: la mayor parte del tiempo empleado en la labor de la ciencia de datos se insume en la poco glamorosa tarea de recopilar, limpiar y combinar los registros necesarios para el análisis. Como consuelo, podemos pensar en que el esfuerzo necesario para llegar a este punto nos ha dado un conocimiento de los datos (su estructura, su contenido, atributos en común, etc) que no teníamos antes.
Aprovechemos entonces nuestra data limpia y ordenada, para producir un mapa que indique por color el grado en que los vecinos de cada barrio interactúan con los canales de atención del gobierno de la Ciudad. Con ggplot dibujaremos los barrios, y pintaremos su interior (“fill”) de acuerdo a la cantidad de contactos, dividida por la población local. Por último, definiremos la paleta de colores a usar en el fill, eligiendo una escala llamada “Spectral”, que va del azul al rojo y es muy usada cuando se quiere resaltar la divergencia de una variable.
ggplot(barrios) +
geom_sf(aes(fill = CONTACTOS / POBLACION)) +
scale_fill_distiller(palette = "Spectral")
¡Y hemos hallado un patrón! La zona caliente de contactos se asienta en el sector noroeste de la Ciudad.
Por supuesto, con esto no puede darse por cerrado el tema; hay muchas facetas que deberíamos analizar para comenzar a entender éste fenómeno social o cualquier otro.
Hemos visto en menos de 100 líneas de qué manera puede utilizarse un paquete de datos como R para hacer un análisis de datos sobre las consultas de los vecinos de la CABA.