R Cheat Sheet
In this R cheat sheet, you'll find every function you reach for in a real analysis session, from c() to ggsave(). Searchable, filterable by dialect, copy ready.
R Basics: Assignment, Operators, Help and Packages
Start here. Assignment, operators, help and workspace commands work in every R session, no packages needed.
R Data Types: Vectors, Coercion, Missing Values and Factors
R has no scalars. A single number is a length one vector, which is why almost everything in R is vectorised by default.
R Data Structures: Matrices, Lists, Data Frames and Tibbles
Matrices hold one type, lists hold anything, data frames are lists of equal length columns. Almost all analysis happens in the third one.
Importing and Exporting Data in R
readr and data.table readers are faster than the base equivalents and never silently convert text to factors.
Data Wrangling With dplyr: Verbs, Joins and Column Operations
Six verbs cover most of the work: filter rows, select columns, mutate values, arrange order, summarise groups, group_by splits.
Reshaping Data With tidyr: Pivoting, Splitting and Nesting
Tidy data means one variable per column, one observation per row. pivot_longer and pivot_wider move between that shape and reporting layouts.
Strings, Regular Expressions and Dates in R
stringr functions all start with str_ and take the string first, which makes them pipe friendly. Base equivalents are listed next to them.
R Functions, Control Flow, Iteration and Object Systems
Reach for a vectorised function first, an apply or map second, and an explicit for loop only when order of execution matters.
Plotting With ggplot2 and Base R Graphics
A plot is data plus aesthetic mappings plus at least one geom. Everything else is a layer added with a plus sign.
Statistics and Modelling in R
Formula notation reads as response on the left, predictors on the right. The same y ~ x syntax drives t tests, ANOVA, linear and generalised models.
data.table Syntax Reference
Read DT[i, j, by] as take rows i, compute j, grouped by. Updates with := change the table in place, without copying it.
R Workflow: Projects, Reporting, Testing and Performance
Reproducibility beats cleverness. Projects, pinned package versions and scripted output make analyses that still run next year.
Time Series, Spatial Data and Shiny Apps in R
Three specialist areas with their own object types. Each one has a dominant package worth learning before anything else.
Common R Gotchas and Silent Bugs
The bugs that cost the most time are silent ones. Every entry here returns a plausible wrong answer rather than an error.
Saved R Snippets
What R Is and Who Uses It
R is a language built for data. It was created in 1993 by Ross Ihaka and Robert Gentleman at the University of Auckland as a free implementation of the earlier S language, and it is now maintained by the R Core Team under the GNU General Public License.
Where general purpose languages treat statistics as a library, R treats it as the core. Vectors, missing values, factors, data frames and model formulas are part of the language itself, not bolted on afterwards.
The people who reach for it tend to be statisticians, epidemiologists, econometricians, bioinformaticians, survey researchers, quantitative analysts and data journalists. If you also write scripts in other languages, our Python cheat sheet covers the closest alternative for the same kind of work. If your work ends in a model, a chart or a report rather than a production service, R usually gets you there with less code. So, an R cheat sheet was much needed.
The package ecosystem is the other half of the story. CRAN hosts more than twenty thousand packages, Bioconductor adds thousands more for genomics, and the tidyverse provides a consistent interface for the everyday work of loading, cleaning, reshaping and plotting data.
Installing R and RStudio
Two separate installs, in this order.
First, download R itself from CRAN. Pick the binary for your operating system and accept the defaults. This gives you the language and the plain console.
Second, install an editor. RStudio Desktop from Posit is the standard choice, with a script pane, console, environment browser, plot viewer and package manager in one window. Positron and VS Code with the R extension are reasonable alternatives.
Check the install by opening the console and running a version check.
R.version.string
#> [1] "R version 4.x.y (yyyy-mm-dd)"
Then install the packages you will use most.
install.packages(c("tidyverse", "data.table", "here", "janitor"))
One habit worth adopting on day one: turn off workspace saving. In RStudio, go to Tools, then Global Options, and uncheck restore .RData at startup. Every script should be able to produce its results from a clean session.
R Syntax Basics
R is case sensitive, indexes from 1 rather than 0, and uses an arrow for assignment.
Assignment and objects
x <- 5 # the conventional form
y = 5 # legal, but keep = for function arguments
5 -> z # right assignment, occasionally useful
Everything in R is an object, and every object has a class. Use class() for the high level type and str() for a compact view of the internals. str() is probably the single most useful function for exploring an unfamiliar object.
Vectors and vectorisation
R has no scalar type. A single number is a vector of length one, which is why arithmetic works on whole columns without a loop.
prices <- c(10, 20, 30)
prices * 1.19
#> [1] 11.9 23.8 35.7
When two vectors have different lengths, the shorter one is recycled. That behaviour is convenient and occasionally dangerous, so pay attention when a warning about object lengths appears.
Missing values
Missing data is a first class citizen. NA propagates through calculations on purpose, which is why mean(x) returns NA when any value is missing. Add na.rm = TRUE once you have decided that dropping those rows is correct.
Note the distinction: NA means a value exists but is unknown, NULL means there is no value at all, NaN is an undefined numeric result, and Inf is the result of dividing by zero.
R Data Structures
Four containers cover almost everything.
| Structure | Dimensions | Contents | Typical use |
|---|---|---|---|
| vector | 1 | One type only | A single column or variable |
| matrix | 2 | One type only | Linear algebra, correlation grids |
| list | 1 | Anything, including other lists | Model objects, JSON, mixed results |
| data.frame | 2 | One type per column | Almost all analysis work |
A data frame is really a list of equal length vectors with a shared row index. That is why df$column returns a vector and why every column can hold a different type while each column stays internally consistent.
Tibbles versus data frames
A tibble is a data frame with the sharp edges filed off. It prints ten rows instead of flooding the console, never converts strings to factors, never does partial column name matching, and always returns a tibble when you subset it rather than silently dropping to a vector.
Anything that accepts a data frame accepts a tibble, so the switch costs nothing.
Factors
Factors store categorical data as integer codes with a lookup table of labels. Models need them, and so do ordered axes in plots.
The classic trap is converting a factor of numbers straight to numeric, which returns the internal codes rather than the labels. Always go through character first.
f <- factor(c("10", "20", "30"))
as.numeric(f) # 1 2 3, the codes
as.numeric(as.character(f)) # 10 20 30, correct
Importing and Exporting Data
Base R can read most formats, but the modern readers are faster and make fewer assumptions.
- CSV and delimited text:
read.csv()in base R,readr::read_csv()for speed and a tibble,data.table::fread()when the file is large enough that you notice the wait. - Excel:
readxl::read_excel()for reading,writexl::write_xlsx()for writing. Neither needs Java. - Statistical formats: the haven package handles SPSS, Stata and SAS files and keeps the variable labels.
- Databases: DBI plus a driver package gives you a connection, and dbplyr lets you write dplyr code that is translated into SQL and executed on the server. Keep our SQL cheat sheet open when you need to read the query it generates.
- R native:
saveRDS()for a single object,save()for several. RDS is the better default because you choose the name when you read it back. - JSON and other exchange formats: jsonlite handles the parsing, and for one off conversions outside R our CSV to JSON converter and JSON to YAML converter are quicker than writing a script.
Two habits prevent most import pain. Set col_types explicitly once you know the schema, so a column of postal codes is never guessed as a number. And use project relative paths through here::here() so the script runs on a colleague's machine without editing.
Data Wrangling With dplyr
The dplyr grammar is small on purpose. Six verbs handle the bulk of transformation work, and they all take a data frame first, which makes them pipe friendly.
| Verb | What it does | Base R equivalent |
|---|---|---|
| filter() | Keeps rows matching a condition | df[df$x > 5, ] |
| select() | Keeps or drops columns | df[, c("a", "b")] |
| mutate() | Adds or changes columns | df$new <- ... |
| arrange() | Sorts rows | df[order(df$x), ] |
| summarise() | Collapses to one row per group | aggregate() |
| group_by() | Splits the frame for the verb that follows | tapply(), split() |
A typical pipeline reads top to bottom like a sentence.
library(dplyr)
report <- sales |>
filter(year == 2024, !is.na(amount)) |>
mutate(net = amount - discount) |>
summarise(
revenue = sum(net),
orders = n(),
.by = region
) |>
arrange(desc(revenue))
Two modern additions are worth knowing. The .by argument groups for a single operation and releases the grouping afterwards, which avoids the classic bug of a grouped data frame quietly following you through the rest of the script. And across() applies one function to many columns without repeating yourself.
Joins
Joins follow SQL naming. left_join() keeps every row on the left, inner_join() keeps only matches, full_join() keeps everything, and the filtering joins semi_join() and anti_join() use the second table as a lookup without adding any of its columns.
Check row counts before and after every join. An unexpected increase means the key is not unique on one side, which is the most common silent error in data work.
Reshaping With tidyr
Tidy data has one variable per column and one observation per row. Most spreadsheets arrive in a different shape, with months or years spread across the header row.
pivot_longer() gathers those columns into two: one holding the former column names, one holding the values.
long <- wide |>
pivot_longer(
cols = q1:q4,
names_to = "quarter",
values_to = "revenue"
)
pivot_wider() does the reverse, which is what you usually want at the very end when producing a table for a report.
Text columns usually need cleaning at the same time. R uses POSIX and Perl compatible expressions, so the patterns in our regex cheat sheet transfer directly, with the caveat that every backslash has to be doubled inside an R string.
The rest of tidyr fills gaps: drop_na() and replace_na() for missing values, fill() for carrying values down a column, complete() for making implicit missing combinations explicit, and separate_wider_delim() for splitting a compound column into parts.
Plotting With ggplot2
ggplot2 implements the grammar of graphics. A plot is data, a mapping from variables to visual properties, and one or more geometric layers. Everything else is optional refinement.
library(ggplot2)
ggplot(mtcars, aes(x = wt, y = mpg, colour = factor(cyl))) +
geom_point(size = 2.5, alpha = 0.85) +
geom_smooth(method = "lm", se = FALSE) +
scale_colour_manual(values = c("#1f6feb", "#0d9488", "#b45c09")) +
labs(
title = "Fuel economy falls as weight rises",
x = "Weight (1000 lbs)",
y = "Miles per gallon",
colour = "Cylinders"
) +
theme_minimal(base_size = 13)
The distinction that trips people up early: anything inside aes() is mapped from a column, anything outside it is a fixed value. Writing colour = "blue" inside aes() creates a single category literally named blue rather than a blue plot.
facet_wrap() is the highest value function in the package. One line turns a crowded chart into a small multiple grid, and comparisons that were impossible become obvious.
Save with ggsave() rather than the export button, so the dimensions are recorded in the script and the figure regenerates identically next time.
Statistics and Modelling
This is where R still has no real competition. Formula notation is consistent across the entire modelling ecosystem: the response goes on the left of the tilde, predictors on the right.
model <- lm(mpg ~ wt + hp, data = mtcars)
summary(model)
confint(model)
predict(model, newdata = new_cars, interval = "prediction")
The same syntax drives t.test(score ~ group), aov(), glm() for logistic and Poisson regression, and mixed models through lme4.
Model objects are lists, so you can pull out coefficients, residuals and fitted values directly. When you need results as a data frame instead of printed output, the broom package converts any model into a tidy table with tidy(), glance() and augment(). That is what makes it practical to fit hundreds of models across groups and compare them in a single table.
data.table for Large Datasets
data.table is the other major dialect. It uses a single bracket form, DT[i, j, by], which reads as take these rows, compute this, grouped by that.
library(data.table)
DT <- fread("transactions.csv")
DT[amount > 0, .(total = sum(amount), n = .N), by = .(region, month)]
Its defining feature is modification by reference. The := operator adds or updates a column without copying the table, which is why it stays fast on data measured in gigabytes where a copy would exhaust memory.
The trade off is readability. dplyr code is easier to hand to a colleague, data.table code is faster and more compact. Many teams use dplyr for exploration and data.table where speed matters, and the two coexist in the same script without conflict.
Common R Mistakes
- Growing objects inside loops. Appending to a vector reallocates memory every iteration. Preallocate with
vector("numeric", n)or use an apply function. - Forgetting na.rm. A single missing value turns any summary into
NA. Decide deliberately whether to exclude those rows. - Comparing with == when you mean %in%. The equality operator recycles and gives the wrong answer against a multi value set.
- Leaving a data frame grouped. Every verb after
group_by()stays grouped until you callungroup(). Use.bywhere you can. - Converting factors to numbers directly. Go through
as.character()first or you get the level codes. - Floating point equality.
0.1 + 0.2 == 0.3is FALSE. Useall.equal()orabs(a - b) < 1e-8. - setwd() at the top of a script. It breaks on every other machine. Use an RStudio project and
here::here(). - rm(list = ls()) as a reset. It leaves attached packages and options behind. Restart the session instead.
Making an Analysis Reproducible
A script that only runs on your laptop is a draft, not a deliverable. Four habits cover most of the gap.
- Use a project, not a working directory. An .Rproj file plus
here::here()means paths resolve the same way for everyone. - Pin your packages.
renv::snapshot()writes a lockfile recording every version, andrenv::restore()rebuilds that library elsewhere. - Version the code. Analyses change constantly and diffs are the only reliable record of why. Our Git cheat sheet covers the commands worth memorising.
- Freeze the environment when it matters. For long lived pipelines, a container pins the R version and system libraries too. The rocker images are the standard base, and our Docker cheat sheet handles the rest.
Reports belong in the same repository as the code that produces them. R Markdown and Quarto both use Markdown for the prose around your chunks, so the formatting syntax is the same one you already use in README files.
Where to Go Next
R for Data Science is the standard free introduction to the tidyverse workflow. Advanced R covers environments, object systems and metaprogramming once the basics feel comfortable.
For reference while you work, the official R manuals remain the authority on language semantics, and Posit publishes printable one page guides for individual packages.
The fastest way to improve, though, is to take a dataset you actually care about and push it all the way through: import, clean, reshape, plot, model, report. Every function on this page exists because someone hit that exact problem first.