Varia

Diez años de MiBici en Guadalajara: los datos hablan

Jorge Zepeda

IDEAS ALTERNATIVAS 1 - OCTUBRE-DICIEMBRE 2025

Sistema de préstamo de bicicletas por suscripción temporal o anual, MiBici tiene presencia en Guadalajara desde diciembre de 2014, y a la fecha ha evolucionado hasta convertirse en un medio práctico para desplazarse en el poniente de la ciudad, desde colonias como Chapalita y Americana hasta Santa Tere y Centro. En el presente artículo analizaremos los datos abiertos disponibles en el portal y obtendremos un panorama general del uso que han hecho del sistema los habitantes de la Perla tapatía.

Diez años de MiBici en Guadalajara
Estación GDL-048 de MiBici, a un costado de la antigua rectoría de la Universidad de Guadalajara. Fuente: Wikimedia Commons.

Preparación del conjunto de datos

Comenzaremos por descargar los datos abiertos de MiBici. Pueden obtenerse en la siguiente URL: https://mibici.net/es/datos-abiertos/ Los datos están disponibles desde diciembre de 2014 hasta septiembre de 2025 (según la fecha de consulta del sitio), es decir, casi 11 años de registros de viajes. Después de descargarlos en sus respectivos directorios, veremos que los nombres tienen la forma datos_abiertos_XXXX_YY.csv, donde XXXX es el año y YY el mes a dos dígitos. En ciertos casos algunos archivos tienen una forma un poco distinta. Para los propósitos de este análisis, los que no tengan esa nomenclatura se deberán renombrar usando el formato antes mencionado. También descargaremos el diccionario, donde aparece información de las estaciones de bicicleta. Una vez que tengamos los archivos, haremos una inspección visual rápida del contenido para verificar que todo se ve bien.

Archivos-1 Archivos-2 Archivos-3 Archivos-4

Dado que al parecer en algunos archivos los valores están entre comillas y en otros no, vamos a eliminarlas. Para ello ejecutamos el script para eliminar las comillas. En Ubuntu, primero lo hacemos ejecutable mediante chmod +x ScriptEliminarComillas.sh y luego lo ejecutamos como ./ScriptEliminarComillas.sh. Cuando termine, los archivos de datos estarán libres de comillas. El script también reemplaza la letra ‘ñ’ por la letra ‘n’.


  
#ScriptEliminarComillas.sh

#!/bin/bash

for i in $(seq 2014 2025)
do
    for j in $(seq 1 12)
    do
        mes=""
        longitudMes="$(echo ${#j})"
        
        if [[ $longitudMes = 1 ]]
            then
                mes="0${j}"
        elif [[ $longitudMes = 2 ]]
            then
                mes=$j
        fi

        existe="$(test -e ./$i/datos_abiertos_${i}_${mes}.csv && echo true || echo false)"

        if [ $existe = 'true' ]
        then
            sed -i 's/"//g' ./$i/datos_abiertos_${i}_${mes}.csv
            sed -i 's/ñ/n/g' ./$i/datos_abiertos_${i}_${mes}.csv
        fi
    done
done
  

Las columnas de los datos son las siguientes: Viaje_Id, Usuario_Id, Genero, Ano_de_nacimiento, Inicio_del_viaje, Fin_del_viaje , Origen_Id y Destino_Id. Inspeccionado los datos se observa que las columnas Viaje_Id y Usuario_Id son de tipo INT, la de Genero es de tipo TEXT NULL (un caracter que acepta valores nulos), la de Ano_de_nacimiento es FLOAT, que luego cambiaremos a INT; las de Inicio_del_viaje y Fin_del_viaje son de tipo TIMESTAMP y Origen_Id y Destino_Id son INT. No obstante, dado que algunos registros tienen el valor ‘NA’ en el año de nacimiento, el tipo de datos de la columna será TEXT (más tarde se cambiará a INT). Para ello vamos a crear la tabla con el siguiente script.


  
--SQLCrearTabla.sql

CREATE TABLE Registros_MiBici
( Viaje_Id INT, 
  Usuario_Id INT,
  Genero TEXT NULL,
  Ano_de_nacimiento TEXT,
  Inicio_del_viaje TIMESTAMP,
  Fin_del_viaje TIMESTAMP,
  Origen_Id INT,
  Destino_Id INT
);
  

Ahora que la hemos creado, vamos a importar los datos con el comando COPY.

  
  
--SQLImportarDatos.sql

COPY Registros_MiBici FROM '/tmp/MiBici/2014/datos_abiertos_2014_12.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2015/datos_abiertos_2015_01.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2015/datos_abiertos_2015_02.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2015/datos_abiertos_2015_03.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2015/datos_abiertos_2015_04.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2015/datos_abiertos_2015_05.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2015/datos_abiertos_2015_06.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2015/datos_abiertos_2015_07.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2015/datos_abiertos_2015_08.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2015/datos_abiertos_2015_09.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2015/datos_abiertos_2015_10.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2015/datos_abiertos_2015_11.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2015/datos_abiertos_2015_12.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2016/datos_abiertos_2016_01.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2016/datos_abiertos_2016_02.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2016/datos_abiertos_2016_03.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2016/datos_abiertos_2016_04.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2016/datos_abiertos_2016_05.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2016/datos_abiertos_2016_06.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2016/datos_abiertos_2016_07.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2016/datos_abiertos_2016_08.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2016/datos_abiertos_2016_09.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2016/datos_abiertos_2016_10.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2016/datos_abiertos_2016_11.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2016/datos_abiertos_2016_12.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2017/datos_abiertos_2017_01.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2017/datos_abiertos_2017_02.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2017/datos_abiertos_2017_03.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2017/datos_abiertos_2017_04.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2017/datos_abiertos_2017_05.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2017/datos_abiertos_2017_06.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2017/datos_abiertos_2017_07.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2017/datos_abiertos_2017_08.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2017/datos_abiertos_2017_09.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2017/datos_abiertos_2017_10.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2017/datos_abiertos_2017_11.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2017/datos_abiertos_2017_12.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2018/datos_abiertos_2018_01.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2018/datos_abiertos_2018_02.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2018/datos_abiertos_2018_03.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2018/datos_abiertos_2018_04.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2018/datos_abiertos_2018_05.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2018/datos_abiertos_2018_06.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2018/datos_abiertos_2018_07.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2018/datos_abiertos_2018_08.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2018/datos_abiertos_2018_09.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2018/datos_abiertos_2018_10.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2018/datos_abiertos_2018_11.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2018/datos_abiertos_2018_12.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2019/datos_abiertos_2019_01.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2019/datos_abiertos_2019_02.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2019/datos_abiertos_2019_03.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2019/datos_abiertos_2019_04.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2019/datos_abiertos_2019_05.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2019/datos_abiertos_2019_06.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2019/datos_abiertos_2019_07.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2019/datos_abiertos_2019_08.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2019/datos_abiertos_2019_09.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2019/datos_abiertos_2019_10.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2019/datos_abiertos_2019_11.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2019/datos_abiertos_2019_12.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2020/datos_abiertos_2020_01.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2020/datos_abiertos_2020_02.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2020/datos_abiertos_2020_03.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2020/datos_abiertos_2020_04.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2020/datos_abiertos_2020_05.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2020/datos_abiertos_2020_06.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2020/datos_abiertos_2020_07.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2020/datos_abiertos_2020_08.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2020/datos_abiertos_2020_09.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2020/datos_abiertos_2020_10.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2020/datos_abiertos_2020_11.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2020/datos_abiertos_2020_12.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2021/datos_abiertos_2021_01.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2021/datos_abiertos_2021_02.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2021/datos_abiertos_2021_03.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2021/datos_abiertos_2021_04.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2021/datos_abiertos_2021_05.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2021/datos_abiertos_2021_06.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2021/datos_abiertos_2021_07.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2021/datos_abiertos_2021_08.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2021/datos_abiertos_2021_09.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2021/datos_abiertos_2021_10.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2021/datos_abiertos_2021_11.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2021/datos_abiertos_2021_12.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2022/datos_abiertos_2022_01.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2022/datos_abiertos_2022_02.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2022/datos_abiertos_2022_03.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2022/datos_abiertos_2022_04.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2022/datos_abiertos_2022_05.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2022/datos_abiertos_2022_06.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2022/datos_abiertos_2022_07.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2022/datos_abiertos_2022_08.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2022/datos_abiertos_2022_09.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2022/datos_abiertos_2022_10.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2022/datos_abiertos_2022_11.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2022/datos_abiertos_2022_12.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2023/datos_abiertos_2023_01.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2023/datos_abiertos_2023_02.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2023/datos_abiertos_2023_03.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2023/datos_abiertos_2023_04.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2023/datos_abiertos_2023_05.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2023/datos_abiertos_2023_06.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2023/datos_abiertos_2023_07.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2023/datos_abiertos_2023_08.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2023/datos_abiertos_2023_09.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2023/datos_abiertos_2023_10.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2023/datos_abiertos_2023_11.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2023/datos_abiertos_2023_12.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2024/datos_abiertos_2024_01.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2024/datos_abiertos_2024_02.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2024/datos_abiertos_2024_03.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2024/datos_abiertos_2024_04.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2024/datos_abiertos_2024_05.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2024/datos_abiertos_2024_06.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2024/datos_abiertos_2024_07.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2024/datos_abiertos_2024_08.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2024/datos_abiertos_2024_09.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2024/datos_abiertos_2024_10.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2024/datos_abiertos_2024_11.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2024/datos_abiertos_2024_12.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2025/datos_abiertos_2025_01.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2025/datos_abiertos_2025_02.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2025/datos_abiertos_2025_03.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2025/datos_abiertos_2025_04.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2025/datos_abiertos_2025_05.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2025/datos_abiertos_2025_06.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2025/datos_abiertos_2025_07.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2025/datos_abiertos_2025_08.csv' CSV HEADER ENCODING 'ISO_8859_5';
COPY Registros_MiBici FROM '/tmp/MiBici/2025/datos_abiertos_2025_09.csv' CSV HEADER ENCODING 'ISO_8859_5';

  

Después de la importación, consultando la cantidad de registros en la tabla tenemos que hay 34,580,963 filas. ¡Enhorabuena! Hemos cargado los datos a la tabla.

  
  
--SQLConsultarRegistrosTotales.sql

SELECT COUNT(*) FROM Registros_MiBici;
  

Pero antes de comenzar a exprimir los registros para obtener información valiosa, tenemos que hacer una limpieza de los datos. Comencemos por la columna Viaje_Id. De entrada queremos ver si hay registros duplicados (aunque en principio no debería, porque en teoría cada viaje es único). Para ello, obtendremos los Viaje_Id para los que haya dos o más registros con el mismo valor. Después de hacer la prueba no obtenemos valores, dado que no hay más de un registro con el mismo Viaje_Id.

  
  
--SQLRevisarDuplicados.sql

SELECT DISTINCT Viaje_Id, COUNT(*) AS Viajes_con_mismo_Viaje_Id
FROM Registros_MiBici
GROUP BY Viaje_Id
HAVING COUNT(*) > 1
ORDER BY Viajes_con_mismo_Viaje_Id DESC;
  

Los registros únicos los obtenemos con la siguiente consulta, que nos arroja nuevamente 34,580,963 filas.

  
  
--SQLObtenerRegistrosUnicos.sql

SELECT COUNT(DISTINCT Viaje_Id)
FROM Registros_MiBici;
  

Dado que la columna Viaje_Id es la única que en teoría no debe permitir valores duplicados, no es necesario revisar los valores duplicados en las demás columnas como parte de la limpieza previa de los datos. Revisando la columna de Genero, observamos que hay 4 géneros: M, F, NULL y [NULL]. Las cantidades se ven bien: aproximadamente 25 millones de registros de hombres, 9 millones de mujeres y 110,000 con valor NULL.

  
  
--SQLObtenerGenerosDistintos.sql

SELECT DISTINCT Genero, COUNT(*) AS Registros_con_genero
FROM Registros_MiBici
GROUP BY Genero;
  

Como ese valor puede deberse a que las personas hayan preferido no revelar su género al momento de registrarse, o que haya habido algún error al crear el registro del viaje, vamos a establecer como [NULL] los valores NULL. PostgreSQL arroja que se modificaron 99,428 registros, que son los mencionados arriba (los otros 10,000 aproximadamente eran ya los que tenían valor NULL).

  
  
--SQLCambiarGeneroNulo.sql

UPDATE Registros_MiBici SET Genero = NULL WHERE Genero = 'NULL';
  

Veamos ahora los valores en la columna Ano_de_nacimiento. Ejecutando la consulta a continuación, obtenemos los siguientes años de nacimiento y la cantidad de cada uno.

  
  
--SQLRevisarAnosNacimiento.sql

SELECT Ano_de_nacimiento, COUNT(Ano_de_nacimiento) AS Cantidad_Registros
FROM Registros_MiBici
GROUP BY Ano_de_nacimiento
ORDER BY Ano_de_nacimiento;
  

  
  
--AnosNacimientoAntesLimpieza.csv

Ano_de_nacimiento Cantidad_registros
1 747
1917.0 206
1918.0 1228
1920 1814
1920.0 1067
1922 34
1922.0 735
1924.0 40
1931 7
1931.0 89
1933 68
1933.0 286
1934.0 8
1935.0 5
1936.0 228
1937 3
1938 2
1939 2449
1939.0 451
1940 62
1940.0 294
1941 1802
1941.0 1027
1942 3851
1942.0 5091
1943 36
1943.0 3528
1944 279
1944.0 904
1945 2543
1945.0 1903
1946 292
1946.0 969
1947 34
1947.0 1116
1948 10374
1948.0 16558
1949 14483
1949.0 25350
1950 8542
1950.0 12283
1951 4434
1951.0 12429
1952 8605
1952.0 22356
1953 6705
1953.0 14027
1954 12661
1954.0 16105
1955 41516
1955.0 43051
1956 29098
1956.0 32078
1957 25862
1957.0 41678
1958 21521
1958.0 35994
1959 38662
1959.0 42735
1960 68864
1960.0 68371
1961 64021
1961.0 70699
1962 56311
1962.0 70239
1963 84479
1963.0 92284
1964 94836
1964.0 104801
1965 77875
1965.0 116656
1966 83879
1966.0 85916
1967 129974
1967.0 120991
1968 134098
1968.0 110493
1969 138314
1969.0 144861
1970 341236
1970.0 158636
1971 158276
1971.0 152842
1972 165020
1972.0 172829
1973 159931
1973.0 157448
1974 169578
1974.0 188288
1975 178867
1975.0 182182
1976 188551
1976.0 211107
1977 229543
1977.0 244093
1978 208379
1978.0 254778
1979 258580
1979.0 270581
1980 291777
1980.0 338447
1981 297742
1981.0 322720
1982 341563
1982.0 432036
1983 309317
1983.0 370167
1984 379548
1984.0 412487
1985 400592
1985.0 478181
1986 479337
1986.0 535264
1987 432134
1987.0 581987
1988 547414
1988.0 705145
1989 633026
1989.0 799801
1990 742984
1990.0 944659
1991 760695
1991.0 885190
1992 821635
1992.0 937862
1993 844960
1993.0 900336
1994 945111
1994.0 885714
1995 940129
1995.0 778637
1996 1047977
1996.0 670152
1997 983972
1997.0 604390
1998 866280
1998.0 410061
1999 819893
1999.0 322803
200 189
2000 664067
2000.0 195494
2001 574729
2001.0 87569
2002 426855
2002.0 45407
2003 302522
2003.0 8501
2004 220677
2004.0 673
2005 126651
2005.0 41
2006 70150
2007 21702
2008 10447
2009 549
2021 571
2022 379
2023 1151
NA 30219
NULL,0
  

Como se observa, algunos años tienen parte decimal, y hay otros que tiene valor de NA. Para corregir esos datos vamos a reemplazar el valor NA por el año 1, vamos a eliminar la parte decimal y a cambiar el tipo de datos para que la columna sea de enteros.

  
  
--SQLLimpiarAnosNacimiento.sql    

UPDATE Registros_MiBici SET Ano_de_Nacimiento = 1
WHERE Ano_de_Nacimiento = 'NA';

ALTER TABLE Registros_MiBici
ALTER COLUMN Ano_de_Nacimiento
TYPE FLOAT
USING (Ano_de_Nacimiento::FLOAT);

ALTER TABLE Registros_MiBici
ALTER COLUMN Ano_de_Nacimiento
TYPE INTEGER
USING (Ano_de_Nacimiento::INTEGER);

UPDATE Registros_MiBici
SET Ano_de_Nacimiento = 1
WHERE Ano_de_Nacimiento IS NULL;

ALTER TABLE Registros_MiBici
ALTER COLUMN Ano_de_Nacimiento
SET NOT NULL;
  

Ahora los resultados son más realistas.

  
  
--AnosNacimientoDespuesLimpieza.csv

Ano_de_nacimiento Cantidad_registros
1 82249
200 189
1917 206
1918 1228
1920 2881
1922 769
1924 40
1931 96
1933 354
1934 8
1935 5
1936 228
1937 3
1938 2
1939 2900
1940 356
1941 2829
1942 8942
1943 3564
1944 1183
1945 4446
1946 1261
1947 1150
1948 26932
1949 39833
1950 20825
1951 16863
1952 30961
1953 20732
1954 28766
1955 84567
1956 61176
1957 67540
1958 57515
1959 81397
1960 137235
1961 134720
1962 126550
1963 176763
1964 199637
1965 194531
1966 169795
1967 250965
1968 244591
1969 283175
1970 499872
1971 311118
1972 337849
1973 317379
1974 357866
1975 361049
1976 399658
1977 473636
1978 463157
1979 529161
1980 630224
1981 620462
1982 773599
1983 679484
1984 792035
1985 878773
1986 1014601
1987 1014121
1988 1252559
1989 1432827
1990 1687643
1991 1645885
1992 1759497
1993 1745296
1994 1830825
1995 1718766
1996 1718129
1997 1588362
1998 1276341
1999 1142696
2000 859561
2001 662298
2002 472262
2003 311023
2004 221350
2005 126692
2006 70150
2007 21702
2008 10447
2009 549
2021 571
2022 379
2023 1151
  

Ahora veamos los años de las fechas. Evidentemente los años son correctos. Para la columna Fin_de_Viaje los valores son los mismos.

  
  
--SQLObtenerAnosInicioViaje.sql

SELECT DATE_PART('year', Inicio_del_Viaje) AS Ano_de_Inicio
FROM Registros_MiBici
GROUP BY DATE_PART('year', Inicio_del_Viaje)
ORDER BY DATE_PART('year', Inicio_del_Viaje);
  

  
  
--AnosInicioViaje.csv

Ano_de_inicio
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
  

Enseguida revisemos la columna Origen_Id. Para el caso de la columna Destino_Id los valores son los mismos. Los 384 registros corresponden a las 384 estaciones que hay o ha habido en la ciudad. Ahora ya podemos comenzar a obtener información sobre estos once años de MiBici y contar su historia.

  
  
--SQLObtenerIdsOrigen.sql

SELECT DISTINCT Origen_Id
FROM Registros_MiBici
ORDER BY Origen_Id;
  

  
  
--IdsOrigen.csv

Origen_id
2
3
4
5
6
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
246
247
248
249
250
251
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
400

  

Los datos hablan

Comencemos por ver cómo ha sido la evolución de la cantidad de viajes a lo largo de los años. Para ello, agruparemos los registros por el año de la fecha de inicio del viaje.

  
  
--SQLViajesPorAno.sql

SELECT DATE_PART('year', Inicio_del_Viaje) AS Ano_del_Viaje, COUNT(*) AS Viajes_Totales
FROM Registros_MiBici
GROUP BY DATE_PART('year', Inicio_del_Viaje)
ORDER BY DATE_PART('year', Inicio_del_Viaje);
  

El resultado se muestra en la Tabla 1. La Fig. 1 muestra la evolución. Se observa una tendencia al alza entre los años 2014-2019, que se interrumpe con la pandemia en 2020 y retoma el crecimiento a partir del 2021.

Tabla 1. Viajes totales realizados por año

Año del viaje Viajes por año
2014 23967
2015 469871
2016 941918
2017 2516534
2018 3403485
2019 4636652
2020 2866072
2021 3184410
2022 4202942
2023 4292971
2024 4645921
2025 3396220

Fig. 1. Viajes totales por año

Desglosemos la cantidad de viajes por mes. En la Fig. 2 se muestra la evolución, y en la Tabla 2 los resultados de la consulta, que es similar a la de de los viajes por año pero cambiando year por month.

Fig. 2. Viajes totales realizados por mes

Tabla 2. Viajes totales realizados por mes

Mes del viaje Viajes por mes
Enero 2877881
Febrero 2895352
Marzo 3117299
Abril 2710638
Mayo 2995991
Junio 2818684
Julio 2825921
Agosto 3027858
Septiembre 3068417
Octubre 2946752
Noviembre 2825984
Diciembre 2470186

Del uso podemos observar ciertas tendencias. De entrada, los meses con más viajes son marzo y septiembre, y los que menos uso registran son abril y diciembre. Naturalmente, estos últimos corresponden con las vacaciones de Semana Santa/Pascua y Navidad. Una observación interesante es que desde septiembre hasta diciembre hay una tendencia la baja, que queda por explicar. Probablemente se deba a que el año va en camino a cerrarse, y por ello la cantidad de personas que usan MiBici (probablemente por razones de trabajo) es menor.

Veamos la tendencia por mes desde el diciembre de 2014 hasta septiembre de 2025. Ejecutando el siguiente script, los resultados aparecen en la Fig. 3.


  
--SQLObtenerViajesDesglosadosPorMes.sql

SELECT DATE_TRUNC('month', Inicio_del_Viaje) AS Mes_del_Viaje, COUNT(*) AS Viajes_Totales
FROM Registros_MiBici
GROUP BY DATE_TRUNC('month', Inicio_del_Viaje)
ORDER BY Mes_del_Viaje;
  

Fig. 3. Viajes realizados por mes

De entrada, se observa que hay una tendencia ligeramente al alza desde el inicio (diciembre de 2014) y agosto de 2016. Entre julio y noviembre de 2016 hay un incremento mayor, y durante 2017 y 2018 hay caídas en los meses de abril y diciembre. En enero de 2019 hay un incremento sustancial, y se mantiene la tendencia errática hasta principios de 2020, cuando hay un descenso abrupto entre febrero y mayo, debido evidentemente a la pandemia. Después de esa caída en el primer semestre de 2020 comienza una recuperación, y a partir de marzo de 2022 parece estabilizarse el comportamiento, con mínimos de 330,000 y máximos de 400,000 viajes al mes.

Ahora que tenemos un panorama general de la evolución del uso de MiBici desde su implementación hasta septiembre de 2025, hagamos un análisis por variables demográficas. Comenzando por género, podemos consultar la cantidad de hombres, mujeres y géneros no especificados. Los resultados se muestran en la Fig. 4.


  
--SQLObtenerRegistrosPorGenero.sql

SELECT Genero, COUNT(*) AS Cantidad_viajes
FROM Registros_MiBici
GROUP BY Genero;
  

Fig. 4. Cantidad de viajes totales por género

Vemos que los viajes han sido realizados aproximadamente en un 26 % por mujeres y un 74 % por hombres, aunado a un porcentaje pequeño de registros con valor NULL. ¿Pero siempre se ha mantenido esta distribución? Obtengamos la evolución de los registros por género y por año. En la siguiente tabla se muestran los resultados y en la Fig. 5 la evolución.


  
--SQLObtenerViajesPorGeneroPorAno.sql

SELECT EXTRACT(YEAR FROM Inicio_del_Viaje) AS Ano_del_Viaje,
       ROUND(100 * (COUNT(*) FILTER (WHERE Genero = 'F')::NUMERIC / COUNT(*)::NUMERIC), 1) AS Viajes_mujeres_respecto_total,
       ROUND(100 * (COUNT(*) FILTER (WHERE Genero = 'M')::NUMERIC / COUNT(*)::NUMERIC), 1) AS Viajes_hombres_respecto_total,
       ROUND(100 * (COUNT(*) FILTER (WHERE Genero IS NULL)::NUMERIC / COUNT(*)::NUMERIC), 1) AS Viajes_indefinidos_respecto_total,
       COUNT(*) AS Viajes_totales_por_ano
FROM Registros_MiBici
GROUP BY Ano_del_Viaje
ORDER BY Ano_del_viaje;
  

Fig. 5. Porcentaje de viajes por sexo en cada año

En el 2014 (sólo para el mes de diciembre) la paridad era de poco más de 80 % de los viajes realizados por hombres y poco menos de 20 % realizados por mujeres. No obstante, en lo que va de 2025 la proporción es de aproximadamente 76 % para los primeros y 25 % para las segundas. En cuanto a los indefinidos, el porcentaje se ha mantenido relativamente constante, de modo que casi no supera el 2 %. Dado que la tendencia se ha mantenido estable, es de esperarse que en los años siguientes sea muy similar, a lo mucho de 74 % para el sexo masculino y 27 % para el femenino.

Ahora veamos la distribución por año de nacimiento de los viajes realizados por usuarios. Tenemos los resultados siguientes. En la Fig. 6 se muestra la distribución de los valores.


  
--SQLObtenerViajesPorAnoNacimiento.sql

SELECT Ano_de_nacimiento, COUNT(*) AS Viajes_por_ano_nacimiento
FROM Registros_MiBici
GROUP BY Ano_de_nacimiento
ORDER BY Ano_de_nacimiento;
  

Fig. 6. Viajes totales por año de nacimiento

De entrada se observa que el primer año de nacimiento registrado tiene un valor mayor que los que le siguen. Evidentemente se trata de un error en los datos, aunado a que son parte de los valores que nosotros establecimos para los años de nacimiento que tenían valores nulos. Enseguida se observa que hay una tendencia casi constante entre 1950 y 1968 y un pico en 1970 que sobresale en la tendencia al alza. La explicación probablemente sea porque la fecha inicial en sistemas UNIX, el valor de la época, es el 1 de enero de 1970 a las 00:00:00 UTC, es decir, a media noche. Es posible que al agregar los registros de viaje y no haber proporcionado una fecha de nacimiento del usuario, el sistema tomara el valor de la época por defecto, insertando así el año de 1970 como fecha de nacimiento. Aunado a eso, se observa un pico menos pronunciado entre 1975 y 1990 (ver la Fig. 7). Mirándolo más detenidamente se observa que en efecto el pico ocurrió en 1982 y la caída fue en 1983. La explicación probablemente sea la famosa crisis en torno al control generalizado de cambios y a la estatización de la banca en México, ambas medidas anunciadas en el sexto informe de gobierno en septiembre de 1982 por el presidente de entonces, José López Portillo (1976-1982). 1 Aquella crisis, ya lejana el la memoria de las generaciones que la vivieron, probablemente causó un descenso en la natalidad y en consecuencia una reducción en las personas que nacieron en ese año. La verificación de esta hipótesis es tarea de la demografía y la sociología, y queda fuera del alcance de este artículo.

Fig. 7. Viajes totales por año de nacimiento (1975-1990)

Finalmente, en la distribución general de la Fig. 6 se observa que el máximo se alcanza aproximadamente en el año 1994, es decir, las personas que tienen hoy 30-31 años. Parece ser una distribución de Poisson, dado que está sesgada hacia la derecha. Pero, ¿siempre han sido ese rango de edades el que ha concentrado la mayoría de los viajes? Para determinarlo, obtendremos el promedio de las cinco edades que más viajes han registrado, desde 2014 hasta 2025. En la Fig. 8 se muestra la evolución del promedio de las cinco edades con más viajes registrados.


  
--SQLObtenerViajesPorAnoPorEdad.sql

SELECT
  Ano_del_Viaje,
  --Ano_del_Viaje::INTEGER - Ano_de_nacimiento AS Edad,
  ROUND(AVG(Ano_del_Viaje - Ano_de_nacimiento) OVER (PARTITION BY Ano_del_viaje ORDER BY COUNT(*) DESC), 1) AS Edad_Promedio
  --Viajes_totales,
  --Posicion
FROM 
  (SELECT
    EXTRACT(YEAR FROM Inicio_del_Viaje) AS Ano_del_Viaje,
    Ano_de_nacimiento,    
    COUNT(*) AS Viajes_totales,
    ROW_NUMBER() OVER (PARTITION BY EXTRACT(YEAR FROM Inicio_del_Viaje) ORDER BY COUNT(*) DESC) AS Posicion
  FROM Registros_MiBici
  GROUP BY EXTRACT(YEAR FROM Inicio_del_Viaje), Ano_de_nacimiento
  ORDER BY Viajes_totales, COUNT(*) DESC
  )
WHERE Posicion <= 5
GROUP BY Ano_del_Viaje, Ano_de_nacimiento, Viajes_totales, Posicion
ORDER BY Ano_del_viaje, Posicion;
  

Fig. 8. Promedio de las cinco edades con más viajes en cada año

Ahora veamos las duraciones de los viajes. De entrada observamos que los viajes que duran entre uno y dos minutos son menores que los que duran menos de un minuto y los que duran entre dos y tres. Además, se percibe que hay un ligero aumento en los viajes que duran 60 o más de 60 minutos respecto a la tendencia casi constante en ese rango de duración. En la mayoría de los casos, estas duraciones no representan viajes reales, dado que si se consulta particularmente hay registros que indican que el viaje duró meses. En la Fig. 9 se muestra la distribución de manera gráfica. Se observa que es una distribución de Poisson en la que la mayor frecuencia de duración de los viajes es entre 2 y 12 minutos. Más adelante obtendremos la correlación para los viajes con duración entre 5 y 30 minutos, que corroborarán los datos visuales de la gráfica.


  
--SQLObtenerViajesPorDuracion.sql

SELECT
  60::INTEGER AS Duracion_del_viaje,
  COUNT(*) AS Numero_de_viajes
FROM Registros_MiBici
WHERE AGE(Fin_del_viaje, Inicio_del_viaje) >= INTERVAL '60 minutes'
GROUP BY Duracion_del_viaje
UNION
SELECT
  (EXTRACT(EPOCH FROM DATE_TRUNC('MINUTE', AGE(Fin_del_viaje, Inicio_del_viaje))) / 60)::INTEGER AS Duracion_del_viaje,
  COUNT(*) AS Numero_de_viajes
FROM Registros_MiBici
WHERE AGE(Fin_del_viaje, Inicio_del_viaje) < INTERVAL '60 minutes'
GROUP BY Duracion_del_viaje
ORDER BY Duracion_del_viaje;
  

Fig. 9. Viajes por duración

Pasemos a la distribución de la hora de inicio de los viajes. En la Fig. 10 aparecen los resultados. De entrada se observa que el grueso de los viajes está entre las 8 de la mañana y las 8 de la noche. La hora con más viajes es a las 6 de la tarde, seguido por las 7 de la noche y las 8 de la mañana. De las 6 de la tarde en adelante se observa una tendencia a la baja. El máximo de las 18 horas probablemente se explique por la salida de los trabajos de las personas, y el máximo local a las 8 de la mañana por la entrada a los mismos.


  
--SQLObtenerViajesPorHoraInicio.sql

SELECT EXTRACT(HOUR FROM Inicio_del_viaje) AS Hora_inicio,
       COUNT(*) AS Viajes_por_hora_del_dia
FROM Registros_MiBici
GROUP BY Hora_Inicio
ORDER BY Hora_Inicio;
  

Fig. 10. Viajes por hora de inicio

Ahora veamos cuáles estaciones tienen más viajes de origen y cuáles tienen más viajes de destino. Los resultados se muestran en la Fig. 11. Limitaremos los resultados a 30 estaciones con más viajes. Se observa que la estación con Id 51 tiene aproximadamente 744,000 viajes de origen, es decir, que de esa estación se iniciaron 744,000 viajes. Si revisamos el diccionario (nomenclatura_2025_09.csv) observamos que es la estación GDL-049, que corresponde a la de López Cotilla en el Parque de la Revolución. En efecto, al verificar visualmente el lugar se observa que esa estación tiene dos hileras de bicicletas.


  
--SQLObtenerEstacionesConMasViajesOrigen.sql

SELECT Origen_Id,
       COUNT(*) AS Viajes_inicio_por_estacion
FROM Registros_MiBici
GROUP BY Origen_Id
ORDER BY Viajes_inicio_por_estacion DESC
LIMIT 30;
  

Fig. 11. Estaciones con mayor cantidad de inicio de viajes

Para obtener las estaciones con más viajes de destino, es decir, donde se terminaron más viajes, obtenemos los resultados de la Fig. 12. Nuevamente, la estación con Id 51 es la que tiene más viajes de destino, aproximadamente 1,000,000.


  
--SQLObtenerEstacionesConMasViajesDestino.sql

SELECT Destino_Id,
       COUNT(*) AS Viajes_termino_por_estacion
FROM Registros_MiBici
GROUP BY Destino_Id
ORDER BY Viajes_destino_por_estacion DESC
LIMIT 30;
  

Fig. 12. Estaciones con mayor cantidad de término de viajes

¿Qué tan concentrados están los inicios de viaje en las estaciones que tienen más registros? Para ello obtendremos las estaciones con más viajes de inicio y el porcentaje acumulado que la estación y las estaciones con más viajes representan respecto al total. En la Fig. 13 se muestra el valor. Se observa que las 15 estaciones con más viajes (aproximadamente 3.8 % del total de estaciones) cuentan con 20 % del total de viajes, y las 96 estaciones (25 % del total) concentran aproximadamente 62 % del total de viajes. Evidentemente hay una concentración elevada.


  
--SQLObtenerViajesAcumuladosPorEstaciones.sql

SELECT --Destino_Id,
     ROW_NUMBER() OVER () AS Posicion_estacion,
     COUNT(*) AS Viajes_destino_por_estacion,
     SUM(COUNT(*)) OVER (ORDER BY COUNT(*) DESC) AS Acumulado,
     SUM(COUNT(*)) OVER () AS Total,
     ROUND(100 * (SUM(COUNT(*)) OVER (ORDER BY COUNT(*) DESC)::NUMERIC / SUM(COUNT(*)) OVER ()::NUMERIC), 2) AS Porcentaje_total_respecto_acumulado
FROM Registros_MiBici
GROUP BY Destino_Id
ORDER BY Viajes_destino_por_estacion DESC
LIMIT 96;
  

Fig. 13. Acumulado de viajes totales para las estaciones con más viajes

¿Qué correlaciones hay? Hasta este momento hemos observado únicamente las distribuciones de las variables unas frente a otras (cantidad de viajes realizados por hora, relación hombres/mujeres a través de los años, etc.). Ahora es momento de obtener algunas correlaciones entre las variables (que nos ayudarán a verificar los datos visuales). De entrada agruparemos los registros por la edad de la persona al momento de realizar el viaje; obtendremos también la duración promedio del viaje para todos los viajes realizados por personas con una edad determinada, y la cantidad de dichos viajes. Con esas tres columnas numéricas obtendremos las correlaciones. Obtenemos un coeficiente de correlación r ≈ 0,58 para las variables de edad y duración promedio; de r ≈ −0,25 para la edad y el número de viajes, y r ≈ −0,18 para el número de viajes y la duración promedio del mismo. En el primer caso el valor de aproximadamente 0,38 indica que en efecto existe una correlación, aunque moderada, entre la edad de la persona y el número de viajes realizados. Esto se explica porque en efecto, dado que el rango de edades con más viajes está entre 25 y 35 años, conforme la edad aumenta desde aproximadamente los 16 años, la cantidad de viajes aumenta (véase la Fig. 6).


  
--SQLObtenerCorrelaciones.sql

WITH Columnas AS (
  SELECT EXTRACT(YEAR FROM AGE(Fin_del_viaje, MAKE_DATE(Ano_de_nacimiento, 1, 1))) AS Edad,
         EXTRACT(EPOCH FROM DATE_TRUNC('SECOND', AVG(AGE(Fin_del_viaje, Inicio_del_viaje))))/60 AS Duracion_promedio_del_viaje,
         COUNT(*) AS Numero_viajes
  FROM Registros_MiBici
  --WHERE EXTRACT(EPOCH FROM DATE_TRUNC('SECOND', AGE(Fin_del_viaje, Inicio_del_viaje)))/60 >= 7 AND
  --EXTRACT(EPOCH FROM DATE_TRUNC('SECOND', AGE(Fin_del_viaje, Inicio_del_viaje)))/60 <= 30
  GROUP BY Edad
)

SELECT ROUND(CORR(Edad, Duracion_promedio_del_viaje)::NUMERIC, 2) AS Edad_Duracion_promedio,
       ROUND(CORR(Edad, Numero_Viajes)::NUMERIC, 2) AS Edad_Numero_Viajes,
       ROUND(CORR(Numero_Viajes, Duracion_promedio_del_viaje)::NUMERIC, 2) AS Numero_Viajes_Duracion_promedio
FROM Columnas;
  

Si las correlaciones las tomamos para los viajes con duración entre 5 y 30 minutos, deberíamos obtener una correlación negativa, dado que en la Fig. 9 se observa que los viajes alcanzan una cantidad máxima alrededor de los 7 minutos, y a partir de esa duración, conforme aumenta la duración del viaje, comienza a descender la cantidad de los mismos. Efectivamente, la correlación entre la duración del viaje y la cantidad de viajes con esa duración es r ≈ -0.81, lo que indica una correlación negativa fuerte: entre más aumenta la duración del viaje, menor es la cantidad de viajes y viceversa.


  
--SQLObtenerCorrelacionDuracionViajeNumeroViajes.sql

WITH Duracion_viaje_numero_viajes AS (
  SELECT  
    EXTRACT(EPOCH FROM DATE_TRUNC('MINUTE', '01:00:00'::INTERVAL))/60 AS Duracion_del_viaje,
    COUNT(*) AS Numero_de_viajes
  FROM Registros_MiBici
  WHERE AGE(Fin_del_viaje, Inicio_del_viaje) >= INTERVAL '60 minutes'
  GROUP BY Duracion_del_viaje
  UNION
  SELECT
    EXTRACT(EPOCH FROM DATE_TRUNC('MINUTE', AGE(Fin_del_viaje, Inicio_del_viaje))) / 60 AS Duracion_del_viaje,    
    COUNT(*) AS Numero_de_viajes
  FROM Registros_MiBici
  WHERE AGE(Fin_del_viaje, Inicio_del_viaje) > INTERVAL '5 minutes' AND
  AGE(Fin_del_viaje, Inicio_del_viaje) < INTERVAL '30 minutes'
  GROUP BY Duracion_del_viaje
  ORDER BY Duracion_del_viaje
)  

SELECT ROUND(CORR(Duracion_del_viaje, Numero_de_viajes)::NUMERIC, 2) AS Duracion_viaje_numero_viajes_r,
       ROUND(REGR_SLOPE(Numero_de_viajes, Duracion_del_viaje)::NUMERIC, 2) AS Pendiente,
       ROUND(REGR_INTERCEPT(Numero_de_viajes, Duracion_del_viaje)::NUMERIC, 2) AS Intercepcion_y,
       ROUND(REGR_R2(Numero_de_viajes, Duracion_del_viaje)::NUMERIC, 3) AS R_Cuadrada
FROM Duracion_viaje_numero_viajes;
  


  
--SQLObtenerValoresParaCorrelaciones.sql

SELECT EXTRACT(YEAR FROM AGE(Fin_del_viaje, MAKE_DATE(Ano_de_nacimiento, 1, 1))) AS Edad,
       ROUND(EXTRACT(EPOCH FROM DATE_TRUNC('SECOND', AVG(AGE(Fin_del_viaje, Inicio_del_viaje))))/60, 1) AS Duracion_promedio_del_viaje,
       COUNT(*) AS Numero_viajes
FROM Registros_MiBici
WHERE EXTRACT(EPOCH FROM DATE_TRUNC('SECOND', AGE(Fin_del_viaje, Inicio_del_viaje)))/60 >= 6 AND
EXTRACT(EPOCH FROM DATE_TRUNC('SECOND', AGE(Fin_del_viaje, Inicio_del_viaje)))/60 <= 30
GROUP BY Edad
  


  
--CSVCorrelaciones.csv

Edad Duracion_promedio_del_viaje Numero_viajes
0 12.5 334
1 11.7 656
2 12.0 288
13 16.4 13
14 8.9 17
15 17.7 21
16 13.1 8260
17 12.6 56021
18 12.6 141353
19 12.5 304439
20 12.6 469403
21 12.6 630268
22 12.7 789680
23 12.8 1004149
24 12.9 1216323
25 12.9 1346396
26 13.0 1410115
27 12.9 1392620
28 13.0 1363294
29 13.0 1270997
30 13.0 1154334
31 13.0 1061510
32 13.0 974472
33 12.9 875420
34 13.0 779685
35 13.1 696772
36 13.0 610591
37 13.1 559789
38 13.0 512148
39 13.1 495258
40 13.1 449421
41 13.1 404681
42 13.1 379042
43 13.1 348938
44 13.2 316351
45 13.2 295565
46 13.2 267227
47 13.1 257343
48 13.2 251003
49 13.3 234259
50 13.1 223145
51 13.1 216001
52 13.2 217890
53 13.4 216172
54 13.5 213828
55 13.4 194884
56 13.2 149558
57 13.5 135834
58 13.7 121021
59 13.8 107970
60 13.4 92681
61 13.1 74807
62 13.1 73622
63 13.2 62238
64 13.3 56855
65 13.1 43095
66 12.9 32533
67 13.1 30009
68 13.7 30800
69 13.5 28747
70 13.8 20209
71 13.8 12054
72 13.4 10248
73 13.0 8769
74 13.4 7011
75 13.8 6773
76 13.3 6110
77 13.1 3821
78 12.3 1587
79 12.9 2097
80 13.2 1809
81 13.9 986
82 12.6 918
83 12.7 1905
84 13.8 789
85 13.8 436
86 12.6 274
87 16.0 12
88 7.0 11
89 15.1 51
90 12.8 9
94 11.2 13
97 15.2 356
98 14.3 70
99 15.4 197
100 12.6 667
101 12.6 673
102 12.1 841
103 11.7 243
104 14.6 182
105 12.1 14
1824 17.6 141
2013 18.6 151
2014 18.1 2321
2015 17.9 4078
2016 18.3 8190
2017 18.3 7493
2018 18.3 7465
2019 18.3 2255
2020 13.9 3426
2021 13.0 11279
2022 13.1 4825
2023 12.7 1674
2024 13.0 567
  


  
#PythonCorrelaciones.py

import pandas as pd
import matplotlib.pyplot as plt

data = pd.read_csv('Registros.csv')
matriz_correlacion = data.corr().round(2)

print(matriz_correlacion)
  

La matriz de correlaciones obtenida en Python se muestra en la Tabla 3. Efectivamente, el valor de 0.58 es el que habíamos obtenido entre la edad y la duración promedio del viaje.

Tabla 3. Matriz de correlaciones

Edad Duración promedio del viaje Número de viajes
Edad 1.00 0.58 -0.18
Duración promedio del viaje 0.58 1.00 -0.18
Número de viajes -0.25 -0.18 1.00

Ahora obtendremos los valores de la pendiente y la intersección con el eje y para una regresión lineal.


  
--SQLObtenerCorrelacionNumeroViajesDuracionViaje.sql

WITH Duracion_viaje_numero_viajes AS (
  SELECT  
    EXTRACT(EPOCH FROM DATE_TRUNC('MINUTE', '01:00:00'::INTERVAL))/60 AS Duracion_del_viaje,
    COUNT(*) AS Numero_de_viajes
  FROM Registros_MiBici
  WHERE AGE(Fin_del_viaje, Inicio_del_viaje) >= INTERVAL '60 minutes'
  GROUP BY Duracion_del_viaje
  UNION
  SELECT
    EXTRACT(EPOCH FROM DATE_TRUNC('MINUTE', AGE(Fin_del_viaje, Inicio_del_viaje))) / 60 AS Duracion_del_viaje,    
    COUNT(*) AS Numero_de_viajes
  FROM Registros_MiBici
  WHERE AGE(Fin_del_viaje, Inicio_del_viaje) > INTERVAL '5 minutes' AND
  AGE(Fin_del_viaje, Inicio_del_viaje) < INTERVAL '30 minutes'
  GROUP BY Duracion_del_viaje
  ORDER BY Duracion_del_viaje
)  

SELECT ROUND(CORR(Duracion_del_viaje, Numero_de_viajes)::NUMERIC, 2) AS Duracion_viaje_numero_viajes_r,
       ROUND(REGR_SLOPE(Numero_de_viajes, Duracion_del_viaje)::NUMERIC, 2) AS Pendiente,
       ROUND(REGR_INTERCEPT(Numero_de_viajes, Duracion_del_viaje)::NUMERIC, 2) AS Intercepcion_y,
       ROUND(REGR_R2(Numero_de_viajes, Duracion_del_viaje)::NUMERIC, 3) AS R_Cuadrada
FROM Duracion_viaje_numero_viajes;

  

Tenemos que la ecuación de la recta tiene la forma y = -58986.77x + 2145804.96. En la ecuación de la recta anterior, x es la duración del viaje (en minutos) y y es la cantidad de viajes aproximada con esa duración, para el intervalo especificado. Nuevamente, obtenemos el valor de r ≈ −0,81 que habíamos obtenido anteriormente, dado que lo tomamos para los viajes entre 5 y 30 minutos de duración. Asimismo, el valor de r2 es aproximadamente 0.652, lo que significa que la variable independiente explica alrededor de 62.5 % de la variación en la variable dependiente.

Conclusión

Ahora sí podemos contar una historia general del uso de MiBici en la capital de Jalisco. En los poco más de 11 años los tapatíos han realizado aproximadamente 34 millones de viajes; en el primer año, 2014, sólo se realizaron aproximadamente 23 mil, dado que se implementó en diciembre, pero en 2024 hubo aproximadamente 4.6 millones de viajes. La cantidad de viajes ha seguido casi siempre una tendencia al alza, salvo en 2020 a raíz de la pandemia, donde tuvo una caída de aproximadamente 40 % respecto al 2019. 2022 y 2023 tuvieron cantidades similares de viajes, pero en 2024 hubo un mayor incremento.

Respecto a los viajes realizados por mes, marzo y septiembre son los que más han registrado viajes, mientras que abril y diciembre es cuando las personas usan menos el servicio. Aproximadamente desde 2022 el uso de MiBici por mes sigue el mismo patrón, con usos máximos en marzo y usos mínimos en diciembre.

En cuanto a los viajes realizados por género, en términos globales aproximadamente el 73 % de los mismos los han realizado hombres y el 27 % mujeres, aunque la tendencia desde el inicio ha sido que incremente la participación femenina y se reduzca la masculina, aunque no parece que vaya a modificarse esa relación.

Por otro lado, atendiendo al aspecto generacional, las personas nacidas entre 1985 y 2000 son las que más han usado el servicio, confirmado así la percepción popular de que es una actividad principalmente de jóvenes, siendo las personas nacidas en 1994 las que están en primer lugar. La edad promedio de las cinco edades que más viaje han realizado está entre 25 y 28 años.

Respecto a la duración de los viajes, generalmente estos duran entre 2 y 15 minutos, con las duraciones de 5 y 6 minutos como las más frecuentes. Esto sugiere que las personas en efecto usan MiBici como medio para desplazamientos breves en la ciudad, en vez de viajes de 25 minutos o más.

Las horas del día que más viajes registran son las 6 de la tarde, aunado a las 7 de la noche y las 8 de la mañana, que son generalmente las entradas y salidas de las escuelas y trabajos.

Agrupando por las estaciones desde donde se inician la mayor cantidad de viajes, se tiene que la estación GDL-049, la de López Cotilla en el Parque de la Revolución es la que registra más viajes de inicio y también de de destino. La segunda estación con más viajes de inicio es la GDL-009, en el cruce de Calz. Federalismo y Joaquín Angulo. En segundo lugar de las de destino está la estación GDL-048, a un costado de la antigua rectoría de Guadalajara, sobre la calle Constancio Hernández en su cruce con avenida Juárez, .

Se tiene que el 25 % del total de estaciones (96 de 384) concentran aproximadamente 62 % del total de los viajes, aunado a que algunas de esas 384 estaciones ya no están en servicio y otras se encuentran en el centro de Zapopan.

Al obtener correlaciones, encontramos que hay una correlación moderada (r ≈ 0,58) para la edad frente a la duración promedio del viaje, debida a que la mayoría de los viajes tiene una duración menor a 30 minutos, y conforme la edad aumenta de 16 a 30 años aproximadamente, también aumenta la duración del viaje. No obstante, si se obtiene la correlación entre la duración de los viajes y la cantidad de estos, se obtiene un valor de aproximadamente r ≈ −0,81, que indica una relación estrecha en sentido inverso, esto es, si aumenta la duración de los viajes, tiende a reducirse la cantidad de estos.

Esta es la historia que hemos encontrado en este pequeño conjunto de datos de un servicio en la Perla tapatía. Dadas las tendencias observadas a lo largo de los años, es poco probable que cambien, a menos que existan eventos inesperados, como iniciativas amplias para la adopción de la bicicleta como medio de transporte o ampliaciones sustanciales de la red de MiBici.

  1. La inflación en esos años llegó casi al 100 % anual, el crecimiento era cercano a cero, la deuda externa era aproximadamente de 85 mil millones de dólares (con el 20 % contratado a corto plazo) Véase Luis Medina Peña, Hacia el nuevo Estado. México, 1920-2000, Ciudad de México, Fondo de Cultura Económica, 3a ed., 2010, pp. 198-203.