R and RStudio setup

Lesson 1 — R Programming

Lesson 1 of 12 Beginner ~60 min

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

  1. R from CRAN — use the current release for training, unless a validated project requires an older one
  2. RStudio Desktop from posit.co
  3. On Windows, Rtools (matching your R version) — required to compile packages from source; current R 4.6.x uses Rtools45
  4. 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()
TipMultiple R versions

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 directly

install.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 tag
WarningCorporate proxies

If 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 project
Noterenv 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=hunter2
Sys.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 source

When 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
  1. They never installed it. Ask for rownames(installed.packages()) or simply "admiral" %in% rownames(installed.packages()).
  2. 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.
  3. The project uses renv and they have not restored it. Ask for renv::status().
A fourth, sneakier cause: they are on a different R version and the package failed to install from source because Rtools is missing. 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 .RData restore — it is the main source of “works on my machine”
  • Use Projects and here(); never setwd()
  • install.packages() once per machine, library() once per session
  • renv::init() / snapshot() / restore() makes a project’s library reproducible
  • .Rprofile holds R code, .Renviron holds 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.

Back to top