Project Summary

Business Problem

Based on available materials following business case for this project is to identify new sales strategies to increase market presence through targeted product offering. For this purpose main goals of the project are: * better understand trands of how customers use smart devices * identify new growth oppounities for Bellabeat * suggest marketing strategy changes to benefit from the findings

Characters

  • Urška Sršen: Bellabeat’s cofounder and Chief Creative Ocer
  • Sando Mur: Mathematician and Bellabeat’s cofounder; key member of the Bellabeat executive team
  • Bellabeat marketing analytics team: A team of data analysts responsible for collecting, analyzing, and repoing data that helps guide Bellabeat’s marketing strategy. You joined this team six months ago and have been busy learning about Bellabeat’’s mission and business goals — as well as how you, as a junior data analyst, can help Bellabeat achieve them.

Bellabeat Products

  • Bellabeat app: The Bellabeat app provides users with health data related to their activity, sleep, stress, menstrual cycle, and mindfulness habits. This data can help users beer understand their current habits and make healthy decisions. The Bellabeat app connects to their line of sma wellness products.
  • Leaf: Bellabeat’s classic wellness tracker can be worn as a bracelet, necklace, or clip. The Leaf tracker connects to the Bellabeat app to track activity, sleep, and stress.
  • Time: This wellness watch combines the timeless look of a classic timepiece with sma technology to track useractivity, sleep, and stress. The Time watch connects to the Bellabeat app to provide you with insights into your daily wellness.
  • Spring: This is a water bole that tracks daily water intake using sma technology to ensure that you are appropriately hydrated throughout the day. The Spring bole connects to the Bellabeat app to track your hydration levels.
  • Bellabeat membership: Bellabeat also oers a subscription-based membership program for users. Membership gives users 24/7 access to fully personalized guidance on nutrition, activity, sleep, health and beauty, and mindfulness based on their lifestyle and goals.

Project scope

  • Find and document the data source
  • Filter and clean the data
  • Conduct analysis
  • Visualize results
  • Provide recommendations

Execution

Init

library(stringr)
library(readr)
library(ggplot2)
library(janitor)
## 
## Attaching package: 'janitor'
## The following objects are masked from 'package:stats':
## 
##     chisq.test, fisher.test
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(lubridate)
## 
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
## 
##     date, intersect, setdiff, union

Prepare & Process

Data Source

For this project a FitBit Fitness Tracker Data has been taken. It’s a public data distributed under the CC0: Public Domain license.

Data quality is low, data is old, purly formated, sample is small and missing a lot of values. Decided to do cleansing and initial analysis as is and then see if any conclusions possible with existing data or it’s need to be enhanced.

I won’t change original file names for sake data source will be updated. Instead i standartize them on the loading phase to have properly named datasets.

Here are the original datasets:

print (list.files ("downloads/FitData"))
##  [1] "dailyActivity_merged.csv"           "heartrate_seconds_merged.csv"      
##  [3] "hourlyCalories_merged.csv"          "hourlyIntensities_merged.csv"      
##  [5] "hourlySteps_merged.csv"             "minuteCaloriesNarrow_merged.csv"   
##  [7] "minuteIntensitiesNarrow_merged.csv" "minuteMETsNarrow_merged.csv"       
##  [9] "minuteSleep_merged.csv"             "minuteStepsNarrow_merged.csv"      
## [11] "weightLogInfo_merged.csv"
Load Data

For loading I use read_csv and suppressing messages to have clearer output.

activity_d <- suppressMessages(read_csv("Downloads/FitData/dailyActivity_merged.csv"))
calories_h <- suppressMessages(read_csv("Downloads/FitData/hourlyCalories_merged.csv"))
intensities_h <- suppressMessages(read_csv("Downloads/FitData/hourlyIntensities_merged.csv"))
steps_h <- suppressMessages(read_csv("Downloads/FitData/hourlySteps_merged.csv"))
sleep_m <- suppressMessages(read_csv("Downloads/FitData/minuteSleep_merged.csv"))
log_info <- suppressMessages(read_csv("Downloads/FitData/weightLogInfo_merged.csv"))
heart_rate_s <- suppressMessages(read_csv("Downloads/FitData/heartrate_seconds_merged.csv"))
Check missing values

Using the below function to verify if there are any N/A in the data. Nothing have found.

# colSums(is.na(" Your_Dataset_Name "))
Verify Duplicates.
# sum(duplicated( Your_Dataset_Name ))
sum(duplicated(sleep_m))
## [1] 525
# Sleep had returned 525 values. Checked them. Not real ones
head(sleep_m[duplicated(sleep_m), ])
## # A tibble: 6 × 4
##           Id date                 value       logId
##        <dbl> <chr>                <dbl>       <dbl>
## 1 4319703577 4/5/2016 10:50:00 PM     3 11344563687
## 2 4319703577 4/5/2016 10:51:00 PM     3 11344563687
## 3 4319703577 4/5/2016 10:52:00 PM     2 11344563687
## 4 4319703577 4/5/2016 10:53:00 PM     2 11344563687
## 5 4319703577 4/5/2016 10:54:00 PM     2 11344563687
## 6 4319703577 4/5/2016 10:55:00 PM     1 11344563687
Clean column names for consistency
sleep_m <- clean_names(sleep_m)
steps_h <- clean_names(steps_h)
activity_d <- clean_names(activity_d)
calories_h <- clean_names(calories_h)
intensities_h <- clean_names(intensities_h)
log_info <- clean_names(log_info)
heart_rate_s <- clean_names(heart_rate_s)
Clean date-time format
#creating time column
sleep_m$time <- as.POSIXct(sleep_m$date, format = "%m/%d/%Y %I:%M:%S %p")
steps_h$time <- as.POSIXct(steps_h$activity_hour, format = "%m/%d/%Y %I:%M:%S %p")
activity_d$date <- as.POSIXct(activity_d$activity_date, format = "%m/%d/%Y")
calories_h$time <- as.POSIXct(calories_h$activity_hour, format = "%m/%d/%Y %I:%M:%S %p")
intensities_h$time <- as.POSIXct(intensities_h$activity_hour, format = "%m/%d/%Y %I:%M:%S %p")
log_info$time <- as.POSIXct(log_info$date, format = "%m/%d/%Y %I:%M:%S %p")
heart_rate_s$time <- as.POSIXct(heart_rate_s$time, format = "%m/%d/%Y %I:%M:%S %p")
# creating date column
sleep_m$date <- as.Date(sleep_m$time)  
steps_h$date <- as.Date(steps_h$time)  
activity_d$date <- as.Date(activity_d$date)  
calories_h$date <- as.Date(calories_h$time)  
intensities_h$date <- as.Date(intensities_h$time)  
log_info$date <- as.Date(log_info$time)  
heart_rate_s$date <- as.Date(heart_rate_s$time)  
Create “hourly timed” datasets for heart_rate and sleep
# Create heart rate H dataset
heart_rate_h <- heart_rate_s %>%
  mutate(
    time = floor_date(as.POSIXct(time, format = "%Y-%m-%d %H:00:00"), unit = "hour")
  ) %>%
  group_by(id, date, time) %>%
  summarise(
    average_heart_rate = as.integer(round(mean(value, na.rm = TRUE))),
    .groups = "drop"
  )

# Process sleep H dataset
sleep_h <- sleep_m %>%
  mutate(
    time = floor_date(as.POSIXct(time, format = "%Y-%m-%d %H:00:00"), unit = "hour")
  ) %>%
  group_by(id, date, time) %>%
  summarise(
    is_sleeping = as.integer(n() > 0),
    .groups = "drop"
  )

Analyse

Create calendar dataset

This is needed to understand the timeframe and maybe further use to combine statistic

activity_dates <- activity_d %>% distinct(date)  
steps_dates <- steps_h %>% distinct(date)  
calendar <- bind_rows(activity_dates, steps_dates) %>%
  distinct(date) %>%
  arrange(date) %>%
  filter(!is.na(date))

rm (activity_dates,steps_dates)

head(calendar)
## # A tibble: 6 × 1
##   date      
##   <date>    
## 1 2016-03-12
## 2 2016-03-13
## 3 2016-03-14
## 4 2016-03-15
## 5 2016-03-16
## 6 2016-03-17
Create profile dataset

This is needed to have a unified view on user

Used data from: * log_info * activity_d * heart_rate_s * steps_h * calories_h

rm(profiles)
## Warning in rm(profiles): object 'profiles' not found
# Calculate average_sleep_time per day 
average_sleep_per_day <- sleep_h %>%
  group_by(id, date) %>%
  summarize(daily_sleep_time = sum(is_sleeping == 1, na.rm = TRUE)) %>%
  ungroup() %>%
  group_by(id) %>%
  summarize(average_sleep_h = round(mean(daily_sleep_time, na.rm = TRUE), digits=1))
## `summarise()` has grouped output by 'id'. You can override using the `.groups`
## argument.
# Calculate average steps per day 
average_steps_per_day <- steps_h %>%
  group_by(id, date) %>%
  summarize(daily_steps = sum(step_total, na.rm = TRUE)) %>%
  ungroup() %>%
  group_by(id) %>%
  summarize(average_steps_per_day = as.integer(mean(daily_steps, na.rm = TRUE)))
## `summarise()` has grouped output by 'id'. You can override using the `.groups`
## argument.
# Calculate average calories per day 
average_cal_per_day <- calories_h %>%
  group_by(id, date) %>%
  summarize(daily_calories = sum(calories, na.rm = TRUE)) %>%
  ungroup() %>%
  group_by(id) %>%
  summarize(average_calories_per_day = as.integer(mean(daily_calories, na.rm = TRUE)))
## `summarise()` has grouped output by 'id'. You can override using the `.groups`
## argument.
# Calculate average heart rate by id
average_heart_rate <- heart_rate_s %>%
  group_by(id) %>%
  summarize(average_heart_rate = as.integer(mean(value, na.rm = TRUE)))

# Calculate the activity count by id 
activities_per_day <- activity_d %>%
  group_by(id) %>%
  summarize(
    activities_per_day = round(n()/nrow(calendar), 1) 
  ) 

# Create the profiles dataset and merge all required fields
profiles <- log_info %>%
  group_by(id) %>%
  summarize(
    weight_kg = round(mean(weight_kg, na.rm = TRUE), digits = 1),
    bmi = round(mean(bmi, na.rm = TRUE), digits = 2)
  ) %>%
  full_join(activities_per_day, by = "id") %>%
  full_join(average_sleep_per_day, by = "id") %>%
  full_join(average_heart_rate, by = "id") %>%
  full_join(average_steps_per_day, by = "id") %>%
  full_join(average_cal_per_day, by = "id")

#remove temp dataset
rm(activities_per_day, average_heart_rate, average_sleep_per_day, average_steps_per_day, average_cal_per_day)

# enhance with additional category for analysis based on heart_rate
profiles <- profiles %>%
  mutate(heart_rating = case_when(
    average_heart_rate >= 0 & average_heart_rate < 70 ~ "superhero",
    average_heart_rate >= 70 & average_heart_rate < 85 ~ "norm",
    average_heart_rate >= 85 ~ "attention",
    TRUE ~ "ghost"
  ))

# View the result
head(profiles)
## # A tibble: 6 × 9
##         id weight_kg   bmi activities_per_day average_sleep_h average_heart_rate
##      <dbl>     <dbl> <dbl>              <dbl>           <dbl>              <int>
## 1   1.50e9      53.3  23.0                0.6             7.2                 NA
## 2   1.93e9     130.   46.2                0.4             7.5                 NA
## 3   2.35e9      63.4  24.8                0.5             8.1                 76
## 4   2.87e9      57    21.6                0.4            NA                   NA
## 5   2.89e9      88.4  25.0                0.2            NA                   NA
## 6   4.45e9      92.4  35.0                0.5             7.3                 NA
## # ℹ 3 more variables: average_steps_per_day <int>,
## #   average_calories_per_day <int>, heart_rating <chr>
Gather measurements into one table connected to user and combined on hourly basis
## Measures --------------------------------------
measures <- intensities_h %>%
  full_join(calories_h, by = c("id", "time")) %>%
  full_join(steps_h, by = c("id", "time")) %>%
  select(id, time, date, intensity = total_intensity, steps = step_total, calories)

measures$date <- as.Date(measures$time)

#remove NA values
measures <- measures %>% filter(!is.na(time))

#add coefs for adjusting measured values for better visualization on a plot
intensity_coef <- 1  # Adjust as needed for better visualization
calories_coef <- 0.1  # Adjust to match the scale of intensity and steps
steps_coef <- 0.03  # Adjust to match the scale of intensity and calories

#adding scaled intesity, calories and steps
measures <- measures %>%
  mutate(
    scaled_intensity = intensity * intensity_coef,
    scaled_calories = calories * calories_coef,
    scaled_steps = steps * steps_coef
    )
rm(intensity_coef, calories_coef, steps_coef )
  
#adding time_of_day category
measures$time_of_day <- case_when(
  lubridate::hour(measures$time) >= 6 & lubridate::hour(measures$time) < 12 ~ "Morning",
  lubridate::hour(measures$time) >= 12 & lubridate::hour(measures$time) < 15 ~ "Day",
  lubridate::hour(measures$time) >= 15 & lubridate::hour(measures$time) < 18 ~ "Afternoon",
  lubridate::hour(measures$time) >= 18 & lubridate::hour(measures$time) < 21 ~ "Evening",
  lubridate::hour(measures$time) >= 21 | lubridate::hour(measures$time) < 6 ~ "Night",
  TRUE ~ "Unknown"
)

# Ensure the time_of_day is ordered correctly
measures$time_of_day <- factor(measures$time_of_day, 
                                levels = c("Morning", "Day", "Afternoon", "Evening", "Night")
                               # labels = c(1, 2, 3, 4, 5)
                               )
#adding hour field for further analysis
measures <- measures %>%
  mutate(hour = hour(time))

# Check the results
#table(measures$time_of_day)
#str(measures$time_of_day)

# View the resulting dataset
head(measures)
## # A tibble: 6 × 11
##           id time                date       intensity steps calories
##        <dbl> <dttm>              <date>         <dbl> <dbl>    <dbl>
## 1 1503960366 2016-03-12 00:00:00 2016-03-12         0     0       48
## 2 1503960366 2016-03-12 01:00:00 2016-03-12         0     0       48
## 3 1503960366 2016-03-12 02:00:00 2016-03-12         0     0       48
## 4 1503960366 2016-03-12 03:00:00 2016-03-12         0     0       48
## 5 1503960366 2016-03-12 04:00:00 2016-03-12         0     0       48
## 6 1503960366 2016-03-12 05:00:00 2016-03-12         0     0       48
## # ℹ 5 more variables: scaled_intensity <dbl>, scaled_calories <dbl>,
## #   scaled_steps <dbl>, time_of_day <fct>, hour <int>

Show Data

Dependancy between activities, steps and heart rate.

This shows us that not all people have their heart rate tracked properly during their activities. We should target to that users category a heart_rate functionality of our devices and populate the understanding of benefits from this information being available for end user health.

Other than that it’s lack of data to make any justified conclusions about corelation between heart_rate and daily_activities.

Ghost is someone for whome we do not have info about heart_rate.

ggplot(profiles, aes(x = average_steps_per_day, y = activities_per_day, shape = heart_rating, color = heart_rating, size=average_calories_per_day)) +
  geom_point(stat = "identity") +
  labs(
    title = "Average Activities Per Day by User",
    x = "Steps Per Day",
    y = "Activities Per Day"
  ) +
  theme_minimal()
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_point()`).

##### Calories burned by user + steps While there is a strong corelation between number of steps and burned calories by user. Analysis shows us that there are users who burn calories without significant amount of steps. Which mean they actively perform some other exercises. We need to encourage this category of users utilize benefits of our product for their activities tracking

ggplot(measures, aes(x = as.factor(id))) +
  geom_boxplot(mapping = aes(y = calories), fill = "purple", color = "black", alpha = 0.7) +
  stat_summary(mapping = aes(y = steps, color = "Average Steps"), 
               fun = "mean", geom = "line", group = 1, size = 1) +
  labs(
    title = "Calories Burned by User",
    x = "User",
    y = "Calories",
    color = "Legend"
  ) +
  scale_color_manual(values = c("Average Steps" = "blue")) +
  theme_minimal()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

##### Calories, Intensivity and Steps distribution through the day This just confirmed that people are more active during the day.

ggplot(measures, aes(x = hour)) +
  geom_smooth(aes(y = scaled_intensity, color = "Intensity"), size = 1) +
  geom_smooth(aes(y = scaled_calories, color = "Calories"), size = 1) +
  geom_smooth(aes(y = scaled_steps, color = "Steps"), size = 1) +
  labs(
    title = "Scaled Intensity, Calories, and Steps by Hour",
    x = "Hour of the Day",
    y = "Scaled Values",
    color = "Metrics"
  ) +
  scale_color_manual(values = c("Intensity" = "blue", "Calories" = "red", "Steps" = "green")) +
  theme_minimal()
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'

##### Calories, Intensivity and Steps distribution through the measured period This showing that at the end of month users were less active. Need to research more data from other monthes and get insides from domain experts to make some conclusions. It might be related to a spring term break at school or connected to “monthly habbits”

# Create a plot for the 32 days showing intensity, calories, and steps
ggplot(measures, aes(x = date)) +
  geom_smooth(aes(y = scaled_intensity, color = "Intensity"), size = 1) +
  geom_smooth(aes(y = scaled_calories, color = "Calories"), size = 1) +
  geom_smooth(aes(y = scaled_steps, color = "Steps"), size = 1) +
  labs(
    title = "Intensity, Calories, and Steps Over 32 Days",
    x = "Date",
    y = "Scaled Values",
    color = "Metrics"
  ) +
  scale_color_manual(values = c("Intensity" = "blue", "Calories" = "red", "Steps" = "green")) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'

Recommendations

  • Send reminders and motivational messages during peak activity hours to encourage consistent movement.
  • Provide personalized fitness goals and activity recommendations based on user data.
  • Introduce gamification elements like badges and challenges to increase engagement.
  • Promote balanced sleep habits with personalized tips and challenges for consistent rest.
  • Offer heart-rate-specific workouts and mindfulness activities to balance intensity and relaxation.
  • Highlight community trends to foster engagement through leaderboards and group challenges.
  • Target specific times of day with tailored campaigns (e.g., morning workouts, evening relaxation).
  • Educate users on the interconnectedness of steps, calories, intensity, sleep, and heart rate for overall wellness.
  • Develop holistic wellness challenges that incorporate multiple metrics like sleep, activity, and calorie burn.
  • Send personalized nudges to re-engage users showing signs of inactivity or disengagement.
  • Launch community-driven goals that encourage collective achievements and rewards.
  • Create time-based campaigns to promote healthy habits tailored to daily routines.