|
1 | | -# Hierarchical-Interoception |
| 1 | +# Hierarchical-Interoception |
| 2 | + |
| 3 | +This repository contains the complete analysis pipeline for hierarchical psychometric function modeling applied to interoceptive tasks. The project implements Bayesian hierarchical models for analyzing Heart Rate Discrimination Task (HRDT) and Respiratory Resistance Sensitivity Task (RRST) data, with comprehensive power analysis capabilities and interactive visualization tools. |
| 4 | + |
| 5 | +## Table of Contents |
| 6 | + |
| 7 | +1. [Technical Details of the Hierarchical Model](#technical-details-of-the-hierarchical-model) |
| 8 | +2. [Data Simulation Workflow](#data-simulation-workflow) |
| 9 | +3. [Reproducing Key Figures](#reproducing-key-figures) |
| 10 | +4. [Shiny App Deployment and Usage](#shiny-app-deployment-and-usage) |
| 11 | + |
| 12 | +## Technical Details of the Hierarchical Model |
| 13 | + |
| 14 | +### Model Formulation |
| 15 | + |
| 16 | +The hierarchical model implements a psychometric function with three key parameters: |
| 17 | + |
| 18 | +1. **Threshold (α)**: The stimulus intensity at which the probability of correct response is 0.5 |
| 19 | +2. **Slope (β)**: The steepness of the psychometric function, indicating sensitivity |
| 20 | +3. **Lapse rate (λ)**: The asymptotic error rate for extreme stimulus intensities |
| 21 | + |
| 22 | +### Mathematical Framework |
| 23 | + |
| 24 | +The psychometric function is defined as: |
| 25 | + |
| 26 | +$$P(response) = \lambda + (1 - 2\lambda) \cdot \left(0.5 + 0.5 \cdot \text{erf}\left(\frac{\beta \cdot (x - \alpha)}{\sqrt{2}}\right)\right)$$ |
| 27 | + |
| 28 | +Where: |
| 29 | +- $x$ is the stimulus intensity |
| 30 | +- $\alpha$ is the threshold parameter |
| 31 | +- $\beta$ is the slope parameter (constrained to be positive) |
| 32 | +- $\lambda$ is the lapse rate (constrained to [0, 0.5]) |
| 33 | +- $\text{erf}$ is the error function |
| 34 | + |
| 35 | +### Hierarchical Structure |
| 36 | + |
| 37 | +The model implements a three-level hierarchy: |
| 38 | + |
| 39 | +1. **Group-level parameters**: Population means and variances for each parameter |
| 40 | +2. **Subject-level parameters**: Individual deviations from group means |
| 41 | +3. **Trial-level observations**: Binary responses to stimulus presentations |
| 42 | + |
| 43 | +### Stan Implementation |
| 44 | + |
| 45 | +The model is implemented in Stan with the following key components: |
| 46 | + |
| 47 | +```stan |
| 48 | +// Group-level parameters |
| 49 | +vector[N] gm; // Group means for all parameters |
| 50 | +vector<lower = 0>[N] tau_u; // Between-participant scales |
| 51 | +matrix[N, S] z_expo; // Participant deviations |
| 52 | +
|
| 53 | +// Transformed parameters |
| 54 | +vector[S] alpha_int = (gm[1] + (tau_u[1] * z_expo[1,]))'; |
| 55 | +vector[S] beta_int = (gm[2] + (tau_u[2] * z_expo[2,]))'; |
| 56 | +vector[S] lapse = (inv_logit(gm[3] + (tau_u[3] * z_expo[3,])) / 2)'; |
| 57 | +``` |
| 58 | + |
| 59 | +### Prior Specifications |
| 60 | + |
| 61 | +The model uses empirically informed priors derived from previous studies: |
| 62 | + |
| 63 | +- **Threshold (α)**: Normal(-8.67, 0.52×10) for intercept, Normal(0, 11.23) for treatment effect |
| 64 | +- **Slope (β)**: Normal(-2.3, 0.02×10) for intercept, Normal(0, 0.34) for treatment effect |
| 65 | +- **Lapse rate (λ)**: Normal(-4.32, 0.29) |
| 66 | + |
| 67 | +### Model Comparison |
| 68 | + |
| 69 | +The repository supports both Gaussian and Gumbel cumulative distribution functions: |
| 70 | + |
| 71 | +- **Gaussian**: Uses the error function (erf) for the cumulative normal |
| 72 | +- **Gumbel**: Uses the exponential function for the cumulative Gumbel |
| 73 | + |
| 74 | +Model comparison is performed using leave-one-out cross-validation (LOO-CV) with moment matching. |
| 75 | + |
| 76 | +## Data Simulation Workflow |
| 77 | + |
| 78 | +### Using the BRMS Demo |
| 79 | + |
| 80 | +The `apps & wrappers/BRMS demo.Rmd` provides a complete workflow for data simulation and analysis: |
| 81 | + |
| 82 | +1. **Data Simulation**: Generate synthetic data with known parameters |
| 83 | +2. **Model Specification**: Define the hierarchical psychometric function |
| 84 | +3. **Model Fitting**: Fit using BRMS with Stan backend |
| 85 | +4. **Diagnostics**: Check convergence and sampling quality |
| 86 | +5. **Inference**: Extract posterior distributions and credible intervals |
| 87 | + |
| 88 | +### Simulation Parameters |
| 89 | + |
| 90 | +```r |
| 91 | +# Design parameters |
| 92 | +n_subj_HRDT <- 50 |
| 93 | +n_rep_HRDT <- 30 |
| 94 | +x_seq_HRDT <- rep(seq(-40, 40, 5), n_rep_HRDT) |
| 95 | + |
| 96 | +# Group-level parameters |
| 97 | +mu_alpha_HRDT <- -8.6 |
| 98 | +sd_alpha_HRDT <- 11.6 |
| 99 | +mu_beta_HRDT <- -2.3 |
| 100 | +sd_beta_HRDT <- 0.3 |
| 101 | +mu_lambda_HRDT <- -4.2 |
| 102 | +sd_lambda_HRDT <- 2 |
| 103 | +``` |
| 104 | + |
| 105 | +### Model Specification in BRMS |
| 106 | + |
| 107 | +```r |
| 108 | +bff_HRDT = bf( |
| 109 | + y|trials(n) ~ (inv_logit(lambda) / 2 + |
| 110 | + (1-inv_logit(lambda)) * |
| 111 | + (0.5 + 0.5 * erf(exp(beta)*(x-alpha)/(sqrt(2))))), |
| 112 | + alpha ~ condition + (condition | subj), |
| 113 | + beta ~ condition + (condition | subj), |
| 114 | + lambda ~ (1 | subj), |
| 115 | + nl = TRUE, |
| 116 | + family = binomial(link = "identity") |
| 117 | +) |
| 118 | +``` |
| 119 | + |
| 120 | +### Running the Analysis |
| 121 | + |
| 122 | +```r |
| 123 | +fit_eip_HRDT <- brm( |
| 124 | + bff_HRDT, |
| 125 | + data = sim_data_HRDT, |
| 126 | + chains = 4, |
| 127 | + cores = 4, |
| 128 | + seed = seed, |
| 129 | + backend = "cmdstan", |
| 130 | + warmup = 2000, |
| 131 | + iter = 4000, |
| 132 | + control = list( |
| 133 | + adapt_delta = 0.9, |
| 134 | + max_treedepth = 10 |
| 135 | + ), |
| 136 | + prior = empirically_informed_priors_HRDT |
| 137 | +) |
| 138 | +``` |
| 139 | + |
| 140 | +## Reproducing Key Figures |
| 141 | + |
| 142 | +### Figure 1: Psychometric Function Forms |
| 143 | + |
| 144 | +**File**: `figures_scripts/Figure1.Rmd` |
| 145 | + |
| 146 | +This figure compares different psychometric function formulations: |
| 147 | + |
| 148 | +```r |
| 149 | +PFs <- tibble(x = seq(-6, 11, .001)) %>% |
| 150 | + mutate( |
| 151 | + Gaussian = pnorm(x - 2), |
| 152 | + Gumbel = 1 - exp(-10^(0.4 * (x - 2.4))) |
| 153 | + ) %>% |
| 154 | + pivot_longer(cols = !x) %>% |
| 155 | + rename(Formulation = name) |
| 156 | +``` |
| 157 | + |
| 158 | +**To reproduce**: |
| 159 | +```r |
| 160 | +source("figures_scripts/Figure1.Rmd") |
| 161 | +``` |
| 162 | + |
| 163 | +### Figure 3: Model Recovery |
| 164 | + |
| 165 | +**File**: `figures_scripts/Figure3.Rmd` |
| 166 | + |
| 167 | +This figure shows model recovery performance across different generative models: |
| 168 | + |
| 169 | +```r |
| 170 | +# Load model recovery results |
| 171 | +load(here::here("results", "model_and_parameter_recovery.RData")) |
| 172 | +model_recovery <- results |
| 173 | + |
| 174 | +# Calculate recovery statistics |
| 175 | +mod_comp <- tibble( |
| 176 | + winning_model = rep(NaN, 400), |
| 177 | + generative_model = rep(NaN, 400), |
| 178 | + any_pareto_k = rep(NaN, 400), |
| 179 | + significant = rep(NaN, 400), |
| 180 | + meanrhat = rep(NaN, 400), |
| 181 | + sum_div = rep(NaN, 400) |
| 182 | +) |
| 183 | +``` |
| 184 | + |
| 185 | +**To reproduce**: |
| 186 | +```r |
| 187 | +source("figures_scripts/Figure3.Rmd") |
| 188 | +``` |
| 189 | + |
| 190 | +### Additional Figures |
| 191 | + |
| 192 | +- **Figure 4**: Parameter recovery plots (`figures_scripts/Figure4.Rmd`) |
| 193 | +- **Figure 5**: Power analysis results (`figures_scripts/Figure5.Rmd`) |
| 194 | +- **Figure 6**: Hierarchical vs. simple t-test comparison (`figures_scripts/Figure6.Rmd`) |
| 195 | + |
| 196 | +## Shiny App Deployment and Usage |
| 197 | + |
| 198 | +### App Overview |
| 199 | + |
| 200 | +The Shiny app (`apps & wrappers/shiny app.R`) provides an interactive interface for exploring power analysis results with three main panels: |
| 201 | + |
| 202 | +1. **Power Grid**: Visualize power contours across different sample sizes |
| 203 | +2. **Effect Size vs. Power**: Examine power as a function of effect size |
| 204 | +3. **Manual Input**: Get specific power estimates for custom parameters |
| 205 | + |
| 206 | +### Deployment |
| 207 | + |
| 208 | +#### Local Deployment |
| 209 | + |
| 210 | +1. **Install Dependencies**: |
| 211 | +```r |
| 212 | +install.packages(c("shiny", "tidyverse", "flextable", "posterior")) |
| 213 | +``` |
| 214 | + |
| 215 | +2. **Run the App**: |
| 216 | +```r |
| 217 | +shiny::runApp("apps & wrappers/shiny app.R") |
| 218 | +``` |
| 219 | + |
| 220 | +#### Server Deployment |
| 221 | + |
| 222 | +1. **Prepare for Deployment**: |
| 223 | +```r |
| 224 | +# Ensure all data files are accessible |
| 225 | +# Check that results/power analysis/Extracted/all_draws.csv exists |
| 226 | +``` |
| 227 | + |
| 228 | +2. **Deploy to Shiny Server**: |
| 229 | +```bash |
| 230 | +# Copy files to server |
| 231 | +scp -r "apps & wrappers/" user@server:/path/to/shiny/apps/ |
| 232 | +scp -r results/ user@server:/path/to/shiny/data/ |
| 233 | +``` |
| 234 | + |
| 235 | +### App Usage |
| 236 | + |
| 237 | +#### Power Grid Panel |
| 238 | + |
| 239 | +1. **Select Parameter**: Choose between "Threshold" or "Slope" |
| 240 | +2. **Set Effect Size**: Specify the desired effect size (0.0 to 1.0) |
| 241 | +3. **Adjust Sample Ranges**: Use sliders to set subject and trial ranges |
| 242 | +4. **Choose Power Level**: Select desired power (0.80, 0.90, or 0.95) |
| 243 | +5. **Interpret Results**: Contour lines show combinations achieving target power |
| 244 | + |
| 245 | +#### Effect Size vs. Power Panel |
| 246 | + |
| 247 | +1. **Set Sample Size**: Specify number of subjects and trials |
| 248 | +2. **View Power Curve**: See how power changes with effect size |
| 249 | +3. **Identify Minimum Effect**: Find the smallest detectable effect size |
| 250 | + |
| 251 | +#### Manual Input Panel |
| 252 | + |
| 253 | +1. **Enter Parameters**: Specify exact sample sizes and effect size |
| 254 | +2. **Submit Query**: Get precise power estimates |
| 255 | +3. **View Results**: See power for hierarchical, simple t-test, and uncertainty propagation methods |
| 256 | + |
| 257 | +### Data Requirements |
| 258 | + |
| 259 | +The app requires the following data files: |
| 260 | +- `results/power analysis/Extracted/all_draws.csv`: Power analysis results |
| 261 | +- `results/power analysis/Extracted/all_data.csv`: Summary statistics |
| 262 | + |
| 263 | +### Customization |
| 264 | + |
| 265 | +To modify the app for different analyses: |
| 266 | + |
| 267 | +1. **Update Data Source**: Modify the `all_data` loading in the server function |
| 268 | +2. **Add New Parameters**: Extend the parameter selection options |
| 269 | +3. **Customize Plots**: Modify the ggplot aesthetics and themes |
| 270 | +4. **Add New Panels**: Create additional tabPanels for new functionality |
| 271 | + |
| 272 | +## File Structure |
| 273 | + |
| 274 | +``` |
| 275 | +Hierarchical-Interoception/ |
| 276 | +├── Analysis/ # Main analysis scripts |
| 277 | +├── apps & wrappers/ # BRMS demo and Shiny app |
| 278 | +├── Data/ # Raw data files |
| 279 | +├── figures/ # Generated figures |
| 280 | +├── figures_scripts/ # Figure generation scripts |
| 281 | +├── results/ # Analysis results and power analysis |
| 282 | +├── scripts/ # Utility functions and main scripts |
| 283 | +├── simulated_data/ # Simulated datasets |
| 284 | +└── stanmodels/ # Stan model definitions |
| 285 | +``` |
| 286 | + |
| 287 | +## Dependencies |
| 288 | + |
| 289 | +### Required R Packages |
| 290 | +```r |
| 291 | +install.packages(c( |
| 292 | + "cmdstanr", "tidyverse", "posterior", "bayesplot", |
| 293 | + "tidybayes", "rstan", "brms", "pracma", "shiny", |
| 294 | + "flextable", "loo", "furrr" |
| 295 | +)) |
| 296 | +``` |
| 297 | + |
| 298 | +### Stan Installation |
| 299 | +```r |
| 300 | +# Install cmdstanr |
| 301 | +install.packages("cmdstanr", repos = c("https://mc-stan.org/r-packages/", getOption("repos"))) |
| 302 | + |
| 303 | +# Check cmdstan installation |
| 304 | +cmdstanr::check_cmdstan_toolchain() |
| 305 | +cmdstanr::install_cmdstan() |
| 306 | +``` |
| 307 | + |
| 308 | +## Citation |
| 309 | + |
| 310 | +If you use this code in your research, please cite: |
| 311 | + |
| 312 | +``` |
| 313 | +Courtin, A.S., & Fischer Ehmsen, J. (2025). Hierarchical Bayesian modeling of interoceptive psychometric functions. [Paper DOI to be added] |
| 314 | +``` |
| 315 | + |
| 316 | +## Contact |
| 317 | + |
| 318 | +For questions or issues with this repository, please contact the authors or open an issue on the repository page. |
0 commit comments