# How to Visualize Genomic Data in R with ggplot2: A Beginner's Guide

By the end of this guide, you will be able to take a table of genomic measurements and turn it into a layered, publication-quality figure in R. You will build a [sequencing coverage](/knowledge/molecular-biology/sequencing-coverage) track with gene annotations, a volcano plot from differential expression results, a grouped expression comparison of the kind used in published cancer studies, and a sequence logo for a DNA motif. You will also know which Bioconductor packages extend ggplot2 for genomics work, which errors to expect, and how to check that a figure is telling the truth.

You need three things on hand. First, a recent release of R, version 4.1 or newer, because the examples use the native pipe operator. Second, RStudio Desktop, which is free and makes running scripts comfortable, though plain R works too. Third, an internet connection for package installation. No prior ggplot2 experience is assumed. If you can run mean() on a vector of numbers, you can follow every example here.

Every dataset in this guide is simulated with a fixed random seed, so the code runs on any machine without downloads. When you finish the walkthrough, swap in your own processed data and the same code keeps working.

## Why R for Bioinformatics, and Why ggplot2

R is the default statistical language of genomics. Bioconductor, the open source and open development collection of R packages for high-throughput biology, supplies the standard analysis workflows, and its protocols run quality control, background correction, normalization, and summarization before any figure is drawn [1]. The graphics layer on top of those workflows is usually ggplot2. A 2023 methods chapter on metagenomics [data visualization](/blog/data-analysis/data-visualization-basics) teaches plotting in two stages, first with base R graphics and then with ggplot2, which it describes as widely used for data visualization in R [2].

Published studies lean on the same tool for their final figures. A 2025 study of insulin receptor silencing in lung adenocarcinoma retrieved expression data from the [Gene Expression Omnibus](/blog/guides/gene-expression-omnibus), screened hub genes in the ERK pathway, and used ggplot2 to visualize the significant genes, comparing expression patterns between tumor and para cancer tissue [3].

The deeper reason for ggplot2's dominance is conceptual. It implements the grammar of graphics, an idea that describes any plot as data plus aesthetic mappings plus stacked layers, rather than as a list of drawing commands. That description fits genomic data unusually well, because a genomic figure is almost always the same stack: positions on one axis, signal or expression on the other, samples as colors or facets, and genes or variants as annotation layers.

The ecosystem builds on this foundation. GenVisR provides publication-quality cohort-level genome graphics on top of ggplot2 and Bioconductor [4]. ggcoverage visualizes and annotates sequencing coverage [5]. ggkegg brings KEGG pathway data into the same grammar [6]. Learn the core grammar once and all of these packages become readable.

## Install Everything You Need

Open R or RStudio and run the following. The first line installs the core packages from CRAN, the general R package repository. The second installs BiocManager, the installer for Bioconductor, and then three genomic graphics packages.

```r
# Core plotting and data-wrangling packages from CRAN
install.packages(c("ggplot2", "dplyr", "tidyr", "ggrepel", "ggseqlogo", "scales"))

# Bioconductor packages for genomics-specific graphics
install.packages("BiocManager")
BiocManager::install(c("GenVisR", "ggcoverage", "ggkegg"))
```

Installation prints progress lines and ends with messages such as "* DONE (ggplot2)" on macOS and Linux, or "The downloaded binary packages are in" followed by a folder path on Windows. Some Bioconductor packages compile native code and pull many dependencies, so the first install can take several minutes. The walkthrough below only needs the CRAN packages, so you can start plotting while the Bioconductor installs run.

Confirm the setup:

```r
library(ggplot2)
library(dplyr)
packageVersion("ggplot2")
```

You should see output in this format:

```
[7] '3.5.1'
```

Any recent version works. If a command in this guide fails with "could not find function", the relevant package is installed but not attached, so run its library() call.

## The Grammar of Graphics in Two Minutes

Every ggplot2 figure follows the same skeleton:

```r
ggplot(data = my_data, aes(x = position, y = signal)) +
  geom_line() +
  scale_y_continuous(labels = scales::comma) +
  facet_wrap(~sample) +
  theme_bw()
```

Read the pieces like a sentence:

- **data**, the data frame holding your measurements.
- **aes()**, short for aesthetics, the mapping from columns to visual properties such as x, y, color, and fill.
- **geom_**, the geometric object that draws the data, such as points, lines, areas, or boxes.
- **scale_**, the rules that translate data values into visual values, such as log axes, color palettes, and axis labels.
- **facet_**, small multiples that split the plot by a categorical column.
- **theme**, everything that is not data, such as fonts, gridlines, and backgrounds.

Layers stack with the + operator, and each layer can carry its own data argument. That last detail is the workhorse of genomic graphics, because it lets you overlay annotation tracks on top of signal tracks without merging the underlying tables.

The table below maps common genomic questions to the geoms that answer them.

| Figure goal | Core geom(s) | Key aesthetics | Typical genomic use |
|, - |, - |, - |, - |
| Coverage track | geom_area(), geom_line() | x = position, y = depth, fill = sample | WGS, WES, RNA-seq, ChIP-seq coverage |
| Volcano plot | geom_point() | x = log2 fold change, y = -log10(p), color = direction | Differential expression or binding |
| MA plot | geom_point() + geom_hline() | x = mean expression, y = log2 fold change | Normalization quality control |
| Group comparison | geom_boxplot() + geom_jitter() | x = group, y = expression, fill = group | Tumor versus adjacent tissue |
| Signal heatmap | geom_tile() | x = position, y = sample, fill = signal | Enrichment around peaks |
| Sequence logo | ggseqlogo() | sequences or a position weight matrix | [Transcription factor](/knowledge/molecular-biology/transcription-factor) binding sites |
| Pathway map | ggkegg on ggraph | node and edge attributes | KEGG pathway visualization |
| Cohort mutation summary | GenVisR waterfall() | gene, variant class, cohort | Tumor mutation summaries |

## How to Visualize Genomic Data in R with ggplot2, Step by Step

![Flow of the six walkthrough steps: tidy the data, draw the first layer, facet by sample, add gene annotations, polish, then save.](/article-images/illustrations/ggplot2-genomic-data-visualization-six-step-build.webp)

The walkthrough builds one figure in six steps. The data simulates read depth across a 2,000 base pair region for two samples, with an enrichment peak engineered into the second sample. Everything is reproducible because set.seed() fixes the random number generator.

### Step 1: Get the Data into a Tidy Data Frame

Genomic data usually arrives in wide format, one column per sample. ggplot2 works best with tidy data, meaning one row per observation and one column per variable. Convert first, plot second.

```r
library(tidyr)

set.seed(42)

positions <- 1:2000

coverage_wide <- data.frame(
  position = positions,
  Sample_A = rnbinom(length(positions), size = 2, mu = 15),
  Sample_B = rnbinom(length(positions), size = 2, mu = 15)
)

# Engineer a simulated enrichment peak into Sample_B
peak <- coverage_wide$position > 1200 & coverage_wide$position < 1500
coverage_wide$Sample_B[peak] <- coverage_wide$Sample_B[peak] + 90

coverage <- pivot_longer(coverage_wide,
                         cols = -position,
                         names_to = "sample",
                         values_to = "coverage")

head(coverage)
```

The rnbinom() draws come from a negative binomial distribution, the standard model for overdispersed read counts. Here size = 2 sets the dispersion and mu = 15 sets the mean depth. Real coverage depends on your sequencing depth, these numbers exist only so the example runs everywhere.

Your output from head(coverage) will look like this:

```
# A tibble: 6 x 3
  position sample   coverage
     <int> <chr>       <int>
1        1 Sample_A       22
2        1 Sample_B       12
3        2 Sample_A       15
4        2 Sample_B        9
5        3 Sample_A       13
6        3 Sample_B       18
```

Three columns, one row per position per sample. That is the shape ggplot2 wants.

### Step 2: Draw the First Layer

```r
p1 <- ggplot(coverage, aes(x = position, y = coverage)) +
  geom_line(aes(color = sample), linewidth = 0.4)
p1
```

Two thin, jagged lines appear, one per sample, with counts mostly below 50 and a spike in Sample_B between positions 1200 and 1500. The linewidth parameter controls line thickness. In ggplot2 version 3.4.0 and later, linewidth replaced the older size parameter for lines, so older tutorials that use size still run but print a warning.

Assigning the plot to p1 matters. Nothing is drawn until you type the object name, which keeps the figure editable and lets you add layers later without retyping.

### Step 3: Split by Sample with Facets

```r
p2 <- ggplot(coverage, aes(x = position, y = coverage)) +
  geom_area(aes(fill = sample), alpha = 0.5, position = "identity") +
  facet_wrap(~sample, ncol = 1)
p2
```

Three things changed. geom_area() fills the region under each line, alpha = 0.5 makes the fill half transparent, and position = "identity" tells ggplot2 to draw both samples at their true values instead of stacking them. facet_wrap(~sample, ncol = 1) splits the single panel into two stacked panels, one per sample. Small multiples like this are the cleanest way to compare coverage across many samples, because shared axes keep the scales honest.

### Step 4: Layer Genomic Annotations

```r
genes <- data.frame(
  gene  = c("ORF1", "ORF2", "ORF3"),
  start = c(200, 900, 1300),
  end   = c(600, 1200, 1800)
)

p3 <- p2 +
  geom_rect(data = genes,
            aes(xmin = start, xmax = end, ymin = 0, ymax = Inf),
            fill = "grey85", inherit.aes = FALSE) +
  geom_text(data = genes,
            aes(x = (start + end) / 2, y = Inf, label = gene),
            vjust = 1.5, inherit.aes = FALSE)
p3
```

Two annotation layers now sit on top of the coverage. geom_rect() draws grey bands over each open reading frame and geom_text() writes the gene names at the top of each panel. Both layers set inherit.aes = FALSE, which stops them from demanding the x and y mappings defined in the base call, and both supply their own data argument.

Notice what happens with faceting. The genes table has no sample column, so ggplot2 draws the same annotation in every panel, which is exactly what you want for a shared annotation track. If an annotation differs per sample, add the sample column to the annotation table and it will filter into the matching panels automatically.

The expected result is two stacked panels, each with pale grey bands spanning positions 200 to 600, 900 to 1200, and 1300 to 1800, with ORF1, ORF2, and ORF3 printed along the top edge. The Sample_B panel shows the simulated peak rising inside ORF3.

### Step 5: Polish Scales, Labels, and Themes

```r
p4 <- p3 +
  scale_fill_manual(values = c(Sample_A = "#1B9E77", Sample_B = "#D95F02")) +
  labs(title = "Simulated coverage across a 2 kb region",
       x = "Position (bp)", y = "Read depth", fill = NULL) +
  theme_bw(base_size = 12) +
  theme(strip.background = element_rect(fill = "grey92"),
        legend.position = "none")
p4
```

scale_fill_manual() fixes the fill colors with hex codes taken from the Dark2 palette in RColorBrewer, which stays distinguishable under most forms of color vision deficiency. labs() sets the title and axis labels and removes the fill legend title. theme_bw() swaps the default grey panel background for a white one with light gridlines, and base_size = 12 scales all text together. The final theme() call recolors the facet strip and drops the legend, since each facet is already labeled.

Small theme details decide whether a figure looks assembled or authored. Set them once, in one place, and reuse them across the project.

### Step 6: Save the Figure

```r
ggsave("coverage_track.png", p4, width = 6, height = 4, dpi = 300)
ggsave("coverage_track.pdf", p4, width = 6, height = 4)
```

ggsave() writes the last plotted object, or any object you name. Width and height are in inches. The PNG version uses dpi = 300, the resolution most journals ask for in raster images. The PDF version is vector graphics, which stays sharp at any zoom and is the better choice for line art like coverage tracks. Use SVG the same way if your target system prefers it.

### How to Check the Figure Is Right

Before trusting any genomic figure, run three checks. First, compare the axis ranges against the data ranges:

```r
range(coverage$position)
max(coverage$coverage)
```

The x axis should span 1 to 2000 and the y axis should reach the maximum coverage, which lands well above the baseline because of the engineered peak. Second, count the facets. With two samples, facet_wrap() must produce exactly two panels. A missing panel usually means a typo in the column name, which ggplot2 treats as a new group. Third, verify the annotation coordinates against the source table, because a gene drawn at the wrong position looks plausible and is the hardest error to spot. A quick filter confirms the peak sits where you engineered it:

```r
coverage |>
  filter(sample == "Sample_B", position > 1200, position < 1500) |>
  summarise(mean_peak = mean(coverage))
```

The result should be far above the baseline mean of about 15.

### Worked Example: A Volcano Plot from Differential Expression Results

Volcano plots summarize differential expression analysis, with fold change on the x axis and [statistical significance](/blog/guides/statistical-significance) on the y axis. Simulate a results table of the kind produced by DESeq2 or edgeR:

```r
set.seed(7)

de <- data.frame(
  gene   = paste0("Gene_", 1:5000),
  log2FC = rnorm(5000, sd = 1.2),
  padj   = 10^(-runif(5000, min = 0, max = 6))
)

de <- de |>
  mutate(direction = case_when(
    log2FC >= 1  & padj < 0.05 ~ "Up",
    log2FC <= -1 & padj < 0.05 ~ "Down",
    TRUE                       ~ "Not significant"
  ))

table(de$direction)
```

The padj column holds adjusted p-values drawn between 1 and 10 to the power of minus 6. case_when() assigns each gene a direction using the conventional thresholds, a linear fold change of at least 2, which is a log2 fold change of 1, and an adjusted p-value below 0.05. The table() output lands near these counts, with a few hundred genes called Up or Down and several thousand not significant:

```
             Down Not significant              Up
              224            4618             158
```

Now the plot:

```r
volcano <- ggplot(de, aes(x = log2FC, y = -log10(padj), color = direction)) +
  geom_point(size = 1, alpha = 0.5) +
  geom_hline(yintercept = -log10(0.05), linetype = "dashed", color = "grey40") +
  geom_vline(xintercept = c(-1, 1), linetype = "dashed", color = "grey40") +
  scale_color_manual(values = c(Up = "#B2182B", Down = "#2166AC",
                                `Not significant` = "grey70")) +
  labs(x = "log2 fold change", y = "-log10 adjusted p-value",
       color = "Direction") +
  theme_bw()
volcano
```

The y axis takes the negative log10 of the adjusted p-value, so smaller p-values climb higher and the significance threshold at 0.05 becomes a horizontal line at roughly 1.3. Backticks around `Not significant` let the color vector use a name that contains spaces.

Label the strongest hits with ggrepel, which nudges text labels apart so they stop overlapping:

```r
library(ggrepel)

top_up <- de |>
  filter(direction == "Up") |>
  slice_max(order_by = -log10(padj), n = 5)

volcano +
  geom_text_repel(data = top_up, aes(label = gene),
                  size = 3, max.overlaps = 20)
```

The expected figure shows a symmetrical grey cloud in the middle, red points drifting to the upper right, blue points to the upper left, dashed threshold lines, and five labeled red genes at the top.

One caution that saves careers in lab meetings. The thresholds on a volcano plot are display conventions. The statistical test happens in the differential expression model, not in the plot. State the model and the thresholds in your methods section.

### Worked Example: Expression by Group

Grouped expression comparisons are a standard final figure. A published lung adenocarcinoma study, which screened genes in the ERK pathway after insulin receptor silencing, used ggplot2 to visualize significant genes and compare expression patterns between tumor and para cancer tissue [3]. The structure of that figure is a boxplot per gene, split by group. Here is the pattern with simulated counts:

```r
set.seed(11)

expression <- expand.grid(
  gene  = c("IR", "ERK1", "ERK2"),
  group = c("Tumor", "Adjacent"),
  rep   = 1:6
) |>
  mutate(counts = rpois(n(), lambda = 60) +
           ifelse(gene == "IR" & group == "Tumor", 40, 0))

ggplot(expression, aes(x = group, y = counts, fill = group)) +
  geom_boxplot(outlier.shape = NA, alpha = 0.7) +
  geom_jitter(width = 0.15, size = 1, alpha = 0.7) +
  facet_wrap(~gene, scales = "free_y") +
  scale_fill_manual(values = c(Tumor = "#B2182B", Adjacent = "#4393C3")) +
  labs(x = NULL, y = "Expression (simulated counts)") +
  theme_bw() +
  theme(legend.position = "none")
```

expand.grid() builds every combination of gene, group, and replicate, six replicates per group per gene. rpois() draws Poisson counts, and the mutate() call adds 40 to the IR gene in tumors so the figure has a visible signal. scales = "free_y" in facet_wrap() gives each gene panel its own y range, which matters when genes differ by an order of magnitude.

The expected figure shows three panels. In the IR panel, the Tumor box sits clearly above the Adjacent box. The ERK1 and ERK2 panels show overlapping boxes, the visual signature of no difference.

The counts here are simulated for plotting practice. Real count comparisons need a proper count model, such as the negative binomial frameworks in DESeq2 or edgeR, and the plot comes after that test.

### Worked Example: A Sequence Logo

A sequence logo displays the base composition at each position of a motif, with letter height proportional to frequency. In R, the ggseqlogo package has been the most popular sequence logo API since its release [7].

```r
library(ggseqlogo)

set.seed(3)
motifs <- replicate(40, paste(sample(c("A", "C", "G", "T"), 10, replace = TRUE),
                              collapse = ""))

ggseqlogo(motifs) + theme_bw()
```

The replicate() call builds 40 random 10-base sequences. ggseqlogo() accepts either raw sequences like these or a position weight matrix, and it stacks the letters at each position with heights that reflect how often each base occurs there. Because the function returns a ggplot object, adding theme_bw() works exactly as it does everywhere else in this guide, and you can keep adding layers on top.

The same grammar now exists outside R. The plotnine package brings ggplot2-style plotting to Python, and plotnineSeqSuite provides a ggseqlogo-like API for sequence logos, sequence alignment diagrams, and sequence histograms in that style, which lowers the cost for teams that work across both languages [7].

## A Bioconductor Tutorial Path: Packages That Extend ggplot2

![Decision path asking whether the figure needs cohort mutations, coverage tracks or KEGG pathways, otherwise staying with plain ggplot2.](/article-images/illustrations/ggplot2-genomic-data-visualization-package-choice.webp)

CRAN hosts the core grammar. Bioconductor hosts the genomics-specific extensions, as an open source and open development collection of R packages for high-throughput biological analysis [1]. The packages below all speak ggplot2, so your themes, scales, and layering habits transfer directly.

### GenVisR for Cohort-Level Genome Graphics

GenVisR addresses a recurring problem in genomics, summarizing data from large studies at the cohort level. It provides a rapid, easy-to-use suite of publication-quality genomic visualization tools while keeping a high degree of flexibility by building on ggplot2 and Bioconductor, and it is distributed through Bioconductor under a GPLv3 license with support through GitHub and the Bioconductor support website [4].

Its functions cover the standard cohort figures. waterfall() draws cohort-wide mutation summaries, gene by gene with variant classification. genCov() draws gene-centered coverage. TvTi() summarizes the balance of transitions and transversions. lolliplot() places mutations on a protein diagram. Install and attach it like any Bioconductor package:

```r
BiocManager::install("GenVisR")
library(GenVisR)
```

### ggcoverage for NGS Coverage Tracks

ggcoverage visualizes and annotates genome coverage across next-generation sequencing data. It accepts BAM, BigWig, BedGraph, and TSV inputs, provides read normalization, consensus peak generation, and track loading, and offers annotations adapted to whole genome sequencing, whole exome sequencing, RNA-seq, and ChIP-seq. Annotations superimpose with the + operator, plots are publication quality, and everything remains customizable with ggplot2. Protein coverage visualization is supported as well [5].

The design lesson matters more than any single function call. ggcoverage treats annotations as layers in the grammar, the same pattern you built by hand in Step 4 of the walkthrough. The package removes the preprocessing pain while keeping the mental model identical.

### ggkegg for KEGG Pathways

The Kyoto Encyclopedia of Genes and Genomes, KEGG, is a widely used systems biology resource, but flexible visualization of its pathway data has been a gap in existing software. ggkegg integrates KEGG information with ggplot2 and ggraph, enabling enhanced pathway visualization and network analysis, with demonstrated applications in single-cell, bulk transcriptome, and microbiome analyses [6]. The package and its documentation live on GitHub. If your results section needs a pathway figure with per-gene overlay of fold changes, this tool keeps you inside the grammar you already know.

### Cogito for Comparing Annotated Intervals

Studies often combine several experiment types and conditions, and the number of combinations grows quickly. Cogito, the COmpare annotated Genomic Intervals TOol, produces an unbiased, structured overview of complex genomic datasets spanning data types such as RNA-seq and ChIP-seq across conditions, returning key information about genomic and epigenomic intervals in a text-based report alongside customizable output [8]. Use it early, to decide which comparisons deserve a full analysis, rather than as a figure factory.

### Plot the Processed Data, Not the Raw Signal

Every package above assumes processed input. Keep the same discipline in your own pipeline. Bioconductor protocols for microarray analysis run quality control, background correction, normalization, and probe summarization before differential expression and visualization begin [1]. The equivalent for RNA-seq is alignment or pseudoalignment, counting, and normalization before any ggplot2 call. A figure built on unnormalized values answers a question nobody asked.

## ggplot2 Genomic Data Visualization Tips That Save Hours

These habits separate quick drafts from figures you can defend.

1. **Keep the data tidy.** One row per observation, one column per variable. Most ggplot2 frustration is a data shape problem, not a plotting problem.

2. **Control ordering with factors.** ggplot2 orders character columns alphabetically. Set the order you mean:

```r
coverage$sample <- factor(coverage$sample, levels = c("Sample_B", "Sample_A"))
```

3. **Use log scales for counts and fold changes.** scale_y_log10() handles count data that spans orders of magnitude, and it pairs naturally with fold change axes.

4. **Zoom with coord_cartesian(), not with limits.** coord_cartesian(ylim = c(0, 200)) magnifies the view while keeping every point in the calculations. Setting limits inside a scale silently removes data outside the range.

5. **Facet instead of overloading one panel.** facet_wrap(~sample, scales = "free_y") beats a spaghetti plot of ten samples every time.

6. **Choose colorblind-safe palettes.** The Dark2 palette from RColorBrewer and the viridis scales, available as scale_colour_viridis_d(), stay readable under the most common forms of color vision deficiency.

7. **Repel, do not overlap, labels.** ggrepel moves text labels apart automatically. Hand-tuned geom_text() positions break the moment the data changes.

8. **Set a project theme once.** theme_set(theme_bw(base_size = 12)) at the top of a script gives every figure in the project the same typography.

9. **Export vector graphics for manuscripts.** ggsave("figure.pdf") or an SVG keeps lines sharp at any size. Use PNG at dpi = 300 when the destination requires raster images.

10. **Bin before plotting large regions.** Whole-chromosome coverage means millions of points. Summarize into fixed windows first:

```r
binned <- coverage |>
  mutate(bin = floor(position / 50) * 50) |>
  group_by(bin, sample) |>
  summarise(mean_coverage = mean(coverage), .groups = "drop")
```

11. **Record the environment.** Run sessionInfo() at the end of the analysis and save the output with the script. Package versions change behavior, and reviewers sometimes ask.

## Common Mistakes and Limitations

1. **Forgetting the + operator at line ends.** The most frequent ggplot2 error for beginners. If the + sits at the start of the next line instead of the end of the previous one, R sees two separate expressions and reports something like "Error: unexpected symbol in ...". End every layer line with +.

2. **Putting fixed values inside aes().** Aesthetics map data columns to visuals. A constant belongs outside aes(). Putting a fixed-length vector inside aes() triggers "Error: Aesthetics must be either length 1 or the same as the data (N): fill", where N is your row count.

3. **Mixing discrete and continuous scales.** Passing a character column to a numeric scale, or the reverse, raises "Error: Discrete value supplied to continuous scale". Check column types with str(data) before plotting.

4. **Accepting alphabetical axis order.** Chromosomes sort as chr1, chr10, chr11, chr2 unless you convert the column to a factor with explicit levels. The same applies to sample names and treatment stages.

5. **Plotting raw counts across samples.** [Sequencing depth](/knowledge/diagnostics/molecular/how-to-calculate-sequencing-depth-and-coverage-for-your-ngs-run) differs between libraries, so raw counts compare library size as much as biology. Normalize first. The microarray tradition in Bioconductor made background correction, normalization, and summarization mandatory steps before visualization for good reason [1].

6. **Overplotting.** Ten million points produce a black rectangle. Reduce with alpha transparency, smaller point sizes, window binning, or facetting.

7. **Treating the figure as the analysis.** A volcano plot displays results from a statistical model. Drawing threshold lines does not perform the test, and a beautiful plot of unnormalized data is a well-formatted wrong answer. Run the model, then visualize its output.

8. **Not recording versions.** A figure that cannot be regenerated is a liability. Save the script, the seed, and the sessionInfo() output together.

9. **Reading more from a figure than it holds.** Plots summarize populations and patterns. They do not diagnose anything. Individual biological interpretation, and any clinical decision, belongs with a qualified clinician who can weigh the full case.

10. **Practicing only on simulated data.** The examples here exist so the code runs everywhere. Real genomic data brings missing values, uneven depths, and batch effects, so budget time for cleaning before plotting.

## Frequently Asked Questions

### Do I need to learn base R plotting before ggplot2?

No. ggplot2 stands on its own, and most modern R for bioinformatics teaching starts there. Base graphics still matter for reading older scripts, but nothing in a genomic workflow requires them first.

### Which packages do I need for a basic genomic figure?

ggplot2 and dplyr cover the core. Add tidyr for reshaping, ggrepel for labels, and ggseqlogo for motifs. The Bioconductor packages GenVisR, ggcoverage, and ggkegg cover cohort summaries, coverage tracks, and pathway maps when you need them.

### How do I plot a whole chromosome without slowing R to a crawl?

Summarize into fixed windows, for example 1 kb bins, and plot the per-window mean or maximum instead of every base. A region with tens of millions of aligned reads becomes a few thousand points, which ggplot2 handles comfortably.

### Can ggplot2 figures go straight into a manuscript?

Yes. Export with ggsave() as PDF or SVG for vector graphics, or as PNG at 300 dpi for raster, and set width and height in inches to match the target column. Journals accept ggplot2 output routinely.

### When should I use a specialized package instead of plain ggplot2?

Use plain ggplot2 while learning and for custom figures. Switch to GenVisR, ggcoverage, or ggkegg when the figure needs domain-specific annotations, such as cohort mutation summaries, coverage with gene models, or KEGG pathways, that would take days to assemble by hand.

### Why does R say "could not find function ggplot"?

The ggplot2 package is not installed or not attached. Run install.packages("ggplot2") once, then library(ggplot2) at the top of every script that plots.

### Why do my samples appear in alphabetical order on the axis?

ggplot2 sorts character columns alphabetically. Convert the column to a factor with explicit levels, for example factor(sample, levels = c("Tumor", "Adjacent")), and the axis follows your order.

### Can I make the same graphics in Python?

Yes. The plotnine library implements ggplot2-style plotting in Python, and plotnineSeqSuite adds sequence logo, alignment, and sequence histogram graphics in the same style for teams working across both languages.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "Do I need to learn base R plotting before ggplot2?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "No. ggplot2 stands on its own, and most modern R for bioinformatics teaching starts there. Base graphics still matter for reading older scripts, but nothing in a genomic workflow requires them first."
      }
    },
    {
      "@type": "Question",
      "name": "Which packages do I need for a basic genomic figure?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "ggplot2 and dplyr cover the core. Add tidyr for reshaping, ggrepel for labels, and ggseqlogo for motifs. The Bioconductor packages GenVisR, ggcoverage, and ggkegg cover cohort summaries, coverage tracks, and pathway maps when you need them."
      }
    },
    {
      "@type": "Question",
      "name": "How do I plot a whole chromosome without slowing R to a crawl?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Summarize into fixed windows, for example 1 kb bins, and plot the per-window mean or maximum instead of every base. A region with tens of millions of aligned reads becomes a few thousand points, which ggplot2 handles comfortably."
      }
    },
    {
      "@type": "Question",
      "name": "Can ggplot2 figures go straight into a manuscript?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Yes. Export with ggsave() as PDF or SVG for vector graphics, or as PNG at 300 dpi for raster, and set width and height in inches to match the target column. Journals accept ggplot2 output routinely."
      }
    },
    {
      "@type": "Question",
      "name": "When should I use a specialized package instead of plain ggplot2?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Use plain ggplot2 while learning and for custom figures. Switch to GenVisR, ggcoverage, or ggkegg when the figure needs domain-specific annotations, such as cohort mutation summaries, coverage with gene models, or KEGG pathways, that would take days to assemble by hand."
      }
    },
    {
      "@type": "Question",
      "name": "Why does R say \"could not find function ggplot\"?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "The ggplot2 package is not installed or not attached. Run install.packages(\"ggplot2\") once, then library(ggplot2) at the top of every script that plots."
      }
    },
    {
      "@type": "Question",
      "name": "Why do my samples appear in alphabetical order on the axis?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "ggplot2 sorts character columns alphabetically. Convert the column to a factor with explicit levels, for example factor(sample, levels = c(\"Tumor\", \"Adjacent\")), and the axis follows your order."
      }
    },
    {
      "@type": "Question",
      "name": "Can I make the same graphics in Python?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Yes. The plotnine library implements ggplot2-style plotting in Python, and plotnineSeqSuite adds sequence logo, alignment, and sequence histogram graphics in the same style for teams working across both languages."
      }
    }
  ]
}
</script>

## Sources

1. [The use of open source bioinformatics tools to dissect transcriptomic data.](https://pubmed.ncbi.nlm.nih.gov/22183662/)
2. [Metagenomics Data Visualization Using R.](https://pubmed.ncbi.nlm.nih.gov/37258873/)
3. [Gene-knockdown Methods for Silencing Nuclear-localized Insulin Receptors in Lung Adenocarcinoma Cells: A Bioinformatics Approach.](https://pubmed.ncbi.nlm.nih.gov/39911279/)
4. [GenVisR: Genomic Visualizations in R.](https://pubmed.ncbi.nlm.nih.gov/27288499/)
5. [ggcoverage: an R package to visualize and annotate genome coverage for various NGS data.](https://pubmed.ncbi.nlm.nih.gov/37559015/)
6. [ggkegg: analysis and visualization of KEGG data utilizing the grammar of graphics.](https://pubmed.ncbi.nlm.nih.gov/37846038/)
7. [plotnineSeqSuite: a Python package for visualizing sequence data using ggplot2 style.](https://pubmed.ncbi.nlm.nih.gov/37789265/)
8. [Cogito: automated and generic comparison of annotated genomic intervals.](https://pubmed.ncbi.nlm.nih.gov/35927614/)