bio-longitudinal-monitoring

$npx mdskill add GPTomics/bioSkills/bio-longitudinal-monitoring

Monitors ctDNA dynamics over time for treatment response and relapse detection

  • Tracks tumor fraction and mutation trends across serial liquid biopsies
  • Uses pandas and matplotlib for data analysis and visualization
  • Analyzes clearance kinetics and defines molecular response criteria
  • Delivers insights on treatment efficacy and early relapse signals

SKILL.md

.github/skills/bio-longitudinal-monitoringView on GitHub ↗
---
name: bio-longitudinal-monitoring
description: Tracks ctDNA dynamics over time for treatment response monitoring using serial liquid biopsy samples. Analyzes tumor fraction trends, mutation clearance kinetics, and defines molecular response criteria. Use when monitoring patients during therapy or detecting molecular relapse before clinical progression.
tool_type: python
primary_tool: pandas
---

## Version Compatibility

Reference examples tested with: matplotlib 3.8+, numpy 1.26+, pandas 2.2+, scipy 1.12+

Before using code patterns, verify installed versions match. If versions differ:
- Python: `pip show <package>` then `help(module.function)` to check signatures

If code throws ImportError, AttributeError, or TypeError, introspect the installed
package and adapt the example to match the actual API rather than retrying.

# Longitudinal Monitoring

**"Track ctDNA levels over my patient's treatment"** → Monitor tumor fraction and mutation dynamics across serial liquid biopsy timepoints for treatment response assessment and early relapse detection.
- Python: `pandas` + `matplotlib` for trend analysis and molecular response classification

Track ctDNA dynamics over treatment for response assessment and relapse detection.

## Key Metrics

| Metric | Description | Clinical Relevance |
|--------|-------------|-------------------|
| Tumor fraction trend | Change over time | Response/progression |
| Mutation clearance | Time to undetectable | Depth of response |
| Molecular relapse | ctDNA rise | Early relapse detection |
| Lead time | ctDNA vs imaging | Months before clinical |

## Tracking Tumor Fraction

```python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt


def analyze_tf_dynamics(patient_data):
    '''
    Analyze tumor fraction dynamics over treatment.

    Args:
        patient_data: DataFrame with columns [sample_id, timepoint, tumor_fraction, treatment_phase]
    '''
    # Sort by timepoint
    patient_data = patient_data.sort_values('timepoint')

    # Calculate log2 fold changes
    baseline_tf = patient_data.iloc[0]['tumor_fraction']
    patient_data['log2_fc'] = np.log2(patient_data['tumor_fraction'] / baseline_tf)

    # Calculate response metrics
    min_tf = patient_data['tumor_fraction'].min()
    min_timepoint = patient_data.loc[patient_data['tumor_fraction'].idxmin(), 'timepoint']

    metrics = {
        'baseline_tf': baseline_tf,
        'nadir_tf': min_tf,
        'nadir_timepoint': min_timepoint,
        'max_reduction': baseline_tf - min_tf,
        'log2_max_reduction': np.log2(baseline_tf / min_tf) if min_tf > 0 else np.inf
    }

    return patient_data, metrics


def define_response(tf_series, baseline, criteria='2log'):
    '''
    Define molecular response based on tumor fraction changes.

    Args:
        tf_series: Series of tumor fractions
        baseline: Baseline tumor fraction
        criteria: Response criteria (e.g., '2log' for 2-log reduction)
    '''
    if criteria == '2log':
        # 2-log (100-fold) reduction
        threshold = baseline / 100
    elif criteria == '1log':
        threshold = baseline / 10
    elif criteria == 'undetectable':
        threshold = 0.001  # Assay-dependent LOD

    response = tf_series < threshold
    return response
```

## Mutation Tracking

```python
def track_mutations(mutation_data):
    '''
    Track specific mutations across timepoints.

    Args:
        mutation_data: DataFrame with [timepoint, mutation, vaf]
    '''
    # Pivot to get VAF per mutation per timepoint
    pivot = mutation_data.pivot_table(
        index='timepoint',
        columns='mutation',
        values='vaf',
        aggfunc='first'
    )

    # Calculate mean VAF trend
    pivot['mean_vaf'] = pivot.mean(axis=1)

    # Identify cleared mutations
    last_timepoint = pivot.index.max()
    cleared = []
    for mut in pivot.columns:
        if mut == 'mean_vaf':
            continue
        if pivot.loc[last_timepoint, mut] < 0.001 or pd.isna(pivot.loc[last_timepoint, mut]):
            cleared.append(mut)

    return pivot, cleared


def calculate_clearance_kinetics(mutation_data, mutation):
    '''
    Calculate mutation clearance half-life.
    '''
    mut_data = mutation_data[mutation_data['mutation'] == mutation].sort_values('timepoint')

    if len(mut_data) < 3:
        return None

    # Log-linear regression for exponential decay
    from scipy import stats

    x = mut_data['timepoint'].values
    y = np.log(mut_data['vaf'].values + 1e-6)  # Add small value to avoid log(0)

    slope, intercept, r_value, p_value, std_err = stats.linregress(x, y)

    # Half-life = ln(2) / |slope|
    half_life = np.log(2) / abs(slope) if slope < 0 else np.inf

    return {
        'mutation': mutation,
        'half_life': half_life,
        'slope': slope,
        'r_squared': r_value**2
    }
```

## Relapse Detection

```python
def detect_molecular_relapse(tf_series, baseline, threshold_increase=2):
    '''
    Detect molecular relapse from tumor fraction series.

    Args:
        tf_series: DataFrame with [timepoint, tumor_fraction]
        baseline: Post-treatment nadir
        threshold_increase: Fold-increase to call relapse
    '''
    tf_series = tf_series.sort_values('timepoint')

    # Find nadir
    nadir_idx = tf_series['tumor_fraction'].idxmin()
    nadir_tf = tf_series.loc[nadir_idx, 'tumor_fraction']
    nadir_time = tf_series.loc[nadir_idx, 'timepoint']

    # Check for increase after nadir
    post_nadir = tf_series[tf_series['timepoint'] > nadir_time]

    relapse_detected = False
    relapse_timepoint = None

    for idx, row in post_nadir.iterrows():
        if row['tumor_fraction'] > nadir_tf * threshold_increase:
            relapse_detected = True
            relapse_timepoint = row['timepoint']
            break

    return {
        'nadir_tf': nadir_tf,
        'nadir_timepoint': nadir_time,
        'relapse_detected': relapse_detected,
        'relapse_timepoint': relapse_timepoint
    }
```

## Visualization

```python
def plot_ctdna_dynamics(patient_data, treatment_lines=None, output_file=None):
    '''
    Plot ctDNA dynamics over treatment.
    '''
    fig, ax = plt.subplots(figsize=(10, 6))

    # Plot tumor fraction on log scale
    ax.semilogy(patient_data['timepoint'], patient_data['tumor_fraction'],
                'o-', linewidth=2, markersize=8)

    # Add treatment lines
    if treatment_lines:
        for time, label in treatment_lines:
            ax.axvline(x=time, color='gray', linestyle='--', alpha=0.5)
            ax.text(time, ax.get_ylim()[1], label, rotation=90, va='top')

    ax.set_xlabel('Time (days)')
    ax.set_ylabel('Tumor Fraction')
    ax.set_title('ctDNA Dynamics During Treatment')

    # Add LOD line
    ax.axhline(y=0.01, color='red', linestyle=':', alpha=0.5, label='LOD')

    ax.legend()

    if output_file:
        plt.savefig(output_file, dpi=150, bbox_inches='tight')

    return fig, ax
```

## Clinical Integration

**Goal:** Generate a structured monitoring report combining tumor fraction dynamics, mutation clearance status, and molecular response classification for clinical decision support.

**Approach:** Aggregate tumor fraction trend analysis, mutation tracking pivot tables, and response criteria into a single report dictionary with standardized molecular response categories.

```python
def generate_monitoring_report(patient_id, tf_data, mutation_data, imaging_data=None):
    '''
    Generate clinical monitoring report.
    '''
    report = {
        'patient_id': patient_id,
        'analysis_date': pd.Timestamp.now()
    }

    # Tumor fraction analysis
    tf_data, tf_metrics = analyze_tf_dynamics(tf_data)
    report['tumor_fraction'] = tf_metrics

    # Mutation analysis
    mutation_pivot, cleared = track_mutations(mutation_data)
    report['mutations_tracked'] = len(mutation_pivot.columns) - 1
    report['mutations_cleared'] = len(cleared)

    # Response assessment
    current_tf = tf_data.iloc[-1]['tumor_fraction']
    baseline_tf = tf_data.iloc[0]['tumor_fraction']

    if current_tf < 0.001:
        report['response'] = 'Complete molecular response'
    elif current_tf < baseline_tf * 0.01:
        report['response'] = 'Major molecular response (>2 log)'
    elif current_tf < baseline_tf * 0.5:
        report['response'] = 'Partial molecular response'
    else:
        report['response'] = 'Stable/Progressive'

    return report
```

## Related Skills

- ctdna-mutation-detection - Detect mutations to track
- tumor-fraction-estimation - Estimate tumor fraction per timepoint
- fragment-analysis - Complement with fragmentomics trends

More from GPTomics/bioSkills

SkillDescription
bio-admet-predictionPredicts ADMET properties using ADMETlab 3.0 API or DeepChem models. Estimates bioavailability, CYP inhibition, hERG liability, and 119 toxicity endpoints with uncertainty quantification. Filters for PAINS and other structural alerts. Use when filtering compounds for drug-likeness or prioritizing leads by predicted safety.
bio-alignment-amplicon-clippingTrim PCR primers from aligned reads in amplicon-panel BAMs using samtools ampliconclip. Use when processing SARS-CoV-2 ARTIC, hereditary cancer panels, ctDNA hot-spot panels, or any amplicon assay where primer-derived bases would falsely confirm reference at primer footprints.
bio-alignment-filteringFilter alignments by flags, mapping quality, and regions using samtools view and pysam. Use when extracting specific reads, removing low-quality alignments, or subsetting to target regions.
bio-alignment-indexingCreate and use BAI/CSI indices for BAM/CRAM files using samtools and pysam. Use when enabling random access to alignment files or fetching specific genomic regions.
bio-alignment-ioRead, write, and convert multiple sequence alignment files using Biopython Bio.AlignIO. Supports Clustal, PHYLIP, Stockholm, FASTA, Nexus, and other alignment formats for phylogenetics and conservation analysis. Use when reading, writing, or converting alignment file formats.
bio-alignment-msa-parsingParse and analyze multiple sequence alignments using Biopython. Extract sequences, identify conserved regions, analyze gaps, work with annotations, and manipulate alignment data for downstream analysis. Use when parsing or manipulating multiple sequence alignments.
bio-alignment-msa-statisticsCalculate alignment statistics including sequence identity, conservation scores, substitution matrices, and similarity metrics. Use when comparing alignment quality, measuring sequence divergence, and analyzing evolutionary patterns.
bio-alignment-multiplePerform multiple sequence alignment using MAFFT, MUSCLE5, ClustalOmega, or T-Coffee. Guides tool and algorithm selection based on dataset size, sequence divergence, and downstream application. Use when aligning three or more homologous sequences for phylogenetics, conservation analysis, or evolutionary studies.
bio-alignment-pairwisePerform pairwise sequence alignment using Biopython Bio.Align.PairwiseAligner. Use when comparing two sequences, finding optimal alignments, scoring similarity, and identifying local or global matches between DNA, RNA, or protein sequences.
bio-alignment-sortingSort alignment files by coordinate or read name using samtools and pysam. Use when preparing BAM files for indexing, variant calling, or paired-end analysis.