Rで解析:Garmin Connectのfitファイルを操作する「FITfileR」パッケージ


健康管理や研究目的でのスマートウォッチの利用を目にすることが多くなってきました。スマートウォッチは各社から販売されていますが、マラソンランナーなどに人気のGarmin(ガーミン)で得られるデータをRで読み込むのに便利な「FITfileR」パッケージを紹介します。

GarminはGarmin Connectと連携することで、スマートフォンのアプリやGarmin Connectのサイトで各種データを確認することが出来ます。

Garmin Connectのサイトで入手できるデータは「fitファイル」というバイナリファイルです。

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

「fitファイル」のプロトコルが公開されていますので、興味ある方は「readBin」コマンドでファイルを解析してはいかがでしょうか。

なお、実行コマンドでも紹介していますが、「fitファイル」の時間起点は「December 31, 1989 UTC(631065600)」なので、「timestamp_16」は631065600をオフセットに使用することに注意です。

健康管理や研究目的でGarminのスマートウォッチの利用を検討してみたはいかがでしょうか。使用しているGarminnはVÍVOSMART 5です。睡眠やストレスなどの色々な健康情報を取得できます。

パッケージバージョンは0.1.5。実行コマンドはwindows 11のR version 4.2.1で確認しています。

fitファイルの入手

GarminとGarmin Connectの連携が終了している前提です。連携方法はGarminサイトなどで確認してください。

まずはGarmin Connectにアクセスします。

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

アクセス後「fitファイル」は以下の写真のステップで入手可能です。指定日ごとの「fitファイル」がzipで圧縮されたファイルを入手できます。

パッケージのインストール

下記コマンドを実行してください。

#パッケージのインストール
if(!requireNamespace("remotes")) {
  install.packages("remotes")
}
remotes::install_github("grimbough/FITfileR")

実行コマンドの紹介

詳細はコマンド、各パッケージのヘルプを確認してください。Garmin Connectから入手したzipファイルを展開して利用してください。展開後のフォルダに含まれる、全WELLNESS.fitを対象に処理して、心拍数、ストレスレベルのデータを入手するコマンド例です。対象「fitファイル」を「listMessageTypes」コマンドでメッセージを確認し、「getMessagesByType」コマンドで目的のデータを入手することが可能です。

#パッケージの読み込み
library("FITfileR")
#tidyverseパッケージがなければインストール
if(!require("tidyverse", quietly = TRUE)){
  install.packages("tidyverse");require("tidyverse")
}
#lubridateパッケージがなければインストール
#https://www.karada-good.net/analyticsr/r-467/
if(!require("lubridate", quietly = TRUE)){
  install.packages("lubridate");require("lubridate")
}

#wellnessファイルの読み込み
#ReadFit <- readFitFile(file.choose())

#データの確認
#listMessageTypes(ReadFit)
#[1] "file_id"         "event"           "device_info"     "software"       
#[5] "monitoring"      "monitoring_info" "ohr_settings"    "stress_level" 

###連続処理するための準備#####
#展開したfitファイルのフォルダを作業ディレクトリにする
setwd(choose.dir())

#ファイル名を取得
GetFileNames <- list.files()

#_WELLNESS.fitを対象にする
AnaFileNames <- str_subset(GetFileNames, "WELLNESS")

#データ格納用引数
ActivityData <- NULL
HartRateData <- NULL
StressData <- NULL

###繰り返し処理#####
for(i in seq(AnaFileNames)){

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

  if("event" %in% listMessageTypes(ReadFit)){
###活動内容,心拍数の取得#####
MonitoringData <- getMessagesByType(ReadFit,
                                    message_type = "monitoring")

#日付けの一括変換
for(n in seq(MonitoringData)){

  #日付けデータの変換
  MonitoringData[[n]] <-
    MonitoringData[[n]] %>%
    mutate_if(is.POSIXt, ~with_tz(., tz = "Asia/Tokyo")) 
  
}

###timestamp_16の変換#####
#参考資料:https://developer.garmin.com/fit/cookbook/datetime/
#fitファイルの時間起点は「December 31, 1989 UTC(631065600)」なので、
#「timestamp_16」は631065600をオフセットに使用する
#fitファイル開始時間の取得
TimCre <- as.numeric(as.POSIXct(file_id(ReadFit)[[2]])[[1]]) - 631065600

#変換処理
for(n in seq(MonitoringData)){
  
  #日付けデータの変換
  MonitoringData[[n]] <-
    MonitoringData[[n]] %>%
    mutate_at(vars(matches("timestamp_16")),
              function(.){
                TimCre +
                  #bbase::it演算:pythonで「X & 0xffff」
                  bitwAnd((. - TimCre), 0xffff) + 631065600 %>%
                  lubridate::as_datetime() %>%
                  lubridate::with_tz(tz = "Asia/Tokyo")})
}
########

###アクティビティデータを取得#####
#"current_activity_type_intensity"を含む列名のlist番号を取得
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)
  
}}

###心拍数データを取得#####
#"heart_rate"を含む列名のlist番号を取得
HartRateNo <- which(sapply(MonitoringData, function(x) "heart_rate" %in% colnames(x)))
#HartRate <- rbind(HartRate, MonitoringData[[HartRateNo]])
HartRateData <- bind_rows(HartRateData, MonitoringData[[HartRateNo]])

###ストレスレベルの取得#####
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)

  }
}

#列名を整える
colnames(ActivityData) <- c("Time_Stamp", "Activity_type_intensity")
colnames(HartRateData) <- c("Time_Stamp", "Hart_Rate")
colnames(StressData) <- c("Time_Stamp", "Stress_Level")

#オブジェクト:"ActivityData","HartRate","StressData"以外を削除
remove(list = ls()[!ls() %in% c("ActivityData", "HartRateData", "StressData")])
########

出力例

・心拍データをプロットする

#tidyverseパッケージがなければインストール
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()

・ストレスレデータをプロットする

#tidyverseパッケージがなければインストール
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()

少しでも、あなたの解析が楽になりますように!!

スポンサードリンク

スポンサードリンク