Implements the Regional Association Score (RAS) method for
genome-wide association studies (GWAS). For each single nucleotide
polymorphism (SNP), RAS quantifies the strength of association within its
surrounding genomic region, arranges these regional scores along the
chromosome into a signal profile, and locates association regions on that
profile with one of two detectors: the original changepoint detector, or a
box-scan region detector that also delimits broad plateau-shaped regions.
Genotypes can be streamed from a chunked on-disk format through compiled
code so that peak memory no longer grows with chromosome size, and the
regional weights can be taken from an independent external GWAS
(harmonised summary statistics) instead of a within-sample split. The
method is described in Jiang and Zhang (2025)
The RAS package implements the Regional Association Score method for genome-wide association studies. For every SNP it measures the strength of association in the surrounding region, arranges these regional scores along the chromosome into a −log₁₀(p) profile, and locates association regions on that profile. The method supports continuous and binary traits.
If you use this package in your research, please cite:
Y. Jiang & H. Zhang, Empowering genome-wide association studies via a visualizable test based on the regional association score, Proc. Natl. Acad. Sci. U.S.A. 122(9) e2419721122 (2025). https://doi.org/10.1073/pnas.2419721122
ras(), ras_scan() and ras_detect() are now the compiled, disk-backed implementations; the pure-R implementations of 1.0.x are available as ras_original(), ras_scan_original() and ras_detect_original(). ras() still accepts an in-memory genotype matrix.detector = "box" (default) is a box-scan detector that reports intervals [tau_L, tau_R], delimits broad plateau-shaped regions, and calibrates its own threshold on permuted phenotypes. detector = "changepoint" is the published two-pass changepoint detector of 1.0.x. See Choosing a detector below.ras() streams genotypes from a chunked on-disk file (.rasbin), so peak memory is set by a chunk size rather than by the chromosome. Profiles agree with ras_original() to machine precision.ras_harmonize_sumstats() aligns an independent GWAS to your genotype map; ras_scan_external() then scans all your samples with those fixed weights, without a train/holdout split.install.packages("RAS") # CRAN release
# development version
install.packages("remotes")
remotes::install_github("hepingzhangyale/RAS")
Installing from source needs a C compiler (Rtools on Windows).
library(RAS)
result <- ras(
geno, phenotype, covariates,
covariate_cols = c("age", "sex", paste0("pc", 1:10)),
is_continuous = TRUE,
chrom = 1,
save_dir = "results/"
)
print(result) # detected regions
plot(result) # full-chromosome scan profile
plot(result, zoom = TRUE) # zoomed view around each region
result$detection$regions # one row per interval: pos_L, pos_R, T_box, anchor_pos, ...
geno may be an in-memory matrix (converted for the run) or, for anything large, a .rasbin file. By default the box-scan detector calibrates its threshold by repeating the scan on box_null = 100 permuted phenotypes (exact order statistic); box_method = "gumbel" gets a stable threshold from about 20 permutations at the price of being slightly liberal. For a large cohort run the null scans on a random subset of individuals (box_null_n = 20000; the null maximum depends on the linkage structure, not on the sample size), or calibrate once with ras_box_calibrate() and pass box_calibration (see below).
bed_to_rasbin("cohort_chr1.bed", "cohort_chr1.rasbin") # from PLINK 1 .bed/.bim/.fam
# or: geno_to_rasbin(geno_matrix, "cohort_chr1.rasbin") # from an in-memory matrix / .rds
result <- ras(
"cohort_chr1.rasbin", phenotype, covariates,
covariate_cols = c("age", "sex", paste0("pc", 1:10)),
is_continuous = TRUE,
chrom = 1,
chunk_snps = 5000, # SNP columns held in memory at a time
cores = 4, # repetitions run in parallel
save_dir = "results/"
)
Sample order in phenotype and covariates must match the .rasbin file (the .fam order for bed_to_rasbin()).
For binary traits ras() uses a Rao score test that fits the covariate-only null model once and evaluates each window in closed form. The per-window logistic-regression Wald scan that was the default in 1.0.x is still available as scan_test = "glm"; it runs the in-memory ras_original() route (so it needs a genotype matrix) and gives the same detected regions.
Both detectors work on the same scan profile; the choice only affects Stages 2 and 3.
detector = "box" (default) |
detector = "changepoint" |
|
|---|---|---|
| What it reports | intervals [tau_L, tau_R] with an anchor |
single changepoint positions |
| Method | window mean above both flanks and above the profile background, in raw −log₁₀(p) units (ras_box_detect()) |
segmented regression + Davies test in a sliding window, then local re-validation (ras_detect(), ras_validate()) |
| Broad plateau-shaped regions | delimited at their edges | may be missed or placed inside the plateau |
| Threshold | calibrated on permuted phenotypes (automatic, or ras_box_calibrate()) |
fixed p-value defaults (published) |
| Backwards compatibility | new in 1.1.0 | same results as RAS 1.0.x |
## 1. null profiles: the same scan on permuted phenotypes
nulls <- lapply(1:200, function(b) {
set.seed(b)
ras_scan("cohort_chr1.rasbin", sample(phenotype), covariates,
covariate_cols = covs, is_continuous = TRUE,
save_dir = tempfile())$y
})
## 2. threshold at family-wise error 0.05
cal <- ras_box_calibrate(nulls, alpha = 0.05)
## 3. run with that calibration (skips the automatic one)
result <- ras("cohort_chr1.rasbin", phenotype, covariates,
covariate_cols = covs, is_continuous = TRUE,
box_calibration = cal, chrom = 1, save_dir = "results/")
## the changepoint detector of 1.0.x
result_cp <- ras("cohort_chr1.rasbin", phenotype, covariates,
covariate_cols = covs, is_continuous = TRUE,
detector = "changepoint", chrom = 1, save_dir = "results/")
The scan profile is never affected by the detector: result$scan is the same either way, so the two detectors can be compared on one scan.
When an independent GWAS of the same trait is available, its effect sizes can replace the within-sample training split. The scan then uses every individual of the target cohort once.
h <- ras_harmonize_sumstats(
sumstats = "external_gwas.tsv", # rsID / chr / pos / A1 / A2 / beta / p
map = "cohort_chr1.bim" # target variant map
)
h$qc # one row per harmonisation stage
scan <- ras_scan_external(
"cohort_chr1.rasbin", phenotype, covariates,
covariate_cols = covs, weights = h$weights, is_continuous = TRUE
)
det <- ras_box_detect(scan$x, scan$y, calibration = cal) # or ras_detect() + ras_validate()
ras_sumstats_report() gives a non-fatal preview of how a summary statistics file lines up with a map (ID convention, genome build, coverage along the chromosome) before anything is committed.
# Step 1: averaged -log10(p) profile
scan <- ras_scan(geno, phenotype, covariates,
covariate_cols = c("age", "sex", paste0("pc", 1:10)),
is_continuous = TRUE, chrom = 1, save_dir = "results/")
# Step 2 + 3, changepoint detector
detected <- ras_detect(scan$x, scan$y, window_size = 3000,
slope.p.values.threshold.left = 1e-10,
slope.p.values.threshold.right = 1e-20)
final <- ras_validate(detected, x = scan$x, y = scan$y,
this.skip = 10, p.value.threshold = 1e-10)
# Step 2 + 3, box-scan detector
final_box <- ras_box_detect(scan$x, scan$y, calibration = cal)
# Step 4: plot either one
result <- structure(list(scan = scan, detection = final, chrom = 1, save_dir = "results/"),
class = "ras")
plot(result)
?RAS # package overview and pipeline description
?ras # one-call entry point (matrix or .rasbin file)
?ras_original # the pure-R in-memory implementation of 1.0.x
?ras_box_detect # box-scan region detector
?ras_box_calibrate # its threshold
?ras_detect # changepoint detector, first pass
?ras_validate # changepoint detector, second pass
?ras_harmonize_sumstats # external summary statistics
?plot.ras # plotting
?ras_memory # memory and CPU diagnostics for the in-memory route