Skip to content

add matrix to colData of spe object #179

Description

@cathalgking

I would like to add the results of escape to the metadata (colData) of my data object, instead of an assay.
Add the dense numeric matrix from the output of escape.matrix to the colData of my SingleCellExperiment object. Are the orders of the cells conserved (matched) when I add this to metadata?
gene set

str(GS)
List of 2
 $ Bcells: chr [1:5] "MS4A1" "CD79B" "CD79A" "IGH1" ...
 $ Tcells: chr [1:5] "CD3E" "CD3D" "CD3G" "CD7" ...

Run escape

trial <- escape.matrix(spe, 
                              method = "AUCell", 
                              gene.sets = GS, 
                              min.size = NULL)

output

str(trial)
 num [1:163797, 1:2] 0 0 0 0 0 0 0 0 0 0 ...
 - attr(*, "dimnames")=List of 2
  ..$ : chr [1:163797] "1" "2" "4" "5" ...
  ..$ : chr [1:2] "Bcells" "Tcells"

then

# add results to colData of SPE
colData(spe)[, colnames(trial)] <- trial

colData

colData(spe)
DataFrame with 163797 rows and 15 columns
         cell_id transcript_counts control_probe_counts control_codeword_counts total_counts cell_area nucleus_area   sample_id in_tissue       sum
       <integer>         <integer>            <integer>               <integer>    <integer> <numeric>    <numeric> <character> <logical> <numeric>
1              1                28                    1                       0           29   58.3870      26.6422      xenium      TRUE        28
2              2                94                    0                       0           94  197.0167      42.1308      xenium      TRUE        94
4              4                11                    0                       0           11   42.3114      10.0698      xenium      TRUE        11
5              5                48                    0                       0           48  107.6525      37.4797      xenium      TRUE        48
8              8                39                    0                       0           39   85.0292      26.3712      xenium      TRUE        39
...          ...               ...                  ...                     ...          ...       ...          ...         ...       ...       ...
167776    167776               229                    1                       0          230  220.4528      60.5997      xenium      TRUE       229
167777    167777                79                    0                       0           79   37.3894      25.2423      xenium      TRUE        79
167778    167778               397                    0                       0          397  287.0583      86.7000      xenium      TRUE       397
167779    167779               117                    0                       0          117  235.3544      25.1972      xenium      TRUE       117
167780    167780               378                    0                       0          378  270.0795     111.8069      xenium      TRUE       378
        detected     total sizeFactor    Bcells     Tcells
       <integer> <numeric>  <numeric> <numeric>  <numeric>
1             15        28  0.1429156         0 0.00000000
2             38        94  0.4797882         0 0.00000000
4              9        11  0.0561454         0 0.19421488
5             33        48  0.2449982         0 0.00000000
8             25        39  0.1990610         0 0.00826446
...          ...       ...        ...       ...        ...
167776        77       229   1.168846         0          0
167777        37        79   0.403226         0          0
167778        75       397   2.026339         0          0
167779        51       117   0.597183         0          0
167780        77       378   1.929361         0          0

Thanks in advance!

Activity

  1. ncborcherding commented on Jul 23, 2026

    @ncborcherding
    Member

    Hey @cathalgking

    Hi @user — thanks for the detailed write-up with the str() output, that makes this easy to answer.

    Short version: yes, cell order is conserved, and what you have works.

    escape.matrix() never reorders or drops cells. Internally it walks the count matrix in contiguous column blocks of groups (default 1000) and binds the per-block results back together in the same order, so:

    • nrow(output) == ncol(input)
    • rownames(output) is colnames(input), in the original order

    The min.size, min.expr.cells, and min.filter.by arguments filter gene sets and genes, never cells — you always get one row back per cell. Your output confirms it: 163,797 rows against 163,797 columns, with rownames "1", "2", "4", "5", ... in the same order as your colData.

    Quick caveat: match on names, not position

    colData(spe)[, colnames(trial)] <- trial is a positional assignment. It's correct here, but it will silently misalign if anything ever reorders or subsets between the two calls. One extra index makes it order-independent:

    # reorder rows of `trial` to match the object explicitly
    colData(spe)[, colnames(trial)] <- trial[colnames(spe), , drop = FALSE]

    Reproducible check with the example data

    library(escape)
    library(SingleCellExperiment)
    
    GS <- list(Bcells = c("MS4A1", "CD79B", "CD79A", "IGH1", "IGH2"),
               Tcells = c("CD3E", "CD3D", "CD3G", "CD7", "CD8A"))
    
    sce <- Seurat::as.SingleCellExperiment(SeuratObject::pbmc_small)
    
    es <- escape.matrix(sce,
                        method    = "AUCell",
                        gene.sets = GS,
                        min.size  = NULL)
    
    dim(es)                                 # 80 x 2 -> cells x gene sets
    identical(rownames(es), colnames(sce))  # TRUE -> order conserved

    Since you're on Xenium data, colData is the right call if you're feeding these into spatial plots - and columns there subset correctly with spe[, i], so downstream filtering stays aligned. Worth noting that escape's own plotting functions (heatmapEnrichment(), ridgeEnrichment(), etc.) look for the assay, so you may want runEscape() as well and keep both copies if you plan to use those.

    Hope that helps and let me know if you have any issues.

    Nick

  2. cathalgking commented on Jul 24, 2026

    @cathalgking
    Author

    That worked, thanks @ncborcherding
    One related thing I noticed is that escape.matrix() seems to be introducing some variability, at least with AUCell. Have you noticed that? Would it be to do with the chunking that is used? I tried setting a seed and still noticed this variability, although very minor when looking at the overall distributions.

    trial <- escape.matrix(spe, 
                                  method = "AUCell", 
                                  gene.sets = GS, 
                                  min.size = NULL)
    
    head(trial)
          Bcells    Tcells
    1 0.01092896 0.0000000
    2 0.00000000 0.0000000
    3 0.00000000 0.0661157
    4 0.00000000 0.1033058
    5 0.00000000 0.0000000
    6 0.00000000 0.0000000
    

    re-run

    head(trial)
          Bcells    Tcells
    1 0.00000000 0.0000000
    2 0.00000000 0.0000000
    3 0.18032787 0.0000000
    4 0.00000000 0.1942149
    5 0.00000000 0.0000000
    6 0.05464481 0.0000000
    
  3. ncborcherding commented on Jul 24, 2026

    @ncborcherding
    Member

    Hey @cathalgking

    This is a quirk of AUCell itself - it is not deterministic, meaning it will give different values each time it is run.

    Nick

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions