Caso de estudio cyclistic

Cyclistic es una empresa de bicicletlas compartidas en chicago, la cual quiere maximizar la cantidad de membresías anuales, por lo tanto, debemos de encontrar las diferencias que existen entre los cyclistas anuales y los ocasionales para diseñar una estrategía de marketing.

Nuestra tarea es descubrir en qué se diferencian los cyclistas socios anuales y los ciclistas ocasionales con respecto al uso de las bicicletas de cyclistic.

¿En qué se diferencian los socios anuales y los ciclistas ocasionales con respecto al uso de las bicicletas de Cyclistic?

Tenemos un conjunto de datos de los últimos 12 meses de viajes de cyclistic, los vamos a limpiar y tranformar.

Cargamos las librerías necesarias.

Vamos a cargar los datos de los 12 meses y los unimos en un solo conjunto de datos:

enero <- read.csv("D:\\Desktop\\CursoAD\\Caso_Practico_1_Archivos\\2021-2022\\enero2022.csv")
febrero <- read.csv("D:\\Desktop\\CursoAD\\Caso_Practico_1_Archivos\\2021-2022\\febrero2022.csv")
marzo <- read.csv("D:\\Desktop\\CursoAD\\Caso_Practico_1_Archivos\\2021-2022\\marzo2022.csv")
abril <- read.csv("D:\\Desktop\\CursoAD\\Caso_Practico_1_Archivos\\2021-2022\\abril2022.csv")
mayo <- read.csv("D:\\Desktop\\CursoAD\\Caso_Practico_1_Archivos\\2021-2022\\mayo2022.csv")
junio <- read.csv("D:\\Desktop\\CursoAD\\Caso_Practico_1_Archivos\\2021-2022\\junio2022.csv")
julio <- read.csv("D:\\Desktop\\CursoAD\\Caso_Practico_1_Archivos\\2021-2022\\julio2022.csv")
agosto <- read.csv("D:\\Desktop\\CursoAD\\Caso_Practico_1_Archivos\\2021-2022\\agosto2022.csv")
sept2021 <- read.csv("D:\\Desktop\\CursoAD\\Caso_Practico_1_Archivos\\2021-2022\\septiembre2021.csv")
oct2021 <- read.csv("D:\\Desktop\\CursoAD\\Caso_Practico_1_Archivos\\2021-2022\\octubre2021.csv")
nov2021 <- read.csv("D:\\Desktop\\CursoAD\\Caso_Practico_1_Archivos\\2021-2022\\noviembre2021.csv")
dic2021 <- read.csv("D:\\Desktop\\CursoAD\\Caso_Practico_1_Archivos\\2021-2022\\diciembre2021.csv")
c_1 <- rbind(enero,febrero,marzo,abril,mayo,junio,julio,agosto,sept2021,oct2021,nov2021,dic2021)

Observamos el conjunto:

summary(c_1)
##    ride_id          rideable_type       started_at          ended_at        
##  Length:5883043     Length:5883043     Length:5883043     Length:5883043    
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##                                                                             
##  start_station_name start_station_id   end_station_name   end_station_id    
##  Length:5883043     Length:5883043     Length:5883043     Length:5883043    
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##                                                                             
##    start_lat       start_lng         end_lat         end_lng      
##  Min.   :41.64   Min.   :-87.84   Min.   :41.39   Min.   :-88.97  
##  1st Qu.:41.88   1st Qu.:-87.66   1st Qu.:41.88   1st Qu.:-87.66  
##  Median :41.90   Median :-87.64   Median :41.90   Median :-87.64  
##  Mean   :41.90   Mean   :-87.65   Mean   :41.90   Mean   :-87.65  
##  3rd Qu.:41.93   3rd Qu.:-87.63   3rd Qu.:41.93   3rd Qu.:-87.63  
##  Max.   :45.64   Max.   :-73.80   Max.   :42.37   Max.   :-87.50  
##                                   NA's   :5727    NA's   :5727    
##  member_casual     
##  Length:5883043    
##  Class :character  
##  Mode  :character  
##                    
##                    
##                    
## 
colnames(c_1)
##  [1] "ride_id"            "rideable_type"      "started_at"        
##  [4] "ended_at"           "start_station_name" "start_station_id"  
##  [7] "end_station_name"   "end_station_id"     "start_lat"         
## [10] "start_lng"          "end_lat"            "end_lng"           
## [13] "member_casual"
str(c_1)
## 'data.frame':    5883043 obs. of  13 variables:
##  $ ride_id           : chr  "C2F7DD78E82EC875" "A6CF8980A652D272" "BD0F91DFF741C66D" "CBB80ED419105406" ...
##  $ rideable_type     : chr  "electric_bike" "electric_bike" "classic_bike" "classic_bike" ...
##  $ started_at        : chr  "2022-01-13 11:59:47" "2022-01-10 08:41:56" "2022-01-25 04:53:40" "2022-01-04 00:18:04" ...
##  $ ended_at          : chr  "2022-01-13 12:02:44" "2022-01-10 08:46:17" "2022-01-25 04:58:01" "2022-01-04 00:33:00" ...
##  $ start_station_name: chr  "Glenwood Ave & Touhy Ave" "Glenwood Ave & Touhy Ave" "Sheffield Ave & Fullerton Ave" "Clark St & Bryn Mawr Ave" ...
##  $ start_station_id  : chr  "525" "525" "TA1306000016" "KA1504000151" ...
##  $ end_station_name  : chr  "Clark St & Touhy Ave" "Clark St & Touhy Ave" "Greenview Ave & Fullerton Ave" "Paulina St & Montrose Ave" ...
##  $ end_station_id    : chr  "RP-007" "RP-007" "TA1307000001" "TA1309000021" ...
##  $ start_lat         : num  42 42 41.9 42 41.9 ...
##  $ start_lng         : num  -87.7 -87.7 -87.7 -87.7 -87.6 ...
##  $ end_lat           : num  42 42 41.9 42 41.9 ...
##  $ end_lng           : num  -87.7 -87.7 -87.7 -87.7 -87.6 ...
##  $ member_casual     : chr  "casual" "casual" "member" "casual" ...

Buscamos y exploramos valores vacíos o en blanco:

colSums(c_1=="")
##            ride_id      rideable_type         started_at           ended_at 
##                  0                  0                  0                  0 
## start_station_name   start_station_id   end_station_name     end_station_id 
##             884365             884363             946303             946303 
##          start_lat          start_lng            end_lat            end_lng 
##                  0                  0                 NA                 NA 
##      member_casual 
##                  0

Observamos que hay entradas en blanco, los convertimos a valores NA para eliminarlos. Al ser un conjunto de datos ficticio, queda a mi criterio el tratamiento de valores nulos.

c_1[c_1==""] <- NA

Observamos que el cambio se haya realizado correctamente

apply(is.na(c_1),2,sum)
##            ride_id      rideable_type         started_at           ended_at 
##                  0                  0                  0                  0 
## start_station_name   start_station_id   end_station_name     end_station_id 
##             884365             884363             946303             946303 
##          start_lat          start_lng            end_lat            end_lng 
##                  0                  0               5727               5727 
##      member_casual 
##                  0

Eliminamos los valores nulos y comprobamos que ya no estén:

c_2 <- na.omit(c_1)
sum(is.na(c_2))
## [1] 0

Comprobamos que ya no haya valores nulos

apply(is.na(c_2),2,sum)
##            ride_id      rideable_type         started_at           ended_at 
##                  0                  0                  0                  0 
## start_station_name   start_station_id   end_station_name     end_station_id 
##                  0                  0                  0                  0 
##          start_lat          start_lng            end_lat            end_lng 
##                  0                  0                  0                  0 
##      member_casual 
##                  0

Borramos las columnas que considero no necesito para el análisis por ahora:

c_3 <- c_2 %>%  
  select(-c(start_lat, start_lng, end_lat, end_lng))

Cambiamos las columnas started_at y ended_at a formato ymd_hms:

col_started <- ymd_hms(c_3$started_at)
col_ended <- ymd_hms(c_3$ended_at)
#quitamos las columnas con el formato chr
c_4 <- select(c_3, -ended_at, -started_at)
#agregamos las columnas con el formato ymd:hms al conjunto de datos
c_5 <- c_4 %>% mutate(started_at = col_started)%>%
  mutate(ended_at = col_ended)
c_5[1:5,8:9]
##            started_at            ended_at
## 1 2022-01-13 11:59:47 2022-01-13 12:02:44
## 2 2022-01-10 08:41:56 2022-01-10 08:46:17
## 3 2022-01-25 04:53:40 2022-01-25 04:58:01
## 4 2022-01-04 00:18:04 2022-01-04 00:33:00
## 5 2022-01-20 01:31:10 2022-01-20 01:37:12

Creamos la columna “duración viaje” restando a started_at, ended_at. Se crea en segundos pero para fines visuales, también creamos una en formato hms:

c_6 <- c_5 %>% mutate(duracion_viaje_secs = ended_at - started_at)
c_7 <- c_6 %>% mutate(duracion_viaje = as.hms(ended_at - started_at))
c_7[1:5,9:11]
##              ended_at duracion_viaje_secs duracion_viaje
## 1 2022-01-13 12:02:44            177 secs       00:02:57
## 2 2022-01-10 08:46:17            261 secs       00:04:21
## 3 2022-01-25 04:58:01            261 secs       00:04:21
## 4 2022-01-04 00:33:00            896 secs       00:14:56
## 5 2022-01-20 01:37:12            362 secs       00:06:02

Creamos una columna con los días de la semana de los viajes, empezando 1 en domingo y 7 sábado. De igual manera creamos una en formato chr:

c_8 <- c_7 %>% mutate(dia_semana = (as.POSIXlt(c_5$started_at)$wday+1)) %>%
  mutate(dia = (weekdays(c_5$started_at)))
c_8[1:5,10:13]
##   duracion_viaje_secs duracion_viaje dia_semana    dia
## 1            177 secs       00:02:57          5 jueves
## 2            261 secs       00:04:21          2  lunes
## 3            261 secs       00:04:21          3 martes
## 4            896 secs       00:14:56          3 martes
## 5            362 secs       00:06:02          5 jueves

Creamos una columna para extraer los meses de los viajes:

c_9 <- c_8%>%
  mutate(mes_letra = format(c_6$started_at, "%B"))
c_9[1:5,11:14]
##   duracion_viaje dia_semana    dia mes_letra
## 1       00:02:57          5 jueves     enero
## 2       00:04:21          2  lunes     enero
## 3       00:04:21          3 martes     enero
## 4       00:14:56          3 martes     enero
## 5       00:06:02          5 jueves     enero

Creamos una columna para la temporada de los viajes de acuerdo al mes:

c_10 <- c_9 %>% 
  mutate(temporada = case_when(
    (mes_letra == "diciembre") | (mes_letra == "enero")~ "invierno",
    (mes_letra == "marzo") | (mes_letra == "abril") ~ "primavera",
    (mes_letra == "junio") | (mes_letra == "julio") ~ "verano",
    (mes_letra == "febrero") ~ "invierno",
    (mes_letra == "mayo") ~ "primavera",
    (mes_letra == "agosto") ~ "verano",
    (mes_letra == "noviembre") ~ "otoño",
    (mes_letra == "septiembre") | (mes_letra == "octubre") ~ "otoño"))
c_10[1:5,13:15]
##      dia mes_letra temporada
## 1 jueves     enero  invierno
## 2  lunes     enero  invierno
## 3 martes     enero  invierno
## 4 martes     enero  invierno
## 5 jueves     enero  invierno

Revisamos que todos los meses sean proporcionales a la cantidad de variables;

length(c_10$temporada [c_10$temporada == "primavera"])
## [1] 991088
length(c_10$temporada [c_10$temporada == "verano"])
## [1] 1868355
length(c_10$temporada [c_10$temporada == "otoño"])
## [1] 1355026
length(c_10$temporada [c_10$temporada == "invierno"])
## [1] 345677

Análisis de los datos

Comparamos a los miembros anuales con los ciclistas ocasionales:

aggregate(c_10$duracion_viaje ~ c_10$member_casual, FUN = mean)
##   c_10$member_casual c_10$duracion_viaje
## 1             casual      1547.2736 secs
## 2             member       752.7223 secs
aggregate(c_10$duracion_viaje ~ c_10$member_casual, FUN = median)
##   c_10$member_casual c_10$duracion_viaje
## 1             casual            00:14:41
## 2             member            00:09:07
aggregate(c_10$duracion_viaje ~ c_10$member_casual, FUN = max)
##   c_10$member_casual c_10$duracion_viaje
## 1             casual        2442301 secs
## 2             member          89575 secs
aggregate(c_10$duracion_viaje ~ c_10$member_casual, FUN = min)
##   c_10$member_casual c_10$duracion_viaje
## 1             casual          -7621 secs
## 2             member          -7745 secs

Nos damos cuenta que hay viajes en negativo, decidimos eliminarlos

Eliminar viajes con valores negativos e iguales a 0:

cyclistic <- c_10[!(c_10$start_station_name == "HQ QR" | c_10$duracion_viaje_secs<=0),]

Media duración de los viajes:

as.hms(mean(cyclistic$duracion_viaje_secs))
## 00:18:00.034791
mean(cyclistic$duracion_viaje)
## Time difference of 1080.035 secs

Observamos las modas:

mfv(cyclistic$dia)
## [1] "sábado"
mfv(cyclistic$mes_letra)
## [1] "julio"
mfv(cyclistic$temporada)
## [1] "verano"

Cálculamos el promedio de duración de viaje por tipo de ciclistas:

aggregate(cyclistic$duracion_viaje_secs~member_casual,cyclistic, mean,na.rm=TRUE)
##   member_casual cyclistic$duracion_viaje_secs
## 1        casual                1547.4104 secs
## 2        member                 752.7899 secs

Tiempo de viaje promedio por día por cada tipo de ciclista:

aggregate(cyclistic$duracion_viaje ~ cyclistic$member_casual + cyclistic$dia, FUN = mean)
##    cyclistic$member_casual cyclistic$dia cyclistic$duracion_viaje
## 1                   casual       domingo           1783.4258 secs
## 2                   member       domingo            848.0927 secs
## 3                   casual        jueves           1366.4828 secs
## 4                   member        jueves            722.5699 secs
## 5                   casual         lunes           1606.6642 secs
## 6                   member         lunes            729.6108 secs
## 7                   casual        martes           1354.4807 secs
## 8                   member        martes            708.7854 secs
## 9                   casual     miércoles           1324.9922 secs
## 10                  member     miércoles            715.2554 secs
## 11                  casual        sábado           1703.4030 secs
## 12                  member        sábado            845.2656 secs
## 13                  casual       viernes           1441.8493 secs
## 14                  member       viernes            736.1187 secs

Visualización de los datos:

Visualización de la información con Power BI

¿Qué es Power BI?

Porcentaje de ciclistas, tiempo promedio de viaje por minutos y bicicletas preferidas:

Top cinco de estaciones de inicio y de final más concurridas:

Viajes por mes y promedio de tiempo en minutos:

Viajes realizados por día y promedio de tiempo en minutos:

Recomendaciones finales

Tres recomendaciones que pude observar a partir del comportamiento de los datos:

  • Lanzar la campaña de marketing para usuarios casuales empezando la temporada de verano, que abarca los meses junio, julio, agosto y septiembre, así como en los días viernes, sábado y domingo.

  • Enfatizar la campaña de marketing en el uso de la bicicleta clásica qué es la preferida por los ciclistas casuales.

  • Agregar promociones para los miembros anuales que viajen más de 15 minutos, ya que los usuarios casuales suelen realizar viajes más largos a partir de los minutos ya mencionados y puede interesarles la membresía anual al contar con esa promoción.

  • Colocar publicidad en las estaciones de inicio y de final más concurridas por los ciclistas casuales:

  1. Street Dr & Grand Ave

  2. Dusable Lake Shore Dr Monroe St

  3. Dusable Lake Shore Dr & North Blvd

  4. Millennium Park

  5. Michigan Ave & Oak St

Información complementaria de los datos

Conjuntos de datos abiertos, bajo la licencia Motivate, almacenados en: https://divvy-tripdata.s3.amazonaws.com/index.html

Información de contacto

Mariana Raquel Flores Carrillo ir a linkedin: https://www.linkedin.com/in/mariana-raquel-flores-carrillo-20201b247/