</>
CodeLearn Pro
Start Learning Free โ†’

R โ€” Complete Beginner Tutorial

6 modules ยท 18 examples ยท Click โ–ถ Run Code to see the output

โœฆ Intermediateโ† Overview

๐Ÿ’ก How to use this tutorial

Read each explanation, study the code, then click โ–ถ Run Code to see the output.

Module 01

Variables & Basics

R uses <- (arrow) as its primary assignment operator, though = also works. Variables can hold numbers, text, or logical values. Everything in R is a vector โ€” even a single number is technically a vector of length one. print() displays output. cat() prints without the [1] prefix and lets you format output with newlines (\n). R is case-sensitive: myVar and myvar are different variables.

Variables and Data Types

Assign values with <- (the R convention) or =. R has four main types: numeric (numbers), character (text in quotes), logical (TRUE or FALSE), and integer (whole numbers, written with L). Use class() to find the type of any variable. is.numeric(), is.character() etc. check the type.

example.RR
# Assigning variables โ€” <- is the R convention
name    <- "Alice"
age     <- 25L       # L makes it an integer
score   <- 92.5      # numeric (double)
passed  <- TRUE      # logical

# Printing
print(name)
print(age)
cat("Hello,", name, "!\n")
cat("Age:", age, "\n")

# Check types
class(name)
class(age)
class(score)
class(passed)

# typeof โ€” the underlying storage type
typeof(age)
typeof(score)

# is.* functions โ€” check type
is.numeric(score)
is.character(name)
is.logical(passed)

# Type conversion
as.character(42)
as.numeric("3.14")
as.integer(9.9)     # truncates, not rounds
โ–ถ Click Run Code to see output

The [1] before output in R means "element 1 of the result". When R prints a long vector across multiple lines, it shows [1], [10], [20] etc. to help you count where you are. It is not part of the value โ€” it is just R helping you navigate the output.

Arithmetic and String Operations

R handles arithmetic naturally. The ^ or ** operator raises to a power. %% is modulo (remainder). %/% is integer division. For strings, paste() joins them together. paste0() joins without spaces. nchar() counts characters. substr() extracts a portion. toupper() and tolower() change case.

example.RR
# Basic arithmetic
cat(10 + 3, "\n")
cat(10 - 3, "\n")
cat(10 * 3, "\n")
cat(10 / 3, "\n")      # real division โ€” gives decimal
cat(10 %% 3, "\n")     # modulo (remainder)
cat(10 %/% 3, "\n")    # integer division
cat(2 ^ 10, "\n")      # power

# Built-in maths functions
cat(sqrt(144), "\n")
cat(abs(-42), "\n")
cat(round(3.14159, 2), "\n")
cat(ceiling(4.1), "\n")
cat(floor(4.9), "\n")

# String operations
first <- "Hello"
last  <- "World"

cat(paste(first, last), "\n")          # "Hello World"
cat(paste0(first, last), "\n")         # "HelloWorld"
cat(paste(first, last, sep="-"), "\n") # "Hello-World"

cat(nchar("CodeLearn"), "\n")          # 9
cat(toupper("hello"), "\n")
cat(tolower("HELLO"), "\n")
cat(substr("Hello, World!", 8, 13), "\n")
cat(gsub("World", "R", "Hello World"), "\n")
โ–ถ Click Run Code to see output

paste() and paste0() are two of the most-used functions in R. paste() puts a space between items by default โ€” use sep="" to remove it, or use paste0() which has no separator. The collapse parameter joins a vector into a single string: paste(c("a","b","c"), collapse=", ") gives "a, b, c".

Module 02

Vectors

Vectors are the fundamental data structure in R. Every value in R is a vector. You create vectors with c() (short for combine). Vectors can only hold one type โ€” if you mix types, R converts everything to the most general type (a process called coercion). Vector operations in R are element-wise and vectorised โ€” adding two vectors adds corresponding elements without any loop. This is one of the most powerful and unique aspects of R.

Creating and Using Vectors

Create vectors with c(). Use : to create integer sequences. seq() gives more control over sequences. rep() repeats values. Access elements with [index] โ€” R uses 1-based indexing (the first element is [1], not [0] like most languages). Negative indexing removes elements.

example.RR
# Creating vectors
numbers  <- c(3, 1, 4, 1, 5, 9, 2, 6)
names_v  <- c("Alice", "Bob", "Carol", "Dave")
flags    <- c(TRUE, FALSE, TRUE, TRUE, FALSE)

# Sequences
seq1 <- 1:10               # 1 to 10
seq2 <- seq(0, 1, by=0.25) # 0 to 1 in steps of 0.25
seq3 <- seq(1, 20, length.out=5)  # 5 evenly spaced values

print(seq1)
print(seq2)
print(seq3)

# Repetition
rep1 <- rep(0, 5)          # five zeros
rep2 <- rep(c(1, 2), 3)   # repeat the vector 3 times
print(rep1)
print(rep2)

# Accessing elements โ€” R uses 1-based indexing!
print(numbers[1])          # first element (NOT 0 like Python)
print(numbers[3])          # third element
print(numbers[1:4])        # elements 1 through 4
print(numbers[c(1,3,5)])   # specific elements

# Negative indexing โ€” REMOVE those elements
print(numbers[-1])         # everything except first
print(numbers[-(1:3)])     # remove first three
โ–ถ Click Run Code to see output

R is 1-indexed โ€” the first element is at position [1], not [0]. This is different from Python, JavaScript, C, and most other languages which start at 0. It matches how statisticians and mathematicians think about sequences and is one of the first adjustments to make when learning R.

Vector Operations and Statistics

Vector operations in R apply to every element automatically โ€” no loop needed. Adding a single number to a vector adds it to every element. Adding two vectors of the same length adds element by element. R has rich built-in statistical functions: mean, median, sd (standard deviation), var (variance), summary, and more.

example.RR
scores <- c(85, 92, 78, 95, 88, 76, 91, 83, 89, 94)

# Vectorised arithmetic โ€” no loop needed!
curved <- scores + 5        # add 5 to every score
scaled <- scores / 10       # divide every score by 10
print(curved)

# Comparison โ€” returns a logical vector
passed <- scores >= 80
print(passed)

# Use logical vector to filter
print(scores[passed])       # only scores >= 80
print(scores[scores > 90])  # inline filter

# Built-in statistical functions
cat("Mean:   ", mean(scores), "\n")
cat("Median: ", median(scores), "\n")
cat("SD:     ", round(sd(scores), 2), "\n")
cat("Min:    ", min(scores), "\n")
cat("Max:    ", max(scores), "\n")
cat("Sum:    ", sum(scores), "\n")
cat("Length: ", length(scores), "\n")

# Summary โ€” gives quartiles and more
summary(scores)

# Sorting
print(sort(scores))
print(sort(scores, decreasing=TRUE))
โ–ถ Click Run Code to see output

Logical vectors are incredibly powerful in R. When you write scores[scores >= 80], R first creates a logical vector (TRUE/FALSE for each element), then uses it to select the TRUE positions. This pattern โ€” create a condition, use it to filter โ€” is used constantly in data analysis and is much cleaner than writing a for loop.

Module 03

Control Flow

R has standard if/else if/else, for, and while control flow. However, in R you often avoid explicit loops by using vectorised operations and the apply family of functions instead. That said, loops are important to understand. for loops iterate over any vector. while loops run until a condition is false. next skips to the next iteration (like continue in other languages). break exits the loop.

if / else if / else

R's if statement works like most languages. The condition must be a single TRUE or FALSE (not a vector). Use ifelse() for vectorised conditions โ€” it applies the condition to every element of a vector and returns a vector of results. The ifelse() function is far more useful in data analysis than the regular if statement.

example.RR
# Standard if / else if / else
score <- 82

if (score >= 90) {
  cat("Grade: A\n")
} else if (score >= 75) {
  cat("Grade: B\n")
} else if (score >= 60) {
  cat("Grade: C\n")
} else {
  cat("Grade: F\n")
}

# Logical operators
temperature <- 28
is_weekend  <- TRUE

if (temperature > 25 & is_weekend) {
  cat("Perfect beach day!\n")
} else if (temperature > 25) {
  cat("Hot but it's a weekday\n")
} else {
  cat("Stay inside\n")
}

# ifelse() โ€” vectorised if/else (very useful!)
scores <- c(85, 62, 91, 74, 88, 55, 79)
results <- ifelse(scores >= 75, "Pass", "Fail")
print(results)

# Nested ifelse for grades
grades <- ifelse(scores >= 90, "A",
           ifelse(scores >= 75, "B",
           ifelse(scores >= 60, "C", "F")))
print(grades)
โ–ถ Click Run Code to see output

ifelse(condition, yes, no) is one of the most used functions in R data analysis. It works on entire vectors at once โ€” no for loop needed. It replaces the common pattern of looping over rows and applying a condition. When working with data frames, you use ifelse() constantly to create new columns based on conditions.

for and while Loops

for loops in R iterate over the elements of any vector. seq_along() gives a safe sequence matching the vector length. while loops run as long as the condition is TRUE. next skips the rest of the current iteration. break exits the loop immediately. Remember: in R, loops are often slower than vectorised alternatives.

example.RR
# for loop โ€” iterate over a vector
fruits <- c("apple", "banana", "cherry")
for (fruit in fruits) {
  cat("I like", fruit, "\n")
}

# for loop with index using seq_along
scores <- c(85, 92, 78, 95)
for (i in seq_along(scores)) {
  cat("Student", i, "scored:", scores[i], "\n")
}

# for loop โ€” accumulate a result
total <- 0
for (n in 1:10) {
  total <- total + n
}
cat("Sum 1-10:", total, "\n")

# while loop
count <- 1
while (count <= 5) {
  cat("Count:", count, "\n")
  count <- count + 1
}

# next (skip) and break (exit)
cat("Odd numbers only, stop at 8: ")
for (i in 1:10) {
  if (i %% 2 == 0) next   # skip even numbers
  if (i > 8)      break   # stop at 8
  cat(i, "")
}
cat("\n")
โ–ถ Click Run Code to see output

In R, prefer seq_along(x) over 1:length(x) for looping. If x is empty, 1:length(x) gives 1:0 which is c(1,0) โ€” an unexpected two-element vector. seq_along(x) correctly returns an empty sequence for empty vectors. This prevents a whole class of hard-to-debug bugs.

Module 04

Functions

Functions in R are created with the function() keyword. The last evaluated expression in a function is automatically returned โ€” you do not need an explicit return() unless you want to exit early. R functions can have default argument values. The apply family (sapply, lapply, vapply, tapply) lets you apply functions to vectors and lists without writing explicit loops โ€” this is core to the R style.

Defining Functions

Write function(parameters) { body }. The last expression is the return value โ€” return() is optional. Default arguments are set with parameter = default. R functions are first-class objects โ€” you can store them in variables and pass them to other functions.

example.RR
# Simple function โ€” last expression returned automatically
square <- function(x) {
  x ^ 2   # no return() needed
}
cat(square(5), "\n")
cat(square(12), "\n")

# Function with multiple parameters
bmi <- function(weight_kg, height_m) {
  result <- weight_kg / height_m ^ 2
  round(result, 1)
}
cat("BMI:", bmi(70, 1.75), "\n")

# Default argument values
greet <- function(name, greeting = "Hello") {
  paste(greeting, name)
}
cat(greet("Alice"), "\n")
cat(greet("Bob", "Good morning"), "\n")

# Multiple return values (via list or vector)
stats <- function(x) {
  list(
    mean   = round(mean(x), 2),
    median = median(x),
    sd     = round(sd(x), 2),
    n      = length(x)
  )
}

result <- stats(c(85, 92, 78, 95, 88))
cat("Mean:", result$mean, "\n")
cat("SD:  ", result$sd, "\n")
cat("N:   ", result$n, "\n")
โ–ถ Click Run Code to see output

R functions always return the last evaluated expression. This means you can write very concise functions: square <- function(x) x^2 works perfectly without curly braces for single expressions. Use explicit return() only when you need to exit a function early based on a condition.

sapply and lapply โ€” Apply Functions to Vectors

Instead of writing for loops, R has the apply family. sapply() applies a function to each element of a vector and returns a simplified result (usually a vector). lapply() does the same but always returns a list. These are the R way to transform data without loops.

example.RR
# sapply โ€” applies function, returns vector
numbers <- c(1, 4, 9, 16, 25)

roots <- sapply(numbers, sqrt)
print(roots)

# sapply with an anonymous function
cubed <- sapply(1:6, function(x) x ^ 3)
print(cubed)

# sapply over character vector
fruits  <- c("apple", "banana", "cherry")
lengths <- sapply(fruits, nchar)
print(lengths)

upper <- sapply(fruits, toupper)
print(upper)

# lapply โ€” always returns a list
result <- lapply(1:4, function(x) x * 10)
print(result)

# Simplify a list with unlist()
print(unlist(result))

# Apply a function to each column of a data frame
df <- data.frame(
  math    = c(85, 90, 78),
  science = c(92, 88, 95),
  english = c(79, 84, 91)
)

col_means <- sapply(df, mean)
print(col_means)
โ–ถ Click Run Code to see output

sapply() is short for "simplify apply". It tries to return the simplest possible structure โ€” if the results are all single values, it returns a vector. If they are all vectors of the same length, it returns a matrix. If simplification fails, it falls back to a list. For predictable output types, use vapply() which requires you to specify the expected return type.

Module 05

Data Frames

A data frame is R's most important data structure for data analysis. It is a table where each column is a vector and every column has the same number of rows. Think of it like a spreadsheet or a database table. Columns can be different types โ€” one column can be numeric and another can be character. You access columns with $. You filter rows with logical conditions. The dplyr package (part of the tidyverse) makes data frame operations even cleaner.

Creating and Exploring Data Frames

Create a data frame with data.frame(). Each argument becomes a column. Access columns with $name. Use head() to see the first few rows. str() shows the structure. dim() gives dimensions. colnames() lists column names. nrow() and ncol() count rows and columns.

example.RR
# Create a data frame
students <- data.frame(
  name   = c("Alice", "Bob", "Carol", "Dave", "Eve"),
  score  = c(92, 78, 95, 85, 88),
  grade  = c("A", "C", "A", "B", "B"),
  passed = c(TRUE, TRUE, TRUE, TRUE, TRUE),
  stringsAsFactors = FALSE
)

# View the data frame
print(students)

# Explore structure
cat("Dimensions:", dim(students), "\n")
cat("Rows:", nrow(students), "\n")
cat("Columns:", ncol(students), "\n")
cat("Column names:", colnames(students), "\n")

# Access a column with $
print(students$name)
print(students$score)

# Access a specific row [row, column]
print(students[1, ])      # first row, all columns
print(students[1, "name"])  # first row, name column

# Summary of the whole data frame
summary(students$score)
โ–ถ Click Run Code to see output

stringsAsFactors = FALSE is important in older R versions (before R 4.0). In R 3.x, data.frame() automatically converted character columns to factors, which caused many surprising bugs. In R 4.0+ this changed to FALSE by default. Always include it explicitly when your code needs to work across R versions.

Filtering and Transforming Data Frames

Filter rows using logical conditions inside square brackets. Add new columns by assigning to $newcolumn. Remove columns by setting them to NULL. Use order() to sort. merge() joins two data frames. The subset() function is a cleaner alternative to bracket indexing for simple cases.

example.RR
students <- data.frame(
  name  = c("Alice","Bob","Carol","Dave","Eve","Frank"),
  score = c(92, 78, 95, 85, 88, 61),
  stringsAsFactors = FALSE
)

# Filter rows โ€” keep only those scoring 85+
top <- students[students$score >= 85, ]
print(top)

# subset() โ€” cleaner syntax
bottom <- subset(students, score < 80)
print(bottom)

# Add a new column
students$grade <- ifelse(students$score >= 90, "A",
                  ifelse(students$score >= 80, "B",
                  ifelse(students$score >= 70, "C", "F")))
print(students)

# Add curved score
students$curved <- students$score + 3

# Sort by score descending
ranked <- students[order(-students$score), ]
print(ranked)

# Aggregate โ€” average score per grade
grade_avg <- aggregate(score ~ grade, data=students, FUN=mean)
print(grade_avg)
โ–ถ Click Run Code to see output

The syntax df[condition, ] selects rows. The comma is required โ€” df[condition] without a comma selects columns not rows. df[rows, cols] is the full form. df[df$score >= 80, ] means "give me all rows where score is 80 or more, and all columns". This indexing system is consistent and powerful once you internalise it.

Module 06

Statistics & Plots

R was built for statistics โ€” the functions you need are right there without any imports. cor() computes correlation. t.test() runs a t-test. lm() fits linear regression. For plotting, R has a built-in graphics system (plot, hist, boxplot, barplot) that works immediately. ggplot2 is the modern standard for beautiful, layered visualisations โ€” it is one of the most powerful data visualisation tools in existence.

Built-in Statistical Functions

R has the world's richest set of statistical functions built into the base language. Descriptive statistics, correlation, probability distributions, hypothesis tests โ€” all available without any imports. This is why statisticians and data scientists love R.

example.RR
# Generate some sample data
set.seed(42)   # for reproducibility
x <- rnorm(100, mean=50, sd=10)   # 100 normal random numbers
y <- x * 1.5 + rnorm(100, sd=5)  # correlated with x

# Descriptive statistics
cat("Mean:    ", round(mean(x), 2), "\n")
cat("Median:  ", round(median(x), 2), "\n")
cat("SD:      ", round(sd(x), 2), "\n")
cat("Variance:", round(var(x), 2), "\n")
cat("Range:   ", round(range(x), 2), "\n")

# Summary โ€” 5-number summary + mean
print(summary(x))

# Correlation between x and y
cat("Correlation:", round(cor(x, y), 3), "\n")

# Frequency table
grades <- c("A","B","A","C","B","A","F","B","C","A")
print(table(grades))
print(prop.table(table(grades)))   # proportions

# Quantiles
print(quantile(x, probs = c(0.25, 0.50, 0.75, 0.90)))

# Simple t-test โ€” is the mean significantly different from 50?
test_result <- t.test(x, mu = 50)
cat("p-value:", round(test_result$p.value, 4), "\n")
โ–ถ Click Run Code to see output

set.seed() makes random number generation reproducible โ€” anyone running your code with the same seed gets exactly the same "random" numbers. This is essential in data science and statistical analysis so your results can be reproduced. Always set a seed at the top of any analysis that uses randomness.

Base R Plots and ggplot2 Intro

R's base graphics system creates plots instantly with plot(), hist(), boxplot(), and barplot(). For publication-quality charts, ggplot2 uses a layered grammar of graphics: start with ggplot(), add aesthetics with aes(), and add geometric layers like geom_point() and geom_line(). Here we show the code โ€” in RStudio or Jupyter, these would render graphically.

example.RR
# Base R plotting โ€” works in any R environment
# (In RStudio/Jupyter these show as actual plots)

# Histogram
x <- c(85, 92, 78, 95, 88, 76, 91, 83, 89, 94, 71, 87)
cat("hist() would show a histogram of", length(x), "scores\n")
cat("Range:", min(x), "to", max(x), "\n")

# Summary for what a boxplot would show
cat("\nBoxplot summary:\n")
print(quantile(x))

# Bar chart data
languages <- c("Python","JavaScript","Java","R","C++")
popularity <- c(30, 25, 18, 12, 15)
cat("\nBar chart data:\n")
for (i in seq_along(languages)) {
  bar <- paste(rep("โ–ˆ", round(popularity[i]/2)), collapse="")
  cat(sprintf("%-12s %s %d%%\n", languages[i], bar, popularity[i]))
}

# ggplot2 code (would render in RStudio/Jupyter)
cat("\nggplot2 scatter plot code:\n")
cat("library(ggplot2)\n")
cat("df <- data.frame(x = 1:10, y = (1:10)^2)\n")
cat("ggplot(df, aes(x=x, y=y)) +\n")
cat("  geom_point(color='steelblue', size=3) +\n")
cat("  geom_line(color='steelblue') +\n")
cat("  labs(title='y = x squared',\n")
cat("       x='x', y='y') +\n")
cat("  theme_minimal()\n")
โ–ถ Click Run Code to see output

ggplot2 uses + to add layers to a plot. Every plot starts with ggplot(data, aes(x=col1, y=col2)) which sets up the coordinate system. Then you add geometric layers: geom_point() for scatter plots, geom_bar() for bar charts, geom_histogram() for histograms, geom_boxplot() for box plots. The theme_*() functions control the overall visual style.

You finished the R tutorial!

You can now write R with variables, vectors, control flow, functions, data frames and basic statistics. You have the foundation to explore the tidyverse (dplyr, ggplot2, tidyr) and conduct real data analysis.