Este caso de estudio gira en torno a Cyclistic, una empresa de bicicletas compartidas que ha crecido mucho desde su lanzamiento en 2016 en Chicago. Con una flota de 5,824 bicicletas y 692 estaciones, Cyclistic ofrece planes flexibles que atraen a distintos tipos de usuarios: pases individuales o de un día para usuarios ocasionales, y membresías anuales para miembros de Cyclistic. Esa flexibilidad ha ampliado su base de clientes, pero el análisis financiero muestra que las membresías anuales generan más ingresos. Por eso, la directora de marketing, Lily Moreno, quiere diseñar estrategias específicas para convertir a los usuarios ocasionales en miembros anuales. Para lograrlo, es clave analizar las diferencias de comportamiento entre ambos grupos y explorar cómo los medios digitales pueden influir en esa conversión. Este proyecto se basa en el análisis de datos históricos de viajes para identificar patrones y guiar la toma de decisiones de marketing.
Este proyecto capstone forma parte del programa de certificación de Google Data Analytics.
Verificar Certificación
El reto principal para Lily Moreno, directora de marketing de Cyclistic, y su equipo de análisis, es diseñar una estrategia efectiva que motive a los usuarios ocasionales a convertirse en miembros anuales.
Esta conversión es clave para el crecimiento sostenido de la empresa, ya que los miembros anuales representan una fuente de ingresos más estable y rentable. Para construir una estrategia de marketing bien fundamentada,
es necesario entender a fondo el comportamiento de ambos tipos de usuarios y las motivaciones que podrían impulsar un cambio de categoría. En ese sentido, el proyecto se guía por las siguientes preguntas clave:
A través del análisis histórico, identificar patrones de uso entre miembros anuales y usuarios ocasionales para diseñar estrategias de marketing digital que conviertan a los usuarios ocasionales en miembros anuales.
Estas fueron las tecnologías usadas para desarrollar el proyecto:
Para este análisis se usan los datos históricos de viajes en bicicleta de Cyclistic correspondientes a los últimos 12 meses del año 2024. Estos datos fueron proporcionados por Motivate International Inc. y están disponibles en este enlace bajo esta licencia. Por privacidad, se eliminó toda información de usuarios que permitiera identificarlos. Los datos se almacenan en archivos comprimidos en formato .CSV.
-- Monthly Divvy trip data files (2024)
01) 2024-02_divvy_trip-data.csv
02) 2024-03_divvy_trip-data.csv
03) 2024-04_divvy_trip-data.csv
04) 2024-05_divvy_trip-data.csv
05) 2024-06_divvy_trip-data.csv
06) 2024-07_divvy_trip-data.csv
07) 2024-08_divvy_trip-data.csv
08) 2024-09_divvy_trip-data.csv
09) 2024-10_divvy_trip-data.csv
10) 2024-11_divvy_trip-data.csv
11) 2024-12_divvy_trip-data.csv
12) 2024-01_divvy_trip-data.csv
Se creó una base de datos en PostgreSQL para almacenar los datos. Después de descargar y descomprimir los archivos .zip, los datos se cargaron en el esquema MONTHLY_TRIPS, creado previamente en la base de datos. En total se generaron 12 tablas, una por cada archivo correspondiente a los meses del año, con la siguiente configuración:
CREATE TABLE MONTHLY_TRIPS.tripdata_202401(
ride_id VARCHAR(50) PRIMARY KEY,
rideable_type VARCHAR(50),
started_at TIMESTAMP,
ended_at TIMESTAMP,
start_station_name VARCHAR(100),
start_station_id VARCHAR(50),
end_station_name VARCHAR(100),
end_station_id VARCHAR(50),
start_lat NUMERIC,
start_lng NUMERIC,
end_lat NUMERIC,
end_lng NUMERIC,
member_casual VARCHAR(10)
);
Una vez almacenadas las tablas en la base de datos, se configuró un entorno virtual en Python para llevar a cabo el procesamiento de los datos. La estructura del proyecto se organizó así:
venv/ # Python virtual environment
config.py # Configuration file with database connection variables
database.py # Functions to connect to and query the database
requirements.txt # List of project dependencies
test_connection.py # Script to verify the database connection
trip_analysis.ipynb # Exploratory trip analysis notebook
Usando el notebook trip_analysis.ipynb, se estableció la conexión a las tablas almacenadas en postgres y se examinó el contenido de cada una de ellas.
# Define the SQL query to fetch data for January 2024
query = "select * from MONTHLY_TRIPS.tripdata_202401;"
# get the data for January 2024
df_202401 = analyzer.fetch_data(query)
Esta tabla contiene la siguiente información:
Null counts per column (descending order):
end_station_name 20749
end_station_id 20749
start_station_id 19165
start_station_name 19165
end_lat 288
end_lng 288
started_at 0
rideable_type 0
ride_id 0
ended_at 0
start_lat 0
start_lng 0
member_casual 0
dtype: int64
Null percentage per column (descending order):
end_station_name 14.32
end_station_id 14.32
start_station_id 13.23
start_station_name 13.23
end_lat 0.20
end_lng 0.20
started_at 0.00
rideable_type 0.00
ride_id 0.00
ended_at 0.00
start_lat 0.00
start_lng 0.00
member_casual 0.00
dtype: float64
Global null value analysis:
- Total cells: 1,883,349
- Total null values: 80,404
- Global null percentage: 4.27%
Distribución de valores nulos
Dado que la proporción de datos faltantes supera el 10%, descartar esos registros habría implicado una pérdida de información importante. Por eso se decidió conservar las entradas y manejar los valores nulos asignándolos a una categoría específica: "Unknown".
def clean_data(self, df: pd.DataFrame) -> pd.DataFrame:
"""
Clean the DataFrame:
- Fill null values in station columns with 'Unknown'
- Remove rows without destination coordinates
Args:
df (pd.DataFrame): DataFrame
Returns:
pd.DataFrame: cleaned DataFrame
"""
stations_column = [
"start_station_name", "start_station_id",
"end_station_name", "end_station_id"
]
for col in stations_column :
df[col] = df[col].fillna("Unknown")
df = df.dropna(subset=["end_lat", "end_lng"])
return df
Como todos los archivos mostraban patrones de datos faltantes similares, se aplicó el mismo tratamiento de forma consistente en todo el dataset.
for month in range(1, 13):
variable_name = f"df_2024{mes:02}"
df = globals().get(variable_name )
if df is not None:
globals()[variable_name] = analyzer.clean_data(df)
Para apoyar el análisis de datos y descubrir patrones clave para el desarrollo de la estrategia de marketing, se realizaron los siguientes pasos: extracción del día, mes y año a partir de started_at, y cálculo de la duración del viaje.
# Functio to add new columns to the DataFrame
def add_time_columns(self, df: pd.DataFrame) -> pd.DataFrame:
"""
Add new columns to the DataFrame based on the 'started_at' column:
- 'hour': Hour of the trip
- 'day_of_week': Day of the week of the trip
- 'month': Month of the trip
- 'year': Year of the trip
Args:
df (pd.DataFrame): DataFrame with a 'started_at' column of datetime type.
Returns:
pd.DataFrame: DataFrame with new columns added.
"""
# get the trop duration in minutes
df['trip_duration'] = (df['ended_at'] - df['started_at']).dt.total_seconds() / 60 # Trip duration in minutes
# Get the day of the week for each trip
df['day_of_week'] = df['started_at'].dt.day_name()
# Get the month name for each trip
df['month'] = df['started_at'].dt.month
# get the year for each trip
df['year'] = df['started_at'].dt.year
return df
Esta función se aplicó de forma iterativa a las doce tablas con la siguiente implementación:
for i in range(1, 13):
df_name = f"df_2024{i:02}"
df = globals().get(df_name)
if df is not None:
globals()[df_name] = analyzer.add_time_columns(df)
Luego se extrajeron las variables relevantes y todos los datasets se guardaron como archivos .csv para su análisis posterior
# Define the columns to keep in the final DataFrame
columns_to_keep = [
'ride_id',
'rideable_type',
'start_station_name',
'start_station_id',
'end_station_name',
'end_station_id',
'start_lat',
'start_lng',
'end_lat',
'end_lng',
'member_casual',
'day_of_week',
'month',
'year',
'trip_duration'
]
# Store DataFrame in CSV files for each month
for mes in range(1, 13):
df_name = f'df_2024{mes:02}'
df = globals()[df_name] #get the DataFrame from global environment
df[columns_to_keep].to_csv(f'df_2024{mes:02}.csv', index=False)
Los archivos .csv procesados reemplazaron a los datasets originales y se reorganizaron en cuatro tablas trimestrales (Q1: ene-mar, Q2: abr-jun, Q3: jul-sep, Q4: oct-dic).
CREATE TABLE tripdata2024.q1_tripdata AS
SELECT
ride_id,
rideable_type,
start_station_name,
start_station_id,
end_station_name,
end_station_id,
start_lat,
start_lng,
end_lat,
end_lng,
member_casual,
day_of_week,
month,
year,
trip_duration AS ride_length,
'Q1' AS quarter
FROM tripdata2024.data202401
UNION DISTINCT
SELECT
ride_id,
rideable_type,
start_station_name,
start_station_id,
end_station_name,
end_station_id,
start_lat,
start_lng,
end_lat,
end_lng,
member_casual,
day_of_week,
month,
year,
trip_duration AS ride_length,
'Q1' AS quarter
FROM tripdata2024.data202402
UNION DISTINCT
SELECT
ride_id,
trip_duration AS ride_length,
rideable_type,
start_station_name,
start_station_id,
end_station_name,
end_station_id,
start_lat,
start_lng,
end_lat,
end_lng,
member_casual,
day_of_week,
month,
year,
'Q1' AS quarter
FROM tripdata2024.data202403;
La empresa reportó un total de 5.9 millones de usuarios, de los cuales 3.7 millones son miembros actuales y 2.1 millones son usuarios ocasionales.
Es decir, 63.34% son miembros anuales y 36.66% son usuarios ocasionales.
Se registró el número total de viajes por trimestre. Se encontró que la cantidad de viajes aumenta considerablemente en los trimestres de primavera y verano (Q2 y Q3) y disminuye en los trimestres de invierno (Q1 y Q4), una tendencia típica de los servicios de micromovilidad por factores estacionales. El tercer trimestre (julio-septiembre) fue el de mayor actividad, alcanzando un pico de:
Se registró el total de viajes por tipo de vehículo. El 50.9% de los usuarios tiende a usar bicicletas eléctricas, mientras que el 46.6% usó bicicletas clásicas, y el porcentaje restante eligió patinetas eléctricas.
Se analizó el total de viajes realizados en cada trimestre de 2024, segmentado por tipo de usuario (miembro y ocasional) y tipo de vehículo. Se encontró que el primer trimestre (Q1) mostró poca actividad. Los miembros prefirieron tanto bicicletas clásicas como eléctricas, con mayor proporción en las clásicas. En cambio, los usuarios ocasionales tuvieron menor participación en general, pero con un uso equilibrado entre bicicletas clásicas y eléctricas. Durante el segundo trimestre (Q2) se registró un aumento notable en el uso de todos los tipos de vehículo. Las bicicletas eléctricas tuvieron un repunte importante, liderando tanto en volumen de viajes como en preferencia de los usuarios. El tercer trimestre (Q3) marcó el uso más alto en todos los segmentos, con las bicicletas eléctricas como las más usadas. Además, se registraron patinetas eléctricas en ambos tipos de usuario, con mayor uso entre los ocasionales. En el cuarto y último trimestre (Q4) hubo una caída significativa en el volumen de viajes. Aun así, las bicicletas eléctricas se mantuvieron como el vehículo más usado tanto por miembros como por usuarios ocasionales.
El análisis del total de viajes por tipo de usuario a nivel mensual revela lo siguiente:
Se registró el total de viajes diarios, segmentado por tipo de usuario. Los domingos muestran una distribución equilibrada entre ambos grupos, con una ligera mayoría de viajes de miembros anuales (417,067) frente a usuarios ocasionales (368,682). Los lunes, martes y miércoles hay más actividad entre los miembros. En cambio, los jueves, viernes y sábados, la actividad de los miembros tiende a bajar mientras suben los viajes de usuarios ocasionales, reflejando un cambio en los patrones de uso según el día de la semana.
Usuarios miembros
Para los usuarios miembros, el miércoles destaca como el día con mayor volumen de viajes, sobre todo en bicicletas eléctricas (309,425) y clásicas (290,712). Aunque las patinetas eléctricas tienen un uso mucho menor en general, alcanzan un pico modesto el miércoles (9,923). Las bicicletas eléctricas superan de forma constante a las clásicas durante toda la semana. Los niveles más bajos de actividad entre los miembros ocurren los sábados y domingos, lo que indica un patrón de uso ligado principalmente a los traslados entre semana.
Usuarios ocasionales
Para los usuarios ocasionales, el sábado registra el mayor número de viajes, con un uso equilibrado entre bicicletas eléctricas (271,032) y clásicas (213,594). Aunque el uso de patinetas eléctricas es mucho menor, mantiene una presencia constante durante toda la semana. Las bicicletas eléctricas superan ligeramente a las clásicas todos los días. La menor actividad se observa los lunes, martes y miércoles, lo que sugiere que los usuarios ocasionales usan el servicio principalmente con fines recreativos los fines de semana y días no laborables.
A partir del análisis realizado, se identificaron los siguientes hallazgos clave:
Diseñar campañas dirigidas que resalten el valor de un uso frecuente, orientadas a que los usuarios ocasionales se conviertan en miembros anuales. Acciones sugeridas: