Este documento es mi primer proyecto para poner en práctica mis habilidades como Data Analyst. Los ejercicios fueron entregados por la plataforma de estudios Coursera.
Cyclistic es un servicio de renta ficticio de bicicletas públicas en Chicago que incluye 5.800 bicicletas y 600 estaciones. La directora de marketing cree que el éxito futuro de la empresa depende de incrementar la cantidad de membresías anuales. Para esto, necesitan conocer a los ciclistas ocasionales y cómo abordarlos publicitariamente.
¿Cómo lograr el éxito de un negocio de arriendo de bicicletas?
¿Cuál es la diferencia entre usuarios casuales y miembros?
¿Por qué los ciclistas casuales querrían ser miembros?
¿Cómo puede Cyclistic usar los medios digitales para incitar a los ciclistas casuales a convertirse en miembros?
if(!require(tidyverse)) install.packages("tidyverse")
## Loading required package: tidyverse
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.1 ✔ tibble 3.2.1
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.1
## ✔ purrr 1.0.4
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
if(!require(ggplot2)) install.packages("ggplot2")
library(tidyverse)
library(ggplot2)
library(skimr)
library(lubridate)
# Establecemos la carpeta que contiene los archivos
setwd("/Users/buger/Documents/Análisis_de_datos/Caso_Cyclistic")
# Obtener lista de archivos CSV
archivos <- list.files(pattern = "\\.csv$")
# Acá queremos verificar si las columnas de nuestros archivos son iguales
# Extraer nombres de columnas de cada archivo
columnas_por_archivo <- map(archivos, ~names(read_csv(.x, n_max = 0)))
# Crear un resumen
resumen_columnas <- tibble(
archivo = archivos,
columnas = map(columnas_por_archivo, ~paste(sort(.x), collapse = ", "))
)
# Mostrar diferencias entre columnas. Si indica nª 12, son iguales.
resumen_columnas %>%
count(columnas) %>%
arrange(desc(n))
## # A tibble: 1 × 2
## columnas n
## <list> <int>
## 1 <chr [1]> 12
# Unimos los archivos
# Una vez comprobado que son compatibles, unimos los archivos.
datos_combinados <- list.files(pattern = "\\d+-divvy-tripdata\\.csv$") %>%
map_dfr(~read_csv(.x, show_col_types = FALSE))
# Comprobamos nuestros datos
glimpse(datos_combinados)
## Rows: 5,779,568
## Columns: 13
## $ ride_id <chr> "743252713F32516B", "BE90D33D2240C614", "D47BBDDE7C…
## $ rideable_type <chr> "classic_bike", "electric_bike", "classic_bike", "c…
## $ started_at <dttm> 2024-04-22 19:08:21, 2024-04-11 06:19:24, 2024-04-…
## $ ended_at <dttm> 2024-04-22 19:12:56, 2024-04-11 06:22:21, 2024-04-…
## $ start_station_name <chr> "Aberdeen St & Jackson Blvd", "Aberdeen St & Jackso…
## $ start_station_id <chr> "13157", "13157", "TA1307000107", "13157", "TA13070…
## $ end_station_name <chr> "Desplaines St & Jackson Blvd", "Desplaines St & Ja…
## $ end_station_id <chr> "15539", "15539", "13249", "15539", "TA1308000029",…
## $ start_lat <dbl> 41.87773, 41.87772, 41.96167, 41.87773, 41.96161, 4…
## $ start_lng <dbl> -87.65479, -87.65496, -87.65464, -87.65479, -87.654…
## $ end_lat <dbl> 41.87812, 41.87812, 41.95606, 41.87812, 41.88683, 4…
## $ end_lng <dbl> -87.64395, -87.64395, -87.66884, -87.64395, -87.622…
## $ member_casual <chr> "member", "member", "member", "member", "member", "…
# Creamos una columna para indicar el día, mes y hora del viaje
datos_combinados <- datos_combinados %>%
mutate(
hour_of_day = hour(started_at),
day_of_week = wday(started_at, label = TRUE),
month_name = month(started_at, label = TRUE, abbr = FALSE))
# Agregamos una columna nueva con el tiempo de recorrido
datos_combinados <- datos_combinados %>%
mutate(ride_length = as.numeric(difftime(ended_at, started_at, units = "mins"))) %>%
mutate(ride_length = round(ride_length, 2))
# Eliminamos los datos que pertenescan a la estación que no es real "HQ QR"
# además de los que tienen tiempo de recorrido negativo
datos_combinados <- datos_combinados[!(datos_combinados$start_station_name == "HQ QR" | datos_combinados$ride_length < 0),]
# Eliminamos las columnas que no sirven
datos_combinados <- datos_combinados %>%
select(-c(start_lat, start_lng, end_lat, end_lng))
Ya que tenemos listos nuestros datos, comenzamos con la observación.
# Revisamos si se incluyeron las columnas
colnames(datos_combinados)
## [1] "ride_id" "rideable_type" "started_at"
## [4] "ended_at" "start_station_name" "start_station_id"
## [7] "end_station_name" "end_station_id" "member_casual"
## [10] "hour_of_day" "day_of_week" "month_name"
## [13] "ride_length"
glimpse(datos_combinados)
## Rows: 5,779,391
## Columns: 13
## $ ride_id <chr> "743252713F32516B", "BE90D33D2240C614", "D47BBDDE7C…
## $ rideable_type <chr> "classic_bike", "electric_bike", "classic_bike", "c…
## $ started_at <dttm> 2024-04-22 19:08:21, 2024-04-11 06:19:24, 2024-04-…
## $ ended_at <dttm> 2024-04-22 19:12:56, 2024-04-11 06:22:21, 2024-04-…
## $ start_station_name <chr> "Aberdeen St & Jackson Blvd", "Aberdeen St & Jackso…
## $ start_station_id <chr> "13157", "13157", "TA1307000107", "13157", "TA13070…
## $ end_station_name <chr> "Desplaines St & Jackson Blvd", "Desplaines St & Ja…
## $ end_station_id <chr> "15539", "15539", "13249", "15539", "TA1308000029",…
## $ member_casual <chr> "member", "member", "member", "member", "member", "…
## $ hour_of_day <int> 19, 6, 11, 18, 19, 16, 7, 18, 20, 15, 18, 17, 22, 1…
## $ day_of_week <ord> Mon, Thu, Sat, Thu, Fri, Wed, Wed, Thu, Tue, Sat, T…
## $ month_name <ord> April, April, April, April, April, April, April, Ap…
## $ ride_length <dbl> 4.58, 2.95, 16.30, 3.77, 37.37, 5.13, 16.90, 6.03, …
summary(datos_combinados$ride_length)
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## 0.0 5.7 10.0 18.3 17.9 1559.9 1091133
table(datos_combinados$day_of_week)
##
## Sun Mon Tue Wed Thu Fri Sat
## 619107 641757 641223 695859 661645 691107 737560
# Son 5,779,391 de viajes registrados.
# El promedio de tiempo de viajes es de 18 minutos.
# Los sábados es cuando más se utiliza el servicio
# Frecuencia de tipos de bicicleta
datos_combinados %>%
count(rideable_type)
## # A tibble: 4 × 2
## rideable_type n
## <chr> <int>
## 1 classic_bike 2583585
## 2 electric_bike 2027985
## 3 electric_scooter 76688
## 4 <NA> 1091133
# La bicicleta clásica es la que más se utiliza,
# seguida de la bicicleta eléctrica
# Frecuencia de tipos de usuarios
datos_combinados %>%
count(member_casual) %>%
mutate(porcentaje = n / sum(n) * 100)
## # A tibble: 3 × 3
## member_casual n porcentaje
## <chr> <int> <dbl>
## 1 casual 1720682 29.8
## 2 member 2967576 51.3
## 3 <NA> 1091133 18.9
# Los miembros equivalen al 51.3%
# Los usuarios casuales 29.8%
# Promedio de viajes por miembros y no miembros
datos_combinados %>%
group_by(member_casual) %>%
summarise(mean(ride_length))
## # A tibble: 3 × 2
## member_casual `mean(ride_length)`
## <chr> <dbl>
## 1 casual 27.7
## 2 member 12.8
## 3 <NA> NA
# Tiempo promedio de uso por usuario
# Casual: 27 min
# Miembro: 12 min
# Promedio de viaje por tipo de usuario y día
datos_combinados %>%
group_by(member_casual, day_of_week) %>%
summarise(
duracion_promedio = mean(ride_length, na.rm = TRUE),
.groups = "drop"
) %>%
arrange(member_casual, day_of_week)
## # A tibble: 15 × 3
## member_casual day_of_week duracion_promedio
## <chr> <ord> <dbl>
## 1 casual Sun 32.5
## 2 casual Mon 26.2
## 3 casual Tue 23.3
## 4 casual Wed 24.6
## 5 casual Thu 24.3
## 6 casual Fri 27.1
## 7 casual Sat 31.2
## 8 member Sun 14.4
## 9 member Mon 12.1
## 10 member Tue 12.2
## 11 member Wed 12.3
## 12 member Thu 12.3
## 13 member Fri 12.6
## 14 member Sat 14.3
## 15 <NA> <NA> NaN
# Qué día se movilizan más
datos_combinados %>%
mutate(dia_semana = wday(started_at, label = TRUE, week_start = 1)) %>%
count(dia_semana, member_casual) %>%
drop_na() %>%
arrange(desc(n))
## # A tibble: 14 × 3
## dia_semana member_casual n
## <ord> <chr> <int>
## 1 Wed member 483654
## 2 Tue member 458193
## 3 Thu member 453324
## 4 Mon member 437917
## 5 Fri member 429697
## 6 Sat member 378504
## 7 Sat casual 359056
## 8 Sun member 326287
## 9 Sun casual 292820
## 10 Fri casual 261410
## 11 Wed casual 212205
## 12 Thu casual 208321
## 13 Mon casual 203840
## 14 Tue casual 183030
# Usuarios casuales: sábados.
# Miembros: miércoles.
# Frecuencia de viajes diarios
datos_combinados %>%
group_by(member_casual == "member", day_of_week) %>%
count(ride_length) %>%
drop_na() %>%
arrange(desc(n))
## # A tibble: 152,486 × 4
## # Groups: member_casual == "member", day_of_week [14]
## `member_casual == "member"` day_of_week ride_length n
## <lgl> <ord> <dbl> <int>
## 1 TRUE Wed 4.88 505
## 2 TRUE Wed 4.9 500
## 3 TRUE Tue 4.17 493
## 4 TRUE Wed 4.87 490
## 5 TRUE Wed 4.92 483
## 6 TRUE Wed 5.72 483
## 7 TRUE Wed 4.63 477
## 8 TRUE Wed 4.45 476
## 9 TRUE Wed 4.7 475
## 10 TRUE Wed 5.02 473
## # ℹ 152,476 more rows
# Rutas frecuentes
datos_combinados %>%
group_by(start_station_name, end_station_name) %>%
count(member_casual) %>%
arrange(desc(n)) %>%
drop_na()
## # A tibble: 274,158 × 4
## # Groups: start_station_name, end_station_name [179,410]
## start_station_name end_station_name member_casual n
## <chr> <chr> <chr> <int>
## 1 Streeter Dr & Grand Ave Streeter Dr & Grand Ave casual 8705
## 2 DuSable Lake Shore Dr & Monroe St DuSable Lake Shore Dr … casual 7099
## 3 DuSable Lake Shore Dr & Monroe St Streeter Dr & Grand Ave casual 5203
## 4 State St & 33rd St Calumet Ave & 33rd St member 5104
## 5 Calumet Ave & 33rd St State St & 33rd St member 5083
## 6 Michigan Ave & Oak St Michigan Ave & Oak St casual 4597
## 7 Ellis Ave & 60th St Ellis Ave & 55th St member 3991
## 8 Ellis Ave & 55th St Ellis Ave & 60th St member 3758
## 9 Ellis Ave & 60th St University Ave & 57th … member 3718
## 10 University Ave & 57th St Ellis Ave & 60th St member 3710
## # ℹ 274,148 more rows
# Los usuarios casuales parten y terminan en la misma estación.
# Posiblemente la usan solo para pasear
# Horas en las que más se usa la bicicleta
# Todos los usuarios
datos_combinados %>%
group_by(member_casual) %>%
count(hour_of_day) %>%
arrange(desc(n)) %>%
drop_na()
## # A tibble: 48 × 3
## # Groups: member_casual [2]
## member_casual hour_of_day n
## <chr> <int> <int>
## 1 member 17 321298
## 2 member 16 278068
## 3 member 18 246997
## 4 member 8 212871
## 5 member 15 198519
## 6 member 19 170537
## 7 member 7 167112
## 8 casual 17 166717
## 9 member 12 162369
## 10 member 14 161308
## # ℹ 38 more rows
# La hora pic es a las 17:00hrs
# Usuarios casuales
datos_combinados %>%
filter(member_casual == "casual") %>%
group_by(member_casual) %>%
count(hour_of_day) %>%
arrange(desc(n)) %>%
drop_na()
## # A tibble: 24 × 3
## # Groups: member_casual [1]
## member_casual hour_of_day n
## <chr> <int> <int>
## 1 casual 17 166717
## 2 casual 16 154186
## 3 casual 18 140442
## 4 casual 15 135513
## 5 casual 14 124006
## 6 casual 13 119889
## 7 casual 12 115692
## 8 casual 19 100338
## 9 casual 11 99447
## 10 casual 10 78101
## # ℹ 14 more rows
# Los miembros casuales también tienen como hora pic las 17:00 hrs.
# Uso del servicio mensualmente
# Miembros
datos_combinados %>%
group_by(month_name) %>%
count(member_casual) %>%
filter(member_casual == "member") %>%
arrange(member_casual, desc(n))
## # A tibble: 12 × 3
## # Groups: month_name [12]
## month_name member_casual n
## <ord> <chr> <int>
## 1 September member 373058
## 2 August member 356122
## 3 July member 349961
## 4 October member 329547
## 5 June member 327342
## 6 May member 310372
## 7 April member 231991
## 8 November member 201053
## 9 March member 172144
## 10 December member 116932
## 11 February member 103125
## 12 January member 95929
# Mayormente en Septiembre y poco en Enero
# Usuarios casuales
datos_combinados %>%
group_by(month_name) %>%
count(member_casual) %>%
filter(member_casual == "casual") %>%
arrange(member_casual, desc(n))
## # A tibble: 12 × 3
## # Groups: month_name [12]
## month_name member_casual n
## <ord> <chr> <int>
## 1 July casual 262097
## 2 August casual 259014
## 3 September casual 258743
## 4 June casual 239369
## 5 May casual 190237
## 6 October casual 179598
## 7 April casual 108215
## 8 November casual 77763
## 9 March casual 70643
## 10 December casual 32287
## 11 February casual 22845
## 12 January casual 19871
# Julio, agosto y Septiembre.
En total tenemos 5,779,391 viajes registrados.
Cómo se dividen los clientes:
Los miembros equivalen al 51.3%
Los usuarios casuales 29.8%
Los usuarios casuales parten y terminan en la misma estación. Posiblemente utilizan la bicicleta solo para pasear
Los usuarios casuales se mueven mayormente los sábados, mientras que los miembros, los miércoles.
Tiempo de uso promedio:
Usuarios casuales: 27 min.
Miembros: 12 min.
La hora pic del servicio es a las 17:00hrs. por parte de ambos usuarios.
Julio, agosto y Septiembre, son los meses donde más se usa el servicio, es decir, en verano.
datos_combinados %>%
filter(!is.na(member_casual) & !is.na(rideable_type)) %>% # Excluye NA en ambas variables
ggplot(aes(x = member_casual, fill = rideable_type)) +
geom_bar(position = "dodge") +
labs(
title = "Uso de bicicletas por tipo de usuario",
x = "Tipo de usuario",
y = "Número de viajes",
fill = "Tipo de bicicleta"
) +
theme_minimal() +
labs(caption = "Fuente: Divvy Bikes, 2024")
datos_combinados %>%
mutate(mes = floor_date(started_at, "month")) %>%
count(mes, member_casual) %>%
drop_na() %>%
ggplot(aes(x = mes, y = n, color = member_casual)) +
geom_line(linewidth = 1) +
geom_point() +
labs(title = "Uso anual por tipo de usuario",
x = "Mes", y = "Viajes") +
labs(caption = "Fuente: Divvy Bikes, 2024")
datos_combinados %>%
mutate(dia_semana = wday(started_at, label = TRUE, week_start = 1)) %>%
count(dia_semana, member_casual) %>%
drop_na() %>%
ggplot(aes(x = dia_semana, y = n, fill = member_casual)) +
geom_col(position = "dodge") +
labs(title = "Uso semanal por tipo de usuario",
x = "Día de la semana", y = "Viajes") +
labs(caption = "Fuente: Divvy Bikes, 2024")
Las principales caracteristicas de los usuarios casuales, a quienes queremos alcanzar, son:
Equivalen al 29% de los usuarios totales, usan en su mayoría la bicicleta para pasear y mayormente los sábados, a las 17:00hrs, entre Julio y Septiembre.
Se les puede ofrecer planes más flexibles para motivar el paseo durante el fin de semana o la movilización durante esos 3 meses.
Los datos han sido proporcionados por Motivate International Inc. bajo esta licencia.