This report explores daily resting heart rate, resilience
levels,
and physical activity using Oura data.
# Read the datasets (semicolon-delimited)
heartrate_data <- read.csv2("heartrate.csv")
resilience_data <- read.csv2("dailyresilience.csv")
workout_data <- read.csv2("workout.csv")
heartrate_rest_daily <- heartrate_data %>%
mutate(clean_date = as.Date(substr(timestamp, 1, 10))) %>% # extract date
filter(source == "rest") %>% # keep resting HR only
group_by(clean_date) %>%
summarise(avg_bpm = mean(bpm, na.rm = TRUE)) # daily average
workout_calories_daily <- workout_data %>%
mutate(day = as.Date(day)) %>%
group_by(day) %>%
summarise(total_daily_calories = sum(as.numeric(calories), na.rm = TRUE)) %>%
mutate(ActiveThreshold = total_daily_calories > 500) # flag high-activity days
resilience_data <- resilience_data %>%
mutate(day = as.Date(day))
combined <- heartrate_rest_daily %>%
inner_join(resilience_data, by = c("clean_date" = "day")) %>%
left_join(
workout_calories_daily %>% select(day, ActiveThreshold),
by = c("clean_date" = "day")
) %>%
mutate(
ActiveThreshold = replace_na(ActiveThreshold, FALSE),
level = factor(level, levels = c("solid", "adequate", "limited"))
)
# View a few rows to verify the join
head(combined)
## # A tibble: 6 × 6
## clean_date avg_bpm id contributors level ActiveThreshold
## <date> <dbl> <chr> <chr> <fct> <lgl>
## 1 2025-04-22 72.6 1f7d7552-1741-4e7b-88ef… {daytime_re… adeq… TRUE
## 2 2025-04-23 72.1 9ff55cfc-1e17-4483-8914… {daytime_re… adeq… FALSE
## 3 2025-04-24 72.8 d5f0fe7f-78c6-4164-89df… {daytime_re… limi… FALSE
## 4 2025-04-25 71.2 c71ccfc2-617a-4d35-9f1a… {daytime_re… adeq… TRUE
## 5 2025-04-26 69.0 ac6bc474-733f-4b78-889f… {daytime_re… adeq… FALSE
## 6 2025-04-27 70.8 b719a043-3ea3-4451-a3e3… {daytime_re… adeq… FALSE
ggplot(combined, aes(x = clean_date, y = avg_bpm)) +
geom_point(aes(color = level), size = 2.8, alpha = 0.9) +
geom_smooth(method = "loess", se = FALSE, color = "black", linewidth = 1.2) +
geom_point(
data = subset(combined, ActiveThreshold),
aes(shape = "Active Burn > 500"),
size = 4, stroke = 1.1, color = "black", fill = NA
) +
geom_rug(
data = subset(combined, ActiveThreshold),
sides = "b",
length = unit(3, "pt"),
color = "black",
alpha = 0.6
) +
scale_color_manual(
name = "Resilience Level",
values = c(
"limited" = "red",
"adequate" = "gold",
"solid" = "green4"
)
) +
scale_shape_manual(
name = "",
values = c("Active Burn > 500" = 1)
) +
guides(color = guide_legend(order = 1),
shape = guide_legend(order = 2)) +
scale_x_date(
date_breaks = "1 week",
date_labels = "%b %d"
) +
labs(
title = "Resting Heart Rate Over Time",
subtitle = "Colored by resilience level, with >500-calorie days highlighted",
x = "",
y = "Average Resting BPM"
) +
theme_minimal(base_size = 13) +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "right",
plot.title = element_text(face = "bold")
)
On days labeled solid in resilience, heart rate tended to be
lower.
Spikes appear around periods of sustained activity (>500
calories).
The black LOESS line gives the smoothed overall trend.