if (!requireNamespace("ggplot2", quietly = TRUE)) install.packages("ggplot2")
if (!requireNamespace("ggrepel", quietly = TRUE)) install.packages("ggrepel")
if (!requireNamespace("dplyr", quietly = TRUE)) install.packages("dplyr")
library(ggplot2)
library(ggrepel)
library(dplyr)Quadrant Scatter Plot
A quadrant (or nine-grid) scatter plot displays two measurements for the same set of features, overlays quadrant threshold lines, and highlights named features of interest with ggrepel labels [1]. It is the standard figure for multi-omics concordance (RNA vs protein, RNA vs ATAC) or for showing genes in biologically meaningful regions (up/up, up/down, etc.).
Example

Setup
System Requirements: Cross-platform (Linux/MacOS/Windows)
Programming Language: R
Dependent packages:
ggplot2;ggrepel;dplyr
sessioninfo::session_info("attached")─ Session info ───────────────────────────────────────────────────────────────
setting value
version R version 4.6.2 (2026-10-29)
os Ubuntu 26.04.1 LTS
system x86_64, linux-gnu
ui X11
language (EN)
collate C.UTF-8
ctype C.UTF-8
tz UTC
date 2026-11-05
pandoc 3.7.0.2 @ /usr/bin/ (via rmarkdown)
quarto 1.11.9 @ /usr/local/bin/quarto
─ Packages ───────────────────────────────────────────────────────────────────
! package * version date (UTC) lib source
P dplyr * 1.2.1 2026-04-03 [?] RSPM
P ggplot2 * 4.0.3.9000 2026-11-03 [?] Github (tidyverse/ggplot2@b638d65)
P ggrepel * 0.9.8 2026-03-17 [?] RSPM
[1] /home/runner/.cache/R/renv/library/Bizard-5140bdc6/linux-ubuntu-resolute/R-4.6/x86_64-pc-linux-gnu
[2] /home/runner/work/_temp/Library
[3] /opt/R/4.6.2/lib/R/library
* ── Packages attached to the search path.
P ── Loaded and on-disk path mismatch.
──────────────────────────────────────────────────────────────────────────────
Data Preparation
We simulate log2 fold changes for 500 genes measured by RNA-seq and proteomics, with a subset of concordant and discordant genes, plus named highlights.
set.seed(2101)
n <- 500
genes <- paste0("Gene", sprintf("%04d", 1:n))
rna <- rnorm(n, sd = 1.5)
prot <- rna * 0.7 + rnorm(n, sd = 0.8)
# make a few discordant genes
disc <- sample(1:n, 15)
prot[disc] <- -rna[disc] + rnorm(15, sd = 0.3)
df_q <- data.frame(gene = genes, rna = rna, prot = prot)
# genes to label
label_genes <- c("Gene0001", "Gene0002", "Gene0015", "Gene0020",
"Gene0005", "Gene0008", "Gene0003")Visualization
1. Nine-Grid Quadrant Scatter with Labels
threshold <- 1
ggplot(df_q, aes(rna, prot)) +
geom_point(alpha = 0.4, size = 1.5, colour = "grey50") +
geom_vline(xintercept = c(-threshold, threshold), linetype = "dashed", colour = "grey40") +
geom_hline(yintercept = c(-threshold, threshold), linetype = "dashed", colour = "grey40") +
geom_point(data = subset(df_q, gene %in% label_genes),
colour = "#B2182B", size = 2.5) +
ggrepel::geom_text_repel(
data = subset(df_q, gene %in% label_genes),
aes(label = gene), size = 3, colour = "#B2182B",
box.padding = 0.4, max.overlaps = 20) +
coord_equal(xlim = c(-4, 4), ylim = c(-4, 4)) +
labs(x = "RNA-seq log2FC", y = "Proteomics log2FC",
title = "RNA vs protein concordance") +
theme_classic(base_size = 11)
2. Coloured Quadrant Regions
df_q <- df_q |>
mutate(quadrant = case_when(
rna > threshold & prot > threshold ~ "Concordant up",
rna < -threshold & prot < -threshold ~ "Concordant down",
abs(rna) > threshold & abs(prot) > threshold ~ "Discordant",
TRUE ~ "Not significant"))
quad_cols <- c("Concordant up" = "#B2182B", "Concordant down" = "#2166AC",
"Discordant" = "#4D9342", "Not significant" = "grey80")
ggplot(df_q, aes(rna, prot, colour = quadrant)) +
geom_point(size = 1.5, alpha = 0.6) +
geom_vline(xintercept = c(-threshold, threshold), linetype = "dashed", colour = "grey40") +
geom_hline(yintercept = c(-threshold, threshold), linetype = "dashed", colour = "grey40") +
scale_colour_manual(values = quad_cols) +
geom_point(data = subset(df_q, gene %in% label_genes),
colour = "black", size = 2.5, shape = 21, fill = "gold") +
ggrepel::geom_text_repel(
data = subset(df_q, gene %in% label_genes),
aes(label = gene), size = 3, colour = "black",
box.padding = 0.4, max.overlaps = 20) +
coord_equal(xlim = c(-4, 4), ylim = c(-4, 4)) +
labs(x = "RNA-seq log2FC", y = "Proteomics log2FC", colour = "Quadrant") +
theme_classic(base_size = 11) +
theme(legend.position = "bottom")
Practice tips
- Adjust the
thresholdto match your data’s fold-change cutoff; the default ±1 corresponds to |log2FC| > 1. - Use
geom_text_repel()withmax.overlaps = 20for readable labels even with many highlighted genes. - The quadrant assignment via
case_when()can be adapted for any number of regions by adding more conditions.
Application
Quadrant scatter plots are the standard figure for multi-omics concordance, drug-response screens, and CRISPR screen comparisons.
Reference
[1] Xiaofan Lu, et al. FigureYa: A Standardized Visualization Framework for Enhancing Biomedical Data Interpretation and Research Efficiency. iMetaMed. 2025. doi:10.1002/imm3.70005
