PivotTable

PivotTable is the core data structure in pymaftools: a feature × sample matrix with two aligned metadata frames that travel with it through every operation.

PivotTable
├── (the matrix)        features (rows) × samples (columns)
├── feature_metadata    one row per feature  (freq, pathway, exon_size, ...)
└── sample_metadata     one row per sample   (subtype, TMB, sex, ...)

Subsetting, sorting and frequency calculations all keep the two metadata frames in sync with the matrix, so you never have to re-align them by hand.

From a MAF

A mutation PivotTable is produced from a MAF. There are two granularities:

from pymaftools import load_example_maf

maf = load_example_maf("multisample")

table = maf.to_gene_table()       # gene × sample   (one row per gene)
muts  = maf.to_mutation_table()   # mutation × sample (one row per variant)

to_gene_table is what you want for an oncoplot. The returned object is a SmallVariationTable (a PivotTable subclass), so every method below applies.

Note

Wild-type cells are the boolean ``False``; mutated cells hold the ``Variant_Classification`` string. The matrix is therefore an object dtype mixing False and strings like "Missense_Mutation". A PivotTable is a pandas.DataFrame (the matrix itself), so to get a plain mutated/not-mutated boolean mask, compare it directly against False:

mutated = table != False        # boolean feature × sample mask

to_pivot_table is a backward-compatible alias for to_gene_table; prefer to_gene_table in new code — the name states the granularity.

A typical prep chain turns the raw matrix into something ready to plot:

table = (
    maf.to_gene_table()
    .add_freq()                                  # feature_metadata["freq"]
    .calculate_tmb(default_capture_size=40)      # sample_metadata["TMB"]
    .sort_features(by="freq", ascending=False)   # high-freq genes on top
    .sort_samples_by_mutations()                 # waterfall the columns
)

Note

calculate_tmb returns a new table rather than mutating in place, so capture the return value (table = table.calculate_tmb(...)) or the TMB column will not appear. The same is true of add_freq / sort_* — they all return a new table, so keep them in one chain as above.

Inspecting the structure

table.shape                       # (n_features, n_samples)
table.feature_metadata.head()     # per-gene annotations (freq, ...)
table.sample_metadata.head()      # per-sample annotations (TMB, subtype, ...)
table.sample_metadata["mutations_count"]   # added by to_gene_table

Persistence

HDF5 is the canonical persistence format. It preserves the matrix, concrete PivotTable subclass, feature metadata, and sample metadata.

from pymaftools import PivotTable, read_h5

table.to_h5("mutation_table.h5")
restored = PivotTable.read_h5("mutation_table.h5")
restored = read_h5("mutation_table.h5")  # equivalent top-level helper

SQLite persistence is deprecated as of 0.5.0 because its practical limit of roughly 2,000 columns is unsuitable for expression and other high-dimensional omics matrices.

Subsetting

PivotTable.subset() filters by features (rows) and/or samples (columns), with both metadata frames kept in sync automatically.

Note

The examples in this and the next two sections are illustrative: the bundled multisample MAF carries only mutations_count in sample_metadata (no subtype/sex/age), and its genes are not TP53/KRAS/EGFR (run table.feature_metadata.index[:10] to see the real ones). Substitute names/columns that exist in your data. For a runnable cohort that already has subtype (plus LUAD_freq/LUSC_freq), load the bundled HDF5 fixture instead:

from pymaftools import load_example_table
table = load_example_table()
# By feature names (use names present in your table)
subset = table.subset(features=["TP53", "KRAS", "EGFR"])

# By boolean mask — samples of a specific subtype
luad = table.subset(samples=table.sample_metadata["subtype"] == "LUAD")

# Combine both — recurrent genes within one subtype
result = table.subset(
    features=table.feature_metadata["freq"] > 0.1,
    samples=table.sample_metadata["subtype"] == "LUSC",
)

Tip

Masks are ordinary pandas boolean Series indexed by feature/sample name, so any pandas expression works (.isin([...]), &, |, ~). Because subset re-slices both metadata frames, the result is a self-consistent PivotTable you can hand straight to the next step.

Mutation frequency

add_freq writes a freq column into feature_metadata (fraction of samples mutated). Pass group_col= to additionally get one column per group:

# Overall freq only
table = table.add_freq()

# Per-subtype columns straight from a sample_metadata column:
# adds LUAD_freq, LUSC_freq, ... alongside the overall freq
table = table.add_freq(group_col="subtype")

# Equivalent explicit form — pass the per-group subsets yourself
table = table.add_freq(
    groups={
        "LUAD": table.subset(samples=table.sample_metadata.subtype == "LUAD"),
        "LUSC": table.subset(samples=table.sample_metadata.subtype == "LUSC"),
    }
)

These per-group columns are what the grouped oncoplot draws as per-section frequency strips (see OncoPlot).

Sorting

# Rows: by a feature_metadata column (single or multiple keys)
table = table.sort_features(by="freq", ascending=False)
table = table.sort_features(by=["size_group", "log2OR"], ascending=[True, False])

# Columns: waterfall by mutation pattern (classic oncoplot look)
table = table.sort_samples_by_mutations(top=10)

# Columns: keep groups contiguous, waterfall within each group
table = table.sort_samples_by_group(
    group_col="subtype", group_order=["LUAD", "LUSC"], top=10
)

Note

The two axes sort differently on purpose, which is why there is no generic sort_samples(by=...) to mirror sort_features(by=...). A feature is sorted by a single scalar per gene (a feature_metadata column such as freq). A sample, in an oncoplot, is sorted by its mutation pattern across genes — the waterfall staircase — which is not a single column value, so it gets the dedicated sort_samples_by_mutations / sort_samples_by_group methods instead.

Note

Grouping in an oncoplot only draws clean sections if the rows/columns are already contiguous by the grouping key. Always sort_features / sort_samples_by_group before group_features / group_samples.

Multi-omics integration

Stack several omics layers that share samples into a Cohort:

from pymaftools import Cohort

cohort = Cohort("my_cohort")
cohort.add_table(mutation_table, "mutation")   # PivotTable first, name second
cohort.add_table(expression_table, "expression")
cohort.add_table(cnv_table, "cnv")

cohort = cohort.subset(samples=shared_samples)  # align to shared samples

For full method signatures, see the API Reference.