Prepared by: Yuxiao Zhang
Maintained by: PUPIL Lab
Last updated: July 2026
Data note. The datasets included with this resource are small demonstration datasets provided for instructional practice. They are intended for learning R syntax, data import, data exploration, and related examples in this handout.
R is a powerful programming language designed for statistical analysis and graphical representation of data. It is used in various scientific disciplines, including psychology, education, and social sciences.
lavaan for structural equation modeling, lme4
for multilevel modeling or mixed-effects models, meta and
robumeta for meta-analysis, and ggplot2 for
advanced graphical representations.Takeaway: R is not just a tool for statistical analysis, but a gateway to making your research more reproducible, robust, and advanced.
RStudio usually has four panes:
Figure 1. RStudio interface with script editor, console, environment/history, and plots/files/packages/help panes.
The screenshot shows the four main panes learners will use most often: script editor, console, environment/history, and plots/files/packages/help.
You can use R like a calculator to run simple calculations.
2 + 3
## [1] 5
3 * 5
## [1] 15
8 / 2
## [1] 4
Variables are like named storage boxes where you can keep data. You
can assign a value to a variable using the assignment arrow
<-.
x <- 5 # Assigning the value 5 to the variable x
x
## [1] 5
Once you have assigned a value, you can use the variable in other operations.
y <- x + 3
y
## [1] 8
Vectors are like lists that can hold multiple values. You can create
a vector to store several numbers at once with the c()
function, which concatenates multiple values into a vector.
numbers <- c(1, 3, 5, 7, 9)
sum(numbers) # Total of all elements in the vector
## [1] 25
mean(numbers) # Average of all elements in the vector
## [1] 5
new_nums <- numbers - mean(numbers)
new_nums
## [1] -4 -2 0 2 4
You can access specific elements in the vector using square
brackets [].
numbers[2] # The second element in the vector
## [1] 3
numbers[1:3] # The first three elements in the vector
## [1] 1 3 5
numbers[-4] # All elements except the fourth one
## [1] 1 3 5 9
new_nums2 <- numbers[-c(3, 5)]
new_nums2
## [1] 1 3 7
You can also use conditions to select only the
elements that meet certain criteria. Suppose you want all the numbers in
numbers that are greater than 5:
numbers > 5 # R checks each element: is it greater than 5?
## [1] FALSE FALSE FALSE TRUE TRUE
numbers[numbers > 5] # Elements greater than 5
## [1] 7 9
Outcome: You should now feel comfortable navigating the RStudio environment, running basic commands, assigning variables, and using vectors.
For this handout, all code assumes that you render or run files from
inside the Part_1_Introduction_and_Data_Exploration folder.
The data files are stored in the data/ folder.
# Run getwd() interactively if you want to check the full local folder path.
basename(getwd()) # Confirms the project folder name without printing a local path
## [1] "Part_1_Introduction_and_Data_Exploration"
list.files("data")
## [1] "PISA_200.sav" "PISA_200.xlsx"
Packages in R are like add-ons or extensions. They contain additional functions that make it easier to work with specific tasks, such as reading Excel or SPSS data files.
To use these packages, you need to install them once and then load them into your R session each time you want to use the package.
# install.packages("readxl")
library(readxl) # For reading Excel (.xlsx) files
# install.packages("haven")
library(haven) # For reading SPSS (.sav) files
Once a package is loaded, you can use the functions it provides. For
example, readxl lets you read Excel files, and
haven lets you read SPSS files.
The workshop materials include both Excel and SPSS versions of a 200-case PISA example dataset.
# Excel file (.xlsx)
pisa_200_excel <- read_excel("data/PISA_200.xlsx")
# SPSS file (.sav)
data_demo <- read_sav("data/PISA_200.sav")
Use head() to view the first few rows of a dataset and
dim() to check how many rows and columns are in the
dataset.
head(data_demo)
## # A tibble: 6 × 7
## Stu_ID Gender Sch_Type Math_Overall Math_Reasoning Math_Relation
## <dbl> <dbl+lbl> <dbl+lbl> <dbl> <dbl> <dbl>
## 1 84002502 1 [Female] 3 [Public] 342. 268. 199.
## 2 84004336 1 [Female] 3 [Public] 581. 649. 560.
## 3 84001560 2 [Male] 3 [Public] 542. 532. 535.
## 4 84002616 1 [Female] 3 [Public] 462. 405. 348.
## 5 84003837 1 [Female] 3 [Public] 468. 453. 470.
## 6 84005224 1 [Female] 3 [Public] 424. 401. 415.
## # ℹ 1 more variable: Math_Quantity <dbl>
dim(data_demo)
## [1] 200 7
The formatted table below shows the first six rows of
data_demo.
| Stu_ID | Gender | Sch_Type | Math_Overall | Math_Reasoning | Math_Relation | Math_Quantity |
|---|---|---|---|---|---|---|
| 84002502 | 1 | 3 | 342.24 | 268.25 | 199.36 | 203.45 |
| 84004336 | 1 | 3 | 581.11 | 648.51 | 559.53 | 586.32 |
| 84001560 | 2 | 3 | 541.69 | 531.56 | 534.86 | 492.39 |
| 84002616 | 1 | 3 | 461.72 | 405.34 | 348.19 | 376.92 |
| 84003837 | 1 | 3 | 468.31 | 453.36 | 469.87 | 515.59 |
| 84005224 | 1 | 3 | 423.53 | 400.63 | 415.30 | 459.28 |
The table shows the main identifiers and math score variables used in the examples.
Use the $ symbol to select a particular column
or variable from your dataset.
summary(data_demo$Math_Overall)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 262.6 407.5 457.4 464.7 524.7 686.9
The summary output shows the minimum, first quartile, median, mean, third quartile, and maximum for math overall scores.
hist(
data_demo$Math_Overall,
main = "Histogram of Math Overall Scores",
xlab = "Math Overall Scores",
ylab = "Frequency",
col = "gray70",
border = "gray20"
)
Figure 2. Histogram of Math Overall scores in the PISA_200 dataset.
The histogram shows that most students in this example dataset have math overall scores in the middle of the observed range, with fewer students at very low or very high score values.
For categorical variables, use table() to create
frequency distributions.
table(data_demo$Gender)
##
## 1 2
## 98 102
table(as_factor(data_demo$Gender)) # Convert to descriptive labels
##
## Female Male
## 98 102
table(as_factor(data_demo$Sch_Type))
##
## Private independent Private Government-dependent
## 8 1
## Public
## 188
The following bar chart displays the gender counts. A bar chart makes category counts easier to compare visually and in text.
gender_counts <- table(as_factor(data_demo$Gender))
barplot(
gender_counts,
main = "Distribution of Gender",
xlab = "Gender",
ylab = "Frequency",
col = "gray65",
border = "gray20"
)
Figure 3. Bar chart of gender distribution in the PISA_200 dataset.
The gender distribution is approximately balanced, with similar counts for female and male students.
Use the pipeline operator |> to chain multiple
operations together.
data_demo$Gender |> as_factor() |> table()
##
## Female Male
## 98 102
Use data[row, col] to select specific rows and
columns.
data_demo[1:10, 1:4]
## # A tibble: 10 × 4
## Stu_ID Gender Sch_Type Math_Overall
## <dbl> <dbl+lbl> <dbl+lbl> <dbl>
## 1 84002502 1 [Female] 3 [Public] 342.
## 2 84004336 1 [Female] 3 [Public] 581.
## 3 84001560 2 [Male] 3 [Public] 542.
## 4 84002616 1 [Female] 3 [Public] 462.
## 5 84003837 1 [Female] 3 [Public] 468.
## 6 84005224 1 [Female] 3 [Public] 424.
## 7 84000675 1 [Female] 3 [Public] 423.
## 8 84002233 2 [Male] 3 [Public] 399.
## 9 84002688 1 [Female] 3 [Public] 571.
## 10 84001135 2 [Male] 3 [Public] 403.
female_score <- data_demo[data_demo$Gender == 1, c("Stu_ID", "Math_Overall")]
head(female_score)
## # A tibble: 6 × 2
## Stu_ID Math_Overall
## <dbl> <dbl>
## 1 84002502 342.
## 2 84004336 581.
## 3 84002616 462.
## 4 84003837 468.
## 5 84005224 424.
## 6 84000675 423.
If you want to save a new dataset, use a relative path so the output goes to the current project folder. The commands below are shown as examples and are not evaluated when the handout renders.
write.csv(female_score, "female_score.csv", row.names = FALSE)
write_sav(female_score, "female_score.sav")
We want to create a new dataset that will help us identify students who may need extra support in math. Please do the following:
Stu_ID, Gender, and
Math_Overall.low_math <- data_demo[data_demo$Math_Overall < 400,
c("Stu_ID", "Gender", "Math_Overall")]
dim(low_math)[1]
## [1] 47
mean(low_math$Math_Overall)
## [1] 350.8397
low_math$Gender |> as_factor() |> table()
##
## Female Male
## 26 21
In this example dataset, the low-math subset contains 47 students, the average math score is about 350.84, and the gender distribution is 26 female students and 21 male students.
Use is.na() to detect and filter missing values. Missing
values can create unexpected rows in logical subsetting if we do not
remove or account for NA values.
school_type_is_private <- data_demo$Sch_Type == 1
# This result includes rows with missing school type values.
data_demo[school_type_is_private, ]
## # A tibble: 11 × 7
## Stu_ID Gender Sch_Type Math_Overall Math_Reasoning Math_Relation
## <dbl> <dbl+lbl> <dbl+lbl> <dbl> <dbl> <dbl>
## 1 NA NA NA NA NA NA
## 2 84000262 2 [Male] 1 [Private i… 484. 544. 434.
## 3 84000571 2 [Male] 1 [Private i… 407. 423. 376.
## 4 84002019 2 [Male] 1 [Private i… 548. 594. 582.
## 5 84003371 2 [Male] 1 [Private i… 477. 457. 535.
## 6 84005456 2 [Male] 1 [Private i… 482. 487. 489.
## 7 84007901 1 [Female] 1 [Private i… 645. 564. 605.
## 8 NA NA NA NA NA NA
## 9 NA NA NA NA NA NA
## 10 84003732 1 [Female] 1 [Private i… 427. 481. 461.
## 11 84000399 1 [Female] 1 [Private i… 502. 407. 465.
## # ℹ 1 more variable: Math_Quantity <dbl>
# This version removes missing school type values from the condition.
data_demo[school_type_is_private & !is.na(school_type_is_private), ]
## # A tibble: 8 × 7
## Stu_ID Gender Sch_Type Math_Overall Math_Reasoning Math_Relation
## <dbl> <dbl+lbl> <dbl+lbl> <dbl> <dbl> <dbl>
## 1 84000262 2 [Male] 1 [Private inde… 484. 544. 434.
## 2 84000571 2 [Male] 1 [Private inde… 407. 423. 376.
## 3 84002019 2 [Male] 1 [Private inde… 548. 594. 582.
## 4 84003371 2 [Male] 1 [Private inde… 477. 457. 535.
## 5 84005456 2 [Male] 1 [Private inde… 482. 487. 489.
## 6 84007901 1 [Female] 1 [Private inde… 645. 564. 605.
## 7 84003732 1 [Female] 1 [Private inde… 427. 481. 461.
## 8 84000399 1 [Female] 1 [Private inde… 502. 407. 465.
## # ℹ 1 more variable: Math_Quantity <dbl>
Only for illustrative purposes, we remove rows with missing school type values. In practice, other approaches should be considered depending on the research question and missing-data mechanism.
data_demo <- data_demo[!is.na(data_demo$Sch_Type), ]
dim(data_demo)
## [1] 197 7
Outcome: You should now understand how to set your working directory, install and load packages, and read common types of data files into R. You should also feel comfortable viewing and exploring your data using basic R functions.
Use these resources to continue learning:
Next Step
After completing Part I, continue to Part II: Data Manipulation, Visualization, and Basic Analysis in R.