Estudio de caso de Bellabeat: ¿Cómo puede una empresa de tecnología de bienestar actuar de manera inteligente?

Este es el caso práctico final de curso de Análisis de Datos de Google. En este estudio de caso, utilizaré los siguientes pasos para el análisis de datos: preguntar, preparar, procesar, analizar, compartir y actuar.

INTRODUCCIÓN

Bellabeat es una empresa de alta tecnología que fabrica productos inteligentes centrados en la salud. La recopilación de datos sobre la actividad física, el sueño, el estrés y la salud reproductora ha permitido a Bellabeat empoderar a las mujeres con conocimientos sobre su propia salud y hábitos. Desde su fundación en 2013, Bellabeat ha crecido rápidamente y se ha posicionado como una empresa de bienestar para las mujeres impulsada por la tecnología.

En el análisis de datos que voy a realizar, analizaré los datos de dispositivos inteligentes para conocer como los clientes los utilizan. Los hallazgos ayudarán a orientar a la estrategia de marketing de la empresa.

Partes interesadas

  • Urška Sršen es la cofundadora y directora creativa de Bellabeat.

  • Sando Mur es el matemático y cofundador de Bellabeat; miembro clave del equipo ejecutivo de Bellabeat.

  • El equipo de analistas de datos se encarga de recopilar, analizar e informar los datos, ellos ayudan a conducir la estrategia de marketing de Bellabeat.

Productos

  • Aplicación Bellabeat: La aplicación Bellabeat proporciona a los usuarios datos de salud relacionados con su actividad física, sueño, estrés, ciclo menstrual y hábitos de conciencia plena. Estos datos pueden ayudar a los usuarios a comprender sus hábitos actuales y adoptar decisiones saludables. La aplicación Bellabeat se conecta a su línea de productos de bienestar inteligentes.

  • Leaf: Dispositivo de seguimiento clásico de bienestar de Bellabeat que se puede usar como pulsera, collar o clip. El dispositivo Leaf se conecta a la aplicación Bellabeat para hacer un seguimiento de la actividad física, el sueño y el estrés.

  • Time: Este reloj de bienestar combina el aspecto intemporal de un reloj clásico con la tecnología inteligente para hacer el seguimiento de la actividad física, el sueño y el estrés del usuario. El reloj Time se conecta a la aplicación Bellabeat para proporcionar información sobre el bienestar diario.

  • Spring: Es una botella de agua que hace el seguimiento diario del consumo de agua mediante el uso de tecnología inteligente para garantizar la hidratación adecuada a lo largo del día. La botella Spring se conecta a la aplicación Bellabeat para hacer el seguimiento de los niveles de hidratación.

Tarea empresarial

Sršen sabe que un análisis de los datos del consumo disponible en la aplicación Bellabeat revelaría más oportunidades de crecimiento. Le pidió al equipo de análisis de marketing que se centrara en un producto de Bellabeat y analizara los datos de uso de dispositivos inteligentes para obtener información sobre cómo las personas ya usan sus dispositivos inteligentes. Luego, utilizando esta información, le gustaría recibir recomendaciones de alto nivel sobre cómo estas tendencias pueden informar la estrategia de marketing de Bellabeat.

Pasos para el análisis de datos Bellabeat

PASO 1 - PREGUNTAR

  • 1) ¿Cuáles son algunas tendencias en el uso de dispositivos inteligentes?

  • 2) ¿Cómo podrían aplicarse estas tendencias a los clientes de Bellabeat?

  • 3) ¿Cómo podrían estas tendencias ayudar a influir en la estrategia de marketing de Bellabeat?

PASO 2 - PREPARAR

Los datos utilizados para este estudio de caso se obtuvieron de FitBit Fitness Tracker Data de Kaggle a través de Amazon Mechanical Turk entre el 12/03/2016 y el 12/05/2016. Este conjunto de datos contiene datos de seguimiento de actividad física personal de 30 usuarios de Fitbit que dieron su consentimiento para el envío de información sobre su actividad diaria, pasos, frecuencia cardíaca y seguimiento del sueño.

¿Los datos son ROCCC1 son confiable, original, completo, actual y citado? Veamos:

  • Confiable: no hay información adicional sobre género, edad o estilo de vida y solo tiene una muestra de 30 participantes.

  • Original: los datos provienen de un tercero Amazon Mechanical Turk.

  • Completo: hay algunos campos a los que les falta información debido a que todos los usuarios no usaron FitBit durante 30 días.

  • Actual: los datos son de 2016. Podría haber cambios en el estilo de vida desde entonces.

  • Citado: Desconocido ya que los datos provienen de un tercero.

PASO 3 - PROCESAR

Debido al gran tamaño de los datos recopilados, usaré R (lenguaje de programación) para realizar este estudio de caso por su capacidad para manejar grandes conjuntos de datos y por nos brindar con una buena visualización de datos. Vamos a por ello:

  • Instalo los paquetes necesarios para iniciar el proceso de limpieza de datos:
library(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.4.4     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2     
## ── 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
library(lubridate)
library(dplyr)
library(ggplot2)
library(janitor)
## 
## Attaching package: 'janitor'
## 
## The following objects are masked from 'package:stats':
## 
##     chisq.test, fisher.test
library(ggpubr)
  • Importo los archivos en .csv:
dailyAct <- read.csv("dailyActivity_merged.csv")
sleepDay <- read.csv("sleepDay_merged.csv")
dailySteps <- read.csv("dailySteps_merged.csv")
dailyCal <- read.csv("dailyCalories_merged.csv")
minsSleep <- read.csv("minuteSleep_merged.csv")
  • Familiarizo con los nombres de las columnas y los tipos de variables:
dailyAct%>% 
  glimpse()
## Rows: 940
## Columns: 15
## $ Id                       <dbl> 1503960366, 1503960366, 1503960366, 150396036…
## $ ActivityDate             <chr> "4/12/2016", "4/13/2016", "4/14/2016", "4/15/…
## $ TotalSteps               <int> 13162, 10735, 10460, 9762, 12669, 9705, 13019…
## $ TotalDistance            <dbl> 8.50, 6.97, 6.74, 6.28, 8.16, 6.48, 8.59, 9.8…
## $ TrackerDistance          <dbl> 8.50, 6.97, 6.74, 6.28, 8.16, 6.48, 8.59, 9.8…
## $ LoggedActivitiesDistance <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ VeryActiveDistance       <dbl> 1.88, 1.57, 2.44, 2.14, 2.71, 3.19, 3.25, 3.5…
## $ ModeratelyActiveDistance <dbl> 0.55, 0.69, 0.40, 1.26, 0.41, 0.78, 0.64, 1.3…
## $ LightActiveDistance      <dbl> 6.06, 4.71, 3.91, 2.83, 5.04, 2.51, 4.71, 5.0…
## $ SedentaryActiveDistance  <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ VeryActiveMinutes        <int> 25, 21, 30, 29, 36, 38, 42, 50, 28, 19, 66, 4…
## $ FairlyActiveMinutes      <int> 13, 19, 11, 34, 10, 20, 16, 31, 12, 8, 27, 21…
## $ LightlyActiveMinutes     <int> 328, 217, 181, 209, 221, 164, 233, 264, 205, …
## $ SedentaryMinutes         <int> 728, 776, 1218, 726, 773, 539, 1149, 775, 818…
## $ Calories                 <int> 1985, 1797, 1776, 1745, 1863, 1728, 1921, 203…
sleepDay%>%  
  glimpse()
## Rows: 413
## Columns: 5
## $ Id                 <dbl> 1503960366, 1503960366, 1503960366, 1503960366, 150…
## $ SleepDay           <chr> "4/12/2016 12:00:00 AM", "4/13/2016 12:00:00 AM", "…
## $ TotalSleepRecords  <int> 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ TotalMinutesAsleep <int> 327, 384, 412, 340, 700, 304, 360, 325, 361, 430, 2…
## $ TotalTimeInBed     <int> 346, 407, 442, 367, 712, 320, 377, 364, 384, 449, 3…
dailySteps%>%  
  glimpse()
## Rows: 940
## Columns: 3
## $ Id          <dbl> 1503960366, 1503960366, 1503960366, 1503960366, 1503960366…
## $ ActivityDay <chr> "4/12/2016", "4/13/2016", "4/14/2016", "4/15/2016", "4/16/…
## $ StepTotal   <int> 13162, 10735, 10460, 9762, 12669, 9705, 13019, 15506, 1054…
dailyCal%>%   
  glimpse()
## Rows: 940
## Columns: 3
## $ Id          <dbl> 1503960366, 1503960366, 1503960366, 1503960366, 1503960366…
## $ ActivityDay <chr> "4/12/2016", "4/13/2016", "4/14/2016", "4/15/2016", "4/16/…
## $ Calories    <int> 1985, 1797, 1776, 1745, 1863, 1728, 1921, 2035, 1786, 1775…
minsSleep%>% 
  glimpse()
## Rows: 188,521
## Columns: 4
## $ Id    <dbl> 1503960366, 1503960366, 1503960366, 1503960366, 1503960366, 1503…
## $ date  <chr> "4/12/2016 2:47:30 AM", "4/12/2016 2:48:30 AM", "4/12/2016 2:49:…
## $ value <int> 3, 2, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 1, 1, 1, 1, 1, 1…
## $ logId <dbl> 11380564589, 11380564589, 11380564589, 11380564589, 11380564589,…
  • Observo que hay columnas de tipo “chr” para el formato fecha en algunos marcos de datos. Los cambio al tipo “fecha” para que el formato sea correcto:
dailyAct <- dailyAct %>%
  rename(Date = ActivityDate) %>%
  mutate(Date = as_date(Date, format = "%m/%d/%Y"))
dailyCal <- dailyCal %>%
  rename(Date = ActivityDay) %>%
  mutate(Date = as_date(Date, format = "%m/%d/%Y"))
dailySteps <- dailySteps %>%
  rename(Date = ActivityDay) %>%
  mutate(Date = as_date(Date, format = "%m/%d/%Y"))
sleepDay <- sleepDay %>%
  rename(Date = SleepDay) %>%
  mutate(Date = as_date(Date,format ="%m/%d/%Y %I:%M:%S %p" , tz=Sys.timezone()))
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `Date = as_date(Date, format = "%m/%d/%Y %I:%M:%S %p", tz =
##   Sys.timezone())`.
## Caused by warning:
## ! `tz` argument is ignored by `as_date()`
  • Ahora busco duplicados en los datos:
sum(duplicated(dailyAct))
## [1] 0
sum(duplicated(dailySteps))
## [1] 0
sum(duplicated(dailyCal))
## [1] 0
  • Observo que hay dos marcos de datos con duplicados: “minsSleep” y “sleepDay”.
sum(duplicated(minsSleep))
## [1] 543
sum(duplicated(sleepDay))
## [1] 3
  • Elimino los duplicados en los marcos de datos; “minsSleep” y “sleepDay”. Esto permite una mayor precisión de los datos:
minsSleep <- minsSleep %>% 
  distinct() %>% 
  drop_na()
sleepDay <- sleepDay %>% 
  distinct() %>% 
  drop_na()
  • Comprobando si se eliminaron los duplicados:
sum(duplicated(minsSleep))
## [1] 0
sum(duplicated(sleepDay))
## [1] 0

PASO 4 - ANALIZAR

Ahora que los datos están limpios, empiezo a buscar información inicial a partir de ellos.

  • Fusiono los marcos de datos “dailyAct” y “sleepDay” usando las columnas “Id” y “Date” para simplificar el trazado de los datos.
allActivity <- merge(dailyAct, sleepDay, by = c("Id", "Date"), all = TRUE)
  • Creo una columna “Día de la semana” esto ayudará a organizar la información de una manera más lógica.
allActivity <- allActivity %>% 
  mutate( Weekday = weekdays(as.Date(Date, "%m/%d/%Y")))
View(allActivity)
allActivity$Weekday <- factor(allActivity$Weekday,
  levels = c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"))
View(allActivity)

Intruduciré gráficos rápidos para ver algunas relaciones entre los datos.

Comprobo con qué frecuencia las personas usan su FitBit durante la semana.

ggplot(data = allActivity, aes( x = Weekday, y = TrackerDistance, fill = Weekday)) + geom_bar(stat = “identity”)

  • Parece que hay una disminución en el uso a medida que avanza la semana. Algunos usuarios no utilizan su FitBit durante la semana.

A continuación, miro cuál es la relación entre las calorías quemadas y la actividad no sedentaria:

ggplot(data = allActivity, aes(x=TotalSteps + VeryActiveMinutes + LightlyActiveMinutes + FairlyActiveMinutes, y = Calories, color = Calories)) + 
  scale_color_gradient(low = "yellow", high = "red") +
  geom_point() + stat_smooth(method = lm) + 
  labs(title = "Minutos activos y calorías quemadas", x = "Minutos activos combinados (muy activo + bastante activo + ligeramente activo))")
## `geom_smooth()` using formula = 'y ~ x'
## Warning: The following aesthetics were dropped during statistical transformation: colour
## ℹ This can happen when ggplot fails to infer the correct grouping structure in
##   the data.
## ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
##   variable into a factor?

  • Existe una correlación bastante obvia: cuanto más activos eran los usuarios, más calorías quemaban.

Ahora elijo centrarme en los patrones de sueño. Aquí comparo el tiempo que los usuarios pasaron en la cama con su tiempo de sueño actual.

ggplot(data = allActivity, aes(x = TotalMinutesAsleep, y = TotalTimeInBed)) +
  geom_point()
## Warning: Removed 530 rows containing missing values (`geom_point()`).

Parece que hay algunos usuarios que pasan tiempo en la cama sin estar realmente dormidos. El domingo parece el día en el que la mayoría de usuarios pasan tiempo en la cama sin dormir.

Con estos dos gráficos trazados, quiero ver si existe una correlación entre las calorías quemadas y el tiempo de sueño. Hago una trama para “Tiempo de sueño” frente a “Calorías quemadas”. Y para que sea más fácil de entender el contexto de los minutos, convierto los minutos en horas.

ggplot(data = allActivity, aes(x = Calories, y = TotalMinutesAsleep / 60, color = Calories))+ scale_color_gradient(low = "yellow", high = "red") +
  geom_point() + 
  stat_smooth(method = lm) +
  labs(title = "Total de horas en reposo frente a calorías quemadas", y = "Horas de sueño", x = "
Calorías quemadas")
## `geom_smooth()` using formula = 'y ~ x'
## Warning: Removed 530 rows containing non-finite values (`stat_smooth()`).
## Warning: The following aesthetics were dropped during statistical transformation: colour
## ℹ This can happen when ggplot fails to infer the correct grouping structure in
##   the data.
## ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
##   variable into a factor?
## Warning: Removed 530 rows containing missing values (`geom_point()`).

Parece que los usuarios tuvieron un tiempo de sueño promedio sin importar cuántas calorías quemaron, por lo que no hay correlación ahí.

PASO 5 - COMPARTIR

Recopilo algunas ideas para sacar conclusiones para nuestras partes interesadas. Estos gráficos facilitarán la comparación de diferentes tendencias en los datos.

tma <- ggplot(data = allActivity, aes(TotalMinutesAsleep / 60, SedentaryMinutes, color = TotalMinutesAsleep)) + 
  geom_point() +
  scale_color_gradient(low = "yellow", high = "red") +
  labs(title = "Min sedentarios vs horas de sueño", y = "Minutos sedentarios", x = "Horas de sueño")
ttib <- ggplot(data = allActivity, aes(TotalTimeInBed / 60, SedentaryMinutes, color = TotalTimeInBed)) + 
  geom_point() + 
  scale_color_gradient(low = "yellow", high = "red") +
  labs(title = "Min sedentarios vs horas en la cama", y = "Minutos sedentarios", x = "Horas en la cama")
ggarrange(ttib, tma, ncol = 2, nrow = 1)
## Warning: Removed 530 rows containing missing values (`geom_point()`).
## Removed 530 rows containing missing values (`geom_point()`).

Hay un ligero aumento en la cantidad de usuarios sedentarios que pasan más tiempo despiertos en la cama.

Ahora hago gráficos para ver qué tipos de usuarios de FitBit duermen más.

sms <- ggplot(data = allActivity, aes(TotalMinutesAsleep, SedentaryMinutes, color = TotalMinutesAsleep)) + 
  geom_point() +
  scale_color_gradient(low = "yellow", high = "red")
tss <- ggplot(data = allActivity, aes(TotalMinutesAsleep, TotalSteps, color = TotalMinutesAsleep)) + 
  geom_point() + 
  scale_color_gradient(low = "yellow", high = "red")
tds <- ggplot(data = allActivity, aes(TotalMinutesAsleep, TotalDistance, color = TotalMinutesAsleep)) + 
  geom_point() + 
  scale_color_gradient(low = "yellow", high = "red")
vas <- ggplot(data = allActivity, aes(TotalMinutesAsleep, VeryActiveMinutes, color = TotalMinutesAsleep)) + 
  geom_point() + 
  scale_color_gradient(low = "yellow", high = "red")
ggarrange(sms, tss, tds, vas, ncol = 2, nrow = 2)
## Warning: Removed 530 rows containing missing values (`geom_point()`).
## Removed 530 rows containing missing values (`geom_point()`).
## Removed 530 rows containing missing values (`geom_point()`).
## Removed 530 rows containing missing values (`geom_point()`).

Hago una comparación entre los diferentes tipos de actividades y la cantidad de calorías que queman.

smc <- ggplot(data = allActivity, aes(SedentaryMinutes, Calories, color = Calories)) + 
  geom_point() + 
  scale_color_gradient(low = "yellow", high = "red")
tsc <- ggplot(data = allActivity, aes(TotalSteps, Calories, color = Calories)) + 
  geom_point() + 
  scale_color_gradient(low = "yellow", high = "red")
tdc <- ggplot(data = allActivity, aes(TotalDistance, Calories, color = Calories)) + 
  geom_point() +  
  scale_color_gradient(low = "yellow", high = "red")
vac <- ggplot(data = allActivity, aes(VeryActiveMinutes, Calories, color = Calories)) + 
  geom_point() + scale_color_gradient(low = "yellow", high = "red")
ggarrange(smc,tsc, tdc, vac, ncol = 2, nrow = 2)

Veamos cómo pasa la gente la mayor parte de su tiempo. Desgloso los minutos de actividad en porcentajes para hacer un gráfico circular.

dataPercent <- allActivity %>%
  summarise(sum_fa = sum(FairlyActiveMinutes/1148807*100),
  sum_va = sum(VeryActiveMinutes/1148807*100),
  sum_la = sum(LightlyActiveMinutes/1148807*100), 
  sum_se = sum(SedentaryMinutes/1148807*100),
  sum_total=sum(VeryActiveMinutes+FairlyActiveMinutes+LightlyActiveMinutes+SedentaryMinutes)) %>% 
  round(digits = 2)
piechart <- c(dataPercent$sum_va, dataPercent$sum_fa, dataPercent$sum_la, dataPercent$sum_se, dataPercent$sum)

lbls <- c("Muy activo", "Bastante activo", "Ligeramente activo", "Sedentario")
  
pie(piechart,
    labels = paste(lbls, sep =" "),
    col = c("purple", "turquoise","yellow", "red"),
     main = "Minutos de actividad")

Existe un amplio margen de usuarios sedentarios. Es de esperar un margen mayor, ya que las personas todavía necesitan descansar, pero es desproporcionadamente mayor que los otros tipos de actividades.

PASO 6 - ACTUAR

Observaciones y recomendaciones

Las observaciones y recomendaciones estan basadas en los conocimientos extraídos del análisis entre el 12/03/2016 y el 12/05/2016.

1)Los usuarios de Fitbit no usan su FitBit constantemente:

La aplicación Bellabeat;

  • Podría tener notificaciones que recuerden a los usuarios que deben usar su dispositivo para tener la mayor precisión de los datos.

  • Podría tener una función donde los usuarios podrían obtener hitos por el tiempo que llevan en el dispositivo.

  • Podría agregar una función que permita a los usuarios conectarse con amigos para comparar el tiempo de uso.

2) Los usuarios pasan mucho tiempo sedentarios. La OMS 2 recomiendan 150 ejercicios de actividad física a la semana, lo que equivale aproximadamente a 30 minutos al día

  • Si un usuario no puede estar activo por algún motivo, tener una función en la aplicación donde pueda decirle a la aplicación que no puede estar activo temporalmente.

  • Agregar una función a la aplicación donde el usuario pueda indicar sus intereses para que luego la aplicación pueda sugerir actividades como deportes, natación, caminatas, etc.

  • Configurar los recordatorios si el usuario permanece en modo sedentario durante un período de tiempo prolongado.

3) Los usuarios pasan tiempo en la cama sin dormir. Esto puede deberse al uso del smartphone mientras se están en la cama. La emisión de luz azul mientras se está en la cama puede afectar negativamente a los ciclos del sueño.

La aplicación Bellabeat;

  • Podría tener una función donde los usuarios puedan ingresar a su hora de acostarse. Así siendo, la aplicación puede rastrear el uso del tiempo de pantalla durante este periodo y enviar notificaciones para recordarles que reduzcan el tiempo de pantalla mientras estén en la cama.

  • Podría tener una sección donde se publican consejos y datos sobre el sueño para los usuarios que tienen problemas para conciliar el sueño y permanecer dormidos.

¡Muchas gracias!

Valéria Moreira - Publicist | Data analytics - - - - https://www.linkedin.com/in/valériamoreira - - - - - - - - https://sites.google.com/view/valeria-moreira/inicio?authuser=1


  1. ROCCC - Regional Old Colony Communications Center↩︎

  2. OMS - Organización Mundial de la Salud.↩︎