Analysis in R: “FITfileR” package to manipulate Garmin fit files

RAnalytics

Smartwatches are increasingly used for health management and research. The “FITfileR” package is a convenient way to read data from Garmin, popular with marathon runners and others, in R.

Garmin works with Garmin Connect, which allows users to check various data on a smartphone app or website.

The data available on the Garmin Connect Web site is a binary file called a “fit file.

Flexible and Interoperable Data Transfer (FIT) SDK:https://developer.garmin.com/fit/overview/

The “fit file” protocol is available to the public, so those interested can analyze the file with the “readBin” command.

Note that, as introduced in Example, the time starting point of the “fit file” is “December 31, 1989 UTC (631065600)”, so “timestamp_16” uses 631065600 as the offset.

You may want to consider using a Garmin smartwatch for health management and research purposes. The Garmin I use is the VÍVOSMART 5. It can track a variety of health information such as sleep and stress.

Package version is 0.1.5. Checked with R version 4.2.2.

スポンサーリンク
Sponsored Link

Get the fit file

Garmin and Garmin Connect are assumed to be paired. Please check the Garmin Web site and other sites for the connection method.

First, access Garmin Connect.

Garmin Connect:https://connect.garmin.com/signin/

After accessing the site, the fit file can be obtained by following the steps below. The “fit file” for any given day can be obtained as a zipped file.

Install Package

Run the following command.

#Install Package
if(!requireNamespace("remotes")) {
  install.packages("remotes")
}
remotes::install_github("grimbough/FITfileR")

Example

Refer to the commands and help for each package for details, and extract the zip file you received from Garmin Connect. The following is an example of a command that will process all WELLNESS.fit files in the extracted folder to obtain heart rate and stress level data. The “listMessageTypes” command can be used to check the target fit file for messages, and the “getMessagesByType” command can be used to get the target data.

#Loading the library
library("FITfileR")
#Install the tidyverse package if it is not already there
if(!require("tidyverse", quietly = TRUE)){
  install.packages("tidyverse");require("tidyverse")
}
#Install the lubridate package if it is not already there
#https://www.karada-good.net/analyticsr/r-467/
if(!require("lubridate", quietly = TRUE)){
  install.packages("lubridate");require("lubridate")
}

#Read fit files
#ReadFit <- readFitFile(file.choose())

#Check
#listMessageTypes(ReadFit)
#[1] "file_id"         "event"           "device_info"     "software"       
#[5] "monitoring"      "monitoring_info" "ohr_settings"    "stress_level" 

###Preparation for Continuous Processing#####
#Make the folder of the extracted fit file the working directory
setwd(choose.dir())

#Get file name
GetFileNames <- list.files()

#Target "XXX_WELLNESS.fit"
AnaFileNames <- str_subset(GetFileNames, "WELLNESS")

#Arguments for storing data
ActivityData <- NULL
HartRateData <- NULL
StressData <- NULL

###Iterative processing#####
for(i in seq(AnaFileNames)){

  ReadFit <- readFitFile(AnaFileNames[[i]])

  if("event" %in% listMessageTypes(ReadFit)){
###Activity and Heart Rate acquisition#####
MonitoringData <- getMessagesByType(ReadFit,
                                    message_type = "monitoring")

#Batch conversion of dates
for(n in seq(MonitoringData)){

  #conversion of dates
  MonitoringData[[n]] <-
    MonitoringData[[n]] %>%
    mutate_if(is.POSIXt, ~with_tz(., tz = "America/New_York")) 
  
}

###Conversion of timestamp_16#####
#Reference:https://developer.garmin.com/fit/cookbook/datetime/
#The time starting point of the fit file is "December 31, 1989 UTC (631065600)",
#so "timestamp_16" uses 631065600 as the offset.
#Get "fit" file start time
TimCre <- as.numeric(as.POSIXct(file_id(ReadFit)[[2]])[[1]]) - 631065600

#Conversion process
for(n in seq(MonitoringData)){
  
  #Conversion of Date Data
  MonitoringData[[n]] <-
    MonitoringData[[n]] %>%
    mutate_at(vars(matches("timestamp_16")),
              function(.){
                TimCre +
                  bitwAnd((. - TimCre), 0xffff) + 631065600 %>%
                  lubridate::as_datetime() %>%
                  lubridate::with_tz(tz = "Asia/Tokyo")})
}
########

###Get activity data#####
#Get list number of column names containing "current_activity_type_intensity"
ActivityNo <- which(sapply(MonitoringData,
                           function(x) "current_activity_type_intensity" %in% colnames(x)))

for(For_ActiNo in seq(length(ActivityNo))){
if("timestamp" %in% colnames(MonitoringData[[ActivityNo[For_ActiNo]]])){
  
  ActivityData <- bind_rows(ActivityData,
                            MonitoringData[[ActivityNo[For_ActiNo]]])
  
}else{
  
  MonitoringData[[ActivityNo[For_ActiNo]]] %>%
    select("timestamp_16", "current_activity_type_intensity") %>%
    bind_rows(ActivityData)
  
}}

###Get heart rate data#####
#Get list number of column names containing "heart_rate"
HartRateNo <- which(sapply(MonitoringData, function(x) "heart_rate" %in% colnames(x)))
#HartRate <- rbind(HartRate, MonitoringData[[HartRateNo]])
HartRateData <- bind_rows(HartRateData, MonitoringData[[HartRateNo]])

###Get stress levels#####
StressLevelData <- getMessagesByType(ReadFit,
                                     message_type = "stress_level") %>%
  mutate_if(is.POSIXt, ~with_tz(., tz = "Asia/Tokyo")) 

#StressData <- rbind(StressData, StressLevelData)
StressData <- bind_rows(StressData, StressLevelData)

  }
}

#Prepare column names
colnames(ActivityData) <- c("Time_Stamp", "Activity_type_intensity")
colnames(HartRateData) <- c("Time_Stamp", "Hart_Rate")
colnames(StressData) <- c("Time_Stamp", "Stress_Level")

#Delete all data except "ActivityData", "HartRate", and "StressData
remove(list = ls()[!ls() %in% c("ActivityData", "HartRateData", "StressData")])
########

Output Example

・Plot heart rate data

#Install the tidyverse package if it is not already there
if(!require("tidyverse", quietly = TRUE)){
  install.packages("tidyverse");require("tidyverse")
}
ggplot(HartRateData, aes(x = Time_Stamp, y = Hart_Rate)) +
  geom_area(fill = "#4b61ba") +
  labs(title = "Hart_Rate") +
  theme_minimal()

・Plot Stress Data

#Install the tidyverse package if it is not already there
if(!require("tidyverse", quietly = TRUE)){
  install.packages("tidyverse");require("tidyverse")
}
ggplot(StressData, aes(x = Time_Stamp, y = Stress_Level)) +
geom_area(fill = "#a87963") +
labs(title = "Stress_Level") +
theme_minimal()

I hope this makes your analysis a little easier !!

Copied title and URL