R learning notes

R for Data Science, Statistics & Reporting

By Imonikhe Ayeni

This reference starts with installing R and RStudio, then moves through the language, data cleaning, visualisation, statistics, modelling, reporting and Shiny. Work through the sections in order if you are new to R, or use the contents panel to jump directly to the topic you need.

Beginner to intermediateAbout 10 minutesLast updated September 2026Includes interview refresher

A note from the author

I’m Imonikhe Ayeni. I use R for statistical analysis, research and reproducible reporting.

If you are new to R, begin with RStudio, objects and data frames. The modelling, Quarto and Shiny sections can wait until the foundations are familiar.

No matching sections were found. Try a broader search term.
01

Installing R and RStudio

R is the programming language. RStudio is an integrated development environment that makes R easier to write, run, organise and debug. Install R first, then install RStudio Desktop.

Windows

1. Open the CRAN website.
2. Choose “Download R for Windows”.
3. Select “base”.
4. Download the current Windows installer.
5. Run the installer and accept the standard options.
6. Download RStudio Desktop from Posit.
7. Install and open RStudio.
8. In the Console, run: R.version.string

macOS

1. Open the CRAN website.
2. Choose “Download R for macOS”.
3. Select the installer that matches your Mac and macOS version.
4. Run the downloaded package.
5. Download and install RStudio Desktop.
6. Open RStudio and run: R.version.string

Linux: Ubuntu or Debian

sudo apt update
sudo apt install r-base r-base-dev

# Verify
R --version

# Start R
R

# Exit R
q()

Test R inside RStudio

R.version.string
getwd()

message("R is installed and working.")
02

Understanding RStudio

RStudio normally contains four panes: the source editor, console, environment/history, and files/plots/packages/help. Write reusable code in an R script rather than relying only on the console.

Create your first script

# File > New File > R Script

name <- "Imonikhe"
message <- paste("Welcome to R,", name)

print(message)

Create an R project

1. Select File > New Project.
2. Choose New Directory.
3. Choose New Project.
4. Give the project a meaningful name.
5. Keep scripts, data and outputs inside the project.
6. Use relative paths instead of changing the working directory manually.

Recommended project folders

my-r-project/
├── data/
├── scripts/
├── outputs/
├── reports/
├── figures/
└── my-r-project.Rproj
03

Installing and loading packages

Packages extend R. Install each package once, then load it in every new R session where it is required.

Install packages

install.packages("tidyverse")
install.packages(c(
  "readxl",
  "openxlsx",
  "janitor",
  "skimr",
  "here"
))

Load packages

library(tidyverse)
library(readxl)
library(openxlsx)
library(janitor)
library(skimr)
library(here)

Check and update packages

installed.packages()[, c("Package", "Version")]
update.packages(ask = FALSE)
04

R fundamentals

R commonly uses the assignment operator <-. Objects can store numbers, text, Boolean values, dates, vectors, tables, functions and models.

Variables and data types

name <- "Simon"
age <- 28L
height <- 1.78
is_learning <- TRUE
missing_value <- NA

class(name)
class(age)
class(height)
class(is_learning)

Arithmetic and comparisons

a <- 10
b <- 3

a + b
a - b
a * b
a / b
a %% b
a ^ b

a > b
a == 10
a != b

Strings

message <- "R for Data Science"

tolower(message)
toupper(message)
nchar(message)
grepl("Data", message)

paste("Learning", "R")
paste0("Score: ", 95)
05

Vectors, factors, lists and matrices

A vector stores values of the same basic type. Factors represent categories. Lists can contain different object types. Matrices store two-dimensional values of one type.

Vectors

scores <- c(78, 82, 91, 65, 88)

scores[1]
scores[2:4]
scores[scores >= 80]

length(scores)
mean(scores)
median(scores)
sum(scores)

Factors

gender <- factor(c(
  "Female",
  "Male",
  "Female",
  "Another way"
))

levels(gender)
table(gender)

Lists and matrices

patient <- list(
  name = "John",
  age = 65,
  diagnosis = "Diabetes"
)

patient$name
patient[["age"]]

matrix_data <- matrix(
  1:6,
  nrow = 2,
  byrow = TRUE
)

matrix_data
06

Conditions, loops and vectorisation

R supports familiar control-flow syntax, but vectorised functions are often preferred for data analysis.

Conditions

score <- 75

if (score >= 70) {
  result <- "Distinction"
} else if (score >= 60) {
  result <- "Merit"
} else if (score >= 50) {
  result <- "Pass"
} else {
  result <- "Fail"
}

print(result)

Loops

scores <- c(70, 80, 90)

for (score in scores) {
  print(score)
}

count <- 1

while (count <= 5) {
  print(count)
  count <- count + 1
}

Vectorised alternative

scores <- c(45, 67, 82, 39, 91)

result <- ifelse(
  scores >= 50,
  "Pass",
  "Fail"
)

result
07

Writing functions

Functions make analyses reusable and easier to test. Validate inputs and return clear errors when required.

A reusable function

calculate_mean <- function(values) {
  if (length(values) == 0) {
    stop("The vector cannot be empty.")
  }

  mean(values, na.rm = TRUE)
}

calculate_mean(c(70, 80, 90, NA))

Default arguments

calculate_total <- function(price, quantity = 1) {
  price * quantity
}

calculate_total(20)
calculate_total(20, 3)

Safe error handling

safe_divide <- function(a, b) {
  tryCatch(
    {
      if (b == 0) {
        stop("Cannot divide by zero.")
      }

      a / b
    },
    error = function(error) {
      message(error$message)
      NA_real_
    }
  )
}
08

Data frames and tibbles

A data frame is a table. A tibble is the modern tidyverse version with clearer printing and convenient behaviour.

Create a tibble

library(tibble)

data <- tibble(
  name = c("Alice", "Ben", "Chidi", "David"),
  age = c(25, 32, 41, 29),
  score = c(85, 72, 91, 68)
)

data

Inspect data

head(data)
tail(data)
dim(data)
names(data)
str(data)
summary(data)

library(skimr)
skim(data)

Select rows and columns

library(dplyr)

data |>
  select(name, score)

data |>
  filter(score >= 80)

data |>
  slice(1:3)
09

Importing and exporting data

Use readr for text files, readxl for Excel, haven for SPSS/Stata/SAS, and DBI for databases.

CSV and Excel

library(readr)
library(readxl)

csv_data <- read_csv("data/patients.csv")

excel_data <- read_excel(
  "data/patients.xlsx",
  sheet = "Responses"
)

SPSS, Stata and SAS

library(haven)

spss_data <- read_sav("data/survey.sav")
stata_data <- read_dta("data/survey.dta")
sas_data <- read_sas("data/survey.sas7bdat")

Export

library(readr)
library(openxlsx)

write_csv(
  cleaned_data,
  "outputs/cleaned_data.csv"
)

write.xlsx(
  cleaned_data,
  "outputs/cleaned_data.xlsx"
)
10

Data transformation with dplyr

dplyr provides consistent verbs for selecting, filtering, creating, sorting, grouping and summarising data.

Select, filter and arrange

library(dplyr)

result <- data |>
  select(name, age, score) |>
  filter(score >= 70, age < 40) |>
  arrange(desc(score))

result

Create columns

data <- data |>
  mutate(
    score_percentage = score / 100,
    result = if_else(
      score >= 70,
      "Pass",
      "Fail"
    )
  )

Multiple categories

data <- data |>
  mutate(
    grade = case_when(
      score >= 80 ~ "Excellent",
      score >= 70 ~ "Good",
      score >= 50 ~ "Pass",
      TRUE ~ "Fail"
    )
  )
11

Cleaning data

Clean column names, identify missing values, remove genuine duplicates and convert variables deliberately.

Clean names and missing values

library(janitor)
library(dplyr)
library(tidyr)

data <- data |>
  clean_names() |>
  mutate(
    score = replace_na(score, median(score, na.rm = TRUE)),
    gender = replace_na(gender, "Unknown")
  )

colSums(is.na(data))

Duplicates and types

duplicated_rows <- data |>
  filter(duplicated(patient_id))

data <- data |>
  distinct(patient_id, .keep_all = TRUE) |>
  mutate(
    age = as.numeric(age),
    gender = as.factor(gender)
  )

Dates

library(lubridate)

data <- data |>
  mutate(
    appointment_date = ymd(appointment_date),
    year = year(appointment_date),
    month = month(appointment_date, label = TRUE),
    day_name = wday(appointment_date, label = TRUE)
  )
12

Grouping and summarising

Grouped summaries are central to reporting and exploratory analysis.

Group summaries

summary_table <- data |>
  group_by(gender) |>
  summarise(
    mean_score = mean(score, na.rm = TRUE),
    median_score = median(score, na.rm = TRUE),
    participants = n(),
    .groups = "drop"
  )

summary_table

Counts and percentages

distribution <- data |>
  count(gender, name = "participants") |>
  mutate(
    percentage = participants / sum(participants) * 100
  )

distribution

Across multiple columns

data |>
  summarise(
    across(
      c(age, score),
      list(
        mean = ~ mean(.x, na.rm = TRUE),
        sd = ~ sd(.x, na.rm = TRUE)
      )
    )
  )
13

Joins and reshaping

Joins combine related tables. pivot_longer and pivot_wider move between long and wide structures.

Joins

combined <- patients |>
  left_join(
    hospitals,
    by = "hospital_id"
  )

matched_only <- patients |>
  inner_join(
    hospitals,
    by = "hospital_id"
  )

Long format

library(tidyr)

long_data <- data |>
  pivot_longer(
    cols = starts_with("score_"),
    names_to = "year",
    values_to = "score"
  )

Wide format

wide_data <- long_data |>
  pivot_wider(
    names_from = year,
    values_from = score
  )
14

Visualisation with ggplot2

ggplot2 builds charts in layers: data, aesthetic mappings, geometric marks and labels.

Scatter plot

library(ggplot2)

ggplot(data, aes(x = age, y = score)) +
  geom_point() +
  labs(
    title = "Age and Score",
    x = "Age",
    y = "Score"
  ) +
  theme_minimal()

Bar chart

ggplot(distribution, aes(
  x = gender,
  y = percentage
)) +
  geom_col() +
  labs(
    title = "Participant Distribution",
    x = "Gender",
    y = "Percentage"
  ) +
  theme_minimal()

Box plot

ggplot(data, aes(
  x = response_method,
  y = score
)) +
  geom_boxplot() +
  labs(
    title = "Scores by Response Method",
    x = "Response method",
    y = "Score"
  ) +
  theme_minimal()
15

Statistical analysis

R was designed for statistical computing and includes many tests in base R.

Welch t-test

test_result <- t.test(
  score ~ response_method,
  data = data,
  var.equal = FALSE
)

test_result

Chi-square and Fisher tests

contingency_table <- table(
  data$gender,
  data$response_method
)

chi_result <- chisq.test(contingency_table)
chi_result

fisher_result <- fisher.test(contingency_table)
fisher_result

Linear regression

model <- lm(
  score ~ age + response_method + gender,
  data = data
)

summary(model)
confint(model)

Logistic regression

model <- glm(
  has_disease ~ age + blood_pressure + cholesterol,
  data = data,
  family = binomial()
)

summary(model)

predicted_probability <- predict(
  model,
  newdata = data,
  type = "response"
)
16

Machine learning with tidymodels

tidymodels provides a consistent framework for preprocessing, resampling, modelling, tuning and evaluation.

Split the data

library(tidymodels)

set.seed(42)

data_split <- initial_split(
  data,
  prop = 0.8,
  strata = has_disease
)

training_data <- training(data_split)
testing_data <- testing(data_split)

Recipe and model

model_recipe <- recipe(
  has_disease ~ age + blood_pressure +
    cholesterol + gender,
  data = training_data
) |>
  step_impute_median(all_numeric_predictors()) |>
  step_impute_mode(all_nominal_predictors()) |>
  step_dummy(all_nominal_predictors()) |>
  step_normalize(all_numeric_predictors())

logistic_model <- logistic_reg() |>
  set_engine("glm") |>
  set_mode("classification")

Workflow and evaluation

model_workflow <- workflow() |>
  add_recipe(model_recipe) |>
  add_model(logistic_model)

fitted_model <- fit(
  model_workflow,
  data = training_data
)

predictions <- predict(
  fitted_model,
  testing_data,
  type = "prob"
) |>
  bind_cols(
    predict(fitted_model, testing_data),
    testing_data
  )

metrics(
  predictions,
  truth = has_disease,
  estimate = .pred_class
)
17

Time-series data

Time-series observations have an order. Create lags and rolling features carefully, and evaluate models using chronological splits.

Lag and rolling features

library(dplyr)
library(slider)

time_data <- time_data |>
  arrange(date) |>
  mutate(
    energy_lag_1 = lag(energy_use, 1),
    rolling_mean_7 = slide_dbl(
      energy_use,
      mean,
      .before = 6,
      .complete = TRUE,
      na.rm = TRUE
    )
  )

Monthly aggregation

library(lubridate)

monthly_data <- time_data |>
  mutate(month = floor_date(date, "month")) |>
  group_by(month) |>
  summarise(
    mean_energy = mean(energy_use, na.rm = TRUE),
    .groups = "drop"
  )
18

Quarto and reproducible reporting

Quarto combines narrative, R code, tables and charts in one reproducible document that can render to HTML, PDF, Word or presentations.

Quarto document header

---
title: "Analysis Report"
author: "Imonikhe Ayeni"
format:
  html:
    toc: true
    code-fold: true
execute:
  echo: true
  warning: false
---

R code chunk

```{r}
#| label: participant-summary

library(dplyr)

data |>
  summarise(
    participants = n(),
    mean_score = mean(score, na.rm = TRUE)
  )
```

Render from R

quarto::quarto_render(
  "reports/analysis.qmd"
)
19

Interactive applications with Shiny

Shiny creates interactive web applications directly from R. A basic app contains a user interface and a server function.

Minimal Shiny application

library(shiny)

ui <- fluidPage(
  titlePanel("Score Explorer"),
  sliderInput(
    "minimum_score",
    "Minimum score",
    min = 0,
    max = 100,
    value = 50
  ),
  tableOutput("filtered_table")
)

server <- function(input, output, session) {
  output$filtered_table <- renderTable({
    data |>
      filter(score >= input$minimum_score)
  })
}

shinyApp(ui, server)
20

Good R habits

Clear projects, readable pipelines, documented assumptions and reproducible package environments make work easier to review and maintain.

Recommended practices

• Use R projects and relative paths.
• Prefer clear object names over x, y or temp.
• Keep raw data unchanged.
• Put reusable logic in functions.
• Use set.seed() for reproducible random processes.
• Validate joins by checking row counts and unmatched keys.
• Comment why a decision was made, not only what the code does.
• Store package versions for important projects.

Use here for paths

library(here)

data <- readr::read_csv(
  here("data", "patients.csv")
)

readr::write_csv(
  data,
  here("outputs", "patients_clean.csv")
)

Use renv for reproducibility

install.packages("renv")

renv::init()
renv::snapshot()

# Restore packages later
renv::restore()
21

R key terms and interview refresher

Use this section as a quick revision page before an interview or when a term comes up at work. The aim is to understand the idea well enough to explain it in plain language before memorising syntax.

VectorThe basic one-dimensional R structure. All elements in an atomic vector have the same basic type.
ListA flexible object that can contain items of different types, including vectors, data frames and other lists.
FactorA categorical variable stored with defined levels. Useful when categories have meaning or ordering.
Data frame vs tibbleBoth store tabular data. Tibbles are a tidyverse-friendly form with clearer printing and stricter behaviour.
NA vs NULL vs NaNNA means a missing value; NULL usually means absence of an object or element; NaN is a special numeric value meaning 'not a number'.
VectorisationApplying an operation to an entire vector or column instead of writing an explicit loop.
Pipe|> passes the result on the left into the next function, making multi-step workflows easier to read.
ReproducibilityThe ability for someone else, or future you, to rerun the same analysis and obtain the same result.

Common interview questions

What is the difference between a vector and a list in R?

A vector contains elements of one basic type. A list can contain objects of different types and sizes.

What is the difference between NA and NULL?

NA represents a missing value inside an object. NULL represents the absence of an object or element.

Why use factors?

Factors represent categorical variables and preserve a controlled set and order of levels, which matters in modelling and reporting.

What is the difference between a data frame and a tibble?

A tibble is a modern data-frame class with cleaner printing and fewer automatic conversions.

Why is vectorised code often preferred in R?

It is usually shorter, clearer and often faster than explicit loops for column- or vector-based operations.

Practical interview tests

These short tasks test whether you can apply the tool, explain your reasoning and validate the result. In a live exercise, say your assumptions aloud and check the output rather than rushing straight to syntax.

Summarise mean score and record count by category using dplyr.What it tests: grouped data manipulation

Answer: Use group_by() followed by summarise(), and handle missing values deliberately.

summary <- data |>
  group_by(category) |>
  summarise(
    mean_score = mean(score, na.rm = TRUE),
    records = n(),
    .groups = "drop"
  )
How would you find rows in one table with no match in another?What it tests: join validation

Answer: Use anti_join() when you want rows from the first table whose key does not occur in the second.

Why: anti_join() expresses the QA intention directly.

unmatched <- patients |>
  anti_join(
    hospitals,
    by = "hospital_id"
  )
What would you do before replacing NA values with a mean or median?What it tests: missing-data judgement

Answer: Investigate why values are missing, how many are missing, whether the pattern is systematic, and whether imputation is defensible.

Why: The interviewer is testing judgement, not whether you know one imputation function.

Convert score_2024, score_2025 and score_2026 into long format.What it tests: tidyr reshaping

Answer: Use pivot_longer() to create year and score columns.

long_data <- data |>
  pivot_longer(
    cols = starts_with("score_"),
    names_to = "year",
    values_to = "score"
  )
When would you choose a Welch t-test rather than an equal-variance t-test?What it tests: statistical reasoning

Answer: Use Welch's test to compare two independent means when you do not want to assume equal population variances.

Why: In R, the ordinary two-sample t.test() uses Welch's approach by default unless var.equal = TRUE is supplied.

22

Four-week R learning plan

Build fluency by retyping, modifying and debugging examples. Use datasets relevant to your interests.

Week 1

RStudio, variables, vectors, lists, data frames, conditions, loops, functions and packages.

Week 2

Data import, dplyr, tidyr, missing data, joins, dates, grouped summaries and ggplot2.

Week 3

Statistical tests, linear models, logistic regression, survey analysis and model interpretation.

Week 4

tidymodels, time series, Quarto, Shiny and one complete end-to-end project.