Skip to contents

`ms_segments()` is the CN input link of the msuiter grammar. It is an S7 generic: given a file path it reads a segmentation file from one of the eight SigProfilerMatrixGenerator CN callers, maps the caller's columns onto the canonical allele-specific segment record and returns a typed data.frame; given a canonical data.frame it validates and returns it directly. All violations raise `msuiter_error_*` conditions.

Usage

ms_segments(x, caller = "auto", chrom_col = NULL, ...)

Arguments

x

A character string naming one segmentation file (`.tsv` or `.txt`, optionally `.gz`), or a data.frame in canonical form (columns `chrom`, `start`, `end`, `minor`, `major`, plus optional `sample`).

caller

The CN caller label. Defaults to `"auto"`: the caller is identified from the file's copy-number column signature (each of the eight signatures is distinct). An explicit label (`"ASCAT"`, `"ASCAT_NGS"`, `"ABSOLUTE"`, `"PCAWG"`, `"FACETS"`, `"BATTENBERG"`, `"PURPLE"`, `"SEQUENZA"`, case-insensitive) forces the mapping. For a canonical data.frame, `caller` is required provenance (`"auto"` is rejected there). CNVkit is not an SPMG input branch and its `.cns`/`.cnr` output is not allele-specific; it is rejected as documented-limited.

chrom_col

Optional name of the chromosome column, overriding the per-caller default (file input only). SPMG's matrix generation never reads the chromosome column, so the defaults for ASCAT, ABSOLUTE, PCAWG, FACETS, PURPLE and SEQUENZA follow the callers' standard segment-file headers and are documented-limited; BATTENBERG (`chr`, upstream :402) and ASCAT_NGS (`Chromosome`, upstream example fixture) are source-verified.

...

No passthrough arguments: anything arriving in `...` is rejected as an argument-spelling error (`msuiter_error_input`).

Value

A data.frame with columns `sample` (only when present in the input), `chrom` (character), `start`, `end` (1-based closed bp) and `minor`, `major` (integer), with the io `provenance` in attribute `"provenance"`.

Details

The canonical record mirrors the verified CN48 input (`src/rust/catalog/src/cn48.rs`, `CnSegment`): `chrom` is passed through verbatim, `start`/`end` are 1-based closed bp coordinates and `minor`/`major` are the ordered allele-specific copy numbers (`minor <= major`; the total copy number is their sum). The segment length CN48 bins is the raw difference `end - start` (upstream divides `end - start` by 1e6, :310-329 – NOT `end - start + 1`).

Per-caller derivations (SPMG `CNVMatrixGenerator.py`, master `edccbea6`): ASCAT reads `nMajor`/`nMinor` directly (:104-116, :196-204). ASCAT_NGS derives the minor allele as `Tumour TCN - Tumour BCN` (:215-221); upstream only zero-tests the pair, so the smaller value becomes `minor`. ABSOLUTE's `Modal_HSCN_1`/`Modal_HSCN_2` are likewise an unordered pair (:117-129, :234-242). SEQUENZA requires `A + B == CNt` (:92-103, :206-213) and orders the pair. FACETS takes `minor = lcn.em` (a missing `lcn.em` means LOH upstream, :262-271, and maps to `minor = 0`) and `major = tcn.em - minor`; `lcn.em` above half of `tcn.em` contradicts FACETS' own "less-copy number" semantics and is rejected. PURPLE reads `minorAlleleCopyNumber`/`majorAlleleCopyNumber` (:167-183, :282-295). Battenberg rows expand to one record per present clone block (`nMaj1_A`/`nMin1_A` clonal, `nMaj2_A`/`nMin2_A` subclonal), exactly as upstream tallies both blocks as separate events (:401-425); a block with any missing value is counted into the provenance skip ledger instead of silently dropped (upstream `dropna` :404-405 drops the same rows, but also silently drops rows with missing coordinates/samples where msuiter hard-rejects – stricter by design).

PCAWG files carry no allele split – upstream consumes only `copy_number` and `mutation_type` (:130-141, :243-258). The mapping is: `copy neutral LOH` / `amp LOH` / `hemizygous del LOH` -> `minor = 0`, `major = copy_number`; `loss` with `copy_number` 0/1 -> homdel (0, 0) / LOH (0, 1); `copy neutral` and `gain` -> the documented convention `minor = 1`, `major = copy_number - 1`, which preserves the total copy number and het zygosity and therefore the exact CN48 channel (the true allele split is unrecoverable from the format). Rows outside these shapes (`loss` with total CN >= 2, het-labelled rows with total CN < 2, any other `mutation_type`) have no faithful upstream behaviour – upstream desynchronises its LOH list (:304) or targets the nonexistent `1:het` channel – and are rejected with the offending row numbers.

Deliberate divergences from upstream (documented, not gaps): NaN copy numbers are rejected instead of silently classifying into `9+`/het; declared-role callers (ASCAT, BATTENBERG, FACETS, PURPLE) with `minor > major` are rejected instead of tallied; `sample` is optional passthrough (single-sample tool), not a hard requirement.

The returned `provenance` attribute carries `caller` (resolved label), `source`/`format` and the `parse` statistics (`n_lines`, `n_records`, `n_expanded` for Battenberg subclonal records, `n_skipped`, `skip_reasons`).

Examples

lines <- c(
  "sample\tchr\tstartpos\tendpos\tnMajor\tnMinor",
  "TUMOR1\tchr7\t100000\t9000000\t1\t1",
  "TUMOR1\tchr7\t9000000\t20000000\t3\t0"
)
path <- tempfile(fileext = ".tsv")
writeLines(lines, path)
seg <- ms_segments(path, caller = "ASCAT")
seg
#>   sample chrom start   end minor major
#> 1 TUMOR1  chr7 1e+05 9e+06     1     1
#> 2 TUMOR1  chr7 9e+06 2e+07     0     3
attr(seg, "provenance")$caller
#> [1] "ASCAT"
unlink(path)

# Canonical data.frame: validated direct construction
ms_segments(
  data.frame(chrom = "chr7", start = 100000, end = 9000000,
             minor = 1L, major = 1L),
  caller = "synthetic"
)
#>   chrom start   end minor major
#> 1  chr7 1e+05 9e+06     1     1