Based on available materials following business case for this project is to identify new sales strategies to increase market presence through targeted product offering. For this purpose main goals of the project are: * better understand trands of how customers use smart devices * identify new growth oppounities for Bellabeat * suggest marketing strategy changes to benefit from the findings
library(stringr)
library(readr)
library(ggplot2)
library(janitor)
##
## Attaching package: 'janitor'
## The following objects are masked from 'package:stats':
##
## chisq.test, fisher.test
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(lubridate)
##
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
##
## date, intersect, setdiff, union
For this project a FitBit Fitness Tracker Data has been taken. It’s a public data distributed under the CC0: Public Domain license.
Data quality is low, data is old, purly formated, sample is small and missing a lot of values. Decided to do cleansing and initial analysis as is and then see if any conclusions possible with existing data or it’s need to be enhanced.
I won’t change original file names for sake data source will be updated. Instead i standartize them on the loading phase to have properly named datasets.
Here are the original datasets:
print (list.files ("downloads/FitData"))
## [1] "dailyActivity_merged.csv" "heartrate_seconds_merged.csv"
## [3] "hourlyCalories_merged.csv" "hourlyIntensities_merged.csv"
## [5] "hourlySteps_merged.csv" "minuteCaloriesNarrow_merged.csv"
## [7] "minuteIntensitiesNarrow_merged.csv" "minuteMETsNarrow_merged.csv"
## [9] "minuteSleep_merged.csv" "minuteStepsNarrow_merged.csv"
## [11] "weightLogInfo_merged.csv"
For loading I use read_csv and suppressing messages to have clearer output.
activity_d <- suppressMessages(read_csv("Downloads/FitData/dailyActivity_merged.csv"))
calories_h <- suppressMessages(read_csv("Downloads/FitData/hourlyCalories_merged.csv"))
intensities_h <- suppressMessages(read_csv("Downloads/FitData/hourlyIntensities_merged.csv"))
steps_h <- suppressMessages(read_csv("Downloads/FitData/hourlySteps_merged.csv"))
sleep_m <- suppressMessages(read_csv("Downloads/FitData/minuteSleep_merged.csv"))
log_info <- suppressMessages(read_csv("Downloads/FitData/weightLogInfo_merged.csv"))
heart_rate_s <- suppressMessages(read_csv("Downloads/FitData/heartrate_seconds_merged.csv"))
Using the below function to verify if there are any N/A in the data. Nothing have found.
# colSums(is.na(" Your_Dataset_Name "))
# sum(duplicated( Your_Dataset_Name ))
sum(duplicated(sleep_m))
## [1] 525
# Sleep had returned 525 values. Checked them. Not real ones
head(sleep_m[duplicated(sleep_m), ])
## # A tibble: 6 × 4
## Id date value logId
## <dbl> <chr> <dbl> <dbl>
## 1 4319703577 4/5/2016 10:50:00 PM 3 11344563687
## 2 4319703577 4/5/2016 10:51:00 PM 3 11344563687
## 3 4319703577 4/5/2016 10:52:00 PM 2 11344563687
## 4 4319703577 4/5/2016 10:53:00 PM 2 11344563687
## 5 4319703577 4/5/2016 10:54:00 PM 2 11344563687
## 6 4319703577 4/5/2016 10:55:00 PM 1 11344563687
sleep_m <- clean_names(sleep_m)
steps_h <- clean_names(steps_h)
activity_d <- clean_names(activity_d)
calories_h <- clean_names(calories_h)
intensities_h <- clean_names(intensities_h)
log_info <- clean_names(log_info)
heart_rate_s <- clean_names(heart_rate_s)
#creating time column
sleep_m$time <- as.POSIXct(sleep_m$date, format = "%m/%d/%Y %I:%M:%S %p")
steps_h$time <- as.POSIXct(steps_h$activity_hour, format = "%m/%d/%Y %I:%M:%S %p")
activity_d$date <- as.POSIXct(activity_d$activity_date, format = "%m/%d/%Y")
calories_h$time <- as.POSIXct(calories_h$activity_hour, format = "%m/%d/%Y %I:%M:%S %p")
intensities_h$time <- as.POSIXct(intensities_h$activity_hour, format = "%m/%d/%Y %I:%M:%S %p")
log_info$time <- as.POSIXct(log_info$date, format = "%m/%d/%Y %I:%M:%S %p")
heart_rate_s$time <- as.POSIXct(heart_rate_s$time, format = "%m/%d/%Y %I:%M:%S %p")
# creating date column
sleep_m$date <- as.Date(sleep_m$time)
steps_h$date <- as.Date(steps_h$time)
activity_d$date <- as.Date(activity_d$date)
calories_h$date <- as.Date(calories_h$time)
intensities_h$date <- as.Date(intensities_h$time)
log_info$date <- as.Date(log_info$time)
heart_rate_s$date <- as.Date(heart_rate_s$time)
# Create heart rate H dataset
heart_rate_h <- heart_rate_s %>%
mutate(
time = floor_date(as.POSIXct(time, format = "%Y-%m-%d %H:00:00"), unit = "hour")
) %>%
group_by(id, date, time) %>%
summarise(
average_heart_rate = as.integer(round(mean(value, na.rm = TRUE))),
.groups = "drop"
)
# Process sleep H dataset
sleep_h <- sleep_m %>%
mutate(
time = floor_date(as.POSIXct(time, format = "%Y-%m-%d %H:00:00"), unit = "hour")
) %>%
group_by(id, date, time) %>%
summarise(
is_sleeping = as.integer(n() > 0),
.groups = "drop"
)
This is needed to understand the timeframe and maybe further use to combine statistic
activity_dates <- activity_d %>% distinct(date)
steps_dates <- steps_h %>% distinct(date)
calendar <- bind_rows(activity_dates, steps_dates) %>%
distinct(date) %>%
arrange(date) %>%
filter(!is.na(date))
rm (activity_dates,steps_dates)
head(calendar)
## # A tibble: 6 × 1
## date
## <date>
## 1 2016-03-12
## 2 2016-03-13
## 3 2016-03-14
## 4 2016-03-15
## 5 2016-03-16
## 6 2016-03-17
This is needed to have a unified view on user
Used data from: * log_info * activity_d * heart_rate_s * steps_h * calories_h
rm(profiles)
## Warning in rm(profiles): object 'profiles' not found
# Calculate average_sleep_time per day
average_sleep_per_day <- sleep_h %>%
group_by(id, date) %>%
summarize(daily_sleep_time = sum(is_sleeping == 1, na.rm = TRUE)) %>%
ungroup() %>%
group_by(id) %>%
summarize(average_sleep_h = round(mean(daily_sleep_time, na.rm = TRUE), digits=1))
## `summarise()` has grouped output by 'id'. You can override using the `.groups`
## argument.
# Calculate average steps per day
average_steps_per_day <- steps_h %>%
group_by(id, date) %>%
summarize(daily_steps = sum(step_total, na.rm = TRUE)) %>%
ungroup() %>%
group_by(id) %>%
summarize(average_steps_per_day = as.integer(mean(daily_steps, na.rm = TRUE)))
## `summarise()` has grouped output by 'id'. You can override using the `.groups`
## argument.
# Calculate average calories per day
average_cal_per_day <- calories_h %>%
group_by(id, date) %>%
summarize(daily_calories = sum(calories, na.rm = TRUE)) %>%
ungroup() %>%
group_by(id) %>%
summarize(average_calories_per_day = as.integer(mean(daily_calories, na.rm = TRUE)))
## `summarise()` has grouped output by 'id'. You can override using the `.groups`
## argument.
# Calculate average heart rate by id
average_heart_rate <- heart_rate_s %>%
group_by(id) %>%
summarize(average_heart_rate = as.integer(mean(value, na.rm = TRUE)))
# Calculate the activity count by id
activities_per_day <- activity_d %>%
group_by(id) %>%
summarize(
activities_per_day = round(n()/nrow(calendar), 1)
)
# Create the profiles dataset and merge all required fields
profiles <- log_info %>%
group_by(id) %>%
summarize(
weight_kg = round(mean(weight_kg, na.rm = TRUE), digits = 1),
bmi = round(mean(bmi, na.rm = TRUE), digits = 2)
) %>%
full_join(activities_per_day, by = "id") %>%
full_join(average_sleep_per_day, by = "id") %>%
full_join(average_heart_rate, by = "id") %>%
full_join(average_steps_per_day, by = "id") %>%
full_join(average_cal_per_day, by = "id")
#remove temp dataset
rm(activities_per_day, average_heart_rate, average_sleep_per_day, average_steps_per_day, average_cal_per_day)
# enhance with additional category for analysis based on heart_rate
profiles <- profiles %>%
mutate(heart_rating = case_when(
average_heart_rate >= 0 & average_heart_rate < 70 ~ "superhero",
average_heart_rate >= 70 & average_heart_rate < 85 ~ "norm",
average_heart_rate >= 85 ~ "attention",
TRUE ~ "ghost"
))
# View the result
head(profiles)
## # A tibble: 6 × 9
## id weight_kg bmi activities_per_day average_sleep_h average_heart_rate
## <dbl> <dbl> <dbl> <dbl> <dbl> <int>
## 1 1.50e9 53.3 23.0 0.6 7.2 NA
## 2 1.93e9 130. 46.2 0.4 7.5 NA
## 3 2.35e9 63.4 24.8 0.5 8.1 76
## 4 2.87e9 57 21.6 0.4 NA NA
## 5 2.89e9 88.4 25.0 0.2 NA NA
## 6 4.45e9 92.4 35.0 0.5 7.3 NA
## # ℹ 3 more variables: average_steps_per_day <int>,
## # average_calories_per_day <int>, heart_rating <chr>
## Measures --------------------------------------
measures <- intensities_h %>%
full_join(calories_h, by = c("id", "time")) %>%
full_join(steps_h, by = c("id", "time")) %>%
select(id, time, date, intensity = total_intensity, steps = step_total, calories)
measures$date <- as.Date(measures$time)
#remove NA values
measures <- measures %>% filter(!is.na(time))
#add coefs for adjusting measured values for better visualization on a plot
intensity_coef <- 1 # Adjust as needed for better visualization
calories_coef <- 0.1 # Adjust to match the scale of intensity and steps
steps_coef <- 0.03 # Adjust to match the scale of intensity and calories
#adding scaled intesity, calories and steps
measures <- measures %>%
mutate(
scaled_intensity = intensity * intensity_coef,
scaled_calories = calories * calories_coef,
scaled_steps = steps * steps_coef
)
rm(intensity_coef, calories_coef, steps_coef )
#adding time_of_day category
measures$time_of_day <- case_when(
lubridate::hour(measures$time) >= 6 & lubridate::hour(measures$time) < 12 ~ "Morning",
lubridate::hour(measures$time) >= 12 & lubridate::hour(measures$time) < 15 ~ "Day",
lubridate::hour(measures$time) >= 15 & lubridate::hour(measures$time) < 18 ~ "Afternoon",
lubridate::hour(measures$time) >= 18 & lubridate::hour(measures$time) < 21 ~ "Evening",
lubridate::hour(measures$time) >= 21 | lubridate::hour(measures$time) < 6 ~ "Night",
TRUE ~ "Unknown"
)
# Ensure the time_of_day is ordered correctly
measures$time_of_day <- factor(measures$time_of_day,
levels = c("Morning", "Day", "Afternoon", "Evening", "Night")
# labels = c(1, 2, 3, 4, 5)
)
#adding hour field for further analysis
measures <- measures %>%
mutate(hour = hour(time))
# Check the results
#table(measures$time_of_day)
#str(measures$time_of_day)
# View the resulting dataset
head(measures)
## # A tibble: 6 × 11
## id time date intensity steps calories
## <dbl> <dttm> <date> <dbl> <dbl> <dbl>
## 1 1503960366 2016-03-12 00:00:00 2016-03-12 0 0 48
## 2 1503960366 2016-03-12 01:00:00 2016-03-12 0 0 48
## 3 1503960366 2016-03-12 02:00:00 2016-03-12 0 0 48
## 4 1503960366 2016-03-12 03:00:00 2016-03-12 0 0 48
## 5 1503960366 2016-03-12 04:00:00 2016-03-12 0 0 48
## 6 1503960366 2016-03-12 05:00:00 2016-03-12 0 0 48
## # ℹ 5 more variables: scaled_intensity <dbl>, scaled_calories <dbl>,
## # scaled_steps <dbl>, time_of_day <fct>, hour <int>
This shows us that not all people have their heart rate tracked properly during their activities. We should target to that users category a heart_rate functionality of our devices and populate the understanding of benefits from this information being available for end user health.
Other than that it’s lack of data to make any justified conclusions about corelation between heart_rate and daily_activities.
Ghost is someone for whome we do not have info about heart_rate.
ggplot(profiles, aes(x = average_steps_per_day, y = activities_per_day, shape = heart_rating, color = heart_rating, size=average_calories_per_day)) +
geom_point(stat = "identity") +
labs(
title = "Average Activities Per Day by User",
x = "Steps Per Day",
y = "Activities Per Day"
) +
theme_minimal()
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_point()`).
##### Calories burned by user + steps While there is a strong corelation
between number of steps and burned calories by user. Analysis shows us
that there are users who burn calories without significant amount of
steps. Which mean they actively perform some other exercises. We
need to encourage this category of users utilize benefits of our product
for their activities tracking
ggplot(measures, aes(x = as.factor(id))) +
geom_boxplot(mapping = aes(y = calories), fill = "purple", color = "black", alpha = 0.7) +
stat_summary(mapping = aes(y = steps, color = "Average Steps"),
fun = "mean", geom = "line", group = 1, size = 1) +
labs(
title = "Calories Burned by User",
x = "User",
y = "Calories",
color = "Legend"
) +
scale_color_manual(values = c("Average Steps" = "blue")) +
theme_minimal()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
##### Calories, Intensivity and Steps distribution through the day This
just confirmed that people are more active during the day.
ggplot(measures, aes(x = hour)) +
geom_smooth(aes(y = scaled_intensity, color = "Intensity"), size = 1) +
geom_smooth(aes(y = scaled_calories, color = "Calories"), size = 1) +
geom_smooth(aes(y = scaled_steps, color = "Steps"), size = 1) +
labs(
title = "Scaled Intensity, Calories, and Steps by Hour",
x = "Hour of the Day",
y = "Scaled Values",
color = "Metrics"
) +
scale_color_manual(values = c("Intensity" = "blue", "Calories" = "red", "Steps" = "green")) +
theme_minimal()
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'
##### Calories, Intensivity and Steps distribution through the measured
period This showing that at the end of month users were less active.
Need to research more data from other monthes and get insides from
domain experts to make some conclusions. It might be related to a spring
term break at school or connected to “monthly habbits”
# Create a plot for the 32 days showing intensity, calories, and steps
ggplot(measures, aes(x = date)) +
geom_smooth(aes(y = scaled_intensity, color = "Intensity"), size = 1) +
geom_smooth(aes(y = scaled_calories, color = "Calories"), size = 1) +
geom_smooth(aes(y = scaled_steps, color = "Steps"), size = 1) +
labs(
title = "Intensity, Calories, and Steps Over 32 Days",
x = "Date",
y = "Scaled Values",
color = "Metrics"
) +
scale_color_manual(values = c("Intensity" = "blue", "Calories" = "red", "Steps" = "green")) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'