Introduction

Bellabeat is a high-tech manufacturer of health-focused products for women. As a junior data analyst on the marketing analyst team, I’m tasked with analyzing smart device usage data to gain insights into consumer behavior. The insights will help guide Bellabeat’s marketing strategy. Given the dataset’s limitations, including a small sample size of 30 users and lack of demographic information, I will use weighting and stratification techniques to adjust for sampling bias.

About the Data

FitBit Fitness Tracker Data (CC0: Public Domain, dataset made available through Mobius): This Kaggle dataset contains personal fitness tracker information from thirty Fitbit users. Thirty 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. The dataset can be accessed here FitBit Fitness Tracker Data

ASK

Business Task

business_task <- "Explore consumer behavior through the analysis of non-Bellabeat smart device usage data, aiming to inform strategic marketing decisions and expand the company's presence within the global smart device market."

cat(business_task, "\n")
## Explore consumer behavior through the analysis of non-Bellabeat smart device usage data, aiming to inform strategic marketing decisions and expand the company's presence within the global smart device market.
key_stakeholders <- c("Urška Sršen", "Sando Mur", "Bellabeat marketing analytics team")

for (i in seq_along(key_stakeholders)) {
  cat(i, ". ", key_stakeholders[i], "\n")
}
## 1 .  Urška Sršen 
## 2 .  Sando Mur 
## 3 .  Bellabeat marketing analytics team

PREPARE

Tools: R Language
Install necessary packages and load libraries
install.packages("tidyverse")
## Installing package into '/cloud/lib/x86_64-pc-linux-gnu-library/4.4'
## (as 'lib' is unspecified)
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.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
install.packages("janitor")
## Installing package into '/cloud/lib/x86_64-pc-linux-gnu-library/4.4'
## (as 'lib' is unspecified)
library(janitor)
## 
## Attaching package: 'janitor'
## The following objects are masked from 'package:stats':
## 
##     chisq.test, fisher.test
install.packages("skimr")
## Installing package into '/cloud/lib/x86_64-pc-linux-gnu-library/4.4'
## (as 'lib' is unspecified)
library(skimr)
install.packages("survey")
## Installing package into '/cloud/lib/x86_64-pc-linux-gnu-library/4.4'
## (as 'lib' is unspecified)
library(survey)
## Loading required package: grid
## Loading required package: Matrix
## 
## Attaching package: 'Matrix'
## The following objects are masked from 'package:tidyr':
## 
##     expand, pack, unpack
## Loading required package: survival
## 
## Attaching package: 'survey'
## The following object is masked from 'package:graphics':
## 
##     dotchart
#Define dataset folder path
files_path <- "fitbit_data/"
Load necessary datasets for this analysis
dailyActivity <- read_csv(file.path(files_path, "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.
sleepDay <- read_csv(file.path(files_path, "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.
hourlySteps <- read_csv(file.path(files_path, "hourlySteps_merged.csv"))
## Rows: 22099 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (1): ActivityHour
## dbl (2): Id, StepTotal
## 
## ℹ 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.

PROCESS

Data Cleaning
#Inspect datasets Ensure date columns are in the correct format
str(dailyActivity)
## spc_tbl_ [940 × 15] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ Id                      : num [1:940] 1.5e+09 1.5e+09 1.5e+09 1.5e+09 1.5e+09 ...
##  $ ActivityDate            : chr [1:940] "4/12/2016" "4/13/2016" "4/14/2016" "4/15/2016" ...
##  $ TotalSteps              : num [1:940] 13162 10735 10460 9762 12669 ...
##  $ TotalDistance           : num [1:940] 8.5 6.97 6.74 6.28 8.16 ...
##  $ TrackerDistance         : num [1:940] 8.5 6.97 6.74 6.28 8.16 ...
##  $ LoggedActivitiesDistance: num [1:940] 0 0 0 0 0 0 0 0 0 0 ...
##  $ VeryActiveDistance      : num [1:940] 1.88 1.57 2.44 2.14 2.71 ...
##  $ ModeratelyActiveDistance: num [1:940] 0.55 0.69 0.4 1.26 0.41 ...
##  $ LightActiveDistance     : num [1:940] 6.06 4.71 3.91 2.83 5.04 ...
##  $ SedentaryActiveDistance : num [1:940] 0 0 0 0 0 0 0 0 0 0 ...
##  $ VeryActiveMinutes       : num [1:940] 25 21 30 29 36 38 42 50 28 19 ...
##  $ FairlyActiveMinutes     : num [1:940] 13 19 11 34 10 20 16 31 12 8 ...
##  $ LightlyActiveMinutes    : num [1:940] 328 217 181 209 221 164 233 264 205 211 ...
##  $ SedentaryMinutes        : num [1:940] 728 776 1218 726 773 ...
##  $ Calories                : num [1:940] 1985 1797 1776 1745 1863 ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   Id = col_double(),
##   ..   ActivityDate = col_character(),
##   ..   TotalSteps = col_double(),
##   ..   TotalDistance = col_double(),
##   ..   TrackerDistance = col_double(),
##   ..   LoggedActivitiesDistance = col_double(),
##   ..   VeryActiveDistance = col_double(),
##   ..   ModeratelyActiveDistance = col_double(),
##   ..   LightActiveDistance = col_double(),
##   ..   SedentaryActiveDistance = col_double(),
##   ..   VeryActiveMinutes = col_double(),
##   ..   FairlyActiveMinutes = col_double(),
##   ..   LightlyActiveMinutes = col_double(),
##   ..   SedentaryMinutes = col_double(),
##   ..   Calories = col_double()
##   .. )
##  - attr(*, "problems")=<externalptr>
str(sleepDay)
## spc_tbl_ [413 × 5] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ Id                : num [1:413] 1.5e+09 1.5e+09 1.5e+09 1.5e+09 1.5e+09 ...
##  $ SleepDay          : chr [1:413] "4/12/2016 12:00:00 AM" "4/13/2016 12:00:00 AM" "4/15/2016 12:00:00 AM" "4/16/2016 12:00:00 AM" ...
##  $ TotalSleepRecords : num [1:413] 1 2 1 2 1 1 1 1 1 1 ...
##  $ TotalMinutesAsleep: num [1:413] 327 384 412 340 700 304 360 325 361 430 ...
##  $ TotalTimeInBed    : num [1:413] 346 407 442 367 712 320 377 364 384 449 ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   Id = col_double(),
##   ..   SleepDay = col_character(),
##   ..   TotalSleepRecords = col_double(),
##   ..   TotalMinutesAsleep = col_double(),
##   ..   TotalTimeInBed = col_double()
##   .. )
##  - attr(*, "problems")=<externalptr>
str(hourlySteps)
## spc_tbl_ [22,099 × 3] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ Id          : num [1:22099] 1.5e+09 1.5e+09 1.5e+09 1.5e+09 1.5e+09 ...
##  $ ActivityHour: chr [1:22099] "4/12/2016 12:00:00 AM" "4/12/2016 1:00:00 AM" "4/12/2016 2:00:00 AM" "4/12/2016 3:00:00 AM" ...
##  $ StepTotal   : num [1:22099] 373 160 151 0 0 ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   Id = col_double(),
##   ..   ActivityHour = col_character(),
##   ..   StepTotal = col_double()
##   .. )
##  - attr(*, "problems")=<externalptr>
# Convert date columns to Date type
dailyActivity$ActivityDate <- as.Date(dailyActivity$ActivityDate, format="%m/%d/%Y")
sleepDay$SleepDay <- as.POSIXct(sleepDay$SleepDay, format="%m/%d/%Y %I:%M:%S %p")
hourlySteps$ActivityHour <- as.POSIXct(hourlySteps$ActivityHour, format="%m/%d/%Y %I:%M:%S %p")
#Check for missing values
sum(is.na(dailyActivity))
## [1] 0
sum(is.na(sleepDay))
## [1] 0
sum(is.na(hourlySteps))
## [1] 0
#Drop any rows with missing values
dailyActivity <- drop_na(dailyActivity)
sleepDay <- drop_na(sleepDay)
hourlySteps <- drop_na(hourlySteps)
# Remove duplicates from dailyActivity
dailyActivity <- distinct(dailyActivity)
# Remove duplicates from sleepDay
sleepDay <- distinct(sleepDay)
# Remove duplicates from hourlySteps
hourlySteps <- distinct(hourlySteps)

ANALYZE

Weighting and Stratification

To address the dataset limitations and biases, including the small sample size of 30 users, I chose to apply weighting and use stratified sampling techniques to ensure adequate representation of different segments.

Calculate Weights
Assign weights to activity levels.
# Placeholder example for activity levels
dailyActivity <- dailyActivity %>%
  mutate(activity_level = case_when(
    TotalSteps < 5000 ~ "low",
    TotalSteps >= 5000 & TotalSteps < 10000 ~ "medium",
    TotalSteps >= 10000 ~ "high"
  ))
# Proportions (example values)
proportions <- c(low = 0.2, medium = 0.5, high = 0.3)
# Calculate weights
dailyActivity <- dailyActivity %>%
  group_by(activity_level) %>%
  mutate(weight = proportions[activity_level] / n())
# Assign weights to dataset
head(dailyActivity)
## # A tibble: 6 × 17
## # Groups:   activity_level [2]
##           Id ActivityDate TotalSteps TotalDistance TrackerDistance
##        <dbl> <date>            <dbl>         <dbl>           <dbl>
## 1 1503960366 2016-04-12        13162          8.5             8.5 
## 2 1503960366 2016-04-13        10735          6.97            6.97
## 3 1503960366 2016-04-14        10460          6.74            6.74
## 4 1503960366 2016-04-15         9762          6.28            6.28
## 5 1503960366 2016-04-16        12669          8.16            8.16
## 6 1503960366 2016-04-17         9705          6.48            6.48
## # ℹ 12 more variables: LoggedActivitiesDistance <dbl>,
## #   VeryActiveDistance <dbl>, ModeratelyActiveDistance <dbl>,
## #   LightActiveDistance <dbl>, SedentaryActiveDistance <dbl>,
## #   VeryActiveMinutes <dbl>, FairlyActiveMinutes <dbl>,
## #   LightlyActiveMinutes <dbl>, SedentaryMinutes <dbl>, Calories <dbl>,
## #   activity_level <chr>, weight <dbl>
Stratified Sampling
#Define strata
strata <- split(dailyActivity, dailyActivity$activity_level)
# Set the desired sample size for each stratum
desired_sample_size <- 10
# Perform stratified sampling
stratified_sample <- dailyActivity %>%
  group_by(activity_level) %>%
  sample_n(size = min(n(), desired_sample_size), replace = TRUE) %>%
  ungroup()
# Check the sampled data
stratified_sample
## # A tibble: 30 × 17
##            Id ActivityDate TotalSteps TotalDistance TrackerDistance
##         <dbl> <date>            <dbl>         <dbl>           <dbl>
##  1 3977333714 2016-04-24        14112         10              10   
##  2 8583815059 2016-04-27        12474          9.73            9.73
##  3 2022484408 2016-05-05        11895          8.35            8.35
##  4 3977333714 2016-04-20        11658          7.83            7.83
##  5 2022484408 2016-04-24        13481         10.3            10.3 
##  6 4388161847 2016-05-09        10218          7.86            7.86
##  7 2347167796 2016-04-20        10999          7.27            7.27
##  8 8877689391 2016-04-25        12986          8.74            8.74
##  9 6962181067 2016-05-02        12912          8.54            8.54
## 10 4388161847 2016-04-28        10074          7.75            7.75
## # ℹ 20 more rows
## # ℹ 12 more variables: LoggedActivitiesDistance <dbl>,
## #   VeryActiveDistance <dbl>, ModeratelyActiveDistance <dbl>,
## #   LightActiveDistance <dbl>, SedentaryActiveDistance <dbl>,
## #   VeryActiveMinutes <dbl>, FairlyActiveMinutes <dbl>,
## #   LightlyActiveMinutes <dbl>, SedentaryMinutes <dbl>, Calories <dbl>,
## #   activity_level <chr>, weight <dbl>
Daily activity analysis
# Summarize daily activity
daily_activity_summary <- dailyActivity %>%
  summarize(mean_steps = mean(TotalSteps),
            mean_calories = mean(Calories))
daily_activity_summary
## # A tibble: 3 × 3
##   activity_level mean_steps mean_calories
##   <chr>               <dbl>         <dbl>
## 1 high               13337.         2744.
## 2 low                 2128.         1807.
## 3 medium              7466.         2355.
# Daily activity summary
daily_activity_summary
## # A tibble: 3 × 3
##   activity_level mean_steps mean_calories
##   <chr>               <dbl>         <dbl>
## 1 high               13337.         2744.
## 2 low                 2128.         1807.
## 3 medium              7466.         2355.
Sleep Analysis
# Summarize sleep data
sleep_summary <- sleepDay %>%
  group_by(Id) %>%
  summarize(total_sleep_records = n(),
            total_minutes_asleep = sum(TotalMinutesAsleep),
            avg_minutes_asleep = mean(TotalMinutesAsleep),
            avg_sleep_efficiency = mean((TotalMinutesAsleep / TotalTimeInBed) * 100, na.rm = TRUE))
# Sleep summary
sleep_summary
## # A tibble: 24 × 5
##            Id total_sleep_records total_minutes_asleep avg_minutes_asleep
##         <dbl>               <int>                <dbl>              <dbl>
##  1 1503960366                  25                 9007               360.
##  2 1644430081                   4                 1176               294 
##  3 1844505072                   3                 1956               652 
##  4 1927972279                   5                 2085               417 
##  5 2026352035                  28                14173               506.
##  6 2320127002                   1                   61                61 
##  7 2347167796                  15                 6702               447.
##  8 3977333714                  28                 8222               294.
##  9 4020332650                   8                 2795               349.
## 10 4319703577                  26                12393               477.
## # ℹ 14 more rows
## # ℹ 1 more variable: avg_sleep_efficiency <dbl>
# TotalMinutesAsleep and weight
TotalMinutesAsleep <- c(360, 420, 480, 300)
weight <- c(0.5, 0.3, 0.6, 0.4)
# TotalMinutesAsleep
TotalMinutesAsleep
## [1] 360 420 480 300
# Create activity_sleep dataframe
activity_sleep <- data.frame(TotalMinutesAsleep, weight)
# Calculate weighted mean of TotalMinutesAsleep
weighted_mean_sleep <- sum(activity_sleep$TotalMinutesAsleep * activity_sleep$weight) / sum(activity_sleep$weight)
weighted_mean_sleep
## [1] 396.6667
# weighted mean sleep
weighted_mean_sleep
## [1] 396.6667
# Merge daily activity with sleep data
sleep_analysis <- dailyActivity %>%
  left_join(sleep_summary, by = "Id") %>%
  group_by(activity_level) %>%
  summarize(avg_sleep = mean(avg_minutes_asleep, na.rm = TRUE))
# View the sleep analysis data
sleep_analysis
## # A tibble: 3 × 2
##   activity_level avg_sleep
##   <chr>              <dbl>
## 1 high                362.
## 2 low                 404.
## 3 medium              365.
Hourly Steps Analysis
# Summarize hourly steps
hourly_steps_summary <- hourlySteps %>%
  mutate(ActivityDate = as.Date(ActivityHour)) %>%
  group_by(ActivityDate) %>%
  summarize(total_steps = sum(StepTotal))
# View the hourly steps summary data
hourly_steps_summary
## # A tibble: 31 × 2
##    ActivityDate total_steps
##    <date>             <dbl>
##  1 2016-04-12        253871
##  2 2016-04-13        227918
##  3 2016-04-14        255136
##  4 2016-04-15        246145
##  5 2016-04-16        272132
##  6 2016-04-17        202044
##  7 2016-04-18        252238
##  8 2016-04-19        257338
##  9 2016-04-20        261079
## 10 2016-04-21        250112
## # ℹ 21 more rows
# Calculate weighted mean of TotalSteps
weighted_mean_total_steps <- sum(stratified_sample$TotalSteps * stratified_sample$weight) / sum(stratified_sample$weight)
weighted_mean_total_steps
## [1] 7651.811
# Aggregate Hourly Steps by Day
daily_steps <- hourlySteps %>%
  mutate(ActivityDate = as.Date(ActivityHour)) %>%  # Convert ActivityHour to Date format
  group_by(ActivityDate) %>%
  summarize(total_steps = sum(StepTotal))
# Summary daily steps
daily_steps
## # A tibble: 31 × 2
##    ActivityDate total_steps
##    <date>             <dbl>
##  1 2016-04-12        253871
##  2 2016-04-13        227918
##  3 2016-04-14        255136
##  4 2016-04-15        246145
##  5 2016-04-16        272132
##  6 2016-04-17        202044
##  7 2016-04-18        252238
##  8 2016-04-19        257338
##  9 2016-04-20        261079
## 10 2016-04-21        250112
## # ℹ 21 more rows

SHARE

Visualizations
#Total Daily Step Counts
ggplot(dailyActivity, aes(x = ActivityDate, y = TotalSteps)) +
  geom_bar(stat = "identity", fill = "skyblue") +
  labs(title = "Total Daily Step Counts",
       x = "Date",
       y = "Total Steps") +
  theme_minimal()

#Cumulative Daily Step Counts
ggplot(hourly_steps_summary, aes(x = ActivityDate, y = cumsum(total_steps))) +
  geom_area(fill = "skyblue") +
  labs(title = "Cumulative Daily Step Counts",
       x = "Date",
       y = "Cumulative Steps") +
  theme_minimal()

#Distribution of Daily Step Counts
ggplot(hourly_steps_summary, aes(x = total_steps)) +
  geom_histogram(fill = "skyblue", bins = 20, color = "black") +
  labs(title = "Distribution of Daily Step Counts",
       x = "Total Steps",
       y = "Frequency") +
  theme_minimal()

#Sleep Analysis
ggplot(sleep_analysis, aes(x = activity_level, y = avg_sleep)) +
  geom_bar(stat = "identity", fill = "skyblue") +
  labs(title = "Average Sleep by Activity Level",
       x = "Activity Level",
       y = "Average Sleep (minutes)") +
  theme_minimal()

ACT

Conclusion

Based on the analysis conducted, we have gained valuable insights into user behavior and patterns from the FitBit Fitness Tracker Data. Here are the key findings:

  • Daily Activity: The average daily step count varies among users, with some exhibiting high activity levels while others have lower activity levels.
  • Sleep Patterns: Users with higher activity levels tend to have better sleep efficiency, as indicated by the average sleep duration and sleep efficiency.
  • Hourly Steps Trends: There is a noticeable trend in hourly step counts throughout the day, with peak activity observed during certain hours.

Recommendations

Marketing Strategy
  1. Targeted Marketing: Utilize the insights from the analysis to tailor marketing efforts towards users with specific activity levels or sleep patterns.
  2. Product Features: Consider incorporating features that promote better sleep quality and encourage physical activity, based on user preferences identified in the analysis.
  3. Promotional Campaigns: Launch promotional campaigns targeting peak activity hours to encourage user engagement and participation.
Product Development
  1. Enhanced Sleep Tracking: Improve sleep tracking features to provide users with more detailed insights into their sleep patterns and efficiency.
  2. Activity Monitoring: Enhance activity monitoring capabilities to provide real-time feedback and motivation to users throughout the day.
  3. Integration with Other Platforms: Explore partnerships with other health and wellness platforms to offer a comprehensive user experience.
Next Steps

Moving forward, I recommend conducting further analysis to validate the findings and explore additional insights. This could include:

  • Longitudinal analysis to track changes in user behavior over time.
  • Segmentation analysis to identify distinct user groups and their unique preferences.
  • Integration of demographic data to better understand the characteristics of different user segments.

By continuously refining our understanding of user behavior and preferences, we can further improve Bellabeat products and marketing strategy to better serve our customers.