R and RStudio setup
Lesson 1 — R Programming
Learning objectives
- Install R and RStudio and understand why they are separate things
- Navigate the RStudio panes and configure the options that matter
- Use RStudio Projects instead of
setwd() - Install packages from CRAN and understand where they live
- Create a reproducible project library with
renv - Know which files belong in a project and which never should
R and RStudio are not the same thing
R is the language and the engine. RStudio is an IDE that talks to that engine. You install R first; RStudio finds it. If R is missing or broken, RStudio opens but nothing runs.
The distinction matters when things go wrong. “RStudio won’t start” and “R can’t find a package” are different problems with different fixes, and on a locked-down corporate machine you will hit both.
Install order
- R from CRAN — use the current release for training, unless a validated project requires an older one
- RStudio Desktop from posit.co
- On Windows, Rtools (matching your R version) — required to compile packages from source; current R 4.6.x uses Rtools45
- On macOS, the Xcode command line tools:
xcode-select --install
Check what you have from the R console:
R.version.string
#> [1] "R version 4.6.1 (2026-06-24)"
# Where R keeps things
.libPaths()
#> [1] "C:/Users/you/AppData/Local/R/win-library/4.6"
#> [2] "C:/Program Files/R/R-4.6.1/library"
# Full environment report — paste this into support tickets
sessionInfo()Studies get locked to an R version for validation reasons. On Windows, RStudio lets you switch with Tools → Global Options → General → R version. On macOS/Linux, use rig to install and switch between versions cleanly.
The four panes
| Pane | What it is for | Warning |
|---|---|---|
| Source (top-left) | Your scripts. This is the artefact. | Anything not saved here does not exist |
| Console (bottom-left) | The live R session | Never do real work only here |
| Environment / History (top-right) | Objects currently in memory | A crowded environment usually means a script that only runs once |
| Files / Plots / Packages / Help (bottom-right) | Everything else | Help is better than a web search for base R |
The single most useful shortcut: Ctrl/Cmd + Enter sends the current line or selection from Source to Console. Learn it before anything else.
Others worth memorising:
| Shortcut | Action |
|---|---|
| Ctrl/Cmd + Shift + M | Insert %>% |
| Alt + - | Insert <- |
| Ctrl/Cmd + Shift + F10 | Restart R session |
| Ctrl/Cmd + Shift + P | Re-run previous chunk |
| F2 | Jump to function definition |
| Ctrl/Cmd + . | Fuzzy-find any file or function |
Global options you should change immediately
Go to Tools → Global Options → General and:
- Uncheck “Restore .RData into workspace at startup”
- Set “Save workspace to .RData on exit” to Never
This is not a style preference. A restored workspace means your script appears to work because an object from three days ago is still in memory. You will not find out until someone else runs it. Restarting R often — and having it come back empty — is the cheapest reproducibility check available.
Under Code → Editing, also turn on:
- Insert spaces for tab, width 2
- Auto-detect indentation
- Strip trailing horizontal whitespace when saving
Under Code → Display, turn on “Show margin” at column 80.
Projects, not setwd()
A script that begins
setwd("C:/Users/rgaduputi/Documents/studies/ABC-101/analysis")works on exactly one machine. An RStudio Project fixes this. A project is a directory containing an .Rproj file; opening it sets the working directory to that folder and starts a fresh R session scoped to it.
Create one with File → New Project → New Directory → New Project, or from code:
usethis::create_project("~/studies/abc101")Inside a project, refer to files relative to the project root using the here package, which works the same whether the code is run from the console, a script, a Quarto document in a subfolder, or a scheduled job:
library(here)
here()
#> [1] "/Users/you/studies/abc101"
readr::read_csv(here("data", "raw", "dm.csv"))A layout that scales:
abc101/
├── abc101.Rproj
├── README.md
├── renv.lock
├── data/
│ ├── raw/ # never modified, never written to
│ └── derived/ # everything here is reproducible from raw/
├── R/ # functions only, no side effects
├── scripts/ # numbered, run in order
│ ├── 01-import.R
│ ├── 02-derive.R
│ └── 03-report.R
├── outputs/
│ └── figures/
└── tests/
The rule that makes this work: data/raw/ is read-only, and everything else can be deleted and regenerated. If that is not true, you have hidden state.
Installing packages
install.packages("dplyr") # from CRAN
install.packages(c("dplyr", "tidyr", "haven"))
library(dplyr) # attach for this session
dplyr::filter(df, x > 1) # or call directlyinstall.packages() is done once per machine. library() is done once per session, at the top of the script. A common beginner mistake is putting install.packages() inside a script — it re-downloads the package every run and will fail on a machine without internet access.
For a larger setup, pak gives clearer dependency resolution and handles CRAN, GitHub and local packages through one interface:
install.packages("pak")
pak::pkg_install(c("dplyr", "tidyr", "haven", "renv"))To install from GitHub (common for pharmaverse packages before a CRAN release):
# install.packages("pak")
pak::pak("pharmaverse/admiral")
pak::pak("pharmaverse/admiral@v1.1.1") # pin to a tagIf install.packages() hangs or fails with an SSL error, you probably need proxy settings. Ask IT for the proxy URL and put it in ~/.Renviron:
http_proxy=http://proxy.company.com:8080
https_proxy=http://proxy.company.com:8080
Many companies also run an internal CRAN mirror (Posit Package Manager). Point at it in ~/.Rprofile:
options(repos = c(CRAN = "https://packagemanager.company.com/cran/latest"))Reproducible environments with renv
.libPaths() is shared across all your projects. That means upgrading dplyr for a new project silently changes the behaviour of an old one. In a regulated setting that is unacceptable; in any setting it is unpleasant.
renv gives each project its own library and a lockfile recording exact versions.
# Once, in the project
renv::init()
# Work normally
install.packages("admiral")
library(admiral)
# Record the current state
renv::snapshot()renv::snapshot() writes renv.lock, a JSON file listing every package and its version and source. Commit it. A colleague then runs:
renv::restore()and gets a byte-identical library.
Useful commands:
renv::status() # what differs between library and lockfile
renv::snapshot() # library -> lockfile
renv::restore() # lockfile -> library
renv::update() # deliberately move versions forward
renv::deactivate() # turn it off for this projectrenv versus Docker
renv pins R packages. It does not pin the R version, the operating system, or system libraries. The lockfile records the R version used, but renv::restore() will not install that R version for you. For a submission-grade environment you need renv and a container image (or a validated environment provided by your organisation). Lesson 12 of the Clinical Programming course covers this properly.
.Rprofile and .Renviron
Two startup files, frequently confused:
| File | Contains | Example |
|---|---|---|
.Rprofile |
R code run at startup | options(stringsAsFactors = FALSE) |
.Renviron |
Environment variables, KEY=value, no R code |
SAS_DATA_PATH=/mnt/studies |
Both can be project-level (in the project root) or user-level (in ~). Project overrides user.
usethis::edit_r_profile() # user-level
usethis::edit_r_profile("project")
usethis::edit_r_environ()Keep .Rprofile minimal. Anything that changes how code behaves — options that affect results, silently attached packages — makes your scripts non-portable in a way that is very hard to debug. Secrets go in .Renviron, which is never committed:
# .Renviron
DB_PASSWORD=hunter2Sys.getenv("DB_PASSWORD")Getting help
?mean # help page for a function
??"linear model" # search all installed help
example(mean) # run the examples
vignette("dplyr") # long-form guide
args(mean) # just the arguments
mean # print the sourceWhen you are stuck, produce a reprex — a minimal, self-contained example. Copy the code to your clipboard and run:
reprex::reprex()It runs the code in a clean session and puts formatted, ready-to-paste output back on the clipboard. Half the time, building the reprex reveals the bug.
Common setup mistakes
| Mistake | Symptom | Fix |
|---|---|---|
Restoring .RData at startup |
Script “works” only for you | Turn it off; restart R often |
setwd() at the top of scripts |
Fails on every other machine | Use a Project and here() |
install.packages() inside a script |
Slow, fails offline | Move to a setup script or renv |
| Shared library across projects | Upgrading breaks an old study | renv::init() per project |
| Spaces or accents in the project path | Cryptic build failures on Windows | Keep paths ASCII, no spaces |
| Working in the console only | Nothing is reproducible | Write scripts; console is for experiments |
Exercise 1.1 — Build a project skeleton
Create a new RStudio Project called training-sandbox. Inside it, create the directory structure data/raw, data/derived, R, scripts, outputs. Add a README.md describing what the project is for. Then write scripts/01-check.R that prints the R version, the project root, and the contents of data/raw.
Show solution
# From the console, once
usethis::create_project("~/training-sandbox")
# Then, inside the new project
dir.create("data/raw", recursive = TRUE)
dir.create("data/derived", recursive = TRUE)
dir.create("R")
dir.create("scripts")
dir.create("outputs/figures", recursive = TRUE)
writeLines(
c("# training-sandbox",
"",
"Scratch project used while working through the R Programming course."),
"README.md"
)scripts/01-check.R:
library(here)
cat("R version: ", R.version.string, "\n")
cat("Project root:", here(), "\n")
cat("Raw data files:\n")
print(list.files(here("data", "raw")))Exercise 1.2 — Lock the environment
Initialise renv in your sandbox project, install dplyr and here, and take a snapshot. Open renv.lock and find the recorded version of dplyr. Then work out what a colleague would need to run to reproduce your library.
Show solution
renv::init()
install.packages(c("dplyr", "here"))
renv::snapshot()renv.lock is JSON; the relevant fragment looks like:
"dplyr": {
"Package": "dplyr",
"Version": "1.1.4",
"Source": "Repository",
"Repository": "CRAN"
}A colleague clones the project, opens it (which triggers renv to bootstrap itself), and runs:
renv::restore()You can also read the version programmatically:
jsonlite::fromJSON("renv.lock")$Packages$dplyr$Version
#> [1] "1.1.4"Exercise 1.3 — Diagnose a broken setup
A colleague reports: “I ran your script and got Error in library(admiral) : there is no package called 'admiral', but it works fine for you.” List three plausible causes and the command you would ask them to run for each.
Show solution
- They never installed it. Ask for
rownames(installed.packages())or simply"admiral" %in% rownames(installed.packages()). - It is installed in a library R is not looking at — common when IT migrates profiles or when R was upgraded (libraries are versioned by minor release). Ask for
.libPaths()and compare with yours. - The project uses
renvand they have not restored it. Ask forrenv::status().
sessionInfo() combined with the install log distinguishes this. In all cases, sessionInfo() from both machines is the fastest first request.
Recap
- R is the engine, RStudio is the editor; install R first
- Turn off
.RDatarestore — it is the main source of “works on my machine” - Use Projects and
here(); neversetwd() install.packages()once per machine,library()once per sessionrenv::init()/snapshot()/restore()makes a project’s library reproducible.Rprofileholds R code,.Renvironholds secrets and paths; neither should change results
Next: Objects and data types — what an R object actually is, and why c(1, "a") gives you a character vector.