# CITE-seq data with CiteFuse and MuData

Danila Bredikhin1* and Ilia Kats2**

1European Molecular Biology Laboratory, Heidelberg, Germany
2German Cancer Research Center, Heidelberg, Germany

*danila.bredikhin@embl.de
**i.kats@dkfz-heidelberg.de

#### 2025-06-04

## 0.1 Introduction

CITE-seq data provide RNA and surface protein counts for the same cells. This tutorial shows how MuData can be integrated into with Bioconductor workflows to analyse CITE-seq data.

## 0.2 Installation

The most recent dev build can be installed from GitHub:

    library(remotes)
    remotes::install_github("ilia-kats/MuData")

Stable version of `MuData` will be available in future bioconductor versions.

## 0.3 Loading libraries

    library(MuData)
    library(SingleCellExperiment)
    library(MultiAssayExperiment)
    library(CiteFuse)
    library(scater)

    library(rhdf5)

## 0.4 Loading data

We will use CITE-seq data available within `CiteFuse` Bioconductor package.

    data("CITEseq_example", package = "CiteFuse")
    lapply(CITEseq_example, dim)
    #> $RNA
    #> [1] 19521   500
    #>
    #> $ADT
    #> [1]  49 500
    #>
    #> $HTO
    #> [1]   4 500

This dataset contains three matrices — one with `RNA` counts, one with antibody-derived tags (`ADT`) counts and one with hashtag oligonucleotide (`HTO`) counts.

## 0.5 Processing count matrices

While CITE-seq analysis workflows such as CiteFuse should be consulted for more details, below we exemplify simple data transformations in order to demonstrate how their output can be saved to an H5MU file later on.

Following the CiteFuse tutorial, we start with creating a `SingleCellExperiment` object with the three matrices:

    sce_citeseq <- preprocessing(CITEseq_example)
    sce_citeseq
    #> class: SingleCellExperiment
    #> dim: 19521 500
    #> metadata(0):
    #> assays(1): counts
    #> rownames(19521): hg19_AL627309.1 hg19_AL669831.5 ... hg19_MT-ND6
    #>   hg19_MT-CYB
    #> rowData names(0):
    #> colnames(500): AAGCCGCGTTGTCTTT GATCGCGGTTATCGGT ... TTGGCAACACTAGTAC
    #>   GCTGCGAGTTGTGGCC
    #> colData names(0):
    #> reducedDimNames(0):
    #> mainExpName: NULL
    #> altExpNames(2): ADT HTO

We will add a new assay with normalised RNA counts:

    sce_citeseq <- scater::logNormCounts(sce_citeseq)
    sce_citeseq  # new assay: logcounts
    #> class: SingleCellExperiment
    #> dim: 19521 500
    #> metadata(0):
    #> assays(2): counts logcounts
    #> rownames(19521): hg19_AL627309.1 hg19_AL669831.5 ... hg19_MT-ND6
    #>   hg19_MT-CYB
    #> rowData names(0):
    #> colnames(500): AAGCCGCGTTGTCTTT GATCGCGGTTATCGGT ... TTGGCAACACTAGTAC
    #>   GCTGCGAGTTGTGGCC
    #> colData names(1): sizeFactor
    #> reducedDimNames(0):
    #> mainExpName: NULL
    #> altExpNames(2): ADT HTO

To the ADT modality, we will add an assay with normalised counts:

    sce_citeseq <- CiteFuse::normaliseExprs(
      sce_citeseq, altExp_name = "ADT", transform = "log"
    )
    altExp(sce_citeseq, "ADT")  # new assay: logcounts
    #> class: SummarizedExperiment
    #> dim: 49 500
    #> metadata(0):
    #> assays(2): counts logcounts
    #> rownames(49): B220 (CD45R) B7-H1 (PD-L1) ... TCRb TCRg
    #> rowData names(0):
    #> colnames(500): AAGCCGCGTTGTCTTT GATCGCGGTTATCGGT ... TTGGCAACACTAGTAC
    #>   GCTGCGAGTTGTGGCC
    #> colData names(0):

We will also generate reduced dimensions:

    sce_citeseq <- scater::runPCA(
      sce_citeseq, exprs_values = "logcounts", ncomponents = 20
    )

    scater::plotReducedDim(sce_citeseq, dimred = "PCA",
                           by_exprs_values = "logcounts", colour_by = "CD27")

## 0.6 Making a MultiAssayExperiment object

An appropriate structure for multimodal datasets is `MultiAssayExperiment`.

We will make a respective MultiAssayExperiment object from `sce_citeseq`:

    experiments <- list(
      ADT = altExp(sce_citeseq, "ADT"),
      HTO = altExp(sce_citeseq, "HTO")
    )

    # Drop other modalities from sce_citeseq
    altExp(sce_citeseq) <- NULL
    experiments[["RNA"]] <- sce_citeseq

    mae <- MultiAssayExperiment(experiments)

## 0.7 Writing to H5MU

We can write the contents of the MultiAssayExperiment object into an H5MU file:

    writeH5MU(mae, "citefuse_example.h5mu")

We can check that all the modalities were written to the file:

    h5 <- rhdf5::H5Fopen("citefuse_example.h5mu")
    h5ls(H5Gopen(h5, "mod"), recursive = FALSE)
    #>   group name     otype dclass dim
    #> 0     /  ADT H5I_GROUP
    #> 1     /  HTO H5I_GROUP
    #> 2     /  RNA H5I_GROUP

… both assays for ADT — raw counts are stored in `X` and normalised counts are in the corresponding layer:

    h5ls(H5Gopen(h5, "mod/ADT"), FALSE)
    #>   group   name     otype dclass dim
    #> 0     /      X H5I_GROUP
    #> 1     / layers H5I_GROUP
    #> 2     /    obs H5I_GROUP
    #> 3     /    var H5I_GROUP
    h5ls(H5Gopen(h5, "mod/ADT/layers"), FALSE)
    #>   group      name       otype dclass      dim
    #> 0     / logcounts H5I_DATASET  FLOAT 49 x 500

… as well as reduced dimensions (PCA):

    h5ls(H5Gopen(h5, "mod/RNA/obsm"), FALSE)
    #>   group name       otype dclass      dim
    #> 0     /  PCA H5I_DATASET  FLOAT 20 x 500
    # There is an alternative way to access groups:
    # h5&'mod'&'RNA'&'obsm'
    rhdf5::H5close()

## 0.8 References

- Muon: multimodal omics analysis framework preprint

- mudata (Python) documentation

- muon documentation and web page

- Kim HJ, Lin Y, Geddes TA, Yang P, Yang JYH (2020). “CiteFuse enables multi-modal analysis of CITE-seq data.” Bioinformatics, 36(14), 4137–4143. https://doi.org/10.1093/bioinformatics/btaa282.

- Ramos M, Schiffer L, Re A, Azhar R, Basunia A, Cabrera CR, Chan T, Chapman P, Davis S, Gomez-Cabrero D, Culhane AC, Haibe-Kains B, Hansen K, Kodali H, Louis MS, Mer AS, Reister M, Morgan M, Carey V, Waldron L (2017). “Software For The Integration Of Multi-Omics Experiments In Bioconductor.” Cancer Research, 77(21); e39-42.

## 0.9 Session Info

    sessionInfo()
    #> R version 4.5.0 (2025-04-11 ucrt)
    #> Platform: x86_64-w64-mingw32/x64
    #> Running under: Windows Server 2022 x64 (build 20348)
    #>
    #> Matrix products: default
    #>   LAPACK version 3.12.1
    #>
    #> locale:
    #> [1] LC_COLLATE=C
    #> [2] LC_CTYPE=English_United States.utf8
    #> [3] LC_MONETARY=English_United States.utf8
    #> [4] LC_NUMERIC=C
    #> [5] LC_TIME=English_United States.utf8
    #>
    #> time zone: America/New_York
    #> tzcode source: internal
    #>
    #> attached base packages:
    #> [1] stats4    stats     graphics  grDevices utils     datasets  methods
    #> [8] base
    #>
    #> other attached packages:
    #>  [1] scater_1.37.0               ggplot2_3.5.2
    #>  [3] scuttle_1.19.0              CiteFuse_1.21.0
    #>  [5] MultiAssayExperiment_1.35.3 SingleCellExperiment_1.31.0
    #>  [7] SummarizedExperiment_1.39.0 Biobase_2.69.0
    #>  [9] GenomicRanges_1.61.0        GenomeInfoDb_1.45.4
    #> [11] IRanges_2.43.0              MatrixGenerics_1.21.0
    #> [13] matrixStats_1.5.0           MuData_1.13.0
    #> [15] rhdf5_2.53.1                S4Vectors_0.47.0
    #> [17] BiocGenerics_0.55.0         generics_0.1.4
    #> [19] Matrix_1.7-3                BiocStyle_2.37.0
    #>
    #> loaded via a namespace (and not attached):
    #>   [1] gridExtra_2.3        rlang_1.1.6          magrittr_2.0.3
    #>   [4] ggridges_0.5.6       compiler_4.5.0       vctrs_0.6.5
    #>   [7] reshape2_1.4.4       stringr_1.5.1        pkgconfig_2.0.3
    #>  [10] crayon_1.5.3         fastmap_1.2.0        magick_2.8.6
    #>  [13] XVector_0.49.0       labeling_0.4.3       ggraph_2.2.1
    #>  [16] rmarkdown_2.29       ggbeeswarm_0.7.2     UCSC.utils_1.5.0
    #>  [19] tinytex_0.57         purrr_1.0.4          bluster_1.19.0
    #>  [22] xfun_0.52            beachmat_2.25.1      randomForest_4.7-1.2
    #>  [25] cachem_1.1.0         jsonlite_2.0.0       rhdf5filters_1.21.0
    #>  [28] DelayedArray_0.35.1  BiocParallel_1.43.3  Rhdf5lib_1.31.0
    #>  [31] tweenr_2.0.3         cluster_2.1.8.1      irlba_2.3.5.1
    #>  [34] parallel_4.5.0       R6_2.6.1             stringi_1.8.7
    #>  [37] bslib_0.9.0          RColorBrewer_1.1-3   limma_3.65.1
    #>  [40] compositions_2.0-8   jquerylib_0.1.4      Rcpp_1.0.14
    #>  [43] bookdown_0.43        knitr_1.50           mixtools_2.0.0.1
    #>  [46] BiocBaseUtils_1.11.0 splines_4.5.0        igraph_2.1.4
    #>  [49] tidyselect_1.2.1     dichromat_2.0-0.1    abind_1.4-8
    #>  [52] yaml_2.3.10          viridis_0.6.5        codetools_0.2-20
    #>  [55] lattice_0.22-7       tibble_3.2.1         plyr_1.8.9
    #>  [58] withr_3.0.2          evaluate_1.0.3       Rtsne_0.17
    #>  [61] survival_3.8-3       bayesm_3.1-6         polyclip_1.10-7
    #>  [64] kernlab_0.9-33       pillar_1.10.2        BiocManager_1.30.25
    #>  [67] tensorA_0.36.2.1     plotly_4.10.4        dbscan_1.2.2
    #>  [70] scales_1.4.0         glue_1.8.0           metapod_1.17.0
    #>  [73] pheatmap_1.0.12      lazyeval_0.2.2       tools_4.5.0
    #>  [76] BiocNeighbors_2.3.1  robustbase_0.99-4-1  data.table_1.17.4
    #>  [79] ScaledMatrix_1.17.0  locfit_1.5-9.12      scran_1.37.0
    #>  [82] graphlayouts_1.2.2   tidygraph_1.3.1      cowplot_1.1.3
    #>  [85] grid_4.5.0           tidyr_1.3.1          edgeR_4.7.2
    #>  [88] nlme_3.1-168         beeswarm_0.4.0       BiocSingular_1.25.0
    #>  [91] ggforce_0.4.2        vipor_0.4.7          rsvd_1.0.5
    #>  [94] cli_3.6.5            segmented_2.1-4      S4Arrays_1.9.1
    #>  [97] viridisLite_0.4.2    dplyr_1.1.4          uwot_0.2.3
    #> [100] gtable_0.3.6         DEoptimR_1.1-3-1     sass_0.4.10
    #> [103] digest_0.6.37        dqrng_0.4.1          SparseArray_1.9.0
    #> [106] ggrepel_0.9.6        htmlwidgets_1.6.4    farver_2.1.2
    #> [109] memoise_2.0.1        htmltools_0.5.8.1    lifecycle_1.0.4
    #> [112] httr_1.4.7           statmod_1.5.0        MASS_7.3-65