[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M7ECCV2S2X3THVTNPCCXZXNK":3,"skill-same:01M7ECCV2S2X3THVTNPCCXZXNK":62},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":30,"commitSha":31,"skillDirectory":32,"forksCount":14,"repositoryHtmlUrl":33,"repositoryHomepageUrl":33,"ownerHtmlUrl":34,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":35,"install":36,"resources":49},"01M7ECCV2S2X3THVTNPCCXZXNK","bio-copy-number-cnv-visualization",null,"Visualize copy number profiles, segments, allele-specific tracks, and cohort patterns from CNVkit, GATK, ASCAT, FACETS, Sequenza, and other callers. Covers genome-wide and per-chromosome log2 scatter plots, B-allele-frequency\u002Fminor-allele-fraction tracks, ideograms, cohort heatmaps, circos views, and caller-native plots. Use when creating publication CNV figures, choosing which plot answers a given question, diagnosing a wrong diploid baseline visually, displaying loss of heterozygosity, or deciding what depth-only plots cannot reveal.","Mr-Nobody-Anonymous\u002FAll-skills","Mr-Nobody-Anonymous","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F184253119?v=4","01M7AEXFJHKTYQW5R0ZCQQTWFV","anonymous","01M7C00M3GQR4QKP9GZA42GFAK",0,"MIT","https:\u002F\u002Fgithub.com\u002FMr-Nobody-Anonymous\u002FAll-skills\u002Fblob\u002Fmain\u002Fawesome_skills\u002Fbiology\u002Fbio-copy-number-cnv-visualization\u002FSKILL.md",[],[],"2026-11-15 14:19:17",false,"---\nname: bio-copy-number-cnv-visualization\ndescription: Visualize copy number profiles, segments, allele-specific tracks, and cohort patterns from CNVkit, GATK, ASCAT, FACETS, Sequenza, and other callers. Covers genome-wide and per-chromosome log2 scatter plots, B-allele-frequency\u002Fminor-allele-fraction tracks, ideograms, cohort heatmaps, circos views, and caller-native plots. Use when creating publication CNV figures, choosing which plot answers a given question, diagnosing a wrong diploid baseline visually, displaying loss of heterozygosity, or deciding what depth-only plots cannot reveal.\ntool_type: mixed\nprimary_tool: matplotlib\nsource: \"https:\u002F\u002Fgithub.com\u002FGPTomics\u002FbioSkills\"\nsource_repository: \"GPTomics\u002FbioSkills\"\nsource_path: \"copy-number\u002Fcnv-visualization\u002FSKILL.md\"\nlicense: \"MIT\"\nimported_at: \"2026-09-20\"\n---\n\n## Version Compatibility\n\nReference examples tested with: matplotlib 3.8+, pandas 2.2+, numpy 1.26+, seaborn 0.13+, CNVkit 0.9.10+, GATK 4.5+; R 4.3+ with ggplot2 3.5+.\n\nBefore using code patterns, verify installed versions match. If versions differ:\n- Python: `pip show matplotlib pandas` then `help(function)` for signatures\n- R: `packageVersion('ggplot2')` then `?function_name`\n- CLI: `cnvkit.py version`, `gatk --version`\n\nIf code throws ImportError, AttributeError, or TypeError, introspect the installed package and adapt the example rather than retrying.\n\n# CNV Visualization\n\n**\"Plot my copy number profile\"** -> A CNV figure is an argument, not a picture. The plot type, the y-axis quantity, and where the diploid baseline sits all determine what the reader can conclude. The single most important rule: a depth-only log2 plot cannot show loss of heterozygosity, cannot show tumor purity, and silently misleads if the diploid baseline is centered on a non-diploid mode.\n\n- CLI: `cnvkit.py scatter` \u002F `diagram` \u002F `heatmap`; `gatk PlotModeledSegments`\n- Python: `matplotlib` for custom genome-wide and allele-specific tracks\n- R: `ggplot2`, `karyoploteR` for publication ideograms\n\n## Plot Selection — What Each View Reveals and Hides\n\n| Plot | Answers | Reveals | Cannot show |\n|------|---------|---------|-------------|\n| Genome-wide log2 scatter + segments | Where are the gains\u002Flosses? | Focal vs broad events, noise level | LOH, purity, allele-specific state |\n| Per-chromosome scatter | Is this focal event real and where are its boundaries? | Breakpoints, bin support, weight | Absolute CN without purity |\n| BAF \u002F minor-allele-fraction track | Is there allelic imbalance \u002F LOH? | CN-neutral LOH, mirrored imbalance | Total copy number alone |\n| Combined log2 + BAF (two-panel) | What is the allele-specific state? | Gains vs CN-LOH vs balanced | — (this is the complete view) |\n| Cohort heatmap | What is recurrent across samples? | Shared arm\u002Ffocal events | Per-sample breakpoint detail |\n| Ideogram \u002F diagram | Where do events sit relative to cytobands\u002Fgenes? | Gene-level context | Quantitative amplitude |\n| Circos | Genome-wide CNV + SV breakpoints together | CNV-SV co-localization | Fine amplitude detail |\n| Caller-native (GATK\u002FASCAT\u002FFACETS) | Did the caller fit correctly? | Model fit, segment confidence | — (diagnostic, not publication) |\n\nThe decision rule: if the biological question involves LOH, allele-specific gain, or whole-genome doubling, a log2-only plot is insufficient — pair it with a BAF track.\n\n## CNVkit Built-in Plots\n\n```bash\ncnvkit.py scatter sample.cnr -s sample.cns -o scatter.png            # genome-wide\ncnvkit.py scatter sample.cnr -s sample.cns -c chr17 -o chr17.png      # one chromosome\ncnvkit.py scatter sample.cnr -s sample.cns -v sample.vcf.gz -o baf.png  # with BAF panel\ncnvkit.py diagram sample.cnr -s sample.cns -o diagram.pdf             # ideogram\ncnvkit.py heatmap cohort\u002F*.cns -d -o cohort_heatmap.pdf              # cohort, desaturated\n```\n\nPassing `-v` with a VCF adds a B-allele-frequency panel — use it whenever LOH matters.\n\n## Genome-Wide log2 Profile with Segments\n\n**Goal:** Render a publication genome-wide CNV profile with colored segments.\n\n**Approach:** Map per-bin log2 to cumulative genomic coordinates, plot bins as faint points, overlay segment medians as colored horizontal lines, mark chromosome boundaries.\n\n```python\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\ndef plot_genome_profile(cnr_file, cns_file, output=None, gain=0.3, loss=-0.3):\n    '''Genome-wide log2 scatter with segment overlay.'''\n    cnr = pd.read_csv(cnr_file, sep='\\t')\n    cns = pd.read_csv(cns_file, sep='\\t')\n    chroms = [f'chr{i}' for i in range(1, 23)] + ['chrX', 'chrY']\n\n    offsets, cum = {}, 0\n    for c in chroms:\n        sub = cnr[cnr['chromosome'] == c]\n        if sub.empty:\n            continue\n        offsets[c] = cum\n        cum += sub['end'].max()\n\n    cnr = cnr[cnr['chromosome'].isin(offsets)].copy()\n    cnr['x'] = cnr.apply(lambda r: offsets[r['chromosome']] + r['start'], axis=1)\n\n    fig, ax = plt.subplots(figsize=(16, 4))\n    ax.scatter(cnr['x'], cnr['log2'], s=1, c='0.7', alpha=0.5, rasterized=True)\n    for _, seg in cns.iterrows():\n        if seg['chromosome'] not in offsets:\n            continue\n        x0 = offsets[seg['chromosome']] + seg['start']\n        x1 = offsets[seg['chromosome']] + seg['end']\n        color = 'red' if seg['log2'] > gain else 'blue' if seg['log2'] \u003C loss else '0.3'\n        ax.hlines(seg['log2'], x0, x1, colors=color, linewidth=2.5)\n    for c, x in offsets.items():\n        ax.axvline(x, color='0.9', linewidth=0.5)\n    ax.axhline(0, color='black', linewidth=0.6)\n    ax.set_ylim(-2, 2)\n    ax.set_ylabel('log2 copy ratio')\n    ax.set_xlabel('genomic position')\n    fig.tight_layout()\n    if output:\n        fig.savefig(output, dpi=200)\n    return fig, ax\n```\n\n## Combined log2 + B-Allele-Frequency Panel\n\n**Goal:** Show total copy number and allelic imbalance together so CN-neutral LOH and allele-specific gains are visible.\n\n**Approach:** Stack two axes — log2 on top, BAF below. Mirror BAF about 0.5 so allelic imbalance reads as deviation from the center line.\n\n```python\nimport numpy as np\n\ndef plot_log2_baf(cnr_file, baf_df, output=None):\n    '''Two-panel plot: log2 copy ratio above, B-allele frequency below.\n    baf_df: columns chromosome, position, baf (germline-het sites only).'''\n    cnr = pd.read_csv(cnr_file, sep='\\t')\n    fig, (ax_cn, ax_baf) = plt.subplots(2, 1, figsize=(16, 6), sharex=True)\n\n    ax_cn.scatter(range(len(cnr)), cnr['log2'], s=1, c='0.6', alpha=0.5)\n    ax_cn.axhline(0, color='black', linewidth=0.6)\n    ax_cn.set_ylabel('log2 ratio')\n    ax_cn.set_ylim(-2, 2)\n\n    # Plot BAF and its mirror; a tight band at 0.5 = balanced, split bands = imbalance\u002FLOH\n    ax_baf.scatter(range(len(baf_df)), baf_df['baf'], s=2, c='0.4', alpha=0.5)\n    ax_baf.scatter(range(len(baf_df)), 1 - baf_df['baf'], s=2, c='0.4', alpha=0.5)\n    ax_baf.axhline(0.5, color='black', linewidth=0.6)\n    ax_baf.set_ylabel('B-allele frequency')\n    ax_baf.set_ylim(0, 1)\n    ax_baf.set_xlabel('het SNP index')\n    fig.tight_layout()\n    if output:\n        fig.savefig(output, dpi=200)\n    return fig\n```\n\nA copy-neutral LOH region shows log2 ~ 0 but BAF splitting away from 0.5 — invisible on any log2-only plot.\n\n## Cohort Heatmap\n\n**Goal:** Show recurrent CNV patterns across a cohort.\n\n**Approach:** Resample every sample's segments onto a common genomic bin grid, stack into a samples-by-bins matrix, render with a diverging colormap centered at zero.\n\n```python\nimport seaborn as sns\n\ndef plot_cohort_heatmap(cns_files, bin_size=1_000_000, output=None):\n    '''Recurrent-CNV heatmap across a cohort on a uniform bin grid.'''\n    chroms = [f'chr{i}' for i in range(1, 23)]\n    columns = []\n    for c in chroms:\n        columns += [(c, b) for b in range(0, 250_000_000, bin_size)]\n    matrix = {}\n    for f in cns_files:\n        name = f.split('\u002F')[-1].replace('.cns', '')\n        cns = pd.read_csv(f, sep='\\t')\n        row = {}\n        for c, b in columns:\n            hits = cns[(cns['chromosome'] == c) &\n                       (cns['start'] \u003C b + bin_size) & (cns['end'] > b)]\n            row[(c, b)] = hits['log2'].mean() if not hits.empty else 0.0\n        matrix[name] = row\n    df = pd.DataFrame(matrix).T\n    fig, ax = plt.subplots(figsize=(14, max(4, 0.3 * len(cns_files))))\n    sns.heatmap(df, cmap='RdBu_r', center=0, vmin=-1.5, vmax=1.5,\n                xticklabels=False, ax=ax)\n    ax.set_xlabel('genomic bin')\n    ax.set_ylabel('sample')\n    if output:\n        fig.savefig(output, dpi=200, bbox_inches='tight')\n    return fig\n```\n\n## Caller-Native Diagnostic Plots\n\n```bash\n# GATK: denoised ratios + modeled segments with allelic info\ngatk PlotModeledSegments --denoised-copy-ratios tumor.denoisedCR.tsv \\\n    --allelic-counts tumor.hets.tsv --segments tumor.modelFinal.seg \\\n    --sequence-dictionary reference.dict --output-prefix tumor -O plots\u002F\n```\n\nASCAT (`ascat.runAscat` ASPCF and sunrise plots), Sequenza (`sequenza.results` chromosome view and the cellularity\u002Fploidy contour), and FACETS (`plotSample`) emit diagnostic plots — always inspect these to confirm the purity\u002Fploidy fit before trusting downstream calls. They are diagnostic, not publication, figures.\n\n## Failure Modes\n\n### The diploid-baseline centering trap\n\n**Trigger:** Plotting log2 from a hyper-aneuploid or whole-genome-doubled tumor with the y-axis centered on the data median or mode.\n\n**Mechanism:** If most of the genome is at tetraploid baseline, centering on the mode places 4 copies at log2 0. Every true diploid region then appears deleted and the plot tells the opposite story.\n\n**Symptom:** A genome-wide pattern of \"loss\" (or \"gain\") inconsistent with the BAF track or with the caller's ploidy estimate.\n\n**Fix:** Anchor the y-axis baseline to the caller's ploidy estimate, not the data mode. Always show a BAF track alongside; if BAF says balanced where log2 says deleted, the centering is wrong.\n\n### log2 axis presented as if it were absolute copy number\n\n**Trigger:** Labeling a log2 y-axis \"copy number\" or comparing log2 amplitudes across samples of different purity.\n\n**Mechanism:** log2 ratio compresses with decreasing purity — a true CN=4 amplification at 40% purity has roughly half the log2 amplitude of the same event at 80% purity.\n\n**Symptom:** A real amplification looks weaker than a passenger gain in a purer sample; cross-sample amplitude comparisons are meaningless.\n\n**Fix:** For cross-sample or absolute claims, plot integer copy number from a purity-corrected caller, not raw log2. Label log2 axes \"log2 copy ratio\".\n\n### Cohort heatmap binning erases focal events\n\n**Trigger:** Large uniform bins (e.g. 1-3 Mb) in a cohort heatmap.\n\n**Mechanism:** A focal amplification (e.g. a few hundred kb at MYC or ERBB2) is averaged with flanking neutral sequence and disappears.\n\n**Symptom:** Known recurrent focal drivers absent from the heatmap; only arm-level events visible.\n\n**Fix:** Use a bin size matched to the question — Mb bins for arm-level surveys, gene-centric or GISTIC peak regions for focal drivers. Consider a separate gene-level panel.\n\n## Quantitative Thresholds\n\n| Choice | Value | Rationale |\n|--------|-------|-----------|\n| log2 y-axis range | -2 to 2 | Covers homozygous loss to ~8-copy gain; clip extreme amplicons separately |\n| Gain\u002Floss plot coloring | log2 > 0.3 \u002F \u003C -0.3 | Visual convention (no single primary citation); not a calling threshold (see cnvkit-analysis) |\n| Cohort heatmap bin (arm-level) | ~1 Mb | Balances resolution and matrix size |\n| Cohort heatmap colormap center | 0 | Diverging map must be zero-centered or gains\u002Flosses are not comparable |\n| Rasterize scatter points | yes, for > ~50k bins | Keeps vector PDFs openable; segments stay vector |\n\n## Common Errors\n\n| Error \u002F symptom | Cause | Solution |\n|-----------------|-------|----------|\n| Whole genome looks lost or gained | y-axis centered on a non-diploid mode | Anchor baseline to caller ploidy; add a BAF track |\n| LOH region not visible | log2-only plot | Add a BAF \u002F minor-allele-fraction panel |\n| Focal driver missing from heatmap | Bins too large | Use gene-level or GISTIC-peak bins |\n| Giant unopenable PDF | 100k+ vector scatter points | `rasterized=True` on the scatter |\n| Chromosomes out of order \u002F overlapping | String-sorted contig names | Explicit chromosome order list |\n| Amplitudes incomparable across samples | Plotting raw log2 across mixed purity | Plot purity-corrected integer CN |\n\n## References\n\n- Talevich E et al 2016. CNVkit: genome-wide copy number detection from targeted DNA sequencing. PLoS Comput Biol 12:e1004873\n- Van Loo P et al 2010. Allele-specific copy number analysis of tumors. PNAS 107:16910 (BAF interpretation)\n- Gel B, Serra E 2017. karyoploteR: an R\u002FBioconductor package to plot customizable genomes. Bioinformatics 33:3088\n\n## Related Skills\n\n- copy-number\u002Fcnvkit-analysis - Generates the .cnr\u002F.cns inputs and built-in plots\n- copy-number\u002Fgatk-cnv - GATK denoised ratios and modeled-segment plots\n- copy-number\u002Fallele-specific-copy-number - Source of BAF\u002FMAF tracks and ploidy estimates\n- copy-number\u002Frecurrent-cnv - Cohort-level recurrence underlying heatmaps\n- data-visualization\u002Fggplot2-fundamentals - General publication-figure grammar\n- data-visualization\u002Fcircos-plots - Circular genome layouts for CNV + SV\n","## Version Compatibility\n\nReference examples tested with: matplotlib 3.8+, pandas 2.2+, numpy 1.26+, seaborn 0.13+, CNVkit 0.9.10+, GATK 4.5+; R 4.3+ with ggplot2 3.5+.\n\nBefore using code patterns, verify installed versions match. If versions differ:\n- Python: `pip show matplotlib pandas` then `help(function)` for signatures\n- R: `packageVersion('ggplot2')` then `?function_name`\n- CLI: `cnvkit.py version`, `gatk --version`\n\nIf code throws ImportError, AttributeError, or TypeError, introspect the installed package and adapt the example rather than retrying.\n\n# CNV Visualization\n\n**\"Plot my copy number profile\"** -> A CNV figure is an argument, not a picture. The plot type, the y-axis quantity, and where the diploid baseline sits all determine what the reader can conclude. The single most important rule: a depth-only log2 plot cannot show loss of heterozygosity, cannot show tumor purity, and silently misleads if the diploid baseline is centered on a non-diploid mode.\n\n- CLI: `cnvkit.py scatter` \u002F `diagram` \u002F `heatmap`; `gatk PlotModeledSegments`\n- Python: `matplotlib` for custom genome-wide and allele-specific tracks\n- R: `ggplot2`, `karyoploteR` for publication ideograms\n\n## Plot Selection — What Each View Reveals and Hides\n\n| Plot | Answers | Reveals | Cannot show |\n|------|---------|---------|-------------|\n| Genome-wide log2 scatter + segments | Where are the gains\u002Flosses? | Focal vs broad events, noise level | LOH, purity, allele-specific state |\n| Per-chromosome scatter | Is this focal event real and where are its boundaries? | Breakpoints, bin support, weight | Absolute CN without purity |\n| BAF \u002F minor-allele-fraction track | Is there allelic imbalance \u002F LOH? | CN-neutral LOH, mirrored imbalance | Total copy number alone |\n| Combined log2 + BAF (two-panel) | What is the allele-specific state? | Gains vs CN-LOH vs balanced | — (this is the complete view) |\n| Cohort heatmap | What is recurrent across samples? | Shared arm\u002Ffocal events | Per-sample breakpoint detail |\n| Ideogram \u002F diagram | Where do events sit relative to cytobands\u002Fgenes? | Gene-level context | Quantitative amplitude |\n| Circos | Genome-wide CNV + SV breakpoints together | CNV-SV co-localization | Fine amplitude detail |\n| Caller-native (GATK\u002FASCAT\u002FFACETS) | Did the caller fit correctly? | Model fit, segment confidence | — (diagnostic, not publication) |\n\nThe decision rule: if the biological question involves LOH, allele-specific gain, or whole-genome doubling, a log2-only plot is insufficient — pair it with a BAF track.\n\n## CNVkit Built-in Plots\n\n```bash\ncnvkit.py scatter sample.cnr -s sample.cns -o scatter.png            # genome-wide\ncnvkit.py scatter sample.cnr -s sample.cns -c chr17 -o chr17.png      # one chromosome\ncnvkit.py scatter sample.cnr -s sample.cns -v sample.vcf.gz -o baf.png  # with BAF panel\ncnvkit.py diagram sample.cnr -s sample.cns -o diagram.pdf             # ideogram\ncnvkit.py heatmap cohort\u002F*.cns -d -o cohort_heatmap.pdf              # cohort, desaturated\n```\n\nPassing `-v` with a VCF adds a B-allele-frequency panel — use it whenever LOH matters.\n\n## Genome-Wide log2 Profile with Segments\n\n**Goal:** Render a publication genome-wide CNV profile with colored segments.\n\n**Approach:** Map per-bin log2 to cumulative genomic coordinates, plot bins as faint points, overlay segment medians as colored horizontal lines, mark chromosome boundaries.\n\n```python\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\ndef plot_genome_profile(cnr_file, cns_file, output=None, gain=0.3, loss=-0.3):\n    '''Genome-wide log2 scatter with segment overlay.'''\n    cnr = pd.read_csv(cnr_file, sep='\\t')\n    cns = pd.read_csv(cns_file, sep='\\t')\n    chroms = [f'chr{i}' for i in range(1, 23)] + ['chrX', 'chrY']\n\n    offsets, cum = {}, 0\n    for c in chroms:\n        sub = cnr[cnr['chromosome'] == c]\n        if sub.empty:\n            continue\n        offsets[c] = cum\n        cum += sub['end'].max()\n\n    cnr = cnr[cnr['chromosome'].isin(offsets)].copy()\n    cnr['x'] = cnr.apply(lambda r: offsets[r['chromosome']] + r['start'], axis=1)\n\n    fig, ax = plt.subplots(figsize=(16, 4))\n    ax.scatter(cnr['x'], cnr['log2'], s=1, c='0.7', alpha=0.5, rasterized=True)\n    for _, seg in cns.iterrows():\n        if seg['chromosome'] not in offsets:\n            continue\n        x0 = offsets[seg['chromosome']] + seg['start']\n        x1 = offsets[seg['chromosome']] + seg['end']\n        color = 'red' if seg['log2'] > gain else 'blue' if seg['log2'] \u003C loss else '0.3'\n        ax.hlines(seg['log2'], x0, x1, colors=color, linewidth=2.5)\n    for c, x in offsets.items():\n        ax.axvline(x, color='0.9', linewidth=0.5)\n    ax.axhline(0, color='black', linewidth=0.6)\n    ax.set_ylim(-2, 2)\n    ax.set_ylabel('log2 copy ratio')\n    ax.set_xlabel('genomic position')\n    fig.tight_layout()\n    if output:\n        fig.savefig(output, dpi=200)\n    return fig, ax\n```\n\n## Combined log2 + B-Allele-Frequency Panel\n\n**Goal:** Show total copy number and allelic imbalance together so CN-neutral LOH and allele-specific gains are visible.\n\n**Approach:** Stack two axes — log2 on top, BAF below. Mirror BAF about 0.5 so allelic imbalance reads as deviation from the center line.\n\n```python\nimport numpy as np\n\ndef plot_log2_baf(cnr_file, baf_df, output=None):\n    '''Two-panel plot: log2 copy ratio above, B-allele frequency below.\n    baf_df: columns chromosome, position, baf (germline-het sites only).'''\n    cnr = pd.read_csv(cnr_file, sep='\\t')\n    fig, (ax_cn, ax_baf) = plt.subplots(2, 1, figsize=(16, 6), sharex=True)\n\n    ax_cn.scatter(range(len(cnr)), cnr['log2'], s=1, c='0.6', alpha=0.5)\n    ax_cn.axhline(0, color='black', linewidth=0.6)\n    ax_cn.set_ylabel('log2 ratio')\n    ax_cn.set_ylim(-2, 2)\n\n    # Plot BAF and its mirror; a tight band at 0.5 = balanced, split bands = imbalance\u002FLOH\n    ax_baf.scatter(range(len(baf_df)), baf_df['baf'], s=2, c='0.4', alpha=0.5)\n    ax_baf.scatter(range(len(baf_df)), 1 - baf_df['baf'], s=2, c='0.4', alpha=0.5)\n    ax_baf.axhline(0.5, color='black', linewidth=0.6)\n    ax_baf.set_ylabel('B-allele frequency')\n    ax_baf.set_ylim(0, 1)\n    ax_baf.set_xlabel('het SNP index')\n    fig.tight_layout()\n    if output:\n        fig.savefig(output, dpi=200)\n    return fig\n```\n\nA copy-neutral LOH region shows log2 ~ 0 but BAF splitting away from 0.5 — invisible on any log2-only plot.\n\n## Cohort Heatmap\n\n**Goal:** Show recurrent CNV patterns across a cohort.\n\n**Approach:** Resample every sample's segments onto a common genomic bin grid, stack into a samples-by-bins matrix, render with a diverging colormap centered at zero.\n\n```python\nimport seaborn as sns\n\ndef plot_cohort_heatmap(cns_files, bin_size=1_000_000, output=None):\n    '''Recurrent-CNV heatmap across a cohort on a uniform bin grid.'''\n    chroms = [f'chr{i}' for i in range(1, 23)]\n    columns = []\n    for c in chroms:\n        columns += [(c, b) for b in range(0, 250_000_000, bin_size)]\n    matrix = {}\n    for f in cns_files:\n        name = f.split('\u002F')[-1].replace('.cns', '')\n        cns = pd.read_csv(f, sep='\\t')\n        row = {}\n        for c, b in columns:\n            hits = cns[(cns['chromosome'] == c) &\n                       (cns['start'] \u003C b + bin_size) & (cns['end'] > b)]\n            row[(c, b)] = hits['log2'].mean() if not hits.empty else 0.0\n        matrix[name] = row\n    df = pd.DataFrame(matrix).T\n    fig, ax = plt.subplots(figsize=(14, max(4, 0.3 * len(cns_files))))\n    sns.heatmap(df, cmap='RdBu_r', center=0, vmin=-1.5, vmax=1.5,\n                xticklabels=False, ax=ax)\n    ax.set_xlabel('genomic bin')\n    ax.set_ylabel('sample')\n    if output:\n        fig.savefig(output, dpi=200, bbox_inches='tight')\n    return fig\n```\n\n## Caller-Native Diagnostic Plots\n\n```bash\n# GATK: denoised ratios + modeled segments with allelic info\ngatk PlotModeledSegments --denoised-copy-ratios tumor.denoisedCR.tsv \\\n    --allelic-counts tumor.hets.tsv --segments tumor.modelFinal.seg \\\n    --sequence-dictionary reference.dict --output-prefix tumor -O plots\u002F\n```\n\nASCAT (`ascat.runAscat` ASPCF and sunrise plots), Sequenza (`sequenza.results` chromosome view and the cellularity\u002Fploidy contour), and FACETS (`plotSample`) emit diagnostic plots — always inspect these to confirm the purity\u002Fploidy fit before trusting downstream calls. They are diagnostic, not publication, figures.\n\n## Failure Modes\n\n### The diploid-baseline centering trap\n\n**Trigger:** Plotting log2 from a hyper-aneuploid or whole-genome-doubled tumor with the y-axis centered on the data median or mode.\n\n**Mechanism:** If most of the genome is at tetraploid baseline, centering on the mode places 4 copies at log2 0. Every true diploid region then appears deleted and the plot tells the opposite story.\n\n**Symptom:** A genome-wide pattern of \"loss\" (or \"gain\") inconsistent with the BAF track or with the caller's ploidy estimate.\n\n**Fix:** Anchor the y-axis baseline to the caller's ploidy estimate, not the data mode. Always show a BAF track alongside; if BAF says balanced where log2 says deleted, the centering is wrong.\n\n### log2 axis presented as if it were absolute copy number\n\n**Trigger:** Labeling a log2 y-axis \"copy number\" or comparing log2 amplitudes across samples of different purity.\n\n**Mechanism:** log2 ratio compresses with decreasing purity — a true CN=4 amplification at 40% purity has roughly half the log2 amplitude of the same event at 80% purity.\n\n**Symptom:** A real amplification looks weaker than a passenger gain in a purer sample; cross-sample amplitude comparisons are meaningless.\n\n**Fix:** For cross-sample or absolute claims, plot integer copy number from a purity-corrected caller, not raw log2. Label log2 axes \"log2 copy ratio\".\n\n### Cohort heatmap binning erases focal events\n\n**Trigger:** Large uniform bins (e.g. 1-3 Mb) in a cohort heatmap.\n\n**Mechanism:** A focal amplification (e.g. a few hundred kb at MYC or ERBB2) is averaged with flanking neutral sequence and disappears.\n\n**Symptom:** Known recurrent focal drivers absent from the heatmap; only arm-level events visible.\n\n**Fix:** Use a bin size matched to the question — Mb bins for arm-level surveys, gene-centric or GISTIC peak regions for focal drivers. Consider a separate gene-level panel.\n\n## Quantitative Thresholds\n\n| Choice | Value | Rationale |\n|--------|-------|-----------|\n| log2 y-axis range | -2 to 2 | Covers homozygous loss to ~8-copy gain; clip extreme amplicons separately |\n| Gain\u002Floss plot coloring | log2 > 0.3 \u002F \u003C -0.3 | Visual convention (no single primary citation); not a calling threshold (see cnvkit-analysis) |\n| Cohort heatmap bin (arm-level) | ~1 Mb | Balances resolution and matrix size |\n| Cohort heatmap colormap center | 0 | Diverging map must be zero-centered or gains\u002Flosses are not comparable |\n| Rasterize scatter points | yes, for > ~50k bins | Keeps vector PDFs openable; segments stay vector |\n\n## Common Errors\n\n| Error \u002F symptom | Cause | Solution |\n|-----------------|-------|----------|\n| Whole genome looks lost or gained | y-axis centered on a non-diploid mode | Anchor baseline to caller ploidy; add a BAF track |\n| LOH region not visible | log2-only plot | Add a BAF \u002F minor-allele-fraction panel |\n| Focal driver missing from heatmap | Bins too large | Use gene-level or GISTIC-peak bins |\n| Giant unopenable PDF | 100k+ vector scatter points | `rasterized=True` on the scatter |\n| Chromosomes out of order \u002F overlapping | String-sorted contig names | Explicit chromosome order list |\n| Amplitudes incomparable across samples | Plotting raw log2 across mixed purity | Plot purity-corrected integer CN |\n\n## References\n\n- Talevich E et al 2016. CNVkit: genome-wide copy number detection from targeted DNA sequencing. PLoS Comput Biol 12:e1004873\n- Van Loo P et al 2010. Allele-specific copy number analysis of tumors. PNAS 107:16910 (BAF interpretation)\n- Gel B, Serra E 2017. karyoploteR: an R\u002FBioconductor package to plot customizable genomes. Bioinformatics 33:3088\n\n## Related Skills\n\n- copy-number\u002Fcnvkit-analysis - Generates the .cnr\u002F.cns inputs and built-in plots\n- copy-number\u002Fgatk-cnv - GATK denoised ratios and modeled-segment plots\n- copy-number\u002Fallele-specific-copy-number - Source of BAF\u002FMAF tracks and ploidy estimates\n- copy-number\u002Frecurrent-cnv - Cohort-level recurrence underlying heatmaps\n- data-visualization\u002Fggplot2-fundamentals - General publication-figure grammar\n- data-visualization\u002Fcircos-plots - Circular genome layouts for CNV + SV\n",{"name":5,"source":24,"license":15,"tool_type":25,"description":7,"imported_at":26,"source_path":27,"primary_tool":28,"source_repository":29},"https:\u002F\u002Fgithub.com\u002FGPTomics\u002FbioSkills","mixed","2026-09-20","copy-number\u002Fcnv-visualization\u002FSKILL.md","matplotlib","GPTomics\u002FbioSkills","main","2d1750717747097b602acf30bd09a7cbd4b4c6ea","awesome_skills\u002Fbiology\u002Fbio-copy-number-cnv-visualization","https:\u002F\u002Fgithub.com\u002FMr-Nobody-Anonymous\u002FAll-skills","https:\u002F\u002Fgithub.com\u002FMr-Nobody-Anonymous","2026-11-09 11:45:36",{"source":8,"skillName":5,"commands":37,"prompt":47,"zipAvailable":48},[38,41,44],{"tool":39,"command":40},"npx","npx skills add Mr-Nobody-Anonymous\u002FAll-skills --skill bio-copy-number-cnv-visualization",{"tool":42,"command":43},"bunx","bunx skills add Mr-Nobody-Anonymous\u002FAll-skills --skill bio-copy-number-cnv-visualization",{"tool":45,"command":46},"pnpm","pnpm dlx skills add Mr-Nobody-Anonymous\u002FAll-skills --skill bio-copy-number-cnv-visualization","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FMr-Nobody-Anonymous\u002FAll-skills\u002Fblob\u002Fmain\u002Fawesome_skills\u002Fbiology\u002Fbio-copy-number-cnv-visualization\u002FSKILL.md",true,[50,56],{"path":51,"resourceType":52,"resourceTypeDesc":53,"sizeBytes":54,"sourceHtmlUrl":55},"examples\u002Fplot_cnv.py","SCRIPT","脚本",3602,"https:\u002F\u002Fgithub.com\u002FMr-Nobody-Anonymous\u002FAll-skills\u002Fblob\u002Fmain\u002Fawesome_skills\u002Fbiology\u002Fbio-copy-number-cnv-visualization\u002Fexamples\u002Fplot_cnv.py",{"path":57,"resourceType":58,"resourceTypeDesc":59,"sizeBytes":60,"sourceHtmlUrl":61},"usage-guide.md","REFERENCE","参考文档",4061,"https:\u002F\u002Fgithub.com\u002FMr-Nobody-Anonymous\u002FAll-skills\u002Fblob\u002Fmain\u002Fawesome_skills\u002Fbiology\u002Fbio-copy-number-cnv-visualization\u002Fusage-guide.md",[63,71,78,85,92,99],{"skillNo":64,"name":65,"nameZh":6,"description":66,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":67,"categories":68,"keywords":69,"publishedAt":70},"01M7EXRQF9GFM7D9BG6N5HAT34","oath","Auditing regulatory compliance (SOC2\u002FPCI-DSS\u002FHIPAA\u002FISO 27001): maps requirements, checks controls, designs audit trails, implements Policy as Code. Use when compliance auditing is needed.","https:\u002F\u002Fgithub.com\u002FMr-Nobody-Anonymous\u002FAll-skills\u002Fblob\u002Fmain\u002Fawesome_skills\u002Fdevsecops\u002Foath\u002FSKILL.md",[],[],"2026-11-15 14:25:24",{"skillNo":72,"name":73,"nameZh":6,"description":74,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":75,"categories":76,"keywords":77,"publishedAt":19},"01M7EXRH7TYTNZJT0BQXEWAMW7","nexus","Orchestrating multi-specialist task chains and scope-adaptive product delivery: classifies intent, selects and executes the minimum viable chain, aggregates results, and verifies acceptance criteria. For multi-domain tasks, build-first delivery, and product lifecycle execution.","https:\u002F\u002Fgithub.com\u002FMr-Nobody-Anonymous\u002FAll-skills\u002Fblob\u002Fmain\u002Fawesome_skills\u002Fdevsecops\u002Fnexus\u002FSKILL.md",[],[],{"skillNo":79,"name":80,"nameZh":6,"description":81,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":82,"categories":83,"keywords":84,"publishedAt":19},"01M7EXRCDPFWVEEEFR2ZQGJCYY","nest","Designing LLM-optimized folder structures: audits and restructures directories for context efficiency, progressive disclosure, and prompt cache performance. Not for general repo structure (Grove).","https:\u002F\u002Fgithub.com\u002FMr-Nobody-Anonymous\u002FAll-skills\u002Fblob\u002Fmain\u002Fawesome_skills\u002Fdevsecops\u002Fnest\u002FSKILL.md",[],[],{"skillNo":86,"name":87,"nameZh":6,"description":88,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":89,"categories":90,"keywords":91,"publishedAt":19},"01M7EXR7JEER60TH0HD0AH4EE9","native","Implementing production iOS\u002FAndroid\u002FmacOS native features (SwiftUI, Compose) and iterating a screen against a reference design. Not for cross-platform RN\u002FFlutter (Port) or web (Artisan).","https:\u002F\u002Fgithub.com\u002FMr-Nobody-Anonymous\u002FAll-skills\u002Fblob\u002Fmain\u002Fawesome_skills\u002Fdevsecops\u002Fnative\u002FSKILL.md",[],[],{"skillNo":93,"name":94,"nameZh":6,"description":95,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":96,"categories":97,"keywords":98,"publishedAt":19},"01M7EXR133J7DD5961R80QTRZY","muse","Defining and managing design tokens, applying token systems to existing codebases, building design system foundations. Use for spacing, color, typography, dark mode, cross-platform output.","https:\u002F\u002Fgithub.com\u002FMr-Nobody-Anonymous\u002FAll-skills\u002Fblob\u002Fmain\u002Fawesome_skills\u002Fdevsecops\u002Fmuse\u002FSKILL.md",[],[],{"skillNo":100,"name":101,"nameZh":6,"description":102,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":103,"categories":104,"keywords":105,"publishedAt":19},"01M7EXQWWX7ECQN0J50B41YN5N","morph","Converting document formats (Markdown\u002FWord\u002FExcel\u002FPDF\u002FHTML) and generating reusable conversion scripts. Use for distributable specs, accessibility-compliant PDFs, or Pandoc\u002FLibreOffice pipelines.","https:\u002F\u002Fgithub.com\u002FMr-Nobody-Anonymous\u002FAll-skills\u002Fblob\u002Fmain\u002Fawesome_skills\u002Fdevsecops\u002Fmorph\u002FSKILL.md",[],[]]