Non-wear detection from ActiGraph data

The actigraph.sleepr package implements two non-wear detection algorithms: Troiano (Troiano et al. 2008) and Choi (Choi et al. 2011). For illustration let’s use one day of sample data recorded by a GT3X+ monitor. See the sleep vignette for details on reading AGD files and collapsing epochs.

file_10s <- system.file("extdata", "ActiSleepPlus-RawData-Day01.agd",
  package = "actigraph.sleepr"
)
agdb_60s <- read_agd(file_10s) %>%
  collapse_epochs(60) %>%
  mutate(date = lubridate::as_date(timestamp)) %>%
  select(date, timestamp, starts_with("axis")) %>%
  group_by(date)
agdb_60s
#> # A tibble: 1,440 × 5
#> # Groups:   date [2]
#>    date       timestamp           axis1 axis2 axis3
#>    <date>     <dttm>              <int> <int> <int>
#>  1 2012-04-04 2012-04-04 13:29:00   600  1025   891
#>  2 2012-04-04 2012-04-04 13:30:00    88   367    74
#>  3 2012-04-04 2012-04-04 13:31:00   493   646   622
#>  4 2012-04-04 2012-04-04 13:32:00   358   284   174
#>  5 2012-04-04 2012-04-04 13:33:00   290   341    77
#>  6 2012-04-04 2012-04-04 13:34:00    57   166    23
#>  7 2012-04-04 2012-04-04 13:35:00   158   240    40
#>  8 2012-04-04 2012-04-04 13:36:00    68   214   102
#>  9 2012-04-04 2012-04-04 13:37:00  1500  1562  1252
#> 10 2012-04-04 2012-04-04 13:38:00   165   296    59
#> # ℹ 1,430 more rows

Long stretches that consist almost entirely of zero counts (zero epochs) suggest that the device wasn’t worn at all and therefore should be excluded from downstream analysis.

Non-wear detection with the Troiano algorithm

The Troiano algorithm for detecting periods of non-wear formalizes a technique used to analyze the 2003-2004 NHANES data; the original SAS source code can be found at https://riskfactor.cancer.gov/tools/nhanes_pam/. The method has some flexibility as a non-wear period can contain a few nonzero epochs of artifactual movement (spikes).

activity_threshold
Highest activity level to be considered “zero”; an epoch with activity exceeding the threshold is considered a “spike”. The default threshold is 0.
min_period_len
Minimum number of consecutive zero epoch to start a non-wear period. The default is 60.
max_nonzero_count
Epochs with activity greater than max_nonzero_count are labeled “zero”. The default is Inf.
spike_tolerance
Also known as artifactual movement interval. At most spike_tolerance “nonzero” epochs can occur in sequence during a non-wear period without interrupting it. The default is 2.
spike_stoplevel
An activity spike that exceeds spike_stoplevel counts ends a non-wear period, even if the spike tolerance has not been reached. The default is 100.
use_magnitude
If true, the magnitude of the vector (axis1, axis2, axis3) is used to measure activity; otherwise the axis1 value is used. The default is FALSE.
endat_nnz_seq
If true, a non-wear period ends with a run of nonzero epochs that is longer than spike_tolerance. This corresponds to the option “Require consecutive epochs outside of the activity threshold” in ActiLife’s Wear Time Validation menu. The default is TRUE.

The Troiano algorithm specifies that a non-wear period starts with min_length consecutive epochs/minutes of zero activity and ends with more than spike_tolerance epochs/minutes of nonzero activity.

periods_nonwear <- agdb_60s %>% apply_troiano(min_period_len = 45)
periods_nonwear
#> # A tibble: 2 × 4
#> # Groups:   date [1]
#>   date       period_start        period_end          length
#>   <date>     <dttm>              <dttm>               <int>
#> 1 2012-04-05 2012-04-05 02:56:00 2012-04-05 03:59:00     63
#> 2 2012-04-05 2012-04-05 04:54:00 2012-04-05 05:45:00     51

The parameter settings of the Troiano algorithm are stored as attributes.

tail(attributes(periods_nonwear), 8)
#> $nonwear_algorithm
#> [1] "Troiano"
#> 
#> $min_period_len
#> [1] 45
#> 
#> $max_nonzero_count
#> [1] Inf
#> 
#> $spike_tolerance
#> [1] 2
#> 
#> $spike_stoplevel
#> [1] 100
#> 
#> $activity_threshold
#> [1] 0
#> 
#> $endat_nnz_seq
#> [1] TRUE
#> 
#> $use_magnitude
#> [1] FALSE

Once non-wear periods are detected, we can further screen those intervals. For example, we can ignore non-wear periods that are too short.

periods_nonwear %>% filter(length >= 60)
#> # A tibble: 1 × 4
#> # Groups:   date [1]
#>   date       period_start        period_end          length
#>   <date>     <dttm>              <dttm>               <int>
#> 1 2012-04-05 2012-04-05 02:56:00 2012-04-05 03:59:00     63

Or we can flag 24-hour day as invalid if the device was worn only for a short period of time.

# Find the periods during which the device was worn
periods_wear <- complement_periods(
  periods_nonwear, agdb_60s,
  period_start, period_end
)
periods_wear
#> # A tibble: 4 × 4
#> # Groups:   date [2]
#>   date       period_start        period_end          length
#>   <date>     <dttm>              <dttm>               <int>
#> 1 2012-04-04 2012-04-04 13:29:00 2012-04-04 23:59:00    631
#> 2 2012-04-05 2012-04-05 00:00:00 2012-04-05 02:55:00    176
#> 3 2012-04-05 2012-04-05 04:00:00 2012-04-05 04:53:00     54
#> 4 2012-04-05 2012-04-05 05:46:00 2012-04-05 13:28:00    463

# Label each epoch with the period_id of the wear period it falls in
# or with NA if the epoch falls outside a wear period
agdb_wear <- combine_epochs_periods(
  agdb_60s, periods_wear,
  period_start, period_end
)
agdb_wear
#> # A tibble: 1,440 × 6
#> # Groups:   date [2]
#>    date       timestamp           axis1 axis2 axis3 period_id
#>    <date>     <dttm>              <int> <int> <int>     <int>
#>  1 2012-04-04 2012-04-04 13:29:00   600  1025   891         1
#>  2 2012-04-04 2012-04-04 13:30:00    88   367    74         1
#>  3 2012-04-04 2012-04-04 13:31:00   493   646   622         1
#>  4 2012-04-04 2012-04-04 13:32:00   358   284   174         1
#>  5 2012-04-04 2012-04-04 13:33:00   290   341    77         1
#>  6 2012-04-04 2012-04-04 13:34:00    57   166    23         1
#>  7 2012-04-04 2012-04-04 13:35:00   158   240    40         1
#>  8 2012-04-04 2012-04-04 13:36:00    68   214   102         1
#>  9 2012-04-04 2012-04-04 13:37:00  1500  1562  1252         1
#> 10 2012-04-04 2012-04-04 13:38:00   165   296    59         1
#> # ℹ 1,430 more rows

# Once we add the wear/nonwear information, it is straightforward to
# compute summary statistics or join with external minute-by-minute data
agdb_wear %>%
  group_by(period_id, .add = TRUE) %>%
  summarise(
    time_worn = n(),
    ave_counts = mean(axis1),
    .groups = "drop_last"
  )
#> # A tibble: 5 × 4
#> # Groups:   date [2]
#>   date       period_id time_worn ave_counts
#>   <date>         <int>     <int>      <dbl>
#> 1 2012-04-04         1       631    1446.  
#> 2 2012-04-05         1       176      75.9 
#> 3 2012-04-05         2        54      31.5 
#> 4 2012-04-05         3       463    1208.  
#> 5 2012-04-05        NA       116       8.42

Non-wear detection with the Choi algorithm

The Choi algorithm extends the Troiano algorithm by requiring that short spikes of artifactual movement during a non-wear period are preceded and followed by consecutive zero epochs.

min_period_len
Minimum number of consecutive zero epoch to start a non-wear period. The default is 90.
min_window_len
The minimum number of consecutive “zero” epochs immediately preceding and following a spike of artifactual movement. The default is 30.
spike_tolerance
Also known as artifactual movement interval. At most spike_tolerance “nonzero” epochs can occur in sequence during a non-wear period without interrupting it. The default is 2.
use_magnitude
If true, the magnitude of the vector (axis1, axis2, axis3) is used to measure activity; otherwise the axis1 value is used. The default is FALSE.
periods_nonwear <- agdb_60s %>% apply_choi(
  min_period_len = 45,
  min_window_len = 10
)
periods_nonwear
#> # A tibble: 1 × 4
#> # Groups:   date [1]
#>   date       period_start        period_end          length
#>   <date>     <dttm>              <dttm>               <int>
#> 1 2012-04-05 2012-04-05 03:03:00 2012-04-05 03:59:00     56

Again, the parameter settings are saved as attributes.

tail(attributes(periods_nonwear), 5)
#> $nonwear_algorithm
#> [1] "Choi"
#> 
#> $min_period_len
#> [1] 45
#> 
#> $min_window_len
#> [1] 10
#> 
#> $spike_tolerance
#> [1] 2
#> 
#> $use_magnitude
#> [1] FALSE

References

Troiano, Richard P, David Berrigan, Kevin W Dodd, Louise C Mâsse, Timothy Tilert, and Margaret McDowell. 2008. “Physical Activity in the United States Measured by Accelerometer.” Medicine & Science in Sports & Exercise 40 (1): 181–88.

Choi, Leena, Zhouwen Liu, Charles E. Matthews, and Maciej S. Buchowski. 2011. “Validation of Accelerometer Wear and Nonwear Time Classification Algorithm.” Medicine & Science in Sports & Exercise 43 (2): 357–64.