You will be analysing data from the Scottish School Leavers Survey (SSLS), a nationally representative survey of young people. We use data from seven cohorts of young people collected in the first sweep of the study, carried out at the end of the final year of compulsory schooling (aged 16-17) when most sample members had taken Standard grades.These are subject-based examinations, typically taken in up to eight subjects. Each subject is graded on a scale from 1 (highest) to 7 (lowest). The dependent variable is a total attainment score calculated by assigning 7 points for a ‘1’, 6 for a ‘2’ and so on.
The analysis dataset contains the following five variables:
caseid: Anonymised student identifier.
score:
Point score calculated from awards in Standard grades. Scores range from
0 to 75, with a higher score indicating a higher attainment.
cohort90: The sample includes the following cohorts: 1984, 1986, 1988, 1990, 1996 and 1998. The cohort90 variable is calculated by subtracting 1990 from each value. Thus values range from -6 (corresponding to 1984) to 8 (1998), with 1990 coded as zero.
female: Sex of student (1=female, 0=male).
sclass: Social class, defined as the higher class of the mother or father (1=managerial and professional, 2=intermediate, 3=working, 4=unclassified).
There are 33,988 students in the dataset.
We will begin by looking at the relationship between attainment (score) and cohort (cohort90). Has attainment changed over time and, if so, is the trend linear?
Download the R dataset for this lesson:data1
Then open the R statistical software package.
Read the dataset into R using the read.table function and create a dataframe object named mydata:
setwd("C:/Users/grpl22/OneDrive - Durham University/homePC/teaching/MLM 2022/2- Practicals/newprac")
mydata <- read.table(file = "data1.txt", sep = ",", header = TRUE)
We specify the sep = “,” argument as the file is a comma separated variable file while we specify the header = TRUE argument as the first line of this file contains the names of the variables. If we did not specify this second argument, the variable names would be incorrectly treated as the first row of data in the dataframe.
Use the dim function to display the number of rows of data and the number of columns of variables in the dataframe:
You can also embed plots, for example:
dim(mydata)
## [1] 33988 5
and use the str function to produce a basic description of the dataframe, which includes some general information on the number of variables, along with a list of the variables and their first few values:
str(mydata)
## 'data.frame': 33988 obs. of 5 variables:
## $ caseid : int 339 340 345 346 352 353 354 361 362 363 ...
## $ score : int 49 18 46 43 17 29 15 19 45 12 ...
## $ cohort90: int -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 ...
## $ female : int 0 0 0 0 0 0 0 0 0 0 ...
## $ sclass : int 2 3 4 3 3 2 3 2 3 1 ...
The dataframe contains 33,988 observations on 5 variables.
Dataframes may be displayed in matrix form. You can view subsets of the dataframe using standard matrix indexing conventions. Here we specify rows 1:20 to display only the first 20 rows of observations in the data. Note, we have not specified which columns we wish to display and so the values of all the variables are displayed:
mydata[1:20, ]
## caseid score cohort90 female sclass
## 1 339 49 -6 0 2
## 2 340 18 -6 0 3
## 3 345 46 -6 0 4
## 4 346 43 -6 0 3
## 5 352 17 -6 0 3
## 6 353 29 -6 0 2
## 7 354 15 -6 0 3
## 8 361 19 -6 0 2
## 9 362 45 -6 0 3
## 10 363 12 -6 0 1
## 11 6824 0 -4 0 1
## 12 6826 0 -4 0 3
## 13 6827 20 -4 0 2
## 14 6828 32 -4 0 1
## 15 6829 0 -4 0 2
## 16 6834 24 -4 0 3
## 17 6836 23 -4 0 2
## 18 13206 7 -2 0 3
## 19 13209 38 -2 0 3
## 20 13215 46 -2 0 1
For example, the 10th student in the data belongs to the 1984 cohort and scored 12 out of 75. This student is a boy from a managerial social class background.
Having viewed the data we will examine score and cohort90, the variables to be considered in our first regression analysis.
We will begin by obtaining a histogram and descriptive statistics for the dependent variable, score. We obtain a histogram with the hist command:
hist(mydata$score, xlim = c(0,80))
where we have referred to the score variable within the mydata dataframe as mydata$score.
The histogram should look like the above figure. The xlim argument is used to scale the x-axis from zero to 80. Apart from a peak at around zero, the distribution looks approximately normal. Remember that in a linear regression model it is the residuals that are assumed to be normal; we will check this assumption at the end of the exercise.
The summary command can be used to calculate and display a variety of univariate summary statistics for the variables in the dataset. To obtain summary statistics only for score:
summary(mydata$score)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.00 19.00 33.00 31.09 45.00 75.00
The standard deviation of score can also be obtained with the sd command
sd(mydata$score)
## [1] 17.31437
We see that score has a mean of 31.09, a standard deviation of 17.31 and can range between a minimum and maximum value of 0 and 75.
Because cohort90 contains only six distinct values, we will look at its distribution in a frequency table rather than graphically. The table command produces one-way (and two-way) tables of frequency counts. We use this command to tabulate cohort90 and we store the table as a new object mytable.
mytable <- table(mydata$cohort90)
mytable
##
## -6 -4 -2 0 6 8
## 6478 6325 5245 4371 4244 7325
The number of observations in each category from -6 (year 1984) to 8 (year 1998) are shown. To obtain the row percentages we use the prop.table command
prop.table(mytable)
##
## -6 -4 -2 0 6 8
## 0.1905967 0.1860951 0.1543192 0.1286042 0.1248676 0.2155172
For example, 12.86% of students in our dataset belong to the 1990 cohort (coded zero). The largest proportion of students is from the 1998 cohort, with somewhat smaller proportions from 1990 and 1996.
Finally we can also calculate the cumulative percentages by combining the previous prop.table command with the cumsum command:
cumsum(prop.table(mytable))
## -6 -4 -2 0 6 8
## 0.1905967 0.3766918 0.5310109 0.6596152 0.7844828 1.0000000
These three pieces of output can easily be combined into a new matrix using the cbind command which can combine the columns of vectors, matrices and dataframes. We do this below, calling the new matrix mytablecomb
mytablecomb <- cbind(mytable, prop.table(mytable), cumsum(prop.table(mytable)))
mytablecomb
## mytable
## -6 6478 0.1905967 0.1905967
## -4 6325 0.1860951 0.3766918
## -2 5245 0.1543192 0.5310109
## 0 4371 0.1286042 0.6596152
## 6 4244 0.1248676 0.7844828
## 8 7325 0.2155172 1.0000000
Finally we add to the matrix the column headings “Freq”, “Perc” and “Cum”
colnames(mytablecomb) <- c("Freq", "Perc", "Cum")
mytablecomb
## Freq Perc Cum
## -6 6478 0.1905967 0.1905967
## -4 6325 0.1860951 0.3766918
## -2 5245 0.1543192 0.5310109
## 0 4371 0.1286042 0.6596152
## 6 4244 0.1248676 0.7844828
## 8 7325 0.2155172 1.0000000
Before fitting a linear regression model with attainment and cohort, we use the plot command to examine the nature of their relationship using a scatterplot. The ylim argument is used to scale the y-axis from zero to 80.
plot(mydata$cohort90, mydata$score, ylim = c(0,80))
Although there is some suggestion of a positive linear trend, it is difficult to see the relationship because of the small number of distinct values of cohort90. We will therefore supplement the scatterplot with a table of the mean attainment score for each value of cohort90.
To tabulate the mean of score for each value of cohort90, we first need to use a series of commands to construct the table and then to store the table in a new object called tablescore.
l <- tapply(mydata$score, factor(mydata$cohort90), length)
m <- tapply(mydata$score, factor(mydata$cohort90), mean)
s <- tapply(mydata$score, factor(mydata$cohort90), sd)
tableScore <- cbind("Freq" = l, "mean(score)" = m, "sd(score)" = s)
The tapply command applies a function to each group of values given by the levels of the specified factor. Thus, the first use of the tapply command calculates the length of score for each value of cohort90, providing the number of observations for each level of cohort90. The second use of the tapply command calculates the mean value of score separately for each value of cohort90 while the third use of the tapply command calculates the standard deviation of score for each cohort.
We can view tableScore by simply typing its name into the R console
tableScore
## Freq mean(score) sd(score)
## -6 6478 23.65545 18.07995
## -4 6325 24.77265 17.37533
## -2 5245 28.52450 15.93629
## 0 4371 29.10043 15.76355
## 6 4244 39.43473 13.55147
## 8 7325 41.33065 13.00926
We can see that the mean attainment has increased over time.
The Pearson correlation coefficient for the linear relationship between score and cohort90 can be obtained with the cor command:
cor(mydata$score, mydata$cohort90)
## [1] 0.4088625
The correlation is 0.409.
If we assume that the trend in attainment is linear, we can represent the relationship between attainment and cohort by a linear regression model of the form:
SCOREi = β0 + β1COHORT90i + ei
where scorei is the attainment score for student i and cohort90i indicates their school year (centred at 1990). The difference between a student’s actual score and that predicted for their cohort is the residual ei.
β0 and β1 are the intercept and slope of the population regression line. Because cohort90 is centred around around 1990, β0 is the attainment score expected for a student taking the exams in 1990. The slope β1 is the expected change in attainment for each 1-year increase in cohort90.
In R, linear regression models are fitted with the lm command. The response variable follows the command which is then followed by a ~ then by a list of explanatory variables (excluding the constant as this is included by default).Below we choose to store the model results in a new object called fit.
fit <- lm(score ~ cohort90, data = mydata)
We then display the results using the summary command.
summary(fit)
##
## Call:
## lm(formula = score ~ cohort90, data = mydata)
##
## Residuals:
## Min 1Q Median 3Q Max
## -41.31 -11.73 0.56 12.20 46.20
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 30.72873 0.08582 358.04 <2e-16 ***
## cohort90 1.32214 0.01601 82.59 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 15.8 on 33986 degrees of freedom
## Multiple R-squared: 0.1672, Adjusted R-squared: 0.1671
## F-statistic: 6822 on 1 and 33986 DF, p-value: < 2.2e-16
A variety of output is displayed. The top portion of output provides summary statistics for the residuals. The bottom portion of output reports standard model summary statistics. However, of main interest is the table of regression coefficients which is reported in the middle half of the output. The estimated slope coefficient is given next to cohort90 while the estimated intercept is given next to (Intercept). he column labelled Std. Error gives the associated standard errors.
The fitted regression line is:
SCOˆREi = 30.73+1.32COHORT90i
The estimated intercept is 30.73, which is the attainment score predicted for a student with cohort90 = 0, i.e. a student from the 1990 cohort.
The estimated slope is 1.32, which is interpreted as the increase in attainment predicted for each successive cohort. Note that we are assuming a linear trend from 1984 to 1998, i.e. the 1.32 year-on-year increase is assumed to apply across the range.
We can make predictions about attainment for each cohort using the fitted regression line. Note, however, that we should be cautious about making predictions based on a linear model for years where there are no observations, particularly between the 1990 and 1996 cohorts. We have no evidence for a linear trend during this period.
Some examples of predictions are given in below Table, alongside the actual mean attainment scores for these cohorts. The predicted values are close to the observed values, suggesting that the linear assumption is reasonable.
cohort90 scôre Observed mean score -6 (i.e. 1984) 30.73 + (1.32 × -6) = 22.8 23.7 -2 (i.e. 1988) 30.73 + (1.32 × -2) = 28.1 28.5 8 (i.e. 1998) 30.73 + (1.32 × 8) = 41.30 41.3
We can plot the fitted regression line by calculating predicted values and plotting them against cohort90. The predict command is used to calculate the predicted values and to store them in a new object called predscore:
predscore <- predict(fit)
We use the plot command to plot the predicted values predscore against cohort90. We specify “l” for the type argument to specify that the type of graph should be a line plot:
plot(mydata$cohort90, predscore, type = "l")
The new object predscore contains the predicted score for every observation in the data. Clearly this variable contains far more information than we need to plot out a straight line, where only two end-points are required. Plotting the predicted score for every observation is computationally inefficient as it slows down the time required to plot the graph. While this is not a particular problem here, it can be a considerable problem for more complex graphs.
A more computationally efficient way to plot this graph is to simply run the command for the subset of data which gives each distinct combinations of the two variables used to plot the graph. To do this, we first need to create a new matrix object which contains the two variables of interest. Here we combine the cohort90 variable with the predscore vector.
uniquedata <- cbind(cohort90 = mydata$cohort90, predscore = predscore)
We then reduce this matrix to keep only unique records using the unique command
uniquedata <- unique(uniquedata)
We now repeat the plot command, but this time using the unique records of the two variables which are found in the uniquedata object. Note how the [,1] and [,2] syntax is used to refer to column one (cohort90) and column two (predscore) of the uniquedata matrix.
plot(uniquedata[, 1], uniquedata[, 2], xlab = "cohort", ylab = "predscore", type = "l")
The following graph will appear which is the same as the computationally less efficient one plotted above. However, this graph is drawn considerably quicker as it is based on 6 observations rather than 33988. Note that in the plot command we specified the xlab and ylab arguments in order to give labels to the x-axis and the y-axis.
The residual (unexplained) variance parameter σ2e is not directly estimated by lm, rather it is calculated post-estimation and is reported, as a standard deviation, in the lower portion of the lm output next to Residual standard error:. The variance can be calculated straightforwardly by squaring Residual standard error: to give σˆ2e = 15.82 = 249.64.
R-squared, the proportion of the total variance in score that is explained by cohort90, is also reported in the lower portion of the lm output as Multiple R-squared: 0.1672. Thus 16.7% of the variance in attainment scores is explained by differences between cohorts.
Note that R2 is equal to the squared correlation coefficient: (0.409)^2 = 0.167.
We can test the statistical significance of the linear trend using a t-test. The null hypothesis for the test is
H0: β1 = 0, i.e. the slope of the population regression line is zero.
The test statistic is calculated by dividing the estimated slope by its standard error:
t = 1.322/0.016 = 82.6
which is clearly strongly significant (much larger than 1.96, the critical value at the 5% level of significance). We therefore conclude that there is strong evidence of a linear trend.
Fortunately, lm automatically calculates t-test statistics for us and so we do not need to calculate these by hand. t-test statistics for each explanatory variable are reported in the table of regression coefficients in the column labelled t value. We also do not need to compare manually these t-test statistics to tables of critical values as the output additionally reports p-values for the significance of the coefficient of each explanatory variable. These are reported in the column labelled Pr(>|t|). Variables with p-values smaller than 0.05 are significant at the 5% level.
Before carrying out further analysis, we should check the model assumptions. In particular, given the non-normality of score (due to a high proportion of zero scores), we will check the distribution of the residuals.
We will look at two plots:
-standardised residuals vs. normal scores (a normal plot), and -standardised residuals vs. fixed part prediction (scôrei).
To calculate standardised residuals we use the rstandard command and we store these in a new object named estd:
estd <- rstandard(fit)
To produce the standardised residuals vs. normal scores plot we use the qqnorm command. We follow this command with the qqline command in order to superimpose a 45 degree line. The qqnorm command produces a normal QQ plot of the values in the variable (here estd). qqline adds a line to a normal quantile-quantile plot which passes through the first and third quartiles.
qqnorm(estd)
qqline(estd)
To produce the standardised residuals vs. fixed part prediction (scôrei) plot:
plot(predscore, estd)
If the residuals are normally distributed, then the first plot should show them lying on the 45 degree line. There seems to be some departure from linearity at the top and bottom ends; we will explore this further using a histogram.
The second plot is used to check for heteroskedasticity. If the assumption of constant residual variance (homoskedasticity) holds, the vertical spread of points should be the same for each predicted value. This assumption seems reasonable here.
To obtain a histogram of the standardised residuals:
hist(estd)
The histogram shows an approximately normal distribution and so we do not consider a transformation of score.
None of the above plots show any obvious outliers.
Crawley, MJ (2007) The R Book. John Wiley & Sons, Ltd, Chichester.
Croxford, L. and Raffe, D. (2006) “Education Markets and Social Class Inequality: A Comparison of Trends in England, Scotland and Wales”. In R. Teese (Ed.) Inequality Revisited. Berlin: Springer.