The purpose of this Vignette is to show the main functionality of the fitbitViz R package. You can read more about the Fitbit Web API and how to create an application to receive a token and the user-id in the README.md file. For the rest of this vignette I’ll assume that the following variables are defined:
#..................
# parameter setting
#..................
= 'My user-id' # Specify here your 'user-id'
USER_ID = "My token" # Specify here your 'token' token
= 11 # for this use case pick the 11th week of the year 2021
WEEK
= 135 # print that many character in case of an error
num_character_error
= fitbitViz:::split_year_in_weeks(year = 2021) # split a year in weeks
weeks_2021
# Start the week at monday (see: https://github.com/tidyverse/lubridate/issues/509)
= lubridate::floor_date(lubridate::ymd(weeks_2021[WEEK]), unit = 'weeks') + 1
date_start
# Add 6 days to the 'date_start' variable to come to a 7-days plot
= date_start + 6
date_end
= "00H 40M 0S"
sleep_time_begins = "08H 00M 0S"
sleep_time_ends
= FALSE # disable verbosity VERBOSE
The previous code snippet uses one week of my personal Fitbit data (the 11th week of 2021) to plot my
The data for all these functions are available to download using the csv buttons in this Rmarkdown file.
The heart_rate_time_series() function takes the user-id, token, the start- and end-dates, the start- and end-time, the detail level (1 minute) and returns the heart rate time series. Each output plot (of the multiplot) includes in the x-axis the time and in the y-axis the heart rate value. The highest heart rate value (peak) of the day is highlighted using a vertical and horizontal blue line,
#.......................
# heart rate time series
#.......................
= fitbitViz::heart_rate_time_series(user_id = USER_ID,
heart_dat token = token,
date_start = as.character(date_start),
date_end = as.character(date_end),
time_start = '00:00',
time_end = '23:59',
detail_level = '1min',
ggplot_intraday = TRUE,
ggplot_ncol = 2,
ggplot_nrow = 4,
verbose = VERBOSE,
show_nchar_case_error = num_character_error)
$plt heart_dat
The heart rate heatmap shows the min, median and max heart rate Levels in the y-axis for each day of the specified week (x-axis). As the legend shows, the displayed values range from 40 to 220 and higher values appear in purple or orange color,
#............................
# heart rate intraday heatmap [ plot options: https://yihui.org/knitr/options/#plots ]
#............................
= heart_dat$heart_rate_intraday
heart_intra
= fitbitViz::heart_rate_heatmap(heart_rate_intraday_data = heart_intra,
hrt_heat angle_x_axis = 0)
hrt_heat
This function computes the root mean square of successive differences (RMSSD) and a higher heart rate variability is linked with better health. Based on the Fitbit application information and the Wikipedia article the heart rate variability is computed normally in ms (milliseconds), however I use the ‘1min’ rather than the ‘1sec’ interval because I observed that it is more consistent,
#.......................
# heart rate variability
#.......................
= fitbitViz::heart_rate_variability_sleep_time(heart_rate_data = heart_dat,
hrt_rt_var sleep_begin = sleep_time_begins,
sleep_end = sleep_time_ends,
ggplot_hr_var = TRUE,
angle_x_axis = 25)
$hr_var_plot hrt_rt_var
= fitbitViz::heart_rate_variability_sleep_time(heart_rate_data = heart_dat,
hrt_rt_var sleep_begin = sleep_time_begins,
sleep_end = sleep_time_ends,
ggplot_hr_var = TRUE,
angle_x_axis = 25)
The sleep time series visualization is similar to the Fitbit Mobile Visualization and in the x-axis shows the specified by the user sleep time interval whereas in the y-axis shows the sleep Levels (wake, rem, light, deep). Lower levels like deep sleep appear in dark blue whereas higher levels like wake appear in light blue,
#.......................
# sleep data time series
#.......................
= fitbitViz::sleep_time_series(user_id = USER_ID,
sleep_ts token = token,
date_start = as.character(date_start),
date_end = as.character(date_end),
ggplot_color_palette = 'ggsci::blue_material',
ggplot_ncol = 2,
ggplot_nrow = 4,
show_nchar_case_error = num_character_error,
verbose = VERBOSE)
$plt_lev_segments sleep_ts
To make use of the GPS data from the Fitbit Application we have first to extract the log-id for a time interval after a specified Date,
#...................
# extract the log-id (required for the GPS data)
#...................
= fitbitViz::extract_LOG_ID(user_id = USER_ID,
log_id token = token,
after_Date = as.character(date_start),
limit = 10,
sort = 'asc',
verbose = VERBOSE)
# log_id
Once we have the log-id we can define the time zone of the route to receive all GPS data,
#....................................................
# return the gps-ctx data.table for the output log-id
#....................................................
= fitbitViz::GPS_TCX_data(log_id = log_id,
res_tcx user_id = USER_ID,
token = token,
time_zone = 'Europe/Athens',
verbose = VERBOSE)
# res_tcx
The following Leaflet Point Coordinates show my outdoor activity during the 11th week of 2021 (the legend shows the elevation of the route),
#................................
# Create the Leaflet / LeafGL Map
#................................
= fitbitViz::leafGL_point_coords(dat_gps_tcx = res_tcx,
res_lft color_points_column = 'AltitudeMeters',
provider = leaflet::providers$Esri.WorldImagery,
option_viewer = rstudioapi::viewer,
CRS = 4326)
res_lft
Another option of this package is to plot a route in 3-dimensional space. For this purpose we’ll use the rayshader package, which internally uses rgl (OpenGL). First, we have to extend the boundaries of our route for approximately 1.000 thousand meters (adjust this value depending on your area of interest),
#...................................................
# compute the sf-object buffer and the raster-extend (1000 meters buffer)
#...................................................
= fitbitViz::extend_AOI_buffer(dat_gps_tcx = res_tcx,
sf_rst_ext buffer_in_meters = 1000,
CRS = 4326,
verbose = VERBOSE)
# sf_rst_ext
Then for the extended area we will download Copernicus Digital Elevation Model (DEM) data. The Copernicus elevation data come either in 30 or in 90 meter resolution. We will pick the 30 meter resolution product for this route. The CopernicusDEM is an R package, make sure that you have installed and configured the awscli Operating System Requirement if you intend to download and reproduce the next 3-dimensional map using the elevation data,
#..................................................................
# Download the Copernicus DEM 30m elevation data
# there is also the option to download the DEM 90m elevation data
# which is of lower resolution but the image size is smaller which
# means faster download
#..................................................................
= tempdir()
dem_dir # dem_dir
= CopernicusDEM::aoi_geom_save_tif_matches(sf_or_file = sf_rst_ext$sfc_obj,
dem30 dir_save_tifs = dem_dir,
resolution = 30,
crs_value = 4326,
threads = parallel::detectCores(),
verbose = VERBOSE)
= list.files(dem_dir, pattern = '.tif', full.names = T)
TIF # TIF
if (length(TIF) > 1) {
#....................................................
# create a .VRT file if I have more than 1 .tif files
#....................................................
= file.path(dem_dir, 'VRT_mosaic_FILE.vrt')
file_out
= CopernicusDEM::create_VRT_from_dir(dir_tifs = dem_dir,
vrt_dem30 output_path_VRT = file_out,
verbose = VERBOSE)
}
if (length(TIF) == 1) {
#..................................................
# if I have a single .tif file keep the first index
#..................................................
= TIF[1]
file_out
}
#.......................................
# crop the elevation DEM based on the
# coordinates extent of the GPS-CTX data
#.......................................
= fitbitViz::crop_DEM(tif_or_vrt_dem_file = file_out,
raysh_rst sf_buffer_obj = sf_rst_ext$sfc_obj,
verbose = VERBOSE)
# terra::plot(raysh_rst)
The GPS route that I use is an ascending & descending route therefore we can convert the GPS (TCX) data to a spatial LINESTRING by using the maximum altitude as a split point of the route to visualize the ascending route in blue and the descending in red (there is also the alternative to specify the split point based on time using the time_split_asc_desc parameter),
= fitbitViz::gps_lat_lon_to_LINESTRING(dat_gps_tcx = res_tcx,
linestring_dat CRS = 4326,
time_split_asc_desc = NULL,
verbose = VERBOSE)
then we create the ‘elevation_sample_points’ data.table parameter for the 3-dim plot based on the min., middle and max. altitude of the previously computed ‘res_tcx’ data,
= c(which.min(res_tcx$AltitudeMeters),
idx_3m as.integer(length(res_tcx$AltitudeMeters) / 2),
which.max(res_tcx$AltitudeMeters))
= c('latitude', 'longitude', 'AltitudeMeters')
cols_3m = res_tcx[idx_3m, ..cols_3m] dat_3m
and finally we visualize the 3-dimensional Rayshader Map,
#.....................................................
# Conversion of the 'SpatRaster' to a raster object
# because the 'rayshader' package accepts only rasters
#.....................................................
= raster::raster(raysh_rst)
rst_obj ::projection(rst_obj) <- terra::crs(raysh_rst, proj = TRUE)
raster
= file.path(tempdir(), 'rayshader_img.png')
snapshot_rayshader_path
::open3d(useNULL = TRUE) # this removes the second rgl-popup-window
rgl
::rayshader_3d_DEM(rst_buf = rst_obj,
fitbitVizrst_ext = sf_rst_ext$raster_obj_extent,
linestring_ASC_DESC = linestring_dat,
elevation_sample_points = dat_3m,
zoom = 0.3,
windowsize = c(1000, 800),
add_shadow_rescale_original = FALSE,
verbose = TRUE)
::rgl.snapshot(snapshot_rayshader_path)
rgl::par3d(mouseMode = "trackball") # options: c("trackball", "polar", "zoom", "selecting")
rgl::rglwidget() rgl
In the output map we observe