Skip to content

Commit 8ac938a

Browse files
Merge pull request #80 from wadpac/issue78_fitbit_heartrate
Enable reading of fitbit heartrate data
2 parents 8f9d972 + 3932ea5 commit 8ac938a

File tree

6 files changed

+1262
-5
lines changed

6 files changed

+1262
-5
lines changed

NEWS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# Changes in version 1.0.5 (release date:??-??-2025)
2+
3+
- Fitbit: Now also loads heart rate data #78
4+
5+
- ActiWatch: Enable recognition of file extension when filename has more than one dot.
6+
17
# Changes in version 1.0.4 (release date:31-03-2025)
28

39
- Fitbit: Fix bug preventing the loading of a sequence of json files. #76

R/getExtension.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ getExtension <- function(filename){
22
# Extract file extension
33
ex <- unlist(strsplit(basename(filename), split = "[.]"))
44
if (length(ex) < 2) stop(paste0("Cannot recognise extension from '", filename, "' as filename, please check"), call. = FALSE)
5-
return(ex[-1])
5+
return(ex[length(ex)])
66
}
77

88

R/mergeFitbitData.R

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,36 @@ mergeFitbitData = function(filenames = NULL, desiredtz = "", configtz = NULL) {
33
stop("Provide at least two filenames")
44
}
55
cnt = 1
6+
7+
# change order filenames such that
8+
# heart rate if present is at the end, because heart rate
9+
# has irregular epoch length and needs to be interpolated
10+
filenames = filenames[c(grep(pattern = "sleep", x = basename(filenames), invert = FALSE),
11+
grep(pattern = "calories", x = basename(filenames), invert = FALSE),
12+
grep(pattern = "step", x = basename(filenames), invert = FALSE),
13+
grep(pattern = "heart", x = basename(filenames), invert = FALSE))]
14+
615
while (cnt <= length(filenames)) {
716
D = readFitbit(filename = filenames[cnt], desiredtz = desiredtz, configtz = configtz)
817
if (cnt == 1) {
918
data = D
1019
} else {
20+
if (length(grep("heart", x = colnames(D))) > 0) {
21+
# interpolate heart rate to 30 seconds to ease merging with
22+
# other data types
23+
t0 = round(as.numeric(D$dateTime[1]) / 30) * 30
24+
t1 = round(as.numeric(D$dateTime[nrow(D)]) / 30) * 30
25+
newTime = seq(t0, t1, by = 30)
26+
newD = as.data.frame(resample(raw = as.matrix(D$heart_rate),
27+
rawTime = as.numeric(D$dateTime),
28+
time = newTime,
29+
stop = nrow(D)))
30+
colnames(newD)[1] = "heart_rate"
31+
if (length(newTime) > nrow(newD)) newTime = newTime[1:nrow(newD)]
32+
newD$dateTime = as.POSIXct(newTime, tz = desiredtz)
33+
rm(D)
34+
D = newD[, c("dateTime", "heart_rate")]
35+
}
1136
# double names is possible when recording is split across json files
1237
# in that case there may be multiple calories, steps and sleep files
1338
doubleNames = colnames(D)[colnames(D) %in% colnames(data)]
@@ -43,8 +68,7 @@ mergeFitbitData = function(filenames = NULL, desiredtz = "", configtz = NULL) {
4368
cnt = cnt + 1
4469
}
4570
data = data[order(data$dateTime),]
46-
47-
# fill gaps
71+
# fill gaps with NA values
4872
timeRange = range(data$dateTime)
4973
epochSize = min(diff(as.numeric(data$dateTime[1:pmin(10, nrow(data))])))
5074
timeFrame = data.frame(dateTime = seq( timeRange[1], timeRange[2], by = epochSize))

R/readFitbit.R

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,15 @@ readFitbit = function(filename = NULL, desiredtz = "",
7777
D = handleTimeGaps(data, epochSize = 30)
7878
D$value = as.numeric(D$value) / 2
7979
colnames(D)[2] = dataType
80+
} else if (dataType == "heart_rate") {
81+
collapseHR = function(x) {
82+
y = data.frame(dateTime = x$dateTime, bpm = x$value$bpm, confidence = x$value$confidence)
83+
return(y)
84+
}
85+
D = as.data.frame(data.table::rbindlist(lapply(D, collapseHR), fill = TRUE))
86+
D = D[!duplicated(D),]
87+
D$dateTime = as.POSIXct(D$dateTime, format = "%m/%d/%y %H:%M:%S", tz = configtz)
88+
colnames(D)[2] = dataType
8089
} else {
8190
stop("File type not recognised")
8291
}

0 commit comments

Comments
 (0)