# scQTLtools: an R/Bioconductor package for comprehensive identification and visualization of single-cell eQTLs

# Introduction

Single-cell RNA sequencing (scRNA-seq) enables expression quantitative trait locus (eQTL) analysis at cellular resolution, offering new opportunities to uncover regulatory variants with cell-type-specific effects. However, existing tools are often limited in functionality, input compatibility, or scalability for sparse single-cell data. To address these challenges, we developed scQTLtools, a comprehensive R/Bioconductor package that facilitates end-to-end single-cell eQTL analysis, from preprocessing to visualization. The toolkit supports flexible input formats, including Seurat and SingleCellExperiment objects, handles both binary and three-class genotype encodings, and provides dedicated functions for gene expression normalization, SNP and gene filtering, eQTL mapping, and versatile result visualization. To accommodate diverse data characteristics, scQTLtools implements three statistical models—linear regression, Poisson regression, and zero-inflated negative binomial regression. We applied scQTLtools to scRNA-seq data from human acute myeloid leukemia and identified eQTLs with regulatory effects that varied across cell types. Visualization of SNP–gene pairs revealed both positive and negative associations between genotype and gene expression. These results demonstrate the ability of scQTLtools to uncover cell-type-specific regulatory variation that is often missed by bulk eQTL analyses. Overall, scQTLtools offers a robust, flexible, and user-friendly framework for dissecting genotype-expression relationships in heterogeneous cellular populations.

## Rationale for Bioconductor Submission

By seeking inclusion in Bioconductor, we aim to integrate scQTLtools into a well-established ecosystem that is widely used by researchers in bioinformatics. Bioconductor’s rigorous standards for package quality and its focus on reproducibility will enhance the credibility of scQTLtools. Additionally, being part of Bioconductor will provide access to a broader user base and foster collaboration with other developers, contributing to the ongoing improvement and validation of the package.

## Citation

If you find this tool useful, please cite:

https://github.com/XFWuCN/scQTLtools

https://bioconductor.org/packages/3.21/bioc/html/scQTLtools.html

https://bioconductor.org/packages/devel/bioc/html/scQTLtools.html

# Installation

    if (!require("BiocManager"))
        install.packages("BiocManager")
    BiocManager::install("scQTLtools")

# Overview of the package

The functions in scQTLtools can be categorized into data input, data pre-process, sc-eQTL calling and visualization modules. The functions and their brief descriptions are summarized below.

## General Workflow

Each module is summarized as shown below.

scQTLtools requires two key input data: a single-cell gene expression dataset and a corresponding SNP genotype matrix. The single-cell gene expression dataset can be either a gene expression matrix or an object such as a Seurat v4 object or a Bioconductor SingleCellExperiment object. The input genotype matrix should follow a 0/1/2/3 encoding scheme: 1 for homozygous reference genotype, 2 for homozygous alternative genotype, 3 for heterozygous genotype, and 0 for missing values. Moreover, scQTLtools can support a simplified 0/1/2 encoding scheme, where 2 denotes a non-reference genotype. Additionally, the package includes functionality to normalize the raw single-cell gene expression matrix, and filter SNP–gene pairs. After pre-process, scQTLtools implements the `callQTL()` function to identify sc-eQTLs. Moreover, visualization at the single-cell level demonstrates the specificity of eQTLs across distinct cell types or cellular states.

## Comparison and advantages compared to similar works

We compared scQTLtools to other packages with similar functionality, including eQTLsingle, SCeQTL, Matrix eQTL, and iBMQ, as shown in the table below.

Among these tools, scQTLtools stands out for its comprehensive features:

1. scQTLtools accepts SingleCellExperiment object and Seurat object as input data formats, which are particularly beneficial for users working with single-cell RNA-seq data, and promote the interoperability with the current Bioconductor ecosystem.

2. scQTLtools supports both binary and three-class genotype encodings, enhancing its applicability across different genetic studies.

3. scQTLtools offers extensive data pre-processing capabilities, including quality control filtering for SNPs and genes, and normalization of raw gene expression matrix. Users can also customize the genomic window for defining SNP–gene pairs, enabling flexible cis-eQTL discovery.

4. scQTLtools provides three commonly used statistical models, which cater to various data distributions and analysis needs. This diversity allows users to select the most appropriate model for their specific dataset.

5. scQTLtools includes a wide range of visualization tools, which facilitate detailed exploration and interpretation of eQTL results at the single-cell level.

6. scQTLtools supports grouping by both cell type and cell state, which is crucial for analyzing the nuanced effects of genetic variants on gene expression within heterogeneous cell populations.

Overall, scQTLtools offers a comprehensive suite of features that enhance the analysis and interpretation of eQTLs.

# Required input files

The input file requires a single-cell gene expression dataset and a corresponding SNP genotype matrix. The single-cell gene expression dataset can be either a gene expression matrix or an object such as a Seurat v4 object or a Bioconductor SingleCellExperiment object.

- gene expression matrix: describes gene expressions, each row represents a gene and each column represents a cell.
- Seurat object/SingleCellExperiment object: a Seurat/SingleCellExperiment object.
- SNP genotype matrix: A matrix where each row represents a variant and each column a cell, using a 0/1/2/3 encoding scheme.

The columns (cells) of the genotype matrix should correspond to the columns (cells) of the gene expression matrix.

Example

    library(scQTLtools)
    # gene expression matrix
    data(GeneData)
    # SeuratObject
    data(Seurat_obj)
    # load the genotype data
    data(SNPData)
    data(SNPData2)

# Create eQTL object

The createQTLObject class is an R object designed to store data related to eQTL analysis, encompassing data lists, result data frames, and slots for biClassify, species, and group information.

Example

    eqtl_matrix <- createQTLObject(
        snpMatrix = SNPData,
        genedata = GeneData,
        biClassify = FALSE,
        species = 'human',
        group = NULL)

Users can set `biClassify` to TRUE to change the genotype encoding mode.

Example

    eqtl_matrix_bi <- createQTLObject(
        snpMatrix = SNPData,
        genedata = GeneData,
        biClassify = TRUE,
        species = 'human',
        group = NULL)

Users can use Seurat object instead of gene expression matrix.

Example

    eqtl_seurat <- createQTLObject(
        snpMatrix = SNPData2,
        genedata = Seurat_obj,
        biClassify = FALSE,
        species = 'human',
        group = "celltype")

Users can also take SingleCellExperiment object as input.

Example

    # Create a SingleCellExperiment object
    library(SingleCellExperiment)
    sce <- SingleCellExperiment(assays = list(counts = GeneData))
    eqtl_sce <- createQTLObject(
        snpMatrix = SNPData,
        genedata = sce,
        biClassify = FALSE,
        species = 'human',
        group = NULL)

# Normalize the raw gene expression matrix

Use `normalizeGene()` to normalize the raw gene expression matrix.

Example

    eqtl_matrix  <- normalizeGene(
        eQTLObject = eqtl_matrix,
        method = "logNormalize")

    eqtl_sce  <- normalizeGene(
        eQTLObject = eqtl_sce,
        method = "logNormalize")

# Identify the valid SNP–gene pairs

Here we use `filterGeneSNP()` to filter SNP–gene pairs.

Example

    eqtl_matrix <- filterGeneSNP(
        eQTLObject = eqtl_matrix,
        snpNumOfCellsPercent = 2,
        expressionMin = 0,
        expressionNumOfCellsPercent = 2)

    eqtl_seurat <- filterGeneSNP(
        eQTLObject = eqtl_seurat,
        snpNumOfCellsPercent = 2,
        expressionMin = 0,
        expressionNumOfCellsPercent = 2)

    eqtl_sce <- filterGeneSNP(
        eQTLObject = eqtl_sce,
        snpNumOfCellsPercent = 2,
        expressionMin = 0,
        expressionNumOfCellsPercent = 2)

# Identify single-cell eQTLs

Here we use `callQTL()` to perform single cell eQTL analysis.

Example

    eqtl1_matrix <- callQTL(
        eQTLObject = eqtl_matrix,
        gene_ids = NULL,
        downstream = NULL,
        upstream = NULL,
        gene_mart = NULL,
        snp_mart = NULL,
        pAdjustMethod = "bonferroni",
        useModel = "linear",
        pAdjustThreshold = 0.05,
        logfcThreshold = 0.1)

    eqtl1_seurat <- callQTL(
        eQTLObject = eqtl_seurat,
        gene_ids = NULL,
        downstream = NULL,
        upstream = NULL,
        gene_mart = NULL,
        snp_mart = NULL,
        pAdjustMethod = "bonferroni",
        useModel = "linear",
        pAdjustThreshold = 0.05,
        logfcThreshold = 0.025)

    eqtl1_sce <- callQTL(
        eQTLObject = eqtl_sce,
        gene_ids = NULL,
        downstream = NULL,
        upstream = NULL,
        gene_mart = NULL,
        snp_mart = NULL,
        pAdjustMethod = "bonferroni",
        useModel = "linear",
        pAdjustThreshold = 0.05,
        logfcThreshold = 0.025)

Users can use the parameter `gene_ids` to select one or several genes of interest for identifying sc-eQTLs.

Example

    eqtl2_matrix <- callQTL(
        eQTLObject = eqtl_matrix,
        gene_ids = c("CNN2",
                    "RNF113A",
                    "SH3GL1",
                    "INTS13",
                    "PLAU"),
        downstream = NULL,
        upstream = NULL,
        gene_mart = NULL,
        snp_mart = NULL,
        pAdjustMethod = "bonferroni",
        useModel = "poisson",
        pAdjustThreshold = 0.05,
        logfcThreshold = 0.1)

Users can also set `upstream` and `downstream` to specify SNPs proximal to the gene in the genome.

Example

    eqtl3_matrix <- callQTL(
        eQTLObject = eqtl_matrix,
        gene_ids = NULL,
        downstream = -9e7,
        upstream = 2e8,
        gene_mart = NULL,
        snp_mart = NULL,
        pAdjustMethod = "bonferroni",
        useModel = "poisson",
        pAdjustThreshold = 0.05,
        logfcThreshold = 0.05)

# Visualize the results.

Use `visualizeQTL()` to visualize sc-eQTL results. Four plot types are supported: “histplot”, “violin”, “boxplot”, or “QTLplot”.

Example

violin plot:

    visualizeQTL(
        eQTLObject = eqtl1_matrix,
        SNPid = "1:632647",
        Geneid = "RPS27",
        groupName = NULL,
        plottype = "QTLplot",
        removeoutlier = TRUE)

QTLplot:

By incorporating cell annotation from Seurat or SingleCellExperiment objects, scQTLtools can reveal cell-type-specific patterns of eQTL effects.

    visualizeQTL(
        eQTLObject = eqtl1_seurat,
        SNPid = "1:632647",
        Geneid = "RPS27",
        groupName = NULL,
        plottype = "QTLplot",
        removeoutlier = TRUE)

In addition, the parameter `groupName` is used to specify a particular single-cell group of interest.

    visualizeQTL(
        eQTLObject = eqtl1_seurat,
        SNPid = "1:632647",
        Geneid = "RPS27",
        groupName = "GMP",
        plottype = "QTLplot",
        removeoutlier = TRUE)

boxplot:

    visualizeQTL(
        eQTLObject = eqtl1_sce,
        SNPid = "1:632647",
        Geneid = "RPS27",
        groupName = NULL,
        plottype = "boxplot",
        removeoutlier = TRUE)

# References

1. Miao, Z., Deng, K.e., Wang, X., Zhang, X., Berger, B., 2018. DEsingle for detecting three types of differential expression in single-cell RNA-seq data. Bioinformatics 34 (18), 3223–3224. https://doi.org/10.1093/bioinformatics/bty332
2. Hu, Y., Xi, X., Yang, Q., Zhang, X., 2020. SCeQTL: An R package for identifying eQTL from single-cell parallel sequencing data. BMC Bioinformatics 21, 1–12. https://doi.org/10.1186/s12859-020-3534-6
3. Nathan, A., Asgari, S., Ishigaki, K. et al. Single-cell eQTL models reveal dynamic T cell state dependence of disease loci. Nature 606, 120–128 (2022). https://doi.org/10.1038/s41586-022-04713-1
4. Ma, T. , Li, H. , & Zhang, X. . (2021). Discovering single-cell eQTLs from scRNA-seq data only. Cold Spring Harbor Laboratory. https://doi.org/10.1016/j.gene.2022.146520
5. Schmiedel, B. J., Gonzalez-Colin, C., Fajardo, V., Rocha, J., Madrigal, A., Ramírez-Suástegui, C., Bhattacharyya, S., Simon, H., Greenbaum, J. A., Peters, B., Seumois, G., Ay, F., Chandra, V., & Vijayanand, P. (2022). Single-cell eQTL analysis of activated T cell subsets reveals activation and cell type-dependent effects of disease-risk variants. Science immunology, 7(68), eabm2508. https://doi.org/10.1126/sciimmunol.abm2508
6. Imholte, G. C. , Marie-Pier, S. B. , Labbe Aurélie,
   Deschepper, C. F. , & Raphael, G. . (2013). Ibmq: a r/bioconductor package for integrated bayesian modeling of eqtl data. Bioinformatics(21), 2797-2798. https://doi.org/10.1093/bioinformatics/btt485
7. Shabalin A. A. (2012). Matrix eQTL: ultra fast eQTL analysis via large matrix operations. Bioinformatics (Oxford, England), 28(10), 1353–1358. https://doi.org/10.1093/bioinformatics/bts163

# Session Info

    sessionInfo()

    ## R version 4.5.2 (2025-10-31)
    ## Platform: x86_64-pc-linux-gnu
    ## Running under: Ubuntu 24.04.4 LTS
    ##
    ## Matrix products: default
    ## BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
    ## LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so;  LAPACK version 3.12.0
    ##
    ## locale:
    ##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C
    ##  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=C
    ##  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8
    ##  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C
    ##  [9] LC_ADDRESS=C               LC_TELEPHONE=C
    ## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
    ##
    ## time zone: Etc/UTC
    ## tzcode source: system (glibc)
    ##
    ## attached base packages:
    ## [1] stats4    stats     graphics  grDevices utils     datasets  methods
    ## [8] base
    ##
    ## other attached packages:
    ##  [1] SingleCellExperiment_1.32.0 SummarizedExperiment_1.40.0
    ##  [3] Biobase_2.70.0              GenomicRanges_1.62.1
    ##  [5] Seqinfo_1.0.0               IRanges_2.44.0
    ##  [7] S4Vectors_0.48.0            BiocGenerics_0.56.0
    ##  [9] generics_0.1.4              MatrixGenerics_1.22.0
    ## [11] matrixStats_1.5.0           scQTLtools_1.2.4
    ## [13] BiocStyle_2.38.0
    ##
    ## loaded via a namespace (and not attached):
    ##   [1] DBI_1.3.0            httr2_1.2.2          biomaRt_2.66.1
    ##   [4] rlang_1.1.7          magrittr_2.0.4       gamlss_5.5-0
    ##   [7] compiler_4.5.2       RSQLite_2.4.6        png_0.1-8
    ##  [10] vctrs_0.7.1          stringr_1.6.0        pkgconfig_2.0.3
    ##  [13] crayon_1.5.3         fastmap_1.2.0        dbplyr_2.5.2
    ##  [16] XVector_0.50.0       labeling_0.4.3       rmarkdown_2.30
    ##  [19] purrr_1.2.1          bit_4.6.0            xfun_0.56
    ##  [22] cachem_1.1.0         jsonlite_2.0.0       progress_1.2.3
    ##  [25] blob_1.3.0           DelayedArray_0.36.0  BiocParallel_1.44.0
    ##  [28] VGAM_1.1-14          parallel_4.5.2       prettyunits_1.2.0
    ##  [31] R6_2.6.1             bslib_0.10.0         stringi_1.8.7
    ##  [34] RColorBrewer_1.1-3   limma_3.66.0         parallelly_1.46.1
    ##  [37] jquerylib_0.1.4      Rcpp_1.1.1           knitr_1.51
    ##  [40] future.apply_1.20.2  Matrix_1.7-4         splines_4.5.2
    ##  [43] tidyselect_1.2.1     abind_1.4-8          yaml_2.3.12
    ##  [46] gamlss.dist_6.1-1    codetools_0.2-20     curl_7.0.0
    ##  [49] listenv_0.10.0       lattice_0.22-9       tibble_3.3.1
    ##  [52] withr_3.0.2          KEGGREST_1.50.0      S7_0.2.1
    ##  [55] evaluate_1.0.5       future_1.69.0        survival_3.8-6
    ##  [58] BiocFileCache_3.0.0  xml2_1.5.2           Biostrings_2.78.0
    ##  [61] pillar_1.11.1        BiocManager_1.30.27  filelock_1.0.3
    ##  [64] sp_2.2-1             hms_1.1.4            ggplot2_4.0.2
    ##  [67] scales_1.4.0         globals_0.19.0       glue_1.8.0
    ##  [70] maketools_1.3.2      tools_4.5.2          sys_3.4.3
    ##  [73] locfit_1.5-9.12      buildtools_1.0.0     fs_1.6.6
    ##  [76] dotCall64_1.2        grid_4.5.2           AnnotationDbi_1.72.0
    ##  [79] patchwork_1.3.2      nlme_3.1-168         cli_3.6.5
    ##  [82] rappdirs_0.3.4       spam_2.11-3          S4Arrays_1.10.1
    ##  [85] dplyr_1.2.0          gtable_0.3.6         yulab.utils_0.2.4
    ##  [88] DESeq2_1.50.2        sass_0.4.10          digest_0.6.39
    ##  [91] gamlss.data_6.0-7    progressr_0.18.0     SparseArray_1.10.9
    ##  [94] SeuratObject_5.3.0   farver_2.1.2         memoise_2.0.1
    ##  [97] htmltools_0.5.9      lifecycle_1.0.5      httr_1.4.8
    ## [100] statmod_1.5.1        bit64_4.6.0-1        MASS_7.3-65