Initial release
This commit is contained in:
39
.gitignore
vendored
39
.gitignore
vendored
@ -1,38 +1 @@
|
|||||||
# ---> R
|
gadgetbridge
|
||||||
# History files
|
|
||||||
.Rhistory
|
|
||||||
.Rapp.history
|
|
||||||
|
|
||||||
# Session Data files
|
|
||||||
.RData
|
|
||||||
|
|
||||||
# Example code in package build process
|
|
||||||
*-Ex.R
|
|
||||||
|
|
||||||
# Output files from R CMD build
|
|
||||||
/*.tar.gz
|
|
||||||
|
|
||||||
# Output files from R CMD check
|
|
||||||
/*.Rcheck/
|
|
||||||
|
|
||||||
# RStudio files
|
|
||||||
.Rproj.user/
|
|
||||||
|
|
||||||
# produced vignettes
|
|
||||||
vignettes/*.html
|
|
||||||
vignettes/*.pdf
|
|
||||||
|
|
||||||
# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3
|
|
||||||
.httr-oauth
|
|
||||||
|
|
||||||
# knitr and R markdown default cache directories
|
|
||||||
/*_cache/
|
|
||||||
/cache/
|
|
||||||
|
|
||||||
# Temporary files created by R markdown
|
|
||||||
*.utf8.md
|
|
||||||
*.knit.md
|
|
||||||
|
|
||||||
# Shiny token, see https://shiny.rstudio.com/articles/shinyapps.html
|
|
||||||
rsconnect/
|
|
||||||
|
|
||||||
|
26
README.md
26
README.md
@ -1,3 +1,27 @@
|
|||||||
# PebbleHealthStats
|
# PebbleHealthStats
|
||||||
|
|
||||||
A R notebook to analyze your sleep and step data recorded by a Pebble watch
|
## Introduction and requirements
|
||||||
|
|
||||||
|
A R notebook to analyze your sleep and step data recorded by a *Pebble* watch
|
||||||
|
|
||||||
|
You need to be using [Gadgetbridge](https://gadgetbridge.org/) with your *Pebble* to have access to your data. Health data is then stored in a *SQLite* database.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
The easiest way to use this projet is to download [RStudio](https://www.rstudio.com/products/rstudio/download/#download), open `pebble_health_stats.Rmd` and press *Knit* on the top left of the editor.
|
||||||
|
|
||||||
|
You will need to have [R](https://www.r-project.org/) running on you PC as well as the package [Tidyverse](https://www.tidyverse.org/).
|
||||||
|
On a [Debian](https://www.debian.org/) machine, all you need to do is:
|
||||||
|
```
|
||||||
|
sudo apt install r-cran-tidyverse
|
||||||
|
```
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|
Open `pebble_health_stats.html` for a demo of the output.
|
||||||
|
|
||||||
|
## If you want to go further
|
||||||
|
|
||||||
|
This program only works with *Pebble* watches but is probably easy to adapt for a *Mi Band*, *Amazfit*, or any other watch compatible with *Gadgetbridge*.
|
||||||
|
|
||||||
|
You can also adapt the code to add new visualisations, or explore data (heart rate…).
|
||||||
|
287
pebble_health_stats.Rmd
Normal file
287
pebble_health_stats.Rmd
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
---
|
||||||
|
title: "Health stats"
|
||||||
|
output:
|
||||||
|
html_document:
|
||||||
|
toc: yes
|
||||||
|
pdf_document: default
|
||||||
|
html_notebook:
|
||||||
|
code_folding: hide
|
||||||
|
toc: yes
|
||||||
|
---
|
||||||
|
|
||||||
|
```{r setup, include=FALSE}
|
||||||
|
knitr::opts_chunk$set(echo = FALSE, warning = FALSE)
|
||||||
|
```
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
This documents provides an overview of health data recorded by *Gadgetbridge* using a *Pebble* smartwatch.
|
||||||
|
|
||||||
|
# Preparation
|
||||||
|
Put the *GadgetBridge* database in the current folder, then knit the current `.Rmd` file.
|
||||||
|
|
||||||
|
You should also set you **timezone** in the corresponding variable.
|
||||||
|
|
||||||
|
* `PEBBLE_HEALTH_ACTIVITY_SAMPLE` is a simple table containing steps and timestamps.
|
||||||
|
* `PEBBLE_HEALTH_ACTIVITY_OVERLAY` is more complex. It tracks activities (sleep, deep sleep, nap…) with a start and an end date. Note that date may overlap: you can be both in sleep and deep sleep.
|
||||||
|
|
||||||
|
I have decided that any event happening after 8pm is registered for the next day. For instance:
|
||||||
|
|
||||||
|
* if you go to bed at 9pm on Tuesday and wake up at 7am on Wednesday, the data will return 10 hours of sleep on Wednesday
|
||||||
|
* if you go to bed at 7pm on Tuesday, a part of your sleep will be added to Tuesday. The cutoff won't exactly be 8pm, it will depend on the duration of the first sleep session recorded by you watch.
|
||||||
|
|
||||||
|
```{r include=FALSE}
|
||||||
|
# Load data ------------------------------------------------------------------
|
||||||
|
library(DBI)
|
||||||
|
library(RSQLite)
|
||||||
|
library(tidyverse)
|
||||||
|
library(lubridate)
|
||||||
|
library(scales)
|
||||||
|
con <- DBI::dbConnect(RSQLite::SQLite(), dbname = "gadgetbridge")
|
||||||
|
steps_data <- dbReadTable(con, "PEBBLE_HEALTH_ACTIVITY_SAMPLE")
|
||||||
|
sleep_data <- dbReadTable(con, "PEBBLE_HEALTH_ACTIVITY_OVERLAY")
|
||||||
|
dbDisconnect(con)
|
||||||
|
#str(steps_data)
|
||||||
|
|
||||||
|
# Transform data -------------------------------------------------------------
|
||||||
|
|
||||||
|
## Interprate timestamp
|
||||||
|
timezone = "Europe/Paris"
|
||||||
|
steps_data$datetime <- as_datetime(steps_data$TIMESTAMP)
|
||||||
|
sleep_data$datetime_from <- with_tz(as_datetime(sleep_data$TIMESTAMP_FROM), timezone)
|
||||||
|
sleep_data$datetime_to <- with_tz(as_datetime(sleep_data$TIMESTAMP_TO), timezone)
|
||||||
|
|
||||||
|
## Calculate activity duration
|
||||||
|
## Data recorded after 8pm is attached to the next day
|
||||||
|
sleep_data$day <- date(sleep_data$datetime_from + hours(4))
|
||||||
|
sleep_data$duration <- int_length(
|
||||||
|
sleep_data$datetime_from %--% sleep_data$datetime_to
|
||||||
|
) / period_to_seconds(minutes(1))
|
||||||
|
sleep_data$bed_time <- case_when(
|
||||||
|
sleep_data$RAW_KIND == 1 ~ sleep_data$datetime_from
|
||||||
|
)
|
||||||
|
sleep_data$wakeup_time <- case_when(
|
||||||
|
sleep_data$RAW_KIND == 1 ~ sleep_data$datetime_to
|
||||||
|
)
|
||||||
|
|
||||||
|
## Convert RAW_KIND to the corresponding activity and summarize values
|
||||||
|
sleep_data <- sleep_data %>%
|
||||||
|
spread(RAW_KIND,duration,sep="_") %>%
|
||||||
|
group_by(day) %>%
|
||||||
|
summarise(
|
||||||
|
sleep = sum(RAW_KIND_1, na.rm = TRUE),
|
||||||
|
deep_sleep = sum(RAW_KIND_2, na.rm = TRUE),
|
||||||
|
nap = sum(RAW_KIND_3, na.rm = TRUE),
|
||||||
|
deep_nap = sum(RAW_KIND_4, na.rm = TRUE),
|
||||||
|
walk = sum(RAW_KIND_5, na.rm = TRUE),
|
||||||
|
run = sum(RAW_KIND_6, na.rm = TRUE), # Really not sure about this one
|
||||||
|
bed_time = min(bed_time, na.rm = TRUE),
|
||||||
|
wakeup_time = max(wakeup_time, na.rm = TRUE)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
# Visualisation
|
||||||
|
## Steps
|
||||||
|
### Distribution of steps
|
||||||
|
```{r}
|
||||||
|
data <- steps_data %>%
|
||||||
|
mutate(date = date(datetime)) %>%
|
||||||
|
select(date,STEPS) %>%
|
||||||
|
group_by(date) %>%
|
||||||
|
summarise(steps = sum(STEPS))
|
||||||
|
|
||||||
|
ggplot(data, aes(steps)) +
|
||||||
|
geom_histogram(binwidth = 500) +
|
||||||
|
theme_minimal() +
|
||||||
|
labs(x="Steps", y="Number of occurences")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Distribution of steps per day of week
|
||||||
|
```{r}
|
||||||
|
data <- steps_data %>%
|
||||||
|
mutate(
|
||||||
|
date = date(datetime),
|
||||||
|
wday = wday(
|
||||||
|
datetime,
|
||||||
|
label = TRUE,
|
||||||
|
week_start = getOption("lubridate.week.start", 1)
|
||||||
|
)
|
||||||
|
) %>%
|
||||||
|
select(date, wday, STEPS) %>%
|
||||||
|
group_by(date, wday) %>%
|
||||||
|
summarise(steps = sum(STEPS))
|
||||||
|
|
||||||
|
ggplot(data, aes(x=wday,y=steps)) +
|
||||||
|
geom_boxplot() +
|
||||||
|
theme_minimal() +
|
||||||
|
labs(x="Day of week", y="Number of steps")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Number of steps per month
|
||||||
|
```{r}
|
||||||
|
data <- steps_data %>%
|
||||||
|
mutate(month = floor_date(datetime, unit = "month")) %>%
|
||||||
|
select(month,STEPS) %>%
|
||||||
|
group_by(month) %>%
|
||||||
|
summarise(steps = sum(STEPS))
|
||||||
|
|
||||||
|
ggplot(data, aes(x=month, y=steps)) +
|
||||||
|
geom_col() +
|
||||||
|
scale_x_datetime(labels = date_format("%Y-%m")) +
|
||||||
|
scale_y_continuous(
|
||||||
|
breaks = seq(0,500000,50000),
|
||||||
|
labels=function(x) format(x, big.mark = " ")
|
||||||
|
) +
|
||||||
|
geom_smooth(method = lm) +
|
||||||
|
theme_minimal() +
|
||||||
|
labs(x="Month", y="Number of steps")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Average number of steps per hour of the day, year after year
|
||||||
|
```{r}
|
||||||
|
data <- steps_data %>%
|
||||||
|
mutate(
|
||||||
|
date = date(datetime),
|
||||||
|
time = datetime-floor_date(datetime, unit="day"),
|
||||||
|
year = year(datetime)
|
||||||
|
) %>%
|
||||||
|
group_by(date) %>%
|
||||||
|
mutate(cumsteps = cumsum(STEPS)) %>%
|
||||||
|
select(date, time, year, cumsteps) %>%
|
||||||
|
ungroup() %>%
|
||||||
|
group_by(time, year) %>%
|
||||||
|
summarise(min = min(cumsteps), max = max(cumsteps), average = mean(cumsteps))
|
||||||
|
|
||||||
|
ggplot(data) +
|
||||||
|
geom_step(aes(x=time, y=average)) +
|
||||||
|
theme_minimal() +
|
||||||
|
labs(title = "Average number of steps per hour of the day", x="Hour", y="Number of steps") +
|
||||||
|
scale_x_continuous(
|
||||||
|
breaks = seq(
|
||||||
|
0,
|
||||||
|
period_to_seconds(hours(24)),
|
||||||
|
period_to_seconds(hours(1))
|
||||||
|
),
|
||||||
|
labels = seq(0,24,1)
|
||||||
|
) +
|
||||||
|
facet_wrap(vars(year))
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Active time per week
|
||||||
|
```{r}
|
||||||
|
data <- sleep_data %>%
|
||||||
|
mutate(
|
||||||
|
wday = wday(
|
||||||
|
day,
|
||||||
|
label = TRUE,
|
||||||
|
week_start = getOption("lubridate.week.start", 1)
|
||||||
|
)
|
||||||
|
) %>%
|
||||||
|
select(day, wday, walk) %>%
|
||||||
|
group_by(day, wday) %>%
|
||||||
|
summarise(walk_time = sum(walk)/60)
|
||||||
|
|
||||||
|
ggplot(data, aes(x=wday,y=walk_time)) +
|
||||||
|
geom_boxplot() +
|
||||||
|
theme_minimal() +
|
||||||
|
labs(x="Day of week", y="Hours active")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sleep
|
||||||
|
### Distribution of sleep duration
|
||||||
|
```{r}
|
||||||
|
data <- sleep_data %>%
|
||||||
|
group_by(day) %>%
|
||||||
|
summarise(sleep_duration = sum(sleep)/60)
|
||||||
|
|
||||||
|
ggplot(data) +
|
||||||
|
geom_histogram(aes(sleep_duration), bins = 50) +
|
||||||
|
scale_x_continuous(breaks = seq(0,12,1)) +
|
||||||
|
theme_minimal() +
|
||||||
|
labs(x="Sleep duration (hours)", y="Number of occurences")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Distribution of deep sleep duration
|
||||||
|
```{r}
|
||||||
|
data <- sleep_data %>%
|
||||||
|
group_by(day) %>%
|
||||||
|
summarise(deep_sleep_duration = sum(deep_sleep)/60)
|
||||||
|
|
||||||
|
ggplot(data) +
|
||||||
|
geom_histogram(aes(deep_sleep_duration),bins = 50) +
|
||||||
|
scale_x_continuous(breaks = seq(0,12,1)) +
|
||||||
|
theme_minimal() +
|
||||||
|
labs(x="Deep sleep duration (hours)", y="Number of occurences")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Distribution of sleep duration per day of week, year after year
|
||||||
|
```{r}
|
||||||
|
data <- sleep_data %>%
|
||||||
|
mutate(
|
||||||
|
wday = wday(
|
||||||
|
day,
|
||||||
|
label = TRUE,
|
||||||
|
week_start = getOption("lubridate.week.start", 1)
|
||||||
|
),
|
||||||
|
year = year(day)
|
||||||
|
) %>%
|
||||||
|
select(year, day, wday, sleep) %>%
|
||||||
|
group_by(year, day, wday) %>%
|
||||||
|
summarise(sleep_duration = sum(sleep)/60)
|
||||||
|
|
||||||
|
ggplot(data, aes(x=wday,y=sleep_duration)) +
|
||||||
|
geom_boxplot() +
|
||||||
|
theme_minimal() +
|
||||||
|
labs(x="Day of week", y="Sleep duration") +
|
||||||
|
facet_grid(rows = vars(year), )
|
||||||
|
```
|
||||||
|
|
||||||
|
### Distribution of nap duration
|
||||||
|
```{r}
|
||||||
|
data <- sleep_data %>%
|
||||||
|
filter(nap > 0) %>%
|
||||||
|
group_by(day) %>%
|
||||||
|
summarise(nap_time = sum(nap))
|
||||||
|
|
||||||
|
ggplot(data) +
|
||||||
|
geom_histogram(aes(nap_time), bins = 10) +
|
||||||
|
scale_x_continuous(breaks = seq(0,240,15)) +
|
||||||
|
theme_minimal() +
|
||||||
|
labs(x="Nap duration (minutes)", y="Number of occurences")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Time of bed and waking up by year
|
||||||
|
```{r}
|
||||||
|
data <- sleep_data %>%
|
||||||
|
mutate(
|
||||||
|
year = year(day),
|
||||||
|
month = floor_date(day, unit = "month"),
|
||||||
|
bed_time_hms = hms::as.hms(
|
||||||
|
period_to_seconds(
|
||||||
|
hours(hour(bed_time)) + minutes(minute(bed_time))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
wakeup_time_hms = hms::as.hms(
|
||||||
|
period_to_seconds(
|
||||||
|
hours(hour(wakeup_time))+minutes(minute(wakeup_time))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) %>%
|
||||||
|
drop_na(bed_time_hms) %>%
|
||||||
|
drop_na(wakeup_time_hms)
|
||||||
|
|
||||||
|
ggplot(data) +
|
||||||
|
geom_histogram(aes(bed_time_hms), fill="orange", alpha=0.5, bins=30) +
|
||||||
|
geom_histogram(aes(wakeup_time_hms), fill="blue", alpha=0.5, bins=30) +
|
||||||
|
scale_x_continuous(
|
||||||
|
breaks = seq(
|
||||||
|
0,
|
||||||
|
period_to_seconds(hours(24)),
|
||||||
|
period_to_seconds(hours(1))
|
||||||
|
),
|
||||||
|
labels = seq(0,24,1)
|
||||||
|
) +
|
||||||
|
theme_minimal() +
|
||||||
|
labs(x="Bed time and wakeup time", y="Number of occurences") +
|
||||||
|
facet_grid(rows = vars(year),scales="free_y")
|
||||||
|
```
|
508
pebble_health_stats.html
Normal file
508
pebble_health_stats.html
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user