Executive Summary

This project analyzes public Fitbit fitness-tracker data to identify patterns in activity and sleep behavior and translate those patterns into high-level marketing implications for Bellabeat. The analysis uses two daily-level datasets: dailyActivity_merged.csv and sleepDay_merged.csv.

The portfolio version of the analysis follows the case-study framework of Ask, Prepare, Process, Analyze, Share, and Act, while expanding the original notebook with explicit data-quality checks, reproducible transformations, relationship analysis, and interpretation.

1. Ask: Business Task and Research Questions

Bellabeat is a wellness technology company whose products connect users with information about activity, sleep, stress, hydration, and other aspects of daily wellness. The business task is to use non-Bellabeat smart-device data to understand user behavior and identify insights that can inform Bellabeat’s marketing strategy.

The analysis addresses the original case-study questions:

  1. What are some trends in smart-device usage?
  2. How could these trends apply to Bellabeat customers?
  3. How could these trends help influence Bellabeat marketing strategy?

The analysis focuses on the Bellabeat app as the product context because the app can connect multiple wellness behaviors and translate tracker data into personalized guidance.

2. Prepare: Data Sources and Scope

The source case study identifies the FitBit Fitness Tracker Data, made available through Mobius, as the principal public dataset. The full source package contains multiple files at daily, hourly, and minute-level resolutions.

For this project, two files were selected:

The two files are complementary rather than redundant: the activity table provides a broad daily activity profile, while the sleep table adds a second dimension of wellness behavior.

Data limitations

The dataset contains observations from a small group of Fitbit users over approximately one month. The activity file contains 33 distinct users, while the sleep file contains 24 distinct users. The dataset therefore does not represent a broad population sample, and the absence of demographic variables means that findings cannot be assumed to describe Bellabeat’s female customer base specifically.

The data are also from 2016 and therefore should be interpreted as behavioral evidence from an older smart-device ecosystem rather than as a current estimate of consumer behavior.

3. Process: Data Import, Cleaning, and Validation

3.1 Load packages

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.4
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.4.4     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.0
## ✔ 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(here)
## here() starts at /Users/mac/Downloads
library(skimr)
library(janitor)
## 
## Attaching package: 'janitor'
## 
## The following objects are masked from 'package:stats':
## 
##     chisq.test, fisher.test
library(readr)
library(lubridate)

3.2 Import data

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.
sleep_day <- 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.

3.3 Initial inspection

head(daily_activity)
glimpse(daily_activity)
## Rows: 940
## Columns: 15
## $ Id                       <dbl> 1503960366, 1503960366, 1503960366, 150396036…
## $ ActivityDate             <chr> "4/12/2016", "4/13/2016", "4/14/2016", "4/15/…
## $ TotalSteps               <dbl> 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        <dbl> 25, 21, 30, 29, 36, 38, 42, 50, 28, 19, 66, 4…
## $ FairlyActiveMinutes      <dbl> 13, 19, 11, 34, 10, 20, 16, 31, 12, 8, 27, 21…
## $ LightlyActiveMinutes     <dbl> 328, 217, 181, 209, 221, 164, 233, 264, 205, …
## $ SedentaryMinutes         <dbl> 728, 776, 1218, 726, 773, 539, 1149, 775, 818…
## $ Calories                 <dbl> 1985, 1797, 1776, 1745, 1863, 1728, 1921, 203…
colnames(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"
head(sleep_day)
glimpse(sleep_day)
## 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  <dbl> 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ TotalMinutesAsleep <dbl> 327, 384, 412, 340, 700, 304, 360, 325, 361, 430, 2…
## $ TotalTimeInBed     <dbl> 346, 407, 442, 367, 712, 320, 377, 364, 384, 449, 3…
colnames(sleep_day)
## [1] "Id"                 "SleepDay"           "TotalSleepRecords" 
## [4] "TotalMinutesAsleep" "TotalTimeInBed"
n_distinct(daily_activity$Id)
## [1] 33
n_distinct(sleep_day$Id)
## [1] 24
nrow(daily_activity)
## [1] 940
nrow(sleep_day)
## [1] 413

The activity table contains 940 daily observations from 33 users. The sleep table contains 413 records from 24 users.

3.4 Standardize names and dates

A key improvement over the original notebook is that cleaning operations are assigned back to the data frames so that the cleaned objects are actually used in subsequent analysis.

daily_activity <- daily_activity %>%
  clean_names() %>%
  mutate(activity_date = mdy(activity_date))

sleep_day <- sleep_day %>%
  clean_names() %>%
  mutate(sleep_day = mdy_hms(sleep_day))

3.5 Check missing values and duplicates

sum(is.na(daily_activity))
## [1] 0
sum(is.na(sleep_day))
## [1] 0
sum(duplicated(daily_activity))
## [1] 0
sum(duplicated(sleep_day))
## [1] 3
sleep_day %>%
  count(id, sleep_day) %>%
  filter(n > 1)

The supplied files contain no missing values. The activity table has no duplicate rows. The sleep table contains three exact duplicate records; these are removed before merging.

sleep_day <- sleep_day %>%
  distinct()

sleep_day <- sleep_day %>%
  distinct(id, sleep_day, .keep_all = TRUE)

3.6 Data-quality observations

The activity dataset contains some zero-step days and days with 1,440 sedentary minutes. These observations are retained rather than automatically deleted because a tracker may legitimately record a day with no detected steps. They should, however, be considered when interpreting averages.

mean(daily_activity$total_steps == 0)
## [1] 0.08191489
mean(daily_activity$sedentary_minutes == 1440)
## [1] 0.08404255
mean(daily_activity$calories == 0)
## [1] 0.004255319

4. Analyze: Descriptive Analysis

4.1 Activity profile

daily_activity %>%
  select(total_steps,
         total_distance,
         very_active_minutes,
         fairly_active_minutes,
         lightly_active_minutes,
         sedentary_minutes,
         calories) %>%
  summary()
##   total_steps    total_distance   very_active_minutes fairly_active_minutes
##  Min.   :    0   Min.   : 0.000   Min.   :  0.00      Min.   :  0.00       
##  1st Qu.: 3790   1st Qu.: 2.620   1st Qu.:  0.00      1st Qu.:  0.00       
##  Median : 7406   Median : 5.245   Median :  4.00      Median :  6.00       
##  Mean   : 7638   Mean   : 5.490   Mean   : 21.16      Mean   : 13.56       
##  3rd Qu.:10727   3rd Qu.: 7.713   3rd Qu.: 32.00      3rd Qu.: 19.00       
##  Max.   :36019   Max.   :28.030   Max.   :210.00      Max.   :143.00       
##  lightly_active_minutes sedentary_minutes    calories   
##  Min.   :  0.0          Min.   :   0.0    Min.   :   0  
##  1st Qu.:127.0          1st Qu.: 729.8    1st Qu.:1828  
##  Median :199.0          Median :1057.5    Median :2134  
##  Mean   :192.8          Mean   : 991.2    Mean   :2304  
##  3rd Qu.:264.0          3rd Qu.:1229.5    3rd Qu.:2793  
##  Max.   :518.0          Max.   :1440.0    Max.   :4900

Across 940 daily activity observations, the mean number of steps is approximately 7,638 per day. Mean sedentary time is approximately 991 minutes per day, while the mean is approximately 21 minutes of very active time, 14 minutes of fairly active time, and 193 minutes of lightly active time.

The activity profile is therefore characterized by a substantial amount of sedentary time relative to recorded active time.

4.2 Sleep profile

sleep_day %>%
  select(total_sleep_records,
         total_minutes_asleep,
         total_time_in_bed) %>%
  summary()
##  total_sleep_records total_minutes_asleep total_time_in_bed
##  Min.   :1.00        Min.   : 58.0        Min.   : 61.0    
##  1st Qu.:1.00        1st Qu.:361.0        1st Qu.:403.8    
##  Median :1.00        Median :432.5        Median :463.0    
##  Mean   :1.12        Mean   :419.2        Mean   :458.5    
##  3rd Qu.:1.00        3rd Qu.:490.0        3rd Qu.:526.0    
##  Max.   :3.00        Max.   :796.0        Max.   :961.0

The mean recorded sleep duration is approximately 419 minutes, or 6.99 hours, while mean time in bed is approximately 459 minutes, or 7.64 hours.

The difference between time in bed and minutes asleep represents time spent in bed but not recorded as asleep. The mean difference is approximately 39 minutes.

5. Relationships and Visual Analysis

5.1 Steps and sedentary time

ggplot(daily_activity,
       aes(x = total_steps, y = sedentary_minutes)) +
  geom_point(alpha = 0.5) +
  geom_smooth(method = "lm", se = FALSE) +
  labs(
    title = "Daily Steps and Sedentary Time",
    x = "Total steps per day",
    y = "Sedentary minutes per day"
  )
## `geom_smooth()` using formula = 'y ~ x'

The relationship is negative: days with more steps tend to have fewer sedentary minutes. Pearson’s correlation is approximately r = -0.33, indicating a modest negative association. The relationship is not strong enough to imply that increasing steps alone determines sedentary behavior, but it supports the idea that activity and sedentary behavior are related dimensions of daily behavior.

This is useful for segmentation because Bellabeat could distinguish between users who are already active and users whose main opportunity is reducing prolonged sedentary time.

5.2 Very active minutes and calories

ggplot(daily_activity,
       aes(x = very_active_minutes, y = calories)) +
  geom_point(alpha = 0.5) +
  geom_smooth(method = "lm", se = FALSE) +
  labs(
    title = "Very Active Time and Calories Burned",
    x = "Very active minutes per day",
    y = "Calories burned per day"
  )
## `geom_smooth()` using formula = 'y ~ x'

Very active minutes have a moderate positive relationship with calories burned (r ≈ 0.62). This indicates that days with more vigorous activity tend to coincide with higher recorded energy expenditure. This is a stronger relationship than the steps-versus-sedentary association and provides a clear example of how activity intensity can be used as a meaningful behavioral signal.

5.3 Sleep duration and time in bed

ggplot(sleep_day,
       aes(x = total_minutes_asleep, y = total_time_in_bed)) +
  geom_point(alpha = 0.5) +
  geom_smooth(method = "lm", se = FALSE) +
  labs(
    title = "Sleep Duration and Time in Bed",
    x = "Minutes asleep",
    y = "Minutes in bed"
  )
## `geom_smooth()` using formula = 'y ~ x'

Sleep duration and time in bed are very strongly associated (r ≈ 0.93). This is expected because time asleep is a major component of time in bed, but the difference between the two variables is still informative: users can spend considerable time in bed without that entire period being recorded as sleep.

5.4 Sleep duration and daily steps

Before combining the datasets, dates are standardized and the tables are joined by user ID and date. This is important because joining only on Id would match each user’s sleep observations to every activity day for that user and would artificially duplicate observations.

combined_data <- daily_activity %>%
  inner_join(
    sleep_day,
    by = c("id" = "id", "activity_date" = "sleep_day")
  )
ggplot(combined_data,
       aes(x = total_minutes_asleep, y = total_steps)) +
  geom_point(alpha = 0.5) +
  geom_smooth(method = "lm", se = FALSE) +
  labs(
    title = "Sleep Duration and Daily Steps",
    x = "Minutes asleep",
    y = "Total steps"
  )
## `geom_smooth()` using formula = 'y ~ x'

The matched daily observations show a weak negative association between sleep duration and steps (r ≈ -0.19). This association is small in practical terms and should not be interpreted causally. At the individual-user level, the relationship is even less conclusive, so the evidence does not support a strong claim that people who sleep longer necessarily walk less or more.

The more defensible business insight is that sleep and activity are related wellness dimensions that can be monitored together, rather than treated as independent behaviors.

6. Additional Behavioral Pattern: Day of Week

daily_activity %>%
  mutate(day_of_week = weekdays(activity_date)) %>%
  group_by(day_of_week) %>%
  summarise(
    average_steps = mean(total_steps),
    observations = n()
  )

Average recorded steps are highest on Saturday and Tuesday in this sample and lowest on Sunday. The weekday pattern suggests that activity is not uniform across the week.

Because the differences are observational and the sample is small, these results should be used as a hypothesis for marketing experimentation rather than as a universal behavioral rule.

7. Key Findings

Finding 1 — Sedentary behavior is a meaningful component of daily activity.
The average day contains approximately 991 sedentary minutes, and higher sedentary time is associated with fewer steps. This suggests an opportunity for interventions focused not only on exercise but also on breaking up sedentary periods.

Finding 2 — Vigorous activity is strongly connected with recorded calorie expenditure.
Very active minutes show a moderate positive association with calories burned. Activity intensity can therefore provide a useful behavioral signal for personalized feedback.

Finding 3 — Sleep duration is generally below eight hours in the observed records.
The mean sleep duration is approximately 6.99 hours. Only about 28% of recorded sleep observations reach eight hours or more, while about 24% are below six hours. These descriptive results indicate substantial variation in sleep behavior.

Finding 4 — Sleep and activity should be considered together.
The matched data show only a weak negative relationship between sleep duration and daily steps. Rather than supporting a simple “more sleep means fewer steps” conclusion, the data suggest that sleep and physical activity should be treated as complementary dimensions of wellness.

Finding 5 — Weekly behavior varies.
Average steps differ by day of week, with Saturday showing the highest average in this sample and Sunday the lowest. This creates a potential basis for time-sensitive engagement strategies.

8. Business Insights and Marketing Implications

8.1 Product positioning: from tracking to personalized wellness guidance

The data suggest that users generate multiple behavioral signals—activity, sedentary time, and sleep—that can be interpreted together. For Bellabeat, this supports positioning the app not merely as a tracker, but as a tool that helps users understand relationships among everyday wellness behaviors.

8.2 Segmentation opportunity

A practical segmentation framework could distinguish:

  • Active users: higher activity and lower sedentary time.
  • Sedentary users: relatively low steps and high sedentary time.
  • Sleep-focused users: users with limited sleep duration or large gaps between time in bed and time asleep.
  • Balanced users: relatively consistent activity and sleep patterns.

The available data do not contain demographic information, so these should be treated as behavioral segments, not demographic segments.

8.3 Content strategy

Marketing content could emphasize small, measurable behavior changes:

  • movement reminders for sedentary users;
  • activity goals for users seeking greater daily movement;
  • sleep-awareness content for users with short sleep duration;
  • weekly summaries that connect activity and sleep rather than reporting isolated metrics.

8.4 Timing of engagement

The observed day-of-week differences suggest an opportunity to test whether engagement messages perform differently across the week. For example, Bellabeat could experiment with weekend movement prompts or end-of-week wellness summaries.

These recommendations are hypotheses derived from the observed sample and should be validated through current Bellabeat product data and controlled marketing experiments.

9. Limitations

Several limitations constrain the conclusions:

  1. Small and non-representative sample. The activity data contain 33 users and the sleep data contain 24 users.
  2. Limited observation period. The records cover approximately one month in 2016.
  3. No demographic variables. The dataset does not establish that participants represent Bellabeat’s female target market.
  4. Third-party device data. The data describe Fitbit users rather than Bellabeat users.
  5. Missing sleep coverage. Not every activity record has a corresponding sleep record.
  6. Observational relationships. Correlations describe associations and do not establish causation.
  7. Potential tracker non-wear. Zero-step or extremely sedentary days may reflect non-wear or incomplete logging rather than true behavior.

For future analysis, current Bellabeat data, demographic information, longer observation windows, and more complete sleep/activity coverage would materially strengthen the evidence.

10. Conclusion

This analysis demonstrates an end-to-end R workflow for transforming public smart-device data into business insights. The evidence indicates that sedentary behavior, activity intensity, sleep duration, and weekly activity patterns are useful dimensions for understanding wellness behavior.

The strongest practical opportunity is not to optimize one metric in isolation, but to use multiple behavioral signals to deliver personalized and context-aware guidance. For Bellabeat, this supports a marketing strategy centered on personalized wellness coaching, behavioral segmentation, and timely engagement.

The findings should be regarded as hypothesis-generating rather than causal or population-representative. Their primary value is demonstrating how a reproducible analytical workflow can move from raw device data to interpretable business questions, evidence-based insights, and testable marketing recommendations.

Appendix A: Reproducibility Notes

The analysis is implemented in R using tidyverse, janitor, lubridate, skimr, here, and readr.

The portfolio version explicitly records the data-cleaning decisions, validates duplicate and missing records, standardizes dates before joining, and joins activity and sleep observations at the user-date level.

Appendix B: Original Course Framework

The project follows the original Google Data Analytics case-study structure:

Ask → Prepare → Process → Analyze → Share → Act

The original case study asks analysts to identify the business task, describe data sources, document cleaning, summarize analysis, communicate findings visually, and provide high-level recommendations.