| Title: | Optimize PTSD Diagnostic Criteria |
|---|---|
| Description: | Provides tools for analyzing and optimizing PTSD (Post-Traumatic Stress Disorder) diagnostic criteria using PCL-5 (PTSD Checklist for DSM-5) and CAPS-5 (Clinician-Administered PTSD Scale for DSM-5) data. Functions identify optimal subsets of PCL-5 items that maintain diagnostic accuracy while reducing assessment burden. Includes tools for both hierarchical (cluster-based) and non-hierarchical symptom combinations, calculation of diagnostic metrics, and comparison with standard DSM-5 criteria. Model validation is conducted using holdout and cross-validation methods to assess robustness and generalizability of the results. For more details see Weidmann et al. (2025) <doi:10.31219/osf.io/6rk72_v1>. |
| Authors: | Laura Weidmann [aut] (ORCID: <https://orcid.org/0009-0006-5509-6961>), Tobias R. Spiller [aut, cre] (ORCID: <https://orcid.org/0000-0002-0107-0743>), Flavio A. Schüepp [aut] (ORCID: <https://orcid.org/0000-0002-0750-3888>) |
| Maintainer: | Tobias R. Spiller <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.4.1 |
| Built: | 2026-07-14 06:54:59 UTC |
| Source: | https://github.com/tobiasrspiller/ptsddiag |
'r lifecycle::badge("deprecated")'
Convenience wrapper around optimize_combinations with the
original PCL-5 defaults: 6 symptoms, 4 required, top 3 returned.
Identifies the three best six-symptom combinations for PTSD diagnosis where any four symptoms must be present, regardless of their cluster membership.
analyze_best_six_symptoms_four_required( data, score_by = "balanced_accuracy", DT = FALSE )analyze_best_six_symptoms_four_required( data, score_by = "balanced_accuracy", DT = FALSE )
data |
A dataframe containing exactly 20 columns with PCL-5 item scores (output of rename_ptsd_columns). Each symptom should be scored on a 0-4 scale where:
|
score_by |
Character string specifying optimization criterion:
|
DT |
Logical. If |
The function:
Tests all possible combinations of 6 symptoms from the 20 PCL-5 items
Requires 4 symptoms to be present (>=2 on original 0-4 scale) for diagnosis
Identifies the three combinations that best match the original DSM-5 diagnosis
Optimization can be based on:
Maximizing balanced accuracy, the mean of sensitivity and specificity (the default)
Minimizing false cases (both false positives and false negatives)
Minimizing only false negatives (newly non-diagnosed cases)
The symptom clusters in PCL-5 are:
Items 1-5: Intrusion symptoms (Criterion B)
Items 6-7: Avoidance symptoms (Criterion C)
Items 8-14: Negative alterations in cognitions and mood (Criterion D)
Items 15-20: Alterations in arousal and reactivity (Criterion E)
A list containing:
best_symptoms: List of three vectors, each containing six symptom numbers representing the best combinations found
diagnosis_comparison: Dataframe comparing original DSM-5 diagnosis with diagnoses based on the three best combinations
summary: Diagnostic accuracy metrics for each combination. A data.frame
by default, or an interactive datatable if
DT = TRUE.
optimize_combinations for the generalized version with
configurable parameters.
# Use a 250-row subset of the bundled data to keep the example fast ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) # Find best combinations with the default balanced-accuracy criterion results <- analyze_best_six_symptoms_four_required(ptsd_data) # Get symptom numbers results$best_symptoms # View raw comparison data results$diagnosis_comparison # View summary statistics results$summary# Use a 250-row subset of the bundled data to keep the example fast ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) # Find best combinations with the default balanced-accuracy criterion results <- analyze_best_six_symptoms_four_required(ptsd_data) # Get symptom numbers results$best_symptoms # View raw comparison data results$diagnosis_comparison # View summary statistics results$summary
'r lifecycle::badge("deprecated")'
Convenience wrapper around optimize_combinations_clusters with
the original PCL-5 defaults: 6 symptoms, 4 required, top 3 returned, and
standard DSM-5 cluster structure.
Identifies the three best six-symptom combinations for PTSD diagnosis where four symptoms must be present and must include at least one symptom from each DSM-5 criterion cluster.
analyze_best_six_symptoms_four_required_clusters( data, score_by = "balanced_accuracy", DT = FALSE )analyze_best_six_symptoms_four_required_clusters( data, score_by = "balanced_accuracy", DT = FALSE )
data |
A dataframe containing exactly 20 columns with PCL-5 item scores (output of rename_ptsd_columns). Each symptom should be scored on a 0-4 scale where:
|
score_by |
Character string specifying optimization criterion:
|
DT |
Logical. If |
The function:
Generates valid combinations ensuring representation from all clusters
Requires 4 symptoms to be present (>=2 on original 0-4 scale) for diagnosis
Validates that present symptoms include at least one from each cluster
Identifies the three combinations that best match the original DSM-5 diagnosis
DSM-5 PTSD symptom clusters:
Cluster 1 (B) - Intrusion: Items 1-5
Cluster 2 (C) - Avoidance: Items 6-7
Cluster 3 (D) - Negative alterations in cognitions and mood: Items 8-14
Cluster 4 (E) - Alterations in arousal and reactivity: Items 15-20
Optimization can be based on:
Maximizing balanced accuracy, the mean of sensitivity and specificity (the default)
Minimizing false cases (both false positives and false negatives)
Minimizing only false negatives (newly non-diagnosed cases)
A list containing:
best_symptoms: List of three vectors, each containing six symptom numbers representing the best combinations found
diagnosis_comparison: Dataframe comparing original DSM-5 diagnosis with diagnoses based on the three best combinations
summary: Diagnostic accuracy metrics for each combination. A data.frame
by default, or an interactive datatable if
DT = TRUE.
optimize_combinations_clusters for the generalized
version with configurable parameters and custom cluster definitions.
# This deprecated wrapper always runs the full 6-symptom hierarchical # search, so its example uses a 50-row subset to stay fast ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:50, ], id_col = c("patient_id", "age", "sex")) # Find best hierarchical combinations with the default balanced-accuracy # criterion results <- analyze_best_six_symptoms_four_required_clusters(ptsd_data) # Get symptom numbers results$best_symptoms # View raw comparison data results$diagnosis_comparison # View summary statistics results$summary# This deprecated wrapper always runs the full 6-symptom hierarchical # search, so its example uses a 50-row subset to stay fast ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:50, ], id_col = c("patient_id", "age", "sex")) # Find best hierarchical combinations with the default balanced-accuracy # criterion results <- analyze_best_six_symptoms_four_required_clusters(ptsd_data) # Get symptom numbers results$best_symptoms # View raw comparison data results$diagnosis_comparison # View summary statistics results$summary
Applies one or more pre-specified symptom combinations to a new dataset and returns a comparison dataframe with the baseline DSM-5 diagnosis and the simplified diagnosis for each combination. This function enables external validation by allowing combinations derived from one dataset to be tested on another.
apply_symptom_combinations(data, combinations, n_required = 4, clusters = NULL)apply_symptom_combinations(data, combinations, n_required = 4, clusters = NULL)
data |
A dataframe containing exactly 20 columns with PCL-5 item scores
(output of
|
combinations |
A list of integer vectors. Each vector contains symptom
indices (e.g., |
n_required |
Integer specifying how many symptoms must be present (scored >= 2 on original scale) for a positive diagnosis (default: 4). Must be between 1 and the length of each combination. |
clusters |
For PCL-5:
|
The function:
Computes the baseline DSM-5 diagnosis (PTSD_orig) on the
provided data using binarized scores
Binarizes the symptom data (scores >= 2 become 1, others become 0)
For each combination, determines diagnosis based on whether at least
n_required symptoms are present
If clusters is provided, additionally checks that present
symptoms span all defined clusters (hierarchical checking)
Returns a comparison dataframe suitable for
summarize_ptsd_changes
Typical workflow for external validation:
Derive optimal combinations on Dataset A using
optimize_combinations or
optimize_combinations_clusters
Apply those combinations to Dataset B using this function
Compute diagnostic metrics using
summarize_ptsd_changes and
create_readable_summary
A dataframe with columns:
Any carry-through columns from data (e.g. an ID column added
via rename_ptsd_columns), prepended in original order.
PTSD_orig: Logical. Full DSM-5 diagnosis computed on this data.
One column per combination (logical): Simplified diagnosis for each
combination. Column names follow the symptom_X_Y_Z pattern.
This dataframe can be passed directly to summarize_ptsd_changes
to compute diagnostic accuracy metrics.
optimize_combinations and
optimize_combinations_clusters for deriving optimal
combinations.
summarize_ptsd_changes and
create_readable_summary for computing and formatting
diagnostic metrics from the returned comparison dataframe.
# Create example data set.seed(42) ptsd_data <- data.frame(matrix(sample(0:4, 400, replace=TRUE), ncol=20)) names(ptsd_data) <- paste0("symptom_", 1:20) # Define some combinations to test my_combinations <- list( c(1, 6, 8, 10, 15, 19), c(2, 7, 9, 11, 16, 20) ) # Apply combinations (non-hierarchical) comparison <- apply_symptom_combinations(ptsd_data, my_combinations, n_required = 4) # Compute metrics metrics <- summarize_ptsd_changes(comparison) create_readable_summary(metrics) # Apply with hierarchical checking pcl5_clusters <- list(B = 1:5, C = 6:7, D = 8:14, E = 15:20) hier_comparison <- apply_symptom_combinations(ptsd_data, my_combinations, n_required = 4, clusters = pcl5_clusters) summarize_ptsd_changes(hier_comparison)# Create example data set.seed(42) ptsd_data <- data.frame(matrix(sample(0:4, 400, replace=TRUE), ncol=20)) names(ptsd_data) <- paste0("symptom_", 1:20) # Define some combinations to test my_combinations <- list( c(1, 6, 8, 10, 15, 19), c(2, 7, 9, 11, 16, 20) ) # Apply combinations (non-hierarchical) comparison <- apply_symptom_combinations(ptsd_data, my_combinations, n_required = 4) # Compute metrics metrics <- summarize_ptsd_changes(comparison) create_readable_summary(metrics) # Apply with hierarchical checking pcl5_clusters <- list(B = 1:5, C = 6:7, D = 8:14, E = 15:20) hier_comparison <- apply_symptom_combinations(ptsd_data, my_combinations, n_required = 4, clusters = pcl5_clusters) summarize_ptsd_changes(hier_comparison)
Converts one or more combination specifications – as returned by
read_combinations – into the definitions list that
evaluate_definitions expects. This is the validation-site
counterpart of extract_definitions: the derivation site
exports each rule as a JSON file, and the validation site turns the
imported files into evaluable definitions in one call.
as_definitions(specs, n_top = NULL)as_definitions(specs, n_top = NULL)
specs |
A single combination specification (the list returned by
|
n_top |
Integer or |
Each definition is labelled by, in order of precedence: the name given to
the list element in specs; the label stored in the file by
write_combinations; or an automatic label of the form
"4/6 Hierarchical" derived from the rule (n_required /
number of symptoms, hierarchical when the specification carries a cluster
structure). Duplicate labels are an error – name the list elements to
disambiguate.
A cluster structure stored in the specification is preserved in the definition, so hierarchical rules exported with non-default clusters are evaluated with exactly those clusters.
A named list in the shape returned by
extract_definitions: one element per specification, each a
list with symptoms (list of integer vectors), n_required,
hierarchical, and clusters (NULL unless the
specification stored a cluster structure). Pass it to
evaluate_definitions.
read_combinations,
evaluate_definitions, extract_definitions.
# A derivation site exports a rule ... tmp <- tempfile(fileext = ".json") write_combinations(list(c(1, 6, 8, 10, 15, 19), c(2, 7, 9, 11, 16, 20)), tmp, n_required = 4, label = "4/6 Non-hierarchical") # ... and the validation site turns the imported file into definitions spec <- read_combinations(tmp) definitions <- as_definitions(spec, n_top = 2) str(definitions) unlink(tmp)# A derivation site exports a rule ... tmp <- tempfile(fileext = ".json") write_combinations(list(c(1, 6, 8, 10, 15, 19), c(2, 7, 9, 11, 16, 20)), tmp, n_required = 4, label = "4/6 Non-hierarchical") # ... and the validation site turns the imported file into definitions spec <- read_combinations(tmp) definitions <- as_definitions(spec, n_top = 2) str(definitions) unlink(tmp)
Converts PCL-5 symptom scores from their original 0-4 scale to binary values (0/1) based on the clinical threshold for symptom presence (>=2).
binarize_data(data)binarize_data(data)
data |
A dataframe containing exactly 20 columns with PCL-5 item scores (output of rename_ptsd_columns). Each symptom should be scored on a 0-4 scale where:
Note: This function should only be used with raw symptom scores before calculating the total score, as it will convert all values in the dataframe to 0/1, which would invalidate any total score column if present. |
The function implements the standard clinical threshold for PTSD symptom presence where:
Scores of 0-1 ("Not at all" and "A little bit") → 0 (symptom absent)
Scores of 2-4 ("Moderately" to "Extremely") → 1 (symptom present)
A dataframe with the same structure as input but with all symptom scores converted to binary values:
0 = Symptom absent (original scores 0-1)
1 = Symptom present (original scores 2-4)
# Create sample data sample_data <- data.frame( matrix(sample(0:4, 20 * 10, replace = TRUE), nrow = 10, ncol = 20) ) colnames(sample_data) <- paste0("symptom_", 1:20) # Binarize scores binary_data <- binarize_data(sample_data) binary_data # Should only show 0s and 1s# Create sample data sample_data <- data.frame( matrix(sample(0:4, 20 * 10, replace = TRUE), nrow = 10, ncol = 20) ) colnames(sample_data) <- paste0("symptom_", 1:20) # Binarize scores binary_data <- binarize_data(sample_data) binary_data # Should only show 0s and 1s
Calculates the total PCL-5 (PTSD Checklist for DSM-5) score by summing all 20 symptom scores. The total score ranges from 0 to 80, with higher scores indicating greater symptom severity.
calculate_ptsd_total(data)calculate_ptsd_total(data)
data |
A dataframe containing standardized PCL-5 item scores (output of rename_ptsd_columns). Each symptom should be scored on a 0-4 scale where:
|
Calculates the total score from PCL-5 items
A dataframe with all original columns plus an additional column "total" containing the sum of all 20 symptom scores (range: 0-80)
# Create sample data sample_data <- data.frame( matrix(sample(0:4, 20 * 10, replace = TRUE), nrow = 10, ncol = 20) ) colnames(sample_data) <- paste0("symptom_", 1:20) # Calculate total scores scores_with_total <- calculate_ptsd_total(sample_data) print(scores_with_total$total)# Create sample data sample_data <- data.frame( matrix(sample(0:4, 20 * 10, replace = TRUE), nrow = 10, ncol = 20) ) colnames(sample_data) <- paste0("symptom_", 1:20) # Calculate total scores scores_with_total <- calculate_ptsd_total(sample_data) print(scores_with_total$total)
Pre-flight check for a data frame of PCL-5 item scores. Unlike the
fail-fast validation inside the workflow functions, this check runs
all checks and reports every problem at once – column count,
numeric type, the integer 0-4 scoring range, and missing values – so a
data file can be fixed in one pass instead of one error at a time. Run it
on your item columns before rename_ptsd_columns.
check_pcl5_data(data, id_col = NULL)check_pcl5_data(data, id_col = NULL)
data |
A data frame containing the PCL-5 item columns (plus any
columns named in |
id_col |
Optional character vector naming identifier column(s) to
exclude from the check, mirroring |
Two input shapes are supported. If data already contains the renamed
columns symptom_1 through symptom_20, those are checked by
name (extra columns such as total are ignored). Otherwise all
non-id_col columns are treated as the item columns in positional
DSM-5 order, exactly as rename_ptsd_columns will interpret
them – the check then also requires that there are exactly 20 of them.
Rows in which every item is 0 are reported as an informational note, not an error: whether symptom-free records are kept (e.g. to contribute true negatives in a validation sample) or excluded is an analytic choice.
invisible(TRUE) when every check passes; otherwise the
function aborts with a report listing all failed checks.
rename_ptsd_columns, which this check prepares for.
# Clean data passes and reports each check ptsd_items <- simulated_ptsd[1:100, paste0("S", 1:20)] check_pcl5_data(ptsd_items) # A data frame with several problems reports them all in one error bad <- ptsd_items bad$extra_column <- 1 # 21 item columns bad$S3[5] <- NA # a missing value bad$S7[2] <- 9 # out of the 0-4 range try(check_pcl5_data(bad))# Clean data passes and reports each check ptsd_items <- simulated_ptsd[1:100, paste0("S", 1:20)] check_pcl5_data(ptsd_items) # A data frame with several problems reports them all in one error bad <- ptsd_items bad$extra_column <- 1 # 21 item columns bad$S3[5] <- NA # a missing value bad$S7[2] <- 9 # out of the 0-4 range try(check_pcl5_data(bad))
Produces a single unified summary table comparing the diagnostic performance
of multiple criteria against a chosen reference standard. Suitable for use as
a manuscript table comparing optimized symptom combinations, ICD-11, CAPS-5,
and DSM-5-TR in one kable-ready output.
compare_diagnostic_systems( data, ..., icd11 = TRUE, caps5_data = NULL, reference = c("pcl5", "caps5"), labels = NULL )compare_diagnostic_systems( data, ..., icd11 = TRUE, caps5_data = NULL, reference = c("pcl5", "caps5"), labels = NULL )
data |
A dataframe containing exactly 20 columns of PCL-5 item scores
(output of |
... |
Zero or more comparison dataframes, each containing a
|
icd11 |
Logical. If |
caps5_data |
Optional dataframe containing exactly 20 columns of CAPS-5
item severity scores (output of |
reference |
Character. Which DSM-5-TR diagnosis serves as the reference
standard: |
labels |
Optional character vector of display names for the systems
coming from |
The function:
Computes the PCL-5 DSM-5-TR diagnosis from data
If caps5_data is provided, computes the CAPS-5 DSM-5-TR
diagnosis
Sets the reference standard based on reference: either the
PCL-5 or CAPS-5 DSM-5-TR diagnosis. The reference row always appears
first with sensitivity = specificity = 1.
Optionally computes ICD-11 diagnosis from data when
icd11 = TRUE
Collects all non-PTSD_orig columns from the ...
comparison dataframes (e.g. output of
apply_symptom_combinations)
Calls summarize_ptsd_changes internally and reshapes
the result into a presentation-ready table
When caps5_data is NULL (default), labels follow the original
convention: "DSM-5-TR" and "ICD-11". When caps5_data
is provided, labels are disambiguated with the instrument name:
"DSM-5-TR (PCL-5)", "DSM-5-TR (CAPS-5)",
"ICD-11 (PCL-5)".
When caps5_data is provided, the strict PTSD_orig validation
on ... inputs is relaxed to a row-count check only, because
comparison dataframes may have been derived from either the PCL-5 or CAPS-5
data (which produce different PTSD_orig vectors).
A data.frame with one row per diagnostic system and the
following columns:
system: Display name of the diagnostic criterion
n_diagnosed: Number of cases meeting the criterion
pct_diagnosed: Percentage of total sample diagnosed (2 dp)
sensitivity: 4 dp
specificity: 4 dp
ppv: Positive predictive value, 4 dp
npv: Negative predictive value, 4 dp
n_false_negative: Cases missed vs. reference
pct_false_negative: Percentage of total sample, 2 dp
n_false_positive: Cases over-diagnosed vs. reference
pct_false_positive: Percentage of total sample, 2 dp
n_misclassified: Total misclassified cases
accuracy: Proportion classified the same as the reference
((total - misclassified) / total), 4 dp
balanced_accuracy: Mean of sensitivity and specificity
((sensitivity + specificity) / 2), 4 dp
create_icd11_diagnosis for the ICD-11 comparison dataframe.
create_caps5_diagnosis for standalone CAPS-5 diagnosis.
apply_symptom_combinations for generating comparison dataframes
from optimized symptom combinations.
optimize_combinations and
optimize_combinations_clusters for deriving optimal combinations.
ptsd_data <- rename_ptsd_columns(simulated_ptsd, id_col = c("patient_id", "age", "sex")) # ICD-11 vs DSM-5-TR only (no optimized combinations) tbl <- compare_diagnostic_systems(ptsd_data, icd11 = TRUE) tbl # Add two pre-specified combinations combos <- apply_symptom_combinations( ptsd_data, combinations = list(c(1, 6, 8, 10, 15, 19), c(2, 7, 9, 11, 16, 20)), n_required = 4 ) tbl2 <- compare_diagnostic_systems( ptsd_data, combos, icd11 = TRUE, labels = c("Combo A", "Combo B") ) knitr::kable(tbl2) # With CAPS-5 as gold standard reference caps5_raw <- data.frame(matrix(sample(0:4, 20 * nrow(simulated_ptsd), replace = TRUE), ncol = 20)) caps5_data <- rename_caps5_columns(caps5_raw) tbl3 <- compare_diagnostic_systems( ptsd_data, combos, icd11 = TRUE, caps5_data = caps5_data, reference = "caps5" ) knitr::kable(tbl3)ptsd_data <- rename_ptsd_columns(simulated_ptsd, id_col = c("patient_id", "age", "sex")) # ICD-11 vs DSM-5-TR only (no optimized combinations) tbl <- compare_diagnostic_systems(ptsd_data, icd11 = TRUE) tbl # Add two pre-specified combinations combos <- apply_symptom_combinations( ptsd_data, combinations = list(c(1, 6, 8, 10, 15, 19), c(2, 7, 9, 11, 16, 20)), n_required = 4 ) tbl2 <- compare_diagnostic_systems( ptsd_data, combos, icd11 = TRUE, labels = c("Combo A", "Combo B") ) knitr::kable(tbl2) # With CAPS-5 as gold standard reference caps5_raw <- data.frame(matrix(sample(0:4, 20 * nrow(simulated_ptsd), replace = TRUE), ncol = 20)) caps5_data <- rename_caps5_columns(caps5_raw) tbl3 <- compare_diagnostic_systems( ptsd_data, combos, icd11 = TRUE, caps5_data = caps5_data, reference = "caps5" ) knitr::kable(tbl3)
Runs several optimization scenarios on the same dataset and bundles the results into a single object suitable for tabular and visual comparison. Reproduces the multi-scenario workflow used in the PTSDdiag preprint (4/6 hierarchical, 4/6 non-hierarchical, 3/6 non-hierarchical) in one call, and also supports adding fixed criteria such as ICD-11 to the comparison.
compare_optimizations( data, scenarios = NULL, include_icd11 = FALSE, n_top = 10, score_by = "balanced_accuracy", clusters = NULL, show_progress = TRUE )compare_optimizations( data, scenarios = NULL, include_icd11 = FALSE, n_top = 10, score_by = "balanced_accuracy", clusters = NULL, show_progress = TRUE )
data |
A dataframe containing the 20 PCL-5 item columns
|
scenarios |
Optional named list of scenario configurations. Each element is a list with:
When |
include_icd11 |
Logical. When |
n_top |
Integer. Number of top combinations to retain per optimize
scenario (default 10). Fixed scenarios always contribute exactly one
combination regardless of |
score_by |
Character. Optimization criterion:
|
clusters |
Optional named list of integer vectors defining the PCL-5 clusters used by hierarchical optimize scenarios that do not specify their own. Defaults to the DSM-5 B/C/D/E grouping when needed. |
show_progress |
Logical. Forwarded to each optimize scenario's
progress bar. Default |
Each scenario is either:
optimize (default): runs optimize_combinations
or optimize_combinations_clusters depending on
hierarchical. Returns the top n_top combinations.
fixed: applies a pre-defined diagnostic criterion (such as ICD-11 PTSD) and treats its fixed symptom set as a single "combination" for the purpose of the multi-scenario tables and heatmap.
Fixed scenarios let researchers benchmark optimized criteria against published systems in a uniform output.
Any non-symptom columns present in data (e.g. an ID column added via
rename_ptsd_columns(..., id_col = "patient_id")) are carried through
to each scenario's per-row diagnosis_comparison, so per-participant
diagnoses can be joined back to demographics.
An object of class ptsdiag_comparison, a list with:
scenarios: named list of per-scenario results. Each element
mirrors the shape returned by optimize_combinations
(best_symptoms, diagnosis_comparison, summary,
n_tied) and carries a type attribute.
config: data.frame with one row per scenario summarising the
configuration used.
n_rows: number of input rows.
call: the matched call.
Pass the result to summarize_top_combinations for a
manuscript-ready performance table, to symptom_frequency for
the long-format symptom inclusion counts, and to
plot_symptom_frequency for the heatmap.
optimize_combinations,
optimize_combinations_clusters,
create_icd11_diagnosis,
summarize_top_combinations,
symptom_frequency,
plot_symptom_frequency.
# Use a 250-row subset of the bundled data to keep the example fast ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) # A compact optimized rule plus ICD-11 (a small 4-symptom search keeps # the example fast; omit `scenarios` to run the three default rules) comp <- compare_optimizations( ptsd_data, scenarios = list( "3/4 Non-hierarchical" = list(n_symptoms = 4, n_required = 3, hierarchical = FALSE) ), include_icd11 = TRUE, n_top = 5, show_progress = FALSE ) print(comp) # Manuscript Table 2 summarize_top_combinations(comp, as_percent = TRUE)# Use a 250-row subset of the bundled data to keep the example fast ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) # A compact optimized rule plus ICD-11 (a small 4-symptom search keeps # the example fast; omit `scenarios` to run the three default rules) comp <- compare_optimizations( ptsd_data, scenarios = list( "3/4 Non-hierarchical" = list(n_symptoms = 4, n_required = 3, hierarchical = FALSE) ), include_icd11 = TRUE, n_top = 5, show_progress = FALSE ) print(comp) # Manuscript Table 2 summarize_top_combinations(comp, as_percent = TRUE)
Applies the DSM-5-TR PTSD diagnostic algorithm to CAPS-5
(Clinician-Administered PTSD Scale for DSM-5) item scores and returns a
single-column dataframe indicating diagnostic status. Because CAPS-5 uses the
same 20-item structure, 0–4 severity scale, and 2 symptom-presence
threshold as the PCL-5, the diagnostic algorithm is identical.
create_caps5_diagnosis(data)create_caps5_diagnosis(data)
data |
A dataframe containing exactly 20 columns of CAPS-5 item severity
scores (output of |
The DSM-5-TR diagnostic criteria applied are:
Criterion B (Intrusion): 1 of items 1–5 with severity
2
Criterion C (Avoidance): 1 of items 6–7 with severity
2
Criterion D (Negative cognitions/mood): 2 of items 8–14
with severity 2
Criterion E (Arousal/reactivity): 2 of items 15–20 with
severity 2
All four criteria must be met for a positive diagnosis.
Unlike create_icd11_diagnosis, this function returns only the
CAPS-5 diagnosis column (PTSD_caps5), not a PCL-5 reference column.
This is because the CAPS-5 diagnosis is typically used as the gold-standard
reference itself, not compared against a PCL-5 baseline.
The returned dataframe can be passed to
compare_diagnostic_systems via its caps5_data parameter,
or used directly for descriptive analyses.
A data.frame with one logical column and one row per
participant:
PTSD_caps5: CAPS-5 DSM-5-TR diagnosis
Any carry-through columns present in data (e.g. an ID column added
via rename_caps5_columns) are prepended in original order.
rename_caps5_columns for standardizing CAPS-5 column names.
compare_diagnostic_systems for comparing CAPS-5 against PCL-5
and optimized symptom combinations in a unified table.
create_icd11_diagnosis for the ICD-11 alternative criteria.
# Simulate CAPS-5 data (using same structure as PCL-5) set.seed(42) caps5_raw <- data.frame(matrix(sample(0:4, 400, replace = TRUE), ncol = 20)) caps5_data <- rename_caps5_columns(caps5_raw) caps5_dx <- create_caps5_diagnosis(caps5_data) head(caps5_dx) table(caps5_dx$PTSD_caps5)# Simulate CAPS-5 data (using same structure as PCL-5) set.seed(42) caps5_raw <- data.frame(matrix(sample(0:4, 400, replace = TRUE), ncol = 20)) caps5_data <- rename_caps5_columns(caps5_raw) caps5_dx <- create_caps5_diagnosis(caps5_data) head(caps5_dx) table(caps5_dx$PTSD_caps5)
Applies ICD-11 PTSD diagnostic criteria to PCL-5 item scores and returns a
comparison dataframe against the full DSM-5-TR criteria. The output is
directly compatible with summarize_ptsd_changes so that ICD-11
diagnostic accuracy can be computed on the same footing as optimized
symptom combinations.
create_icd11_diagnosis(data)create_icd11_diagnosis(data)
data |
A dataframe containing exactly 20 columns of PCL-5 item scores
(output of |
ICD-11 PTSD requires ALL THREE of the following clusters to be met (symptom
present = score 2 on original 0–4 scale):
Re-experiencing in the present: 1 of PCL-5
items 2, 3 (nightmares, flashbacks)
Avoidance: 1 of PCL-5 items 6, 7
Sense of current threat: 1 of PCL-5 items 17, 18
(hypervigilance, exaggerated startle)
A minimum of 3 symptoms total across all ICD-11 items (2, 3, 6, 7, 17, 18) must be present. This is automatically satisfied when all three cluster requirements are met but is enforced explicitly for clarity.
This is the narrow six-item mapping used across the published
PCL-5-to-ICD-11 literature (e.g. Kuester et al. 2017, Schellong et al.
2019, Heeke et al. 2020, Pettrich et al. 2025): ICD-11 requires
re-experiencing to have a here-and-now quality, which nightmares (item 2)
and flashbacks (item 3) capture, while intrusive memories (item 1) as
worded in the PCL-5 do not. To benchmark the broader seven-item variant
(items 1, 2, 3, 6, 7, 17, 18) instead, compute it yourself and pass it to
compare_optimizations as a custom fixed criterion:
broad <- rowSums(data[, paste0("symptom_", c(1, 2, 3))] >= 2) >= 1 &
rowSums(data[, paste0("symptom_", c(6, 7))] >= 2) >= 1 &
rowSums(data[, paste0("symptom_", c(17, 18))] >= 2) >= 1
compare_optimizations(data, scenarios = list(
"ICD-11 (broad)" = list(type = "fixed", criterion = broad,
symptoms = c(1, 2, 3, 6, 7, 17, 18))))
DSM-5-TR diagnosis (PTSD_orig) is computed using the same binarization
logic as the rest of the package (create_ptsd_diagnosis_binarized).
A data.frame with two logical columns and one row per
participant:
PTSD_orig: DSM-5-TR diagnosis (reference standard)
PTSD_icd11: ICD-11 diagnosis
Any carry-through columns present in data (e.g. an ID column added
via rename_ptsd_columns) are prepended in original order so
results can be joined back to the source dataframe.
This dataframe can be passed directly to summarize_ptsd_changes
or used as an input to compare_diagnostic_systems.
compare_diagnostic_systems for a unified cross-system
comparison table.
summarize_ptsd_changes and create_readable_summary
for computing and formatting diagnostic metrics.
# Apply ICD-11 criteria to the built-in simulated dataset ptsd_data <- rename_ptsd_columns(simulated_ptsd, id_col = c("patient_id", "age", "sex")) icd11_result <- create_icd11_diagnosis(ptsd_data) head(icd11_result) # Feed directly into the metrics pipeline metrics <- summarize_ptsd_changes(icd11_result) create_readable_summary(metrics)# Apply ICD-11 criteria to the built-in simulated dataset ptsd_data <- rename_ptsd_columns(simulated_ptsd, id_col = c("patient_id", "age", "sex")) icd11_result <- create_icd11_diagnosis(ptsd_data) head(icd11_result) # Feed directly into the metrics pipeline metrics <- summarize_ptsd_changes(icd11_result) create_readable_summary(metrics)
Determines whether DSM-5 diagnostic criteria for PTSD are met using binarized symptom scores (0/1) for PCL-5 items. This is an alternative to determine_ptsd_diagnosis() that works with pre-binarized data.
create_ptsd_diagnosis_binarized(data)create_ptsd_diagnosis_binarized(data)
data |
A dataframe containing exactly 20 columns of PCL-5 item scores (output of rename_ptsd_columns) named symptom_1 to symptom_20. Each symptom should be scored on a 0-4 scale where:
Note: This function should only be used with raw symptom scores (output of rename_ptsd_columns) and not with data containing a total score column, as the internal binarization process would invalidate the total score. |
The function applies the DSM-5 diagnostic criteria for PTSD using binary indicators of symptom presence:
Criterion B (Intrusion): At least 1 present symptom from items 1-5
Criterion C (Avoidance): At least 1 present symptom from items 6-7
Criterion D (Negative alterations in cognitions and mood): At least 2 present symptoms from items 8-14
Criterion E (Alterations in arousal and reactivity): At least 2 present symptoms from items 15-20
A dataframe with a single column "PTSD_orig" containing TRUE/FALSE values indicating whether DSM-5 diagnostic criteria are met based on binarized scores
# Create sample data sample_data <- data.frame( matrix(sample(0:4, 20 * 10, replace = TRUE), nrow = 10, ncol = 20) ) colnames(sample_data) <- paste0("symptom_", 1:20) # Get diagnosis using binarized approach diagnosis_results <- create_ptsd_diagnosis_binarized(sample_data) diagnosis_results$PTSD_orig# Create sample data sample_data <- data.frame( matrix(sample(0:4, 20 * 10, replace = TRUE), nrow = 10, ncol = 20) ) colnames(sample_data) <- paste0("symptom_", 1:20) # Get diagnosis using binarized approach diagnosis_results <- create_ptsd_diagnosis_binarized(sample_data) diagnosis_results$PTSD_orig
Determines whether DSM-5 diagnostic criteria for PTSD are met based on PCL-5 item scores, using the original non-binarized values (0-4 scale).
create_ptsd_diagnosis_nonbinarized(data)create_ptsd_diagnosis_nonbinarized(data)
data |
A dataframe that can be either:
Each symptom should be scored on a 0-4 scale where:
|
The function applies the DSM-5 diagnostic criteria for PTSD:
Criterion B (Intrusion): At least 1 symptom >= 2 from items 1-5
Criterion C (Avoidance): At least 1 symptom >= 2 from items 6-7
Criterion D (Negative alterations in cognitions and mood): At least 2 symptoms >= 2 from items 8-14
Criterion E (Alterations in arousal and reactivity): At least 2 symptoms >= 2 from items 15-20
A symptom is considered present when rated 2 (Moderately) or higher.
A dataframe with all original columns (including 'total' if present) plus an additional column "PTSD_orig" containing TRUE/FALSE values indicating whether DSM-5 diagnostic criteria are met
# Example with output from rename_ptsd_columns sample_data1 <- data.frame( matrix(sample(0:4, 20 * 10, replace = TRUE), nrow = 10, ncol = 20) ) colnames(sample_data1) <- paste0("symptom_", 1:20) diagnosed_data1 <- create_ptsd_diagnosis_nonbinarized(sample_data1) # Check diagnosis results diagnosed_data1$PTSD_orig # Example with output from calculate_ptsd_total sample_data2 <- calculate_ptsd_total(sample_data1) diagnosed_data2 <- create_ptsd_diagnosis_nonbinarized(sample_data2) # Check diagnosis results diagnosed_data2$PTSD_orig# Example with output from rename_ptsd_columns sample_data1 <- data.frame( matrix(sample(0:4, 20 * 10, replace = TRUE), nrow = 10, ncol = 20) ) colnames(sample_data1) <- paste0("symptom_", 1:20) diagnosed_data1 <- create_ptsd_diagnosis_nonbinarized(sample_data1) # Check diagnosis results diagnosed_data1$PTSD_orig # Example with output from calculate_ptsd_total sample_data2 <- calculate_ptsd_total(sample_data1) diagnosed_data2 <- create_ptsd_diagnosis_nonbinarized(sample_data2) # Check diagnosis results diagnosed_data2$PTSD_orig
Formats the output of summarize_ptsd_changes() into a more readable table with proper labels and formatting of percentages and metrics.
create_readable_summary(summary_stats, DT = FALSE)create_readable_summary(summary_stats, DT = FALSE)
summary_stats |
A dataframe output from summarize_ptsd_changes() containing raw diagnostic metrics and counts |
DT |
Logical. If |
Reformats the diagnostic metrics into a presentation-ready format:
Combines counts with percentages for diagnosed/non-diagnosed cases
Rounds diagnostic accuracy metrics to 4 decimal places
Provides clear column headers for all metrics
A formatted data.frame (or a datatable widget
when DT = TRUE) with the following columns:
Scenario: Name of the diagnostic criterion
Total Diagnosed: Count and percentage of diagnosed cases
Total Non-Diagnosed: Count and percentage of non-diagnosed cases
True Positive: Count of cases diagnosed under both criteria
True Negative: Count of cases not diagnosed under either criterion
Newly Diagnosed: Count of new positive diagnoses (false positive)
Newly Non-Diagnosed: Count of new negative diagnoses (false negative)
True Cases: Total correctly classified cases
False Cases: Total misclassified cases
Sensitivity, Specificity, PPV, NPV, Accuracy, Balanced Accuracy: Diagnostic accuracy metrics (4 decimals)
# Using the output from summarize_ptsd_changes n_cases <- 100 sample_data <- data.frame( PTSD_orig = sample(c(TRUE, FALSE), n_cases, replace = TRUE), PTSD_alt1 = sample(c(TRUE, FALSE), n_cases, replace = TRUE) ) # Generate and format summary diagnostic_metrics <- summarize_ptsd_changes(sample_data) readable_summary <- create_readable_summary(diagnostic_metrics) print(readable_summary)# Using the output from summarize_ptsd_changes n_cases <- 100 sample_data <- data.frame( PTSD_orig = sample(c(TRUE, FALSE), n_cases, replace = TRUE), PTSD_alt1 = sample(c(TRUE, FALSE), n_cases, replace = TRUE) ) # Generate and format summary diagnostic_metrics <- summarize_ptsd_changes(sample_data) readable_summary <- create_readable_summary(diagnostic_metrics) print(readable_summary)
Validates PTSD diagnostic models using k-fold cross-validation to assess generalization performance and identify stable symptom combinations.
cross_validation( data, k = 5, score_by = "balanced_accuracy", seed = 123, n_symptoms = 6, n_required = 4, n_top = 3, DT = FALSE )cross_validation( data, k = 5, score_by = "balanced_accuracy", seed = 123, n_symptoms = 6, n_required = 4, n_top = 3, DT = FALSE )
data |
A dataframe containing the 20 PCL-5 item columns
|
k |
Number of folds for cross-validation (default: 5) |
score_by |
Character string specifying optimization criterion:
|
seed |
Integer for random number generation reproducibility (default: 123) |
n_symptoms |
Integer specifying how many symptoms per combination (default: 6). Must be between 1 and 20. |
n_required |
Integer specifying how many symptoms must be present for
diagnosis (default: 4). Must be between 1 and |
n_top |
Integer specifying how many top combinations to return (default: 3). Must be a positive integer. |
DT |
Logical. If |
The function:
Splits data into k stratified folds (preserving the proportion of
diagnosed cases in each fold via vfold_cv)
For each fold, trains on k-1 folds and tests on the held-out fold
Identifies symptom combinations that appear across multiple folds
Calculates average performance metrics for repeated combinations
Two models are evaluated:
Model without cluster representation: Any n_required of
n_symptoms symptoms
Model with cluster representation: n_required of
n_symptoms symptoms with at least one from each cluster
If the future.apply package is installed and a
plan has been set (e.g.,
future::plan(future::multisession)), folds are processed in
parallel via future_lapply. On macOS
(including Apple Silicon), use future::multisession rather than
future::multicore, especially inside RStudio.
A list containing:
without_clusters: Results for model without cluster representation
fold_results: List of diagnostic comparisons for each fold
summary_by_fold: Detailed results for each fold (data.frame or DT widget)
combinations_summary: Average performance for combinations appearing in multiple folds (data.frame, DT widget, or NULL if no combinations repeat)
with_clusters: Results for model with cluster representation
fold_results: List of diagnostic comparisons for each fold
summary_by_fold: Detailed results for each fold (data.frame or DT widget)
combinations_summary: Average performance for combinations appearing in multiple folds (data.frame, DT widget, or NULL if no combinations repeat)
# Use a 250-row subset of the bundled data to keep the example fast ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) # 3-fold cross-validation of a compact 3-of-4 definition (a 4-symptom # search keeps the example fast; use n_symptoms = 6, n_required = 4 for # the classic rule) cv_results <- cross_validation(ptsd_data, k = 3, n_symptoms = 4, n_required = 3) # View summary for each fold cv_results$without_clusters$summary_by_fold # View combinations that appeared multiple times cv_results$without_clusters$combinations_summary# Use a 250-row subset of the bundled data to keep the example fast ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) # 3-fold cross-validation of a compact 3-of-4 definition (a 4-symptom # search keeps the example fast; use n_symptoms = 6, n_required = 4 for # the classic rule) cv_results <- cross_validation(ptsd_data, k = 3, n_symptoms = 4, n_required = 3) # View summary for each fold cv_results$without_clusters$summary_by_fold # View combinations that appeared multiple times cv_results$without_clusters$combinations_summary
Applies a set of pre-derived symptom definitions to a dataset and returns a
performance table scoring each one against a reference standard. By default
the reference is the sample's own full DSM-5-TR diagnosis; supply
reference to score against an external standard instead (e.g. a
clinician CAPS diagnosis). Because it needs only the definitions (symptom
indices and rules) and a data frame, the same call can be run at a site
that never saw the data the definitions were derived from.
evaluate_definitions( data, definitions, include_icd11 = TRUE, reference = NULL, include_full_pcl5 = NULL, tidy = FALSE, as_percent = FALSE )evaluate_definitions( data, definitions, include_icd11 = TRUE, reference = NULL, include_full_pcl5 = NULL, tidy = FALSE, as_percent = FALSE )
data |
A dataframe with the 20 PCL-5 item columns |
definitions |
A named list of definitions, as returned by
|
include_icd11 |
Logical. If |
reference |
|
include_full_pcl5 |
Logical or |
tidy |
Logical. If |
as_percent |
Logical. Only with |
Each definition is applied with its own rule via
apply_symptom_combinations (using the definition's own
cluster structure when present, otherwise the default PCL-5 clusters, when
hierarchical = TRUE). When include_icd11 = TRUE, the ICD-11
criterion (create_icd11_diagnosis) is added as a fixed
benchmark, computed locally on the supplied data.
With an external reference, rows with a missing reference value are
excluded from the evaluation (with a message reporting how many), and a
"Full 20-item PCL-5" ceiling row is added by default: the full
DSM-5-TR PCL-5 diagnosis scored against the same reference. This row
separates the cost of using a reduced symptom set from the intrinsic
disagreement between the PCL-5 and the external standard – no reduced rule
can be expected to exceed it.
With tidy = FALSE, a formatted performance table (see
create_readable_summary): one row for the reference
standard (labelled PTSD_orig), one per definition (labelled by
rule and symptom set), plus the optional "Full 20-item PCL-5" and
"ICD-11" rows.
With tidy = TRUE, a data.frame with columns Approach,
Rank (the combination's rank within its definition),
Combination (comma-separated PCL-5 item numbers; NA for the
full-PCL-5 ceiling row), TP, FN, FP, TN,
Sensitivity, Specificity, PPV, NPV,
Accuracy, Balanced Accuracy. The reference self-comparison
row is omitted. The layout matches
summarize_top_combinations, so derivation and validation
results can be combined with rbind().
extract_definitions, as_definitions,
compare_optimizations,
summarize_top_combinations.
# Use a 250-row subset and a small 4-symptom search to keep the example # fast; omit `scenarios` to run the three default rules ptsd <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) comp <- compare_optimizations( ptsd, scenarios = list( "3/4 Non-hierarchical" = list(n_symptoms = 4, n_required = 3, hierarchical = FALSE) ), n_top = 10, show_progress = FALSE ) definitions <- extract_definitions(comp, n = 3) # Default: formatted table against the full DSM-5-TR PCL-5 diagnosis evaluate_definitions(ptsd, definitions) # Tidy analysis table (same layout as summarize_top_combinations()) evaluate_definitions(ptsd, definitions, tidy = TRUE) # Against an external reference: the bundled general-population sample # carries paired CAPS-5 items, standing in for a clinician diagnosis gp <- simulated_ptsd_genpop[1:400, ] caps <- create_caps5_diagnosis( rename_caps5_columns(gp[, paste0("C", 1:20)]) )$PTSD_caps5 ptsd_gp <- rename_ptsd_columns(gp[, c("patient_id", paste0("S", 1:20))], id_col = "patient_id") ptsd_gp$caps <- caps evaluate_definitions(ptsd_gp, definitions, reference = "caps", tidy = TRUE)# Use a 250-row subset and a small 4-symptom search to keep the example # fast; omit `scenarios` to run the three default rules ptsd <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) comp <- compare_optimizations( ptsd, scenarios = list( "3/4 Non-hierarchical" = list(n_symptoms = 4, n_required = 3, hierarchical = FALSE) ), n_top = 10, show_progress = FALSE ) definitions <- extract_definitions(comp, n = 3) # Default: formatted table against the full DSM-5-TR PCL-5 diagnosis evaluate_definitions(ptsd, definitions) # Tidy analysis table (same layout as summarize_top_combinations()) evaluate_definitions(ptsd, definitions, tidy = TRUE) # Against an external reference: the bundled general-population sample # carries paired CAPS-5 items, standing in for a clinician diagnosis gp <- simulated_ptsd_genpop[1:400, ] caps <- create_caps5_diagnosis( rename_caps5_columns(gp[, paste0("C", 1:20)]) )$PTSD_caps5 ptsd_gp <- rename_ptsd_columns(gp[, c("patient_id", paste0("S", 1:20))], id_col = "patient_id") ptsd_gp$caps <- caps evaluate_definitions(ptsd_gp, definitions, reference = "caps", tidy = TRUE)
Pulls the top symptom combinations of each optimized scenario out of a
compare_optimizations result and returns them as a compact,
shareable object. Each definition is described only by its symptom indices
and the rule needed to apply it (how many must be present, and whether
cluster representation is required), so the object contains no
participant-level data and can be shared across sites.
extract_definitions(comparison, n = 5)extract_definitions(comparison, n = 5)
comparison |
A |
n |
Integer. Number of top combinations to keep per optimized scenario (default 5). Capped at the number available. |
For each type = "optimize" scenario in the comparison, the rule
(n_required, hierarchical) is read from
comparison$config, so the only thing the user supplies is how many
combinations to carry per scenario. Fixed scenarios (e.g. ICD-11) are
skipped, because their symptom set is published rather than derived.
The result pairs with evaluate_definitions: extract the
definitions from one sample, then evaluate them in any sample.
A named list (one element per optimized scenario). Each element is a list with:
symptoms: list of integer vectors (the top-n
combinations).
n_required: integer threshold for that scenario.
hierarchical: logical, whether cluster representation is
required.
evaluate_definitions,
as_definitions for building the same object from
combinations imported with read_combinations,
compare_optimizations.
# Use a 250-row subset and a small 4-symptom search to keep the example # fast; omit `scenarios` to run the three default rules ptsd <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) comp <- compare_optimizations( ptsd, scenarios = list( "3/4 Non-hierarchical" = list(n_symptoms = 4, n_required = 3, hierarchical = FALSE) ), n_top = 10, show_progress = FALSE ) definitions <- extract_definitions(comp, n = 5) lapply(definitions, function(d) d$symptoms)# Use a 250-row subset and a small 4-symptom search to keep the example # fast; omit `scenarios` to run the three default rules ptsd <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) comp <- compare_optimizations( ptsd, scenarios = list( "3/4 Non-hierarchical" = list(n_symptoms = 4, n_required = 3, hierarchical = FALSE) ), n_top = 10, show_progress = FALSE ) definitions <- extract_definitions(comp, n = 5) lapply(definitions, function(d) d$symptoms)
Validates PTSD diagnostic models using a train-test split approach (holdout validation). Trains the model on a portion of the data and evaluates performance on the held-out test set.
holdout_validation( data, train_ratio = 0.7, score_by = "balanced_accuracy", seed = 123, n_symptoms = 6, n_required = 4, n_top = 3, DT = FALSE )holdout_validation( data, train_ratio = 0.7, score_by = "balanced_accuracy", seed = 123, n_symptoms = 6, n_required = 4, n_top = 3, DT = FALSE )
data |
A dataframe containing the 20 PCL-5 item columns
|
train_ratio |
Numeric between 0 and 1 indicating proportion of data for training (default: 0.7 for 70/30 split) |
score_by |
Character string specifying optimization criterion:
|
seed |
Integer for random number generation reproducibility (default: 123) |
n_symptoms |
Integer specifying how many symptoms per combination (default: 6). Must be between 1 and 20. |
n_required |
Integer specifying how many symptoms must be present for
diagnosis (default: 4). Must be between 1 and |
n_top |
Integer specifying how many top combinations to return (default: 3). Must be a positive integer. |
DT |
Logical. If |
The function:
Splits data into training and test sets based on train_ratio
Finds optimal symptom combinations on training data
Evaluates these combinations on test data
Compares results to original DSM-5 diagnoses
Two models are evaluated:
Model without cluster representation: Any n_required of
n_symptoms symptoms
Model with cluster representation: n_required of
n_symptoms symptoms with at least one from each cluster
A list containing:
without_clusters: Results for model without cluster representation
best_combinations: The n_top best symptom combinations
from training
test_results: Diagnostic comparison on test data
summary: Formatted summary statistics (data.frame or DT widget)
with_clusters: Results for model with cluster representation
best_combinations: The n_top best symptom combinations
from training
test_results: Diagnostic comparison on test data
summary: Formatted summary statistics (data.frame or DT widget)
# Use a 250-row subset of the bundled data to keep the example fast ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) # Validate a compact 3-of-5 definition (a 5-symptom search keeps the # example fast; use n_symptoms = 6, n_required = 4 for the classic rule) validation_results <- holdout_validation(ptsd_data, train_ratio = 0.7, n_symptoms = 5, n_required = 3) # Access results validation_results$without_clusters$summary validation_results$with_clusters$summary# Use a 250-row subset of the bundled data to keep the example fast ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) # Validate a compact 3-of-5 definition (a 5-symptom search keeps the # example fast; use n_symptoms = 6, n_required = 4 for the classic rule) validation_results <- holdout_validation(ptsd_data, train_ratio = 0.7, n_symptoms = 5, n_required = 3) # Access results validation_results$without_clusters$summary validation_results$with_clusters$summary
Identifies the best symptom combinations for PTSD diagnosis where a specified number of symptoms must be present, regardless of their cluster membership. This is a generalized version that allows configuring the number of symptoms per combination, the required threshold, and how many top results to return.
optimize_combinations( data, n_symptoms = 6, n_required = 4, n_top = 3, score_by = "balanced_accuracy", DT = FALSE, show_progress = TRUE )optimize_combinations( data, n_symptoms = 6, n_required = 4, n_top = 3, score_by = "balanced_accuracy", DT = FALSE, show_progress = TRUE )
data |
A dataframe containing exactly 20 columns with PCL-5 item scores
(output of
|
n_symptoms |
Integer specifying how many symptoms per combination (default: 6). Must be between 1 and 20. |
n_required |
Integer specifying how many symptoms must be present for
diagnosis (default: 4). Must be between 1 and |
n_top |
Integer specifying how many top combinations to return (default: 3). Must be a positive integer. |
score_by |
Character string specifying optimization criterion:
|
DT |
Logical. If |
show_progress |
Logical. If |
The function:
Tests all possible combinations of n_symptoms symptoms from the
20 PCL-5 items
Requires n_required symptoms to be present (>=2 on original 0-4
scale) for diagnosis
Identifies the n_top combinations that best match the original
DSM-5 diagnosis
Optimization can be based on:
Maximizing balanced accuracy, the mean of sensitivity and specificity (the default)
Minimizing false cases (both false positives and false negatives)
Minimizing only false negatives (newly non-diagnosed cases)
The symptom clusters in PCL-5 are:
Items 1-5: Intrusion symptoms (Criterion B)
Items 6-7: Avoidance symptoms (Criterion C)
Items 8-14: Negative alterations in cognitions and mood (Criterion D)
Items 15-20: Alterations in arousal and reactivity (Criterion E)
A list containing:
best_symptoms: List of n_top vectors, each containing
n_symptoms symptom numbers representing the best combinations found
diagnosis_comparison: Dataframe comparing original DSM-5 diagnosis with
diagnoses based on the best combinations. If data carried
non-symptom columns (e.g. an ID column added via
rename_ptsd_columns), those are prepended in original order.
summary: Diagnostic accuracy metrics for each combination. A data.frame
by default, or an interactive datatable if
DT = TRUE.
n_tied: Integer. Number of additional combinations that scored
identically to the best combination but are not included in the top
results. When n_tied > 0, the reported "best" combination is one
of several equivalent solutions. Ties are broken by lexicographic order
of symptom indices.
# Use a 250-row subset of the bundled data to keep the example fast ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) # Find best 6-symptom combinations requiring 4 present (classic defaults, # optimized for balanced accuracy) results <- optimize_combinations(ptsd_data, n_symptoms = 6, n_required = 4, score_by = "balanced_accuracy") # Find best 5-symptom combinations requiring 3 present, return top 5, # this time minimizing total misclassifications results2 <- optimize_combinations(ptsd_data, n_symptoms = 5, n_required = 3, n_top = 5, score_by = "accuracy") # Get symptom numbers results$best_symptoms # Check how many combinations tied with the best results$n_tied # View summary statistics results$summary# Use a 250-row subset of the bundled data to keep the example fast ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) # Find best 6-symptom combinations requiring 4 present (classic defaults, # optimized for balanced accuracy) results <- optimize_combinations(ptsd_data, n_symptoms = 6, n_required = 4, score_by = "balanced_accuracy") # Find best 5-symptom combinations requiring 3 present, return top 5, # this time minimizing total misclassifications results2 <- optimize_combinations(ptsd_data, n_symptoms = 5, n_required = 3, n_top = 5, score_by = "accuracy") # Get symptom numbers results$best_symptoms # Check how many combinations tied with the best results$n_tied # View summary statistics results$summary
Identifies the best symptom combinations for PTSD diagnosis where a specified number of symptoms must be present and must include at least one symptom from each defined cluster. This maintains the hierarchical structure of the diagnostic criteria while allowing configurable parameters.
optimize_combinations_clusters( data, n_symptoms = 6, n_required = 4, n_top = 3, score_by = "balanced_accuracy", clusters, DT = FALSE, show_progress = TRUE )optimize_combinations_clusters( data, n_symptoms = 6, n_required = 4, n_top = 3, score_by = "balanced_accuracy", clusters, DT = FALSE, show_progress = TRUE )
data |
A dataframe containing exactly 20 columns with PCL-5 item scores
(output of
|
n_symptoms |
Integer specifying how many symptoms per combination (default: 6). Must be at least as large as the number of clusters. |
n_required |
Integer specifying how many symptoms must be present for
diagnosis (default: 4). Must be between 1 and |
n_top |
Integer specifying how many top combinations to return (default: 3). Must be a positive integer. |
score_by |
Character string specifying optimization criterion:
|
clusters |
A named list of integer vectors defining the cluster structure. Each list element represents one cluster, with the integer vector specifying which symptom indices belong to that cluster. Cluster elements must not overlap. This parameter is required (no default). For PCL-5:
|
DT |
Logical. If |
show_progress |
Logical. If |
The function:
Generates valid combinations ensuring representation from all clusters
Requires n_required symptoms to be present (>=2 on original 0-4
scale) for diagnosis
Validates that present symptoms include at least one from each cluster
Identifies the n_top combinations that best match the original
DSM-5 diagnosis
The clusters parameter must be a named list specifying the cluster
structure. For PCL-5, the standard clusters are:
Cluster B (Intrusion): Items 1-5
Cluster C (Avoidance): Items 6-7
Cluster D (Negative alterations in cognitions and mood): Items 8-14
Cluster E (Alterations in arousal and reactivity): Items 15-20
Optimization can be based on:
Maximizing balanced accuracy, the mean of sensitivity and specificity (the default)
Minimizing false cases (both false positives and false negatives)
Minimizing only false negatives (newly non-diagnosed cases)
A list containing:
best_symptoms: List of n_top vectors, each containing
n_symptoms symptom numbers representing the best combinations found
diagnosis_comparison: Dataframe comparing original DSM-5 diagnosis with
diagnoses based on the best combinations. If data carried
non-symptom columns (e.g. an ID column added via
rename_ptsd_columns), those are prepended in original order.
summary: Diagnostic accuracy metrics for each combination. A data.frame
by default, or an interactive datatable if
DT = TRUE.
# Use a 250-row subset of the bundled data to keep the example fast ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) # Find best hierarchical combinations with PCL-5 clusters (a 5-symptom # search keeps the example fast; the classic rule uses n_symptoms = 6, # n_required = 4) pcl5_clusters <- list(B = 1:5, C = 6:7, D = 8:14, E = 15:20) results <- optimize_combinations_clusters(ptsd_data, n_symptoms = 5, n_required = 3, score_by = "balanced_accuracy", clusters = pcl5_clusters) # Get symptom numbers results$best_symptoms # View summary statistics results$summary# Use a 250-row subset of the bundled data to keep the example fast ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) # Find best hierarchical combinations with PCL-5 clusters (a 5-symptom # search keeps the example fast; the classic rule uses n_symptoms = 6, # n_required = 4) pcl5_clusters <- list(B = 1:5, C = 6:7, D = 8:14, E = 15:20) results <- optimize_combinations_clusters(ptsd_data, n_symptoms = 5, n_required = 3, score_by = "balanced_accuracy", clusters = pcl5_clusters) # Get symptom numbers results$best_symptoms # View summary statistics results$summary
Visualises how often each of the 20 PCL-5 symptoms is selected across the
top combinations of each optimization scenario in a
compare_optimizations result. Replicates the symptom-frequency
heatmap (Figure 1) of the PTSDdiag preprint and helps identify "core"
symptoms that recur across data-driven combinations.
plot_symptom_frequency( comparison, type = c("relative", "absolute"), show_overall = TRUE, overall_includes_fixed = FALSE, symptom_labels = NULL, low_colour = "#f7fbff", high_colour = "#084594" )plot_symptom_frequency( comparison, type = c("relative", "absolute"), show_overall = TRUE, overall_includes_fixed = FALSE, symptom_labels = NULL, low_colour = "#f7fbff", high_colour = "#084594" )
comparison |
A |
type |
|
show_overall |
Logical. Include the pooled |
overall_includes_fixed |
Logical. If |
symptom_labels |
Optional character vector of length 20 used to label
the x-axis ticks. Default uses the numeric indices |
low_colour, high_colour
|
Gradient endpoints for the fill scale. |
Each tile shows the frequency with which a symptom appears in the stored
combinations of a scenario. Fixed criteria (e.g. ICD-11) appear as rows
with cells at RelFreq = 1 on their included symptoms and
RelFreq = 0 elsewhere. The optional OVERALL row pools across
optimization scenarios by default (set
overall_includes_fixed = TRUE to include fixed criteria in the
pool). It is rendered in a separate facet so it is visually distinct from
the per-scenario rows.
Requires the ggplot2 package.
A ggplot object. Users can extend it with additional
layers, themes, or labels via the usual + operator.
compare_optimizations,
symptom_frequency,
summarize_top_combinations.
# Use a 250-row subset and a small 4-symptom search to keep the example # fast; omit `scenarios` to run the three default rules ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) comp <- compare_optimizations( ptsd_data, scenarios = list( "3/4 Non-hierarchical" = list(n_symptoms = 4, n_required = 3, hierarchical = FALSE) ), include_icd11 = TRUE, n_top = 5, show_progress = FALSE ) plot_symptom_frequency(comp)# Use a 250-row subset and a small 4-symptom search to keep the example # fast; omit `scenarios` to run the three default rules ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) comp <- compare_optimizations( ptsd_data, scenarios = list( "3/4 Non-hierarchical" = list(n_symptoms = 4, n_required = 3, hierarchical = FALSE) ), include_icd11 = TRUE, n_top = 5, show_progress = FALSE ) plot_symptom_frequency(comp)
Print method for ptsdiag_comparison objects
## S3 method for class 'ptsdiag_comparison' print(x, ...)## S3 method for class 'ptsdiag_comparison' print(x, ...)
x |
A |
... |
Unused. |
The input object invisibly.
Imports symptom combinations from a JSON file previously created by
write_combinations. The returned list contains all fields
needed to apply the combinations to new data via
apply_symptom_combinations.
read_combinations(file)read_combinations(file)
file |
Character string. Path to the JSON file to read. |
The function validates the imported data using the same checks as
apply_symptom_combinations, ensuring that the file contains
valid combinations, a valid n_required threshold, and (if present)
a valid cluster structure.
If the file was created with a different version of PTSDdiag than the one currently installed, an informational message is displayed.
A named list with the following elements:
List of numeric vectors. Each vector contains symptom indices for one combination.
Character vector of canonical combination IDs (sorted
symptom indices joined by underscores, e.g. "4_6_7_17_19_20").
Computed from the combinations if not present in the file (backward
compatibility with files created before v0.2.1).
Integer vector of ranks (1 = best). Computed from list position if not present in the file.
Numeric. Number of symptoms required for a positive diagnosis.
NULL for non-hierarchical combinations, or a named
list of numeric vectors defining the cluster structure.
Named list with additional metadata: n_symptoms
and score_by (may be NULL if not recorded).
Character string with the user-provided description.
Character string with the display label stored by
write_combinations, or NA for files written without
one (including files from versions before 0.4.0). Used by
as_definitions to name the definition.
Character string indicating which package version created the file.
Character string with the creation timestamp.
The returned list carries class ptsdiag_spec, so it can be handed
directly to as_definitions or evaluate_definitions
(which converts it automatically). For row-level diagnoses, the
combinations, n_required, and clusters elements can
also be passed to apply_symptom_combinations:
spec <- read_combinations("my_combos.json")
result <- apply_symptom_combinations(
data, spec$combinations, spec$n_required, spec$clusters
)
write_combinations to export combinations to a JSON file.
as_definitions to convert one or more imported specifications
into the definitions list used by evaluate_definitions.
apply_symptom_combinations to apply imported combinations
to new data.
# Write example combinations my_combos <- list( c(1, 6, 8, 10, 15, 19), c(2, 7, 9, 11, 16, 20) ) tmp <- tempfile(fileext = ".json") write_combinations(my_combos, tmp, n_required = 4, score_by = "balanced_accuracy") # Read them back spec <- read_combinations(tmp) spec$combinations spec$n_required # Apply to data (example workflow) # comparison <- apply_symptom_combinations( # new_data, spec$combinations, spec$n_required, spec$clusters # ) # Clean up unlink(tmp)# Write example combinations my_combos <- list( c(1, 6, 8, 10, 15, 19), c(2, 7, 9, 11, 16, 20) ) tmp <- tempfile(fileext = ".json") write_combinations(my_combos, tmp, n_required = 4, score_by = "balanced_accuracy") # Read them back spec <- read_combinations(tmp) spec$combinations spec$n_required # Apply to data (example workflow) # comparison <- apply_symptom_combinations( # new_data, spec$combinations, spec$n_required, spec$clusters # ) # Clean up unlink(tmp)
Standardizes column names in CAPS-5 (Clinician-Administered PTSD Scale for
DSM-5) data by renaming them to a consistent format (symptom_1 through
symptom_20). This standardization allows CAPS-5 data to be used with
the same downstream functions as PCL-5 data (e.g.,
create_caps5_diagnosis, apply_symptom_combinations).
rename_caps5_columns(data, id_col = NULL)rename_caps5_columns(data, id_col = NULL)
data |
A dataframe containing exactly 20 columns of CAPS-5 item severity
ratings (plus any columns named in
|
id_col |
Optional character vector naming column(s) in |
The function assumes the input data contains exactly 20 columns corresponding
to the 20 CAPS-5 items. Each item is a single severity rating (0–4) assigned
by the clinician, who combines information about frequency and intensity into
that score. The columns are renamed sequentially from symptom_1 to
symptom_20, maintaining their original order.
The CAPS-5 items map to the same DSM-5 PTSD symptom clusters as the PCL-5:
symptom_1 to symptom_5: Intrusion symptoms (Criterion B)
symptom_6 to symptom_7: Avoidance symptoms (Criterion C)
symptom_8 to symptom_14: Negative alterations in cognitions and mood (Criterion D)
symptom_15 to symptom_20: Alterations in arousal and reactivity (Criterion E)
The output naming (symptom_1:symptom_20) is intentionally identical to
the PCL-5 convention so that downstream functions such as
apply_symptom_combinations and
compare_diagnostic_systems work transparently on CAPS-5 data.
A dataframe with CAPS-5 columns renamed to symptom_1 through
symptom_20. If id_col is supplied, the named columns are
prepended (in original order).
rename_ptsd_columns for the PCL-5 equivalent.
create_caps5_diagnosis for computing a CAPS-5 DSM-5 diagnosis
from the renamed data.
# Example with simulated CAPS-5 data caps5_data <- data.frame( matrix(sample(0:4, 20 * 10, replace = TRUE), nrow = 10, ncol = 20) ) renamed_caps5 <- rename_caps5_columns(caps5_data) colnames(renamed_caps5) # symptom_1 through symptom_20# Example with simulated CAPS-5 data caps5_data <- data.frame( matrix(sample(0:4, 20 * 10, replace = TRUE), nrow = 10, ncol = 20) ) renamed_caps5 <- rename_caps5_columns(caps5_data) colnames(renamed_caps5) # symptom_1 through symptom_20
Standardizes column names in PCL-5 (PTSD Checklist for DSM-5) data by renaming them to a consistent format (symptom_1 through symptom_20). This standardization is essential for subsequent analyses using other functions in the package.
rename_ptsd_columns(data, id_col = NULL)rename_ptsd_columns(data, id_col = NULL)
data |
A dataframe containing exactly 20 columns of PCL-5 item scores
(plus any columns named in
|
id_col |
Optional character vector naming column(s) in |
The function assumes the input data contains exactly 20 columns corresponding to the 20 items of the PCL-5. The columns are renamed sequentially from symptom_1 to symptom_20, maintaining their original order. The PCL-5 items correspond to different symptom clusters:
symptom_1 to symptom_5: Intrusion symptoms (Criterion B)
symptom_6 to symptom_7: Avoidance symptoms (Criterion C)
symptom_8 to symptom_14: Negative alterations in cognitions and mood (Criterion D)
symptom_15 to symptom_20: Alterations in arousal and reactivity (Criterion E)
A dataframe with PCL-5 columns renamed to symptom_1 through
symptom_20. If id_col is supplied, the named columns are
prepended (in original order).
check_pcl5_data for a pre-flight check that reports
every data problem at once before this step.
# Example with a sample PCL-5 dataset sample_data <- data.frame( matrix(sample(0:4, 20 * 10, replace = TRUE), nrow = 10, ncol = 20) ) renamed_data <- rename_ptsd_columns(sample_data) colnames(renamed_data) # Shows new column names # Carry a participant identifier through the workflow sample_data$patient_id <- sprintf("P%03d", seq_len(nrow(sample_data))) with_id <- rename_ptsd_columns(sample_data, id_col = "patient_id") head(with_id)# Example with a sample PCL-5 dataset sample_data <- data.frame( matrix(sample(0:4, 20 * 10, replace = TRUE), nrow = 10, ncol = 20) ) renamed_data <- rename_ptsd_columns(sample_data) colnames(renamed_data) # Shows new column names # Carry a participant identifier through the workflow sample_data$patient_id <- sprintf("P%03d", seq_len(nrow(sample_data))) with_id <- rename_ptsd_columns(sample_data, id_col = "patient_id") head(with_id)
Scores every candidate combination of n_symptoms PCL-5
items against the full DSM-5-TR diagnosis and returns the complete ranked
table – not just the best ones. This is the exhaustive companion to
optimize_combinations /
optimize_combinations_clusters (which keep only the top
n_top): use it to study how performance decays across the whole
candidate set, e.g. to show that many symptom sets are near-interchangeable
(a plateau of near-optimal combinations followed by a drop).
score_all_combinations( data, n_symptoms = 6, n_required = 4, clusters = NULL, score_by = "balanced_accuracy", chunk_size = 1000, show_progress = TRUE )score_all_combinations( data, n_symptoms = 6, n_required = 4, clusters = NULL, score_by = "balanced_accuracy", chunk_size = 1000, show_progress = TRUE )
data |
A dataframe with the 20 PCL-5 item columns |
n_symptoms |
Integer. Number of items per combination (default 6). |
n_required |
Integer. How many of the items must be present (score >= 2) for a positive diagnosis (default 4). |
clusters |
|
score_by |
Character. Metric that defines the ranking:
|
chunk_size |
Integer. Number of combinations scored per chunk (default 1000). Affects speed and parallel granularity only, never the result. |
show_progress |
Logical. If |
With clusters = NULL, all choose(20, n_symptoms) subsets are
scored (38,760 for six symptoms). With a cluster structure, only the
combinations containing at least one item per cluster are scored (13,685
six-symptom sets for the default PCL-5 clusters), and the diagnosis
additionally requires the present symptoms to span all clusters – the same
candidate set and rule as optimize_combinations_clusters.
The hierarchical per-row cluster check makes this mode noticeably slower.
Combinations are processed in chunks. If the future.apply package is
installed and a future plan is set (e.g.
future::plan(future::multisession)), chunks are scored in parallel;
otherwise they are scored sequentially with a progress bar. Results are
identical either way.
The returned combination_id uses the same canonical format as
write_combinations (sorted item numbers joined by
underscores), so the full curve can be joined against exported top-k
combinations.
A data.frame with one row per candidate combination, sorted
best-to-worst by score_by (ties broken by combination_id
for determinism):
rank: 1 = best.
combination_id: sorted item numbers joined by underscores
(e.g. "1_6_8_10_15_19").
tp, fn, fp, tn: the 2x2 counts against
the full DSM-5-TR diagnosis.
sensitivity, specificity, ppv, npv,
accuracy, balanced_accuracy: metrics on the 0-1 scale
(NA where a denominator is zero).
The attributes n_symptoms, n_required, clusters,
score_by, and n_combinations record the configuration.
optimize_combinations,
optimize_combinations_clusters,
compare_optimizations.
# A 4-symptom search on a 250-row subset keeps the example fast ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) curve <- score_all_combinations(ptsd_data, n_symptoms = 4, n_required = 3, show_progress = FALSE) nrow(curve) # choose(20, 4) = 4845 combinations, all ranked head(curve) # The full balanced-accuracy curve, e.g. for a rank plot: # plot(curve$rank, curve$balanced_accuracy, type = "l", log = "x")# A 4-symptom search on a 250-row subset keeps the example fast ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) curve <- score_all_combinations(ptsd_data, n_symptoms = 4, n_required = 3, show_progress = FALSE) nrow(curve) # choose(20, 4) = 4845 combinations, all ranked head(curve) # The full balanced-accuracy curve, e.g. for a rank plot: # plot(curve$rank, curve$balanced_accuracy, type = "l", log = "x")
A dataset containing simulated responses from 5,000 patients on the PCL-5
(PTSD Checklist for DSM-5). Each patient rated 20 PTSD symptoms on a scale
from 0 to 4. The dataset additionally ships with three demographic columns
(patient_id, age, sex) so that the demographic
carry-through workflow (id_col) can be demonstrated end-to-end on
the bundled data.
simulated_ptsdsimulated_ptsd
A data frame with 5,000 rows and 23 columns:
Character. Synthetic participant identifier
("P0001"–"P5000").
Integer. Age in years (truncated normal, range 18–80).
Factor. "female" / "male".
Intrusive memories
Nightmares
Flashbacks
Emotional reactivity to reminders
Physical reactions to reminders
Avoiding memories/thoughts/feelings
Avoiding external reminders
Amnesia
Strong negative beliefs
Distorted blame
Negative trauma-related emotions
Decreased interest in activities
Detachment or estrangement
Trouble experiencing positive emotions
Irritability/aggression
Risk-taking behavior
Hypervigilance
Heightened startle reaction
Difficulty concentrating
Sleep problems
The symptoms are rated on a 5-point scale:
0 = Not at all
1 = A little bit
2 = Moderately
3 = Quite a bit
4 = Extremely
The symptoms correspond to DSM-5 PTSD criteria:
Symptoms 1-5: Criterion B (Intrusion)
Symptoms 6-7: Criterion C (Avoidance)
Symptoms 8-14: Criterion D (Negative alterations in cognitions and mood)
Symptoms 15-20: Criterion E (Alterations in arousal and reactivity)
Symptoms were simulated independently. Real PCL-5 data exhibits within-cluster correlations (e.g., between intrusion symptoms). Optimization results on these data are for illustration only; real-world performance should be evaluated on empirical datasets.
Simulated data for demonstration purposes
A dataset containing simulated responses from 1,200 individuals in a general
population sample on the PCL-5 (PTSD Checklist for DSM-5). Each individual
rated 20 PTSD symptoms on a scale from 0 to 4. This dataset has a lower
PTSD prevalence (~21
simulated_ptsd (~94
validation of diagnostic criteria derived from clinical samples. Like
simulated_ptsd, it ships with three demographic columns
(patient_id, age, sex). It also ships paired
clinician-administered CAPS-5 severity ratings (C1–C20) for
the same participants, simulated to correlate with the PCL-5 items so that
the PCL-5 and CAPS-5 total scores correlate about 0.8 (the level usually
reported empirically). This supports the paired-instrument workflow in the
CAPS-5 vignette without inventing data inline.
simulated_ptsd_genpopsimulated_ptsd_genpop
A data frame with 1,200 rows and 43 columns:
Character. Synthetic participant identifier
("G0001"–"G1200").
Integer. Age in years (truncated normal, range 18–80).
Factor. "female" / "male".
PCL-5: Intrusive memories
PCL-5: Nightmares
PCL-5: Flashbacks
PCL-5: Emotional reactivity to reminders
PCL-5: Physical reactions to reminders
PCL-5: Avoiding memories/thoughts/feelings
PCL-5: Avoiding external reminders
PCL-5: Amnesia
PCL-5: Strong negative beliefs
PCL-5: Distorted blame
PCL-5: Negative trauma-related emotions
PCL-5: Decreased interest in activities
PCL-5: Detachment or estrangement
PCL-5: Trouble experiencing positive emotions
PCL-5: Irritability/aggression
PCL-5: Risk-taking behavior
PCL-5: Hypervigilance
PCL-5: Heightened startle reaction
PCL-5: Difficulty concentrating
PCL-5: Sleep problems
CAPS-5: Intrusive memories
CAPS-5: Nightmares
CAPS-5: Flashbacks
CAPS-5: Emotional reactivity to reminders
CAPS-5: Physical reactions to reminders
CAPS-5: Avoiding memories/thoughts/feelings
CAPS-5: Avoiding external reminders
CAPS-5: Amnesia
CAPS-5: Strong negative beliefs
CAPS-5: Distorted blame
CAPS-5: Negative trauma-related emotions
CAPS-5: Decreased interest in activities
CAPS-5: Detachment or estrangement
CAPS-5: Trouble experiencing positive emotions
CAPS-5: Irritability/aggression
CAPS-5: Risk-taking behavior
CAPS-5: Hypervigilance
CAPS-5: Heightened startle reaction
CAPS-5: Difficulty concentrating
CAPS-5: Sleep problems
The CAPS-5 items (C1–C20) are paired clinician-administered
severity ratings for the same participants and the same symptoms as the
PCL-5 items, simulated to correlate with them at a total-score r of about
0.8.
The symptoms are rated on a 5-point scale:
0 = Not at all
1 = A little bit
2 = Moderately
3 = Quite a bit
4 = Extremely
The symptoms correspond to DSM-5 PTSD criteria:
Symptoms 1-5: Criterion B (Intrusion)
Symptoms 6-7: Criterion C (Avoidance)
Symptoms 8-14: Criterion D (Negative alterations in cognitions and mood)
Symptoms 15-20: Criterion E (Alterations in arousal and reactivity)
The data was simulated as a mixture of approximately 17% individuals with elevated symptom profiles (PTSD-like) and 83% with low symptom levels, resulting in approximately 21% meeting full DSM-5 diagnostic criteria.
Symptoms were simulated independently. Real PCL-5 data exhibits within-cluster correlations (e.g., between intrusion symptoms). Optimization results on these data are for illustration only; real-world performance should be evaluated on empirical datasets.
Simulated data for demonstration purposes
simulated_ptsd for the clinical veteran sample with
higher prevalence.
Creates a summary of PCL-5 total scores and PTSD diagnoses, including mean total score, standard deviation, and number of positive diagnoses.
summarize_ptsd(data)summarize_ptsd(data)
data |
A dataframe containing at minimum:
|
This function calculates key summary statistics for PCL-5 data:
Mean total score (severity indicator)
Standard deviation of total scores (variability in severity)
Count of positive PTSD diagnoses (prevalence in the sample)
A dataframe with one row containing:
mean_total: Mean PCL-5 total score
sd_total: Standard deviation of PCL-5 total scores
n_diagnosed: Number of positive PTSD diagnoses
# Create sample data sample_data <- data.frame( total = sample(0:80, 100, replace = TRUE), PTSD_orig = sample(c(TRUE, FALSE), 100, replace = TRUE) ) # Generate summary statistics summary_stats <- summarize_ptsd(sample_data) print(summary_stats)# Create sample data sample_data <- data.frame( total = sample(0:80, 100, replace = TRUE), PTSD_orig = sample(c(TRUE, FALSE), 100, replace = TRUE) ) # Generate summary statistics summary_stats <- summarize_ptsd(sample_data) print(summary_stats)
Compares different PTSD diagnostic criteria by calculating diagnostic accuracy metrics and changes in diagnosis status relative to a baseline criterion.
summarize_ptsd_changes(data)summarize_ptsd_changes(data)
data |
A dataframe where:
|
The function calculates multiple diagnostic metrics comparing each diagnostic criterion to a baseline criterion (PTSD_orig):
Basic counts:
Number and percentage of diagnosed/non-diagnosed cases per criterion
Number of newly diagnosed (false positive) and newly non-diagnosed (false negative) cases
True positive and true negative cases
Diagnostic accuracy metrics:
Sensitivity: Proportion of true PTSD cases correctly identified
Specificity: Proportion of non-PTSD cases correctly identified
PPV (Positive Predictive Value): Probability that a positive diagnosis is correct
NPV (Negative Predictive Value): Probability that a negative diagnosis is correct
A dataframe containing the following columns for each diagnostic criterion:
column: Name of the diagnostic criterion
diagnosed: Number of cases diagnosed as PTSD
non_diagnosed: Number of cases not diagnosed as PTSD
diagnosed_percent: Percentage of cases diagnosed
non_diagnosed_percent: Percentage of cases not diagnosed
newly_diagnosed: Cases diagnosed under new but not baseline criterion (false positive)
newly_nondiagnosed: Cases diagnosed under baseline but not new criterion (false negative)
true_positive: Cases diagnosed under both criteria
true_negative: Cases not diagnosed under either criterion
true_cases: Sum of true positives and true negatives
false_cases: Sum of newly diagnosed (false positive) and newly non-diagnosed (false negative)
sensitivity, specificity, ppv, npv: Standard diagnostic accuracy metrics
accuracy: Proportion of cases classified the same as the baseline criterion ((true positives + true negatives) / total)
balanced_accuracy: Mean of sensitivity and specificity ((sensitivity + specificity) / 2), robust to imbalanced prevalence of the baseline diagnosis
# Create sample diagnostic data set.seed(123) n_cases <- 100 sample_data <- data.frame( PTSD_orig = sample(c(TRUE, FALSE), n_cases, replace = TRUE), PTSD_alt1 = sample(c(TRUE, FALSE), n_cases, replace = TRUE), PTSD_alt2 = sample(c(TRUE, FALSE), n_cases, replace = TRUE) ) # Calculate diagnostic metrics diagnostic_metrics <- summarize_ptsd_changes(sample_data) diagnostic_metrics# Create sample diagnostic data set.seed(123) n_cases <- 100 sample_data <- data.frame( PTSD_orig = sample(c(TRUE, FALSE), n_cases, replace = TRUE), PTSD_alt1 = sample(c(TRUE, FALSE), n_cases, replace = TRUE), PTSD_alt2 = sample(c(TRUE, FALSE), n_cases, replace = TRUE) ) # Calculate diagnostic metrics diagnostic_metrics <- summarize_ptsd_changes(sample_data) diagnostic_metrics
Produces a manuscript-ready table summarising the diagnostic performance of
each top combination (or fixed criterion) in a
compare_optimizations result. The output matches the layout
of the PTSDdiag preprint's Table 2: one row per combination, with
Approach / Rank / Combination / TP / FN / FP / TN / Sensitivity /
Specificity / PPV / NPV / Accuracy / Balanced Accuracy.
summarize_top_combinations(comparison, top_n = NULL, as_percent = FALSE)summarize_top_combinations(comparison, top_n = NULL, as_percent = FALSE)
comparison |
A |
top_n |
Optional integer. Per-scenario limit on combinations to
include. Fixed scenarios always contribute exactly one row. Default
|
as_percent |
Logical. If |
For each scenario, the per-row diagnosis_comparison dataframe is
summarised via summarize_ptsd_changes. The self-comparison
PTSD_orig row is dropped, the remaining rows are renamed, and the
scenario label is prepended.
Sensitivity, specificity, PPV, NPV, accuracy and balanced accuracy are
returned on the 0-1 fraction scale by default (matching
compare_diagnostic_systems); set as_percent = TRUE to
convert to 0-100 for manuscript display. Accuracy is (TP + TN) / N,
the quantity maximised by score_by = "accuracy"; balanced accuracy
is (sensitivity + specificity) / 2, the quantity maximised by the
default score_by = "balanced_accuracy".
A data.frame with columns: Approach, Rank,
Combination, TP, FN, FP, TN,
Sensitivity, Specificity, PPV, NPV,
Accuracy, Balanced Accuracy.
compare_optimizations,
symptom_frequency,
plot_symptom_frequency.
# Use a 250-row subset and a small 4-symptom search to keep the example # fast; omit `scenarios` to run the three default rules ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) comp <- compare_optimizations( ptsd_data, scenarios = list( "3/4 Non-hierarchical" = list(n_symptoms = 4, n_required = 3, hierarchical = FALSE) ), include_icd11 = TRUE, n_top = 5, show_progress = FALSE ) summarize_top_combinations(comp, as_percent = TRUE)# Use a 250-row subset and a small 4-symptom search to keep the example # fast; omit `scenarios` to run the three default rules ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) comp <- compare_optimizations( ptsd_data, scenarios = list( "3/4 Non-hierarchical" = list(n_symptoms = 4, n_required = 3, hierarchical = FALSE) ), include_icd11 = TRUE, n_top = 5, show_progress = FALSE ) summarize_top_combinations(comp, as_percent = TRUE)
Returns a long-format data frame giving how often each of the 20 PCL-5
symptoms appears in the top combinations of each scenario in a
compare_optimizations result. This is the data source for
plot_symptom_frequency and matches the structure of the
preprint's Supplementary Table S4.
symptom_frequency( comparison, include_overall = TRUE, overall_includes_fixed = FALSE )symptom_frequency( comparison, include_overall = TRUE, overall_includes_fixed = FALSE )
comparison |
A |
include_overall |
Logical. If |
overall_includes_fixed |
Logical. If |
For optimize scenarios, Count ranges from 0 to n_top (the
number of stored combinations). For fixed scenarios such as ICD-11, the
fixed symptom set contributes exactly one combination so Count is
either 0 or 1. RelFreq normalises Count by the number of
combinations stored in that scenario.
The optional OVERALL row pools counts across scenarios. By default
fixed scenarios are excluded from the OVERALL pool so that OVERALL
continues to reflect data-driven symptom selection. Set
overall_includes_fixed = TRUE to weight every combination equally.
A data.frame with columns Symptom (integer 1-20),
Approach (factor with levels in scenario order, optionally ending
in "OVERALL"), Count (integer), RelFreq (numeric in
\[0, 1\]).
compare_optimizations,
plot_symptom_frequency,
summarize_top_combinations.
# Use a 250-row subset and a small 4-symptom search to keep the example # fast; omit `scenarios` to run the three default rules ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) comp <- compare_optimizations( ptsd_data, scenarios = list( "3/4 Non-hierarchical" = list(n_symptoms = 4, n_required = 3, hierarchical = FALSE) ), include_icd11 = TRUE, n_top = 5, show_progress = FALSE ) freq <- symptom_frequency(comp) head(freq)# Use a 250-row subset and a small 4-symptom search to keep the example # fast; omit `scenarios` to run the three default rules ptsd_data <- rename_ptsd_columns(simulated_ptsd[1:250, ], id_col = c("patient_id", "age", "sex")) comp <- compare_optimizations( ptsd_data, scenarios = list( "3/4 Non-hierarchical" = list(n_symptoms = 4, n_required = 3, hierarchical = FALSE) ), include_icd11 = TRUE, n_top = 5, show_progress = FALSE ) freq <- symptom_frequency(comp) head(freq)
Exports optimized symptom combinations to a human-readable JSON file. This enables sharing of derived combinations across research groups without needing to share raw data, supporting reproducible derivation-validation workflows.
write_combinations( combinations, file, n_required = 4, clusters = NULL, n_symptoms = NULL, score_by = NULL, description = "", label = NULL )write_combinations( combinations, file, n_required = 4, clusters = NULL, n_symptoms = NULL, score_by = NULL, description = "", label = NULL )
combinations |
A list of integer vectors specifying symptom
combinations, or the full result object from
|
file |
Character string. Path to the output JSON file. |
n_required |
Integer specifying how many symptoms must be present for
a positive diagnosis (default: 4). This value is stored in the file so
that |
clusters |
|
n_symptoms |
Integer or |
score_by |
Character or |
description |
Character string. Optional free-text description of the derivation context (e.g., sample characteristics, dataset name). Default is an empty string. |
label |
Character string or |
The JSON file contains the combinations alongside metadata needed to apply
them via apply_symptom_combinations: the required symptom
threshold (n_required) and optional cluster structure
(clusters). Additional fields (score_by, n_symptoms,
description) provide context for reproducibility.
The combinations argument can be either:
A list of integer vectors (e.g., from results$best_symptoms)
The full result object from optimize_combinations or
optimize_combinations_clusters (the function automatically
extracts $best_symptoms)
The file path (invisibly), following the convention of
write.csv.
read_combinations to import combinations from a JSON file.
optimize_combinations and
optimize_combinations_clusters for deriving optimal
combinations.
apply_symptom_combinations for applying imported combinations
to new data.
# Create example combinations my_combos <- list( c(1, 6, 8, 10, 15, 19), c(2, 7, 9, 11, 16, 20) ) # Write to a temporary file tmp <- tempfile(fileext = ".json") write_combinations(my_combos, tmp, n_required = 4, score_by = "balanced_accuracy", description = "Example non-hierarchical combinations") # Can also pass a full optimization result directly: # write_combinations(optimization_result, tmp, n_required = 4) # Clean up unlink(tmp)# Create example combinations my_combos <- list( c(1, 6, 8, 10, 15, 19), c(2, 7, 9, 11, 16, 20) ) # Write to a temporary file tmp <- tempfile(fileext = ".json") write_combinations(my_combos, tmp, n_required = 4, score_by = "balanced_accuracy", description = "Example non-hierarchical combinations") # Can also pass a full optimization result directly: # write_combinations(optimization_result, tmp, n_required = 4) # Clean up unlink(tmp)