Introducción

El Ministerio de Transportes anunció que empezaría publicar los datos recabados, por medio de teléfonos móviles, de nuestra movilidad. El ministerio reúne esos datos, según dicen, para ayudarles a tomar mejores decisiones políticas respecto a la movilidad, y lo hacen, añaden, respetando en todo momento la Ley de Protección de Datos, anonimizando los datos que publican. Más allá de que todo esto pueda ser polémico, o que no se esté de acuerdo con las intenciones del Ministerio, lo cierto es que han dado acceso a los datos y podemos descargarlos y aplicar nuestros propios análisis.

Los datos

Los datos a los que dedicaremos nuestra atención son los viajes diarios a nivel ayuntamiento. Los podemos descargar por días o por meses completos. Por ejemplo, pinchando en este enlace te descargarás los datos del mes de enero de 2022. Es un tar de tres gigas y dentro están los ficheros diarios de ese mes comprimidos.

Esos ficheros diarios son csv que agrupan los movimientos por columnas:

  • Temporales
    • Fecha
    • Hora
  • Situación
    • Provincia de residencia
    • Origen del desplazamiento
    • Destino del desplazamiento
  • Sociales
    • Sexo de la persona que se desplaza
    • Edad
    • Renta
  • Viajes
    • Distancia recorrida en cada viaje
    • Cantidad de viajes
    • Kilómetros recorridos en total
    • Actividad de origen
    • Actividad de destino
    • Posibilidad de que el origen sea por estudios
    • Posibilidad de que el destino sea por estudios

En total quince columnas, que usan de separador el símbolo de la tubería (|). Cada fichero tiene además unos diez millones de filas. Esto solo para un día, hay colgados dos años completos, 2022 y 2023. Si uniéramos todos los ficheros en un único csv que incluyera toda la información el archivo tendría un tamaño de más de 650 GB. No todos los ordenadores en la casa de uno tienen esa cantidad de RAM en el momento de escribir esto (la fecha exacta debe de estar puesta por ahí arriba).

Miller

Miller es una herramienta que permite trabajar con ficheros csv sin necesidad de cargar los datos en memoria. He probado otros programas con distinto éxito (qsv, csvtk, duckdb), son todos buenos programas, pero al final siempre me quedo con miller.

Supongamos que queremos cambiar el separador original (|) del fichero del 01/01/2022 por una coma. Abrimos un terminal y tecleamos:

mlr -I --csv --ifs "|" cat 20220101_Viajes_municipios.csv.gz
  • mlr es el comando de miller
  • -I, Esta bandera cambia los ficheros in situ, sin tener que renombrarlos o utilizar ficheros temporales. Por defecto miller enviaría a la salida estándar, que podríamos encauzar a otro fichero con >.
  • --csv. Miller puede manejar más tipos de ficheros además de csv, como pueden ser JSON, tsv, etc. Así que debemos indicar qué tipo de fichero es el que queremos modificar.
  • --ifs "|" o --ifs pipe, que también vale, indica a miller que el fichero de entrada emplea el símbolo “|” como separador.
  • Por último el fichero. Fíjate en que no he tenido que descomprimirlo para trabajar con él, miller funciona con algunos tipos de compresión como gz o bzip.

Si hacemos un bucle con for en bash podemos transformar todos los ficheros que guardemos en un directorio. Tardará un rato, pero cualquier ordenador, aunque tenga poca potencia y poca memoria podrá hacer la faena.

Hagamos algo más complicado

El ejemplo de arriba es sencillo, y no conlleva ningún cálculo, pero supongamos que nos hemos bajado todos los fichero de un mes y queremos saber qué día de la semana se recorrren menos kilómetros de media en cada provincia.

El comando sería este:

mlr -I --ifs "|" -c --mfrom Transformar/*.csv.gz -- \
    put '$fecha = sub(string($fecha), "(\d{4})(\d{2})(\d{2})", "\1-\2-\3"); $mes = sub(string($fecha), "\d{4}-(\d{2})-\d+", "\1")' \
    then put 'segundos = strptime($fecha, "%Y-%m-%d"); $dia_semana = strftime(segundos, "%u")' \
    then stats1 -a sum -f viajes_km -g residencia,dia_semana \
    then stats1 -a mean -f viajes_km_sum -g residencia,dia_semana \
    then top --min -f viajes_km_sum_mean -a -g residencia \
    then join -j cpro -r residencia -f provincias.csv \
    then sort -f cpro \
    then cut -f provincia,dia_semana > dia_semana_menos_viajes_por_provincia.csv 

Parece mucha cosa, pero no es para tanto. En primer lugar fíjate en todas esas líneas que empiezan por then. Miller permite ir encadenando órdenes recogiendo el resultado de la orden anterior de manera que podemos ir añadiendo pasos hasta conseguir el resultado que deseamos. Tampoco es necesario poner todo ese comando a la vez, de hecho es fácil equivocarse en algún paso y que hayamos perdido un montón de tiempo solo para descubrir que el resultado no es válido. Como yo ya he ejecutado ese comando con anterioridad sé que está bien, pero es mejor ir por partes y guardar la salida de cada paso en un fichero csv intermedio.

mlr -I --ifs "|" -c --mfrom Movilidad/*.csv.gz -- put '$fecha = sub(string($fecha), "(\d{4})(\d{2})(\d{2})", "\1-\2-\3"); $mes = sub(string($fecha), "\d{4}-(\d{2})-\d+", "\1")' 

Este comando va a transformar todos los ficheros que hemos descargado dentro del directorio Movilidad cambiando el formato de la columna fecha y añadiéndoles dos columnas nuevas: mes y dia_semana. Además de ello ahora los ficheros ya no utilizan como separador la “tubería” sino la coma.

Comentaré solo las novedades.

  • -c es una abreviatura de --csv.
  • --mfrom <ficheros> --, cuando queramos que miller se ejecute sobre todos los ficheros que cumplan un patrón o que estén dentro de una directorio lo indicaremos de esta manera. Anteriormente dije que podíamos hacerlo con el iterador con la shell, pero de este modo parece más práctico.
  • put, este subcomando indica que vamos a modificar o crear nuevas columnas. Ponemos comillas simples alrededor de lo que indiquemos a este subcomando.
    • $fecha. Para hacer referencia a una columna pondremos el signo “$” al principio del nombre.
    • sub. Es una función que sustituye un texto por otro, para ello necesita que le indiquemos tres campos:
      1. Indicamos el lugar donde hay que buscar lo que queremos cambiar, en este caso en la columna fecha, que ha sido reconocida por miller como un campo numérico, así que con la función string indicamos que es un campo alfanumérico para que sub la maneje correctamente.
      2. El segundo campo es una de expresión regular1 que divide fecha en sus tres partes; año, mes y día.
      3. Por último intercalamos un guión entre cada una de las partes. Con la columna fecha en este formato la mayoría de programas para tratar csv van a identificar la tipo de la columna correctamente.
    • Separaremos la creación de una nueva columna con ;. Creamos la columna $mes a partir de la columna fecha.

Con el siguiente comando añadimos la columna con el día de la semana:

mlr -I -c --mfrom Movilidad/*.csv.gz -- put 'segundos = strptime($fecha, "%Y-%m-%d"); $dia_semana = strftime(segundos, "%u")'
  • Dos funciones nuevas con miller, hay muchas más que puedes revisar en su documentación. Primero pasamos a segundos la fecha actual con strptime y luego convertimos esos segundos en el día de la semana.

Una vez tenemos los ficheros csv con las columnas que vamos a necesitar podríamos reutilizarlos para hacer análisis distintos del que vamos a hacer aquí. Seguimos:

mlr -c --mfrom Movilidad/*.csv.gz -- \
    stats1 -a sum -f viajes_km -g residencia,dia_semana \
    then stats1 -a mean -f viajes_km_sum -g residencia,dia_semana \
    then top --min -f viajes_km_sum_mean -a -g residencia \
    then join -j cpro -r residencia -f provincias.csv \
    then sort -f cpro \
    then cut -f provincia,dia_semana > dia_semana_menos_viajes_por_provincia.csv 
  • stats1, subcomando para hacer algunas estadísticas con las columnas, como la media, mediana, moda, sumatorios, etc. En este caso indicamos que queremos que sume todos los kilómetros indicados en la columna viajes_km agrupados por residencia y dia_semana. Después necesitamos hacer la media, porque puede haber días entre semana festivos con movilidad distinta a la normal.
  • top. Con este subcomando eleginos el día con mayor valor, con la bandera --min todo lo contrario. Hay que indicar también en qué columna buscar el mayor valor y por cuál la agrupamos.
  • join. Se pueden unir otras tablas con este subcomando, en este caso con el fichero provincias.csv (descárgalo aquí). Con él relacionamos el código que aparece en residencia con un nombre más reconocible. Por ejemplo, la provincia 28 equivale a Madrid.
  • sort, ordena por la columna que indiquemos. El orden podría ser inverso, claro.
  • cut, ahora seleccionamos las columnas con las que quedarnos, eliminando así las columnas que ya no necesitamos.
  • Por último redirigimos el resultado a un fichero.

Resultados

He aplicado estos comandos a los dos años de los que tenemos datos, tarda casi siete horas desde que ya hemos añadido las columnas mes y dia_semana. Eso sí, el consumo de miller no sube nunca de 2 GB. Te recomiendo la aplicación pueue para organizar las ejecuciones de programas que consumen mucho tiempo.

Por si te interesa el resultado el día que más se viaja con diferencia es el viernes, se suman los viajes relacionados con el trabajo y los estudios a los viajes de ocio de fin de semana. Respecto a qué día se viaja menos hay más variedad.

Se viaja menos el martes en:

  • Álava, Barcelona, Burgos, Cáceres, Córdoba, La Coruña, Granada, Jaén, León, La Rioja, Madrid, Málaga, Navarra, Asturias, Palencia, Las Palmas, Salamanca, Sevilla, Soria, Valladolid, Zaragoza, Ceuta y Melilla

Sábado:

  • Albacete, Badajoz y Valencia.

Y domingo:

  • Alicante, Almería, Ávila, Baleares, Cádiz, Castellón, Ciudad Real, Cuenca, Girona, Guadalajara, Gipuzkoa, Huelva, Huesca, Lleida, Lugo, Murcia, Ourense, Pontevedra, Santa Cruz de Tenerife, Cantabria, Segovia, Tarragona, Teruel, Toledo, Bizkaia y Zamora

  1. No retrases el aprender expresiones regulares, si te gusta la informática te serán necesarias más pronto que tarde. ↩︎