Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Analyzes cfDNA methylation patterns for cancer detection using cfMeDIP-seq or bisulfite sequencing with MethylDackel. Identifies cancer-specific methylation signatures and performs tissue-of-origin deconvolution. Use when using methylation biomarkers for early cancer detection or minimal residual disease.
.claude/skills/bio-methylation-based-detection/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | — | — |
| case-13 | ✗→✓ | ▲ Improved | — | — |
| case-19 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✗→✓ | ▲ Improved | — | — |
| case-03 | ✗→✓ | ▲ Improved | — | — |
Reference examples tested with: Bismark 0.24+, numpy 1.26+, pandas 2.2+, pysam 0.22+, scipy 1.12+, statsmodels 0.14+
Before using code patterns, verify installed versions match. If versions differ:
pip show <package> then help(module.function) to check signatures<tool> --version then <tool> --help to confirm flagsIf code throws ImportError, AttributeError, or TypeError, introspect the installed package and adapt the example to match the actual API rather than retrying.
"Detect cancer from cfDNA methylation patterns" → Analyze cell-free DNA methylation for multi-cancer early detection and tissue-of-origin classification using bisulfite or enzymatic conversion.
MethylDackel extract for methylation calling from cfDNA bisulfite dataAnalyze cfDNA methylation for cancer detection and tissue-of-origin analysis.
| Method | Description | cfDNA Input | |--------|-------------|-------------| | cfMeDIP-seq | Enrichment-based, good for low input | >= 5 ng | | Bisulfite-seq | Single-base resolution | >= 10 ng | | EM-seq | Enzymatic, less degradation | >= 10 ng |
MethylDackel is actively maintained and integrated with nf-core/methylseq.
bash# Extract methylation from bisulfite BAM MethylDackel extract \ reference.fa \ sample_bismark.bam \ --CHG \ --CHH \ -o sample_methylation # Output: sample_methylation_CpG.bedGraph, etc. # Merge C and G strand calls MethylDackel mergeContext \ reference.fa \ sample_methylation
pythonimport subprocess import pandas as pd import numpy as np def extract_methylation(bam_file, reference, output_prefix, min_depth=5): ''' Extract methylation from bisulfite-seq BAM using MethylDackel. ''' subprocess.run([ 'MethylDackel', 'extract', reference, bam_file, '-o', output_prefix, '--minDepth', str(min_depth), '--mergeContext' ], check=True) # Parse output bedgraph = f'{output_prefix}_CpG.bedGraph' meth = pd.read_csv(bedgraph, sep='\t', header=None, names=['chrom', 'start', 'end', 'meth_pct', 'meth', 'unmeth']) return meth def calculate_methylation_beta(meth_df): '''Calculate beta values (0-1 scale).''' meth_df['beta'] = meth_df['meth'] / (meth_df['meth'] + meth_df['unmeth']) return meth_df
pythondef find_differentially_methylated_regions(cancer_samples, normal_samples, min_diff=0.2): ''' Find differentially methylated regions between cancer and normal. Args: cancer_samples: List of methylation DataFrames normal_samples: List of methylation DataFrames min_diff: Minimum beta difference ''' from scipy import stats # Merge samples cancer_betas = pd.concat([s['beta'] for s in cancer_samples], axis=1) normal_betas = pd.concat([s['beta'] for s in normal_samples], axis=1) results = [] for idx in cancer_betas.index: c_vals = cancer_betas.loc[idx].dropna() n_vals = normal_betas.loc[idx].dropna() if len(c_vals) < 3 or len(n_vals) < 3: continue diff = c_vals.mean() - n_vals.mean() stat, pval = stats.mannwhitneyu(c_vals, n_vals, alternative='two-sided') if abs(diff) >= min_diff: results.append({ 'region': idx, 'cancer_mean': c_vals.mean(), 'normal_mean': n_vals.mean(), 'diff': diff, 'pvalue': pval }) results_df = pd.DataFrame(results) # FDR correction from statsmodels.stats.multitest import multipletests if len(results_df) > 0: _, results_df['fdr'], _, _ = multipletests(results_df['pvalue'], method='fdr_bh') return results_df.sort_values('fdr')
Goal: Estimate the tissue-of-origin composition of cfDNA by decomposing its methylation profile against a reference atlas of tissue-specific methylomes.
Approach: Align sample beta values to reference atlas regions, then solve for non-negative tissue proportions using constrained least squares (NNLS) and normalize to sum to one.
pythondef tissue_deconvolution(sample_meth, reference_atlas): ''' Deconvolve tissue composition from cfDNA methylation. Args: sample_meth: Sample methylation DataFrame reference_atlas: Reference methylomes per tissue type ''' from scipy.optimize import nnls # Align samples to reference regions common_regions = sample_meth.index.intersection(reference_atlas.index) sample_vec = sample_meth.loc[common_regions, 'beta'].values ref_matrix = reference_atlas.loc[common_regions].values # Non-negative least squares for proportions proportions, residual = nnls(ref_matrix, sample_vec) # Normalize to sum to 1 proportions = proportions / proportions.sum() return dict(zip(reference_atlas.columns, proportions))
pythondef analyze_mced_regions(meth_df, mced_regions): ''' Analyze multi-cancer early detection (MCED) regions. Similar to Galleri-style analysis. ''' results = {} for cancer_type, regions in mced_regions.items(): region_betas = meth_df[meth_df['chrom'].isin(regions)] results[cancer_type] = { 'mean_beta': region_betas['beta'].mean(), 'hypermethylated_frac': (region_betas['beta'] > 0.8).mean(), 'hypomethylated_frac': (region_betas['beta'] < 0.2).mean() } return results
pythondef analyze_cfmedip(bam_file, output_prefix, genome_bins): ''' Analyze cfMeDIP-seq data for methylation enrichment. ''' import pysam bam = pysam.AlignmentFile(bam_file, 'rb') bin_counts = {} for chrom, start, end in genome_bins: count = bam.count(chrom, start, end) bin_counts[(chrom, start, end)] = count bam.close() # Normalize by total reads and bin size total = sum(bin_counts.values()) for key in bin_counts: bin_size = key[2] - key[1] bin_counts[key] = (bin_counts[key] / total) * 1e6 / (bin_size / 1000) # RPM per kb return bin_counts
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 19 counted toward the lift figure. The other 3 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +32 percentage points is the difference between those two pass rates over the 19 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.