Dyadic Data Analysis with R

Part II: Applied Tutorial

Pascal Küng
Turu Stadler

Today’s aims and program

Time Focus
11:00–12:45 Introduction and types of dyadic models
12:45–14:00 Lunch break
14:00–15:30 Cross-sectional APIM in R
15:30–15:45 Short break
15:45–16:45 Intensive longitudinal APIM in R
16:45–17:00 Short break
17:00–18:00 Exercises in R

Software and frameworks

SEM vs. MLM

  • Many simple dyadic models can be represented in either framework.
  • The best framework is the one that represents your conceptual model most naturally (Ledermann & Kenny, 2017).

SEM / DSEM is often more natural for

  • Latent dyadic constructs (e.g., a CFM)
  • Item-level measurement models for reflective measures
  • Linked outcomes or equations [e.g., mediation; Ledermann & Macho (2009)]
  • Short dynamic panels in wide-format [e.g., LD-APIM; Gistelinck et al. (2021)]
  • Intensive lagged or coupled dynamics via DSEM/RDSEM (e.g., Asparouhov et al., 2018)

MLM is often more natural for

  • Parsimonious observed-variable models with modest numbers of dyads
  • Nested grouping and flexible random-effect structures
  • Observed-outcome growth or concurrent ILD models with flexible time trends
  • Complex observed-variable regressions—interactions, polynomials, splines, and dyadic RSA—especially with repeated measures or additional nesting
  • Specialized generalized mixed models in R, including zero-inflated, hurdle, and Tweedie models

SEM vs. MLM: Missing data

  • MLM: Complete-case
    • Outcome missing: Valid under MAR, MLM uses remaining observations
    • Predictor missing: Valid under MAR, if missingness does not depend on the outcome after accounting for predictors
  • SEM with FIML and joint likelihood
    • Valid under MAR: Models predictors and outcomes jointly
  • MI: Imputation
    • Handles missing outcomes, predictors, and auxiliary information
    • Must match the analysis and preserve person, dyad, role, and time structure

Include important predictors of missingness and missing values. FIML and MI cover more MAR situations than row omission; none automatically solves MNAR.

Ledermann & Kenny (2017); Hughes et al. (2019); Grund et al. (2018)

Why R

Why we teach R

  • Free to use: no license fees or institutional access barriers
  • Open source: statistical implementations can be inspected, audited, and adapted
  • Reproducible: workflows can be rerun, reviewed, shared, and version-controlled
  • Extensible: specialized methods integrate with data, graphics, and reporting

Current trade-offs

  • SEM/DSEM: R spreads these capabilities across specialized packages
    • lavaan: fixed-wave and limited two-level SEM
    • mlts and ctsem: multivariate dynamic models
      • Dyadic DSEM in R remains cumbersome and limited
      • Mplus remains the most complete (non-open) option with DSEM capabilities
  • MLM/ILD: glmmTMB and brms both model serial dependence
    • Neither directly offers a separable partner-by-time residual structure such as \(\mathrm{UN}\otimes\mathrm{AR}(1)\)
      • SAS PROC MIXED and SPSS MIXED do for Gaussian repeated-measures models

Cross-sectional APIM in R

One possible workflow

  1. Define dyads, distinguishability, and research questions
  2. Prepare the data and validate the dyadic structure
  3. Describe and visualize measures and observed relationships
  4. Estimate the models
  5. Verify model adequacy, distinguishability (if applicable), and robustness
  6. Visualize, interpret, and report results

Synthesized from Kenny et al. (2006), Ledermann & Kenny (2015), and Stas et al. (2018).

One possible workflow

  1. Define dyads, distinguishability, and research questions
  2. Prepare the data and validate the dyadic structure
  3. Describe and visualize measures and observed relationships
  4. Estimate the models
  5. Verify model adequacy, distinguishability (if applicable), and robustness
  6. Visualize, interpret, and report results

Synthesized from Kenny et al. (2006), Ledermann & Kenny (2015), and Stas et al. (2018).

The Dataset

Same as before:

  • 36 physically inactive romantic couples
  • Both partners intended to become more physically active
  • Smartphone-based planning interventions and JITAIs
  • Daily diaries over 55 days

Aggregated for cross-sectional analyses

Research Question

RQ: In romantic couples, is a person’s self-efficacy linked to physical activity in both partners?

Measurement - predictor

Self-efficacy

Rough English translation of the original German item
“I am confident that I can be physically active tomorrow, even if it becomes difficult.”

Adapted from Sniehotta et al. (2005)

0 = not at all true today
5 = completely true today

Measurement - outcome

Moderate to vigorous physical activity (MVPA)

Rough English translation of the original German item
“How many minutes did you spend engaging in moderate-to-vigorous physical activity today?”

Adapted from Amireault & Godin (2015)

  • Alone: _____ minutes
  • Together with your partner: _____ minutes

Total MVPA = solo MVPA + joint MVPA.

Distinguishable dyads

Distinguishing by gender

Workflow

  1. Define dyads, distinguishability, and research questions
  2. Prepare the data and validate the dyadic structure
  3. Describe and visualize measures and observed relationships
  4. Estimate the models
  5. Verify model adequacy, distinguishability, and robustness
  6. Visualize, interpret, and report results

Load and inspect the raw data

library(dplyr) # for easier data-wrangling, exports functions like `select()`

raw_dyad_data <- readRDS("dyadic-person-means.rds") |>
  dplyr::select(
    couple_id, person_id, gender, efficacy, total_mvpa
  )

# usually use head(raw_dyad_data), slide_table is a custom function for html tables on these slides
slide_table(raw_dyad_data[1:4,])
couple_id person_id gender efficacy total_mvpa
1.00 1.00 male 3.02 33.61
1.00 2.00 female 0.89 12.71
2.00 3.00 male 0.13 42.83
2.00 4.00 female 2.37 44.54


This dataset is stored as an R object. Other packages provide functions to import various formats, such as: readxl::read_excel(), utils::read.csv(), haven::read_sav(), haven::read_sas()

Create the required format and validate the data

For the distinguishable APIM we need to add:

  • efficacy_actor and efficacy_partner columns
  • Dummy coded numeric variables for the two roles

Validate things like:

  • no dyad with more than two members,
  • each dyad-member combination occurs only once (per occasion),
  • gender labels are present and consistent
  • you only have the type of dyad you expect

Create the required format and validate the data

We can use the dyadMLM package to help with data preparation and validation.

library(dyadMLM)

apim_distinguishable_data <- dyadMLM::prepare_dyad_data( # exported function from dyadMLM
  data = raw_dyad_data,
  dyad = couple_id,
  member = person_id,
  role = gender,
  predictors = efficacy,
  model_types = 'apim',
  # if we want to fit an exchangeable model from the same data we can:
  include_arbitrary_member_contrast = TRUE,
  add_apim_gmc_predictors = TRUE,
  seed = 123
)

print(apim_distinguishable_data)

Create the required format and validate the data

# dyadMLM data
# Rows: 72 | Dyads: 36 | Intensive longitudinal: no
# Structure: dyad = couple_id, member = person_id, role = gender
#
# Dyad compositions:
# female_x_male distinguishable 36 dyads
#
# Added columns:
#   .composition                inferred dyad composition
#   .composition_role           composition-specific member role
#   .is_{role}                  composition-role indicator columns
#   .member_contrast_arbitrary  composition-specific member contrasts coded
#                               -1/+1 in arbitrary direction for
#                               exchangeability-constrained random effects.
#                               Values are 0 for other compositions
#   .{pred}_actor               APIM actor predictor: actor's original
#                               predictor values
#   .{pred}_partner             APIM partner predictor: partner's original
#                               predictor values
#   .{pred}_gmc                 APIM grand-mean-centered predictor source:
#                               original values minus the mean across all
#                               retained non-missing observations
#   .{pred}_gmc_actor           APIM grand-mean-centered actor predictor:
#                               actor's value relative to the mean across all
#                               retained non-missing observations
#   .{pred}_gmc_partner         APIM grand-mean-centered partner predictor:
#                               partner's value relative to the mean across all
#                               retained non-missing observations
#
# A tibble: 72 × 15
   couple_id person_id gender efficacy total_mvpa .composition .composition_role
       <int>     <int> <fct>     <dbl>      <dbl> <fct>        <fct>            
 1         1         1 male      3.02       33.6  female_x_ma… female_x_male_ma…
 2         1         2 female    0.891      12.7  female_x_ma… female_x_male_fe…
 3         2         3 male      0.132      42.8  female_x_ma… female_x_male_ma…
 4         2         4 female    2.37       44.5  female_x_ma… female_x_male_fe…
 5         3         5 female    3.33       62.9  female_x_ma… female_x_male_fe…
 6         3         6 male      1.62       32.3  female_x_ma… female_x_male_ma…
 7         4         7 male      1.09       21.5  female_x_ma… female_x_male_ma…
 8         4         8 female    0.755      51.9  female_x_ma… female_x_male_fe…
 9         5         9 female    1.48        9.79 female_x_ma… female_x_male_fe…
10         5        10 male      1.35       11.4  female_x_ma… female_x_male_ma…
# ℹ 62 more rows
# ℹ 8 more variables: .is_female <dbl>, .is_male <dbl>,
#   .member_contrast_arbitrary <dbl>, .efficacy_gmc <dbl>,
#   .efficacy_actor <dbl>, .efficacy_partner <dbl>, .efficacy_gmc_actor <dbl>,
#   .efficacy_gmc_partner <dbl>

Create the required format and validate the data Manual code →

Looking at the columns immediately relevant to us:

head(apim_distinguishable_data) |>
  dplyr::select(
    couple_id, person_id, gender, .is_female, .is_male, .efficacy_gmc, .efficacy_gmc_actor, .efficacy_gmc_partner, total_mvpa
  ) |>
  slide_table()
couple_id person_id gender .is_female .is_male .efficacy_gmc .efficacy_gmc_actor .efficacy_gmc_partner total_mvpa
1.00 1.00 male 0.00 1.00 0.64 0.64 -1.49 33.61
1.00 2.00 female 1.00 0.00 -1.49 -1.49 0.64 12.71
2.00 3.00 male 0.00 1.00 -2.24 -2.24 -0.01 42.83
2.00 4.00 female 1.00 0.00 -0.01 -0.01 -2.24 44.54
3.00 5.00 female 1.00 0.00 0.96 0.96 -0.76 62.86
3.00 6.00 male 0.00 1.00 -0.76 -0.76 0.96 32.29

Workflow

  1. Define dyads, distinguishability, and research questions
  2. Prepare the data and validate the dyadic structure
  3. Describe and visualize measures and observed relationships
  4. Estimate the models
  5. Verify model adequacy, distinguishability, and robustness
  6. Visualize, interpret, and report results

Create a wide view for role-specific exploration

The models will NOT use this dataset

apim_distinguishable_wide_data <- apim_distinguishable_data |>
  dplyr::select(couple_id, gender, efficacy, total_mvpa) |>
  tidyr::pivot_wider(
    names_from = gender,
    values_from = c(efficacy, total_mvpa)
  )

slide_table(head(apim_distinguishable_wide_data))
couple_id efficacy_male efficacy_female total_mvpa_male total_mvpa_female
1.00 3.02 0.89 33.61 12.71
2.00 0.13 2.37 42.83 44.54
3.00 1.62 3.33 32.29 62.86
4.00 1.09 0.75 21.55 51.89
5.00 1.35 1.48 11.35 9.79
6.00 1.18 0.63 7.55 3.43

Describe focal variables per role

library(report)

apim_distinguishable_wide_data |>
  dplyr::select(-couple_id) |>
  report::report_table() |>
  slide_table()
Variable n_Obs Mean SD Median MAD Min Max Skewness Kurtosis n_Missing
efficacy_male 36.00 2.29 0.99 2.32 1.18 0.13 4.60 0.16 -0.39 0.00
efficacy_female 36.00 2.46 1.01 2.39 0.85 0.46 4.92 0.01 0.28 0.00
total_mvpa_male 36.00 28.84 20.28 27.26 16.82 3.51 111.16 2.11 7.00 0.00
total_mvpa_female 36.00 33.58 24.39 29.85 20.90 3.43 126.35 1.74 4.80 0.00

Visualize focal variable by partner role

library(ggplot2)
library(see)

plot_role_histograms <- function(data, group, var) {
  var_label <- rlang::as_label(rlang::enquo(var))

  ggplot(
    data,
    aes(x = {{ var }}, y = after_stat(density))
  ) +
    geom_histogram(
      bins = 15, boundary = 0,
      fill = "steelblue", color = "white", alpha = 0.75
    ) +
    geom_density(
      bounds = c(0, Inf), color = "navy", linewidth = 1.1
    ) +
    facet_wrap(vars({{ group }}), nrow = 1) +
    labs(x = var_label, y = "Density") +
    see::theme_modern(base_size = 16)
}

Visualize focal variable by partner role - Efficacy

plot_role_histograms(apim_distinguishable_data, gender, efficacy)

Visualize focal variable by partner role - MVPA

plot_role_histograms(apim_distinguishable_data, gender, total_mvpa)

Observed dyadic correlations

The wide data ensure that each couple contributes once.

library(correlation)
library(insight)

apim_distinguishable_wide_data |>
  correlation::correlation(
    select = c("efficacy_female", "total_mvpa_female"),
    select2 = c("efficacy_male", "total_mvpa_male")
  ) |>
  summary() |>
  insight::print_md(footer = "")
Correlation Matrix (pearson-method)
Parameter efficacy_male total_mvpa_male
efficacy_female 0.60*** 0.43*
total_mvpa_female 0.42* 0.82***

Visualize partner similarity

plot_partner_similarity <- function(data, x, y, id, title, n_mahalanobis = 3) {
  x_name <- rlang::as_name(rlang::enquo(x))
  y_name <- rlang::as_name(rlang::enquo(y))
  xy <- cbind(data[[x_name]], data[[y_name]])
  labels <- data |>
    dplyr::mutate(
      .distance = mahalanobis(xy, colMeans(xy), cov(xy))
    ) |>
    dplyr::distinct({{ id }}, .keep_all = TRUE) |>
    dplyr::slice_max(.distance, n = n_mahalanobis, with_ties = FALSE)

  ggplot(data, aes(x = {{ x }}, y = {{ y }})) +
    geom_point(color = "steelblue") +
    geom_smooth(method = "lm", se = FALSE, color = "navy") +
    geom_text(
      data = labels,
      aes(label = {{ id }}),
      vjust = -0.6, check_overlap = TRUE, size = 4
    ) +
    labs(title = title, x = "Female", y = "Male") +
    scale_y_continuous(expand = expansion(mult = c(0.05, 0.12))) +
    see::theme_modern(base_size = 16)
}

Visualize partner similarity

n_mahalanobis <- 4

see::plots(
  plot_partner_similarity(
    apim_distinguishable_wide_data,
    efficacy_female, efficacy_male, couple_id, "Efficacy",
    n_mahalanobis = n_mahalanobis
  ),
  plot_partner_similarity(
    apim_distinguishable_wide_data,
    total_mvpa_female, total_mvpa_male, couple_id, "Daily MVPA",
    n_mahalanobis = n_mahalanobis
  )
)

Visualize partner similarity

Labels: four largest Mahalanobis distances in each plot.

Workflow

  1. Define dyads, distinguishability, and research questions
  2. Prepare the data and validate the dyadic structure
  3. Describe and visualize measures and observed relationships
  4. Estimate the models
  5. Verify model adequacy, distinguishability, and robustness
  6. Visualize, interpret, and report results

Distinguishable APIM

Conceptual distinguishable APIM with role-specific actor and partner paths and correlated outcome residuals.

The APIM starts with two member-specific equations

For each dyad \(i\), each member with role \(r \in \{\mathrm{M},\mathrm{F}\}\) has their own predictor \(X^{(\mathrm{A})}_{ri}\) and their partner’s predictor \(X^{(\mathrm{P})}_{ri}\):

\[ \begin{gathered} Y_{\mathrm{M},i} = b_{0,\mathrm{M}} + \color{#1769AA}{a_{\mathrm{M}} \times X^{(\mathrm{A})}_{\mathrm{M},i}} + \color{#7B3FA1}{p_{\mathrm{M}} \times X^{(\mathrm{P})}_{\mathrm{M},i}} + \epsilon_{\mathrm{M},i} \\ Y_{\mathrm{F},i} = b_{0,\mathrm{F}} + \color{#1769AA}{a_{\mathrm{F}} \times X^{(\mathrm{A})}_{\mathrm{F},i}} + \color{#7B3FA1}{p_{\mathrm{F}} \times X^{(\mathrm{P})}_{\mathrm{F},i}} + \epsilon_{\mathrm{F},i} \end{gathered} \]

- Actor effects - Partner effects

In long format, each equation applies to one member row of the dyad.

How can we combine these two equations into one model?

Role indicators combine the equations

The indicators \(D_{\mathrm{M},ri}\) and \(D_{\mathrm{F},ri}\) identify the male or female row:

\[ \begin{aligned} Y_{ri} ={}& \underbrace{ b_{0,\mathrm{M}} \times D_{\mathrm{M},ri} + b_{0,\mathrm{F}} \times D_{\mathrm{F},ri} }_{\text{role-specific intercepts}} \\[0.35em] &+ \color{#1769AA}{ \underbrace{ a_{\mathrm{M}} \times D_{\mathrm{M},ri} \times X^{(\mathrm{A})}_{ri} + a_{\mathrm{F}} \times D_{\mathrm{F},ri} \times X^{(\mathrm{A})}_{ri} }_{\text{role-specific actor effects}} } \\[0.35em] &+ \color{#7B3FA1}{ \underbrace{ p_{\mathrm{M}} \times D_{\mathrm{M},ri} \times X^{(\mathrm{P})}_{ri} + p_{\mathrm{F}} \times D_{\mathrm{F},ri} \times X^{(\mathrm{P})}_{ri} }_{\text{role-specific partner effects}} } + \epsilon_{ri}. \end{aligned} \]

For a male row: \(D_\mathrm{M}=1,\ D_\mathrm{F}=0\). For a female row: the reverse.

Residuals remain correlated within dyads

The two member rows from the same dyad retain a joint residual structure:

\[ \operatorname{Cov} \begin{pmatrix} \epsilon_{\mathrm{F}i} \\ \epsilon_{\mathrm{M}i} \end{pmatrix} = \boldsymbol{\Sigma}_{\epsilon} = \begin{bmatrix} \sigma_{\epsilon_\mathrm{F}}^{2} & \rho_{\epsilon_\mathrm{F}\epsilon_\mathrm{M}} \sigma_{\epsilon_\mathrm{F}}\sigma_{\epsilon_\mathrm{M}} \\ \rho_{\epsilon_\mathrm{F}\epsilon_\mathrm{M}} \sigma_{\epsilon_\mathrm{F}}\sigma_{\epsilon_\mathrm{M}} & \sigma_{\epsilon_\mathrm{M}}^{2} \end{bmatrix} \]

Fitting the distinguishable APIM in glmmTMB

library(glmmTMB)

apim_distinguishable_model <- glmmTMB(
  total_mvpa ~
    # Role-specific intercepts
    0 + .is_female + .is_male +

    # Role-specific actor effects
    .is_female:.efficacy_gmc_actor +
    .is_male:.efficacy_gmc_actor +

    # Role-specific partner effects
    .is_female:.efficacy_gmc_partner +
    .is_male:.efficacy_gmc_partner +

    # Within-dyad residual covariance
    (0 + .is_female + .is_male | couple_id),
  dispformula = ~ 0,
  family = gaussian(),
  data = apim_distinguishable_data
)

summary(apim_distinguishable_model)

Fitting the distinguishable APIM in glmmTMB

 Family: gaussian  ( identity )
Formula:          
total_mvpa ~ 0 + .is_female + .is_male + .is_female:.efficacy_gmc_actor +  
    .is_male:.efficacy_gmc_actor + .is_female:.efficacy_gmc_partner +  
    .is_male:.efficacy_gmc_partner + (0 + .is_female + .is_male |  
    couple_id)
Dispersion:                  ~0
Data: apim_distinguishable_data

      AIC       BIC    logLik -2*log(L)  df.resid 
    605.4     625.9    -293.7     587.4        63 

Random effects:

Conditional model:
 Groups    Name       Variance Std.Dev. Corr 
 couple_id .is_female 388.3    19.70         
           .is_male   286.0    16.91    0.79 
Number of obs: 72, groups:  couple_id, 36

Conditional model:
                                 Estimate Std. Error z value Pr(>|z|)    
.is_female                         32.853      3.342   9.829  < 2e-16 ***
.is_male                           29.167      2.869  10.168  < 2e-16 ***
.is_female:.efficacy_gmc_actor     11.867      4.139   2.867  0.00414 ** 
.is_male:.efficacy_gmc_actor        7.970      3.611   2.207  0.02730 *  
.is_female:.efficacy_gmc_partner    3.090      4.207   0.734  0.46274    
.is_male:.efficacy_gmc_partner      4.048      3.552   1.140  0.25442    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Workflow

  1. Define dyads, distinguishability, and research questions
  2. Prepare the data and validate the dyadic structure
  3. Describe and visualize measures and observed relationships
  4. Estimate the models
  5. Verify model adequacy, distinguishability, and robustness
  6. Visualize, interpret, and report results

Verify model adequacy, distinguishability, and robustness

  1. Did estimation succeed?
  2. Does the model reproduce the important data patterns?
  3. Is distinguishability supported?
  4. Are conclusions robust or driven by few dyads or consequential modeling choices? (sensitivity analyses)

Did estimation succeed / converge?

The model converged without warnings… a good first sign!

Other things to look out for in the model output:

  • Estimated variances near \(0\) or correlations near \(\pm 1\) \(\rightarrow\) possible boundary fit
  • NaN or infinite estimates, standard errors, or model fit indices

Residual diagnostics / reproduction of data patterns

Residual diagnostics are not as straightforward anymore

  • Linear models: straightforward checks
  • Mixed models: residuals arise at multiple levels
  • Generalized models: expectations depend on the family and link
  • Dyadic models: partners’ residuals are correlated

Solution: Compare the data with simulations from the fitted model.

DHARMa: one diagnostic scale across models

The DHARMa package (Hartig, 2026):

  • Simulates outcomes from the fitted model
  • Checks whether the observed residuals behave as expected under simulations from the fitted model.
  • Adequate fit \(\rightarrow\) approximately uniform, pattern-free residuals

One function works across many model families!

Simulate the full dyadic residual structure

library(DHARMa)

apim_distinguishable_dharma_residuals <- DHARMa::simulateResiduals(
  fittedModel = apim_distinguishable_model,
  n = 10000,

  # Since our model uses dispformula = ~ 0, we cannot use DHARMa's default
  # conditional simulations. With unconditional, the random-effects block representing
  # our residuals is re-simulated.
  simulateREs = "unconditional",

  # Male and female residuals are correlated. DHARMa estimates this correlation
  # from the simulations and rotates the residuals to "remove" it.
  rotation = 'estimated',
  seed = 123
)

Inspect DHARMa residuals

In this model:

  • Uniformity and dispersion are plausible
  • No simulation outliers
  • A fitted-value pattern remains

Next: inspect roles without rotation.

More targeted checks and guidance on interpretation of the plots: DHARMa vignette.

Inspect DHARMa residuals by role

apim_distinguishable_female_dharma_residuals <- DHARMa::recalculateResiduals(
  apim_distinguishable_dharma_residuals,

  # Recalculate from the same simulations for one role.
  # One observation per couple: no rotation needed.
  sel = ~ .is_female == 1,
  rotation = NULL,
  seed = 123
)

apim_distinguishable_male_dharma_residuals <- DHARMa::recalculateResiduals(
  apim_distinguishable_dharma_residuals,
  sel = ~ .is_male == 1,
  rotation = NULL,
  seed = 123
)

The fitted-value pattern appears among women

In this model:

  • Pattern detected among women
  • No comparable pattern among men

Next: try a defensible outcome scale.

More targeted checks and guidance on interpretation of the plots: DHARMa vignette.

Square-root MVPA reduces the fitted-value pattern

Compared with raw MVPA:

  • Uniformity and dispersion are plausible
  • No simulation outliers
  • No pooled fitted-value pattern

Substantive result unchanged: actor effects remain positive and supported; partner effects remain unsupported.

In this example:

  • Sensitivity analysis: square-root MVPA; conclusions unchanged
  • Primary analysis: report the prespecified raw-MVPA model because the deviation was modest and minutes are directly interpretable
  • General rule: larger or consequential diagnostic problems: revise main model

Transform—or change the outcome family?

  • Transformation may help for a nonnegative, right-skewed outcome
    • Try sqrt() or log1p() (handles zeros)
    • Refit and rerun DHARMa; the histogram alone is not sufficient
  • A non-Gaussian family may be preferable
    • Particularly for counts, proportions, bounded, or zero-heavy outcomes
    • Family and link choices require outcome-specific care
  • Workshop scope: generalized models are not covered here

Test distinguishability

  • Fit model with exchangeability constraints

    • Partial constraints can be very informative!
  • Compare models

  • Pool only if theoretically defensible, aligned with RQ and theory, and compatible with the data

    • Depending on goals you can also report the distinguishable model and report results from this analysis

However, non-significant comparisons are not proof that roles are identical.

Gistelinck et al. (2018)

Estimate and compare:

dyadMLM::compare_nested_models(
  apim_distinguishable_model, apim_exchangeable_comparison_model
)
Likelihood-ratio test for nested models fitted to equivalent data
Assumes mathematical nesting and an appropriate chi-squared reference distribution.

                                   Df    AIC    BIC  logLik deviance  Chisq
apim_exchangeable_comparison_model  5 604.25 615.64 -297.13   594.25       
apim_distinguishable_model          9 605.44 625.93 -293.72   587.44 6.8155
                                   Chi Df Pr(>Chisq)
apim_exchangeable_comparison_model                  
apim_distinguishable_model              4      0.146

Conclusion (5% level): The likelihood-ratio test finds no clear improvement from `apim_exchangeable_comparison_model` to `apim_distinguishable_model` (p = 0.146). This does not establish equal fit.

Test robustness - are conclusions driven by few dyads?

plot_actor_association <- function(data, model, role, title,
                                   n_mahalanobis = 3) {
  xy <- cbind(data$.efficacy_gmc_actor, data$total_mvpa)
  labels <- data |>
    dplyr::mutate(.distance = mahalanobis(xy, colMeans(xy), cov(xy))) |>
    dplyr::slice_max(.distance, n = n_mahalanobis)
  coefficients <- glmmTMB::fixef(model)$cond

  ggplot(data, aes(.efficacy_gmc_actor, total_mvpa)) +
    geom_point(color = "steelblue") +
    geom_abline(intercept = coefficients[role],
                slope = coefficients[paste0(role, ":.efficacy_gmc_actor")],
                color = "navy", linewidth = 16 / 11) +
    geom_text(
      data = labels, aes(label = couple_id),
      vjust = -0.6, check_overlap = TRUE, size = 4
    ) +
    labs(
      title = title,
      x = "Actor efficacy (grand-mean centered)",
      y = "Daily MVPA (minutes)"
    ) +
    scale_y_continuous(expand = expansion(mult = c(0.05, 0.12))) +
    see::theme_modern(base_size = 16) +
    theme(plot.title.position = "panel",
          plot.title = element_text(hjust = 0.5))
}

Test robustness - are conclusions driven by few dyads?

n_mahalanobis <- 4

see::plots(
  plot_actor_association(
    dplyr::filter(apim_distinguishable_data, gender == "female"),
    apim_distinguishable_model, ".is_female", "Women",
    n_mahalanobis = n_mahalanobis
  ),
  plot_actor_association(
    dplyr::filter(apim_distinguishable_data, gender == "male"),
    apim_distinguishable_model, ".is_male", "Men",
    n_mahalanobis = n_mahalanobis
  )
) +
  patchwork::plot_layout(
    axes = "collect",
    axis_titles = "collect"
  )

Test robustness - are conclusions driven by few dyads?

Labels: four largest Mahalanobis distances in each plot.

Omitting Couple 28 changes the associations

apim_without_couple_28 <- update(
  apim_distinguishable_model,
  data = filter(apim_distinguishable_data, couple_id != 28)
)

actor_sensitivity <- parameters::compare_parameters(
  `Full data` = apim_distinguishable_model,
  `Without Couple 28` = apim_without_couple_28,
  keep = "\\.efficacy_gmc_actor"
)
Actor slope Full data Without Couple 28
Women 11.87** 8.44*
Men 7.97 * 5.23

* \(p < .05\); ** \(p < .01\). Couple 28 was flagged in both visual screens. Its omission weakens both slopes; only the men’s result crosses \(p=.05\).

Other sensitivity analyses you could do

Fit a small set of (prespecified) models to test:

  • coding decisions
  • different plausible outcome families or transformations
  • missing-data handling
  • covariates with a substantive confounding or precision rationale

Workflow

  1. Define dyads, distinguishability, and research questions
  2. Prepare the data and validate the dyadic structure
  3. Describe and visualize measures and observed relationships
  4. Estimate the models
  5. Verify model adequacy, distinguishability, and robustness
  6. Visualize, interpret, and report results

Reporting results and answering the research question

library(parameters)

parameters::model_parameters(apim_distinguishable_model, effects = 'fixed') |>
  insight::print_md(footer = "")
Fixed Effects
Parameter Coefficient SE 95% CI z p
is female 32.85 3.34 (26.30, 39.40) 9.83 < .001
is male 29.17 2.87 (23.55, 34.79) 10.17 < .001
is female × efficacy gmc actor 11.87 4.14 (3.75, 19.98) 2.87 0.004
is male × efficacy gmc actor 7.97 3.61 (0.89, 15.05) 2.21 0.027
is female × efficacy gmc partner 3.09 4.21 (-5.16, 11.34) 0.73 0.463
is male × efficacy gmc partner 4.05 3.55 (-2.91, 11.01) 1.14 0.254

In this sample, higher self-efficacy was associated with more of one’s own MVPA for both women and men, but not clearly with the partner’s MVPA.

Reporting results and answering the research question

Random effects in our model represent the residual structure

parameters::model_parameters(apim_distinguishable_model, effects = 'random') |>
  insight::print_md(footer = "")
Random Effects
Parameter Coefficient 95% CI
SD (.is_female: couple_id) 19.70 (15.64, 24.83)
SD (.is_male: couple_id) 16.91 (13.42, 21.31)
Cor (.is_female~.is_male: couple_id) 0.79

Visualize results

parameters::model_parameters(apim_distinguishable_model, effects = "fixed") |>
  plot()

What should be reported?

  • Analysis sample: dyads and observations, exclusions, aggregation, and missing-data handling
  • Model specification: outcome family and link, fixed effects, dyadic covariance structure, and equality constraints
  • Estimation and model decision: estimator, software and version, convergence, boundary estimates, distinguishability comparison, and retained model
  • Results: focal estimates with 95% confidence intervals in substantive units and the fitted variance-covariance parameters
  • Checks and interpretation: residual diagnostics, influential dyads, prespecified sensitivity analyses, material changes, and causal limits

Short break!

Exchangeable dyads

The same model but constrained

Workflow

  1. Define dyads, distinguishability, and research questions
  2. Prepare the data and validate the dyadic structure
  3. Describe and visualize measures and observed relationships
  4. Estimate the models
  5. Verify model adequacy, distinguishability, and robustness
  6. Visualize, interpret, and report results

Define dyads, distinguishability, and research questions

Same data and RQ, now with exchangeability constraints for illustration.

Workflow

  1. Define dyads, distinguishability, and research questions
  2. Prepare the data and validate the dyadic structure
  3. Describe and visualize measures and observed relationships
  4. Estimate the models
  5. Verify model adequacy, distinguishability, and robustness
  6. Visualize, interpret, and report results

Create the required format and validate the data

You can use dyadMLM::prepare_dyad_data() to prepare and validate the data.

For exchangeable dyads, we can just omit the role argument.

apim_exchangeable_data <- dyadMLM::prepare_dyad_data(
  data = raw_dyad_data,
  dyad = couple_id,
  member = person_id,
  predictors = efficacy,
  model_types = 'apim',
  add_apim_gmc_predictors = TRUE,
  seed = 123
)

print(apim_exchangeable_data)

If we want to estimate both distinguishable and exchangeable versions, we would pass role and set include_arbitrary_member_contrast = TRUE.

Create the required format and validate the data

# dyadMLM data
# Rows: 72 | Dyads: 36 | Intensive longitudinal: no
# Structure: dyad = couple_id, member = person_id
#
# Dyad compositions:
# assumed_exchangeable exchangeable 36 dyads
#
# Added columns:
#   .composition                inferred dyad composition
#   .composition_role           composition-specific member role
#   .is_exchangeable            composition-role indicator columns
#   .member_contrast_arbitrary  composition-specific member contrasts coded
#                               -1/+1 in arbitrary direction for
#                               exchangeability-constrained random effects.
#                               Values are 0 for other compositions
#   .{pred}_actor               APIM actor predictor: actor's original
#                               predictor values
#   .{pred}_partner             APIM partner predictor: partner's original
#                               predictor values
#   .{pred}_gmc                 APIM grand-mean-centered predictor source:
#                               original values minus the mean across all
#                               retained non-missing observations
#   .{pred}_gmc_actor           APIM grand-mean-centered actor predictor:
#                               actor's value relative to the mean across all
#                               retained non-missing observations
#   .{pred}_gmc_partner         APIM grand-mean-centered partner predictor:
#                               partner's value relative to the mean across all
#                               retained non-missing observations
#
# A tibble: 72 × 14
   couple_id person_id gender efficacy total_mvpa .composition .composition_role
       <int>     <int> <fct>     <dbl>      <dbl> <fct>        <fct>            
 1         1         1 male      3.02       33.6  assumed_exc… assumed_exchange…
 2         1         2 female    0.891      12.7  assumed_exc… assumed_exchange…
 3         2         3 male      0.132      42.8  assumed_exc… assumed_exchange…
 4         2         4 female    2.37       44.5  assumed_exc… assumed_exchange…
 5         3         5 female    3.33       62.9  assumed_exc… assumed_exchange…
 6         3         6 male      1.62       32.3  assumed_exc… assumed_exchange…
 7         4         7 male      1.09       21.5  assumed_exc… assumed_exchange…
 8         4         8 female    0.755      51.9  assumed_exc… assumed_exchange…
 9         5         9 female    1.48        9.79 assumed_exc… assumed_exchange…
10         5        10 male      1.35       11.4  assumed_exc… assumed_exchange…
# ℹ 62 more rows
# ℹ 7 more variables: .is_exchangeable <dbl>, .member_contrast_arbitrary <dbl>,
#   .efficacy_gmc <dbl>, .efficacy_actor <dbl>, .efficacy_partner <dbl>,
#   .efficacy_gmc_actor <dbl>, .efficacy_gmc_partner <dbl>

Create the required format and validate the data

Looking at the relevant columns:

head(apim_exchangeable_data) |>
  dplyr::select(
    couple_id, person_id, .member_contrast_arbitrary, .efficacy_gmc,
    .efficacy_gmc_actor, .efficacy_gmc_partner, total_mvpa
  ) |>
  slide_table()
couple_id person_id .member_contrast_arbitrary .efficacy_gmc .efficacy_gmc_actor .efficacy_gmc_partner total_mvpa
1.00 1.00 -1.00 0.64 0.64 -1.49 33.61
1.00 2.00 1.00 -1.49 -1.49 0.64 12.71
2.00 3.00 -1.00 -2.24 -2.24 -0.01 42.83
2.00 4.00 1.00 -0.01 -0.01 -2.24 44.54
3.00 5.00 -1.00 0.96 0.96 -0.76 62.86
3.00 6.00 1.00 -0.76 -0.76 0.96 32.29

Workflow

  1. Define dyads, distinguishability, and research questions
  2. Prepare the data and validate the dyadic structure
  3. Describe and visualize measures and observed relationships
  4. Estimate the models
  5. Verify model adequacy, distinguishability, and robustness
  6. Visualize, interpret, and report results

Describe focal variables (pooled)

No wide-view of the data is needed for exchangeable dyads. We can describe the data directly to obtain pooled estimates:

apim_exchangeable_data |>
  dplyr::select(efficacy, total_mvpa) |>
  report::report_table() |>
  slide_table()
Variable n_Obs Mean SD Median MAD Min Max Skewness Kurtosis n_Missing
efficacy 72.00 2.38 0.99 2.38 1.06 0.13 4.92 0.09 -0.15 0.00
total_mvpa 72.00 31.21 22.40 27.55 18.67 3.43 126.35 1.89 5.34 0.00

Visualize focal variables

plot_pooled_histogram <- function(data, var) {
  var_label <- rlang::as_label(rlang::enquo(var))

  ggplot(
    data,
    aes(x = {{ var }}, y = after_stat(density))
  ) +
    geom_histogram(
      bins = 15, boundary = 0,
      fill = "steelblue", color = "white", alpha = 0.75
    ) +
    geom_density(
      bounds = c(0, Inf), color = "navy", linewidth = 1.1
    ) +
    see::theme_modern(base_size = 16)
}

Visualize focal variables

see::plots(
  plot_pooled_histogram(apim_exchangeable_data, efficacy),
  plot_pooled_histogram(apim_exchangeable_data, total_mvpa)
)

Observed correlations

For exchangeable dyads, ICCs summarize partner similarity:

library(wbCorr)

apim_exchangeable_correlations <- wbCorr(
  apim_exchangeable_data[,c('efficacy','total_mvpa')],
  cluster = apim_exchangeable_data$couple_id
)

summary(apim_exchangeable_correlations, 'wb')
$merged_wb
           efficacy total_mvpa
efficacy     [0.60]     0.44**
total_mvpa  0.56***     [0.79]

$note
[1] "***p < 0.001, **p < 0.01, *p < 0.05"

Diagonal ICCs: expected correlation between partners on each variable

Off-diagonals: top right = within-couple correlation; bottom left = between-couple correlation

Visualize partner similarity

partner_similarity_data <- apim_exchangeable_data |>
  dplyr::group_by(couple_id) |>
  dplyr::mutate(
    efficacy_partner = rev(efficacy),
    total_mvpa_partner = rev(total_mvpa)
  ) |>
  dplyr::ungroup()

see::plots(
  plot_partner_similarity(
    partner_similarity_data,
    efficacy, efficacy_partner, couple_id, "Efficacy",
    n_mahalanobis = 4
  ) +
    labs(x = "Focal member", y = "Partner"),
  plot_partner_similarity(
    partner_similarity_data,
    total_mvpa, total_mvpa_partner, couple_id, "Daily MVPA",
    n_mahalanobis = 4
  ) +
    labs(x = "Focal member", y = "Partner")
)

Visualize partner similarity

Each couple is shown twice—once in each orientation; labels mark the four largest dyad-level Mahalanobis distances.

Workflow

  1. Define dyads, distinguishability, and research questions
  2. Prepare the data and validate the dyadic structure
  3. Describe and visualize measures and observed relationships
  4. Estimate the models
  5. Verify model adequacy, distinguishability, and robustness
  6. Visualize, interpret, and report results

Exchangeable APIM

Conceptual exchangeable APIM with pooled actor and partner paths and correlated outcome residuals.

In MLM we constrain / pool by omitting terms

One single equation:

For each dyad \(i\), each member \(j\) has their own predictor \(X^{(\mathrm{A})}_{j,i}\) and their partner’s predictor \(X^{(\mathrm{P})}_{j,i}\):

\[ Y_{j,i} = b_{0} + \color{#1769AA}{a \times X^{(\mathrm{A})}_{j,i}} + \color{#7B3FA1}{p \times X^{(\mathrm{P})}_{j,i}} + \epsilon_{j,i} \]

- Actor effects - Partner effects

In long format, each equation applies to one member row of the dyad.

The tricky part: residuals need to remain correlated within dyads

  • The two member rows from the same dyad retain a joint residual structure

  • Both members now have the same residual variance (pooled)

  • Position 1 and 2 within the dyad are arbitrary

\[ \operatorname{Cov} \begin{pmatrix} \epsilon_{1i} \\ \epsilon_{2i} \end{pmatrix} = \boldsymbol{\Sigma}_{\epsilon} = \begin{bmatrix} \sigma_{\epsilon}^{2} & \rho_{\epsilon_1\epsilon_2}\sigma_{\epsilon}^{2} \\ \rho_{\epsilon_1\epsilon_2}\sigma_{\epsilon}^{2} & \sigma_{\epsilon}^{2} \end{bmatrix} \]

Fitting the exchangeable APIM in glmmTMB

apim_exchangeable_model <- glmmTMB(
  total_mvpa ~
    # Pooled intercept
    1 +

    # Pooled actor effect
    .efficacy_gmc_actor +

    # Pooled partner effect
    .efficacy_gmc_partner +

    #------ Within-dyad residual covariance (Sum/Deviation) ------

    # Residuals of partners might be similar (positively correlated)
    (1 | couple_id) +

    # Residuals of partners might move opposite (negatively correlated)
    (0 + .member_contrast_arbitrary | couple_id),

  dispformula = ~ 0,
  family = gaussian(),
  data = apim_exchangeable_data
)

summary(apim_exchangeable_model)

The mean-deviation parameterization

  • The DIM is equivalent to the APIM

  • The DIM represents member residuals using two uncorrelated components:

    • Mean block (1 | couple_id): \(r_{\mathrm{M}i}=(\epsilon_{1i}+\epsilon_{2i})/2\); both members move together

    • Deviation block (0 + .member_contrast_arbitrary | couple_id): \(r_{\mathrm{D}i}=(\epsilon_{1i}-\epsilon_{2i})/2\); members move in opposite directions

Using this parametrization allows us to represent random effects and later in ILD even random slopes with the correct constraints.

We can later backtransform to the familiar person-level structure.

Workflow

  1. Define dyads, distinguishability, and research questions
  2. Prepare the data and validate the dyadic structure
  3. Describe and visualize measures and observed relationships
  4. Estimate the models
  5. Verify model adequacy, distinguishability, and robustness
  6. Visualize, interpret, and report results

Verify model adequacy, distinguishability, and robustness

Same as before:

  1. Did estimation succeed?
  2. Does the model reproduce the important data patterns?
  3. Does a distinguishable model fit clearly better?
  4. Are conclusions robust or driven by few dyads or consequential modeling choices? (sensitivity analyses)

Workflow

  1. Define dyads, distinguishability, and research questions
  2. Prepare the data and validate the dyadic structure
  3. Describe and visualize measures and observed relationships
  4. Estimate the models
  5. Verify model adequacy, distinguishability, and robustness
  6. Visualize, interpret, and report results

Reporting results and answering the research questions

parameters::model_parameters(apim_exchangeable_model, effects = 'fixed') |>
  insight::print_md(footer = "")
Fixed Effects
Parameter Coefficient SE 95% CI z p
(Intercept) 31.21 2.89 (25.54, 36.88) 10.79 < .001
efficacy gmc actor 10.34 2.04 (6.35, 14.34) 5.07 < .001
efficacy gmc partner 3.17 2.04 (-0.83, 7.17) 1.55 0.120

In the parsimonious exchangeable model, higher self-efficacy was associated with more of one’s own MVPA, but not clearly with the partner’s MVPA.

Reporting results and answering the research questions

parameters::model_parameters(apim_exchangeable_model, effects = 'random') |>
  insight::print_md(footer = "")
Random Effects
Parameter Coefficient 95% CI
SD (Intercept: couple_id) 17.36 (13.78, 21.87)
SD (.member_contrast_arbitrary: couple_id) 6.48

Often it is preferable to rotate the covariance back to the familiar member-level covariance matrix:

  • Member residual variance: \(\operatorname{Var}(\epsilon_{1i})=\operatorname{Var}(\epsilon_{2i}) =\operatorname{Var}(r_{\mathrm{M}i})+\operatorname{Var}(r_{\mathrm{D}i})\)

  • Partner residual covariance: \(\operatorname{Cov}(\epsilon_{1i},\epsilon_{2i}) =\operatorname{Var}(r_{\mathrm{M}i})-\operatorname{Var}(r_{\mathrm{D}i})\)

Reporting results and answering the research questions

No need to do so by hand. Rotating back with dyadMLM:

dyadMLM::recover_exchangeable_covariance(apim_exchangeable_model)
Recovered exchangeable member-level covariance

Pair `pair_1`
Shared:     us(1 | couple_id)
Difference: us(0 + .member_contrast_arbitrary | couple_id)

Variance-covariance:
                       1       2      
1 member1: (Intercept) 343.216 259.269
2 member2: (Intercept) 259.269 343.216

Standard deviations and correlations:
                       1      2     
1 member1: (Intercept) 18.526 0.755 
2 member2: (Intercept) 0.755  18.526

Visualize results

parameters::model_parameters(apim_exchangeable_model, effects = 'fixed') |>
  plot(show_intercept = TRUE)

Optional: DIM and DSM

Worked examples are available in the package vignettes of dyadMLM.

Open the DIM vignette →

Open the DSM vignette →

Intensive longitudinal APIM

Intensive longitudinal APIM - Workflow

We can follow the same workflow. Additional possible ILD considerations:

  • Check compliance / missingness
  • Inspect and visualize paired trajectories and patterns
  • Separate within- and between-person variation
    • Inspect ICCs and multilevel reliability where applicable
    • Model dyads on multiple levels
  • Check and account for residual temporal dependence

Synthesized from Bolger & Laurenceau (2013), Revol et al. (2024), McNeish & Hamaker (2020), and del Rosario & West (2025).

Workflow

  1. Define dyads, distinguishability, and research questions
  2. Prepare the data and validate the dyadic structure
  3. Describe and visualize measures and observed relationships
  4. Estimate the models
  5. Verify model adequacy, distinguishability, and robustness
  6. Visualize, interpret, and report results

The Dataset

Same as before:

  • 36 physically inactive romantic couples
  • Both partners intended to become more physically active
  • Smartphone-based planning interventions and JITAIs
  • Daily diaries over 55 days

Not aggregated anymore

ILD Research Question

RQ: How are previous-day deviations and stable between-person differences in perceived behavioral control over next-day physical activity associated with one’s own and one’s partner’s current-day MVPA?

Measurement - predictor

Perceived behavioral control (PBC) over next-day physical activity

Rough English translation of the original German item
“Being physically active tomorrow would/will be for me …”

Adapted from Ajzen (1991)

0 = impossible—crucial prerequisites are not met
3 = possible with some effort—several prerequisites are not met
6 = easily possible—all prerequisites are met

  • Lagged predictor: perceived behavioral control on day \(t-1\), MVPA on day \(t\)

Measurement - outcome

Device-based MVPA

Accelerometer-derived measure
Daily minutes of moderate-to-vigorous physical activity

  • Days with less than 10 hours of awake wear time \(\rightarrow\) NA
  • Analyze \(\log(\text{MVPA})\) to reduce right skew

Exponentiated coefficients describe multiplicative differences in MVPA.

Workflow

  1. Define dyads, distinguishability, and research questions
  2. Prepare the data and validate the dyadic structure
  3. Describe and visualize measures and observed relationships
  4. Estimate the models
  5. Verify model adequacy, distinguishability, and robustness
  6. Visualize, interpret, and report results

Load the daily measures

We set MVPA to NA on days with less than 10 hours of weartime, center time at the middle of the study in full-study units, and select the needed variables.

raw_ild_dyad_data <- readRDS("dyadic-data.rds") |>
  dplyr::mutate(
    device_based_mvpa = dplyr::if_else(
      awake_wear_minutes >= 600,
      device_based_mvpa,
      NA
    ),
    log_device_based_mvpa = log(device_based_mvpa),
    diaryday_c = (diaryday - 27) / 54
  )|>
  dplyr::select(
    couple_id, person_id, diaryday, diaryday_c, gender,
    pbc = perceived_behavioral_control,
    device_based_mvpa, log_device_based_mvpa, awake_wear_minutes
  ) |>
  dplyr::arrange(couple_id, diaryday, person_id)

slide_table(head(raw_ild_dyad_data))

Load the daily measures

couple_id person_id diaryday diaryday_c gender pbc device_based_mvpa log_device_based_mvpa awake_wear_minutes
1.00 1.00 0.00 -0.50 male 6.00 156.25 5.05 876.00
1.00 2.00 0.00 -0.50 female 2.00 NA NA 585.00
1.00 1.00 1.00 -0.48 male 6.00 134.50 4.90 761.00
1.00 2.00 1.00 -0.48 female 3.00 152.00 5.02 778.00
1.00 1.00 2.00 -0.46 male 4.00 99.00 4.60 841.00
1.00 2.00 2.00 -0.46 female 3.00 NA NA 444.00

Create the required format and validate the data

For ILD we decompose the predictor(s):

  • Between-person component: Person mean centered on the mean of person means (stable)
    • \(\bar X_{ri} - \bar X\)
  • Within-person component: Daily deviations from the person mean
    • \(X_{ri,t} - \bar X_{ri}\)

In this example we then lag the within-person component (\(X_{ri,t-1}\))

Create the required format and validate the data Manual code →

The dyadMLM package can handle centering alongside the construction and validation of the dyadic dataset:

ild_apim_data <- dyadMLM::prepare_dyad_data(
  data = raw_ild_dyad_data,
  dyad = couple_id,
  member = person_id,
  role = gender,
  time = diaryday,
  predictors = pbc,
  lag1_predictors = pbc,
  model_types = "apim",
  temporal_decomposition = "2l",
  include_arbitrary_member_contrast = TRUE,
  seed = 123
)

ild_apim_data <- ild_apim_data |>
  dplyr::mutate(
    diaryday_f = factor(diaryday, levels = 0:54)
  )

print(ild_apim_data)

Create the required format and validate the data Manual code →

# dyadMLM data
# Rows: 3960 | Dyads: 36 | Intensive longitudinal: yes
# Structure:
#   dyad = couple_id, member = person_id, role = gender, time = diaryday
#
# Dyad compositions:
# female_x_male distinguishable 36 dyads
#
# Added columns:
#   .composition                inferred dyad composition
#   .composition_role           composition-specific member role
#   .is_{role}                  composition-role indicator columns
#   .member_contrast_arbitrary  composition-specific member contrasts coded
#                               -1/+1 in arbitrary direction for
#                               exchangeability-constrained random effects.
#                               Values are 0 for other compositions
#   .{pred}_lag1                lag-1 raw predictor values
#   .{pred}_cwp                 within-person predictor: momentary deviations
#                               from each person's usual level
#   .{pred}_cwp_lag1            lag-1 within-person predictor: momentary
#                               deviations from each person's usual level
#   .{pred}_cbp                 between-person predictor: stable differences
#                               from the average person's usual level
#   .{pred}_actor               APIM actor predictor: actor's original
#                               predictor values
#   .{pred}_actor_lag1          lag-1 APIM actor predictor: actor's original
#                               predictor values
#   .{pred}_partner             APIM partner predictor: partner's original
#                               predictor values
#   .{pred}_partner_lag1        lag-1 APIM partner predictor: partner's
#                               original predictor values
#   .{pred}_cwp_actor           APIM within-person actor predictor: actor's
#                               momentary deviations from their usual level
#   .{pred}_cwp_actor_lag1      lag-1 APIM within-person actor predictor:
#                               actor's momentary deviations from their usual
#                               level
#   .{pred}_cwp_partner         APIM within-person partner predictor: partner's
#                               momentary deviations from their usual level
#   .{pred}_cwp_partner_lag1    lag-1 APIM within-person partner predictor:
#                               partner's momentary deviations from their usual
#                               level
#   .{pred}_cbp_actor           APIM between-person actor predictor: actor's
#                               stable difference from the average person's
#                               usual level
#   .{pred}_cbp_partner         APIM between-person partner predictor:
#                               partner's stable difference from the average
#                               person's usual level
#
# A tibble: 3,960 × 29
   couple_id person_id diaryday diaryday_c gender   pbc device_based_mvpa
       <int>     <int>    <dbl>      <dbl> <fct>  <dbl>             <dbl>
 1         1         1        0     -0.5   male       6             156. 
 2         1         2        0     -0.5   female     2              NA  
 3         1         1        1     -0.481 male       6             134. 
 4         1         2        1     -0.481 female     3             152  
 5         1         1        2     -0.463 male       4              99  
 6         1         2        2     -0.463 female     3              NA  
 7         1         1        3     -0.444 male       5             110  
 8         1         2        3     -0.444 female     1              NA  
 9         1         1        4     -0.426 male       3              71.2
10         1         2        4     -0.426 female     0             130. 
# ℹ 3,950 more rows
# ℹ 22 more variables: log_device_based_mvpa <dbl>, awake_wear_minutes <dbl>,
#   .composition <fct>, .composition_role <fct>, .is_female <dbl>,
#   .is_male <dbl>, .member_contrast_arbitrary <dbl>, .pbc_cwp <dbl>,
#   .pbc_cbp <dbl>, .pbc_lag1 <dbl>, .pbc_cwp_lag1 <dbl>, .pbc_actor <dbl>,
#   .pbc_partner <dbl>, .pbc_cwp_actor <dbl>, .pbc_cwp_partner <dbl>,
#   .pbc_cbp_actor <dbl>, .pbc_cbp_partner <dbl>, .pbc_actor_lag1 <dbl>, …

Workflow

  1. Define dyads, distinguishability, and research questions
  2. Prepare the data and validate the dyadic structure
  3. Describe and visualize measures and observed relationships
  4. Estimate the models
  5. Verify model adequacy, distinguishability, and robustness
  6. Visualize, interpret, and report results

Report measures

Among other things you may check and report:

  • Missingness and general summary statistics
  • ICCs
  • Within- and between- person correlations (e.g., with wbCorr)
  • Visualize outcome trajectories over time

Plot paired raw MVPA trajectories

raw_ild_dyad_data |>
  dplyr::filter(couple_id %in% c(2, 7, 17, 26)) |>
  ggplot(aes(diaryday, device_based_mvpa, color = gender,
             group = person_id)) +
  geom_line(linewidth = 0.65, na.rm = TRUE) +
  geom_point(size = 1.1, na.rm = TRUE) +
  facet_wrap(~ couple_id, ncol = 2) +
  scale_color_manual(values = c(female = "#004D40", male = "#00A6B2")) +
  scale_y_continuous(breaks = seq(0, 300, by = 100)) +
  labs(x = "Diary day", y = "MVPA (minutes)", color = NULL) +
  see::theme_modern(base_size = 16) +
  theme(legend.position = "top")

Plot paired raw MVPA trajectories

Workflow

  1. Define dyads, distinguishability, and research questions
  2. Prepare the data and validate the dyadic structure
  3. Describe and visualize measures and observed relationships
  4. Estimate the models
  5. Verify model adequacy, distinguishability, and robustness
  6. Visualize, interpret, and report results

Fitting the ILD APIM: Two member-specific equations

For dyad \(i\) at occasion \(t\), superscripts \(\mathrm{W}\) and \(\mathrm{B}\) denote within-person and between-person components.

\[ \begin{aligned} Y_{\mathrm{M},i,t} ={}& b_{0,\mathrm{M}} + \color{#1769AA}{ a^{\mathrm{W}}_{\mathrm{M}}X^{(\mathrm{A,W})}_{\mathrm{M},i,t-1} + a^{\mathrm{B}}_{\mathrm{M}}X^{(\mathrm{A,B})}_{\mathrm{M},i} } + \color{#7B3FA1}{ p^{\mathrm{W}}_{\mathrm{M}}X^{(\mathrm{P,W})}_{\mathrm{M},i,t-1} + p^{\mathrm{B}}_{\mathrm{M}}X^{(\mathrm{P,B})}_{\mathrm{M},i} } + u_{0,\mathrm{M}i} + \epsilon_{\mathrm{M},i,t} \\[0.6em] Y_{\mathrm{F},i,t} ={}& b_{0,\mathrm{F}} + \color{#1769AA}{ a^{\mathrm{W}}_{\mathrm{F}}X^{(\mathrm{A,W})}_{\mathrm{F},i,t-1} + a^{\mathrm{B}}_{\mathrm{F}}X^{(\mathrm{A,B})}_{\mathrm{F},i} } + \color{#7B3FA1}{ p^{\mathrm{W}}_{\mathrm{F}}X^{(\mathrm{P,W})}_{\mathrm{F},i,t-1} + p^{\mathrm{B}}_{\mathrm{F}}X^{(\mathrm{P,B})}_{\mathrm{F},i} } + u_{0,\mathrm{F}i} + \epsilon_{\mathrm{F},i,t}. \end{aligned} \]

Actor effects · Partner effects

  • \(u_{0,ri}\): stable member-within-dyad deviation
  • \(\epsilon_{r,it}\): occasion-specific residual
  • Optional random slope: add \(u_{\mathrm{A},ri}X^{(\mathrm{A,W})}_{r,i,t-1}\)
  • Same stacking approach: role indicators select the male or female equation

Repeated measures separate two covariance levels

Stable across occasions

couple_id

  • Role-specific random intercepts
  • Optional role-specific random slopes
  • Partner covariance in stable levels or slopes

At each single occasion

couple_id:diaryday

  • Partner-specific Gaussian residual variance
  • Same-occasion residual correlation
  • Independent across occasions unless modeled otherwise

Fitting the distinguishable ILD APIM with glmmTMB

ild_apim_distinguishable_model <- glmmTMB::glmmTMB(
  log_device_based_mvpa ~

    # Role-specific fixed intercepts
    0 + .is_female + .is_male +

    # Role-specific fixed time-slope
    .is_female:diaryday_c + .is_male:diaryday_c +

    # --------------- WITHIN PERSON APIM ---------------
    # Role-specific fixed actor- and partner effects

    .is_female:.pbc_cwp_actor_lag1 +
    .is_male:.pbc_cwp_actor_lag1 +

    .is_female:.pbc_cwp_partner_lag1 +
    .is_male:.pbc_cwp_partner_lag1 +

    # --------------- BETWEEN PERSON APIM ---------------
    # Role-specific fixed actor-and partner effects

    .is_female:.pbc_cbp_actor +
    .is_male:.pbc_cbp_actor +

    .is_female:.pbc_cbp_partner +
    .is_male:.pbc_cbp_partner +

    # Stable dyad/member random intercepts (correlated)
    (0 + .is_female + .is_male | couple_id) +

    # Same-occasion Gaussian residual covariance
    (0 + .is_female + .is_male | couple_id:diaryday),
  dispformula = ~ 0,
  family = gaussian(),
  data = ild_apim_data
)

summary(ild_apim_distinguishable_model)

Workflow

  1. Define dyads, distinguishability, and research questions
  2. Prepare the data and validate the dyadic structure
  3. Describe and visualize measures and observed relationships
  4. Estimate the models
  5. Verify model adequacy, distinguishability, and robustness
  6. Visualize, interpret, and report results

Testing for residual autocorrelation

ild_apim_no_ar_dharma <- simulate_ild_dharma(
  model = ild_apim_distinguishable_model,
  dyad = ild_apim_data$couple_id,
  member = ild_apim_data$person_id,
  time = ild_apim_data$diaryday,
  n = 1000,
  seed = 123
)

diagnostic <- test_ild_lag1(
  ild_apim_no_ar_dharma,
  plot = TRUE
)

The helper compares the observed consecutive-day correlation with correlations simulated from the fitted model.

Testing for residual autocorrelation

Add role-specific residual AR(1) processes

  • Separate AR(1) series for the female and male partner
  • Role-specific residual variability and persistence \(\rho\)
  • Same-day partner covariance retained separately
ild_apim_distinguishable_ar1_model <- update(
  ild_apim_distinguishable_model,
  . ~ . +
    ar1(0 + .is_female:diaryday_f | couple_id) +
    ar1(0 + .is_male:diaryday_f | couple_id)
)
glmmTMB::VarCorr(ild_apim_distinguishable_ar1_model)

The two AR(1) terms represent independent member-specific persistence. The dyad-day SDs and correlation describe the remaining same-day variation.

Add role-specific residual AR(1) processes


Conditional model:
 Groups             Name                   Std.Dev. Corr        
 couple_id          .is_female             0.23723              
                    .is_male               0.25925  0.343       
 couple_id.diaryday .is_female             0.35352              
                    .is_male               0.37559  0.388       
 couple_id.1        .is_female:diaryday_f1 0.23017  0.878 (ar1) 
 couple_id.2        .is_male:diaryday_f1   0.22912  0.814 (ar1) 

Simulate residuals from the AR(1) model

With almost 2,800 observations, DHARMa tests are very sensitive. Visual inspection becomes more meaningful.

Does the fitted AR(1) reproduce lag-1 dependence?

ild_apim_ar1_lag1_test <- test_ild_lag1(
  ild_apim_dharma_residuals,
  plot = TRUE
)

The correlation need not become zero. It should be typical under simulations from the fitted AR(1) model.

Fit the exchangeable model with pooled AR(1)

ild_apim_exchangeable_ar1_model <- glmmTMB::glmmTMB(
  log_device_based_mvpa ~
    1 +
    diaryday_c +
    .pbc_cwp_actor_lag1 +
    .pbc_cwp_partner_lag1 +
    .pbc_cbp_actor +
    .pbc_cbp_partner +

    # Couple-level variance-covariance structure
    (1 | couple_id) +
    (0 + .member_contrast_arbitrary | couple_id) +

    # Occasion-level residual variance-covariance structure
    (1 | couple_id:diaryday) +
    (0 + .member_contrast_arbitrary | couple_id:diaryday) +
    ar1(0 + diaryday_f | couple_id:person_id),
  dispformula = ~ 0,
  family = gaussian(),
  data = ild_apim_data
)

summary(ild_apim_exchangeable_ar1_model)

Each member retains a separate series, but residual variability and persistence \(\rho\) are pooled across members.

Fit the exchangeable model with pooled AR(1)

 Family: gaussian  ( identity )
Formula:          
log_device_based_mvpa ~ 1 + diaryday_c + .pbc_cwp_actor_lag1 +  
    .pbc_cwp_partner_lag1 + .pbc_cbp_actor + .pbc_cbp_partner +  
    (1 | couple_id) + (0 + .member_contrast_arbitrary | couple_id) +  
    (1 | couple_id:diaryday) + (0 + .member_contrast_arbitrary |  
    couple_id:diaryday) + ar1(0 + diaryday_f | couple_id:person_id)
Dispersion:                             ~0
Data: ild_apim_data

      AIC       BIC    logLik -2*log(L)  df.resid 
   3052.4    3123.8   -1514.2    3028.4      2821 

Random effects:

Conditional model:
 Groups               Name                       Variance Std.Dev. Corr      
 couple_id            (Intercept)                0.04350  0.2086             
 couple_id.1          .member_contrast_arbitrary 0.03149  0.1774             
 couple_id.diaryday   (Intercept)                0.09234  0.3039             
 couple_id.diaryday.1 .member_contrast_arbitrary 0.04146  0.2036             
 couple_id.person_id  diaryday_f1                0.05190  0.2278   0.85 (ar1)
Number of obs: 2833, groups:  
couple_id, 36; couple_id:diaryday, 1653; couple_id:person_id, 72

Conditional model:
                       Estimate Std. Error z value Pr(>|z|)    
(Intercept)            4.680245   0.037876  123.57  < 2e-16 ***
diaryday_c            -0.035743   0.046064   -0.78  0.43778    
.pbc_cwp_actor_lag1    0.023424   0.005196    4.51 6.55e-06 ***
.pbc_cwp_partner_lag1  0.016972   0.005174    3.28  0.00104 ** 
.pbc_cbp_actor         0.086201   0.040897    2.11  0.03505 *  
.pbc_cbp_partner       0.003055   0.040779    0.07  0.94029    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Backtransform to member-level variance-covariance structure

dyadMLM::recover_exchangeable_covariance(ild_apim_exchangeable_ar1_model)
Recovered exchangeable member-level covariances (2 block pairs)

Pair `pair_1`
Shared:     us(1 | couple_id)
Difference: us(0 + .member_contrast_arbitrary | couple_id)

Variance-covariance:
                       1     2    
1 member1: (Intercept) 0.075 0.012
2 member2: (Intercept) 0.012 0.075

Standard deviations and correlations:
                       1     2    
1 member1: (Intercept) 0.274 0.160
2 member2: (Intercept) 0.160 0.274

Pair `pair_2`
Shared:     us(1 | couple_id:diaryday)
Difference: us(0 + .member_contrast_arbitrary | couple_id:diaryday)

Variance-covariance:
                       1     2    
1 member1: (Intercept) 0.134 0.051
2 member2: (Intercept) 0.051 0.134

Standard deviations and correlations:
                       1     2    
1 member1: (Intercept) 0.366 0.380
2 member2: (Intercept) 0.380 0.366

Compare distinguishable and exchangeable models

This is a global test, partial constraints can be informative too!

dyadMLM::compare_nested_models(
  ild_apim_distinguishable_ar1_model,
  ild_apim_exchangeable_ar1_model
)
Likelihood-ratio test for nested models fitted to equivalent data
Assumes mathematical nesting and an appropriate chi-squared reference distribution.

                                   Df    AIC    BIC  logLik deviance  Chisq
ild_apim_exchangeable_ar1_model    12 3052.4 3123.8 -1514.2   3028.4       
ild_apim_distinguishable_ar1_model 22 3040.9 3171.8 -1498.5   2996.9 31.487
                                   Chi Df Pr(>Chisq)    
ild_apim_exchangeable_ar1_model                         
ild_apim_distinguishable_ar1_model     10  0.0004873 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Conclusion (5% level): The likelihood-ratio test provides evidence that `ild_apim_distinguishable_ar1_model` fits better than `ild_apim_exchangeable_ar1_model` (p < 0.001).

Workflow

  1. Define dyads, distinguishability, and research questions
  2. Prepare the data and validate the dyadic structure
  3. Describe and visualize measures and observed relationships
  4. Estimate the models
  5. Verify model adequacy, distinguishability, and robustness
  6. Visualize, interpret, and report results

Report the retained distinguishable model

We exponentiate estimates from the log-transformed outcome to obtain geometric mean ratios (GMRs; representing multiplicative differences).

parameters::model_parameters(
  ild_apim_distinguishable_ar1_model,
  effects = 'fixed',
  exponentiate = TRUE
  ) |>
  insight::print_md(footer = "")

Report the retained distinguishable model

Fixed Effects
Parameter Coefficient SE 95% CI z p
is female 121.69 5.84 (110.77, 133.69) 100.11 < .001
is male 98.30 4.96 (89.05, 108.52) 90.94 < .001
is female × diaryday c 0.95 0.06 (0.84, 1.08) -0.83 0.405
is male × diaryday c 0.98 0.06 (0.87, 1.10) -0.38 0.705
is female × pbc cwp actor lag1 1.01 7.50e-03 (1.00, 1.03) 1.48 0.139
is male × pbc cwp actor lag1 1.04 7.88e-03 (1.02, 1.05) 4.55 < .001
is female × pbc cwp partner lag1 1.01 7.15e-03 (1.00, 1.02) 1.44 0.149
is male × pbc cwp partner lag1 1.03 8.16e-03 (1.01, 1.04) 3.15 0.002
is female × pbc cbp actor 0.98 0.06 (0.87, 1.11) -0.29 0.769
is male × pbc cbp actor 1.11 0.06 (1.00, 1.24) 1.88 0.061
is female × pbc cbp partner 1.06 0.06 (0.96, 1.18) 1.19 0.233
is male × pbc cbp partner 1.01 0.07 (0.89, 1.15) 0.21 0.837

Report the retained distinguishable model

see::plots(
  parameters::model_parameters(
    ild_apim_distinguishable_ar1_model,
    effects = "fixed",
    exponentiate = TRUE,
    keep = "_cwp_"
  ) |> plot(),
  parameters::model_parameters(
    ild_apim_distinguishable_ar1_model,
    effects = "fixed",
    exponentiate = TRUE,
    keep = "_cbp_"
  ) |> plot(),
  n_columns = 2,
  tags = c("Within person", "Between persons")
) &
  ggplot2::labs(x = "GMR")

Report the retained distinguishable model

Report the retained distinguishable model

Within person: Men’s MVPA was 3.5% higher when their own previous-day PBC was one point above usual, and 2.5% higher when their partner’s was.

Between persons: There was no clear evidence of actor or partner associations.

General reporting practices for ILD APIM studies

Many studies lack complete reporting

Bar chart of reporting practices in 73 longitudinal APIM studies: 17.8 percent reported a power analysis, 64.4 percent documented model constraints, and 75.3 percent reported a missing-data strategy.

Systematic review of 73 longitudinal APIM studies published from 2008–2025 (Mohammadzadeh Yazd et al., 2026).

Make every dyadic analysis decision visible

  1. Preregister actor and partner hypotheses and key analysis decisions.
  2. Explain missing data: describe missingness and the handling strategy.
  3. Specify the full model: roles and distinguishability, actor and partner paths, constraints, and covariance structure.
  4. Report reliability where applicable: within- and between-level omega for multilevel item scales, e.g., multilevelTools::omegaSEM() (Geldhof et al., 2014; Wiley, 2025).

Mohammadzadeh Yazd et al. (2026)

Exercises in R

Run this in the console to ensure all relevant packages are installed and up to date:

source("00_setup.R")

References

Ajzen, I. (1991). The theory of planned behavior. Organizational Behavior and Human Decision Processes, 50(2), 179–211. https://doi.org/10.1016/0749-5978(91)90020-T
Amireault, S., & Godin, G. (2015). The godin–shephard leisure-time physical activity questionnaire: Validity evidence supporting its use for classifying healthy adults into active and insufficiently active categories. Perceptual and Motor Skills, 120(2), 604–622. https://doi.org/10.2466/03.27.PMS.120v19x7
Asparouhov, T., Hamaker, E. L., & Muthén, B. (2018). Dynamic structural equation models. Structural Equation Modeling: A Multidisciplinary Journal, 25(3), 359–388. https://doi.org/10.1080/10705511.2017.1406803
Asparouhov, T., & Muthén, B. (2020). Comparison of models for the analysis of intensive longitudinal data. Structural Equation Modeling: A Multidisciplinary Journal, 27(2), 275–297. https://doi.org/10.1080/10705511.2019.1626733
Bolger, N., & Laurenceau, J.-P. (2013). Intensive longitudinal methods: An introduction to diary and experience sampling research. Guilford Press. https://www.guilford.com/books/Intensive-Longitudinal-Methods/Bolger-Laurenceau/9781462506781
Brooks, M. E., Kristensen, K., van Benthem, K. J., Magnusson, A., Berg, C. W., Nielsen, A., Skaug, H. J., Mächler, M., & Bolker, B. M. (2017). glmmTMB balances speed and flexibility among packages for zero-inflated generalized linear mixed modeling. The R Journal, 9(2), 378–400. https://doi.org/10.32614/RJ-2017-066
Bürkner, P.-C. (n.d.). Autocorrelation structures. Retrieved July 23, 2026, from https://paulbuerkner.com/brms/reference/autocor-terms.html
del Rosario, K. S., & West, T. V. (2025). A Practical Guide to Specifying Random Effects in Longitudinal Dyadic Multilevel Modeling. Advances in Methods and Practices in Psychological Science, 8(3), 25152459251351286. https://doi.org/10.1177/25152459251351286
Driver, C. C., Oud, J. H. L., & Voelkle, M. C. (2017). Continuous time structural equation modeling with R package ctsem. Journal of Statistical Software, 77(5), 1–35. https://doi.org/10.18637/jss.v077.i05
Geldhof, G. J., Preacher, K. J., & Zyphur, M. J. (2014). Reliability estimation in a multilevel confirmatory factor analysis framework. Psychological Methods, 19(1), 72–91. https://doi.org/10.1037/a0032138
Gistelinck, F., Loeys, T., Decuyper, M., & Dewitte, M. (2018). Indistinguishability tests in the actor–partner interdependence model. British Journal of Mathematical and Statistical Psychology, 71(3), 472–498. https://doi.org/10.1111/bmsp.12129
Gistelinck, F., Loeys, T., & Flamant, N. (2021). Multilevel autoregressive models when the number of time points is small. Structural Equation Modeling: A Multidisciplinary Journal, 28(1), 15–27. https://doi.org/10.1080/10705511.2020.1753517
Grund, S., Lüdtke, O., & Robitzsch, A. (2018). Multiple imputation of missing data for multilevel models: Simulations and recommendations. Organizational Research Methods, 21(1), 111–149. https://doi.org/10.1177/1094428117703686
Hartig, F. (2026). DHARMa: Residual diagnostics for hierarchical (multi-level / mixed) regression models. https://doi.org/10.32614/CRAN.package.DHARMa
Hughes, R. A., Heron, J., Sterne, J. A. C., & Tilling, K. (2019). Accounting for missing data in statistical analyses: Multiple imputation is not always the answer. International Journal of Epidemiology, 48(4), 1294–1304. https://doi.org/10.1093/ije/dyz032
IBM Corp. (n.d.). Covariance structure list (MIXED command). Retrieved July 23, 2026, from https://www.ibm.com/docs/en/spss-statistics/32.0.0?topic=mixed-covariance-structure-list-command
Kenny, D. A., Kashy, D. A., & Cook, W. L. (2006). Dyadic data analysis. Guilford Press.
Koslowski, K., Münch, F., Koch, T., & Holtmann, J. (2025). Mlts: Multilevel latent time series models with R and Stan. https://doi.org/10.32614/CRAN.package.mlts
Kristensen, K., & McGillycuddy, M. (2023, October 14). Covariance structures with glmmTMB. https://glmmtmb.github.io/glmmTMB/articles/covstruct.html
lavaan project. (n.d.). Multilevel SEM. Retrieved July 23, 2026, from https://lavaan.ugent.be/tutorial/multilevel.html
Ledermann, T., & Kenny, D. A. (2015). A toolbox with programs to restructure and describe dyadic data. Journal of Social and Personal Relationships, 32(8), 997–1011. https://doi.org/10.1177/0265407514555273
Ledermann, T., & Kenny, D. A. (2017). Analyzing dyadic data with multilevel modeling versus structural equation modeling: A tale of two methods. Journal of Family Psychology, 31(4), 442–452. https://doi.org/10.1037/fam0000290
Ledermann, T., & Macho, S. (2009). Mediation in dyadic data at the level of the dyads: A structural equation modeling approach. Journal of Family Psychology, 23(5), 661–670. https://doi.org/10.1037/a0016197
McNeish, D., & Hamaker, E. L. (2020). A primer on two-level dynamic structural equation models for intensive longitudinal data in Mplus. Psychological Methods, 25(5), 610–635. https://doi.org/10.1037/met0000250
Merkle, E. C., Rosseel, Y., & Goodrich, B. (n.d.). Two-level SEM. Retrieved July 23, 2026, from https://blavaan.org/articles/multilevel.html
Mohammadzadeh Yazd, F., Foong, H. F., Ibrahim, R., & Kunasekaran, P. (2026). Beyond the individual: A 25-year systematic review of the actor–partner interdependence model in longitudinal dyadic research across disciplines and contexts. Journal of Social and Personal Relationships, 02654075261443593. https://doi.org/10.1177/02654075261443593
Mulder, J. D., & Hamaker, E. L. (2021). Three extensions of the random intercept cross-lagged panel model. Structural Equation Modeling: A Multidisciplinary Journal, 28(4), 638–648. https://doi.org/10.1080/10705511.2020.1784738
Nestler, S., Humberg, S., & Schönbrodt, F. D. (2019). Response surface analysis with multilevel data: Illustration for the case of congruence hypotheses. Psychological Methods, 24(3), 291–308. https://doi.org/10.1037/met0000199
Revol, J., Carlier, C., Lafit, G., Verhees, M., Sels, L., & Ceulemans, E. (2024). Preprocessing experience-sampling-method data: A step-by-step framework, tutorial website, R package, and reporting templates. Advances in Methods and Practices in Psychological Science, 7(4), 25152459241256609. https://doi.org/10.1177/25152459241256609
SAS Institute Inc. (n.d.). SAS/STAT 15.4 user’s guide: The MIXED procedure. SAS Institute Inc. Retrieved July 23, 2026, from https://go.documentation.sas.com/api/collections/pgmsascdc/9.4_3.5/docsets/statug/content/mixed.pdf
Schönbrodt, F. D., Humberg, S., & Nestler, S. (2018). Testing similarity effects with dyadic response surface analysis. European Journal of Personality, 32(6), 627–641. https://doi.org/10.1002/per.2169
Sniehotta, F. F., Scholz, U., & Schwarzer, R. (2005). Bridging the intention–behaviour gap: Planning, self-efficacy, and action control in the adoption and maintenance of physical exercise. Psychology & Health, 20(2), 143–160. https://doi.org/10.1080/08870440512331317670
Stas, L., Kenny, D. A., Mayer, A., & Loeys, T. (2018). Giving dyadic data analysis away: A user-friendly app for actor–partner interdependence models. Personal Relationships, 25(1), 103–119. https://doi.org/10.1111/pere.12230
Wiley, J. F. (2025). multilevelTools: Multilevel and mixed effects model diagnostics and effect sizes. https://doi.org/10.32614/CRAN.package.multilevelTools

Appendix

Create the cross-sectional APIM variables manually ← Data preparation

  • Explicit matching by couple and role
  • Missing partner \(\rightarrow\) NA
apim_manual_data <- raw_dyad_data |>
  dplyr::mutate(
    .is_female = as.numeric(gender == "female"),
    .is_male = as.numeric(gender == "male"),
    .efficacy_gmc = efficacy - mean(efficacy, na.rm = TRUE),
    .efficacy_gmc_actor = .efficacy_gmc
  )

partner_values <- apim_manual_data |>
  dplyr::transmute(
    couple_id,
    gender = dplyr::recode(gender, female = "male", male = "female"),
    .efficacy_gmc_partner = .efficacy_gmc
  )

apim_manual_data <- apim_manual_data |>
  dplyr::left_join(
    partner_values,
    by = c("couple_id", "gender"),
    relationship = "one-to-one"
  )

Decompose and lag perceived behavioral control manually ← Data preparation

pbc_components <- raw_ild_dyad_data |>
  dplyr::arrange(couple_id, person_id, diaryday) |>
  dplyr::group_by(couple_id, person_id) |>
  dplyr::mutate(
    .pbc_person_mean = mean(pbc, na.rm = TRUE),
    .pbc_cwp = pbc - .pbc_person_mean,
    .pbc_cwp_lag1 = dplyr::if_else(
      diaryday == dplyr::lag(diaryday) + 1,
      dplyr::lag(.pbc_cwp),
      NA_real_
    )
  ) |>
  dplyr::ungroup() |>
  dplyr::mutate(
    .pbc_cbp = .pbc_person_mean -
      mean(
        .pbc_person_mean[
          !duplicated(paste(couple_id, person_id))
        ],
        na.rm = TRUE
      )
  )

Match actor and partner components manually ← Data preparation

partner_components <- pbc_components |>
  dplyr::transmute(
    couple_id,
    diaryday,
    gender = dplyr::recode(gender, female = "male", male = "female"),
    .pbc_cwp_partner = .pbc_cwp,
    .pbc_cwp_partner_lag1 = .pbc_cwp_lag1,
    .pbc_cbp_partner = .pbc_cbp
  )

ild_manual_data <- pbc_components |>
  dplyr::rename(
    .pbc_cwp_actor = .pbc_cwp,
    .pbc_cwp_actor_lag1 = .pbc_cwp_lag1,
    .pbc_cbp_actor = .pbc_cbp
  ) |>
  dplyr::left_join(
    partner_components,
    by = c("couple_id", "diaryday", "gender"),
    relationship = "one-to-one"
  )
  • Explicit day-role matching: independent of row order
Universität Zürich Charité – Universitätsmedizin Berlin
Summer School 2026