Package {ctreeMI}


Title: Conditional Inference Trees with Stacked Multiple Imputation
Version: 1.0.0
Description: Implements the stacked-imputation workflow for conditional inference trees ('ctree') described in Sherlock et al. (2026) <doi:10.1080/00273171.2026.2661244>. When data contain missing values, multiply imputed datasets (e.g., from 'mice') are stacked vertically and a single 'ctree' is fit on the combined data. To correct for the artificially inflated sample size introduced by stacking, every node-level test statistic is divided by the number of imputations M, the node-level p-values are recomputed from the chi-squared reference distribution 'ctree' uses (including its multiplicity adjustment across candidate splitting variables), and the tree is compressed bottom-up (the Stack/M correction). Degrees of freedom are derived for each node and each candidate variable, so univariate, bivariate and higher-dimensional outcomes are all handled, as are unordered factor predictors, whose degrees of freedom depend on how many levels remain in a node. The result is a conservative but interpretable single tree that incorporates imputation uncertainty without requiring pooling of structurally different trees. Also exports stack_imputations(), rescale_statistic(), prune_stackM(), node_table() and report_ctreeMI() as standalone utilities. The underlying 'ctree' algorithm is provided by 'partykit' (Hothorn & Zeileis, 2015; Hothorn, Hornik & Zeileis, 2006 <doi:10.1198/106186006X133933>).
License: GPL (≥ 3)
Encoding: UTF-8
RoxygenNote: 7.3.3
Imports: partykit (≥ 1.2-0), mice (≥ 3.0.0), stats, methods
Suggests: testthat (≥ 3.0.0)
Config/testthat/edition: 3
Depends: R (≥ 4.0.0)
URL: https://github.com/Phillip-Sherlock/ctreeMI
BugReports: https://github.com/Phillip-Sherlock/ctreeMI/issues
NeedsCompilation: no
Packaged: 2026-07-27 01:51:55 UTC; coe-phillip.sherlock
Author: Phillip Sherlock ORCID iD [aut, cre]
Maintainer: Phillip Sherlock <phillip.sherlock@ufl.edu>
Repository: CRAN
Date/Publication: 2026-07-27 13:30:02 UTC

ctreeMI: Conditional Inference Trees with Multiple Imputation

Description

'ctreeMI' implements the **stacked-imputation / Stack ? M** workflow for conditional inference trees (ctree) described in Sherlock et al. (2026).

## The problem

Missing data are ubiquitous in applied research. Multiple imputation (MI) is the principled solution, but pooling results across imputations is straightforward only for linear-combination models (via Rubin's rules). Conditional inference trees (ctree) cannot be pooled across imputations because structurally different trees – which split on different variables at different nodes – define different subgroups, so the targets of inference are no longer aligned across imputations.

## The solution

**Stack** the M imputed datasets vertically into one data frame of M x n rows, then fit a **single** ctree. The single tree is coherent and interpretable. The only problem: stacking inflates the nominal sample size by M, so test statistics at each node are similarly inflated and the tree over-splits.

**Stack ? M correction**: Divide the node-level significance threshold by M ('alpha_corrected = alpha / M') before the pruning decision. This is equivalent to dividing each node's test statistic by M before comparing it to the Bonferroni-corrected critical value. Sherlock et al. (2026) validated this approach via Monte Carlo simulation under MCAR, showing sub-nominal (conservative) type-I error and acceptable power.

## Main function

The primary user-facing function is [ctree_stacked()]. It accepts a 'mids' object from [mice::mice()], a list of imputed data frames, or a plain data frame, and returns a fitted tree with full 'partykit' compatibility.

## Citation

If you use 'ctreeMI', please cite both the package and the methodological paper:

“' Sherlock, P., Mansolf, M., Hofheimer, J., Hockett, C. W., O'Connor, T. G., Roubinov, D., Graff, J. C., Lai, J.-S., Bush, N. R., Wright, R. J., & Chiu, Y.-H. M. (2026). Beyond linear risk: A machine learning approach to understanding perinatal depression in context. Multivariate Behavioral Research, 1-16. https://doi.org/10.1080/00273171.2026.2661244 “'

The underlying ctree algorithm should also be cited:

“' Hothorn, T., Hornik, K., & Zeileis, A. (2006). Unbiased recursive partitioning: A conditional inference framework. Journal of Computational and Graphical Statistics, 15(3), 651-674.

Hothorn, T., & Zeileis, A. (2015). partykit: A modular toolkit for recursive partitioning in R. Journal of Machine Learning Research, 16, 3905-3909. “'

Author(s)

Maintainer: Phillip Sherlock phillip.sherlock@ufl.edu (ORCID)

See Also

Useful links:


Check That Node Statistics Can Be Extracted

Description

Diagnostic. Fits small trees under both of the multiplicity settings the correction supports, and verifies that node-level statistics and degrees of freedom can be recovered from the fitted objects. Run it once after installing, or after upgrading 'partykit'.

Usage

check_stackM_extraction(verbose = TRUE)

Arguments

verbose

Logical. Print a per-node report.

Value

Invisibly, 'TRUE' if extraction succeeded everywhere it was tried.

Examples

check_stackM_extraction(verbose = FALSE)


Conditional Inference Tree on Stacked Multiply Imputed Data

Description

Fits a conditional inference tree (ctree) on stacked multiply imputed datasets using the Stack / M rescaling procedure described in Sherlock et al. (2026). Multiply imputed datasets are concatenated vertically ("stacked") and one tree is grown on the combined data. Each node-level chi-square statistic is then divided by the number of imputations (M) to counteract the artificially inflated sample size, the node-level p-values are recomputed, and the tree is compressed bottom-up. This yields a single, coherent, interpretable tree that incorporates imputation variability without requiring the pooling of structurally different trees.

Usage

ctree_stacked(
  formula,
  data,
  m = NULL,
  alpha = 0.05,
  scale_minsize = TRUE,
  verbose = TRUE,
  ...
)

Arguments

formula

A model formula, passed to [partykit::ctree()].

data

A 'mids' object from [mice::mice()], a list of imputed data frames, or a single complete data frame. If a single complete data frame is supplied (no missing data handling needed), the function falls back to a standard [partykit::ctree()] call with a warning.

m

Integer. Number of imputations to use. If 'data' is a 'mids' object or a list, defaults to the number of datasets available. Ignored when 'data' is a plain data frame.

alpha

Numeric. The nominal significance threshold for node-level splitting (default 0.05). It is applied to p-values recomputed from node statistics that have been divided by 'm'; 'alpha' itself is not rescaled. Must be strictly between 0 and 1.

scale_minsize

Logical. If 'TRUE' (default), 'minsplit' and 'minbucket' are multiplied by 'm' so they refer to original rather than stacked observations. The 'partykit' defaults would otherwise permit terminal nodes holding fewer than one original observation.

verbose

Logical. If 'TRUE' (default), prints a message summarising the stacking and correction applied.

...

Additional arguments passed to [partykit::ctree_control()]. Note: 'alpha' in '...' is ignored in favour of the 'alpha' argument above.

Details

## Methodological background

When data contain missing values, a common and principled approach is multiple imputation (Rubin, 1987), wherein M completed datasets are generated by drawing from the posterior predictive distribution of the missing values. For most statistical models, results from M datasets are pooled using Rubin's rules.

Conditional inference trees (ctree; Hothorn, Hornik & Zeileis, 2006) are not straightforward to pool across imputations because the tree structure itself – which variables are split, at what values, and in what order – can differ across imputations. Pooling structurally different trees produces inconsistent subgroup definitions and uninterpretable results.

Rodgers et al. (2021) proposed stacking the M imputed datasets vertically and fitting a single tree to the combined data. This circumvents the structural-mismatch problem and produces one interpretable tree. However, stacking inflates the nominal sample size by a factor of M, causing node-level test statistics to be similarly inflated and the tree to split more aggressively than warranted.

Sherlock et al. (2026) proposed and validated the **Stack / M** correction: each node-level test statistic computed on the stacked data is divided by M, the p-value is recomputed from the rescaled statistic, the Bonferroni adjustment for the number of candidate splitting variables is reapplied, and nodes that no longer meet 'alpha' are pruned. Monte Carlo simulations under MCAR confirmed sub-nominal (conservative) type-I error and reduced but acceptable power.

## Correction applied to the statistic, not to alpha

Rescaling the statistic is **not** equivalent to dividing the significance threshold by M. Writing 'q(p, df)' for the chi-square quantile function, the two rules are:

These coincide only at 'M = 1'. At 'df = 1', 'alpha = 0.05', 'M = 30' the first requires 'X > 115.2' and the second only 'X > 9.9', so threshold rescaling under-corrects by an order of magnitude and grows substantially larger trees than the published method.

Versions 0.1.0 and 0.2.0 of this package implemented threshold rescaling. Trees fitted with those versions are under-corrected and should be refitted.

## Usage with 'mice'

“'r library(mice) imp <- mice(my_data, m = 30, printFlag = FALSE) fit <- ctree_stacked(outcome ~ ., data = imp, alpha = 0.05) print(fit) plot(fit) “'

## Usage with a list of data frames

“'r imp_list <- lapply(1:30, function(i) complete(imp, i)) fit <- ctree_stacked(outcome ~ ., data = imp_list, alpha = 0.05) “'

Value

An object of class 'c("ctreeMI", "constparty", "party")'. This inherits all methods from [partykit::ctree()] output and additionally carries a 'ctreeMI_info' attribute with the following elements:

'm'

Number of imputations used.

'n_original'

Number of rows in a single imputed dataset.

'n_stacked'

Total rows in the stacked dataset.

'alpha'

The significance threshold applied to the rescaled p-values.

'correction'

Character, '"statistic/M"'.

'node_stats'

Data frame of per-node raw statistics, degrees of freedom, rescaled statistics, p-values, and retention.

'n_splits_before', 'n_splits_after'

Splits before and after the correction was applied.

'outcome_dim'

Rank of the influence function of the response, i.e. the degrees of freedom carried by a numeric or ordered predictor.

'minsplit', 'minbucket'

Minimum node sizes actually used, in stacked rows.

'formula'

The model formula.

'call'

The matched call.

References

Sherlock, P., Mansolf, M., Hofheimer, J., Hockett, C. W., O'Connor, T. G., Roubinov, D., Graff, J. C., Lai, J.-S., Bush, N. R., Wright, R. J., & Chiu, Y.-H. M. (2026). Beyond linear risk: A machine learning approach to understanding perinatal depression in context. *Multivariate Behavioral Research*, 1-16. doi:10.1080/00273171.2026.2661244

Hothorn, T., Hornik, K., & Zeileis, A. (2006). Unbiased recursive partitioning: A conditional inference framework. *Journal of Computational and Graphical Statistics*, 15(3), 651-674. doi:10.1198/106186006X133933

Hothorn, T., & Zeileis, A. (2015). partykit: A modular toolkit for recursive partitioning in R. *Journal of Machine Learning Research*, 16, 3905-3909.

Rodgers, D. M., Jacobucci, R., & Grimm, K. J. (2021). A multiple imputation approach for handling missing data in classification and regression trees. *Journal of Behavioral Data Science*, 1(1), 127-153. doi:10.35566/jbds/v1n1/p6

Rubin, D. B. (1987). *Multiple imputation for nonresponse in surveys.* Wiley.

See Also

[partykit::ctree()], [partykit::ctree_control()], [mice::mice()], [stack_imputations()], [rescale_statistic()], [prune_stackM()], [node_table()]

Examples

## Not run: 
library(mice)

# Introduce missingness into the airquality dataset
set.seed(42)
aq <- airquality
aq$Ozone[sample(nrow(aq), 20)] <- NA
aq$Solar.R[sample(nrow(aq), 15)] <- NA

# Impute
imp <- mice(aq, m = 10, printFlag = FALSE)

# Fit ctree with Stack/M correction
fit <- ctree_stacked(Ozone ~ Solar.R + Wind + Temp + Month,
                     data = imp,
                     alpha = 0.05)
print(fit)
plot(fit)

## End(Not run)


Terminal-Node Summary With Effective Sample Sizes

Description

Summarises the nodes of a fitted tree: the split path leading to each node, its size in the stacked data, its effective size in original observations (stacked size divided by 'M'), and node-level outcome summaries.

Usage

node_table(object, terminal_only = TRUE, max_levels = NULL, digits = 3)

Arguments

object

A 'ctreeMI' object from [ctree_stacked()], or any 'party' object (in which case 'M' is taken to be 1).

terminal_only

Logical. Report terminal nodes only (default), or every node including the root.

max_levels

Integer or 'NULL'. Truncate factor level sets longer than this in the printed split path.

digits

Number of digits for the outcome summaries.

Details

Terminal-node sample sizes printed by 'partykit' refer to the stacked data, in which every observation appears 'M' times. 'effective_n' divides by 'M' to give the number of original observations behind each node. Node-level means and proportions need no such adjustment: they are already averages over the imputations.

Value

A data frame of class '"ctreeMI_nodes"' with one row per node: 'node_id', 'depth', 'n_stacked', 'effective_n', any outcome summaries, 'path', and a 'conditions' list column holding the split conditions individually.

See Also

[ctree_stacked()], [report_ctreeMI()]

Examples

set.seed(1)
imps <- lapply(1:5, function(i) {
  x <- stats::rnorm(200)
  data.frame(x = x, y = stats::rnorm(200) + 1.5 * (x > 0))
})
fit <- ctree_stacked(y ~ x, data = imps, verbose = FALSE)
node_table(fit)


Print Method for ctreeMI Objects

Description

Prints a summary of the stacked-imputation settings used to fit the tree, followed by the standard [partykit::ctree()] output.

Usage

## S3 method for class 'ctreeMI'
print(x, ...)

Arguments

x

An object of class '"ctreeMI"', as returned by [ctree_stacked()].

...

Further arguments passed to the partykit print method.

Value

'x', invisibly.


Print a Node Table

Description

Print a Node Table

Usage

## S3 method for class 'ctreeMI_nodes'
print(x, ...)

Arguments

x

A '"ctreeMI_nodes"' object from [node_table()].

...

Passed to [print.data.frame()].

Value

'x', invisibly.


Print a Methods Paragraph

Description

Print a Methods Paragraph

Usage

## S3 method for class 'ctreeMI_report'
print(x, width = 76, ...)

Arguments

x

A '"ctreeMI_report"' object from [report_ctreeMI()].

width

Wrapping width in characters.

...

Currently unused.

Value

'x', invisibly.


Prune a Stacked Tree Using the Stack / M Correction

Description

Applies the Stack / M correction of Sherlock et al. (2026) to a conditional inference tree fitted on stacked multiply imputed data. Every node-level test statistic is divided by 'M', the p-values are recomputed from the chi-square reference distribution 'ctree' uses, the multiplicity adjustment over candidate splitting variables is reapplied, and the tree is compressed bottom-up.

Usage

prune_stackM(tree, m, alpha = 0.05, verbose = TRUE)

Arguments

tree

A 'party'/'constparty' object fitted by [partykit::ctree()] on stacked data, with 'teststat = "quadratic"' and 'testtype' either '"Univariate"' or '"Bonferroni"'. Both are handled: whether the stored p-values already carry ‘partykit'’s multiplicity adjustment is read from the fitted control rather than assumed.

m

Integer. Number of imputations.

alpha

Numeric. Nominal significance threshold.

verbose

Logical. Report how many splits were retained.

Details

# What is tested

'ctree' splits a node when *any* candidate variable meets 'alpha', so that is the rule reapplied here: all candidates tested at the node are rescaled and the smallest corrected p-value decides. Testing only the variable that was split on would not be equivalent, because candidates carry different degrees of freedom and rescaling can reorder them.

# How the tree is compressed

Pruning is bottom-up, as in the analysis for the paper. An internal node is collapsed only once all of its own internal descendants have been collapsed, so a node whose descendant survives the correction keeps its split.

# Degrees of freedom

With 'teststat = "quadratic"' the node-level statistic is chi-square with 'df' equal to the rank of the covariance matrix of the linear statistic: the outcome dimension for a numeric or ordered predictor (1 for a univariate outcome, 2 for a bivariate outcome, and so on), and '(L - 1)' times the outcome dimension for an unordered factor with 'L' levels present in the node. These are derived for every node and every candidate variable, by inverting the (statistic, p-value) pairs 'partykit' stored and falling back on the structural rule where the stored p-value has underflowed to zero, which happens routinely on stacked data.

Value

A list with the pruned 'tree' and a 'node_stats' data frame with one row per internal node of the unpruned tree, recording the variable split on, its raw and rescaled statistics, the degrees of freedom and how they were obtained, the p-values before and after correction, the smallest corrected p-value over all candidates at that node, and whether the split was retained. The full per-candidate table is attached as 'attr(node_stats, "candidates")'.

References

Sherlock, P., et al. (2026). Beyond linear risk: A machine learning approach to understanding perinatal depression in context. *Multivariate Behavioral Research*, 1-16. doi:10.1080/00273171.2026.2661244

Examples

set.seed(1)
n <- 300
d <- data.frame(x = stats::rnorm(n))
d$y1 <- stats::rnorm(n) + 1.2 * (d$x > 0)
d$y2 <- stats::rnorm(n) + 0.9 * (d$x > 0)
grown <- partykit::ctree(y1 + y2 ~ x, data = d)
out <- prune_stackM(grown, m = 5, verbose = FALSE)
out$node_stats


A Methods Paragraph For a ctreeMI Analysis

Description

Generates a paragraph describing the analysis in the form usually required by a methods section: the number of imputations, the size of the stacked dataset, the model, the Stack / M correction and the threshold applied, how many candidate splits survived it, and the shape of the resulting tree.

Usage

report_ctreeMI(object, digits = 3)

Arguments

object

A 'ctreeMI' object from [ctree_stacked()].

digits

Number of digits used in the node summaries.

Value

An object of class '"ctreeMI_report"': a list whose 'text' element is the paragraph, alongside the quantities it reports.

See Also

[ctree_stacked()], [node_table()]

Examples

set.seed(1)
imps <- lapply(1:5, function(i) {
  x <- stats::rnorm(200)
  data.frame(x = x, y = stats::rnorm(200) + 1.5 * (x > 0))
})
fit <- ctree_stacked(y ~ x, data = imps, verbose = FALSE)
report_ctreeMI(fit)


Rescale a Node-Level Test Statistic for Stacking

Description

Divides a node-level chi-square test statistic by the number of imputations 'M' and recomputes the corresponding p-value. This is the Stack / M correction of Sherlock et al. (2026).

Usage

rescale_statistic(statistic, m, df)

Arguments

statistic

Numeric. The raw test statistic computed on the stacked data.

m

Integer. Number of imputations.

df

Numeric. Degrees of freedom of the reference chi-square distribution: the outcome dimension for a numeric or ordered predictor, '(L - 1)' times the outcome dimension for an unordered factor with 'L' levels. See [prune_stackM()], which derives this for you.

Details

Stacking 'M' imputed datasets produces 'M * n' rows, and a chi-square statistic computed on the stacked data scales approximately linearly with 'M'. Dividing the statistic by 'M' returns it to the scale of a single imputed dataset before the p-value is computed.

This is **not** the same as dividing the significance threshold by 'M'. The two rules coincide only at 'M = 1':

For 'df = 1', 'alpha = 0.05' and 'M = 30', the first requires 'X > 115.2' and the second only 'X > 9.9'. Versions 0.1.0 and 0.2.0 implemented the second rule.

Value

A list with 'statistic_rescaled' and 'p_value'.

References

Sherlock, P., Mansolf, M., Hofheimer, J., Hockett, C. W., O'Connor, T. G., Roubinov, D., Graff, J. C., Lai, J.-S., Bush, N. R., Wright, R. J., & Chiu, Y.-H. M. (2026). Beyond linear risk: A machine learning approach to understanding perinatal depression in context. *Multivariate Behavioral Research*, 1-16. doi:10.1080/00273171.2026.2661244

Examples

rescale_statistic(115.2, m = 30, df = 1)


Stack Multiply Imputed Datasets

Description

Concatenates a list of imputed data frames into a single "stacked" data frame. An imputation index column ('.imp') is added to identify which imputed dataset each row originated from. This is the first step of the stacked-imputation workflow described in Rodgers et al. (2021) and applied to conditional inference trees in Sherlock et al. (2026).

Usage

stack_imputations(data_list, imp_col = ".imp")

Arguments

data_list

A list of data frames, each the same dimensions, representing M imputed versions of the same dataset.

imp_col

Character string. Name of the imputation-index column added to the stacked data (default '".imp"'). Set to 'NULL' to suppress this column.

Value

A single data frame with 'M * n' rows, where 'n' is the number of rows in each imputed dataset.

References

Sherlock, P., et al. (2026). Beyond linear risk: A machine learning approach to understanding perinatal depression in context. *Multivariate Behavioral Research*, 1-16. doi:10.1080/00273171.2026.2661244

Rodgers, D. M., Jacobucci, R., & Grimm, K. J. (2021). A multiple imputation approach for handling missing data in classification and regression trees. *Journal of Behavioral Data Science*, 1(1), 127-153. doi:10.35566/jbds/v1n1/p6

Examples

df1 <- data.frame(x = 1:5, y = c(2, 4, 6, 8, 10))
df2 <- data.frame(x = 1:5, y = c(2, 3, 6, 9, 10))
stacked <- stack_imputations(list(df1, df2))
nrow(stacked) # 10
table(stacked$.imp)


Summary Method for ctreeMI Objects

Description

Returns a named list with details about the stacked-imputation fit and the resulting tree structure (number of terminal nodes, tree depth).

Usage

## S3 method for class 'ctreeMI'
summary(object, ...)

Arguments

object

An object of class '"ctreeMI"', as returned by [ctree_stacked()].

...

Currently unused.

Value

A list (invisibly) with components:

'ctreeMI_info'

Stacking metadata from [ctree_stacked()].

'n_terminal_nodes'

Number of terminal nodes in the fitted tree.

'depth'

Maximum depth of the fitted tree.