Analysis Task: identify which attributes of the competitor’s
products are used most and use that information to choose which
Bellabeat product to advertise.
Answer the 3 main 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.
Stakeholders: Urška Sršen: Bellabeat’s cofounder and Chief
Creative Officer,
Sando Mur: Mathematician and Bellabeat’s cofounder; key member
of the Bellabeat executive team
People who can help answer questions: Bellabeat marketing
analytics team: A team of data analysts responsible for collecting,
analyzing, and reporting data that helps guide Bellabeat’s marketing
strategy
Business Task: Use data to choose which Bellabeat product
would be the most appealing to potential customers for
advertisement.
Data integrity using the ROCCC method:
-Reliability: This is raw user data but it is a very small
sample size making it relatively unreliable.
-Original: This is a third party data set and therefore
completely unoriginal.
-Comprehensive: The sample size is small and demographics or
method of choosing participants is not explained. Participants could all
have the same health problem or all be the same age for
example
-Current: It is almost 9 years old and newer data sets are
available, This data set is not current.
-Cited: This data set explains where it came from and who
collected it. It’s not cited like a peer reviewed journal but the method
of citing used is acceptable.
This data set overall is not very reliable but it has
relevant and necessary data for the purpose of this project and is the
set that was suggested by the stakeholders.
Data integrity according to ROCCC method:
-Reliability: This data set is from a very reputable source
(The National Library of Medicine) and is peer reviewed.
-Original: This data was not directly collected by us but is a
second party data set from a trustworthy source.
-Comprehensive: It has a very large sample size. The collection
method was safe and unbiased. Many different aspects of the products
were taken into account. It is satisfactorily
comprehensive.
-Current: This set is now almost 5 years old but that is the
most recent data set available and I am not able to collect a new data
set at this time.
-Cited: The sources have been professionally cited at the bottom
of the report.
This is a very reliable data set. It is from a reputable and
peer reviewed source that explains its collection method and is
unbiased.
It is necessary before beginning to process any data in RStudio to update and call up all libraries I will be using for the project.
install.packages("tidyverse")
install.packages("here")
install.packages("dplyr")
install.packages("janitor")
install.packages("ggplot2")
install.packages("lubridate")
install.packages("RColorBrewer")
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.4 ✔ 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
library(here)
## here() starts at C:/Users/Public/Documents/class/capstone project
library(dplyr)
library(janitor)
##
## Attaching package: 'janitor'
##
## The following objects are masked from 'package:stats':
##
## chisq.test, fisher.test
library(ggplot2)
library(lubridate)
library(RColorBrewer)
Now that the libraries are ready, it is time to upload the data frames. These tables were downloaded to my internal computer memory and uploaded from a set working directory. They are checked to make sure they uploaded properly.
daily_activity_1 <- read.csv("dailyActivity_merged.csv")
glimpse(daily_activity_1)
## Rows: 457
## Columns: 15
## $ Id <dbl> 1503960366, 1503960366, 1503960366, 150396036…
## $ ActivityDate <chr> "3/25/2016", "3/26/2016", "3/27/2016", "3/28/…
## $ TotalSteps <int> 11004, 17609, 12736, 13231, 12041, 10970, 122…
## $ TotalDistance <dbl> 7.11, 11.55, 8.53, 8.93, 7.85, 7.16, 7.86, 7.…
## $ TrackerDistance <dbl> 7.11, 11.55, 8.53, 8.93, 7.85, 7.16, 7.86, 7.…
## $ LoggedActivitiesDistance <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ VeryActiveDistance <dbl> 2.57, 6.92, 4.66, 3.19, 2.16, 2.36, 2.29, 3.3…
## $ ModeratelyActiveDistance <dbl> 0.46, 0.73, 0.16, 0.79, 1.09, 0.51, 0.49, 0.8…
## $ LightActiveDistance <dbl> 4.07, 3.91, 3.71, 4.95, 4.61, 4.29, 5.04, 3.6…
## $ SedentaryActiveDistance <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.0…
## $ VeryActiveMinutes <int> 33, 89, 56, 39, 28, 30, 33, 47, 40, 15, 43, 3…
## $ FairlyActiveMinutes <int> 12, 17, 5, 20, 28, 13, 12, 21, 11, 30, 18, 18…
## $ LightlyActiveMinutes <int> 205, 274, 268, 224, 243, 223, 239, 200, 244, …
## $ SedentaryMinutes <int> 804, 588, 605, 1080, 763, 1174, 820, 866, 636…
## $ Calories <int> 1819, 2154, 1944, 1932, 1886, 1820, 1889, 186…
hourly_calories_1 <- read.csv("hourlyCalories_merged.csv")
glimpse(hourly_calories_1)
## Rows: 24,084
## Columns: 3
## $ Id <dbl> 1503960366, 1503960366, 1503960366, 1503960366, 150396036…
## $ ActivityHour <chr> "3/12/2016 12:00:00 AM", "3/12/2016 1:00:00 AM", "3/12/20…
## $ Calories <int> 48, 48, 48, 48, 48, 48, 48, 48, 48, 49, 89, 134, 130, 81,…
hourly_intensities_1 <- read.csv("hourlyIntensities_merged.csv")
glimpse(hourly_intensities_1)
## Rows: 24,084
## Columns: 4
## $ Id <dbl> 1503960366, 1503960366, 1503960366, 1503960366, 15039…
## $ ActivityHour <chr> "3/12/2016 12:00:00 AM", "3/12/2016 1:00:00 AM", "3/1…
## $ TotalIntensity <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 26, 49, 47, 20, 121, 12…
## $ AverageIntensity <dbl> 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0…
sleep_day_2 <- read.csv("2sleepDay_merged.csv")
glimpse(sleep_day_2)
## 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 <int> 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ TotalMinutesAsleep <int> 327, 384, 412, 340, 700, 304, 360, 325, 361, 430, 2…
## $ TotalTimeInBed <int> 346, 407, 442, 367, 712, 320, 377, 364, 384, 449, 3…
Now with all the data frames that I will be analyzing uploaded, it is time to start studying and inspecting the data.
n_distinct(daily_activity_1$Id) #35
## [1] 35
n_distinct(hourly_calories_1$Id) #34
## [1] 34
n_distinct(hourly_intensities_1$Id) #34
## [1] 34
n_distinct(sleep_day_2$Id) #24
## [1] 24
sum(duplicated(daily_activity_1)) #checking for duplicates
## [1] 0
sum(duplicated(hourly_calories_1))
## [1] 0
sum(duplicated(hourly_intensities_1))
## [1] 0
sum(duplicated(sleep_day_2)) #shows 3 duplicates
## [1] 3
sapply(daily_activity_1, class) # date in character format
## Id ActivityDate TotalSteps
## "numeric" "character" "integer"
## TotalDistance TrackerDistance LoggedActivitiesDistance
## "numeric" "numeric" "numeric"
## VeryActiveDistance ModeratelyActiveDistance LightActiveDistance
## "numeric" "numeric" "numeric"
## SedentaryActiveDistance VeryActiveMinutes FairlyActiveMinutes
## "numeric" "integer" "integer"
## LightlyActiveMinutes SedentaryMinutes Calories
## "integer" "integer" "integer"
sapply(hourly_calories_1, class)
## Id ActivityHour Calories
## "numeric" "character" "integer"
sapply(hourly_intensities_1, class)
## Id ActivityHour TotalIntensity AverageIntensity
## "numeric" "character" "integer" "numeric"
sapply(sleep_day_2, class) #date in character format
## Id SleepDay TotalSleepRecords TotalMinutesAsleep
## "numeric" "character" "integer" "integer"
## TotalTimeInBed
## "integer"
With that code, I discovered that the Daily Activity data
frame has 35 distinct Ids listed. Hourly calories and hourly intensities
have 34, and the sleep (measured by day) data frame has
24.
Then each data frame is checked for duplicates. The sleep
data frame shows 3 duplicates and the others have none.
Then the form (or class) of data in each column in each data
frame is checked. The daily activity and the Sleep data frames have
date columns in character format.
Now it’s time to clean up the errors.
sleep_day_2 <- sleep_day_2 %>% #duplicate correction
distinct() %>%
drop_na()
sum(duplicated(sleep_day_2)) #duplicate recheck
## [1] 0
sleep_day_2 <- sleep_day_2 %>% #converting the dates from character format to date format
mutate(SleepDay = mdy_hms(SleepDay, tz="EST"))
daily_activity <- daily_activity_1 %>%
mutate(ActivityDate = mdy(ActivityDate))
sapply(sleep_day_2, class) # Date rechecks
## $Id
## [1] "numeric"
##
## $SleepDay
## [1] "POSIXct" "POSIXt"
##
## $TotalSleepRecords
## [1] "integer"
##
## $TotalMinutesAsleep
## [1] "integer"
##
## $TotalTimeInBed
## [1] "integer"
sapply(daily_activity, class)
## Id ActivityDate TotalSteps
## "numeric" "Date" "integer"
## TotalDistance TrackerDistance LoggedActivitiesDistance
## "numeric" "numeric" "numeric"
## VeryActiveDistance ModeratelyActiveDistance LightActiveDistance
## "numeric" "numeric" "numeric"
## SedentaryActiveDistance VeryActiveMinutes FairlyActiveMinutes
## "numeric" "integer" "integer"
## LightlyActiveMinutes SedentaryMinutes Calories
## "integer" "integer" "integer"
This cleaning was successful. Now to start processing the
data into something we want to use to analyze and
visualize.
Not all customers wore the watch all day when they wore it. To filter out incomplete information, let’s filter out the partial days by leaving out the days with less than 2000 steps.
applied_daily_activity <- daily_activity %>%
filter(TotalSteps > 2000) # filter out the days where the watch wasn't worn all day
n_distinct(applied_daily_activity$Id) #still 34 unique Ids
## [1] 34
There are still 34 unique Ids (one Id was dropped but 34 is
still plenty to get accurate results).
This is where I began processing the data to get it ready for analysis. I grouped the data by Ids and grouped the days together by so we can see how many days total each person wore the watch. Then I plotted it so we can visualize how popular this feature is.
distinct_daily_activity <- applied_daily_activity %>% #this will show how many days each user wore the watch for
group_by(Id)%>%
summarize(Days = n_distinct(ActivityDate))
distinct_daily_activity %>%
ggplot(aes(x=Id, y= Days))+
geom_point()+
labs( title = "How Many Days Each Person Wore It (to check work)", x= "Id", y = "Number of Days")
Then I did the same for the data collected for sleep. One individual wore it every singe night that data was collected!
distinct_sleep_day_2 <- sleep_day_2 %>% #this will show how many days each user wore the watch for
group_by(Id) %>%
summarize(Days = n_distinct(SleepDay))
distinct_sleep_day_2 %>%
ggplot(aes(x=Id, y= Days))+
geom_point()+
labs( title = "How Many Days Each Person Wore It (to check work)", x= "Id", y = "Number of Days")
It sounds interesting to see if activity or other variables affect how long it takes a person to fall asleep so I made a new column in the sleep table called “TimeAwakeInBed” by subtracting the time asleep from the total time in bed.
sleep_day_2 <- transform(sleep_day_2, TimeAwakeInBed = TotalTimeInBed-TotalMinutesAsleep)
#that made a new column to look at how long each person was laying awake in bed
To continue to gather the data into a format that is easy to plot and study, I made a table out of the averages of each attribute in the daily activity and sleep data frames. I grouped the averages by Id so I didn’t just get an average of each column at the top but instead got an average for each individual using the product. These specific columns were chosen after an analysis of the data points available.
average_activity_columns <-applied_daily_activity %>%
group_by(Id) %>%
summarize(AvgSteps = mean(TotalSteps), AvgTotalDistance =mean(TotalDistance),
AvgVeryActiveDistance = mean(VeryActiveDistance),
AvgModeratelyActiveDistance = mean(ModeratelyActiveDistance),
AvgLightActiveDistance = mean(LightActiveDistance),
AvgVeryActiveMinutes = mean(VeryActiveMinutes),
AvgFairlyActiveMinutes = mean(FairlyActiveMinutes),
AvgLightlyActiveMinutes = mean(LightlyActiveMinutes),
AvgCalories =mean(Calories)
)
average_sleep_columns <-sleep_day_2 %>%
group_by(Id) %>%
summarize(AvgTotalSleepRecords = mean(TotalSleepRecords),
AvgTotalMinutesAsleep =mean(TotalMinutesAsleep),
AvgTotalTimeInBed = mean(TotalTimeInBed),
AvgTimeAwakeInBed = mean(TimeAwakeInBed)
)
Next I’m going to combine the averages tables with the tables that contain the total days each person wore the product and check the integrity of the data so far.
averages_daily_activity_final <- merge(distinct_daily_activity,average_activity_columns, by = "Id")
averages_sleep_activity_final <- merge(distinct_sleep_day_2,average_sleep_columns, by = "Id")
glimpse(averages_daily_activity_final)
## Rows: 34
## Columns: 11
## $ Id <dbl> 1503960366, 1624580081, 1644430081, 184450…
## $ Days <int> 18, 9, 9, 8, 7, 12, 10, 7, 14, 10, 2, 10, …
## $ AvgSteps <dbl> 12274.778, 7283.444, 10157.667, 5460.000, …
## $ AvgTotalDistance <dbl> 8.022222, 4.733333, 7.391111, 3.610000, 2.…
## $ AvgVeryActiveDistance <dbl> 2.9383333, 0.1088889, 1.2600000, 0.0762500…
## $ AvgModeratelyActiveDistance <dbl> 0.67055555, 0.05777778, 2.25555557, 0.0462…
## $ AvgLightActiveDistance <dbl> 4.403333, 4.556667, 3.870000, 3.486250, 2.…
## $ AvgVeryActiveMinutes <dbl> 37.833333, 1.555556, 16.444444, 1.125000, …
## $ AvgFairlyActiveMinutes <dbl> 16.722222, 1.222222, 48.333333, 1.125000, …
## $ AvgLightlyActiveMinutes <dbl> 240.3889, 167.1111, 249.1111, 237.6250, 15…
## $ AvgCalories <dbl> 1893.222, 1397.111, 3186.111, 1868.625, 24…
glimpse(averages_sleep_activity_final)
## Rows: 24
## Columns: 6
## $ Id <dbl> 1503960366, 1644430081, 1844505072, 1927972279, …
## $ Days <int> 25, 4, 3, 5, 28, 1, 15, 28, 8, 26, 23, 28, 5, 27…
## $ AvgTotalSleepRecords <dbl> 1.080000, 1.000000, 1.000000, 1.600000, 1.000000…
## $ AvgTotalMinutesAsleep <dbl> 360.2800, 294.0000, 652.0000, 417.0000, 506.1786…
## $ AvgTotalTimeInBed <dbl> 383.2000, 346.0000, 961.0000, 437.8000, 537.6429…
## $ AvgTimeAwakeInBed <dbl> 22.920000, 52.000000, 309.000000, 20.800000, 31.…
Then I combined the two tables containing all of the average daily activity with all of the average sleep data to have a complete health information table based on Id.Then another data integrity check.
complete_health_info <- merge(averages_daily_activity_final, averages_sleep_activity_final, by = "Id")
glimpse(complete_health_info)
## Rows: 23
## Columns: 16
## $ Id <dbl> 1503960366, 1644430081, 1844505072, 192797…
## $ Days.x <int> 18, 9, 8, 7, 10, 7, 14, 11, 29, 11, 14, 11…
## $ AvgSteps <dbl> 12274.778, 10157.667, 5460.000, 3198.714, …
## $ AvgTotalDistance <dbl> 8.022222, 7.391111, 3.610000, 2.215714, 2.…
## $ AvgVeryActiveDistance <dbl> 2.93833332, 1.25999997, 0.07625000, 0.0000…
## $ AvgModeratelyActiveDistance <dbl> 0.67055555, 2.25555557, 0.04625000, 0.1057…
## $ AvgLightActiveDistance <dbl> 4.403333, 3.870000, 3.486250, 2.110000, 2.…
## $ AvgVeryActiveMinutes <dbl> 37.833333, 16.444444, 1.125000, 0.000000, …
## $ AvgFairlyActiveMinutes <dbl> 16.722222, 48.333333, 1.125000, 2.857143, …
## $ AvgLightlyActiveMinutes <dbl> 240.3889, 249.1111, 237.6250, 159.1429, 18…
## $ AvgCalories <dbl> 1893.222, 3186.111, 1868.625, 2490.143, 14…
## $ Days.y <int> 25, 4, 3, 5, 28, 1, 15, 28, 8, 26, 28, 5, …
## $ AvgTotalSleepRecords <dbl> 1.080000, 1.000000, 1.000000, 1.600000, 1.…
## $ AvgTotalMinutesAsleep <dbl> 360.2800, 294.0000, 652.0000, 417.0000, 50…
## $ AvgTotalTimeInBed <dbl> 383.2000, 346.0000, 961.0000, 437.8000, 53…
## $ AvgTimeAwakeInBed <dbl> 22.920000, 52.000000, 309.000000, 20.80000…
The other two data frames that were loaded, hourly calories and hourly intensities, were already clean so I combined them. Then I checked the data integrity again.
calories_vs_intensities <- merge(hourly_calories_1, hourly_intensities_1)
glimpse(calories_vs_intensities) #it was done right
## Rows: 24,084
## Columns: 5
## $ Id <dbl> 1503960366, 1503960366, 1503960366, 1503960366, 15039…
## $ ActivityHour <chr> "3/12/2016 1:00:00 AM", "3/12/2016 1:00:00 PM", "3/12…
## $ Calories <int> 48, 81, 89, 123, 134, 111, 48, 130, 48, 254, 48, 271,…
## $ TotalIntensity <int> 0, 20, 26, 41, 49, 35, 0, 47, 0, 121, 0, 129, 0, 42, …
## $ AverageIntensity <dbl> 0.000000, 0.333333, 0.433333, 0.683333, 0.816667, 0.5…
n_distinct(calories_vs_intensities$Id) #there are still 34 distinct Ids
## [1] 34
Now that the data has been cleaned and put into an easy to
use form, it’s time to look at some visuals and search for correlations
between data points.
The first visualization is to see if there is a link between
how many calories a person burns in a day and how long they lay awake in
bed at night. The graph suggests that while burning more calories does
not guarantee falling asleep faster, the individuals that laid awake in
bed the longest burned fewer than fewer than average
calories.
complete_health_info %>%
ggplot(aes(x=AvgTimeAwakeInBed, y=AvgCalories))+
geom_point(aes(color = AvgCalories))+
labs(title = "Does Exercise Help You Rest", x= "Time Awake In Bed", y = "Calories burned")+
scale_colour_distiller(palette = "Paired")
The next graph shows the link between how intense the physical activity was in each hour and how many calories were burned that hour by each person who was logging their activity.
calories_vs_intensities %>%
ggplot(aes(x=AverageIntensity, y=Calories, color = Calories))+
geom_point()+
facet_wrap(~Id)+
labs( title = "Calories versus Intenseness for Each Id", x= "Average Intensity", y="Calories")+
scale_color_gradientn(colours = terrain.colors(10))
The last graph shows the link for each person between how many steps they took and how many calories were burned. This seems like an easy association to make. The point of graphing this is to show that people who bought this product took advantage of this feature and used it to motivate themselves to live a healthier lifestyle.
applied_daily_activity %>%
ggplot(aes(x = TotalSteps, y = Calories)) +
geom_point(aes(color = Calories)) +
facet_wrap(~ Id) +
labs(title = "Relationship between Total Steps and Calories for each Id",
x = "Total Steps",
y = "Calories")+
scale_colour_distiller(palette = "Set1")
The last step is to look for number correlations between some
of the gathered health information.
This number list shows how strong of a correlation there is
between the number of calories burned and; total steps, total distance,
how long they were very active for, and how long they were lightly
active for. It shows that total steps, distance, and very active minutes
were moderately correlated with calories burned and lightly active
minutes was weakly correlated.
correlation_daily_activity <- applied_daily_activity %>%
summarize(cor(TotalSteps, Calories),
cor(TotalDistance, Calories),
cor(VeryActiveMinutes, Calories),
cor(LightlyActiveMinutes, Calories)
)
head(correlation_daily_activity)
Then just for fun let’s look at some sleep correlation numbers. This shows a negative correlation between How long people laid awake in bed and how many calories were burned (we saw this on a graph earlier too). It also shows a negative correlation between the total amount of time people spent in bed and how many calories were burned and a very strong correlation between how long people were in bed and how long they were asleep. Those numbers really just show that people are using the product to track their sleeping hours accurately.
correlation_sleep_exercise <- complete_health_info %>%
summarize(cor(AvgTimeAwakeInBed, AvgCalories),
cor(AvgTotalTimeInBed, AvgCalories),
cor(AvgTotalMinutesAsleep, AvgTotalTimeInBed)
)
head(correlation_sleep_exercise)
According to the data analysis by the National Library of
Medicine, people who wear fitness tracking smart devices purchase based
on how it can be used to motivate them, it’s ability to track sleep, its
ability to hold them accountable, and its discretion in wearing it. The
report quoted certain customers as having said
“I wondered why I was so tired when I got up in the morning.
The Fitbit really does track my sleep patterns. I found that I was awake
numerous times (it even tell you exactly what times you are awake) and
it shows when you are restless…” (Sleep tracking)
and “…this works! It’s easy to check during the day to keep
you on target. In fact I actually WANT to check it, to see my progress.
My goal, of course is 10,000 steps a day. Thanks to my Fitbit One, I
know I’m going to get there on a regular basis. It gets me out and
walking and keeps me moving. I am constantly challenging myself. I
finally found something that motivates me to exercise.”
(Motivation)
and “…because I didn’t want to wear a wristband all day since
I am constantly typing I knew it would drive me nuts… I really like this
little guy I clip it to my bra… don’t even notice it is there throughout
the day.” (Discretion)
We can see from the Analysis of the Fitbit data available on
Kaggle that their customers really did take advantage of the features
that track movement and intensity of activity and the sleep tracking
feature.
The Bellabeat product that has all of those features
available is the Leaf. It can be worn in different ways to be kept
discrete and tracks activity information and sleep patterns.That
information holds people accountable and motivates them to exercise. I
recommend Advertising the Leaf as the beginner product for people coming
to Bellabeat then suggesting the app and the membership as the next
step.
Advertising The Leaf and the Bellabeat app together will almost guarantee an increase in new customers.