fNIRS Parkinson DT study - notebook

Introduction

This document is intended to be used as an “open lab notebook” for an fNIRS study on dual-task walking, analyzing an existing dataset, the Park-MOVE dataset collected between 2021 and 2023 (Franzén 2023). Note that while the data is in a central repository, access to the data is restricted and regulated by data transfer agreements.

To provide transparent and systematic support for the findings of the study, this document is updated throughout the study and contains:

  • Background and methods
  • Analysis plan

  • Sensitivity analysis

  • Performed analyses

  • Model validation and robustness analyses

This document is stored on GitHub pages: https://alkvi.github.io/fnirs_stroop_study/

The document is built from the project’s GitHub repository where all code is also contained: https://github.com/alkvi/fnirs_stroop_study

Version 1 was stored at OSF: https://osf.io/m7tx6/

How the dataset used in this study was prepared can be found in: https://github.com/alkvi/fnirs_dataset_preparation

Version history

Version history
Revision Date Update
1 2024-12-09 First version, with analysis plan
2 2025-02-12 Analyses performed, model diagnostics and outlier analysis done. One subject included in rev1 was excluded (after re-evaluating exclusion criteria). Moved format to Quarto.
3 2025-02-14 Update aim 3 to control for education, add details on missing data
4 2025-02-19 Changed correlation method in aim 3 behavioral part to Spearman (more robust), added exploratory analysis of PFC activation difference between groups, added signal quality metrics, used z-score normalization of covariates for more evenly scaled betas (does not affect statistical test values which are the same as before)
5 2025-04-14 Added subject-level ROI correlation to step time variability, exploration of LEDD
6 2025-04-25 Model statistics and formatting, filter properly for aim 1

Notebook rationale

Since we already have a collected dataset, on which we have already run analyses (albeit different analyses), we can take inspiration from Daniël Lakens, for example in (Lakens 2024):

We propose that the ‘open lab notebook’ terminology is more appropriate for studies where data already exist. […] Open lab notebooks focus on robustness analyses, sensitivity analyses, and triangulation to provide support for claims (Munafò and Smith 2018).

Or in the corresponding blog post (http://daniellakens.blogspot.com/2024/07/new-paper-benefits-of-preregistration.html):

That means we also do not expect people who have different epistemological philosophies to preregister – nor is it a logical solution for exploratory research, or certain types of secondary data analysis. We feel it is important to point this out, because there are alternative approaches to argue a test is severe that are better suited for those studies: open lab notebooks, sensitivity analyses, robustness checks, independent replication. It is always important to use the right tool for the job - we do not want preregistration to be mindlessly overused.

So we can be transparent in documenting how the data has already been used, publishing our final analysis plan on OSF, updating it along the way with steps performed as an open lab notebook (although not publishing any data), taking into consideration what we can do to support the severity of tests that will be performed.

Background

Walking can be affected by concurrent cognitive tasks, leading to poorer performance such as slower gait speed (Smith, Cusack, and Blake 2016), and increased gait variability (Smith et al. 2017). While this is true in both aging (Beurskens and Bock 2012) and various neurodegenerative diseases (McIsaac et al. 2018), people with Parkinson’s disease especially have been shown to have walking impairments elicited by dual-task gait (Nieuwhof et al. 2017), leading to fall risk and poorer quality of life (Kelly, Eusterbrock, and Shumway-Cook 2012).

One contributor to dual-task motor impairments and fall risk could be a “posture second” strategy, based on an observation that people with Parkinson’s disease have worse gait and balance while performing cognitive tasks while walking due to focusing on the cognitive task instead of maintaining balance (Bloem et al. 2006). However, in a later review of dual-task walking in Parkinson’s disease this observation was only demonstrated in one study out of several, and so remains unclear (Kelly, Eusterbrock, and Shumway-Cook 2012).

Another contributor to dual-task impairments is thought to be excessive attentional demands of normal gait due to reduced locomotor automaticity (Clark 2015). This is supported by the observation that dual-task impairments increase with cognitive load (Goh, Pearce, and Vas 2021; Maclean et al. 2017) and are exacerbated by reduced executive function (Yogev-Seligmann, Hausdorff, and Giladi 2008).

The indirect (Hamacher et al. 2015; Herold et al. 2017) or executive locomotor (Clark 2015; la Fougère et al. 2010) pathway involving the prefrontal cortex is thought to be involved in dual-task gait impairments. Specifically, this pathway may be relied upon more heavily in people with dual-task impairments due to both reduced locomotor automaticity and increased executive demands of the concurrent task.

One model among several (Festini, Zahodne, and Reuter-Lorenz 2018) for explaining the elevated attentional demand during normal walking in aging is the CRUNCH model (Reuter-Lorenz and Cappell 2008). This model aims to explain functional brain imaging observations of overactivation in older adults compared to younger adults during cognitive and motoric tasks, hypothesizing that overactivation of a region or network in the brain might be due to age-related atrophy (Reuter-Lorenz and Cappell 2008). Such compensation has been further divided into upregulation, selection, and reorganization (Cabeza et al. 2018), where reorganization of activity from automatic circuits to volitional prefrontal circuits during normal walking might explain increased dual-task interference.

The CRUNCH model could also be applied to pathological aging (Bunzeck et al. 2024) including Parkinson’s disease (Kim and Fraser 2022), with an important additional observation of compensatory mechanisms breaking down at a certain tipping point of neural degeneration or task demand (Bunzeck et al. 2024).

However, there are not many studies looking at this mechanism in Parkinson’s disease. While some studies do look at brain activity during dual-tasking in Parkinson’s disease (Kahya et al. 2019), there are only a few that investigate neural correlates of measured brain activity (Kim and Fraser 2022). Adding to the literature on neural correlates and putting results in the context of theoretical models would be a worthwhile effort.

To better understand prefrontal activity during walking and dual-task walking in older adults and people with PD, we set out to test models relating measures of gait automaticity to prefrontal activity, including testing whether cognitive capacity acts as a moderator.

Methods

This study uses data from the Park-MOVE dataset, collected between 2021 and 2023 (Franzén 2023). For this dataset, experiments took place across two sessions at the uMOVE core facility, Karolinska University Hospital, Solna, Stockholm. During the experimental sessions, clinical tests of balance, disease severity and a neuropsychological test battery were performed, along with fNIRS measurement during a block-based complex walking protocol. Participants performed the clinical tests and neuropsychological tests during one session and the fNIRS measurement during another, with approximately one week between the sessions.

Clinical tests of balance were performed for the older adult and PD groups with the Mini-Balance Evaluation Systems Test (Mini-BESTest) (Di Carlo et al. 2016) and disease severity for the PD group with the Movement Disorder Society-sponsored revision of the Unified Parkinson’s Disease Rating Scale (MDS-UPDRS) (Goetz et al. 2008).

The neuropsychological test battery comprised the following tests: The Color-Word Interference Test (CWIT) part III from the Delis-Kaplan Executive Function System (D-KEFS) (Delis 2001), Verbal Fluency part I-III (from D-KEFS), Trail Making Test (TMT) part II and IV (from D-KEFS) and Ray Auditory Verbal Learning Test (RAVLT) (Schmidt 1996).

The fNIRS system used was a NIRSport2 (NIRx) with 8 sources and 8 detectors, with short-separation detection channels for each source to allow for removing superficial blood flow changes in the signal. The optodes transmitted light at 760 and 850 nm, and the sampling frequency was 10 Hz. Data was captured using Aurora (NIRx) (v.1.4). The optodes were fitted to a cap according to the international 10–20 system and placed over the prefrontal area. Caps were chosen in accordance with the head sizes of participants.

The walking protocol analyzed in this study contained tasks of straight walking (Walking ST), standing still while performing an auditory Stroop task (Standing ST) and straight walking while performing the auditory Stroop task (Walking DT). For the straight walking condition, participants were asked to walk straight at a self-selected speed to a cone 30 m from the starting cone and back. The auditory Stroop task consisted of the Swedish words for high and low in a congruent or incongruent high and low pitch. Words were presented to the participants through wireless headphones. Participants were instructed to respond verbally, as fast and correctly as possible, to the corresponding pitch irrespective of the words presented. The responses were recorded to analyze task accuracy and response time.

During each block with an auditory Stroop task, a total of 7-word prompts of high or low were presented in a predetermined randomized order. Participants were instructed to pay equal attention to both tasks when dual-tasking. Block length for stimuli was 20 s long, followed by 15 s of rest period to allow for a baseline measure. Each block condition was performed 6 times (e.g., 6 blocks of walking straight) with the time being approximately 12 minutes to complete the protocol.

Gait parameters (e.g., step time and walking speed) were collected using three wireless inertial sensors (Opal, APDM Inc.) positioned over the lumbar and on top of each foot near the ankle. Raw data in each block was analyzed in the python Gaitmap library (Küderle et al. 2024). Step time variability was calculated based on the standard deviation of left and right steps according to (Galna, Lord, and Rochester 2013). Outliers in gait variables due to unexpected behavior in the protocol (e. g., stopping during a walking block) were excluded from analysis. Two strides were excluded from the start and end of each block to get steady state gait (Miller and Verstraete 1996).

Analysis of fNIRS data was performed in the MATLAB NIRS AnalyzIR toolbox (Santosa et al. 2018). The raw optical density fNIRS data was converted into ΔHbO2 and ΔHHb using the modified Beer-Lambert law (Delpy et al. 1988) with the differential path-length factor (DPF) dependent on age (Scholkmann et al. 2013). The first level (subject level) analysis employed a general linear model (GLM), using pre-whitening and an autoregressive model (AR-IRLS) (Barker, Aarabi, and Huppert 2013) to reduce systemic physiology and motion-induced artifacts. Short-separation channels were used as regressors to further filter out physiological noise. A canonical hemodynamic response function (HRF) was assumed. A ROI analysis of the whole PFC is performed.

Analyses used a combined form of oxygenated (HbO) and deoxygenated (HbR) hemoglobin, in terms of correlation-based signal improvement (CBSI) (Cui, Bray, and Reiss 2010). While often used as a movement artifact correction method, this can be considered as a version of the oxygenated hemoglobin scaled for anticorrelation with the deoxygenated hemoglobin, incorporating information from both into one signal.

The second level (group) analysis is detailed in the analysis section.

Process data

Code block - load data files

Show the code
# Load and prepare all data
var_data_path <- "../../Park-MOVE_fnirs_dataset_v2/IMU_data/imu_variability_parameters.csv"
gait_path <- "../../Park-MOVE_fnirs_dataset_v2/IMU_data//imu_gait_parameters.csv"
time_file <- paste("../../Park-MOVE_fnirs_dataset_v2/Task_data/auditory_stroop_answer_time.csv", sep="")
demo_file <- paste("../../Park-MOVE_fnirs_dataset_v2/basic_demographics.csv", sep="")
redcap_file <- paste("../../Park-MOVE_fnirs_dataset_v2/REDcap_data/All_REDcap_data.csv", sep="")
acc_file <- paste("../../Park-MOVE_fnirs_dataset_v2/Task_data/auditory_stroop_accuracy.csv", sep="")
updrs_file <- paste("../../Park-MOVE_fnirs_dataset_v2/REDcap_data/UPDRS_data.csv", sep="")

# Which ROI?
selected_roi <- "PFC"
subject_roi_file <- paste("../data/subject_level_ROI_beta_cbsi_", selected_roi, ".csv", sep="")

# How handle correlations?
corr_method <- "spearman"

# Do we filter any subjects?
filter_subjects <- c("NA")

### Identifiers
csv_path <- paste("../../Park-MOVE_fnirs_dataset_v2/identifiers_YA.csv", sep="")
identifiers_ya <- read.csv(csv_path)
csv_path <- paste("../../Park-MOVE_fnirs_dataset_v2/identifiers_OA.csv", sep="")
identifiers_oa <- read.csv(csv_path)
csv_path <- paste("../../Park-MOVE_fnirs_dataset_v2/identifiers_PD.csv", sep="")
identifiers_pd <- read.csv(csv_path)

Code block - process data files, calculate dual-task effects, create subject means

Show the code
# Assign group function
assign_group <- function(df, identifiers_ya, identifiers_oa, identifiers_pd){
  df$group <- case_when(
    df$subject %in% identifiers_ya$id_nummer ~ "YA",
    df$subject %in% identifiers_oa$id_nummer ~ "OA",
    df$subject %in% identifiers_pd$id_nummer ~ "PD",
    TRUE ~ NA_character_
  )
  df$group <- factor(df$group, levels=c('YA', 'OA', 'PD'))
  return(df)
}

# Helper function to calculate DT cost
calculate_dt_cost <- function(dt, st) {
  -(dt - st) / st * 100
}

# Demographic data
demo_data <- read.csv(demo_file)
demo_data['sex'][demo_data['sex'] == 0] <- 'Male'
demo_data['sex'][demo_data['sex'] == 1] <- 'Female'
demo_data$sex <- factor(demo_data$sex, levels=c('Male', 'Female'))

# REDcap data
redcap_data <- read.csv(redcap_file) 
names(redcap_data)[names(redcap_data) == 'id_nummer'] <- 'subject'
redcap_data['ramlat_12_man'][redcap_data['ramlat_12_man'] == 0] <- 'No'
redcap_data['ramlat_12_man'][redcap_data['ramlat_12_man'] == 1] <- 'Yes'
redcap_data$ramlat_12_man <- factor(redcap_data$ramlat_12_man, levels=c('No', 'Yes'))

# Merge
all_subject_data <- merge(demo_data, redcap_data, by = "subject", all = TRUE)
all_subject_data <- assign_group(all_subject_data, identifiers_ya, identifiers_oa, identifiers_pd)

# Filter out YA
all_subject_data <- all_subject_data[!all_subject_data$group == 'YA', ]
all_subject_data$group <- factor(all_subject_data$group, levels=c('OA', 'PD'))

# Get TMT contrast
all_subject_data <- all_subject_data %>%
  mutate(tmt_4_tmt_2_contrast = tmt_4 - tmt_2)

# Read and process gait data
gait_data <- read.csv(gait_path)
gait_data <- gait_data %>%
  mutate(Cadence.LR = (Cadence.L + Cadence.R) / 2,
         Single.Support.LR = (Single.Support.L + Single.Support.R) / 2,
         Step.Count.LR = (Step.Count.L + Step.Count.R) / 2,
         Step.Time.LR = (Step.Time.L + Step.Time.R) / 2,
         Stride.Length.LR = (Stride.Length.L + Stride.Length.R) / 2,
         Walking.Speed.LR = (Walking.Speed.L + Walking.Speed.R) / 2,
         trial_type = recode(trial_type, 
                             'Navigation_and_Aud_Stroop' = 'DT_navigation',
                             'Navigation' = 'ST_navigation', 
                             'Straight_walking_and_Aud_Stroop' = 'DT_walk', 
                             'Straight_walking' = 'ST_walk', 
                             'Navigated_walking' = 'ST_navigation')) %>%
  filter(trial_type != "Stand_still_and_Aud_Stroop")

# Get averages of all blocks per subject
gait_data <- gait_data %>%
  group_by(subject, session, trial_type) %>%
  summarise(mean_cadence = mean(Cadence.LR, na.rm = TRUE),
            mean_single_support = mean(Single.Support.LR, na.rm = TRUE), 
            mean_step_count = mean(Step.Count.LR, na.rm = TRUE),
            mean_step_time = mean(Step.Time.LR, na.rm = TRUE),
            mean_stride_length = mean(Stride.Length.LR, na.rm = TRUE),
            mean_walking_speed = mean(Walking.Speed.LR, na.rm = TRUE),
            .groups='drop')

# Make wider so we have one column per trial type
gait_data <- gait_data %>%
  pivot_wider(names_from = c(session, trial_type),
              values_from = c(mean_cadence, 
                              mean_single_support,
                              mean_step_count,
                              mean_step_time,
                              mean_stride_length,
                              mean_walking_speed))

# Read and process gait variability data
gait_variability_data <- read.csv(var_data_path) %>%
  mutate(trial_type = recode(trial_type, 'Navigation' = 'ST_navigation', 
                             'Straight_walking' = 'ST_walk', 
                             'Straight_walking_and_Aud_Stroop' = 'DT_walk', 
                             'Navigated_walking' = 'ST_navigation', 
                             'Navigation_and_Aud_Stroop' = 'DT_navigation'),
         Step.Time.Variability = Step.Time.Variability * 1000)

# Get averages of all blocks per subject
gait_variability_data <- gait_variability_data %>%
  group_by(subject, session, trial_type) %>%
  summarise(step_time_variability = mean(Step.Time.Variability, na.rm = TRUE),
            stride_length_variability = mean(Stride.Length.Variability, na.rm = TRUE), 
            step_time_asymmetry_percent = mean(Step.Time.Asymmetry.Percent, na.rm = TRUE),
            stride_length_asymmetry_percent = mean(Stride.Length.Asymmetry.Percent, na.rm = TRUE),
            .groups='drop')

# Make wider so we have one column per trial type
gait_variability_data <- gait_variability_data %>%
  pivot_wider(names_from = c(session, trial_type),
              values_from = c(step_time_variability, 
                              stride_length_variability,
                              step_time_asymmetry_percent,
                              stride_length_asymmetry_percent))
# Merge gait data
all_gait_data <- merge(gait_data, gait_variability_data, by = "subject", all = TRUE) %>%
  assign_group(identifiers_ya, identifiers_oa, identifiers_pd) %>%
  filter(group != 'YA')

# Add DT costs
all_gait_data <- all_gait_data %>%
  mutate(diff_walk_speed_protocol1 = mean_walking_speed_protocol1_DT_walk - mean_walking_speed_protocol1_ST_walk,
         diff_walk_speed_protocol3 = mean_walking_speed_protocol3_DT_navigation - mean_walking_speed_protocol3_ST_navigation,
         dt_cost_walk_speed_protocol1 = calculate_dt_cost(mean_walking_speed_protocol1_DT_walk, mean_walking_speed_protocol1_ST_walk),
         dt_cost_walk_speed_protocol3 = calculate_dt_cost(mean_walking_speed_protocol3_DT_navigation, mean_walking_speed_protocol3_ST_navigation),
         dt_cost_stride_length_protocol1 = calculate_dt_cost(mean_stride_length_protocol1_DT_walk, mean_stride_length_protocol1_ST_walk),
         dt_cost_stride_length_protocol3 = calculate_dt_cost(mean_stride_length_protocol3_DT_navigation, mean_stride_length_protocol3_ST_navigation),
         dt_cost_step_time_variability_protocol1 = calculate_dt_cost(step_time_variability_protocol1_DT_walk, step_time_variability_protocol1_ST_walk),
         dt_cost_step_time_variability_protocol3 = calculate_dt_cost(step_time_variability_protocol3_DT_navigation, step_time_variability_protocol3_ST_navigation))

# Process auditory stroop data
acc_data <- read.csv(acc_file)
acc_data_long <- pivot_longer(acc_data, 
                         cols = starts_with("accuracy_"), 
                         names_to = "accuracy_variable", 
                         values_to = "accuracy_value")

# Only take protocol 1
acc_data <- acc_data[acc_data$protocol == 'protocol_1', ]

# Make wider so we have one column per trial type
acc_data_wide <- acc_data[c('subject', 'block_type', 'protocol', 'accuracy_total')]
acc_data_wide <- acc_data_wide %>%
  pivot_wider(names_from = c(block_type, protocol),
              values_from = c(accuracy_total))

# Stroop times
time_data <- read.csv(time_file)

# Only take protocol 1
time_data <- time_data[time_data$protocol == 'protocol_1', ]
# One outlier in idx 7504
time_data <- time_data[-c(7504),]

# Assign group
time_data <- assign_group(time_data, identifiers_ya, identifiers_oa, identifiers_pd)
acc_data_long <- assign_group(acc_data_long, identifiers_ya, identifiers_oa, identifiers_pd)

# Get means per group
stroop_time_group_means <- time_data %>%
  group_by(group, block_type) %>%
  summarise(stroop_time_mean_value = mean(answer_time, na.rm = TRUE), .groups='drop')

# Get avg answer time per subject
stroop_time_subject_means <- time_data %>%
  group_by(subject, protocol, block_type) %>%
  summarise(stroop_time_mean_value = mean(answer_time, na.rm = TRUE), .groups='drop')

# Make wider so we have one column per trial type
stroop_time_subject_means_wide <- stroop_time_subject_means %>%
  pivot_wider(names_from = c(block_type, protocol),
              values_from = c(stroop_time_mean_value))

# Add DT costs for stroop time
stroop_time_subject_means_wide <- stroop_time_subject_means_wide %>%
  mutate(dt_cost_stroop_time = -calculate_dt_cost(DT_protocol_1, ST_protocol_1))

# Merge with accuracy
as_data_wide <- merge(acc_data_wide, stroop_time_subject_means_wide, by = "subject", all = TRUE, suffixes = c("_acc", "_time"))
as_data_wide <- assign_group(as_data_wide, identifiers_ya, identifiers_oa, identifiers_pd)
as_data_wide <- as_data_wide[!as_data_wide$group == 'YA', ]

# Process subject ROI data
subject_roi_data = read.csv(subject_roi_file)
colnames(subject_roi_data)[colnames(subject_roi_data) == "Contrast"] <- "Condition"
subject_roi_data[subject_roi_data=="Stand_still_and_Aud_Stroop"] <- "C1_ST_stand"
subject_roi_data[subject_roi_data=="Straight_walking"] <- "C2_ST_walk"
subject_roi_data[subject_roi_data=="Straight_walking_and_Aud_Stroop"] <- "C3_DT_walk"

# Factor code some variables
subject_roi_data$Condition <- as.factor(subject_roi_data$Condition)

# Convert to ms for nicer plotting
subject_roi_data$st_step_time_var <- subject_roi_data$st_step_time_var * 1000
subject_roi_data$dt_step_time_var <- subject_roi_data$dt_step_time_var * 1000

# Let's filter the data to only the ones with complete data for aim 1.
# First, we which subjects have fNIRS data, intersect with gait data
fnirs_subject_ids_oa <- read.csv("../data/subjects_aim1_oa.csv", header=FALSE, col.names=paste0("subject") )
fnirs_subject_ids_pd <- read.csv("../data/subjects_aim1_pd.csv", header=FALSE, col.names=paste0("subject") )
aim_1_subjects <- c(fnirs_subject_ids_oa$subject, fnirs_subject_ids_pd$subject)
non_overlapping_subjects <- setdiff(all_subject_data$subject, aim_1_subjects)

# Filter
all_subject_data <- all_subject_data %>% filter(subject %in% aim_1_subjects)
all_gait_data <- all_gait_data %>% filter(subject %in% aim_1_subjects)
as_data_wide <- as_data_wide %>% filter(subject %in% aim_1_subjects)
subject_roi_data <- subject_roi_data %>% filter(SubjectID %in% aim_1_subjects)

# Split into different groups
all_subject_data <- merge(all_subject_data, all_gait_data, by = "subject", all = TRUE)
all_subject_data <- merge(all_subject_data, as_data_wide[c("subject", "dt_cost_stroop_time")], by = "subject", all = TRUE)
all_subject_data <- subset(all_subject_data, select = -c(group.y))
names(all_subject_data)[names(all_subject_data) == 'group.x'] <- 'group'
ya_data <- all_subject_data[all_subject_data$group == 'YA', ]
ya_data <- ya_data[!is.na(ya_data$subject),]
oa_data <- all_subject_data[all_subject_data$group == 'OA', ]
oa_data <- oa_data[!is.na(oa_data$subject),]
pd_data <- all_subject_data[all_subject_data$group == 'PD', ]
pd_data <- pd_data[!is.na(pd_data$subject),]

# Split into groups and get variables to plot for subject ROI data
grouped_dataframes <- split(subject_roi_data, subject_roi_data$group)
data_OA <- grouped_dataframes$OA
data_PD <- grouped_dataframes$PD
data_oa_c2 <- data_OA %>% 
  filter(Condition == "C2_ST_walk") %>% select(step_time_var = st_step_time_var, Beta, group, Condition)
data_oa_c3 <- data_OA %>% 
  filter(Condition == "C3_DT_walk") %>% select(step_time_var = dt_step_time_var, Beta, group, Condition)
data_pd_c2 <- data_PD %>% 
  filter(Condition == "C2_ST_walk") %>% select(step_time_var = st_step_time_var, Beta, group, Condition)
data_pd_c3 <- data_PD %>% 
  filter(Condition == "C3_DT_walk") %>% select(step_time_var = dt_step_time_var, Beta, group, Condition)
subject_roi_data_combined <- bind_rows(data_oa_c2, data_oa_c3, data_pd_c2, data_pd_c3)

Demographics and gait variables

Demographic data and task performance data was compared between groups using R (v4.2.2) (R Core Team, 2022) (R Core Team 2022) and the arsenal (v3.6.3) package. Normality was assessed with the Shapiro-Wilk normality test and visually with q-q plots. Comparison was done with the Kruska-Wallis test.

Show the code
# https://cran.r-project.org/web/packages/arsenal/vignettes/tableby.html

# Cleaner names
labels(all_subject_data)  <- c(
  age = "Age, yrs", 
  sex = "Gender, female",
  crf_utbildning_ar = "Education, yrs",
  weight = "Weight, kg",
  height = "Height, cm",
  frandin_grimby.x = "Frändin-Grimby",
  ramlat_12_man = "Falls in last 12 months, yes",
  mb_total = "Mini-BESTest score",
  g12_sum = "Walk-12 sum",
  tmt_2 = "TMT2, s",
  tmt_4 = "TMT4, s",
  tmt_4_tmt_2_contrast = "TMT4 - TMT2, s",
  cwit_3 = "CWIT3, s",
  ravlt_ret = "RAVLT retention, words")

labels(all_gait_data)  <- c(
  dt_cost_walk_speed_protocol1 = "DT cost walking speed, %",
  dt_cost_walk_speed_protocol3 = "DT cost walking speed, %",
  dt_cost_stride_length_protocol1 = "DT cost stride length, %",
  dt_cost_stride_length_protocol3 = "DT cost stride length, %",
  dt_cost_step_time_variability_protocol1 = "DT cost step time variability, %",
  dt_cost_step_time_variability_protocol3 = "DT cost step time variability, %",
  mean_walking_speed_protocol1_ST_walk = "Walking speed (ST), m/s",
  mean_walking_speed_protocol1_DT_walk = "Walking speed (DT), m/s",
  mean_walking_speed_protocol2_ST_walk = "Walking speed (straight), m/s",
  mean_walking_speed_protocol2_ST_navigation = "Walking speed (navigation), m/s",
  mean_walking_speed_protocol3_ST_navigation = "Walking speed (ST), m/s",
  mean_walking_speed_protocol3_DT_navigation = "Walking speed (DT), m/s",
  mean_stride_length_protocol1_ST_walk = "Stride length (ST), m",
  mean_stride_length_protocol1_DT_walk = "Stride length (DT), m",
  mean_stride_length_protocol2_ST_walk = "Stride length (straight), m",
  mean_stride_length_protocol2_ST_navigation = "Stride length (navigation), m",
  mean_stride_length_protocol3_ST_navigation = "Stride length (ST), m",
  mean_stride_length_protocol3_DT_navigation = "Stride length (DT), m",
  step_time_variability_protocol2_ST_walk = "Step time variability (straight), ms",
  step_time_variability_protocol1_ST_walk = "Step time variability (ST), ms",
  step_time_variability_protocol1_DT_walk = "Step time variability (DT), ms",
  step_time_variability_protocol2_ST_navigation = "Step time variability (navigation), ms",
  step_time_variability_protocol3_ST_navigation = "Step time variability (ST), ms",
  step_time_variability_protocol3_DT_navigation = "Step time variability (DT), ms",
  turns_velocity_protocol_2 = "Turn velocity (peak), deg/s",
  turns_velocity_protocol_3 = "Turn velocity (peak), deg/s")

# Properties for all tables
mycontrols  <- tableby.control(numeric.test="kwt", cat.test="chisq",  cat.simplify = TRUE, cat.stats="countpct",
                               numeric.simplify=TRUE, numeric.stats=c("meansd"),
                               digits=2, digits.p=2, digits.pct=1)

# No good function for creating sub-headings in the table
# But we could create separate tables for display for now (and then all together for correct statistics later)
tab1 <- tableby(group ~ sex + age + crf_utbildning_ar + weight + height + frandin_grimby.x + ramlat_12_man,
                data=all_subject_data, control=mycontrols)


tab2 <- tableby(group ~ mb_total + g12_sum + tmt_2 + tmt_4 + tmt_4_tmt_2_contrast + cwit_3 + ravlt_ret,
                data=all_subject_data, control=mycontrols)



tab3 <- tableby(group ~
                mean_walking_speed_protocol1_ST_walk +
                mean_walking_speed_protocol1_DT_walk + 
                mean_stride_length_protocol1_ST_walk +
                mean_stride_length_protocol1_DT_walk + 
                step_time_variability_protocol1_ST_walk +
                step_time_variability_protocol1_DT_walk +
                dt_cost_walk_speed_protocol1 + 
                dt_cost_stride_length_protocol1 +
                dt_cost_step_time_variability_protocol1,
                data=all_gait_data, control=mycontrols)

tab4 <- tableby(group ~ 
                mean_walking_speed_protocol2_ST_walk +
                mean_walking_speed_protocol2_ST_navigation +
                mean_stride_length_protocol2_ST_walk +
                mean_stride_length_protocol2_ST_navigation + 
                step_time_variability_protocol2_ST_walk +
                step_time_variability_protocol2_ST_navigation,
                data=all_gait_data, control=mycontrols)


tab5 <- tableby(group ~ 
                mean_walking_speed_protocol3_ST_navigation +
                mean_walking_speed_protocol3_DT_navigation + 
                mean_stride_length_protocol3_ST_navigation +
                mean_stride_length_protocol3_DT_navigation + 
                step_time_variability_protocol3_ST_navigation +
                step_time_variability_protocol3_DT_navigation +
                dt_cost_walk_speed_protocol3 + 
                dt_cost_stride_length_protocol3 + 
                dt_cost_step_time_variability_protocol3,
                data=all_gait_data, control=mycontrols)


labels(as_data_wide)  <- c(
  ST_protocol_1_acc = "Accuracy (ST), %",
  DT_protocol_1_acc = "Accuracy (DT), %",
  DT_protocol_1_time = "Answer time (DT), s",
  ST_protocol_1_time = "Answer time (ST), s",
  dt_cost_stroop_time = "DT cost answer time, %")
  
tab6 <- tableby(group ~ ST_protocol_1_acc + ST_protocol_1_time +
                DT_protocol_1_acc + DT_protocol_1_time + dt_cost_stroop_time,
                data=as_data_wide, control=mycontrols)
Show the code
summary(tab1, title = "demographics")
demographics
OA (N=44) PD (N=37) Total (N=81) p value
Gender, female 19 (43.2%) 16 (43.2%) 35 (43.2%) 1.00
Age, yrs 69.16 (6.91) 69.19 (7.48) 69.17 (7.13) 0.90
Education, yrs 15.17 (2.62) 15.61 (2.35) 15.37 (2.49) 0.28
Weight, kg 74.27 (14.36) 76.22 (16.98) 75.16 (15.54) 0.82
Height, cm 173.61 (10.06) 173.22 (9.10) 173.43 (9.58) 0.85
Frändin-Grimby 4.05 (1.03) 3.78 (0.89) 3.93 (0.97) 0.15
Falls in last 12 months, yes 7 (15.9%) 9 (24.3%) 16 (19.8%) 0.34
Show the code
summary(tab2, title = "clinical & neuropsychological")
clinical & neuropsychological
OA (N=44) PD (N=37) Total (N=81) p value
Mini-BESTest score 25.41 (2.35) 23.41 (3.45) 24.41 (3.10) < 0.01
Walk-12 sum 1.57 (2.19) 7.43 (6.47) 4.58 (5.68) < 0.01
TMT2, s 37.31 (16.92) 58.98 (32.21) 47.21 (27.18) < 0.01
TMT4, s 80.24 (32.99) 112.33 (71.69) 94.64 (55.87) 0.02
TMT4 - TMT2, s 44.02 (31.61) 55.42 (59.66) 49.14 (46.36) 0.38
CWIT3, s 61.37 (18.67) 61.27 (11.66) 61.32 (15.76) 0.37
RAVLT retention, words 10.77 (3.37) 9.00 (3.08) 9.96 (3.34) < 0.01
Show the code
summary(tab3, title = "Gait variables, protocol 1")
Gait variables, protocol 1
OA (N=44) PD (N=37) Total (N=81) p value
Walking speed (ST), m/s 1.24 (0.22) 1.19 (0.18) 1.21 (0.20) 0.10
Walking speed (DT), m/s 1.23 (0.23) 1.13 (0.20) 1.18 (0.22) 0.02
Stride length (ST), m 1.36 (0.17) 1.29 (0.17) 1.33 (0.17) 0.09
Stride length (DT), m 1.34 (0.18) 1.24 (0.19) 1.30 (0.19) 0.02
Step time variability (ST), ms 18.02 (6.83) 22.88 (17.59) 20.24 (13.04) 0.83
Step time variability (DT), ms 18.36 (14.91) 24.18 (15.82) 20.98 (15.50) 0.01
DT cost walking speed, % 0.92 (4.18) 4.35 (5.54) 2.46 (5.11) < 0.01
DT cost stride length, % 1.07 (2.58) 3.88 (5.02) 2.33 (4.10) < 0.01
DT cost step time variability, % -4.97 (91.24) -12.51 (43.31) -8.36 (73.33) 0.09
Show the code
summary(tab6, title = "Auditory Stroop, protocol 1")
Auditory Stroop, protocol 1
OA (N=44) PD (N=37) Total (N=81) p value
Accuracy (ST), % 98.39 (3.81) 95.69 (8.43) 97.14 (6.48) 0.11
Answer time (ST), s 1.09 (0.19) 1.07 (0.18) 1.08 (0.19) 0.65
Accuracy (DT), % 98.12 (4.01) 94.53 (10.85) 96.46 (8.09) 0.09
Answer time (DT), s 1.05 (0.20) 1.04 (0.19) 1.04 (0.19) 0.88
DT cost answer time, % -4.19 (5.58) -2.58 (7.38) -3.45 (6.48) 0.38

Descriptive analysis of dual-task effects

To illustrate patterns of interference and prioritization during dual-tasking, cognitive and motor dual-task effects were calculated and plotted against each other. Quadrants in this plot can be used to categorize dual-task effects in terms of mutual interference, cognitive or motor priority trade offs, or mutual facilitation (Plummer et al. 2013). The resulting diagram for dual-task effect on walking speed and response time for the auditory Stroop task can be seen in Figure 1. A negative dual-task effect indicates a dual-task cost (i.e., worse performance during dual-tasking) and a positive dual-task effect indicates a dual-task benefit (i.e., better performance during dual-tasking).

Most participants had a cognitive priority trade-off when dual-tasking (47%), which means they responded as fast or faster to the Stroop task, but walked slower when dual-tasking. Interesting to note is that this is the “posture second” strategy observed in (Bloem et al. 2006). Most participants had dual-task motor costs (i.e., they walked slower) but some older adults both walked faster and responded faster, ending up in the mutual facilitation quadrant.

Combined with the task data in the tables above, where we can observe a significantly higher dual-task cost on walking speed for the PD group but similar performance on the Stroop task, we find that the PD group has a dual-task impairment that might not be able to be explained by just prioritization. Thus, models of gait automaticity compensation might be extra interesting in this group

Quadrant

Show the code
# Merge dt costs and stroop time on subject
dt_costs <- all_gait_data %>%
  select(starts_with("dt_cost"), subject)
merged_data <- merge(as_data_wide, dt_costs, by = 'subject')
merged_data$group <- factor(merged_data$group, levels=c('OA', 'PD'))

# Make costs into DTE instead
merged_data <- merged_data %>%
  mutate(across(starts_with("dt_cost"), ~ . * -1))

# Get quadrant counts
merged_data$quadrant <- with(merged_data, 
                             ifelse(dt_cost_stroop_time > 0 & dt_cost_walk_speed_protocol1 > 0, "Q1",
                             ifelse(dt_cost_stroop_time < 0 & dt_cost_walk_speed_protocol1 > 0, "Q2",
                             ifelse(dt_cost_stroop_time < 0 & dt_cost_walk_speed_protocol1 < 0, "Q3", "Q4"))))
quadrant_counts <- table(merged_data$quadrant)

# Convert counts to percentages
quadrant_percentages <- round(prop.table(quadrant_counts) * 100, 1)

# Count for each group
oa_counts <- table(subset(merged_data, group == "OA")$quadrant)
pd_counts <- table(subset(merged_data, group == "PD")$quadrant)
oa_counts_pct <- round(prop.table(oa_counts) * 100, 1)
pd_counts_pct <- round(prop.table(pd_counts) * 100, 1)

# Create quadrant plot
p <- ggplot(merged_data, aes(x = dt_cost_stroop_time, y = dt_cost_walk_speed_protocol1, tooltip = subject, color = group)) +
  geom_point_interactive(size = 3) +
  labs(x = 'DTE stroop time (%)', y = 'DTE walking speed (%)') +
  theme_minimal() +
  theme(text = element_text(size = 14)) +  # Increase text size
  geom_quadrant_lines(xintercept = 0, yintercept = 0, linetype = "dashed", color = "black") +
  xlim(-20, 20) +
  ylim(-20, 20) +
  annotate("text", x = 13, y = 17, label = paste("Mutual facilitation\nOA ", oa_counts_pct["Q1"], "%, PD ", pd_counts_pct["Q1"], "%", sep=""), color = "blue", size = 5) +
  annotate("text", x = -13, y = 17, label = paste("Motor priority trade-off\nOA ", oa_counts_pct["Q2"], "%, PD ", pd_counts_pct["Q2"], "%", sep=""), color = "blue", size = 5) +
  annotate("text", x = -13, y = -17, label = paste("Mutual interference\nOA ", oa_counts_pct["Q3"], "%, PD ", pd_counts_pct["Q3"], "%", sep=""), color = "blue", size = 5) +
  annotate("text", x = 13, y = -17, label = paste("Cognitive priority trade-off\nOA ", oa_counts_pct["Q4"], "%, PD ", pd_counts_pct["Q4"], "%", sep=""), color = "blue", size = 5)

# Render the plot
p

Planned analysis (proposed model)

Aim 1:

To test the basic assumption that compensatory cortical activity occurs to compensate for a loss of gait automaticity, evaluate whether a measure of gait automaticity (step time variability) relates to cortical (prefrontal) activity, within each group (OA and PD).

Expectation:

A loss of gait automaticity (assumed to happen in both aging and Parkinson’s disease) should incur compensatory cortical activity, in accordance with upregulation and/or reorganization from the CRUNCH model. Therefore, a loss of gait automaticity should co-occur with (i.e., be related to) an increase in cortical activity.

We will use step time variability as a measure of locomotor automaticity in accordance with the arguments put forth by (Gilat et al. 2017).

In the model below, we would thus expect a significant positive parameter estimate for step_time_var. The model also controls for age. If we find a relationship, we could plot the relationship between step time variability and beta (and possibly other variables in an exploratory fashion). See the section Section 8.5 for sensitivity analysis and effect size considerations.

Model:

beta ~ -1 + condition + step_time_var + age + (disease_severity)

Expected relationship, model 1, with some simulated values

Aim 2:

Explore the association between different dual-task components (gait and cognition) and prioritization and measured PFC activity. Not sure about the relationships here, but interesting to investigate.

Models:

beta ~ -1 + condition + DTC_gait + age + (disease_severity)
beta ~ -1 + condition + DTC_cog + age + (disease_severity)
beta ~ -1 + condition + prioritization + age + (disease_severity)

Aim 3:

Evaluate whether and how cognition moderates the automaticity / brain activation relationship. Increased cognitive ability may influence the need for (degree of) cortical activity in the face of reduced automaticity.

Expectation:

In the literature, we find that dual-task impairments are exacerbated by reduced executive function. One interpretation of this is that people with reduced executive function have lower capacity for compensation. Thus, we could expect a measure of executive function (TMT B) to affect the compensation relationship (i.e., between reduced automaticity and increased cortical activity).

Behavioral: if dual-task impairments are exacerbated by reduced executive function, we would expect a significant positive relationship between DT cost (motor or cognitive cost), and TMT-B completion time (longer completion time, worse executive ability). Run a simple Pearson correlation between TMT-B time and DT cost.

Neurophysiological: this one is more unclear. If we think reduced executive ability might diminish the brain’s ability to compensate for reduced automaticity (at least via the PFC, I am interpreting Bunzeck et al section 5.2), then EF could act as a moderator in the compensation relationship between automaticity and PFC activity. In the model below, the interaction step_time_var:tmt_b would then be significant, probably in the negative direction (longer TMT time = worse EF, less compensation = lower beta). See plot.

Model:

beta ~ -1 + condition + step_time_var * tmt_b + age + education_years + (disease_severity)

or written out fully

beta ~ -1 + condition + step_time_var + tmt_b + step_time_var:tmt_b + age + education_years + (disease_severity)

Expected relationship(s), aim 3, with some simulated values

Model details

Use a linear fixed effects regression model. Also include the effect of walking condition in the model. Include age in the model, to control for the pretty large age span within our groups. Models would be solved in the NIRS Brain AnalyzIR toolbox. Consider significance FDR-adjusted p < 0.05.

Variables:

beta - subject-level ROI estimate of hemoglobin activation from subject-level GLM (CBSI estimate, i.e. a linear combination of oxy and deoxy, scaled by anticorrelation) 

condition - factor with 3 levels (standing audio stroop, walking, dual-task walking) 

step_time_var - subject average step time variability calculated from single-task walking blocks 

tmt_b - time to complete the TMT B for the subject 

DTC_gait - dual-task cost on walking speed 

DTC_cog - dual-task cost on audio stroop reaction time prioritization - 

DTC_gait - DTC_cog

education_years – number of years of education for participant

disease_severity – variable for disease severity for the PD group, determined by likelihood ratio test – LEDD, MDS-UPDRS3 motor score, disease length

Then check if mathematical assumptions of the model have been fulfilled with diagnostic plots: normality of residuals / homogeneity of variance (q-q of residuals, residuals vs fitted), influential outliers (residual vs leverage). If a model does not hold, investigate outliers or change model. Document these steps in an ‘open lab notebook’ approach.

Power and sample size – sensitivity analyses

Primary analysis (aim 1)

Here we want to test that the inclusion of the covariate step_time_var significantly increases the proportion of variance of beta explained by the model (with 3 predictors total).

In GPower:

F tests – Linear multiple regression: Fixed model, R^2 increase

α = 0.05, 1-β = 0.80, sample size = 42 (PD) -> effect size = 0.197

α = 0.05, 1-β = 0.80, sample size = 49 (OA) -> effect size = 0.167

Note that this resulting graph is the same as two-tailed “t tests – Linear multiple regression: fixed model, single regression coefficient”.

These are between medium and large effect sizes going by Cohen’s definitions, defined as: small f2 = 0.02, medium f2 = 0.15, large f2 = 0.35.

This means that we could reliably make conclusions about effects of medium to large size, but not smaller than that. A null result could therefore mean that there might still be a small effect that our sample size is too small to detect. A smallest effect size of interest (SESOI) is difficult to speculate on. However, seeing if there is a medium to large effect is still interesting.

Effect size / sample size curve for model 1 (F test)

Effect size / sample size curve for model 1 (t test)

Secondary analysis (aim 2 & 3)

For the effects of DTC_cog, DTC_gait, and prioritization, the sensitivity analysis would be as above for aim 1. For the moderation model, we would test the interaction effect.

beta ~ -1 + condition + step_time_var * tmt_b + age

Estimating the interaction effect is more involved than main effects. Using InteractionPoweR (Baranger et al. 2023), and setting the following variables:

Letting y = beta, x1 = step time variability, x2 = TMT B score 

alpha = 0.05 Correlation between x1 and y = 0.2 

Correlation between x2 and y = 0.2 

Correlation between x1 and x2 = 0.2 

N = 42

These correlations are assumed and could be very different in reality.

Then, to reach a power of 0.80, we would need an interaction effect size of 0.45. However, according to recommendations (Baranger et al. 2023), one should generally expect the interaction effect size to be smaller than the main effect sizes. Thus, it is unlikely that we would detect this interaction effect. It can be considered entirely exploratory. Consider it a last priority.

How has the data already been used?

We have a pilot study (https://doi.org/10.1002/brb3.2948) where we analysed the first 10 participants and looked at some condition effects. We have a validation study (https://doi.org/10.1016/j.nicl.2024.103637) where we looked at condition effects (single-task and dual—task walking) as well as a number of interaction effects (including the interaction condition:step_time_variability, which was significant for the PD group). In the validation article, we limited analysis to dorsolateral PFC, not the entire PFC. We also did not cut out the initiation phases of the gait variables, nor controlled for age in analysed models. Here, we are using different models and looking at different effects (not the interaction effects as in the validation article).

Performed analysis

Subject-level fNIRS analysis can be found in park_move_protocol_1_subject_analysis.m

Group-level fNIRS analysis can be found in park_move_protocol_1_group_analysis.m

Missing data compared to original targets are:

  • 3 OA (1 after re-evaluating criteria, 2 not following protocol), 2 were also missing gait data (data loss during protocol).
  • 3 PD (1 data loss, 1 not following protocol, 1 not completing protocol), 2 were also missing MDS-UPDRS data.
  • Amount of included subjects in each model is indicated in included_n.

The disease_severity variable in the PD models was decided as MDS-UPDRS 3 motor score, after comparing the Akaike Information Criterion (AIC) for models utilizing each variable (and LEDD was skipped due to too many missing values).

The group-level results (output from linear models) is exported to csv and read below.

The model statistics come from the underlying GroupStats model in the NIRS toolbox, where an ANOVA was run on the linear model with the function anova(model, ‘summary’), see MATLAB documentation and relevant fork commit.

Show the code
# Load fNIRS model results
group_stats_file <- "../data/results_table_cbsi_protocol_1.csv"
group_stats <- read.csv(group_stats_file)
group_stats <- group_stats %>%
  select(-AIC, -BIC, -formula, -type, -ROI, -q) %>%
  rename(Condition = Contrast,
         Aim = comment,
         N = included_n) %>%
  mutate(Condition = str_replace_all(Condition, c("Stand_still_and_Aud_Stroop" = "ST_stand", 
                                        "Straight_walking_and_Aud_Stroop" = "DT_walk",
                                        "Straight_walking" = "ST_walk",
                                        "step_time_var" = "Step time variability",
                                        "3_motor" = "UPDRS 3 Motor",
                                        "cost_walk_speed" = "DT cost walking speed",
                                        "cost_stroop_time" = "DT cost Stroop reaction time",
                                        "prio" = "Priority")))

# Format summary stats columns
group_stats$Fstat <- sapply(group_stats$Fstat, format, digits = 3, nsmall = 3)
group_stats$Fstat_p <- ifelse(group_stats$Fstat_p < 0.001, "<.001", format(group_stats$Fstat_p, digits = 3, nsmall = 3))
group_stats$Rsquared <- sapply(group_stats$Rsquared, format, digits = 3, nsmall = 3)
group_stats$Rsquared_adj <- sapply(group_stats$Rsquared_adj, format, digits = 3, nsmall = 3)

# FDR-Adjust p-values per aim so both groups are included
table_aim_1 <- group_stats %>% filter(Aim == "Aim 1")
table_aim_1$p_adjusted <- p.adjust(table_aim_1$p, method="BH")
table_aim_1 <- table_aim_1 %>% select(1:6, p_adjusted, everything())

table_aim_2 <- group_stats %>% filter(str_detect(Aim, "Aim 2"))
table_aim_2$p_adjusted <- p.adjust(table_aim_2$p, method="BH")
table_aim_2 <- table_aim_2 %>% select(1:6, p_adjusted, everything())
table_aim_2_1 <- table_aim_2 %>% filter(Aim == "Aim 2_1") 
table_aim_2_2 <- table_aim_2 %>% filter(Aim == "Aim 2_2") 
table_aim_2_3 <- table_aim_2 %>% filter(Aim == "Aim 2_3") 

table_aim_3 <- group_stats %>% filter(Aim == "Aim 3")
table_aim_3$p_adjusted <- p.adjust(table_aim_3$p, method="BH")
table_aim_3 <- table_aim_3 %>% select(1:6, p_adjusted, everything())

Results, aim 1

Show the code
fstat_OA <- table_aim_1$Fstat[which(table_aim_1$group == "OA")[1]]
fstat_p_OA <- table_aim_1$Fstat_p[which(table_aim_1$group == "OA")[1]]
rsq_OA <- table_aim_1$Rsquared[which(table_aim_1$group == "OA")[1]]
rsq_adj_OA <- table_aim_1$Rsquared_adj[which(table_aim_1$group == "OA")[1]]

fstat_PD <- table_aim_1$Fstat[which(table_aim_1$group == "PD")[1]]
fstat_p_PD <- table_aim_1$Fstat_p[which(table_aim_1$group == "PD")[1]]
rsq_PD <- table_aim_1$Rsquared[which(table_aim_1$group == "PD")[1]]
rsq_adj_PD <- table_aim_1$Rsquared_adj[which(table_aim_1$group == "PD")[1]]

oa_header <- str_glue("**Group: OA**<br>Model: R square {rsq_OA}, R square (adjusted) {rsq_adj_OA}, F {fstat_OA}, p {fstat_p_OA}")
pd_header <- str_glue("**Group: PD**<br>Model: R square {rsq_PD}, R square (adjusted) {rsq_adj_PD}, F {fstat_PD}, p {fstat_p_PD}")

table_aim_1 %>%
  gt() %>%
  cols_hide(columns = c("Aim", "Fstat", "Fstat_p", "Rsquared", "Rsquared_adj")) %>%
  fmt_number(
    columns = everything(),
    decimals = 2
  ) %>%
  fmt_number(
    columns = c(N, DF),
    decimals = 0
  ) %>%
  fmt(
    columns = c(p, p_adjusted),
    fns = function(x) {
      ifelse(x < 0.001, "<.001", sprintf("%.3f", x))
    }
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_body(
      rows = p_adjusted < 0.05
    )
  ) %>%
  tab_row_group(
    label = md(oa_header),
    rows = group == "OA"
  ) %>%
  tab_row_group(
    label = md(pd_header),
    rows = group == "PD"
  )
Condition Beta SE DF T p p_adjusted group N
Group: PD
Model: R square 0.144, R square (adjusted) 0.0952, F 2.941, p <.001
UPDRS 3 Motor −0.29 0.11 105 −2.52 0.013 0.029 PD 37
ST_stand 0.22 0.14 105 1.59 0.116 0.141 PD 37
ST_walk 0.27 0.15 105 1.82 0.071 0.098 PD 37
DT_walk 0.44 0.16 105 2.67 0.009 0.024 PD 37
age 0.24 0.26 105 0.94 0.351 0.351 PD 37
Step time variability 0.38 0.06 105 6.26 <.001 <.001 PD 37
Group: OA
Model: R square 0.114, R square (adjusted) 0.0796, F 3.255, p <.001
ST_stand −0.27 0.14 127 −1.92 0.057 0.089 OA 44
ST_walk 0.94 0.15 127 6.26 <.001 <.001 OA 44
DT_walk 1.40 0.16 127 8.51 <.001 <.001 OA 44
age 0.53 0.23 127 2.33 0.021 0.039 OA 44
Step time variability −0.17 0.14 127 −1.23 0.221 0.243 OA 44
Show the code
# Note: there are 5 NaN rows where subjects lack gait data for certain conditions
ggplot(subject_roi_data_combined, aes(x = step_time_var, y = Beta)) +
  geom_point() +
  geom_smooth(method = "lm", se = TRUE, color = "blue") +
  coord_cartesian(xlim = c(0, 100), ylim = c(-6, 15)) +
  labs(x = "Step Time Variability (ms)", y = "Beta", title = "Correlation between beta and step time variability") +
  stat_correlation(small.r = TRUE, small.p = TRUE, use_label("R", "P"), label.y=0.8) +
  theme_light() +
  theme(text = element_text(size = 20), axis.text.y = element_text(size = 20), plot.title = element_text(hjust = 0.5)) +
  facet_wrap(~ group)

Results, aim 2

Show the code
fstat_OA <- table_aim_2_1$Fstat[which(table_aim_2_1$group == "OA")[1]]
fstat_p_OA <- table_aim_2_1$Fstat_p[which(table_aim_2_1$group == "OA")[1]]
rsq_OA <- table_aim_2_1$Rsquared[which(table_aim_2_1$group == "OA")[1]]
rsq_adj_OA <- table_aim_2_1$Rsquared_adj[which(table_aim_2_1$group == "OA")[1]]

fstat_PD <- table_aim_2_1$Fstat[which(table_aim_2_1$group == "PD")[1]]
fstat_p_PD <- table_aim_2_1$Fstat_p[which(table_aim_2_1$group == "PD")[1]]
rsq_PD <- table_aim_2_1$Rsquared[which(table_aim_2_1$group == "PD")[1]]
rsq_adj_PD <- table_aim_2_1$Rsquared_adj[which(table_aim_2_1$group == "PD")[1]]

oa_header <- str_glue("**Group: OA**<br>Model: R square {rsq_OA}, R square (adjusted) {rsq_adj_OA}, F {fstat_OA}, p {fstat_p_OA}")
pd_header <- str_glue("**Group: PD**<br>Model: R square {rsq_PD}, R square (adjusted) {rsq_adj_PD}, F {fstat_PD}, p {fstat_p_PD}")

table_aim_2_1 %>%
  gt() %>%
  cols_hide(columns = c("Fstat", "Fstat_p", "Rsquared", "Rsquared_adj")) %>%
  fmt_number(
    columns = everything(),
    decimals = 2
  ) %>%
  fmt_number(
    columns = c(N, DF),
    decimals = 0
  ) %>%
  fmt(
    columns = c(p, p_adjusted),
    fns = function(x) {
      ifelse(x < 0.001, "<.001", sprintf("%.3f", x))
    }
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_body(
      rows = p_adjusted < 0.05
    )
  ) %>%
  tab_row_group(
    label = md(oa_header),
    rows = group == "OA"
  ) %>%
  tab_row_group(
    label = md(pd_header),
    rows = group == "PD"
  )
Condition Beta SE DF T p p_adjusted group Aim N
Group: PD
Model: R square 0.135, R square (adjusted) 0.0841, F 2.639, p <.001
UPDRS 3 Motor −0.22 0.11 102 −2.00 0.048 0.103 PD Aim 2_1 36
ST_stand 0.19 0.14 102 1.34 0.184 0.233 PD Aim 2_1 36
ST_walk 0.17 0.15 102 1.10 0.275 0.324 PD Aim 2_1 36
DT_walk 0.36 0.17 102 2.17 0.032 0.075 PD Aim 2_1 36
age −0.20 0.26 102 −0.78 0.436 0.479 PD Aim 2_1 36
DT cost walking speed 0.25 0.09 102 2.90 0.005 0.019 PD Aim 2_1 36
Group: OA
Model: R square 0.118, R square (adjusted) 0.0836, F 3.379, p <.001
ST_stand −0.20 0.14 127 −1.45 0.151 0.207 OA Aim 2_1 44
ST_walk 1.00 0.15 127 6.70 <.001 <.001 OA Aim 2_1 44
DT_walk 1.47 0.16 127 8.91 <.001 <.001 OA Aim 2_1 44
age 0.62 0.24 127 2.61 0.010 0.033 OA Aim 2_1 44
DT cost walking speed −0.16 0.10 127 −1.57 0.120 0.203 OA Aim 2_1 44
Show the code
fstat_OA <- table_aim_2_2$Fstat[which(table_aim_2_2$group == "OA")[1]]
fstat_p_OA <- table_aim_2_2$Fstat_p[which(table_aim_2_2$group == "OA")[1]]
rsq_OA <- table_aim_2_2$Rsquared[which(table_aim_2_2$group == "OA")[1]]
rsq_adj_OA <- table_aim_2_2$Rsquared_adj[which(table_aim_2_2$group == "OA")[1]]

fstat_PD <- table_aim_2_2$Fstat[which(table_aim_2_2$group == "PD")[1]]
fstat_p_PD <- table_aim_2_2$Fstat_p[which(table_aim_2_2$group == "PD")[1]]
rsq_PD <- table_aim_2_2$Rsquared[which(table_aim_2_2$group == "PD")[1]]
rsq_adj_PD <- table_aim_2_2$Rsquared_adj[which(table_aim_2_2$group == "PD")[1]]

oa_header <- str_glue("**Group: OA**<br>Model: R square {rsq_OA}, R square (adjusted) {rsq_adj_OA}, F {fstat_OA}, p {fstat_p_OA}")
pd_header <- str_glue("**Group: PD**<br>Model: R square {rsq_PD}, R square (adjusted) {rsq_adj_PD}, F {fstat_PD}, p {fstat_p_PD}")

table_aim_2_2 %>%
  gt() %>%
  cols_hide(columns = c("Fstat", "Fstat_p", "Rsquared", "Rsquared_adj")) %>%
  fmt_number(
    columns = everything(),
    decimals = 2
  ) %>%
  fmt_number(
    columns = c(N, DF),
    decimals = 0
  ) %>%
  fmt(
    columns = c(p, p_adjusted),
    fns = function(x) {
      ifelse(x < 0.001, "<.001", sprintf("%.3f", x))
    }
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_body(
      rows = p_adjusted < 0.05
    )
  ) %>%
  tab_row_group(
    label = md(oa_header),
    rows = group == "OA"
  ) %>%
  tab_row_group(
    label = md(pd_header),
    rows = group == "PD"
  )
Condition Beta SE DF T p p_adjusted group Aim N
Group: PD
Model: R square 0.132, R square (adjusted) 0.0823, F 2.656, p <.001
UPDRS 3 Motor −0.12 0.12 105 −1.03 0.304 0.346 PD Aim 2_2 37
ST_stand 0.21 0.14 105 1.47 0.145 0.207 PD Aim 2_2 37
ST_walk 0.27 0.15 105 1.78 0.078 0.151 PD Aim 2_2 37
DT_walk 0.44 0.17 105 2.63 0.010 0.033 PD Aim 2_2 37
age 0.09 0.25 105 0.35 0.731 0.778 PD Aim 2_2 37
DT cost Stroop reaction time 0.27 0.09 105 3.10 0.002 0.012 PD Aim 2_2 37
Group: OA
Model: R square 0.115, R square (adjusted) 0.0799, F 3.211, p <.001
ST_stand −0.36 0.14 124 −2.58 0.011 0.033 OA Aim 2_2 43
ST_walk 0.93 0.15 124 6.21 <.001 <.001 OA Aim 2_2 43
DT_walk 1.31 0.17 124 7.95 <.001 <.001 OA Aim 2_2 43
age 0.47 0.24 124 1.98 0.050 0.103 OA Aim 2_2 43
DT cost Stroop reaction time −0.24 0.14 124 −1.69 0.094 0.172 OA Aim 2_2 43
Show the code
fstat_OA <- table_aim_2_3$Fstat[which(table_aim_2_3$group == "OA")[1]]
fstat_p_OA <- table_aim_2_3$Fstat_p[which(table_aim_2_3$group == "OA")[1]]
rsq_OA <- table_aim_2_3$Rsquared[which(table_aim_2_3$group == "OA")[1]]
rsq_adj_OA <- table_aim_2_3$Rsquared_adj[which(table_aim_2_3$group == "OA")[1]]

fstat_PD <- table_aim_2_3$Fstat[which(table_aim_2_3$group == "PD")[1]]
fstat_p_PD <- table_aim_2_3$Fstat_p[which(table_aim_2_3$group == "PD")[1]]
rsq_PD <- table_aim_2_3$Rsquared[which(table_aim_2_3$group == "PD")[1]]
rsq_adj_PD <- table_aim_2_3$Rsquared_adj[which(table_aim_2_3$group == "PD")[1]]

oa_header <- str_glue("**Group: OA**<br>Model: R square {rsq_OA}, R square (adjusted) {rsq_adj_OA}, F {fstat_OA}, p {fstat_p_OA}")
pd_header <- str_glue("**Group: PD**<br>Model: R square {rsq_PD}, R square (adjusted) {rsq_adj_PD}, F {fstat_PD}, p {fstat_p_PD}")

table_aim_2_3 %>%
  gt() %>%
  cols_hide(columns = c("Fstat", "Fstat_p", "Rsquared", "Rsquared_adj")) %>%
  fmt_number(
    columns = everything(),
    decimals = 2
  ) %>%
  fmt_number(
    columns = c(N, DF),
    decimals = 0
  ) %>%
  fmt(
    columns = c(p, p_adjusted),
    fns = function(x) {
      ifelse(x < 0.001, "<.001", sprintf("%.3f", x))
    }
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_body(
      rows = p_adjusted < 0.05
    )
  ) %>%
  tab_row_group(
    label = md(oa_header),
    rows = group == "OA"
  ) %>%
  tab_row_group(
    label = md(pd_header),
    rows = group == "PD"
  )
Condition Beta SE DF T p p_adjusted group Aim N
Group: PD
Model: R square 0.138, R square (adjusted) 0.0871, F 2.708, p <.001
UPDRS 3 Motor −0.17 0.11 102 −1.51 0.135 0.205 PD Aim 2_3 36
ST_stand 0.21 0.14 102 1.50 0.137 0.205 PD Aim 2_3 36
ST_walk 0.20 0.15 102 1.30 0.198 0.242 PD Aim 2_3 36
DT_walk 0.39 0.17 102 2.35 0.021 0.053 PD Aim 2_3 36
age −0.03 0.26 102 −0.13 0.895 0.895 PD Aim 2_3 36
Priority −0.13 0.09 102 −1.55 0.123 0.203 PD Aim 2_3 36
Group: OA
Model: R square 0.115, R square (adjusted) 0.0797, F 3.206, p <.001
ST_stand −0.34 0.14 124 −2.42 0.017 0.046 OA Aim 2_3 43
ST_walk 0.95 0.15 124 6.34 <.001 <.001 OA Aim 2_3 43
DT_walk 1.34 0.17 124 8.06 <.001 <.001 OA Aim 2_3 43
age 0.31 0.22 124 1.40 0.163 0.215 OA Aim 2_3 43
Priority 0.03 0.11 124 0.29 0.771 0.795 OA Aim 2_3 43

Results, aim 3

Behavioral

Note that this is Spearman correlation instead of planned Pearson, because there are outliers in the PD data that we want the statistics to be robust against.

Show the code
# Filter away NaN, also set TMTIV/B max 300s
pd_data <- pd_data[!is.na(pd_data$tmt_4), ]
pd_data$tmt_4[pd_data$tmt_4 > 300] <- 300
oa_data <- oa_data[!is.na(oa_data$tmt_4), ]
oa_data$tmt_4[oa_data$tmt_4 > 300] <- 300

## TMT4 - DT cost
gg_point_1 = ggplot(data = oa_data, aes(x = tmt_4, y = dt_cost_walk_speed_protocol1)) +
  geom_point_interactive(aes(tooltip = subject, data_id = subject), color="red") +
  geom_smooth(method = "lm", se = TRUE, color = "blue") +
  stat_correlation(method=corr_method, small.r=TRUE, small.p=TRUE, use_label("R", "P"), label.y=1) +
  coord_cartesian(xlim =c(20,300), ylim = c(-10, 15)) +
  labs(x = "TMT IV", y = "DT cost walking speed (%)", title = "OA") +
  geom_hline(yintercept=0, linetype="dashed", color = "black") +
  theme_minimal()

gg_point_2 = ggplot(data = pd_data, aes(x = tmt_4, y = dt_cost_walk_speed_protocol1)) +
  geom_point_interactive(aes(tooltip = subject, data_id = subject), color = "green") +
  geom_smooth(method = "lm", se = TRUE, color = "blue") +
  stat_correlation(method=corr_method, small.r=TRUE, small.p=TRUE, use_label("R", "P"), label.y=1) +
  coord_cartesian(xlim =c(20,300), ylim = c(-10, 15)) +
  labs(x = "TMT IV", y = "DT cost walking speed (%)", title = "PD") +
  geom_hline(yintercept=0, linetype="dashed", color = "black") +
  theme_minimal()

## TMT4 - stoop DT cost
# stat_correlation outputs pipe symbol for gg_point_3 so do it manually
cor_result <- cor.test(oa_data$tmt_4, oa_data$dt_cost_stroop_time, method = corr_method)
r_value <- round(cor_result$estimate, 2)
p_value <- cor_result$p.value
p_label <- ifelse(p_value < 0.001, "< 0.001", paste0("= ", round(p_value, 3)))
custom_label <- paste0("ρ = ", r_value, ", P ", p_label)
gg_point_3 = ggplot(data = oa_data, aes(x = tmt_4, y = dt_cost_stroop_time)) +
  geom_point_interactive(aes(tooltip = subject, data_id = subject), color="red") +
  geom_smooth(method = "lm", se = TRUE, color = "blue") +
  annotate("text", x = 70, y = 10, label = custom_label, parse=FALSE) +
  coord_cartesian(xlim =c(20,300), ylim = c(-25, 15)) +
  labs(x = "TMT IV", y = "DT cost stroop (%)", title = "OA") +
  geom_hline(yintercept=0, linetype="dashed", color = "black") +
  theme_minimal()

gg_point_4 = ggplot(data = pd_data, aes(x = tmt_4, y = dt_cost_stroop_time)) +
  geom_point_interactive(aes(tooltip = subject, data_id = subject), color = "green") +
  geom_smooth(method = "lm", se = TRUE, color = "blue") +
  stat_correlation(method=corr_method, small.r=TRUE, small.p=TRUE, use_label("R", "P"), label.y=1) +
  coord_cartesian(xlim =c(20,300), ylim = c(-25, 15)) +
  labs(x = "TMT IV", y = "DT cost stroop (%)", title = "PD") +
  geom_hline(yintercept=0, linetype="dashed", color = "black") +
  theme_minimal()

# Combine plots with labels
combined_plot <- plot_grid(
  gg_point_1 + labs(tag = "a"),
  gg_point_2 + labs(tag = "b"),
  gg_point_3 + labs(tag = "c"),
  gg_point_4 + labs(tag = "d"),
  ncol = 2, nrow = 2
)

# Print the combined plot
print(combined_plot)

fNIRS

Show the code
fstat_OA <- table_aim_3$Fstat[which(table_aim_3$group == "OA")[1]]
fstat_p_OA <- table_aim_3$Fstat_p[which(table_aim_3$group == "OA")[1]]
rsq_OA <- table_aim_3$Rsquared[which(table_aim_3$group == "OA")[1]]
rsq_adj_OA <- table_aim_3$Rsquared_adj[which(table_aim_3$group == "OA")[1]]

fstat_PD <- table_aim_3$Fstat[which(table_aim_3$group == "PD")[1]]
fstat_p_PD <- table_aim_3$Fstat_p[which(table_aim_3$group == "PD")[1]]
rsq_PD <- table_aim_3$Rsquared[which(table_aim_3$group == "PD")[1]]
rsq_adj_PD <- table_aim_3$Rsquared_adj[which(table_aim_3$group == "PD")[1]]

oa_header <- str_glue("**Group: OA**<br>Model: R square {rsq_OA}, R square (adjusted) {rsq_adj_OA}, F {fstat_OA}, p {fstat_p_OA}")
pd_header <- str_glue("**Group: PD**<br>Model: R square {rsq_PD}, R square (adjusted) {rsq_adj_PD}, F {fstat_PD}, p {fstat_p_PD}")

table_aim_3 %>%
  gt() %>%
  cols_hide(columns = c("Aim", "Fstat", "Fstat_p", "Rsquared", "Rsquared_adj")) %>%
  fmt_number(
    columns = everything(),
    decimals = 2
  ) %>%
  fmt_number(
    columns = c(N, DF),
    decimals = 0
  ) %>%
  fmt(
    columns = c(p, p_adjusted),
    fns = function(x) {
      ifelse(x < 0.001, "<.001", sprintf("%.3f", x))
    }
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_body(
      rows = p_adjusted < 0.05
    )
  ) %>%
  tab_row_group(
    label = md(oa_header),
    rows = group == "OA"
  ) %>%
  tab_row_group(
    label = md(pd_header),
    rows = group == "PD"
  )
Condition Beta SE DF T p p_adjusted group N
Group: PD
Model: R square 0.198, R square (adjusted) 0.122, F 2.619, p <.001
UPDRS 3 Motor −0.25 0.13 96 −1.97 0.052 0.093 PD 35
ST_stand 0.29 0.15 96 1.99 0.049 0.093 PD 35
ST_walk 0.30 0.16 96 1.90 0.060 0.093 PD 35
DT_walk 0.48 0.17 96 2.82 0.006 0.020 PD 35
age 0.43 0.30 96 1.42 0.158 0.179 PD 35
edu −0.35 0.14 96 −2.49 0.015 0.041 PD 35
Step time variability 0.38 0.08 96 4.99 <.001 <.001 PD 35
Step time variability:tmt4 0.04 0.06 96 0.60 0.551 0.551 PD 35
tmt4 −0.42 0.07 96 −5.57 <.001 <.001 PD 35
Group: OA
Model: R square 0.162, R square (adjusted) 0.106, F 2.896, p <.001
ST_stand −0.28 0.14 121 −1.95 0.053 0.093 OA 43
ST_walk 1.06 0.15 121 6.98 <.001 <.001 OA 43
DT_walk 1.49 0.17 121 8.94 <.001 <.001 OA 43
age 0.43 0.33 121 1.31 0.193 0.205 OA 43
edu 0.19 0.10 121 1.93 0.056 0.093 OA 43
Step time variability −0.26 0.15 121 −1.67 0.097 0.126 OA 43
Step time variability:tmt4 0.41 0.27 121 1.52 0.130 0.158 OA 43
tmt4 0.32 0.18 121 1.74 0.084 0.119 OA 43

Model validation

Models were exported from MATLAB and evaluated in R to test assumptions for linearity, homoscedasticity and normality of residuals. Influential outliers were also analyzed.

The residuals are from the NIRS toolbox GroupStats models (the linear models on group level). The number of residuals represent (channels) x (subjects) x (conditions/covariates).

Model validation plots can be found here: Model validation

Diagnostics

As far as I can tell, mostly for the OA group, the residuals have somewhat “fat-tailed” distributions. This would make results in the OA group slightly anticonservative. The PD model distributions look more normally distributed. Residual-Fitted plots look symmetrically distributed.

Outlier analysis

Outliers were evaluated in the Residual-Leverage and influence plots in model validation, and by using the RemoveOutlierSubjects.m function in the NIRS toolbox.

There were a few outliers, more in the OA group than in the PD group. The RemoveOutlierSubjects.m function showed 2 outlier subjects in OA, and 1 in PD. These corresponded to large circles in the influence plots.

Re-running the analysis without the outlier subjects resulted in slightly different beta values, but significant effects in each aim remained mostly the same, except a few condition effects (ST_stand for OA in aim 1, DT_walk for PD in aim 2, ST_stand for OA in aim 3). The difference were in those condition effects which were hovering around FDR-adjusted p .05.

This points to robust results for the effects of interest (step time variability, age, DT costs), and mostly robust results for condition effects.

Exploratory - PFC activity visualization and comparison

Visualization

Below is PFC activity per channel visualized for OA, PD, and the contrast PD - OA.

Color scale indicates T value. Red indicates an increase in BOLD activity for a channel, blue a decrease. The increase/decrease is always from baseline (standing still without a task) to active condition.

In the contrasts, blue thus means OA > PD and red means PD > OA.

Significant increases (FDR p < .05) are indicated as solid lines.

Condition 1 - standing auditory stroop

A few significant channels in each group: decrease for OA and increase for PD. No channel in the contrast had a significant diffrence on its own.

OA

PD

Contrast

Condition 2 - straight walking

OA had more of an increase than PD.

OA

PD

Contrast

Condition 3 - dual-task walking

OA had more of an increase than PD.

OA

PD

Contrast

Contrast ROI

The following model on increase in PFC activity per group was run:

beta ~ -1 + condition:group

Then, the contrast PD - OA was taken for each condition. Results are in the table below.

Results are as seen above: OA had more PFC activity in the walking conditions, while PD had more in the standing auditory stroop condition.

Show the code
# Read contrasts
contrast_file <- "../data/results_table_cbsi_protocol_1_contrast.csv"
contrast_data <- read.csv(contrast_file)
contrast_data <- contrast_data %>%
  select(-type, -ROI, -q) %>%
  rename(Condition = Contrast)

# Nicer names
contrast_data$Condition <- gsub("-Stand_still_and_Aud_Stroop:group_OA\\+Stand_still_and_Aud_Stroop:group_PD", "ST_stand",
                                contrast_data$Condition)
contrast_data$Condition <- gsub("-Straight_walking:group_OA\\+Straight_walking:group_PD", "ST_walk", 
                                contrast_data$Condition)
contrast_data$Condition <- gsub("-Straight_walking_and_Aud_Stroop:group_OA\\+Straight_walking_and_Aud_Stroop:group_PD", "DT_walk",
                                contrast_data$Condition)
# Display
contrast_data %>%
  gt() %>%
  fmt_number(
    columns = everything(),
    decimals = 3
  )
Condition Beta SE DF T p
ST_stand 0.455 0.197 249.000 2.316 0.021
ST_walk −0.592 0.209 249.000 −2.828 0.005
DT_walk −0.937 0.230 249.000 −4.072 0.000

LEDD

The model in the primary aim was also controlled for levodopa equivalent daily dose (LEDD).

There was a significant effect of UPDRS III, step time variability, and LEDD.

The correlation between UPDRS III, step time variability and LEDD was also controlled; there was no correlation.

Note that the LEDD variable has several missing values and only 31 subjects remained in the model, so results should be carefully interpreted.

Show the code
ledd_result_file <- paste("../data/results_table_cbsi_protocol_1_ledd.csv", sep="")
ledd_result <- read.csv(ledd_result_file)
ledd_result <- ledd_result %>%
  select(-AIC, -BIC, -formula, -type, -ROI) %>%
  rename(Condition = Contrast,
         Aim = comment) %>%
  mutate(Condition = str_replace_all(Condition, c("Stand_still_and_Aud_Stroop" = "ST_stand",
                                        "Straight_walking_and_Aud_Stroop" = "DT_walk",
                                        "Straight_walking" = "ST_walk",
                                        "step_time_var" = "Step time variability",
                                        "3_motor" = "UPDRS 3 Motor",
                                        "ledd" = "LEDD")))
ledd_result <- ledd_result %>% select(1:6, q, everything())
ledd_result %>%
  gt() %>%
  fmt_number(
    columns = c(included_n, DF),
    decimals = 0
  ) %>%
  fmt_number(
    columns = -c(included_n, DF),
    decimals = 3
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_body(
      rows = q < 0.05
    )
  )
Condition Beta SE DF T p q group Aim included_n
UPDRS 3 Motor −0.338 0.128 86 −2.636 0.010 0.035 PD LEDD 31
ST_stand 0.222 0.153 86 1.448 0.151 0.212 PD LEDD 31
ST_walk 0.145 0.164 86 0.884 0.379 0.442 PD LEDD 31
DT_walk 0.130 0.179 86 0.730 0.468 0.468 PD LEDD 31
age 0.591 0.289 86 2.043 0.044 0.077 PD LEDD 31
LEDD 0.264 0.112 86 2.355 0.021 0.049 PD LEDD 31
Step time variability 0.198 0.075 86 2.648 0.010 0.035 PD LEDD 31
Show the code
updrs_data <- read.csv(updrs_file)
updrs_data$updrs_3_total <- rowSums(updrs_data[, 35:66], na.rm = TRUE)
updrs_data <- updrs_data[c("id_nummer", "updrs_3_total", "mdsupdrs3_hy")]
names(updrs_data)[names(updrs_data) == 'id_nummer'] <- 'subject'

ledd_updrs = all_subject_data %>% filter(group == "PD") 
ledd_updrs = ledd_updrs[c("subject", "group", "age", "led_total", "step_time_variability_protocol1_ST_walk")]
ledd_updrs <- merge(ledd_updrs, updrs_data, by = "subject", all = FALSE)

plot1 <- ggplot(data = ledd_updrs, aes(x = led_total, y = updrs_3_total, color=mdsupdrs3_hy)) +
  geom_point_interactive(aes(tooltip = subject, data_id = subject)) +
  geom_smooth(method = "lm", se = TRUE, color = "blue") +
  stat_cor(label.y = 40, method = corr_method, size = 4) +
  labs(x = "LEDD (mg)", y = "UPDRS III", title = "LEDD to UPDRS III") +
  geom_hline(yintercept=32, linetype="dashed", color = "black") +
  geom_hline(yintercept=59, linetype="dashed", color = "black") +
  theme_minimal()

plot2 <- ggplot(data = ledd_updrs, aes(x = led_total, y = step_time_variability_protocol1_ST_walk, color=mdsupdrs3_hy)) +
  geom_point_interactive(aes(tooltip = subject, data_id = subject)) +
  geom_smooth(method = "lm", se = TRUE, color = "blue") +
  stat_cor(label.y = 70, method = corr_method, size = 4) +
  labs(x = "LEDD (mg)", y = "Step time variability (ms)", title = "LEDD to step time variability") +
  theme_minimal()

plot_grid(plotlist=list(plot1, plot2), nrow=2)

UPDRS and LEDD, table

Show the code
updrs_data <- assign_group(updrs_data, identifiers_ya, identifiers_oa, identifiers_pd)
updrs_data$mdsupdrs3_hy <- factor(updrs_data$mdsupdrs3_hy, levels = 1:4)
updrs_data <- updrs_data %>% filter(subject %in% aim_1_subjects)

labels(updrs_data)  <- c(
  updrs_3_total = "MDS-UPDRS III",
  mdsupdrs3_hy = "HY")

tab_disease_1 <- tableby(group ~ updrs_3_total + mdsupdrs3_hy,data=updrs_data, control=mycontrols)
summary(tab_disease_1, title = "MDS-UPDRS data")
MDS-UPDRS data
OA (N=44) PD (N=37) Total (N=81) p value
MDS-UPDRS III 0.00 (0.00) 25.38 (9.31) 11.59 (14.17) < 0.01
HY
   1 0 2 (5.4%) 2 (5.4%)
   2 0 20 (54.1%) 20 (54.1%)
   3 0 14 (37.8%) 14 (37.8%)
   4 0 1 (2.7%) 1 (2.7%)
Show the code
ledd_updrs <- ledd_updrs %>% filter(subject %in% aim_1_subjects)
ledd_updrs <- ledd_updrs %>% filter(!is.na(led_total))
tab_disease_2 <- tableby(group ~ led_total, data=ledd_updrs, control=mycontrols)
Warning in tableby(group ~ led_total, data = ledd_updrs, control = mycontrols):
The by-variable has fewer than two levels; statistical tests are ignored
Show the code
summary(tab_disease_2, title = "LEDD data")
LEDD data
PD (N=31) Total (N=31)
led_total 452.87 (228.46) 452.87 (228.46)

Signal quality

fNIRS signal quality in each condition was calculated using MNE-NIRS and QT-NIRS: https://github.com/alkvi/fnirs_dataset_preparation

Show the code
quality_path_sci <- "../data/signal_quality_sci.csv"
quality_path_psp <- "../data/signal_quality_power.csv"
sci_data <- read.csv(quality_path_sci)
psp_data <- read.csv(quality_path_psp)

# Select protocol 1
sci_data <- subset(sci_data, protocol == "protocol1")
psp_data <- subset(psp_data, protocol == "protocol1")

# Get averages of all blocks per subject
psp_data_avg <- psp_data %>%
  group_by(protocol, condition) %>%
  summarise(mean_power = mean(power, na.rm = TRUE),
            mean_power_std = mean(power_std, na.rm = TRUE),
            .groups='drop')

# Add SCI
quality_data <- merge(psp_data_avg, sci_data, by = "condition", all = TRUE)
quality_data <- quality_data[c("condition", "mean_power", "mean_power_std", "sci", "sci_std")]
names(quality_data)[names(quality_data) == "mean_power"] <- "Peak Spectral Power"
names(quality_data)[names(quality_data) == "mean_power_std"] <- "Peak Spectral Power (STD)"
names(quality_data)[names(quality_data) == "sci"] <- "Scalp Coupling Index"
names(quality_data)[names(quality_data) == "sci_std"] <- "Scalp Coupling Index (STD)"

# Display
quality_data %>%
  gt() %>%
  fmt_number(
    columns = everything(),
    decimals = 3
  )
condition Peak Spectral Power Peak Spectral Power (STD) Scalp Coupling Index Scalp Coupling Index (STD)
Stand_still_and_Aud_Stroop 0.246 0.124 0.966 0.085
Straight_walking 0.200 0.109 0.968 0.063
Straight_walking_and_Aud_Stroop 0.192 0.106 0.967 0.062

Manuscript

Finished manuscript will be linked here when ready.

References

Baranger, David A. A., Megan C. Finsaas, Brandon L. Goldstein, Colin E. Vize, Donald R. Lynam, and Thomas M. Olino. 2023. “Tutorial: Power Analyses for Interaction Effects in Cross-Sectional Regressions.” Advances in Methods and Practices in Psychological Science 6 (3): 25152459231187531. https://doi.org/10.1177/25152459231187531.
Barker, Jeffrey W., Ardalan Aarabi, and Theodore J. Huppert. 2013. “Autoregressive Model Based Algorithm for Correcting Motion and Serially Correlated Errors in fNIRS.” Biomedical Optics Express 4 (8): 1366–79. https://doi.org/10.1364/BOE.4.001366.
Beurskens, Rainer, and Otmar Bock. 2012. “Age-Related Deficits of Dual-Task Walking: A Review.” Neural Plasticity 2012 (1): 131608. https://doi.org/10.1155/2012/131608.
Bloem, Bastiaan R., Yvette A. M. Grimbergen, J. Gert van Dijk, and Marten Munneke. 2006. “The ‘Posture Second’ Strategy: A Review of Wrong Priorities in Parkinson’s Disease.” Journal of the Neurological Sciences, Dementia in Parkinson’s Disease: International Symposium, 248 (1): 196–204. https://doi.org/10.1016/j.jns.2006.05.010.
Bunzeck, Nico, Tineke K. Steiger, Ulrike M. Krämer, Kerstin Luedtke, Lisa Marshall, Jonas Obleser, and Sarah Tune. 2024. “Trajectories and Contributing Factors of Neural Compensation in Healthy and Pathological Aging.” Neuroscience & Biobehavioral Reviews 156 (January): 105489. https://doi.org/10.1016/j.neubiorev.2023.105489.
Cabeza, Roberto, Marilyn Albert, Sylvie Belleville, Fergus I. M. Craik, Audrey Duarte, Cheryl L. Grady, Ulman Lindenberger, et al. 2018. “Maintenance, Reserve and Compensation: The Cognitive Neuroscience of Healthy Ageing.” Nature Reviews Neuroscience 19 (11): 701–10. https://doi.org/10.1038/s41583-018-0068-2.
Clark, David J. 2015. “Automaticity of Walking: Functional Significance, Mechanisms, Measurement and Rehabilitation Strategies.” Frontiers in Human Neuroscience 9 (May): 246. https://doi.org/10.3389/fnhum.2015.00246.
Cui, Xu, Signe Bray, and Allan L. Reiss. 2010. “Functional Near Infrared Spectroscopy (NIRS) Signal Improvement Based on Negative Correlation Between Oxygenated and Deoxygenated Hemoglobin Dynamics.” NeuroImage 49 (4): 3039–46. https://doi.org/10.1016/j.neuroimage.2009.11.050.
Delis, Dean C. 2001. Delis - Kaplan Executive Function System: Examiner’s Manual. Psychological Corporation.
Delpy, D. T., M. Cope, P. van der Zee, S. Arridge, S. Wray, and J. Wyatt. 1988. “Estimation of Optical Pathlength Through Tissue from Direct Time of Flight Measurement.” Physics in Medicine and Biology 33 (12): 1433–42. https://doi.org/10.1088/0031-9155/33/12/008.
Di Carlo, Silvia, Elisabetta Bravini, Stefano Vercelli, Giuseppe Massazza, and Giorgio Ferriero. 2016. “The Mini-BESTest: A Review of Psychometric Properties.” International Journal of Rehabilitation Research. Internationale Zeitschrift Fur Rehabilitationsforschung. Revue Internationale De Recherches De Readaptation 39 (2): 97–105. https://doi.org/10.1097/MRR.0000000000000153.
Festini, Sara B., Laura Zahodne, and Patricia A. Reuter-Lorenz. 2018. “Theoretical Perspectives on Age Differences in Brain Activation: HAROLD, PASA, CRUNCHHow Do They STAC Up?” In Oxford Research Encyclopedia of Psychology. https://doi.org/10.1093/acrefore/9780190236557.013.400.
Franzén, Erika. 2023. “Park-MOVE - fNIRS Study of Complex Walking in Parkinson’s diseasePark-MOVE - fNIRS Study of Complex Walking in Parkinson’s Disease.” Karolinska Institutet. https://doi.org/10.48723/VSCR-EQ07.
Galna, Brook, Sue Lord, and Lynn Rochester. 2013. “Is Gait Variability Reliable in Older Adults and Parkinson’s Disease? Towards an Optimal Testing Protocol.” Gait & Posture 37 (4): 580–85. https://doi.org/10.1016/j.gaitpost.2012.09.025.
Gilat, Moran, Peter T. Bell, Kaylena A. Ehgoetz Martens, Matthew J. Georgiades, Julie M. Hall, Courtney C. Walton, Simon J. G. Lewis, and James M. Shine. 2017. “Dopamine Depletion Impairs Gait Automaticity by Altering Cortico-Striatal and Cerebellar Processing in Parkinson’s Disease.” NeuroImage 152 (May): 207–20. https://doi.org/10.1016/j.neuroimage.2017.02.073.
Goetz, Christopher G., Barbara C. Tilley, Stephanie R. Shaftman, Glenn T. Stebbins, Stanley Fahn, Pablo Martinez-Martin, Werner Poewe, et al. 2008. “Movement Disorder Society-sponsored Revision of the Unified Parkinson’s Disease Rating Scale (MDS-UPDRS): Scale Presentation and Clinimetric Testing Results.” Movement Disorders 23 (15): 2129–70. https://doi.org/10.1002/mds.22340.
Goh, Hui-Ting, Miranda Pearce, and Asha Vas. 2021. “Task Matters: An Investigation on the Effect of Different Secondary Tasks on Dual-Task Gait in Older Adults.” BMC Geriatrics 21 (1): 510. https://doi.org/10.1186/s12877-021-02464-8.
Hamacher, Dennis, Fabian Herold, Patrick Wiegel, Daniel Hamacher, and Lutz Schega. 2015. “Brain Activity During Walking: A Systematic Review.” Neuroscience and Biobehavioral Reviews 57 (October): 310–27. https://doi.org/10.1016/j.neubiorev.2015.08.002.
Herold, Fabian, Patrick Wiegel, Felix Scholkmann, Angelina Thiers, Dennis Hamacher, and Lutz Schega. 2017. “Functional Near-Infrared Spectroscopy in Movement Science: A Systematic Review on Cortical Activity in Postural and Walking Tasks.” Neurophotonics 4 (4): 041403. https://doi.org/10.1117/1.NPh.4.4.041403.
Kahya, Melike, Sanghee Moon, Maud Ranchet, Rachel R. Vukas, Kelly E. Lyons, Rajesh Pahwa, Abiodun Akinwuntan, and Hannes Devos. 2019. “Brain Activity During Dual Task Gait and Balance in Aging and Age-Related Neurodegenerative Conditions: A Systematic Review.” Experimental Gerontology 128 (December): 110756. https://doi.org/10.1016/j.exger.2019.110756.
Kelly, Valerie E., Alexis J. Eusterbrock, and Anne Shumway-Cook. 2012. “A Review of Dual-Task Walking Deficits in People with Parkinson’s Disease: Motor and Cognitive Contributions, Mechanisms, and Clinical Implications.” Parkinson’s Disease 2012: 918719. https://doi.org/10.1155/2012/918719.
Kim, Hyejun, and Sarah Fraser. 2022. “Neural Correlates of Dual-Task Walking in People with Central Neurological Disorders: A Systematic Review.” Journal of Neurology 269 (5): 2378–2402. https://doi.org/10.1007/s00415-021-10944-5.
Küderle, Arne, Martin Ullrich, Nils Roth, Malte Ollenschläger, Alzhraa A. Ibrahim, Hamid Moradi, Robert Richer, et al. 2024. “Gaitmap—an Open Ecosystem for IMU-based Human Gait Analysis and Algorithm Benchmarking.” IEEE Open Journal of Engineering in Medicine and Biology 5: 163–72. https://doi.org/10.1109/OJEMB.2024.3356791.
la Fougère, Christian, Andreas Zwergal, Axel Rominger, Stefan Förster, Gunther Fesl, Marianne Dieterich, Thomas Brandt, Michael Strupp, Peter Bartenstein, and Klaus Jahn. 2010. “Real Versus Imagined Locomotion: A [18F]-FDG PET-fMRI Comparison.” NeuroImage 50 (4): 1589–98. https://doi.org/10.1016/j.neuroimage.2009.12.060.
Lakens, Daniel. 2024. “The 20% Statistician: Why I Don’t Expect to Be Convinced by Evidence That Scientific Reform Is Improving Science (and Why That Is Not a Problem).” The 20% Statistician.
Maclean, Linda M., Laura J. E. Brown, H. Khadra, and Arlene J. Astell. 2017. “Observing Prioritization Effects on Cognition and Gait: The Effect of Increased Cognitive Load on Cognitively Healthy Older Adults’ Dual-Task Performance.” Gait & Posture 53 (March): 139–44. https://doi.org/10.1016/j.gaitpost.2017.01.018.
McIsaac, Tara L., Nora E. Fritz, Lori Quinn, and Lisa M. Muratori. 2018. “Cognitive-Motor Interference in Neurodegenerative Disease: A Narrative Review and Implications for Clinical Management.” Frontiers in Psychology 9 (October): 2061. https://doi.org/10.3389/fpsyg.2018.02061.
Miller, Christopher A., and Mary C. Verstraete. 1996. “Determination of the Step Duration of Gait Initiation Using a Mechanical Energy Analysis.” Journal of Biomechanics 29 (9): 1195–99. https://doi.org/10.1016/0021-9290(96)00033-4.
Nieuwhof, Freek, Bastiaan R Bloem, Miriam F Reelick, Esther Aarts, Inbal Maidan, Anat Mirelman, Jeffrey M Hausdorff, Ivan Toni, and Rick C Helmich. 2017. “Impaired Dual Tasking in Parkinson’s Disease Is Associated with Reduced Focusing of Cortico-Striatal Activity.” Brain 140 (5): 1384–98. https://doi.org/10.1093/brain/awx042.
Plummer, Prudence, Gail Eskes, Sarah Wallace, Clare Giuffrida, Michael Fraas, Grace Campbell, Kerry-Lee Clifton, and Elizabeth R. Skidmore. 2013. “Cognitive-Motor Interference During Functional Mobility After Stroke: State of the Science and Implications for Future Research.” Archives of Physical Medicine and Rehabilitation 94 (12): 2565–2574.e6. https://doi.org/10.1016/j.apmr.2013.08.002.
R Core Team. 2022. R: A Language and Environment for Statistical Computing. Manual. Vienna, Austria: R Foundation for Statistical Computing.
Reuter-Lorenz, Patricia A., and Katherine A. Cappell. 2008. “Neurocognitive Aging and the Compensation Hypothesis.” Current Directions in Psychological Science 17 (3): 177–82. https://doi.org/10.1111/j.1467-8721.2008.00570.x.
Santosa, Hendrik, Xuetong Zhai, Frank Fishburn, and Theodore Huppert. 2018. “The NIRS Brain AnalyzIR Toolbox.” Algorithms 11 (5): 73. https://doi.org/10.3390/a11050073.
Schmidt, Michael. 1996. Rey Auditory Verbal Learning Test: RAVLT : A Handbook. Western Psychological Services.
Scholkmann, F., U. Gerber, M. Wolf, and U. Wolf. 2013. “End-Tidal CO2: An Important Parameter for a Correct Interpretation in Functional Brain Studies Using Speech Tasks.” NeuroImage 66 (February): 71–79. https://doi.org/10.1016/j.neuroimage.2012.10.025.
Smith, Erin, Tara Cusack, and Catherine Blake. 2016. “The Effect of a Dual Task on Gait Speed in Community Dwelling Older Adults: A Systematic Review and Meta-Analysis.” Gait & Posture 44 (February): 250–58. https://doi.org/10.1016/j.gaitpost.2015.12.017.
Smith, Erin, Tara Cusack, Caitriona Cunningham, and Catherine Blake. 2017. “The Influence of a Cognitive Dual Task on the Gait Parameters of Healthy Older Adults: A Systematic Review and Meta-Analysis.” Journal of Aging and Physical Activity 25 (4): 671–86. https://doi.org/10.1123/japa.2016-0265.
Yogev-Seligmann, Galit, Jeffrey M. Hausdorff, and Nir Giladi. 2008. “The Role of Executive Function and Attention in Gait.” Movement Disorders: Official Journal of the Movement Disorder Society 23 (3): 329–342; quiz 472. https://doi.org/10.1002/mds.21720.