The Student Performance dataset from the UCI Machine Learning Repository provides information about students’ academic performance in two Portuguese secondary schools. The dataset includes information about students’ personal characteristics, family background, study habits, and academic performance.
The dataset contains, 395 observations and 33 variables, including students’ sex, age, parents’ education, study time, number of past class failures, absences, and grades obtained during three school periods.
For this project, I will focus mainly on the following variables, using them in my visualizations and multiple linear regression analysis:
G1: First-period grade.
G2: Second-period grade.
G3: Final grade.
studytime: Weekly study time, categorized into four levels.
failures: Number of past class failures.
sex: Student’s sex.
The dataset is available through the UCI Machine Learning Repository. I have linked the source here.
Objective
The goal of my research is to explore the relationship between students’ study habits, personal characteristics, and academic performance. I will examine how study time and previous academic performance are associated with students’ final grades. I will also compare average final grades by sex and study time through visualizations. Finally, I will use multiple linear regression to investigate how previous grades, study time, and past class failures help explain differences in students’ final academic performance.
Uploading libraries and dataset
library(readr)library(dplyr)
Attaching package: 'dplyr'
The following objects are masked from 'package:stats':
filter, lag
The following objects are masked from 'package:base':
intersect, setdiff, setequal, union
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(ggplot2)library(highcharter)
Registered S3 method overwritten by 'quantmod':
method from
as.zoo.data.frame zoo
Highcharts (www.highcharts.com) is a Highsoft software product which is
not free for commercial and Governmental use
library(plotly)
Attaching package: 'plotly'
The following object is masked from 'package:ggplot2':
last_plot
The following object is masked from 'package:stats':
filter
The following object is masked from 'package:graphics':
layout
library(RColorBrewer)library(DataExplorer)
df<-read_delim('student-mat.csv', delim =";")
Rows: 395 Columns: 33
── Column specification ────────────────────────────────────────────────────────
Delimiter: ";"
chr (17): school, sex, address, famsize, Pstatus, Mjob, Fjob, reason, guardi...
dbl (16): age, Medu, Fedu, traveltime, studytime, failures, famrel, freetime...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(df)
# A tibble: 6 × 33
school sex age address famsize Pstatus Medu Fedu Mjob Fjob reason
<chr> <chr> <dbl> <chr> <chr> <chr> <dbl> <dbl> <chr> <chr> <chr>
1 GP F 18 U GT3 A 4 4 at_home teacher course
2 GP F 17 U GT3 T 1 1 at_home other course
3 GP F 15 U LE3 T 1 1 at_home other other
4 GP F 15 U GT3 T 4 2 health servic… home
5 GP F 16 U GT3 T 3 3 other other home
6 GP M 16 U LE3 T 4 3 services other reput…
# ℹ 22 more variables: guardian <chr>, traveltime <dbl>, studytime <dbl>,
# failures <dbl>, schoolsup <chr>, famsup <chr>, paid <chr>,
# activities <chr>, nursery <chr>, higher <chr>, internet <chr>,
# romantic <chr>, famrel <dbl>, freetime <dbl>, goout <dbl>, Dalc <dbl>,
# Walc <dbl>, health <dbl>, absences <dbl>, G1 <dbl>, G2 <dbl>, G3 <dbl>
df1$studytime_new <-factor( df1$studytime_new,levels =c("< 2 hours","2-5 hours","5-10 hours","> 10 hours")) #Reordering the categories with factor() since they are not numeric
plot1<- df1 %>%ggplot(aes(studytime_new, average_G3, fill=studytime_new)) +geom_col() +scale_fill_brewer(palette ="PuRd")+geom_text(aes(label =round(average_G3, 2), hjust =0.50, size =2))+labs(x ="Studytime categories",y ="Average final grade (Out of 20)",title ="Average final grade per studytime category",caption ="Source: Machine learning repository")+theme_minimal()plot1
Call:
lm(formula = df4$G3 ~ G2 + G1 + studytime + failures, data = df4)
Residuals:
Min 1Q Median 3Q Max
-9.4132 -0.3780 0.2720 0.9855 3.5168
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -1.19907 0.42944 -2.792 0.00549 **
G2 0.97653 0.04965 19.666 < 2e-16 ***
G1 0.15034 0.05639 2.666 0.00799 **
studytime -0.19667 0.11824 -1.663 0.09706 .
failures -0.26300 0.14172 -1.856 0.06424 .
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 1.928 on 390 degrees of freedom
Multiple R-squared: 0.8246, Adjusted R-squared: 0.8228
F-statistic: 458.5 on 4 and 390 DF, p-value: < 2.2e-16
Linear regression equation:
(Note. Image generated using ChatGPT (OpenAI, 2026))
Linear regression diagnostic plots for the model
par(mfrow =c(2, 2))plot(model1)
Conclusion
Cleaning explanation
The data-cleaning process was relatively straightforward. I started by identifying the missing values (NA) in my dataset using colSums(is.na(df)). This allowed me to check whether any variables contained missing observations. Since the dataset did not contain missing values, I did not need to remove any observations.
I then selected the variables needed for my analysis, including the three grade variables (G1, G2, and G3), studytime, failures, sex, absences, health, Medu, and Fedu. I also created a new categorical variable called studytime_new by using recode() to make the study-time categories easier to understand and interpret.
Visualizations explanation and interpretation
The first plot is a bar chart titled: Average final grade per studytime category. I created this plot by grouping students according to their study-time categories and calculating the average final grade (G3) for each group. I used geom_col() to represent the average grades, with the x-axis representing the study-time categories and the y-axis representing the average final grade. I also used scale_fill_brewer() with the Set3 palette to assign different colors to the bars. The plot suggests that the more longer the studytime, the greater the average grade is.
The second plot is a bar chart titled: Average Final Grade by Sex. I created this plot by grouping students according to sex and calculating the average final grade for each group. I used highcharter to create an interactive bar chart, with the x-axis representing sex and the y-axis representing the average final grade. I also used the Set1 color palette to distinguish the two groups. The chart suggests that male students tend to have higher grades than female students.
The third plot is a boxplot titled: Distribution of Students’ Grades Across Three Periods. I created this plot using the three grade variables (G1, G2, and G3). Since these variables were originally stored in separate columns, I used pivot_longer() to reorganize the dataset into a longer format. I then used geom_boxplot() to compare the distribution of grades across the three school periods. The visualization shows that the distribution of students’ grades across the three periods is generally constant.
Linear regression model interpretation
After examining the correlation between the variables using plot_correlation(), I decided to use multiple linear regression to investigate the relationship between the final grade (G3) and four explanatory variables: G2, G1, studytime, and failures.
The regression model shows that G2 and G1 have positive relationships with the final grade, while studytime and failures have negative relationships with G3.
The coefficient for G2 is approximately 0.97653, indicating that, when the other variables remain constant, a one unit increase in the second-period grade is associated with an increase of approximately 0.98 points in the final grade. The coefficient for G1 is 0.15034, indicating that a one unit increase in the first-period grade is associated with an increase of approximately 0.15 points in the final grade, holding the other variables constant. Plus, the p-values for G2 and G1 are respectively less than 0.001 and 0.01, indicating statistically significant relationships with the final grade.
On the other hand, the coefficient for studytime is -0.19667, while the coefficient for failures is -0.26300, meaning for every one unit increase in studytime there is a decrease of approximately 0.20, and a one unit increase in failure is associated with a decrease of approximately 0.26 in the final grade . However, their p-values are respectively 0.09706 and 0.06424, which are greater than 0.05. Therefore, these two variables are not statistically significant in this model.
The R-squared value of 0.8246 means that the model explains approximately 82.46% of the variation in students’ final grades. The p-value smaller than 2.2e-16. This indicates that the regression model is statistically significant overall.
Finally, the diagnostic plots show that the residuals are not normally distributed and that some observations have unusually large errors. The spread of the residuals also look somewhat uneven, suggesting that the model does not fully meet all regression assumptions.
In sum, the results suggest that previous academic performance, especially the second-period grade, is strongly associated with students' final grades, while studytime and failures do not show statistically significant relationships with G3. However, adding them to my model still allowed me to slightly increase its R-squared value, proving that they slightly impact students' final grades.
Summary of the model:
(Note. Image generated using ChatGPT (OpenAI, 2026))
Obstacles encountered and Solutions
During my analysis, I encountered some difficulties. The first was comparing the three grade variables (G1, G2, and G3) in a single visualization. Since these variables were stored in separate columns, I needed to reorganize the dataset before creating the graph. To address this issue, I used pivot_longer() to transform the data into a longer format, allowing me to represent the three grades in the same boxplot.