# SummarizedExperiment subsetting: keeping assays, rowData, and colData aligned when filtering samples and features

Subset a Bioconductor SummarizedExperiment with [i, j] so assays, rowData, and colData stay synchronized: why drop is ignored on the container, [, j] vs [i, j] semantics, NA logical-index behavior, character indexing pitfalls, assay dimname overlay, and the gotchas that silently desync sample metadata from assay columns.

Exact reference: {"kind":"skill_version","skill_id":"skl_4Fn2Nq8yFf54TIKxUPVAVQ","version_id":"skv_erCPDeUc5a3ssuvwuODkXw"}

Applicability: [{"constraint":"Bioconductor SummarizedExperiment / RangedSummarizedExperiment objects","technology":"Bioconductor","version_scheme":"unknown"},{"constraint":"RNA-seq and ChIP-seq count-matrix workflows in R","technology":"Bioconductor","version_scheme":"unknown"},{"constraint":"Agents building or debugging DESeq2/edgeR/limma pipelines","technology":"Bioconductor","version_scheme":"unknown"},{"constraint":"Agents combining assay matrices with sample metadata from separate files","technology":"Bioconductor","version_scheme":"unknown"}]

# SummarizedExperiment subsetting: keep assays, rowData, and colData aligned

When you filter samples or features in a Bioconductor `SummarizedExperiment`, subset the
container with `[i, j]` -- never the assay matrix directly. The `[` method subsets every assay,
`rowData`/`rowRanges`, and `colData` in one operation, so the pieces cannot drift out of sync.
The official vignette's motivation: "Improperly accounting for metadata and observational data
has resulted in a number of incorrect results and retractions."

## The one rule

Rows are features (genes, transcripts, exons). Columns are samples. Always do
`se_subset <- se[i, j]` and only then extract data with `assay(se_subset)`, `colData(se_subset)`,
`rowData(se_subset)`.

The anti-pattern that corrupts analyses silently:

```r
library(SummarizedExperiment)

# WRONG: subsetting the bare matrix breaks the link to colData.
# Downstream code that pairs assay columns with colData rows by POSITION
# now attributes each sample's data to the wrong sample.
counts <- assay(se)
counts <- counts[, se$dex == "trt"]
stopifnot(ncol(counts) == nrow(colData(se)))  # fails -- desynced

# RIGHT: subset the container; assays and colData move together.
se_trt <- se[, se$dex == "trt"]
stopifnot(ncol(assay(se_trt)) == nrow(colData(se_trt)))  # always true
```

Positional desync is the worst failure mode because nothing errors: a design matrix built from
the full `colData` applied to a filtered assay silently swaps sample labels.

## `drop` is ignored -- the container never collapses

Per the method documentation, the `drop` argument is "ignored by these methods."
`x[i, j, drop=TRUE]` always returns a `SummarizedExperiment`, even for a single row or column.
This differs from base R, where `m[, 1]` becomes a vector:

```r
sub <- se[, 1]          # still a SummarizedExperiment, dim: NFEATURES x 1
class(sub)              # "SummarizedExperiment"
dim(sub)                # e.g. 200 1

# The bare matrix follows base R rules and drops:
vec <- assay(se)[, 1]   # a vector, dimnames lost
```

If you need a single sample's values as a vector, be explicit: `assay(se[, 1])[, 1]`.
If you need a colData column as a vector, use the `[[` accessor (note: `se[[1]]` is the
FIRST colData COLUMN, not the first assay row): `se[[TREATMENT_COL]]` or `se$treatment`.

## `[, j]` keeps all features; `[i, ]` keeps all samples

`i` and `j` may each be numeric, logical, character, or missing. Missing means "keep all":

```r
se_treated   <- se[, se$dex == "trt"]   # sample filter; every feature kept
se_sig       <- se[rowSums(assay(se)) > 10, ]  # feature filter; every sample kept
se_both      <- se[rowSums(assay(se)) > 10, se$dex == "trt"]
se_by_name   <- se[c("ENSG00000000003", "ENSG00000000005"), ]  # character i
```

## Logical indexing: NA creates rows, it does not drop them

Base R subsetting semantics apply inside the container: a logical vector containing NA
produces NA placeholder rows in assays, rowData, and colData -- consistently, so alignment
holds, but the object now contains all-NA rows/columns. Clean your index first:

```r
keep <- se$dex == "trt"
keep[is.na(keep)] <- FALSE   # decide: NA samples are out, explicitly
se <- se[, keep]
```

`subset(x, subset, select)` evaluates `subset` in the context of `rowData(x)` (or
`rowRanges(x)` for a RangedSummarizedExperiment) and `select` in the context of
`colData(x)`; missing values in the logical result are taken as false there, which is safer:

```r
se_trt <- subset(se, select = dex == "trt")
```

## Character indexing and duplicated names

Character `i`/`j` values are matched against `rownames(x)`/`colnames(x)` with bounds
checking -- an unknown name errors. But if dimnames contain DUPLICATES, matching takes the
first occurrence silently. Guarantee unique names before character subsetting, or prefer
logical/numeric indices:

```r
stopifnot(!anyDuplicated(rownames(se)), !anyDuplicated(colnames(se)))
se_sel <- se[SAMPLE_IDS_OF_INTEREST, ]
```

## Assay dimnames are overlaid on extraction

`assay(x)` copies the top-level dimnames of the SE onto the returned matrix by default.
The stored matrix may have different (or NULL) dimnames underneath. Two consequences:

1. `assay(se)` and the matrix you supplied at construction can LOOK identical while the
   stored object differs -- use `assay(se, withDimnames = FALSE)` to see (or pass to
   performance-critical code) the matrix as stored.
2. At construction, assay dimnames must be NULL or identical to the object's dimnames, and
   the constructor validates counts: "nb of cols in 'assay' must equal nb of rows in
   'colData'". If your counts and metadata came from different files, reorder first --
   order matters, the constructor does not reorder for you:

```r
counts <- counts[, rownames(coldata)]      # align columns to colData order
rowranges <- rowranges[rownames(counts), ] # align rows to assay order
stopifnot(identical(rownames(coldata), colnames(counts)))
se <- SummarizedExperiment(assays = list(counts = counts),
                           rowRanges = rowranges, colData = coldata)
```

## metadata() does not subset

`metadata()` is a plain list describing the whole experiment (design formulas, processing
parameters). It is NOT subset-aware: any per-sample or per-feature vector you stash there
goes stale the moment you subset. Keep per-feature data in `rowData`, per-sample data in
`colData`, experiment-wide data in `metadata`.

## Replacement and combining

- `x[i, j] <- value` requires `value` to be a SummarizedExperiment with dimensions,
  dimnames, and assays consistent with the replaced region. You cannot inject a bare matrix.
- `cbind(...)` combines samples: duplicate `rowData` columns across objects must contain
  identical data; assays combine by name matching. `rbind(...)` combines features with the
  mirror constraint on `colData`. `metadata` from all objects is concatenated into a list
  with no name checking -- conflicting entries are silently stacked.

## Quick alignment audit

After any chain of manipulations, run this before modeling:

```r
audit_se <- function(x) {
  stopifnot(
    all(vapply(assays(x), function(a) identical(dim(a), dim(x)), logical(1))),
    nrow(colData(x)) == ncol(x),
    nrow(rowData(x)) == nrow(x),
    is.null(colnames(x)) || identical(rownames(colData(x)), colnames(x)) || is.null(rownames(colData(x)))
  )
  invisible(TRUE)
}
audit_se(se)
```


## Supporting basis and limitations

Grounded in the SummarizedExperiment reference documentation and vignette: the [ method signature x[i, j, drop=TRUE] documents drop as 'ignored by these methods'; i, j may be numeric, logical, character, or missing; subset(x, subset, select) evaluates subset against rowData(x)/rowRanges(x) and select against colData(x) with NA taken as false; assay(x, withDimnames=TRUE) overlays top-level dimnames, withDimnames=FALSE returns the matrix as stored; constructor validity errors include 'nb of cols in assay must equal nb of rows in colData' and assay dimnames must be NULL or identical; x[[i]] accesses column i of colData; x[i, j] <- value requires a SummarizedExperiment value with consistent dimensions; cbind/rbind combine assays by name and merge metadata with no name checking. The vignette states coordination of metadata and assays on subsetting is the key design property, motivated by incorrect results and retractions from desynced metadata.

## Change and rationale

New skill: concrete guide to SummarizedExperiment subsetting semantics in Bioconductor -- subsetting the container with [i, j] keeps assays, rowData, and colData synchronized; drop is ignored by the [ method so the object never collapses; [, j] vs [i, j] dimension semantics; NA logical-index behavior; character indexing with duplicated dimnames; assay() top-level dimname overlay and withDimnames=FALSE; metadata() not subset-aware; construction-time alignment validation; replacement and cbind/rbind combining constraints; plus a post-manipulation alignment audit function.

LLM agents doing Bioconductor bioinformatics work routinely extract assay matrices and filter them with base R, breaking the positional link to colData and silently misattributing sample metadata in downstream models -- the exact failure class the SummarizedExperiment design exists to prevent. A skill capturing the documented subsetting semantics (drop ignored, missing-index behavior, NA logical handling, dimname overlay, constructor validation) gives agents the correct pattern in a reusable form.
