This is a 60-minute introductory laboratory in R and RStudio. The aim is not merely to learn commands, but to understand the computational ideas that support statistical work in R.
The lecture follows the workflow
\[\boxed{\text{Create/Read} \rightarrow \text{Inspect} \rightarrow \text{Manipulate} \rightarrow \text{Summarize} \rightarrow \text{Visualize} \rightarrow \text{Save}}\]
The emphasis is on base R, so that students first
understand vectors, indexing, functions, data frames, and graphics
before moving to packages such as dplyr and
ggplot2.
By the end of the session, students should be able to:
NA;for and while loops;| Time | Topic |
|---|---|
| 0–3 min | RStudio, workspace, help |
| 3–9 min | Arithmetic, objects, data types, logic |
| 9–14 min | Vectors, matrices, arrays |
| 14–20 min | Indexing, vectorization, missing values |
| 20–24 min | Sequences and random numbers |
| 24–29 min | Functions and decisions |
| 29–34 min | Loops and simulation |
| 34–43 min | Graphics |
| 43–47 min | Reading and inspecting data |
| 47–55 min | Descriptive statistics and statistical plots |
| 55–60 min | Data-frame manipulation and saving |
R is a programming language and environment for statistical computing. RStudio is an integrated development environment (IDE) for working with R.
The four main RStudio regions are:
A reproducible analysis should live primarily in the script/document, not only in the Console.
getwd() # Current working directory
#> [1] "/Users/buddhanandabanerjee/Downloads/Stat_Demo_RStudio_60min"
ls() # Objects currently in memory
#> character(0)
getwd() means get working directory. When R
reads or writes a file using only a filename, this directory is the
default location.
To clear the interactive workspace:
rm(list = ls())
This command is not executed while rendering this document because later chunks depend on objects created earlier.
?mean
help(mean)
example(mean)
A strong R user does not memorize every function argument. Instead, they know how to find and interpret documentation.
10 + 5
#> [1] 15
10 - 5
#> [1] 5
10 * 5
#> [1] 50
10 / 5
#> [1] 2
2^5
#> [1] 32
17 %% 5
#> [1] 2
17 %/% 5
#> [1] 3
The operators have the following meanings:
| Operator | Meaning |
|---|---|
+, - |
addition, subtraction |
*, / |
multiplication, division |
^ |
exponentiation |
%% |
remainder |
%/% |
integer division |
R follows the usual order of mathematical operations.
2 + 3 * 4
#> [1] 14
(2 + 3) * 4
#> [1] 20
An object is a named location in which R stores information.
x <- 10
y <- 3
x + y
#> [1] 13
x * y
#> [1] 30
x^y
#> [1] 1000
The conventional assignment operator is <-.
Some common mathematical functions are:
sqrt(25)
#> [1] 5
log(10)
#> [1] 2.302585
exp(1)
#> [1] 2.718282
sin(pi / 2)
#> [1] 1
abs(-7)
#> [1] 7
x <- 10
course <- "R Programming"
passed <- TRUE
class(x)
#> [1] "numeric"
class(course)
#> [1] "character"
class(passed)
#> [1] "logical"
typeof(x)
#> [1] "double"
typeof(course)
#> [1] "character"
typeof(passed)
#> [1] "logical"
Three fundamental types are:
TRUE or
FALSE.A factor represents categorical data.
grade <- factor(c("A", "B", "A", "C", "B"))
grade
#> [1] A B A C B
#> Levels: A B C
levels(grade)
#> [1] "A" "B" "C"
table(grade)
#> grade
#> A B C
#> 2 2 1
levels() shows the possible categories.
table() counts the observations in each category.
x <- 10
x > 5
#> [1] TRUE
x < 5
#> [1] FALSE
x == 10
#> [1] TRUE
x != 10
#> [1] FALSE
x >= 10
#> [1] TRUE
x <= 10
#> [1] TRUE
Do not confuse:
<- : assignment;== : equality comparison.Logical statements can be combined:
(x > 5) & (x < 20) # AND
#> [1] TRUE
(x < 5) | (x == 10) # OR
#> [1] TRUE
!(x > 5) # NOT
#> [1] FALSE
R is fundamentally vector oriented. Many calculations that require explicit loops in other languages can be applied directly to entire vectors in R.
v <- c(10, 20, 30, 40, 50)
v
#> [1] 10 20 30 40 50
length(v)
#> [1] 5
class(v)
#> [1] "numeric"
c() means combine or concatenate. A
vector is a one-dimensional collection of values.
A matrix is a two-dimensional rectangular structure.
A <- matrix(1:12, nrow = 3, ncol = 4)
A
#> [,1] [,2] [,3] [,4]
#> [1,] 1 4 7 10
#> [2,] 2 5 8 11
#> [3,] 3 6 9 12
dim(A)
#> [1] 3 4
By default R fills matrices column by column.
B <- matrix(1:12, nrow = 3, ncol = 4, byrow = TRUE)
B
#> [,1] [,2] [,3] [,4]
#> [1,] 1 2 3 4
#> [2,] 5 6 7 8
#> [3,] 9 10 11 12
An array generalizes the matrix to more than two dimensions.
X <- array(1:24, dim = c(3, 4, 2))
X
#> , , 1
#>
#> [,1] [,2] [,3] [,4]
#> [1,] 1 4 7 10
#> [2,] 2 5 8 11
#> [3,] 3 6 9 12
#>
#> , , 2
#>
#> [,1] [,2] [,3] [,4]
#> [1,] 13 16 19 22
#> [2,] 14 17 20 23
#> [3,] 15 18 21 24
dim(X)
#> [1] 3 4 2
Thus:
\[\text{vector: 1D},\qquad \text{matrix: 2D},\qquad \text{array: higher-dimensional}.\]
Square brackets select elements.
v[1]
#> [1] 10
v[2:4]
#> [1] 20 30 40
v[-1]
#> [1] 20 30 40 50
v[c(1, 3, 5)]
#> [1] 10 30 50
v[v > 25]
#> [1] 30 40 50
The last expression uses logical indexing: R keeps
values for which the condition is TRUE.
The standard matrix convention is
\[\texttt{A[row, column]}.\]
A[2, 3]
#> [1] 8
A[1, ]
#> [1] 1 4 7 10
A[, 2]
#> [1] 4 5 6
A blank row or column index means “all”.
z <- 1:4
z + 10
#> [1] 11 12 13 14
z * 2
#> [1] 2 4 6 8
z^2
#> [1] 1 4 9 16
sqrt(z)
#> [1] 1.000000 1.414214 1.732051 2.000000
These operations act on every element without an explicit loop. This is called vectorization.
M1 <- matrix(c(1, 2, 3, 4), nrow = 2)
M2 <- matrix(c(5, 6, 7, 8), nrow = 2)
M1 * M2
#> [,1] [,2]
#> [1,] 5 21
#> [2,] 12 32
M1 %*% M2
#> [,1] [,2]
#> [1,] 23 31
#> [2,] 34 46
t(M1)
#> [,1] [,2]
#> [1,] 1 2
#> [2,] 3 4
The distinction is essential:
* gives elementwise multiplication;%*% gives matrix multiplication;t() gives the transpose.R represents a missing observation by NA.
x_missing <- c(10, 12, NA, 15, 18)
mean(x_missing)
#> [1] NA
mean(x_missing, na.rm = TRUE)
#> [1] 13.75
is.na(x_missing)
#> [1] FALSE FALSE TRUE FALSE FALSE
sum(is.na(x_missing))
#> [1] 1
na.rm = TRUE means remove NA values for this
calculation.
Statistical caution: computationally ignoring missing observations is not automatically an appropriate missing-data analysis. This example demonstrates syntax only.
x_sort <- c(8, 3, 10, 2, 7)
sort(x_sort)
#> [1] 2 3 7 8 10
sort(x_sort, decreasing = TRUE)
#> [1] 10 8 7 3 2
order(x_sort)
#> [1] 4 2 5 1 3
x_sort[order(x_sort)]
#> [1] 2 3 7 8 10
sort() returns sorted values. order()
returns the positions needed to place values in order. The latter is
especially useful for sorting data frames.
1:10
#> [1] 1 2 3 4 5 6 7 8 9 10
10:1
#> [1] 10 9 8 7 6 5 4 3 2 1
seq(0, 1, by = 0.1)
#> [1] 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
seq(0, 1, length.out = 6)
#> [1] 0.0 0.2 0.4 0.6 0.8 1.0
Use : for simple integer sequences and
seq() when more control is needed.
rep(5, 4)
#> [1] 5 5 5 5
rep(c(1, 2), times = 3)
#> [1] 1 2 1 2 1 2
rep(c("A", "B"), each = 3)
#> [1] "A" "A" "A" "B" "B" "B"
times repeats the whole pattern; each
repeats each element.
set.seed(123)
rnorm(5)
#> [1] -0.56047565 -0.23017749 1.55870831 0.07050839 0.12928774
runif(5)
#> [1] 0.9568333 0.4533342 0.6775706 0.5726334 0.1029247
rbinom(10, size = 1, prob = 0.5)
#> [1] 1 0 0 0 1 1 1 1 1 1
set.seed() makes pseudo-random simulation
reproducible.
rnorm() generates normal observations;runif() generates uniform observations;rbinom() generates binomial observations.u <- c(4, 7, 2, 9, 5)
mean(u)
#> [1] 5.4
median(u)
#> [1] 5
sd(u)
#> [1] 2.701851
var(u)
#> [1] 7.3
min(u)
#> [1] 2
max(u)
#> [1] 9
A function packages a calculation so it can be reused.
square <- function(x) {
return(x^2)
}
square(5)
#> [1] 25
square(c(1, 2, 3, 4))
#> [1] 1 4 9 16
A statistical example:
cv <- function(x) {
return(sd(x) / mean(x))
}
cv(c(12, 15, 18, 20, 25))
#> [1] 0.274986
The coefficient of variation is
\[CV=\frac{s}{\bar{x}},\]
when this quantity is meaningful for the measurement scale.
if
and elsescore <- 75
if (score >= 50) {
print("Pass")
} else {
print("Fail")
}
#> [1] "Pass"
The program evaluates a logical condition and chooses one branch.
for
loopsquares <- numeric(10)
for (i in 1:10) {
squares[i] <- i^2
}
squares
#> [1] 1 4 9 16 25 36 49 64 81 100
numeric(10) pre-allocates the result vector.
Pre-allocation is preferable to repeatedly growing an object inside a
loop.
while loopi <- 1
while (i <= 5) {
print(i)
i <- i + 1
}
#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
A while loop continues while its logical condition
remains true.
set.seed(123)
sample_means <- numeric(200)
for (i in 1:200) {
sample_means[i] <- mean(rnorm(30))
}
summary(sample_means)
#> Min. 1st Qu. Median Mean 3rd Qu. Max.
#> -0.479357 -0.108097 0.016034 0.005272 0.111283 0.346561
Each iteration generates a sample of size 30 from \(N(0,1)\) and records its mean. The resulting 200 means approximate the sampling distribution of the sample mean.
hist(
sample_means,
col = "skyblue",
border = "white",
main = "Sampling Distribution of the Mean",
xlab = "Sample mean"
)
This is the first important bridge from programming to statistical simulation.
xgrid <- seq(-2 * pi, 2 * pi, length.out = 500)
y_sin <- sin(xgrid)
y_cos <- cos(xgrid)
plot(
xgrid, y_sin,
type = "l",
main = "Sine Curve",
xlab = "x",
ylab = "sin(x)"
)
type = "l" requests a line plot.
plot(
xgrid, y_sin,
type = "l",
col = "royalblue3",
lwd = 3,
lty = 1,
main = "Styled Sine Curve",
xlab = "x",
ylab = "sin(x)"
)
Useful arguments:
col: colour;lwd: line width;lty: line type;pch: plotting symbol;cex: relative point/text size.plot(
xgrid, y_sin,
type = "l",
col = "royalblue3",
lwd = 3,
ylim = c(-1.1, 1.1),
xlab = "x",
ylab = "Function value",
main = "Sine and Cosine"
)
lines(xgrid, y_cos, col = "tomato3", lwd = 3, lty = 2)
legend(
"topright",
legend = c("sin(x)", "cos(x)"),
col = c("royalblue3", "tomato3"),
lwd = 3,
lty = c(1, 2),
bty = "n"
)
Remember:
plot()starts a graph;lines()andpoints()add to the current graph.
plot(
xgrid, y_sin,
type = "n",
ylim = c(-1.1, 1.1),
xlab = "x",
ylab = "Function value",
main = "Modern Base-R Style"
)
grid(col = "grey88")
lines(xgrid, y_sin, col = "#0072B2", lwd = 3)
lines(xgrid, y_cos, col = "#D55E00", lwd = 3, lty = 2)
abline(h = 0, col = "grey50", lty = 3)
legend(
"topright",
c("Sine", "Cosine"),
col = c("#0072B2", "#D55E00"),
lwd = 3,
lty = c(1, 2),
bty = "n"
)
type = "n" creates the plotting region without drawing
the observations. This lets us draw the grid first and then place the
curves above it.
plot(
xgrid, y_sin,
type = "n",
ylim = c(-1.1, 1.1),
xlab = "x",
ylab = "sin(x)",
main = "Transparent Shading"
)
grid(col = "grey90")
polygon(
c(xgrid, rev(xgrid)),
c(y_sin, rep(0, length(xgrid))),
col = adjustcolor("royalblue3", alpha.f = 0.20),
border = NA
)
lines(xgrid, y_sin, col = "royalblue3", lwd = 3)
abline(h = 0, col = "grey40", lty = 2)
xsmall <- seq(0, 2 * pi, length.out = 20)
plot(
xsmall, sin(xsmall),
type = "b",
pch = 19,
col = "royalblue3",
lwd = 2,
cex = 1.2,
xlab = "x",
ylab = "sin(x)",
main = "Points and Lines"
)
type = "b" means both points and lines.
par(mfrow = c(2, 2))
plot(xgrid, y_sin, type = "l", col = "#0072B2",
lwd = 3, main = "Sine")
plot(xgrid, y_cos, type = "l", col = "#D55E00",
lwd = 3, main = "Cosine")
plot(xgrid, y_sin^2, type = "l", col = "#009E73",
lwd = 3, main = expression(sin^2(x)))
plot(xgrid, y_sin * y_cos, type = "l", col = "#CC79A7",
lwd = 3, main = "sin(x) cos(x)")
par(mfrow = c(1, 1))
par(mfrow = c(2,2)) divides the plotting device into
four frames.
The general pattern is:
\[\boxed{\text{open device} \rightarrow \text{draw graph} \rightarrow \texttt{dev.off()}}\]
png(
"modern_sine_cosine.png",
width = 1800,
height = 1200,
res = 200
)
plot(xgrid, y_sin, type = "l",
col = "#0072B2", lwd = 4,
xlab = "x", ylab = "Function value")
lines(xgrid, y_cos, col = "#D55E00", lwd = 4, lty = 2)
dev.off()
For a vector PDF:
pdf("modern_sine_cosine.pdf", width = 8, height = 6)
plot(xgrid, y_sin, type = "l",
col = "#0072B2", lwd = 3)
lines(xgrid, y_cos, col = "#D55E00", lwd = 3, lty = 2)
dev.off()
This section uses the teaching files:
student_statistics_demo.csvstudent_statistics_demo.txtPlace them in the same working directory as this document.
students <- read.csv("student_statistics_demo.csv")
head(students)
tail(students)
dim(students)
nrow(students)
ncol(students)
names(students)
str(students)
summary(students)
These commands answer different questions:
| Command | Question |
|---|---|
head() |
What do the first observations look like? |
dim() |
How large is the dataset? |
names() |
What variables are available? |
str() |
What data types were imported? |
summary() |
What is the first descriptive overview? |
students_txt <- read.table(
"student_statistics_demo.txt",
header = TRUE,
sep = "\t"
)
head(students_txt)
sum(is.na(students))
colSums(is.na(students))
A basic data analysis should inspect missingness before computing final summaries.
The code in this section assumes that students has been
imported in Section 8.
mean(students$Score)
median(students$Score)
sd(students$Score)
var(students$Score)
range(students$Score)
quantile(students$Score)
These describe different aspects of a quantitative distribution:
A compact named summary:
score_summary <- c(
N = length(students$Score),
Mean = mean(students$Score),
Median = median(students$Score),
SD = sd(students$Score),
Minimum = min(students$Score),
Q1 = unname(quantile(students$Score, 0.25)),
Q3 = unname(quantile(students$Score, 0.75)),
Maximum = max(students$Score)
)
score_summary
department_frequency <- table(students$Department)
grade_frequency <- table(students$Grade)
department_frequency
grade_frequency
prop.table(department_frequency)
table() returns counts; prop.table()
converts them to proportions.
A bar plot represents categorical frequencies.
barplot(
department_frequency,
main = "Students by Department",
xlab = "Department",
ylab = "Frequency",
col = c("#0072B2", "#D55E00", "#009E73", "#CC79A7"),
border = NA,
las = 2
)
pie(
grade_frequency,
main = "Distribution of Grades",
col = rainbow(length(grade_frequency))
)
Pie charts emphasize part-to-whole composition. Bar plots are generally easier when precise category comparisons are important.
A histogram displays the distribution of a quantitative variable using numerical bins.
hist(
students$Score,
breaks = 12,
col = "#56B4E9",
border = "white",
main = "Distribution of Student Scores",
xlab = "Score",
ylab = "Frequency"
)
A histogram is fundamentally different from a bar plot: its horizontal axis represents intervals of a quantitative scale.
boxplot(
students$Score,
col = "#009E73",
main = "Student Scores",
ylab = "Score"
)
A boxplot summarizes the median, quartiles, spread, and possible outliers.
Grouped boxplots compare distributions:
boxplot(
Score ~ Department,
data = students,
col = c("#0072B2", "#D55E00", "#009E73", "#CC79A7"),
main = "Scores by Department",
xlab = "Department",
ylab = "Score",
las = 2
)
plot(
students$StudyHours,
students$Score,
pch = 19,
col = adjustcolor("#0072B2", alpha.f = 0.60),
xlab = "Study hours",
ylab = "Score",
main = "Study Hours versus Score"
)
fit <- lm(Score ~ StudyHours, data = students)
abline(fit, col = "#D55E00", lwd = 3)
summary(fit)
A scatterplot is appropriate for examining the relationship between two quantitative variables. The fitted line is included only as an introductory demonstration; regression theory belongs in a later lecture.
aggregate(Score ~ Department, data = students, FUN = mean)
aggregate(Score ~ Department, data = students, FUN = sd)
par(mfrow = c(2, 2))
barplot(department_frequency,
col = c("#0072B2", "#D55E00", "#009E73", "#CC79A7"),
border = NA, main = "Department", las = 2)
pie(grade_frequency,
col = rainbow(length(grade_frequency)),
main = "Grades")
hist(students$Score,
breaks = 12, col = "#56B4E9", border = "white",
main = "Scores", xlab = "Score")
boxplot(students$Score,
col = "#009E73", main = "Scores", ylab = "Score")
par(mfrow = c(1, 1))
A data frame is a rectangular statistical structure:
\[\boxed{\text{rows = observations},\qquad \text{columns = variables}.}\]
class(students)
students$Score
students[1, ]
students[1:5, c("ID", "Department", "Score")]
high_scores <- students[students$Score >= 85, ]
head(high_scores)
nrow(high_scores)
The expression students$Score >= 85 produces a
logical vector, which is used to retain matching rows.
ifelse()students$Result <- ifelse(
students$Score >= 50,
"Pass",
"Fail"
)
students$Performance <- ifelse(
students$Score >= 80,
"High",
"Regular"
)
table(students$Result)
table(students$Performance)
ifelse() is vectorized: it applies the condition to
every observation.
students$BMI <- students$Weight_kg /
(students$Height_cm / 100)^2
summary(students$BMI)
This demonstrates how new variables are derived from existing columns.
students_sorted <- students[
order(students$Score, decreasing = TRUE),
]
students_sorted[
1:5,
c("ID", "Department", "Score")
]
write.csv(
students,
"student_statistics_demo_processed.csv",
row.names = FALSE
)
write.csv(
high_scores,
"high_scoring_students.csv",
row.names = FALSE
)
row.names = FALSE avoids writing R’s internal row labels
as an unwanted extra variable.
Once the dataset has been imported, ask students to solve these without introducing any new commands.
mean(students$Score)
sd(students$Score)
table(students$Department)
boxplot(
Score ~ Department,
data = students,
col = c("#0072B2", "#D55E00", "#009E73", "#CC79A7"),
main = "Score by Department",
las = 2
)
excellent_students <- students[students$Score >= 90, ]
head(excellent_students)
write.csv(
excellent_students,
"excellent_students.csv",
row.names = FALSE
)
The important lesson is not a list of commands. It is a workflow.
\[\boxed{ \text{Create/Read} \rightarrow \text{Inspect} \rightarrow \text{Manipulate} \rightarrow \text{Summarize} \rightarrow \text{Visualize} \rightarrow \text{Save} }\]
Students should leave the lecture understanding these core ideas:
A second R lecture can naturally introduce:
d*, p*,
q*, and r*;ggplot2;dplyr;End of tutorial