Introduction

Hi, this is my BellaBeat case study. Here, we will process, analyze and share data collected from a wareable smart device in order to gain insight into how consumers are using their smart devices. Our client is Bellabeat, a smart device company based in San Francisco. Throughout this study, we will follow the data analysis process to answer key business questions and develop actionable insights.

Bellabeats’s goal: Become a larger player in the global smart device market.

Products:

App: track activity, sleep, stress, menstrual cycle, and mindfulness habits. Wearable Device: connects to the app to track activity, sleep, and stress. Watch: connects to the app to track activity, sleep, and stress. Water Bottle: connects to the app to track daily water intake. Subscription membership: personalized guidance on nutrition, activity, sleep, health, beauty, and mindfulness based on their lifestyle and goals.

Deliverables

We will deliver a clear statement of the business task. A description of all data sources used. Documentation of any cleaning or manipulation of data. A summary of our analysis. Supporting visualizations and key findings. Our top high-level insights based on our analysis.

Problem

BellaBeat is a successful health-tech manufacturer of health-focused products for women that would like to see their company grow. Based on review of consumer data they would like a high-level recommendations for how trends can inform Bella beat marketing strategy.

1. Ask

Analyze provided smart device usage data to gain insight into how consumers use non-Bellabeat smart devices. Select one Bellabeat product to apply these insights to in your presentation.

These questions will guide your analysis:

What are some trends in smart device usage? How could these trends apply to Bellabeat customers? *How could these trends help influence Bellabeat’s marketing strategy?

I will produce a report with the following deliverables: A clear summary of the business task A description of all data sources used: Documentation of any cleaning or manipulation of data A summary of your analysis Supporting visualizations and key findings My top high-level content recommendations based on your analysis

2. Prepare, upload, clean

This Kaggle data set contains personal fitness trackers from thirty-three Fitbit users. Thirty-three eligible Fitbit users consented to the submission of personal tracker data, including minute-level output for physical activity, heart rate, and sleep monitoring. It includes information about daily activity, steps, and heart rate that can be used to explore users’ habits.

Our goal is to essentially give broad strokes data about the Bellabeats users. We want to mainly focus on daily data and specifically the dailyActivity_merged file. The dailyActivity_merged has all the “daily” sheet titled columns already merged into it except sleep.

Upload

Upload Packages

.packages = c("tidyverse","lubridate","ggplot2","ggpubr","tidyr","janitor","readr","here", "dplyr","skimr")
.inst <- .packages %in% installed.packages()
if(length(.packages[!.inst]) > 0) install.packages(.packages[!.inst])
lapply(.packages, require, character.only=TRUE)
## 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.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
## Loading required package: ggpubr
## 
## Loading required package: janitor
## 
## 
## Attaching package: 'janitor'
## 
## 
## The following objects are masked from 'package:stats':
## 
##     chisq.test, fisher.test
## 
## 
## Loading required package: here
## 
## here() starts at /cloud/project
## 
## Loading required package: skimr
## [[1]]
## [1] TRUE
## 
## [[2]]
## [1] TRUE
## 
## [[3]]
## [1] TRUE
## 
## [[4]]
## [1] TRUE
## 
## [[5]]
## [1] TRUE
## 
## [[6]]
## [1] TRUE
## 
## [[7]]
## [1] TRUE
## 
## [[8]]
## [1] TRUE
## 
## [[9]]
## [1] TRUE
## 
## [[10]]
## [1] TRUE

Read data into dataframes

daily_activity <- read_csv("dailyActivity_merged.csv")
## Rows: 940 Columns: 15
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (1): ActivityDate
## dbl (14): Id, TotalSteps, TotalDistance, TrackerDistance, LoggedActivitiesDi...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
daily_sleep <- read_csv("sleepDay_merged.csv")
## Rows: 413 Columns: 5
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (1): SleepDay
## dbl (4): Id, TotalSleepRecords, TotalMinutesAsleep, TotalTimeInBed
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
n_distinct(daily_activity$Id) 
## [1] 33
n_distinct(daily_sleep$Id) 
## [1] 24
head(daily_activity)
## # A tibble: 6 × 15
##           Id ActivityDate TotalSteps TotalDistance TrackerDistance
##        <dbl> <chr>             <dbl>         <dbl>           <dbl>
## 1 1503960366 4/12/2016         13162          8.5             8.5 
## 2 1503960366 4/13/2016         10735          6.97            6.97
## 3 1503960366 4/14/2016         10460          6.74            6.74
## 4 1503960366 4/15/2016          9762          6.28            6.28
## 5 1503960366 4/16/2016         12669          8.16            8.16
## 6 1503960366 4/17/2016          9705          6.48            6.48
## # ℹ 10 more variables: LoggedActivitiesDistance <dbl>,
## #   VeryActiveDistance <dbl>, ModeratelyActiveDistance <dbl>,
## #   LightActiveDistance <dbl>, SedentaryActiveDistance <dbl>,
## #   VeryActiveMinutes <dbl>, FairlyActiveMinutes <dbl>,
## #   LightlyActiveMinutes <dbl>, SedentaryMinutes <dbl>, Calories <dbl>
head(daily_sleep)
## # A tibble: 6 × 5
##           Id SleepDay        TotalSleepRecords TotalMinutesAsleep TotalTimeInBed
##        <dbl> <chr>                       <dbl>              <dbl>          <dbl>
## 1 1503960366 4/12/2016 12:0…                 1                327            346
## 2 1503960366 4/13/2016 12:0…                 2                384            407
## 3 1503960366 4/15/2016 12:0…                 1                412            442
## 4 1503960366 4/16/2016 12:0…                 2                340            367
## 5 1503960366 4/17/2016 12:0…                 1                700            712
## 6 1503960366 4/19/2016 12:0…                 1                304            320
names(daily_activity)
##  [1] "Id"                       "ActivityDate"            
##  [3] "TotalSteps"               "TotalDistance"           
##  [5] "TrackerDistance"          "LoggedActivitiesDistance"
##  [7] "VeryActiveDistance"       "ModeratelyActiveDistance"
##  [9] "LightActiveDistance"      "SedentaryActiveDistance" 
## [11] "VeryActiveMinutes"        "FairlyActiveMinutes"     
## [13] "LightlyActiveMinutes"     "SedentaryMinutes"        
## [15] "Calories"
names(daily_sleep)
## [1] "Id"                 "SleepDay"           "TotalSleepRecords" 
## [4] "TotalMinutesAsleep" "TotalTimeInBed"

Clean duplicates, null data

sum(duplicated(daily_activity))
## [1] 0
sum(duplicated(daily_sleep))
## [1] 3

there are 3 duplicates in sleep lets remove them

duplicates <- daily_sleep[duplicated(daily_sleep) | duplicated(daily_sleep, fromLast = TRUE), ]

daily_sleep <- daily_sleep[!duplicated(daily_sleep), ]
sum(duplicated(daily_sleep))
## [1] 0
any(is.na(daily_activity))
## [1] FALSE
any(is.na(daily_sleep))
## [1] FALSE

There are no missing values

3. Process

reminder we are limiting our analysis to Sleep miniutes, Activity (VeryActiveMiniutes,FairlyActiveMiniutes,LightlyActiveMiniutes,TotalActiveMiniutes) *Calories per Day

daily_activity%>%
  select(VeryActiveMinutes, FairlyActiveMinutes, LightlyActiveMinutes) %>%
  summary()
##  VeryActiveMinutes FairlyActiveMinutes LightlyActiveMinutes
##  Min.   :  0.00    Min.   :  0.00      Min.   :  0.0       
##  1st Qu.:  0.00    1st Qu.:  0.00      1st Qu.:127.0       
##  Median :  4.00    Median :  6.00      Median :199.0       
##  Mean   : 21.16    Mean   : 13.56      Mean   :192.8       
##  3rd Qu.: 32.00    3rd Qu.: 19.00      3rd Qu.:264.0       
##  Max.   :210.00    Max.   :143.00      Max.   :518.0
daily_activity %>%  
  select(Calories) %>%
  summary()
##     Calories   
##  Min.   :   0  
##  1st Qu.:1828  
##  Median :2134  
##  Mean   :2304  
##  3rd Qu.:2793  
##  Max.   :4900
daily_sleep %>%  
  select(TotalMinutesAsleep) %>%
  summary()
##  TotalMinutesAsleep
##  Min.   : 58.0     
##  1st Qu.:361.0     
##  Median :432.5     
##  Mean   :419.2     
##  3rd Qu.:490.0     
##  Max.   :796.0

create a new variable TotalActiveMinutes

daily_activity <- daily_activity %>%
  mutate(TotalActiveMinutes = VeryActiveMinutes + FairlyActiveMinutes + LightlyActiveMinutes)

I am going to want to merge data from Sleep and Daily Activity First I will reformat date columns in each to match

Rename SleepDay column in daily_sleep to date

daily_sleep <- daily_sleep %>%
  rename(date = SleepDay)
daily_activity <- daily_activity %>%
  rename(date = ActivityDate)

Convert date columns to the desired format MM/DD/YYYY First, remove unneeded time info from date in sleep

daily_sleep$date <- sub(" .*", "", daily_sleep$date)
daily_sleep$date <- as.Date(daily_sleep$date, format = "%m/%d/%Y")
daily_activity$date <- as.Date(daily_activity$date, format = "%m/%d/%Y")

4. Analyze

Activity vs Calories

ggplot(daily_activity, aes(x = TotalActiveMinutes, y = Calories)) +
  geom_point()

Activity vs Sleep

# Merge data based on Id and date columns
merged_tactive_sleep <- merge(daily_activity[, c("Id", "date", "TotalActiveMinutes")], 
                              daily_sleep[, c("Id", "TotalMinutesAsleep", "date")], 
                              by = c("Id", "date"), all = TRUE)

ggplot(merged_tactive_sleep, aes(x = TotalActiveMinutes, y = TotalMinutesAsleep)) +
  geom_point()
## Warning: Removed 530 rows containing missing values or values outside the scale range
## (`geom_point()`).

# Merge data based on Id and date columns
merged_calories_sleep <- merge(daily_activity[, c("Id", "date", "Calories")], 
                              daily_sleep[, c("Id", "TotalMinutesAsleep", "date")], 
                              by = c("Id", "date"), all = TRUE)
ggplot(merged_calories_sleep, aes(x = Calories, y = TotalMinutesAsleep)) +
  geom_point()
## Warning: Removed 530 rows containing missing values or values outside the scale range
## (`geom_point()`).

There appears to be a relationship between Calories and Activity Minutes We can investigate further by looking at different levels of activity vs calories

5. Share

Lets create 3 plots to see if there is a level of activity relates to calories more than the others Lightly Active Minutes vs Calories Moderately Active Minutes vs Calories *Very Active Minutes vs Calories

Lightly Active Minutes vs Calories

# Merge data based on Id and date columns
merged_lightly_calories <- merge(daily_activity[, c("Id", "date", "LightlyActiveMinutes")], 
                                 daily_activity[, c("Id", "Calories", "date")], 
                                 by = c("Id", "date"), all = TRUE)

# Remove rows with missing values
merged_lightly_calories <- na.omit(merged_lightly_calories)
# Remove rows with zero value for LightlyActiveMinutes
merged_lightly_calories <-merged_lightly_calories[merged_lightly_calories$LightlyActiveMinutes != 0, ]
# Remove rows with zero value for Calories
merged_lightly_calories <-merged_lightly_calories[merged_lightly_calories$Calories != 0, ]


options(repr.plot.width = 16, repr.plot.height = 10)

# Plot LightlyActiveMinutes vs Calories
ggplot(merged_lightly_calories, aes(x = LightlyActiveMinutes, y = Calories)) +
  geom_point(color = "pink", size = 1, alpha = 0.7) +
  geom_smooth(color = "purple", linetype = "dashed", linewidth = 1) +
  labs(title = "Lightly Active Minutes vs. Calories", x = "Lightly Active Minutes", y = "Total Calories") +
  theme_minimal() +
  theme(plot.title = element_text(size = 30, face = "bold"),
        axis.text.x = element_text(size = 24),
        axis.text.y = element_text(size = 24),
        axis.title = element_text(size = 24),
        panel.grid = element_line(color = "gray", linetype = "dashed"))
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

Fairly Active Minutes vs Calories

# Merge data based on Id and date columns
merged_f_active_calories <- merge(daily_activity[, c("Id", "date", "FairlyActiveMinutes")], 
                                 daily_activity[, c("Id", "Calories", "date")], 
                                 by = c("Id", "date"), all = TRUE)

# Remove rows with missing values
merged_f_active_calories <- na.omit(merged_f_active_calories)
# Remove rows with zero value for FairlyActiveMinutes
merged_f_active_calories <-merged_f_active_calories[merged_f_active_calories$LightlyActiveMinutes != 0, ]
# Remove rows with zero value for Calories
merged_f_active_calories <-merged_f_active_calories[merged_f_active_calories$Calories != 0, ]


options(repr.plot.width = 16, repr.plot.height = 10)

# Plot FairlyActiveMinutes vs Calories
ggplot(merged_f_active_calories, aes(x = FairlyActiveMinutes, y = Calories)) +
  geom_point(color = "blue", size = 1, alpha = 0.7) +
  geom_smooth(color = "purple", linetype = "dashed", linewidth = 1) +
  labs(title = "Fairly Active Minutes vs. Calories", x = "Fairly Active Minutes", y = "Total Calories") +
  theme_minimal() +
  theme(plot.title = element_text(size = 30, face = "bold"),
        axis.text.x = element_text(size = 24),
        axis.text.y = element_text(size = 24),
        axis.title = element_text(size = 24),
        panel.grid = element_line(color = "gray", linetype = "dashed"))

#VERYActiveMinutes vs Calories

# Merge data based on Id and date columns
merged_vactive_calories <- merge(daily_activity[, c("Id", "date", "VeryActiveMinutes")], 
                                 daily_activity[, c("Id", "Calories", "date")], 
                                 by = c("Id", "date"), all = TRUE)

# Remove rows with missing values
merged_vactive_calories <- na.omit(merged_vactive_calories)
# Remove rows with zero value for VeryActiveMinutes
merged_vactive_calories <-merged_vactive_calories[merged_vactive_calories$VeryActiveMinutes != 0, ]
# Remove rows with zero value for Calories
merged_lightly_calories <-merged_lightly_calories[merged_lightly_calories$Calories != 0, ]

options(repr.plot.width = 16, repr.plot.height = 10)

# Plot VERYActiveMinutes vs Calories
ggplot(merged_vactive_calories, aes(x = VeryActiveMinutes, y = Calories)) +
  geom_point(color = "pink", size = 3, alpha = 0.7) +
  geom_smooth(color = "purple", linetype = "dashed", linewidth = 1) +
  labs(title = "V.Active Minutes vs. Calories", x = "Very Active Minutes", y = "Total Calories") +
  theme_minimal() +
  theme(plot.title = element_text(size = 30, face = "bold"),
        axis.text.x = element_text(size = 24),
        axis.text.y = element_text(size = 24),
        axis.title = element_text(size = 24),
        panel.grid = element_line(color = "gray", linetype = "dashed"))
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

6. Act

There is a positive relationship between activity and calories that is most pronounced during very active times. And is more pronounced after approximately 30 minutes of very active time.

This strong relationship suggests that customers could be encouraged to increasing tracking of intensity of activity and calories. Tracking calories could be made easier by encorporating the latest software that calculates calories based on a photo taken of the food.

Next steps. Conclusions could be strengthened by review of actual data from BellaBeats over a longer time period of time.