diagnostic Module#

Diagnostic test performance evaluation.

This module provides functions for calculating diagnostic test performance measures: sensitivity, specificity, predictive values, likelihood ratios, and ROC curve analysis.

Classes#

class episia.stats.diagnostic.DiagnosticMeasure(value)[source]#

Bases: Enum

Diagnostic performance measures.

ACCURACY = 'accuracy'#
LR_NEGATIVE = 'lr_negative'#
LR_POSITIVE = 'lr_positive'#
NPV = 'npv'#
PPV = 'ppv'#
SENSITIVITY = 'sensitivity'#
SPECIFICITY = 'specificity'#
YOUDEN = 'youden'#
class episia.stats.diagnostic.DiagnosticResult(tp, fp, fn, tn, sensitivity, specificity, ppv, npv, lr_positive, lr_negative, accuracy, youden, prevalence=None)[source]#

Bases: object

Rich result object for diagnostic test calculations.

Parameters:
__repr__()[source]#

Return repr(self).

Return type:

str

accuracy: float#
fn: int#
fp: int#
lr_negative: float#
lr_positive: float#
npv: float#
ppv: float#
prevalence: float | None = None#
sensitivity: float#
specificity: float#
summary()[source]#

Generate text summary.

Return type:

str

tn: int#
to_dict()[source]#

Convert result to dictionary.

Return type:

Dict

tp: int#
youden: float#
class episia.stats.diagnostic.ROCResult(fpr, tpr, thresholds, auc, optimal_threshold, optimal_point, method='youden')[source]#

Bases: object

Result object for ROC curve analysis.

Parameters:
__repr__()[source]#

Return repr(self).

Return type:

str

auc: float#
fpr: ndarray#
method: str = 'youden'#
optimal_point: Dict[str, float]#
optimal_threshold: float#
plot(backend='plotly', **kwargs)[source]#

Plot the ROC curve — opens in browser (Plotly) or returns Figure (Matplotlib).

Parameters:

backend (str)

show()[source]#

Convenience: plot() then show() — equivalent to roc.plot().show().

thresholds: ndarray#
to_dict()[source]#

Return a JSON-serializable dictionary.

Return type:

Dict

tpr: ndarray#

Functions#

episia.stats.diagnostic.diagnostic_test_2x2(tp, fp, fn, tn, prevalence=None)[source]#

Calculate diagnostic test performance from 2x2 table.

Parameters:
  • tp (int) – True positives

  • fp (int) – False positives

  • fn (int) – False negatives

  • tn (int) – True negatives

  • prevalence (float | None) – Disease prevalence (for PPV/NPV if different from sample)

Returns:

DiagnosticResult object

Return type:

DiagnosticResult

Example

>>> result = diagnostic_test_2x2(80, 20, 10, 90)
>>> print(result.sensitivity)
0.8889
episia.stats.diagnostic.diagnostic_from_data(y_true, y_pred, threshold=0.5)[source]#

Calculate diagnostic measures from true labels and predicted scores.

Parameters:
  • y_true (ndarray) – True binary labels (0 or 1)

  • y_pred (ndarray) – Predicted scores or probabilities

  • threshold (float) – Classification threshold

Returns:

DiagnosticResult object

Return type:

DiagnosticResult

episia.stats.diagnostic.roc_analysis(y_true, y_score, method='youden', **kwargs)[source]#

Perform ROC curve analysis.

Parameters:
  • y_true (ndarray) – True binary labels

  • y_score (ndarray) – Predicted scores or probabilities

  • method (str) – Method for optimal threshold selection: ‘youden’ (default), ‘closest_topleft’, ‘max_accuracy’

Returns:

ROCResult object

Return type:

ROCResult

episia.stats.diagnostic.likelihood_ratio_ci(lr, tp, fp, fn, tn, confidence=0.95)[source]#

Calculate confidence interval for likelihood ratio.

Parameters:
  • lr (float) – Likelihood ratio (positive or negative)

  • tp (int) – 2x2 table counts

  • fp (int) – 2x2 table counts

  • fn (int) – 2x2 table counts

  • tn (int) – 2x2 table counts

  • confidence (float) – Confidence level

Returns:

Tuple of (lower, upper) CI bounds

Return type:

Tuple[float, float]

episia.stats.diagnostic.predictive_values_from_sens_spec(sensitivity, specificity, prevalence)[source]#

Calculate PPV and NPV from sensitivity, specificity, and prevalence.

Parameters:
  • sensitivity (float) – Test sensitivity

  • specificity (float) – Test specificity

  • prevalence (float) – Disease prevalence

Returns:

Tuple of (PPV, NPV)

Return type:

Tuple[float, float]

episia.stats.diagnostic.fagan_nomogram(pre_test_prob, lr)[source]#

Calculate post-test probability using Fagan’s nomogram method.

Parameters:
  • pre_test_prob (float) – Pre-test probability (0-1)

  • lr (float) – Likelihood ratio (positive or negative)

Returns:

Post-test probability

Return type:

float

episia.stats.diagnostic.diagnostic_accuracy_ci(accuracy, n, confidence=0.95)[source]#

Calculate confidence interval for diagnostic accuracy.

Parameters:
  • accuracy (float) – Observed accuracy

  • n (int) – Total sample size

  • confidence (float) – Confidence level

Returns:

Tuple of (lower, upper) CI bounds

Return type:

Tuple[float, float]

episia.stats.diagnostic.compare_diagnostic_tests(test1, test2, paired=False, n=None)[source]#

Compare two diagnostic tests.

Parameters:
  • test1 (DiagnosticResult) – First test results

  • test2 (DiagnosticResult) – Second test results

  • paired (bool) – Whether tests were applied to same subjects

  • n (int | None) – Number of subjects (required if paired=True)

Returns:

Dictionary with comparison statistics

Return type:

Dict[str, float]

Find optimal threshold using multiple criteria.

Parameters:
  • y_true (ndarray) – True labels

  • y_score (ndarray) – Predicted scores

  • criteria (List[str]) – List of criteria to optimize

  • thresholds (ndarray | None) – Specific thresholds to test (optional)

Returns:

Dictionary with optimal thresholds for each criterion

Return type:

Dict[str, Dict]

Examples#

Basic diagnostic test evaluation:

from episia.stats.diagnostic import diagnostic_test_2x2

# True positives, False positives, False negatives, True negatives
result = diagnostic_test_2x2(tp=80, fp=20, fn=10, tn=90)

print(f"Sensitivity: {result.sensitivity:.3f}")
print(f"Specificity: {result.specificity:.3f}")
print(f"PPV: {result.ppv:.3f}")
print(f"NPV: {result.npv:.3f}")
print(f"LR+: {result.lr_positive:.3f}")
print(f"LR-: {result.lr_negative:.3f}")

# Summary
print(result.summary())

ROC curve analysis:

import numpy as np
from episia.stats.diagnostic import roc_analysis

# True labels and predicted probabilities
y_true = np.array([1, 1, 0, 0, 1, 0, 1, 0])
y_score = np.array([0.9, 0.8, 0.3, 0.2, 0.7, 0.4, 0.6, 0.1])

roc = roc_analysis(y_true, y_score, method='youden')
print(roc)
print(f"AUC: {roc.auc:.3f}")
print(f"Optimal threshold: {roc.optimal_threshold:.3f}")

# Plot ROC curve
roc.plot().show()

Fagan’s nomogram:

from episia.stats.diagnostic import fagan_nomogram

# Pre-test probability 20%, LR+ = 10
post_prob = fagan_nomogram(pre_test_prob=0.2, lr=10)
print(f"Post-test probability: {post_prob:.1%}")

Comparing two tests:

result1 = diagnostic_test_2x2(tp=80, fp=20, fn=10, tn=90)
result2 = diagnostic_test_2x2(tp=75, fp=15, fn=15, tn=95)

comparison = compare_diagnostic_tests(result1, result2)
print(f"Sensitivity difference: {comparison['sensitivity_difference']:.3f}")