Skip to contents

Overview

GwasSumStats and QtlSumStats are DFrame-subclass collections of summary statistics. Each row carries one entry — a GRanges of per-variant statistics — annotated by an identity tuple:

Class Row key One row is
GwasSumStats study one GWAS study over a region
QtlSumStats (study, context, trait) one molecular trait in one context

Every entry GRanges must carry these mcols: SNP, A1 (effect / dosage-counted allele), A2 (other allele), Z, N; optionally MAF, INFO, BETA, SE, P. Two settings apply to the whole collection and are stored in slots, not columns:

  • genome — the build label (e.g. "GRCh38").
  • ldSketch — a GenotypeHandle giving the LD reference used for QC and downstream analyses.

Optional per-row columns: varY (phenotype variance) and, for case/control GWAS, nCase / nControl.

QtlSumStats and GwasSumStats can be constructed in two ways:

The loaders run no QC. They build the raw collection with an empty qcInfo slot. Allele harmonization, MAF/INFO/N filtering, and LD-mismatch QC are a separate, mandatory step — summaryStatsQc() — documented in the Quality Control for GWAS and QTL Summary Statistics vignette. Downstream pipelines reject collections whose qcInfo is empty.

Toy data for this vignette

We will use the toy_ref PLINK1 panel (chr22) as the LD reference, and a handful of its variants as our summary statistics so the LD-overlap check succeeds.

genoPrefix <- sub("\\.bed$", "",
                  system.file("extdata", "toy_ref.bed", package = "pecotmr"))
ldSketch <- GenotypeHandle(plink1Prefix = genoPrefix)

# Five real toy_ref chr22 sites (positions and alleles taken from the panel).
sumstatsDf <- data.frame(
  chrom      = rep("22", 5),
  pos        = c(14560203, 14564328, 14850625, 14870204, 14878387),
  variant_id = c("rs11089128", "rs7288972", "rs11167319", "rs8138488", "rs2186521"),
  A1         = c("G", "C", "G", "C", "A"),
  A2         = c("A", "T", "T", "T", "G"),
  z          = c(1.20, -0.42, 2.13, 0.05, -1.83),
  n_sample   = rep(10000L, 5),
  stringsAsFactors = FALSE)

workdir <- file.path(tempdir(), "sumstats-vignette")
dir.create(workdir, showWarnings = FALSE)
gwasTsv <- file.path(workdir, "study1.tsv")
readr::write_tsv(sumstatsDf, gwasTsv)

GwasSumStats from a manifest

The GWAS manifest has one row per study. genome and ldSketch are collection-level, so they are supplied as arguments (or as constant genome / ldSketchPath columns).

Column Required Meaning
study (alias study_id) yes, unique study identifier
sumStatsPath (aliases path, file_path) yes sumstats file
columnMapping (alias column_mapping) no YAML column map (see below)
nCase / nControl no case / control counts
varY no phenotype variance
genome, ldSketchPath single-valued may instead be arguments
gwasManifest <- data.frame(
  study        = "MyGWAS",
  sumStatsPath = gwasTsv,
  stringsAsFactors = FALSE)

gws <- loadGwasSumStatsFromManifest(
  gwasManifest, genome = "GRCh38", ldSketch = ldSketch)
gws
## GwasSumStats: 1 studies, genome build GRCh38
##   LD sketch: plink1 @ /tmp/Rtmpi0K25n/temp_libpatha6b6057489c/pecotmr/extdata/toy_ref
S4Vectors::mcols(gws$entry[[1]])
## DataFrame with 5 rows and 5 columns
##           SNP          A1          A2         Z         N
##   <character> <character> <character> <numeric> <numeric>
## 1  rs11089128           G           A      1.20     10000
## 2   rs7288972           C           T     -0.42     10000
## 3  rs11167319           G           T      2.13     10000
## 4   rs8138488           C           T      0.05     10000
## 5   rs2186521           A           G     -1.83     10000

The ldSketch argument also accepts a path/prefix or a chromosome-sharded genoMeta specification (see ?GenotypeHandle), so a genome-wide reference can be supplied as a #chr,path meta file rather than a single handle.

GwasSumStats manually

The loader is a wrapper over the GwasSumStats() constructor. Built by hand, the pattern is: make one GRanges per study with the required mcols, then call the constructor with the shared genome and ldSketch.

entry <- GenomicRanges::GRanges(
  seqnames = paste0("chr", sumstatsDf$chrom),
  ranges   = IRanges::IRanges(start = sumstatsDf$pos, width = 1L))
S4Vectors::mcols(entry) <- S4Vectors::DataFrame(
  SNP = sumstatsDf$variant_id,
  A1  = sumstatsDf$A1, A2 = sumstatsDf$A2,
  Z   = sumstatsDf$z,  N  = sumstatsDf$n_sample)

gwsManual <- GwasSumStats(
  study    = "MyGWAS",
  entry    = list(entry),
  genome   = "GRCh38",
  ldSketch = ldSketch)
gwsManual
## GwasSumStats: 1 studies, genome build GRCh38
##   LD sketch: plink1 @ /tmp/Rtmpi0K25n/temp_libpatha6b6057489c/pecotmr/extdata/toy_ref

For a case/control study, pass nCase / nControl; for sufficient statistics interface with continuous traits, pass varY.

QtlSumStats from a manifest

The QTL manifest has one row per (study, context, trait) tuple. Each row points at a sumstats file for that trait.

Column Required Meaning
study yes study identifier
context (alias cond) yes context / condition
trait (alias gene_id) yes molecular trait id
sumStatsPath (alias path) yes sumstats file for this trait
columnMapping, varY no as for GWAS
genome, ldSketchPath single-valued may instead be arguments
qtlManifest <- data.frame(
  study        = "MyQTL",
  context      = "Blood",
  trait        = "ENSG00000001",
  sumStatsPath = gwasTsv,           # reuse the toy table for one gene
  stringsAsFactors = FALSE)

qts <- loadQtlSumStatsFromManifest(
  qtlManifest, genome = "GRCh38", ldSketch = ldSketch)
qts
## QtlSumStats: 1 entries, genome build GRCh38
##   1 studies, 1 contexts, 1 traits
##   LD sketch: plink1 @ /tmp/Rtmpi0K25n/temp_libpatha6b6057489c/pecotmr/extdata/toy_ref

QtlSumStats manually

qtsManual <- QtlSumStats(
  study    = "MyQTL",
  context  = "Blood",
  trait    = "ENSG00000001",
  entry    = list(entry),
  genome   = "GRCh38",
  ldSketch = ldSketch)
qtsManual
## QtlSumStats: 1 entries, genome build GRCh38
##   1 studies, 1 contexts, 1 traits
##   LD sketch: plink1 @ /tmp/Rtmpi0K25n/temp_libpatha6b6057489c/pecotmr/extdata/toy_ref

For a multi-context collection (e.g. as input to mash), pass parallel study / context / trait vectors and a matching list of entry GRanges — one element per tuple.

Reading from files

Column mapping (YAML)

When columns in a sumstats file don’t match the standard names, supply a columnMapping — a YAML file of standardName: sourceName entries (the xqtl-protocol format), either per-row in the manifest or as a default argument. Standard keys are chrom, pos, variant_id, A1, A2, z, n_sample (and optional beta, se, p, maf, info).

oddDf <- sumstatsDf
names(oddDf) <- c("CHR", "POS", "RSID", "EA", "OA", "ZSCORE", "SAMPLE_N")
oddTsv <- file.path(workdir, "odd_columns.tsv")
readr::write_tsv(oddDf, oddTsv)

mapPath <- file.path(workdir, "column_map.yaml")
yaml::write_yaml(list(chrom = "CHR", pos = "POS", variant_id = "RSID",
                      A1 = "EA", A2 = "OA", z = "ZSCORE",
                      n_sample = "SAMPLE_N"), mapPath)

gwsMapped <- loadGwasSumStatsFromManifest(
  data.frame(study = "MyGWAS", sumStatsPath = oddTsv, columnMapping = mapPath),
  genome = "GRCh38", ldSketch = ldSketch)
length(gwsMapped$entry[[1]])
## [1] 5

Region-restricted reads (tabix)

If a region is supplied and the sumstats file is compressed with bgzip and indexed with tabix (a <file>.tbi must exist in the same directory), only that region is read. Plain-text files are read whole and the region is ignored with a warning. The tabix header line must begin with # so its column names can be recovered.

tabixDf <- sumstatsDf
names(tabixDf)[1] <- "#chrom"      # tabix header convention
tabixTsv <- file.path(workdir, "study1.sorted.tsv")
readr::write_tsv(tabixDf, tabixTsv)
gz <- Rsamtools::bgzip(tabixTsv, dest = paste0(tabixTsv, ".gz"), overwrite = TRUE)
Rsamtools::indexTabix(gz, seq = 1L, start = 2L, end = 2L, comment = "#")
## [1] "/tmp/RtmpsQ3DfE/sumstats-vignette/study1.sorted.tsv.gz.tbi"
gwsRegion <- loadGwasSumStatsFromManifest(
  data.frame(study = "MyGWAS", sumStatsPath = gz),
  genome = "GRCh38", ldSketch = ldSketch,
  region = "22:14560000-14565000")
length(gwsRegion$entry[[1]])   # only the two variants in the region
## [1] 2

GWAS-VCF

VCF summary statistics are read as GWAS-VCF: the effect allele A1 is the ALT allele, A2 is REF, and the statistics come from the per-study FORMAT fields ES, SE, LP, SS, EAF (Z = ES / SE, N = SS, P = 10^-LP, MAF = fold(EAF)). Reading only a specific requires the VCF to be compressed with bgzip and indexed with tabix. BCF is not supported — convert to a bgzipped VCF first. Support for reading VCFs requires the VariantAnnotation package.

vcfLines <- c(
  "##fileformat=VCFv4.2",
  '##FORMAT=<ID=ES,Number=A,Type=Float,Description="effect size">',
  '##FORMAT=<ID=SE,Number=A,Type=Float,Description="standard error">',
  '##FORMAT=<ID=LP,Number=A,Type=Float,Description="-log10 p">',
  '##FORMAT=<ID=SS,Number=A,Type=Float,Description="sample size">',
  "##contig=<ID=22>",
  paste("#CHROM", "POS", "ID", "REF", "ALT", "QUAL", "FILTER", "INFO",
        "FORMAT", "MyGWAS", sep = "\t"))
for (i in seq_len(nrow(sumstatsDf))) {
  vcfLines <- c(vcfLines, paste(
    "22", sumstatsDf$pos[i], sumstatsDf$variant_id[i],
    sumstatsDf$A2[i], sumstatsDf$A1[i],   # REF = A2, ALT = A1
    ".", "PASS", ".", "ES:SE:LP:SS",
    sprintf("%.3f:0.100:1.000:10000", sumstatsDf$z[i] * 0.1), sep = "\t"))
}
vcfPath <- file.path(workdir, "study1.vcf")
writeLines(vcfLines, vcfPath)

gwsVcf <- loadGwasSumStatsFromManifest(
  data.frame(study = "MyGWAS", sumStatsPath = vcfPath),
  genome = "GRCh38", ldSketch = ldSketch)
S4Vectors::mcols(gwsVcf$entry[[1]])[, c("SNP", "A1", "A2", "Z", "N")]
## DataFrame with 5 rows and 5 columns
##           SNP          A1          A2         Z         N
##   <character> <character> <character> <numeric> <numeric>
## 1  rs11089128           G           A      1.20     10000
## 2   rs7288972           C           T     -0.42     10000
## 3  rs11167319           G           T      2.13     10000
## 4   rs8138488           C           T      0.05     10000
## 5   rs2186521           A           G     -1.83     10000

For a multi-sample GWAS-VCF, pass sampleSelect = "<column>" to pick the study; override the FORMAT tag names with formatMapping if they differ from ES/SE/LP/SS/EAF.

Next steps

The collections carry no QC yet. Run summaryStatsQc() before any downstream analysis (it populates qcInfo; downstream pipelines require it):

gws_qcd <- summaryStatsQc(gws, zMismatchQc = "slalom")

See the Quality Control for GWAS and QTL Summary Statistics vignette for the full QC pipeline. QtlSumStats and GwasSumStats are direct inputs to fineMappingPipeline(), twasWeightsPipeline() (QtlSumStats only) and colocBoostPipeline()— see the Fine-mapping with pecotmr, Learning TWAS weights with pecotmr and Multi-trait colocalization with ColocBoost vignettes.

## [1] "GRCh38"
as.character(gws$study)
## [1] "MyGWAS"
length(getSumStats(gws))   # variants in the first entry
## [1] 5
## R version 4.5.3 (2026-03-11)
## Platform: x86_64-conda-linux-gnu
## Running under: Ubuntu 24.04.4 LTS
## 
## Matrix products: default
## BLAS/LAPACK: /home/runner/work/pecotmr/pecotmr/.pixi/envs/default/lib/libopenblasp-r0.3.33.so;  LAPACK version 3.12.0
## 
## locale:
##  [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
##  [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
##  [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
## [10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   
## 
## time zone: Etc/UTC
## tzcode source: system (glibc)
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] pecotmr_0.6.7
## 
## loaded via a namespace (and not attached):
##   [1] DBI_1.3.0                   bitops_1.0-9               
##   [3] rlang_1.3.0                 magrittr_2.0.5             
##   [5] otel_0.2.0                  matrixStats_1.5.0          
##   [7] susieR_0.16.4               compiler_4.5.3             
##   [9] RSQLite_3.53.3              GenomicFeatures_1.62.0     
##  [11] png_0.1-9                   systemfonts_1.3.2          
##  [13] vctrs_0.7.3                 quadprog_1.5-8             
##  [15] stringr_1.6.0               pkgconfig_2.0.3            
##  [17] crayon_1.5.3                fastmap_1.2.0              
##  [19] XVector_0.50.0              Rsamtools_2.26.0           
##  [21] rmarkdown_2.31              tzdb_0.5.0                 
##  [23] ragg_1.5.2                  purrr_1.2.2                
##  [25] bit_4.6.0                   xfun_0.60                  
##  [27] Rfast_2.1.5.2               cachem_1.1.0               
##  [29] cigarillo_1.0.0             jsonlite_2.0.0             
##  [31] blob_1.3.0                  DelayedArray_0.36.0        
##  [33] reshape_0.8.10              tictoc_1.2.1               
##  [35] BiocParallel_1.44.0         irlba_2.3.7                
##  [37] parallel_4.5.3              R6_2.6.1                   
##  [39] VariantAnnotation_1.56.0    bslib_0.11.0               
##  [41] stringi_1.8.7               RColorBrewer_1.1-3         
##  [43] rtracklayer_1.70.1          GenomicRanges_1.62.1       
##  [45] jquerylib_0.1.4             numDeriv_2016.8-1.1        
##  [47] Rcpp_1.1.2                  Seqinfo_1.0.0              
##  [49] SummarizedExperiment_1.40.0 knitr_1.51                 
##  [51] readr_2.2.0                 IRanges_2.44.0             
##  [53] splines_4.5.3               Matrix_1.7-5               
##  [55] tidyselect_1.2.1            abind_1.4-8                
##  [57] yaml_2.3.12                 codetools_0.2-20           
##  [59] metafor_5.0-1               curl_7.1.0                 
##  [61] lattice_0.22-9              tibble_3.3.1               
##  [63] plyr_1.8.9                  withr_3.0.3                
##  [65] Biobase_2.70.0              KEGGREST_1.50.0            
##  [67] S7_0.2.2                    evaluate_1.0.5             
##  [69] survival_3.8-9              desc_1.4.3                 
##  [71] RcppParallel_5.1.11-2       snpStats_1.60.0            
##  [73] Biostrings_2.78.0           pillar_1.11.1              
##  [75] MatrixGenerics_1.22.0       metadat_1.6-0              
##  [77] stats4_4.5.3                generics_0.1.4             
##  [79] vroom_1.7.1                 mathjaxr_2.0-0             
##  [81] RCurl_1.98-1.19             S4Vectors_0.48.0           
##  [83] hms_1.1.4                   ggplot2_4.0.3              
##  [85] scales_1.4.0                glue_1.8.1                 
##  [87] tools_4.5.3                 BiocIO_1.20.0              
##  [89] BSgenome_1.78.0             GenomicAlignments_1.46.0   
##  [91] fs_2.1.0                    XML_3.99-0.22              
##  [93] grid_4.5.3                  tidyr_1.3.2                
##  [95] AnnotationDbi_1.72.0        nlme_3.1-170               
##  [97] restfulr_0.0.17             cli_3.6.6                  
##  [99] zigg_0.0.2                  textshaping_1.0.5          
## [101] mixsqp_0.3-54               S4Arrays_1.10.1            
## [103] dplyr_1.2.1                 gtable_0.3.6               
## [105] sass_0.4.10                 digest_0.6.39              
## [107] BiocGenerics_0.56.0         SparseArray_1.10.8         
## [109] rjson_0.2.23                htmlwidgets_1.6.4          
## [111] farver_2.1.2                memoise_2.0.1              
## [113] htmltools_0.5.9             pkgdown_2.2.1              
## [115] lifecycle_1.0.5             httr_1.4.8                 
## [117] bit64_4.8.2                 MASS_7.3-66