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

Rの解析に役に立つ記事
スポンサーリンク

健康管理や研究目的でのスマートウォッチの利用を目にすることが多くなってきました。スマートウォッチは各社から販売されていますが、マラソンランナーなどに人気の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())

#&#12487;&#12540;&#12479;&#12398;&#30906;&#35469;
#listMessageTypes(ReadFit)
#[1] "file_id"         "event"           "device_info"     "software"       
#[5] "monitoring"      "monitoring_info" "ohr_settings"    "stress_level" 

###&#36899;&#32154;&#20966;&#29702;&#12377;&#12427;&#12383;&#12417;&#12398;&#28310;&#20633;#####
#&#23637;&#38283;&#12375;&#12383;fit&#12501;&#12449;&#12452;&#12523;&#12398;&#12501;&#12457;&#12523;&#12480;&#12434;&#20316;&#26989;&#12487;&#12451;&#12524;&#12463;&#12488;&#12522;&#12395;&#12377;&#12427;
setwd(choose.dir())

#&#12501;&#12449;&#12452;&#12523;&#21517;&#12434;&#21462;&#24471;
GetFileNames <- list.files()

#_WELLNESS.fit&#12434;&#23550;&#35937;&#12395;&#12377;&#12427;
AnaFileNames <- str_subset(GetFileNames, "WELLNESS")

#&#12487;&#12540;&#12479;&#26684;&#32013;&#29992;&#24341;&#25968;
ActivityData <- NULL
HartRateData <- NULL
StressData <- NULL

###&#32368;&#12426;&#36820;&#12375;&#20966;&#29702;#####
for(i in seq(AnaFileNames)){

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

  if("event" %in% listMessageTypes(ReadFit)){
###&#27963;&#21205;&#20869;&#23481;,&#24515;&#25293;&#25968;&#12398;&#21462;&#24471;#####
MonitoringData <- getMessagesByType(ReadFit,
                                    message_type = "monitoring")

#&#26085;&#20184;&#12369;&#12398;&#19968;&#25324;&#22793;&#25563;
for(n in seq(MonitoringData)){

  #&#26085;&#20184;&#12369;&#12487;&#12540;&#12479;&#12398;&#22793;&#25563;
  MonitoringData[[n]] <-
    MonitoringData[[n]] %>%
    mutate_if(is.POSIXt, ~with_tz(., tz = "Asia/Tokyo")) 
  
}

###timestamp_16&#12398;&#22793;&#25563;#####
#&#21442;&#32771;&#36039;&#26009;:https://developer.garmin.com/fit/cookbook/datetime/
#fit&#12501;&#12449;&#12452;&#12523;&#12398;&#26178;&#38291;&#36215;&#28857;&#12399;&#12300;December 31, 1989 UTC&#65288;631065600&#65289;&#12301;&#12394;&#12398;&#12391;&#12289;
#&#12300;timestamp_16&#12301;&#12399;631065600&#12434;&#12458;&#12501;&#12475;&#12483;&#12488;&#12395;&#20351;&#29992;&#12377;&#12427;
#fit&#12501;&#12449;&#12452;&#12523;&#38283;&#22987;&#26178;&#38291;&#12398;&#21462;&#24471;
TimCre <- as.numeric(as.POSIXct(file_id(ReadFit)[[2]])[[1]]) - 631065600

#&#22793;&#25563;&#20966;&#29702;
for(n in seq(MonitoringData)){
  
  #&#26085;&#20184;&#12369;&#12487;&#12540;&#12479;&#12398;&#22793;&#25563;
  MonitoringData[[n]] <-
    MonitoringData[[n]] %>%
    mutate_at(vars(matches("timestamp_16")),
              function(.){
                TimCre +
                  #bbase::it&#28436;&#31639;:python&#12391;&#12300;X & 0xffff&#12301;
                  bitwAnd((. - TimCre), 0xffff) + 631065600 %>%
                  lubridate::as_datetime() %>%
                  lubridate::with_tz(tz = "Asia/Tokyo")})
}
########

###&#12450;&#12463;&#12486;&#12451;&#12499;&#12486;&#12451;&#12487;&#12540;&#12479;&#12434;&#21462;&#24471;#####
#"current_activity_type_intensity"&#12434;&#21547;&#12416;&#21015;&#21517;&#12398;list&#30058;&#21495;&#12434;&#21462;&#24471;
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)
  
}}

###&#24515;&#25293;&#25968;&#12487;&#12540;&#12479;&#12434;&#21462;&#24471;#####
#"heart_rate"&#12434;&#21547;&#12416;&#21015;&#21517;&#12398;list&#30058;&#21495;&#12434;&#21462;&#24471;
HartRateNo <- which(sapply(MonitoringData, function(x) "heart_rate" %in% colnames(x)))
#HartRate <- rbind(HartRate, MonitoringData[[HartRateNo]])
HartRateData <- bind_rows(HartRateData, MonitoringData[[HartRateNo]])

###&#12473;&#12488;&#12524;&#12473;&#12524;&#12505;&#12523;&#12398;&#21462;&#24471;#####
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)

  }
}

#&#21015;&#21517;&#12434;&#25972;&#12360;&#12427;
colnames(ActivityData) <- c("Time_Stamp", "Activity_type_intensity")
colnames(HartRateData) <- c("Time_Stamp", "Hart_Rate")
colnames(StressData) <- c("Time_Stamp", "Stress_Level")

#&#12458;&#12502;&#12472;&#12455;&#12463;&#12488;:"ActivityData","HartRate","StressData"&#20197;&#22806;&#12434;&#21066;&#38500;
remove(list = ls()[!ls() %in% c("ActivityData", "HartRateData", "StressData")])
########

出力例

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

#tidyverse&#12497;&#12483;&#12465;&#12540;&#12472;&#12364;&#12394;&#12369;&#12428;&#12400;&#12452;&#12531;&#12473;&#12488;&#12540;&#12523;
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&#12497;&#12483;&#12465;&#12540;&#12472;&#12364;&#12394;&#12369;&#12428;&#12400;&#12452;&#12531;&#12473;&#12488;&#12540;&#12523;
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()

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

タイトルとURLをコピーしました