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.
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
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
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/"
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.
#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)
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.
# 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>
#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>
# 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.
# 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.
# 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
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:
Moving forward, I recommend conducting further analysis to validate the findings and explore additional insights. This could include:
By continuously refining our understanding of user behavior and preferences, we can further improve Bellabeat products and marketing strategy to better serve our customers.