(Updated: May 27 2018 20:44)
To install packages visit John Fox’s http://tinyurl.com/York-R-Course
If necessary, update spida2:
#
# devtools::install_github('gmonette/spida2')
#
# OR:
#
# On Windows
# intall.packages('http://blackwell.math.yorku.ca/R/spida2.zip', repos=NULL, type='binary')
#
# On a Mac
# install.packages("http://blackwell.math.yorku.ca/R/spida2.tgz", repos=NULL, type="binary")
#
library(spida2)
library(p3d)
Loading required package: rgl
Attaching package: 'rgl'
The following object is masked from 'package:spida2':
%>%
Attaching package: 'p3d'
The following objects are masked from 'package:spida2':
cell, center, ConjComp, dell, disp, ell, ell.conj, ellbox,
ellplus, ellpt, ellptc, ellpts, ellptsc, elltan, elltanc,
na.include, uv
library(car)
Loading required package: carData
Attaching package: 'car'
The following object is masked from 'package:p3d':
Identify3d
library(effects)
lattice theme set by effectsTheme()
See ?effectsTheme for details.
library(nlme)
Attaching package: 'nlme'
The following object is masked from 'package:spida2':
getData
library(lattice)
library(latticeExtra)
Loading required package: RColorBrewer
At first, it’s easier to learn by following a textbook example where nothing goes wrong. However, when you analyze real data lots of things tend to go wrong – or, more positively, many interesting things happen – and you wish you had experience with more realistic examples. These examples try to do both.
The first example is a gentle analysis in which we use the basic ideas of multilevel models with mixed models. We learn how to formulate and test general linear hypotheses that allow us to ask a much wider range of questions than those accessible through standard output.
The second example, using a different model with the same data, illustrates what to do when ‘everything’ goes wrong, oops, I meant “when a lot of interesting things happen.”
In the second example, the first model does not converge and we have to figure out what to do. We need to test random effects variance parameters that are not tested validly with the usual methods and we learn how to use simulation for this purpose.
Our analysis of BLUPS reveals problems with the model whose correction we consider.
#
The second sample analysis is quite long but it is amply annotated so that you can follow much of it on your own.
Math Achievement and SES in a sample of Public and Catholic high schools
Goal: Study the relationship between SES and Mathach in Public vs Catholic schools
?hsfull
starting httpd help server ... done
dim( hsfull )
[1] 7185 9
head(hsfull)
school mathach ses Sex Minority Size Sector PRACAD DISCLIM
1 1224 5.876 -1.528 Female No 842 Public 0.35 1.597
2 1224 19.708 -0.588 Female No 842 Public 0.35 1.597
3 1224 20.349 -0.528 Male No 842 Public 0.35 1.597
4 1224 8.781 -0.668 Male No 842 Public 0.35 1.597
5 1224 17.898 -0.158 Male No 842 Public 0.35 1.597
6 1224 4.583 0.022 Male No 842 Public 0.35 1.597
hsfull %>% head # same as head(hsfull)
school mathach ses Sex Minority Size Sector PRACAD DISCLIM
1 1224 5.876 -1.528 Female No 842 Public 0.35 1.597
2 1224 19.708 -0.588 Female No 842 Public 0.35 1.597
3 1224 20.349 -0.528 Male No 842 Public 0.35 1.597
4 1224 8.781 -0.668 Male No 842 Public 0.35 1.597
5 1224 17.898 -0.158 Male No 842 Public 0.35 1.597
6 1224 4.583 0.022 Male No 842 Public 0.35 1.597
hsfull %>% some # a random selection of rows
school mathach ses Sex Minority Size Sector PRACAD DISCLIM
285 1436 9.561 0.212 Male No 185 Catholic 1.00 -1.141
1148 2526 9.843 0.762 Female No 572 Catholic 0.97 -2.154
1425 2658 16.587 0.512 Female No 780 Catholic 0.79 -0.961
1496 2768 12.812 0.092 Male No 1680 Public 0.31 0.086
2255 3533 10.121 -0.418 Female No 1015 Catholic 0.54 0.022
3019 4292 7.128 -1.148 Male Yes 1328 Catholic 0.76 -0.674
3392 4530 8.898 -1.848 Female Yes 435 Catholic 0.60 -0.245
4351 6170 10.991 -0.948 Male No 2126 Public 0.25 0.432
4978 6990 4.756 1.292 Female Yes 1650 Public 0.59 0.475
5192 7276 4.027 -0.438 Female No 1630 Public 0.30 0.109
hsfull %>% tail
school mathach ses Sex Minority Size Sector PRACAD DISCLIM
7180 9586 20.967 1.612 Female Yes 262 Catholic 1 -2.416
7181 9586 20.402 1.512 Female No 262 Catholic 1 -2.416
7182 9586 14.794 -0.038 Female No 262 Catholic 1 -2.416
7183 9586 19.641 1.332 Female No 262 Catholic 1 -2.416
7184 9586 16.241 -0.008 Female No 262 Catholic 1 -2.416
7185 9586 22.733 0.792 Female No 262 Catholic 1 -2.416
A ‘uniform quantile’ plot. Data is lined up from shortest to tallest.
xqplot( hsfull ) # gives an idea of distribution, outliers, granularity, etc. at a glance
Figure: A uniform quantile plot. Data are lined up from shortest to tallest uniformly spaced on the x-axis. If a variable is close to uniform, its quantile plot will be close to a diagonal.
Think of a classroom photo with kids lined up from shortest to tallest
Solid line at mean, dashed lines at +/- one standard deviation
Find quantiles by selecting a proportion on x-axis and read of height of graph on y-axis (use the edge of sheet of paper) e.g. .8-quantile = 80th percentile of ses is ~ 0.8
Note: with an approximately normal distribution we expect mean -/+ std. dev. to be at approx the 32nd and 68th percentiles.
xqplot( hsfull , ptype='normal')
Figure: A normal quantile plot. Data are lined up from shortest to tallest and x-axis is stretched at the ends to have shape of ideal normal. If a variable is close to normal, its quantile plot will be close to a diagonal
Data structures, part 1: moving up one level
‘hsfull’ has one student per row.
It is a long file combining variables of all levels. If we want information on schools, we can create a data set that has one row per school, keeping only those variable that are invariant within schools, i.e. Level 2 variables
hsup <- up( hsfull, ~ school ) # one row per school with Level 2 variables
some( hsup )
school Size Sector PRACAD DISCLIM
3013 3013 760 Public 0.56 -0.213
3351 3351 1125 Public 0.35 0.383
3498 3498 388 Catholic 0.92 -1.556
4350 4350 1661 Public 0.62 0.512
4931 4931 603 Catholic 0.79 -1.172
5762 5762 1826 Public 0.24 0.364
7232 7232 1154 Public 0.20 0.975
7919 7919 1451 Public 0.50 -0.402
8775 8775 667 Public 0.28 1.783
9021 9021 472 Catholic 0.82 -0.817
dim( hsup )
[1] 160 5
tab(hsup, ~ Sector)
Sector
Catholic Public Total
70 90 160
gicc: levels of variables in hsfull
Generalized intraclass correlation:
intraclass correlation (variance between / total variance) of ranks for numerical variables
Goodman-Kruskal’s tau for factor and character variables
Interpretation:
gicc( hsfull, ~ school, type = 'raw' ) # uses variance between / total variance of numerical vars
school mathach ses Sex Minority Size Sector
1.0000000 0.1955041 0.2749462 0.2869238 0.4644279 1.0000000 1.0000000
PRACAD DISCLIM
1.0000000 1.0000000
gicc( hsfull, ~ school ) # uses ranks by default
school mathach ses Sex Minority Size Sector
1.0000000 0.1955041 0.2749462 0.2869238 0.4644279 1.0000000 1.0000000
PRACAD DISCLIM
1.0000000 1.0000000
tab_( hsfull, ~ school ) # school size: 'tab_' is a variant of 'tab' with no totals
school
1224 1288 1296 1308 1317 1358 1374 1433 1436 1461 1462 1477 1499 1637 1906
47 25 48 20 48 30 28 35 44 33 57 62 53 27 53
1909 1942 1946 2030 2208 2277 2305 2336 2458 2467 2526 2626 2629 2639 2651
28 29 39 47 60 61 67 47 57 52 57 38 57 42 38
2655 2658 2755 2768 2771 2818 2917 2990 2995 3013 3020 3039 3088 3152 3332
52 45 47 25 55 42 43 48 46 53 59 21 39 52 38
3351 3377 3427 3498 3499 3533 3610 3657 3688 3705 3716 3838 3881 3967 3992
39 45 49 53 38 48 64 51 43 45 41 54 41 52 53
3999 4042 4173 4223 4253 4292 4325 4350 4383 4410 4420 4458 4511 4523 4530
46 64 44 45 58 65 53 33 25 41 32 48 58 47 63
4642 4868 4931 5192 5404 5619 5640 5650 5667 5720 5761 5762 5783 5815 5819
61 34 58 28 57 66 57 45 61 53 52 37 29 25 50
5838 5937 6074 6089 6144 6170 6291 6366 6397 6415 6443 6464 6469 6484 6578
31 29 56 33 43 21 35 58 60 54 30 29 57 35 56
6600 6808 6816 6897 6990 7011 7101 7172 7232 7276 7332 7341 7342 7345 7364
56 44 55 49 53 33 28 44 52 53 48 51 58 56 44
7635 7688 7697 7734 7890 7919 8009 8150 8165 8175 8188 8193 8202 8357 8367
51 54 32 22 51 37 47 44 49 33 30 43 35 27 14
8477 8531 8627 8628 8707 8775 8800 8854 8857 8874 8946 8983 9021 9104 9158
37 41 53 61 48 48 32 32 64 36 58 51 56 55 53
9198 9225 9292 9340 9347 9359 9397 9508 9550 9586
31 36 19 29 57 53 47 35 29 59
tab_( hsfull, ~ school ) %>% c %>% tab # distribution of school sizes
14 19 20 21 22 25 27 28 29 30 31 32
1 1 1 2 1 4 2 4 6 3 2 4
33 34 35 36 37 38 39 41 42 43 44 45
5 1 5 2 3 4 3 4 2 4 6 5
46 47 48 49 50 51 52 53 54 55 56 57
2 7 8 3 1 5 6 12 3 3 5 8
58 59 60 61 62 63 64 65 66 67 Total
6 2 2 4 1 1 3 1 1 1 160
tab_( hsfull, ~ school ) %>% c %>% tab_ %>% plot
tab_( hsfull, ~ school ) %>% as.vector %>% tab_ %>% plot
Level-2 variables
hsup <- up( hsfull, ~ school ) # 'up' one level in the multilevel hierarchy
To include summaries of selected Level-1 variables.
For factors, this produces the mean of the incidence matrix within each school, i.e. the proportion of students in each category of the factor.
For each factor, these proportions must sum to 1.
hsfull %>%
up( ~ school, agg = ~ Sex + ses ) %>%
head
school Size Sector PRACAD DISCLIM Sex_Female Sex_Male ses
1224 1224 842 Public 0.35 1.597 0.5957447 0.4042553 -0.43438298
1288 1288 1855 Public 0.27 0.174 0.4400000 0.5600000 0.12160000
1296 1296 1719 Public 0.32 -0.137 0.6458333 0.3541667 -0.42550000
1308 1308 716 Catholic 0.96 -0.622 0.0000000 1.0000000 0.52800000
1317 1317 455 Catholic 0.95 -1.694 1.0000000 0.0000000 0.34533333
1358 1358 1430 Public 0.25 1.535 0.3666667 0.6333333 -0.01966667
xqplot( hsup )
To speed up calculations, we will use a (pre-)randomly selected quarter of the schools.
However, since it’s a good idea to know that it’s easy to select a random subset of schools, not students, here’s how it’s done.
We can’t randomly sample from the long file if we want each cluster (school) to be either all in or all out.
There are a few ways of selecting all the observations from a sample of clusters. The following seems fairly intuitive.
We first create the school level file and take a sample of school from that file. We then merge the sample file with the longfile. Merge will match with variables that have the same name. By default, it only uses records that match in both files, so it produces the result we want.
hsup <- up ( hsfull, ~ school) # as above
dim(hsup) # one row per school
[1] 160 5
We’ll use a randomly selected subset of half the schools
This has another interesting consequence: we can do a split-half analysis in which the model developed on one half of the clusters is validated with the other half
The following code shows how to split the clusters into two halves although we’ll be using two ‘preselected’ halves
How to select a random sample of 5 numbers from the numbers 1 to 10:
sample(1:10, 5)
[1] 5 9 7 3 6
Select a sample of half the row indices:
selected_rows <- sample( 1:nrow(hsup), round( nrow(hsup)/2 ))
selected_rows
[1] 113 47 36 59 126 138 145 28 57 46 129 62 60 19 90 38 150
[18] 31 149 106 30 61 54 102 91 6 32 157 139 67 68 9 140 11
[35] 82 72 27 128 78 87 56 13 85 58 124 101 121 155 97 119 100
[52] 49 79 103 39 99 84 76 40 156 75 88 123 69 33 4 80 125
[69] 111 8 66 152 77 110 55 18 53 3 71 122
hsu1 <- hsup[selected_rows, ] # first half of schools
hsu2 <- hsup[ - selected_rows, ] # other half
hs1 <- merge( hsu1, hsfull ) # long data for 1st half of schools
hs2 <- merge( hsu2, hsfull ) # long data for other half
dim(hs1)
[1] 3687 9
dim(hs2)
[1] 3498 9
length(unique(hs1$school))
[1] 80
length(unique(hs2$school))
[1] 80
But we will use an even smaller preselected subset of 40 schools
data(hs)
dd <- hs # easier to type repeatedly
dim( dd )
[1] 1977 9
some(dd)
school mathach ses Sex Minority Size Sector PRACAD DISCLIM
58 1906 14.171 0.602 Male No 400 Catholic 0.87 -0.939
268 2629 14.691 -0.768 Male No 1314 Catholic 0.81 -0.613
341 2639 6.166 -0.888 Female Yes 2713 Public 0.14 -0.282
557 3610 13.942 0.602 Male No 1431 Catholic 0.80 -0.621
915 5640 23.048 0.632 Male No 1152 Public 0.41 0.256
916 5640 18.066 -0.038 Female No 1152 Public 0.41 0.256
926 5640 18.225 0.332 Male No 1152 Public 0.41 0.256
987 5650 20.907 -0.768 Female Yes 720 Catholic 0.60 -0.070
1734 8627 13.946 -0.018 Male No 2452 Public 0.25 0.742
1811 8707 8.432 -0.238 Male Yes 1133 Public 0.48 1.542
summary( dd )
school mathach ses Sex
Min. :1317 Min. :-2.832 Min. :-2.49800 Female:1074
1st Qu.:3013 1st Qu.: 7.529 1st Qu.:-0.55800 Male : 903
Median :5650 Median :13.095 Median :-0.02800
Mean :5507 Mean :12.783 Mean :-0.02684
3rd Qu.:7345 3rd Qu.:18.336 3rd Qu.: 0.52200
Max. :9586 Max. :24.993 Max. : 1.65200
Minority Size Sector PRACAD
No :1433 Min. : 215 Catholic:1144 Min. :0.0500
Yes: 544 1st Qu.: 545 Public : 833 1st Qu.:0.3200
Median :1114 Median :0.5800
Mean :1106 Mean :0.5513
3rd Qu.:1415 3rd Qu.:0.7600
Max. :2713 Max. :1.0000
DISCLIM
Min. :-2.4160
1st Qu.:-0.9390
Median :-0.2820
Mean :-0.3022
3rd Qu.: 0.3360
Max. : 1.7420
xqplot( dd )
library(Hmisc, pos = 1000) # to avoid masking functions in other packages
Loading required package: survival
Loading required package: Formula
Loading required package: ggplot2
Attaching package: 'ggplot2'
The following object is masked from 'package:latticeExtra':
layer
The following object is masked from 'package:spida2':
labs
Attaching package: 'Hmisc'
The following object is masked _by_ 'package:p3d':
na.include
The following objects are masked _by_ 'package:spida2':
fillin, Lag, na.include
The following objects are masked from 'package:base':
format.pval, units
describe(dd)
dd
9 Variables 1977 Observations
---------------------------------------------------------------------------
school
n missing distinct Info Mean Gmd .05 .10
1977 0 40 0.999 5507 2695 1906 2458
.25 .50 .75 .90 .95
3013 5650 7345 8707 8874
lowest : 1317 1906 2208 2458 2626, highest: 8707 8854 8874 9550 9586
---------------------------------------------------------------------------
mathach
n missing distinct Info Mean Gmd .05 .10
1977 0 1860 1 12.78 7.863 1.363 3.369
.25 .50 .75 .90 .95
7.529 13.095 18.336 21.892 23.220
lowest : -2.832 -2.721 -2.658 -2.557 -2.550, highest: 24.488 24.545 24.707 24.812 24.993
---------------------------------------------------------------------------
ses
n missing distinct Info Mean Gmd .05 .10
1977 0 323 1 -0.02684 0.8577 -1.270 -0.998
.25 .50 .75 .90 .95
-0.558 -0.028 0.522 0.952 1.202
lowest : -2.498 -2.188 -2.178 -2.128 -2.108, highest: 1.452 1.462 1.512 1.612 1.652
---------------------------------------------------------------------------
Sex
n missing distinct
1977 0 2
Value Female Male
Frequency 1074 903
Proportion 0.543 0.457
---------------------------------------------------------------------------
Minority
n missing distinct
1977 0 2
Value No Yes
Frequency 1433 544
Proportion 0.725 0.275
---------------------------------------------------------------------------
Size
n missing distinct Info Mean Gmd .05 .10
1977 0 40 0.999 1106 714.7 262 311
.25 .50 .75 .90 .95
545 1114 1415 2142 2452
lowest : 215 262 280 311 381, highest: 2142 2190 2452 2650 2713
---------------------------------------------------------------------------
Sector
n missing distinct
1977 0 2
Value Catholic Public
Frequency 1144 833
Proportion 0.579 0.421
---------------------------------------------------------------------------
PRACAD
n missing distinct Info Mean Gmd .05 .10
1977 0 34 0.999 0.5513 0.2903 0.180 0.200
.25 .50 .75 .90 .95
0.320 0.580 0.760 0.878 0.950
lowest : 0.05 0.14 0.18 0.19 0.20, highest: 0.81 0.87 0.89 0.95 1.00
---------------------------------------------------------------------------
DISCLIM
n missing distinct Info Mean Gmd .05 .10
1977 0 40 0.999 -0.3022 1.054 -1.872 -1.534
.25 .50 .75 .90 .95
-0.939 -0.282 0.336 0.975 1.048
lowest : -2.416 -1.872 -1.694 -1.534 -1.484, highest: 0.975 1.013 1.048 1.542 1.742
---------------------------------------------------------------------------
QUESTIONS: looking at univariate distributions for this data:
Any problems with: NAs? Highly skewed distributions? Possible univariate outliers? Skewed factors: i.e. very rare category
Any actions to take?
library(car)
scatterplotMatrix( dd , ellipse = TRUE) # 2-dim summary
QUESTIONS: looking at bivariate relationships
Hint: focus on one column or one row at a time
Bivariate relationships that stand out?
Bivariate outliers?
Curvilinear bivariate relationships?
Which pair has the 'strongest' bivariate relationship?
ddu <- up( dd, ~ school) # only variables invariant within schools
dim(ddu)
[1] 40 5
some(ddu)
school Size Sector PRACAD DISCLIM
2658 2658 780 Catholic 0.79 -0.961
2771 2771 415 Public 0.24 1.048
5650 5650 720 Catholic 0.60 -0.070
5761 5761 215 Catholic 0.63 -0.892
5762 5762 1826 Public 0.24 0.364
6074 6074 2051 Catholic 0.32 -1.018
7232 7232 1154 Public 0.20 0.975
7342 7342 1220 Catholic 0.46 0.380
8854 8854 745 Public 0.18 -0.228
9586 9586 262 Catholic 1.00 -2.416
xqplot( ddu ) # shows just variables invariant within schools
scatterplotMatrix( ddu , ell = T) # between school variables
Include derived Level 2 summaries of Level 1 variables
up(dd, ~school) %>% head
school Size Sector PRACAD DISCLIM
1317 1317 455 Catholic 0.95 -1.694
1906 1906 400 Catholic 0.87 -0.939
2208 2208 1061 Catholic 0.68 -0.864
2458 2458 545 Catholic 0.89 -1.484
2626 2626 2142 Public 0.40 0.142
2629 2629 1314 Catholic 0.81 -0.613
ddall <- up (dd, ~school, all = T)
head(ddall)
school mathach ses Sex Minority Size Sector PRACAD
1317 1317 13.17769 0.34533333 Female Yes 455 Catholic 0.95
1906 1906 15.98317 0.51162264 Female No 400 Catholic 0.87
2208 2208 15.40467 0.42316667 Female No 1061 Catholic 0.68
2458 2458 13.98568 0.22778947 Female Yes 545 Catholic 0.89
2626 2626 13.39661 -0.06484211 Male No 2142 Public 0.40
2629 2629 14.90777 -0.13764912 Male No 1314 Catholic 0.81
DISCLIM
1317 -1.694
1906 -0.939
2208 -0.864
2458 -1.484
2626 0.142
2629 -0.613
To get distribution of proportions for factors – not just modes
ddagg <- up(dd, ~school, agg = ~ Minority + Sex)
head(ddagg)
school Size Sector PRACAD DISCLIM Minority_No Minority_Yes
1317 1317 455 Catholic 0.95 -1.694 0.2708333 0.72916667
1906 1906 400 Catholic 0.87 -0.939 0.9056604 0.09433962
2208 2208 1061 Catholic 0.68 -0.864 1.0000000 0.00000000
2458 2458 545 Catholic 0.89 -1.484 0.3859649 0.61403509
2626 2626 2142 Public 0.40 0.142 1.0000000 0.00000000
2629 2629 1314 Catholic 0.81 -0.613 0.7894737 0.21052632
Sex_Female Sex_Male
1317 1.0000000 0.0000000
1906 0.5094340 0.4905660
2208 0.5833333 0.4166667
2458 1.0000000 0.0000000
2626 0.4736842 0.5263158
2629 0.0000000 1.0000000
xqplot( ddagg )
spm( ddagg, ell = T ) # spm = scatterplotMatrix for slow typists
Warning in MASS::cov.trob(cbind(x[use], y[use]), wt = weights[use]):
Probable convergence failure
Warning in chol.default(shape, pivot = TRUE): the matrix is either rank-
deficient or indefinite
Warning in chol.default(shape, pivot = TRUE): the matrix is either rank-
deficient or indefinite
Warning in MASS::cov.trob(cbind(x[use], y[use]), wt = weights[use]):
Probable convergence failure
Warning in MASS::cov.trob(cbind(x[use], y[use]), wt = weights[use]):
Probable convergence failure
Warning in chol.default(shape, pivot = TRUE): the matrix is either rank-
deficient or indefinite
Warning in chol.default(shape, pivot = TRUE): the matrix is either rank-
deficient or indefinite
Warning in MASS::cov.trob(cbind(x[use], y[use]), wt = weights[use]):
Probable convergence failure
Warning in chol.default(shape, pivot = TRUE): the matrix is either rank-
deficient or indefinite
Warning in chol.default(shape, pivot = TRUE): the matrix is either rank-
deficient or indefinite
QUESTIONS:
An important part of modeling is being able to easily create the variables that capture the phenomena you want to study. Often, these variables are not in the raw data set.
Examples:
Sample size:
capply(y, cluster, fun)
divides ‘y’ into chunks for each value of ‘cluster’
applies the function ‘fun’ to each chunk
if ‘fun’ returns a vector of the same length as the chunk of ‘y’ then ‘capply’ returns that vector in the positions corresponding to the values of ‘y’
if ‘fun’ returns a single value, that value gets returned in every position of ‘y’
How many observations within each school?
dd$n <- capply( dd$school, dd$school, length)
head( dd ) # to see that new variable is constant in first school
school mathach ses Sex Minority Size Sector PRACAD DISCLIM n
1 1317 12.862 0.882 Female No 455 Catholic 0.95 -1.694 48
2 1317 8.961 0.932 Female Yes 455 Catholic 0.95 -1.694 48
3 1317 4.756 -0.158 Female Yes 455 Catholic 0.95 -1.694 48
4 1317 21.405 0.362 Female Yes 455 Catholic 0.95 -1.694 48
5 1317 20.748 1.372 Female No 455 Catholic 0.95 -1.694 48
6 1317 18.362 0.132 Female Yes 455 Catholic 0.95 -1.694 48
some( dd )
school mathach ses Sex Minority Size Sector PRACAD DISCLIM n
449 2771 9.589 0.302 Male No 415 Public 0.24 1.048 55
486 3013 22.377 -0.768 Male No 760 Public 0.56 -0.213 53
572 3610 19.128 -0.808 Female Yes 1431 Catholic 0.80 -0.621 64
905 5619 23.062 0.932 Male No 1118 Catholic 0.77 -1.286 66
1370 7232 6.258 -1.418 Female No 1154 Public 0.20 0.975 52
1409 7342 13.848 -0.718 Male No 1220 Catholic 0.46 0.380 58
1529 7688 19.060 0.792 Male No 1410 Catholic 0.65 -0.575 54
1812 8707 12.167 -0.858 Female Yes 1133 Public 0.48 1.542 48
1863 8874 15.577 0.652 Female Yes 2650 Public 0.20 1.742 36
1919 9586 14.076 0.852 Female No 262 Catholic 1.00 -2.416 59
dd %>%
{up(., ~school)$n} %>% # piping to an expression and using '.' for piped argument
tab
29 32 34 35 36 37 38 41 42 44 45 48
1 2 1 1 1 2 1 1 1 1 2 2
49 51 52 53 54 55 56 57 58 59 60 63
1 1 2 5 1 1 2 3 2 1 1 1
64 65 66 Total
1 1 1 40
dd %>%
{up(., ~school)$n} %>%
tab_ %>%
plot
mean ses by school (sample compositional ses)
dd$ses.m <- with( dd, capply(ses, school, mean, na.rm = TRUE ))
head( dd )
school mathach ses Sex Minority Size Sector PRACAD DISCLIM n
1 1317 12.862 0.882 Female No 455 Catholic 0.95 -1.694 48
2 1317 8.961 0.932 Female Yes 455 Catholic 0.95 -1.694 48
3 1317 4.756 -0.158 Female Yes 455 Catholic 0.95 -1.694 48
4 1317 21.405 0.362 Female Yes 455 Catholic 0.95 -1.694 48
5 1317 20.748 1.372 Female No 455 Catholic 0.95 -1.694 48
6 1317 18.362 0.132 Female Yes 455 Catholic 0.95 -1.694 48
ses.m
1 0.3453333
2 0.3453333
3 0.3453333
4 0.3453333
5 0.3453333
6 0.3453333
ses centered within school (CWG = centered with groups) This is the deviation of a student from the mean of the sample in her school
dd$ses.cwg <- dd$ses - dd$ses.m
head( dd )
school mathach ses Sex Minority Size Sector PRACAD DISCLIM n
1 1317 12.862 0.882 Female No 455 Catholic 0.95 -1.694 48
2 1317 8.961 0.932 Female Yes 455 Catholic 0.95 -1.694 48
3 1317 4.756 -0.158 Female Yes 455 Catholic 0.95 -1.694 48
4 1317 21.405 0.362 Female Yes 455 Catholic 0.95 -1.694 48
5 1317 20.748 1.372 Female No 455 Catholic 0.95 -1.694 48
6 1317 18.362 0.132 Female Yes 455 Catholic 0.95 -1.694 48
ses.m ses.cwg
1 0.3453333 0.53666667
2 0.3453333 0.58666667
3 0.3453333 -0.50333333
4 0.3453333 0.01666667
5 0.3453333 1.02666667
6 0.3453333 -0.21333333
layer <- latticeExtra::layer
xyplot(ses.cwg ~ ses.m, dd) + layer(panel.dell(...,col='red',lwd=3))
Note that ses.cwg and ses.m are uncorrelated
ses heterogeneity as measured by the IQR within schools
dd$ses.iqr <- capply( dd$ses, dd$school,
function ( x ) {
qs <- quantile(x, c(25,75)/100)
qs[2] - qs[1]
}
)
Note: There is an “IQR” function to compute the inter-quartile range but this does it from scratch to illustrate the use of an ‘anonymous’ function, i.e. a function defined on the fly.
We could have done:
dd$ses.iqr <- capply(dd$ses, dd$school, IQR, na.rm = FALSE)
but we could modify the anomymous function it to
dd$ses.trange <- capply( dd$ses, dd$school, # where 'trange' stands for
# 'truncated range'
function ( x ) {
qs <- quantile(x, c(10,90)/100, na.rm = FALSE)
qs[2] - qs[1]
}
)
consider variations like
dd$ses.sd <- capply( dd$ses, dd$school, sd, na.rm = FALSE)
some( dd )
school mathach ses Sex Minority Size Sector PRACAD DISCLIM n
663 4292 12.956 0.852 Male Yes 1328 Catholic 0.76 -0.674 65
677 4292 7.128 -1.148 Male Yes 1328 Catholic 0.76 -0.674 65
763 4530 6.295 -0.738 Female Yes 435 Catholic 0.60 -0.245 63
943 5640 4.037 -0.818 Female No 1152 Public 0.41 0.256 57
1001 5650 8.507 1.262 Female No 720 Catholic 0.60 -0.070 45
1107 5761 14.872 -0.278 Female Yes 215 Catholic 0.63 -0.892 52
1121 5762 10.810 -1.498 Male Yes 1826 Public 0.24 0.364 37
1163 6074 15.573 -0.438 Female No 2051 Catholic 0.32 -1.018 56
1820 8707 18.470 1.512 Female Yes 1133 Public 0.48 1.542 48
1905 9550 22.802 0.302 Male No 1532 Public 0.45 0.791 29
ses.m ses.cwg ses.iqr ses.trange ses.sd
663 -0.48615385 1.3381538 0.8600 1.708 0.6511382
677 -0.48615385 -0.6618462 0.8600 1.708 0.6511382
763 -0.59688889 -0.1411111 0.8850 1.544 0.6210062
943 -0.17659649 -0.6414035 0.8500 1.492 0.5830261
1001 0.02244444 1.2395556 1.0600 2.114 0.7777414
1107 -0.32300000 0.0450000 0.8000 1.917 0.7122389
1121 -1.19394595 -0.3040541 0.6500 1.272 0.5154149
1163 -0.27675000 -0.1612500 0.8175 1.440 0.6271235
1820 0.15512500 1.3568750 1.1350 2.169 0.8042577
1905 0.05303448 0.2489655 1.0200 1.824 0.7847035
head( dd )
school mathach ses Sex Minority Size Sector PRACAD DISCLIM n
1 1317 12.862 0.882 Female No 455 Catholic 0.95 -1.694 48
2 1317 8.961 0.932 Female Yes 455 Catholic 0.95 -1.694 48
3 1317 4.756 -0.158 Female Yes 455 Catholic 0.95 -1.694 48
4 1317 21.405 0.362 Female Yes 455 Catholic 0.95 -1.694 48
5 1317 20.748 1.372 Female No 455 Catholic 0.95 -1.694 48
6 1317 18.362 0.132 Female Yes 455 Catholic 0.95 -1.694 48
ses.m ses.cwg ses.iqr ses.trange ses.sd
1 0.3453333 0.53666667 0.7825 1.166 0.5561583
2 0.3453333 0.58666667 0.7825 1.166 0.5561583
3 0.3453333 -0.50333333 0.7825 1.166 0.5561583
4 0.3453333 0.01666667 0.7825 1.166 0.5561583
5 0.3453333 1.02666667 0.7825 1.166 0.5561583
6 0.3453333 -0.21333333 0.7825 1.166 0.5561583
capply will also create a Level 1 variable if the FUN returns a a vector of length greater than 1. The vector gets recycled to match the length of the argument, i.e. the number of the rows corresponding to a particular id.
The following example shows the calculation of ses rank within schools:
dd$ses.rank <- capply( dd$ses, dd$school, rank , na.last = 'keep' )
some( dd )
school mathach ses Sex Minority Size Sector PRACAD DISCLIM n
12 1317 23.355 0.502 Female No 455 Catholic 0.95 -1.694 48
62 1906 21.122 0.382 Female No 400 Catholic 0.87 -0.939 53
722 4511 20.566 -0.258 Female No 1068 Catholic 0.52 -1.872 58
946 5640 17.085 0.012 Female No 1152 Public 0.41 0.256 57
1027 5720 15.896 -0.078 Male No 381 Catholic 0.65 -0.352 53
1150 5762 2.617 -1.368 Male No 1826 Public 0.24 0.364 37
1216 6484 15.296 1.122 Male No 726 Public 0.19 0.218 35
1394 7342 23.271 -0.748 Male No 1220 Catholic 0.46 0.380 58
1479 7345 5.926 -1.048 Female No 978 Public 0.64 0.336 56
1827 8854 -2.832 -0.408 Male No 745 Public 0.18 -0.228 32
ses.m ses.cwg ses.iqr ses.trange ses.sd ses.rank
12 0.34533333 0.1566667 0.7825 1.166 0.5561583 30.0
62 0.51162264 -0.1296226 1.0100 1.586 0.6135833 24.5
722 -0.10713793 -0.1508621 0.8875 1.476 0.5813363 26.0
946 -0.17659649 0.1885965 0.8500 1.492 0.5830261 37.5
1027 0.03256604 -0.1105660 1.0100 1.730 0.6641693 23.0
1150 -1.19394595 -0.1740541 0.6500 1.272 0.5154149 15.0
1216 -0.18514286 1.3071429 0.7400 1.932 0.6958345 33.0
1394 -0.44782759 -0.3001724 0.7125 1.323 0.5648459 19.0
1479 0.03325000 -1.0812500 1.3550 1.930 0.8257296 6.0
1827 -0.75675000 0.3487500 0.8125 1.732 0.8036439 26.0
head( dd )
school mathach ses Sex Minority Size Sector PRACAD DISCLIM n
1 1317 12.862 0.882 Female No 455 Catholic 0.95 -1.694 48
2 1317 8.961 0.932 Female Yes 455 Catholic 0.95 -1.694 48
3 1317 4.756 -0.158 Female Yes 455 Catholic 0.95 -1.694 48
4 1317 21.405 0.362 Female Yes 455 Catholic 0.95 -1.694 48
5 1317 20.748 1.372 Female No 455 Catholic 0.95 -1.694 48
6 1317 18.362 0.132 Female Yes 455 Catholic 0.95 -1.694 48
ses.m ses.cwg ses.iqr ses.trange ses.sd ses.rank
1 0.3453333 0.53666667 0.7825 1.166 0.5561583 39
2 0.3453333 0.58666667 0.7825 1.166 0.5561583 40
3 0.3453333 -0.50333333 0.7825 1.166 0.5561583 6
4 0.3453333 0.01666667 0.7825 1.166 0.5561583 25
5 0.3453333 1.02666667 0.7825 1.166 0.5561583 47
6 0.3453333 -0.21333333 0.7825 1.166 0.5561583 20
Using the rank instead of the raw value could be useful for highly skewed variables where you don’t want extreme values to have too much influence
The following example shows the use of ‘capply’ to compute a value that depends on more than one variable in the data frame. If the first argument is a data frame, then ‘capply’ splits the data frame into data frames with just the rows belonging to each id. Using the ‘with’ function on these data frames allows you to write an expression as the fourth argument which becomes the second argument of with.
ses discrepancy of Minority in school (requires more than one variable)
dd$minority.diff <- capply( dd, ~ school, with,
mean( ses[ Minority == "Yes" ] , na.rm = T) -
mean( ses[ Minority == "No" ] , na.rm = T) )
Beware: this way of using ‘capply’ can be slow with very large files such as the NPHS long file.
some( dd )
school mathach ses Sex Minority Size Sector PRACAD DISCLIM n
95 1906 -1.603 0.282 Male Yes 400 Catholic 0.87 -0.939 53
141 2208 6.078 0.772 Female No 1061 Catholic 0.68 -0.864 60
200 2458 20.689 0.502 Female No 545 Catholic 0.89 -1.484 57
278 2629 13.913 0.352 Male No 1314 Catholic 0.81 -0.613 57
281 2629 19.111 -0.768 Male No 1314 Catholic 0.81 -0.613 57
1358 7232 17.001 0.332 Female No 1154 Public 0.20 0.975 52
1469 7345 16.652 0.292 Male No 978 Public 0.64 0.336 56
1509 7688 20.928 0.222 Male No 1410 Catholic 0.65 -0.575 54
1556 7688 19.567 -1.278 Male No 1410 Catholic 0.65 -0.575 54
1754 8627 16.660 -0.648 Male No 2452 Public 0.25 0.742 53
ses.m ses.cwg ses.iqr ses.trange ses.sd ses.rank
95 0.51162264 -0.22962264 1.0100 1.586 0.6135833 21.0
141 0.42316667 0.34883333 0.9825 1.505 0.5981188 38.5
200 0.22778947 0.27421053 0.7900 1.948 0.6584097 41.0
278 -0.13764912 0.48964912 1.0600 1.728 0.7063209 43.0
281 -0.13764912 -0.63035088 1.0600 1.728 0.7063209 11.5
1358 -0.09011538 0.42211538 0.7975 1.327 0.5743482 40.0
1469 0.03325000 0.25875000 1.3550 1.930 0.8257296 29.0
1509 0.18588889 0.03611111 0.8275 1.378 0.5644347 28.0
1556 0.18588889 -1.46388889 0.8275 1.378 0.5644347 1.0
1754 0.10483019 -0.75283019 0.8400 1.788 0.7077276 7.0
minority.diff
95 -0.3661667
141 NaN
200 -0.5820390
278 0.7141667
281 0.7141667
1358 -0.0107971
1469 -1.0707154
1509 -0.1387347
1556 -0.1387347
1754 -0.7930556
head( dd )
school mathach ses Sex Minority Size Sector PRACAD DISCLIM n
1 1317 12.862 0.882 Female No 455 Catholic 0.95 -1.694 48
2 1317 8.961 0.932 Female Yes 455 Catholic 0.95 -1.694 48
3 1317 4.756 -0.158 Female Yes 455 Catholic 0.95 -1.694 48
4 1317 21.405 0.362 Female Yes 455 Catholic 0.95 -1.694 48
5 1317 20.748 1.372 Female No 455 Catholic 0.95 -1.694 48
6 1317 18.362 0.132 Female Yes 455 Catholic 0.95 -1.694 48
ses.m ses.cwg ses.iqr ses.trange ses.sd ses.rank
1 0.3453333 0.53666667 0.7825 1.166 0.5561583 39
2 0.3453333 0.58666667 0.7825 1.166 0.5561583 40
3 0.3453333 -0.50333333 0.7825 1.166 0.5561583 6
4 0.3453333 0.01666667 0.7825 1.166 0.5561583 25
5 0.3453333 1.02666667 0.7825 1.166 0.5561583 47
6 0.3453333 -0.21333333 0.7825 1.166 0.5561583 20
minority.diff
1 -0.2676044
2 -0.2676044
3 -0.2676044
4 -0.2676044
5 -0.2676044
6 -0.2676044
Visualizing the school level data again
if(interactive) {
up( dd, ~ school) %>% xqplot
up( dd, ~ school) %>% spm
}
QUESTIONS:
1d - 2d -> 3d
We can see 3 continuous variable + 1 categorical variable (really 4d)
if(interactive){
library( p3d )
Init3d(family = 'serif', cex = 1.2)
dda <- up(dd, ~ school, ~ mathach + ses + Sex)
head(dda)
Plot3d ( mathach ~ ses + Sex_Female | Sector, dda)
fg()
}
Note gender composition of Catholic schools
# just for fun
if(interactive) {
fit <- lm( mathach ~ ses * Sex_Female * Sector +
I(ses*(Sector == 'Catholic')*Sex_Female^2), dda)
summary(fit)
Fit3d ( fit )
Id3d(pad=2)
}
EXERCISE:
Try other possibilities. What do you find?
Preparing for multilevel modelling:
Visualizing data at Level 1 and Level 2
From the mixed model to the hierarchical models:
With software like HLM, MlWin, you need to use separate data sets for the different levels.
With nlme and SAS PROC MIXED, we use the combined long data set with all the variables together.
We first need to separate the Levels of the model.
Level 1 model:
mathach ~ 1 + ses | school
Level 2 model:
B.0 ~ 1 + Sector
B.ses ~ 1 + Sector
~ 1 * ( 1 + Sector ) + ses * ( 1 + Sector )
Mixed model: Simplify combined model
mathach ~ 1 + ses + Sector + ses:Sector
OR (using R’s rules for generating marginal effects)
mathach ~ ses * Sector
Random effects model:
~ 1 + ses | id
– all terms usually contained in Level 1 model
Often, a simpler model is used, e.g. `~ 1 | id``
Compositional variable:
Mean ses | school i.e cvar( ses , school)
Mixed model with compositional variable:
mathach ~ 1 + ses + Sector + ses:Sector + cvar( ses, school) + ...
xyplot( mathach ~ ses | school, dd )
# Make an informative label and ordering for schools
dd$id <- factor( paste( substring( dd$Sector, 1,1), dd$school, sep =''))
some( dd )
school mathach ses Sex Minority Size Sector PRACAD DISCLIM n
147 2208 20.628 0.792 Male No 1061 Catholic 0.68 -0.864 60
362 2658 12.860 0.952 Female No 780 Catholic 0.79 -0.961 45
842 4868 9.368 -0.158 Male Yes 657 Catholic 1.00 -0.219 34
886 5619 1.701 0.472 Female No 1118 Catholic 0.77 -1.286 66
1210 6074 19.882 1.512 Female No 2051 Catholic 0.32 -1.018 56
1484 7345 9.098 -0.358 Female Yes 978 Public 0.64 0.336 56
1495 7345 23.134 0.472 Female No 978 Public 0.64 0.336 56
1584 7697 11.620 0.872 Male No 1734 Public 0.20 0.279 32
1910 9550 -1.005 -0.508 Female Yes 1532 Public 0.45 0.791 29
1958 9586 24.292 1.002 Female No 262 Catholic 1.00 -2.416 59
ses.m ses.cwg ses.iqr ses.trange ses.sd ses.rank
147 0.42316667 0.36883333 0.9825 1.505 0.5981188 41.5
362 0.43844444 0.51355556 0.9100 1.698 0.6402846 37.0
842 0.36111765 -0.51911765 1.0475 1.920 0.7100080 10.0
886 0.42033333 0.05166667 0.7550 1.405 0.5972748 31.0
1210 -0.27675000 1.78875000 0.8175 1.440 0.6271235 56.0
1484 0.03325000 -0.39125000 1.3550 1.930 0.8257296 18.0
1495 0.03325000 0.43875000 1.3550 1.930 0.8257296 35.5
1584 0.25825000 0.61375000 0.8625 1.438 0.6133712 29.0
1910 0.05303448 -0.56103448 1.0200 1.824 0.7847035 9.0
1958 0.62115254 0.38084746 0.8550 1.390 0.5949914 43.0
minority.diff id
147 NaN C2208
362 -0.18305556 C2658
842 -0.77505263 C4868
886 NaN C5619
1210 -0.32796296 C6074
1484 -1.07071545 P7345
1495 -1.07071545 P7345
1584 NaN P7697
1910 -0.56239130 P9550
1958 0.08517857 C9586
# ordering used for panels in xyplot:
dd$id <- reorder( dd$id, dd$ses) # or
dd$id.sec <- reorder( dd$id, dd$ses + 1000* (dd$Sector == "Catholic"))
Note: If you are irritated by caps in variable names use:
names(dd) <- tolower( names(dd) )
Make sure this won’t create duplicates. Variable names are case sensitive in R
gd() # sets 8 default colours for multiple groups using colour-blind friendly colours
show.settings() # first two panels show 'superpose' colours for multiple groups
gd(lwd=c(3,1), lty = c(1,1)) # if arguments have length > 1, superpose options are changed
show.settings() # first two panels show 'superpose' colours for multiple groups
gd(col = 'blue', pch = 16, alpha = 1) # options for single group
xyplot( mathach ~ ses | id.sec, dd, layout = c(7,6),
main = list('schools ordered by sector and mean ses', cex =1, font = 1),
skip = c(rep(FALSE, 19), TRUE, TRUE, rep(FALSE, 21) ),
as.table = TRUE,
panel = function( x,y ,..., type, alpha, lwd) {
panel.xyplot( x, y, ... , alpha = .4)
panel.lines( dell( x, y, radius = 1:2), type = 'l', alpha = .5,...)
panel.lmline( x, y, ..., lwd = 1.5, alpha = 1)
panel.lines( lowess( x, y),..., type = 'l')
}
)
If you like a particular panel function, you can make it yours: R is easy to customize:
mypanel = function( x,y ,..., type, alpha, lwd) {
panel.xyplot( x, y, ... , alpha = .4)
panel.lines( dell( x, y, radius = 1:2), type = 'l', alpha = .5,...)
panel.lmline( x, y, ..., lwd = 1.5, alpha = 1)
panel.lines( lowess( x, y),..., type = 'l')
}
xyplot( mathach ~ ses | id.sec, dd,
panel = mypanel)
Use ‘groups’ to have more than one group per panel
Set options for groups by specifying vectors of length > 1, short vectors get recycled
gd(col = c('red','blue'), pch = c(1,1), cex = c(.7,.7))
show.settings()
xyplot( mathach ~ ses | id.sec, dd, layout = c(7,6),
groups = Sex,
main = list('schools ordered by sector and mean ses', cex =1, font = 1),
skip = c(rep(FALSE, 19), TRUE, TRUE, rep(FALSE, 21) ),
as.table = TRUE,
panel = panel.superpose,
panel.groups = mypanel,
auto.key = list(columns = 2, lines = T)
)
Combine panels and groups to see data in different ways:
gd()
xyplot( mathach ~ ses | Sector, dd, groups = id,
panel = panel.superpose,
panel.groups = mypanel)
too much! Just data and fitted line:
mypanel2 <- function( x,y ,..., type, alpha, col) {
panel.xyplot( x, y, ... ,alpha = alpha)
panel.lmline( x, y, ..., alpha = 1, superpose.col = 'black')
}
gd(col=c('blue','blue'),alpha=c(.1,.1))
xyplot( mathach ~ ses | Sector, dd, groups = id,
panel = panel.superpose,
panel.groups = mypanel2)
fitted lines generally higher and flatter in Catholic sector
xyplot( mathach ~ ses | Sector * cut(ses.m, 5), dd, groups = id,
sub = list('subgroups with similar mean ses', font=1, cex = 1),
panel = panel.superpose,
panel.groups = mypanel2) %>%
useOuterStrips
Note:
Center point and fitted lines
mypanel3 <- function( x,y ,..., type) {
panel.xyplot( mean(x), mean(y), ... )
panel.lmline( x, y, ...)
}
gd(col=c('blue','blue'), alpha = 1, cex = 1)
xyplot( mathach ~ ses | Sector * cut( ses.m, 5), dd, groups = id,
panel = panel.superpose,
panel.groups = mypanel3) %>%
useOuterStrips
Fit an OLS model in each school
# fit.list <- lmList( mathach ~ ses | id, dd)
# may produce an error due to missing values in some variables in 'dd' that
# 'lmList' does not use. To work around this problem, you can use:
fit.list <- lmList( mathach ~ ses | id, dd[,c('mathach','ses','id')])
fit.list
Call:
Model: mathach ~ ses | id
Data: dd[, c("mathach", "ses", "id")]
Coefficients:
(Intercept) ses
P5762 3.114085 -1.01409923
P2639 6.007785 -0.63010463
P8854 5.707002 1.93884461
C4530 10.039029 1.64742597
P7890 7.998220 -0.65596791
C4292 12.786274 -0.16060800
C7342 11.619820 1.01245787
P8874 13.450957 4.09630389
P2771 13.292965 4.26818800
C5761 12.141945 3.10801055
C7172 8.355037 0.99448053
C6074 14.202264 1.52908771
P6484 13.024537 0.60567730
P5640 13.836071 3.82774230
C2629 14.938378 0.22234915
C4511 13.413589 0.04251038
P7232 12.993356 5.00160032
P2626 13.662437 4.09967961
P3013 12.780651 3.83979913
C5650 14.258257 0.68061888
C5720 14.201984 2.46630669
P7345 11.198507 4.21192377
P9550 10.882731 3.89193777
P8627 10.687731 1.86955959
C3610 14.999882 2.95584910
P8707 12.357826 3.39153230
C7688 18.400688 0.11634493
C2458 13.312180 2.95669443
P7697 14.911853 3.13621972
C1317 12.737763 1.27391282
P6897 13.846070 3.58048769
C4868 11.845609 1.28647122
C3992 14.448670 0.53787533
P8531 12.174522 3.31822792
C5619 13.206326 5.25753341
C2208 14.288928 2.63664069
C2658 12.243090 2.62990138
P7919 13.024135 3.98937031
C1906 14.885481 2.14550546
C9586 13.825077 1.67208118
Degrees of freedom: 1977 total; 1897 residual
Residual standard error: 6.106947
levels( dd$id )
[1] "P5762" "P2639" "P8854" "C4530" "P7890" "C4292" "C7342" "P8874"
[9] "P2771" "C5761" "C7172" "C6074" "P6484" "P5640" "C2629" "C4511"
[17] "P7232" "P2626" "P3013" "C5650" "C5720" "P7345" "P9550" "P8627"
[25] "C3610" "P8707" "C7688" "C2458" "P7697" "C1317" "P6897" "C4868"
[33] "C3992" "P8531" "C5619" "C2208" "C2658" "P7919" "C1906" "C9586"
beta <- expand.grid( id = levels( dd$id ))
beta <- cbind( beta, coef( fit.list))
some ( beta )
id (Intercept) ses
C7342 C7342 11.619820 1.0124579
P8874 P8874 13.450957 4.0963039
C5761 C5761 12.141945 3.1080106
C7172 C7172 8.355037 0.9944805
C2629 C2629 14.938378 0.2223492
C5720 C5720 14.201984 2.4663067
C3610 C3610 14.999882 2.9558491
C2458 C2458 13.312180 2.9566944
C1317 C1317 12.737763 1.2739128
C2658 C2658 12.243090 2.6299014
beta <- merge( beta, up(dd, ~ id))
xyplot( `(Intercept)` ~ ses | Sector * cut( ses.m, 3), beta, groups = Sector,
panel = function(x, y, ..., type){
panel.xyplot( x,y , ...)
if ( length(x) > 3)panel.lines( dell( x, y), ..., type = 'l')
},
main = 'beta space'
)
xyplot( mathach ~ ses | id, dd, layout = c(7,6), groups = Sector,
panel = mypanel, auto.key = T)
dda <- up(dd, ~ id, all = T) # means of numerical variables, modes of factors
some(dda)
school mathach ses Sex Minority Size Sector PRACAD
C7342 7342 11.166414 -0.44782759 Male No 1220 Catholic 0.46
C5761 5761 11.138058 -0.32300000 Female No 215 Catholic 0.63
C7172 7172 8.066818 -0.28981818 Female Yes 280 Catholic 0.05
P2626 2626 13.396605 -0.06484211 Male No 2142 Public 0.40
P3013 3013 12.610830 -0.04422642 Male No 760 Public 0.56
P8627 8627 10.883717 0.10483019 Male No 2452 Public 0.25
P8707 8707 12.883938 0.15512500 Female No 1133 Public 0.48
C4868 4868 12.310176 0.36111765 Male No 657 Catholic 1.00
P8531 8531 13.528683 0.40809756 Female No 2190 Public 0.58
P7919 7919 14.849973 0.45767568 Male No 1451 Public 0.50
DISCLIM n ses.m ses.cwg ses.iqr ses.trange ses.sd
C7342 0.380 58 -0.44782759 -4.974128e-19 0.7125 1.323 0.5648459
C5761 -0.892 52 -0.32300000 -5.358461e-18 0.8000 1.917 0.7122389
C7172 1.013 44 -0.28981818 -3.027573e-17 1.0275 1.686 0.6764417
P2626 0.142 38 -0.06484211 6.211337e-18 0.5975 1.384 0.5601067
P3013 -0.213 53 -0.04422642 1.112228e-17 0.7300 1.168 0.4799328
P8627 0.742 53 0.10483019 -6.563514e-18 0.8400 1.788 0.7077276
P8707 1.542 48 0.15512500 8.673617e-19 1.1350 2.169 0.8042577
C4868 -0.219 34 0.36111765 -6.555484e-18 1.0475 1.920 0.7100080
P8531 0.132 41 0.40809756 -1.152428e-17 1.0600 1.650 0.6829747
P7919 -0.402 37 0.45767568 -1.351883e-17 0.6700 1.070 0.5367005
ses.rank minority.diff id id.sec
C7342 29.5 0.1027413 C7342 C7342
C5761 26.5 0.1197576 C5761 C5761
C7172 22.5 0.3915447 C7172 C7172
P2626 19.5 NaN P2626 P2626
P3013 27.0 -0.6765385 P3013 P3013
P8627 27.0 -0.7930556 P8627 P8627
P8707 24.5 -0.5873333 P8707 P8707
C4868 17.5 -0.7750526 C4868 C4868
P8531 21.0 -0.4734848 P8531 P8531
P7919 19.0 0.3127778 P7919 P7919
xyplot( mathach ~ ses, dda , groups = Sector,
panel = mypanel, auto.key = T)
Seeing both together:
dda$id <- 'summary'
gd(pch='.', cex =2)
xyplot( mathach ~ ses | id, rbind(dd,dda), layout = c(7,6),
groups = Sector,
panel = mypanel, auto.key = T)
EXERCISE:
Try groups = Sex or Minority and try ’| id.sec
Do these plots reveal anything useful?
Question to investigate:
mathach ~ ses in differenct Sectors
Level 1 model:
mathach ~ 1 + ses
Level 1 + 2:
mathach ~ 1 + ses + Sector + Sector:ses
Full random model:
~ 1 + ses | id
dd$id <- factor(dd$school)
fit <- lme( mathach ~ ses * Sector, dd, random = ~ 1 + ses | id)
If this fails to converge, increase the number of iteration
fit <- lme( mathach ~ ses * Sector, dd, random = ~ 1 + ses | id,
control = list(msMaxIter=200, msVerbose=T))
0: 11105.191: 1.22722 2.27855 -3.36583
1: 11105.172: 1.23706 3.08949 -3.39049
2: 11105.171: 1.19748 3.08072 -3.39188
3: 11105.161: 1.21737 3.07678 -3.39235
4: 11105.096: 1.22151 2.70896 -3.41493
5: 11105.089: 1.22011 2.64468 -3.43555
6: 11105.085: 1.22389 2.55034 -3.50479
7: 11105.083: 1.22385 2.57106 -3.54636
8: 11105.076: 1.22534 2.60016 -3.72983
9: 11105.057: 1.23381 2.66214 -4.47027
10: 11105.033: 1.25234 2.72319 -5.87758
11: 11105.019: 1.27696 2.75593 -7.28573
12: 11105.013: 1.30246 2.78019 -8.50888
13: 11105.011: 1.31965 2.79844 -9.36213
14: 11105.011: 1.33116 2.81675 -9.93866
15: 11105.011: 1.33714 2.82776 -10.3398
16: 11105.011: 1.33993 2.83733 -10.5086
17: 11105.011: 1.33984 2.84079 -10.5414
18: 11105.011: 1.33908 2.84283 -10.5393
19: 11105.011: 1.33903 2.84278 -10.5371
print(fit)
Linear mixed-effects model fit by REML
Data: dd
Log-restricted-likelihood: -6419.695
Fixed: mathach ~ ses * Sector
(Intercept) ses SectorPublic ses:SectorPublic
13.479564 1.805347 -1.599373 1.388797
Random effects:
Formula: ~1 + ses | id
Structure: General positive-definite, Log-Cholesky parametrization
StdDev Corr
(Intercept) 1.8816109 (Intr)
ses 0.3564559 0.523
Residual 6.1180179
Number of Observations: 1977
Number of Groups: 40
Things we can do with a model
summary( fit )
Linear mixed-effects model fit by REML
Data: dd
AIC BIC logLik
12855.39 12900.09 -6419.695
Random effects:
Formula: ~1 + ses | id
Structure: General positive-definite, Log-Cholesky parametrization
StdDev Corr
(Intercept) 1.8816109 (Intr)
ses 0.3564559 0.523
Residual 6.1180179
Fixed effects: mathach ~ ses * Sector
Value Std.Error DF t-value p-value
(Intercept) 13.479564 0.4502225 1935 29.939782 0.0000
ses 1.805347 0.2905759 1935 6.212995 0.0000
SectorPublic -1.599373 0.6628232 38 -2.412971 0.0208
ses:SectorPublic 1.388797 0.4359721 1935 3.185518 0.0015
Correlation:
(Intr) ses SctrPb
ses 0.094
SectorPublic -0.679 -0.064
ses:SectorPublic -0.063 -0.667 0.163
Standardized Within-Group Residuals:
Min Q1 Med Q3 Max
-3.06175080 -0.74559046 0.03508812 0.78804832 2.73786882
Number of Observations: 1977
Number of Groups: 40
Sequential tests
anova(fit)
numDF denDF F-value p-value
(Intercept) 1 1935 1376.8723 <.0001
ses 1 1935 129.5570 <.0001
Sector 1 38 8.8271 0.0051
ses:Sector 1 1935 10.1475 0.0015
Type II tests
Anova(fit)
Analysis of Deviance Table (Type II tests)
Response: mathach
Chisq Df Pr(>Chisq)
ses 125.0347 1 < 2.2e-16 ***
Sector 8.8271 1 0.002968 **
ses:Sector 10.1475 1 0.001445 **
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Overall test for a variable including all its interactions
wald(fit, 'ses') # overall test for 'ses'
numDF denDF F.value p.value
ses 2 1935 67.59113 <.00001
Coefficients Estimate Std.Error DF t-value p-value Lower 0.95
ses 1.805347 0.290576 1935 6.212995 <.00001 1.235472
ses:SectorPublic 1.388797 0.435972 1935 3.185518 0.00147 0.533773
Coefficients Upper 0.95
ses 2.375222
ses:SectorPublic 2.243822
wald(fit, 'Sector') # overall test for 'Sector'
numDF denDF F.value p.value
Sector 2 38 9.487323 0.00045
Coefficients Estimate Std.Error DF t-value p-value Lower 0.95
SectorPublic -1.599373 0.662823 38 -2.412971 0.02075 -2.941188
ses:SectorPublic 1.388797 0.435972 1935 3.185518 0.00147 0.533773
Coefficients Upper 0.95
SectorPublic -0.257558
ses:SectorPublic 2.243822
Using car::lht
fixef(fit)
(Intercept) ses SectorPublic ses:SectorPublic
13.479564 1.805347 -1.599373 1.388797
coefs <- names(fixef(fit))
coefs
[1] "(Intercept)" "ses" "SectorPublic"
[4] "ses:SectorPublic"
lht(fit, coefs[c(2,4)])
Linear hypothesis test
Hypothesis:
ses = 0
ses:SectorPublic = 0
Model 1: restricted model
Model 2: mathach ~ ses * Sector
Df Chisq Pr(>Chisq)
1
2 2 135.18 < 2.2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
lht(fit, coefs[c(3,4)])
Linear hypothesis test
Hypothesis:
SectorPublic = 0
ses:SectorPublic = 0
Model 1: restricted model
Model 2: mathach ~ ses * Sector
Df Chisq Pr(>Chisq)
1
2 2 18.975 7.581e-05 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Note that this model tacitly assumes that the between-school effect of ses is the same as the within-school effect of ses
This is a big topic but note that deleting rows with NAs does not delete entire clusters, only units within clusters. This is appropriate under a wider range of assumptions than deleting clusters – as one would have to do with a repeated measures analysis. More on this in Lab 4.R
Syntax
fit <- lme( mathach ~ ses * Sector, dd, random = ~ 1 + ses | id,
na.action = na.exclude,
control = list(msMaxIter=200, msVerbose=T))
0: 11105.191: 1.22722 2.27855 -3.36583
1: 11105.172: 1.23706 3.08949 -3.39049
2: 11105.171: 1.19748 3.08072 -3.39188
3: 11105.161: 1.21737 3.07678 -3.39235
4: 11105.096: 1.22151 2.70896 -3.41493
5: 11105.089: 1.22011 2.64468 -3.43555
6: 11105.085: 1.22389 2.55034 -3.50479
7: 11105.083: 1.22385 2.57106 -3.54636
8: 11105.076: 1.22534 2.60016 -3.72983
9: 11105.057: 1.23381 2.66214 -4.47027
10: 11105.033: 1.25234 2.72319 -5.87758
11: 11105.019: 1.27696 2.75593 -7.28573
12: 11105.013: 1.30246 2.78019 -8.50888
13: 11105.011: 1.31965 2.79844 -9.36213
14: 11105.011: 1.33116 2.81675 -9.93866
15: 11105.011: 1.33714 2.82776 -10.3398
16: 11105.011: 1.33993 2.83733 -10.5086
17: 11105.011: 1.33984 2.84079 -10.5414
18: 11105.011: 1.33908 2.84283 -10.5393
19: 11105.011: 1.33903 2.84278 -10.5371
Note: generally ‘na.exclude’ is preferable to ‘na.omit’ It allows residuals and predicted values to match the original data frame, not just the observations that were not missing.
In passing: What to do if the model does not converge
We will revisit this when we need it:
Summary:
?lmeControl
lmeControl
fit <- lme( mathach ~ ses * Sector, dd, random = ~ 1 + ses | id,
control = list( maxIter = 200, msMaxIter = 200, niterEM = 100,
msVerbose = TRUE , returnObject = TRUE ))
summary( fit )
If singular convergence, the frequent cause is that the random model is more complex than the cluster size or the variability between clusters can support. I.e. the variability from cluster to cluster that you are trying to account for with the random part of the model is actually accounted for by the ‘epsilons’, the level 1 or within-cluster variability. Consequently a variance is estimated to be 0 or the G matris is non-singular, i.e. it’s a flat pancake.
As a rule of thumb, if the random effects model has K terms, you need, for a balanced within cluster design, a good deal more than K clusters with at least K observations and some with more than K observations. Otherwise, the G matrix and the within-cluster variance may not be identifiable.
Also, the G matrix might be relatively flat because of scaling OR centering. For example, one dimension is so much more stretched than another.
Try:
Rescale variables so they have similar variability or similar variability in slopes.
Global center x’s in RE model (not necessarily at the mean but that’s a good first try. The ‘ideal’ place is the point of minimum variance of the response. Plot OLS lines to get an idea.
Also try centering within clusters (CWC or CWG). The RE model is not equivalent but it could often be better depending on the process. Use AIC to compare.,
If nothing works, start with simple RE model and add components using a manual forward stepwise approach. Use ‘anova’ and ‘simulate’ to test additional components. If a model does not converge, use …, control = (…., returnObject = TRUE, msVerbose = TRUE) to judge whether the likelihood ratio converges even though the parameters don’t. In this case, anova can give a usable p-value that can be adjusted with ‘simulate’.
`fit.big <- lme( ...., control = list( maxIter = 200,
` msMaxIter = 200, niterEM = 100,
` msVerbose = TRUE , returnObject = TRUE ))`
`anova ( fit.simple, fit.big )`
`zsim <- simulate( fit.simple, nsim = 1000, m2 = fit.big)`
`plot( zsim )`
to test whether ‘fit.simple’ is adequate.
Handling non-convergence is discussed in greater detail in Lab Session 3
First diagnostics: Hausman Test
After fitting a model: diagnostics
Model with contextual variable for ses, i.e. each school’s mean ses: Note: we can use the cvar function for convenience or we can create a variable in the data frame, i.e. ses.m above. We will use cvar because it’s always easy to use even if the variable has not been defined.
Moreover, cvar works correctly with factors generating mean values of indicators for each level of the factor omitting the reference level. We will illustrate this later.
fitc <- lme( mathach ~ (ses + cvar(ses,id))* Sector, dd,
random = ~ 1 + ses | id )
summary( fitc )
Linear mixed-effects model fit by REML
Data: dd
AIC BIC logLik
12839.6 12895.47 -6409.801
Random effects:
Formula: ~1 + ses | id
Structure: General positive-definite, Log-Cholesky parametrization
StdDev Corr
(Intercept) 1.4561519 (Intr)
ses 0.5190639 -0.217
Residual 6.1140010
Fixed effects: mathach ~ (ses + cvar(ses, id)) * Sector
Value Std.Error DF t-value p-value
(Intercept) 13.356053 0.3753744 1935 35.58062 0.0000
ses 1.699397 0.3095543 1935 5.48982 0.0000
cvar(ses, id) 2.407785 1.0629298 36 2.26523 0.0296
SectorPublic -0.934345 0.5628780 36 -1.65994 0.1056
ses:SectorPublic 1.173092 0.4677016 1935 2.50821 0.0122
cvar(ses, id):SectorPublic 1.414896 1.4652174 36 0.96566 0.3407
Correlation:
(Intr) ses cv(,i) SctrPb ss:ScP
ses -0.071
cvar(ses, id) -0.186 -0.248
SectorPublic -0.667 0.047 0.124
ses:SectorPublic 0.047 -0.662 0.164 -0.065
cvar(ses, id):SectorPublic 0.135 0.180 -0.725 0.056 -0.284
Standardized Within-Group Residuals:
Min Q1 Med Q3 Max
-3.09882513 -0.75336271 0.03194452 0.78173508 2.79671295
Number of Observations: 1977
Number of Groups: 40
wald( fitc )
numDF denDF F.value p.value
6 36 407.3664 <.00001
Coefficients Estimate Std.Error DF t-value p-value
(Intercept) 13.356053 0.375374 1935 35.580620 <.00001
ses 1.699397 0.309554 1935 5.489821 <.00001
cvar(ses, id) 2.407785 1.062930 36 2.265235 0.02961
SectorPublic -0.934345 0.562878 36 -1.659942 0.10561
ses:SectorPublic 1.173092 0.467702 1935 2.508206 0.01222
cvar(ses, id):SectorPublic 1.414896 1.465217 36 0.965656 0.34066
Coefficients Lower 0.95 Upper 0.95
(Intercept) 12.619872 14.092234
ses 1.092302 2.306492
cvar(ses, id) 0.252064 4.563507
SectorPublic -2.075914 0.207225
ses:SectorPublic 0.255840 2.090344
cvar(ses, id):SectorPublic -1.556703 4.386494
wald( fitc, -1) # overall test of FE model
numDF denDF F.value p.value
1 5 36 33.98978 <.00001
Estimate Std.Error DF t-value p-value
ses 1.699397 0.309554 1935 5.489821 <.00001
cvar(ses, id) 2.407785 1.062930 36 2.265235 0.02961
SectorPublic -0.934345 0.562878 36 -1.659942 0.10561
ses:SectorPublic 1.173092 0.467702 1935 2.508206 0.01222
cvar(ses, id):SectorPublic 1.414896 1.465217 36 0.965656 0.34066
Lower 0.95 Upper 0.95
ses 1.092302 2.306492
cvar(ses, id) 0.252064 4.563507
SectorPublic -2.075914 0.207225
ses:SectorPublic 0.255840 2.090344
cvar(ses, id):SectorPublic -1.556703 4.386494
wald( fitc, 'cvar')
numDF denDF F.value p.value
cvar 2 36 9.749657 0.00041
Coefficients Estimate Std.Error DF t-value p-value
cvar(ses, id) 2.407785 1.062930 36 2.265235 0.02961
cvar(ses, id):SectorPublic 1.414896 1.465217 36 0.965656 0.34066
Coefficients Lower 0.95 Upper 0.95
cvar(ses, id) 0.252064 4.563507
cvar(ses, id):SectorPublic -1.556703 4.386494
wald( fitc, 'ses') # overall test of all 'ses' effects
numDF denDF F.value p.value
ses 4 36 39.23784 <.00001
Coefficients Estimate Std.Error DF t-value p-value
ses 1.699397 0.309554 1935 5.489821 <.00001
cvar(ses, id) 2.407785 1.062930 36 2.265235 0.02961
ses:SectorPublic 1.173092 0.467702 1935 2.508206 0.01222
cvar(ses, id):SectorPublic 1.414896 1.465217 36 0.965656 0.34066
Coefficients Lower 0.95 Upper 0.95
ses 1.092302 2.306492
cvar(ses, id) 0.252064 4.563507
ses:SectorPublic 0.255840 2.090344
cvar(ses, id):SectorPublic -1.556703 4.386494
wald( fitc, 'Sector') # overall test of all 'Sector' effects
numDF denDF F.value p.value
Sector 3 36 3.939573 0.01576
Coefficients Estimate Std.Error DF t-value p-value
SectorPublic -0.934345 0.562878 36 -1.659942 0.10561
ses:SectorPublic 1.173092 0.467702 1935 2.508206 0.01222
cvar(ses, id):SectorPublic 1.414896 1.465217 36 0.965656 0.34066
Coefficients Lower 0.95 Upper 0.95
SectorPublic -2.075914 0.207225
ses:SectorPublic 0.255840 2.090344
cvar(ses, id):SectorPublic -1.556703 4.386494
wald( fitc, ':') # overall test of all interaction effects
numDF denDF F.value p.value
: 2 36 4.675875 0.01566
Coefficients Estimate Std.Error DF t-value p-value
ses:SectorPublic 1.173092 0.467702 1935 2.508206 0.01222
cvar(ses, id):SectorPublic 1.414896 1.465217 36 0.965656 0.34066
Coefficients Lower 0.95 Upper 0.95
ses:SectorPublic 0.255840 2.090344
cvar(ses, id):SectorPublic -1.556703 4.386494
# i.e. are lines parallel between sectors
Including cvar(ses, id) has two important consequences:
1) It unbiases the estimated effect of ses so it estimates
the WITHIN-SCHOOL effect of ses unbiased by the between
school effect of ses
2) It provides an estimate of the 'contextual effect' of ses
and of the BETWEEN-SCHOOL (= compositional) effect of ses which
is equal to the sum of the contextual and the within-effect
wald( fitc )
numDF denDF F.value p.value
6 36 407.3664 <.00001
Coefficients Estimate Std.Error DF t-value p-value
(Intercept) 13.356053 0.375374 1935 35.580620 <.00001
ses 1.699397 0.309554 1935 5.489821 <.00001
cvar(ses, id) 2.407785 1.062930 36 2.265235 0.02961
SectorPublic -0.934345 0.562878 36 -1.659942 0.10561
ses:SectorPublic 1.173092 0.467702 1935 2.508206 0.01222
cvar(ses, id):SectorPublic 1.414896 1.465217 36 0.965656 0.34066
Coefficients Lower 0.95 Upper 0.95
(Intercept) 12.619872 14.092234
ses 1.092302 2.306492
cvar(ses, id) 0.252064 4.563507
SectorPublic -2.075914 0.207225
ses:SectorPublic 0.255840 2.090344
cvar(ses, id):SectorPublic -1.556703 4.386494
wald( fitc, 'cvar')
numDF denDF F.value p.value
cvar 2 36 9.749657 0.00041
Coefficients Estimate Std.Error DF t-value p-value
cvar(ses, id) 2.407785 1.062930 36 2.265235 0.02961
cvar(ses, id):SectorPublic 1.414896 1.465217 36 0.965656 0.34066
Coefficients Lower 0.95 Upper 0.95
cvar(ses, id) 0.252064 4.563507
cvar(ses, id):SectorPublic -1.556703 4.386494
Anova(fitc)
Analysis of Deviance Table (Type II tests)
Response: mathach
Chisq Df Pr(>Chisq)
ses 90.9731 1 < 2.2e-16 ***
cvar(ses, id) 18.5668 1 1.641e-05 ***
Sector 2.4670 1 0.11626
ses:Sector 6.2911 1 0.01213 *
cvar(ses, id):Sector 0.9325 1 0.33422
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Note that although neither ‘cvar’ component in the model seems overwhelmingly significant, the joint test that both are 0 is much more significant. This is a common kind of occurence and illustrates how individual component p-values should not be used individually to judge the overall contribution of a variable.
Interpretation of the coefficients of in this model:
> wald( fitc )
numDF denDF F.value p.value
6 76 555.2492 <.00001
-- This is a test that ALL parameters (including the intercept)
are equal to 0. It is generally of no interest. The
command 'wald(fitc, -1)' tests all parameters except the
intercept and provides a test that model as a whole is
not predictive.
numDF denDF F.value p.value
6 36 407.3664 <.00001
Coefficients Estimate Std.Error DF t-value p-value
(Intercept) 13.356053 0.375374 1935 35.580620 <.00001
-- E(Y) when ses = 0 for a Catholic school
mean ses = 0
ses 1.699397 0.309554 1935 5.489821 <.00001
-- Expected difference in E(Y) associated with
a difference of 1 unit of individual ses
within a given Catholic school. Often called the
'WITHIN-SCHOOL' effect of ses.
cvar(ses, id) 2.407785 1.062930 36 2.265235 0.02961
-- Expected different in E(Y) associated with
a difference in a Catholic school mean ses of 1 unit
with individual ses constant. Often call the
'CONTEXTUAL' effect of ses.
The sum of the 'WITHIN-SCHOOL' and the 'CONTEXTUAL'
effects is the 'COMPOSITIONAL' or 'BETWEEN-SCHOOL' effect
SectorPublic -0.934345 0.562878 36 -1.659942 0.10561
-- Public intercept minus Catholic intercept
ses:SectorPublic 1.173092 0.467702 1935 2.508206 0.01222
-- coef of 'ses' in Public schools minus value in
Catholic schools
cvar(ses, id):SectorPublic 1.414896 1.465217 36 0.965656 0.34066
-- ditto for coefficient of 'cvar(ses, id)'
wald( fitc )
numDF denDF F.value p.value
6 36 407.3664 <.00001
Coefficients Estimate Std.Error DF t-value p-value
(Intercept) 13.356053 0.375374 1935 35.580620 <.00001
ses 1.699397 0.309554 1935 5.489821 <.00001
cvar(ses, id) 2.407785 1.062930 36 2.265235 0.02961
SectorPublic -0.934345 0.562878 36 -1.659942 0.10561
ses:SectorPublic 1.173092 0.467702 1935 2.508206 0.01222
cvar(ses, id):SectorPublic 1.414896 1.465217 36 0.965656 0.34066
Coefficients Lower 0.95 Upper 0.95
(Intercept) 12.619872 14.092234
ses 1.092302 2.306492
cvar(ses, id) 0.252064 4.563507
SectorPublic -2.075914 0.207225
ses:SectorPublic 0.255840 2.090344
cvar(ses, id):SectorPublic -1.556703 4.386494
All ‘possible’ effects of ses:
They’re actually ‘infinite’ consisting of ALL linear combinations of the row space of the following matrix which has dimension 4.
SO: infinite in number but finite in dimension
L <- rbind(
'Within-school effect of ses | Cath' = c(0, 1, 0, 0, 0, 0),
'Contextual effect of ses | Cath' = c(0, 0, 1, 0, 0, 0),
'Between-school effect of ses | Cath' = c(0, 1, 1, 0, 0, 0),
'Within-school effect of ses | Pub' = c(0, 1, 0, 0, 1, 0),
'Contextual effect of ses | Pub' = c(0, 0, 1, 0, 0, 1),
'Between-school effect of ses | Pub' = c(0, 1, 1, 0, 1, 1),
'Within-school effect of ses | Pub - Cath' = c(0, 0, 0, 0, 1, 0),
'Contextual effect of ses | Pub - Cath' = c(0, 0, 0, 0, 0, 1),
'Between-school effect of ses | Pub - Cath' = c(0, 0, 0, 0, 1, 1))
wald( fitc, L)
numDF denDF F.value p.value
1 4 36 39.23784 <.00001
Estimate Std.Error DF
Within-school effect of ses | Cath 1.699397 0.309554 1935
Contextual effect of ses | Cath 2.407785 1.062930 36
Between-school effect of ses | Cath 4.107183 1.030638 36
Within-school effect of ses | Pub 2.872489 0.350601 1935
Contextual effect of ses | Pub 3.822681 1.008485 36
Between-school effect of ses | Pub 6.695170 0.956243 36
Within-school effect of ses | Pub - Cath 1.173092 0.467702 1935
Contextual effect of ses | Pub - Cath 1.414896 1.465217 36
Between-school effect of ses | Pub - Cath 2.587987 1.405921 36
t-value p-value Lower 0.95
Within-school effect of ses | Cath 5.489821 <.00001 1.092302
Contextual effect of ses | Cath 2.265235 0.02961 0.252064
Between-school effect of ses | Cath 3.985090 0.00031 2.016953
Within-school effect of ses | Pub 8.193048 <.00001 2.184894
Contextual effect of ses | Pub 3.790518 0.00055 1.777378
Between-school effect of ses | Pub 7.001537 <.00001 4.755820
Within-school effect of ses | Pub - Cath 2.508206 0.01222 0.255840
Contextual effect of ses | Pub - Cath 0.965656 0.34066 -1.556703
Between-school effect of ses | Pub - Cath 1.840777 0.07391 -0.263353
Upper 0.95
Within-school effect of ses | Cath 2.306492
Contextual effect of ses | Cath 4.563507
Between-school effect of ses | Cath 6.197413
Within-school effect of ses | Pub 3.560084
Contextual effect of ses | Pub 5.867984
Between-school effect of ses | Pub 8.634521
Within-school effect of ses | Pub - Cath 2.090344
Contextual effect of ses | Pub - Cath 4.386494
Between-school effect of ses | Pub - Cath 5.439328
We can ask ‘subquestions’ with multiple dfs
Question: Is there evidence of an effect of ses in Catholic schools?
wald(fitc, list('ses in Catholic schools' = L[1:3,]))
numDF denDF F.value p.value
ses in Catholic schools 2 36 22.08524 <.00001
Estimate Std.Error DF t-value
Within-school effect of ses | Cath 1.699397 0.309554 1935 5.489821
Contextual effect of ses | Cath 2.407785 1.062930 36 2.265235
Between-school effect of ses | Cath 4.107183 1.030638 36 3.985090
p-value Lower 0.95 Upper 0.95
Within-school effect of ses | Cath <.00001 1.092302 2.306492
Contextual effect of ses | Cath 0.02961 0.252064 4.563507
Between-school effect of ses | Cath 0.00031 2.016953 6.197413
Question: Is there evidence of an effect of ses in Public schools?
wald(fitc, list('ses in Public schools' = L[4:6,]))
numDF denDF F.value p.value
ses in Public schools 2 36 56.39045 <.00001
Estimate Std.Error DF t-value
Within-school effect of ses | Pub 2.872489 0.350601 1935 8.193048
Contextual effect of ses | Pub 3.822681 1.008485 36 3.790518
Between-school effect of ses | Pub 6.695170 0.956243 36 7.001537
p-value Lower 0.95 Upper 0.95
Within-school effect of ses | Pub <.00001 2.184894 3.560084
Contextual effect of ses | Pub 0.00055 1.777378 5.867984
Between-school effect of ses | Pub <.00001 4.755820 8.634521
Question: Is there evidence of a difference between Public and Catholic?
wald(fitc, list('Difference between effect of ses between Sectors' = L[7:9,]))
numDF denDF F.value
Difference between effect of ses between Sectors 2 36 4.675875
p.value
Difference between effect of ses between Sectors 0.01566
Estimate Std.Error DF
Within-school effect of ses | Pub - Cath 1.173092 0.467702 1935
Contextual effect of ses | Pub - Cath 1.414896 1.465217 36
Between-school effect of ses | Pub - Cath 2.587987 1.405921 36
t-value p-value Lower 0.95
Within-school effect of ses | Pub - Cath 2.508206 0.01222 0.255840
Contextual effect of ses | Pub - Cath 0.965656 0.34066 -1.556703
Between-school effect of ses | Pub - Cath 1.840777 0.07391 -0.263353
Upper 0.95
Within-school effect of ses | Pub - Cath 2.090344
Contextual effect of ses | Pub - Cath 4.386494
Between-school effect of ses | Pub - Cath 5.439328
or
wald(fitc, ':')
numDF denDF F.value p.value
: 2 36 4.675875 0.01566
Coefficients Estimate Std.Error DF t-value p-value
ses:SectorPublic 1.173092 0.467702 1935 2.508206 0.01222
cvar(ses, id):SectorPublic 1.414896 1.465217 36 0.965656 0.34066
Coefficients Lower 0.95 Upper 0.95
ses:SectorPublic 0.255840 2.090344
cvar(ses, id):SectorPublic -1.556703 4.386494
Note: that the estimated different in contextual effect is larger than the difference in within effect, yet the p-values are ‘reversed’.
This illustrates the fact that there is generally more information – i.e. smaller SEs – for within effects than for contextual effects – although not always.
Although the interaction in not significant, there is much less power to detect between-school effects than within-school effects and depending on the purposes of the analysis it might not be reasonable to assume that the slopes are equal just because the p-value does not provide evidence that they are not.
QUESTIONS:
How would you test whether there is ANY difference between the two sectors – not just in the effect of SES.
Why does the second to last F test have 2 numDFs although there are three lines in the L matrix?
The effects
package, provides an excellent way of visualizing the effects of terms in a model. If a model has
the table of estimated coefficients and their standard errors give a very incomplete picture of the model.
Effect plots as implemented in the ‘effects’ package let you see the effect of categorical variables at all levels and the conditional effect of interacting variables at various levels of the other variables with which they interact. They convey a great deal more than the numerical summary of estimates and standard deviations and they should be used routinely.
There is one caveat in using effect plots with mixed models in that they may, when looking at effects of level-1 variables, give the impression that an effect is less important than it actually is. The effect plot shows error bands around the predicted value of the response. With mixed models, the there could be a large standard error around predicted values but a relatively small standard error around the difference of two predicted values.
Thus, testing specific hypotheses with linear hypothesis matrices or constructing graphical estimates of quantities other than predicted values can play a role in the analysis and reporting of results.
library(effects)
We need to refit with a computed variable for cvar(ses, id)
dd$ses.cvar <- with(dd, cvar(ses, id))
fitc2 <- update(fitc, . ~ (ses + ses.cvar) * Sector)
Range of predicted values given levels of interacting variables
predictorEffects(fitc2, 'ses')
ses predictor effect
ses*Sector effect
Sector
ses Catholic Public
-2 9.892629 6.574122
-1 11.592026 9.446611
-0.4 12.611665 11.170105
0.6 14.311062 14.042594
2 16.690219 18.064079
predictorEffects(fitc2, 'ses.cvar')
ses.cvar predictor effect
ses.cvar*Sector effect
Sector
ses.cvar Catholic Public
-1 10.90265 8.521924
-0.7 11.62499 9.668729
-0.3 12.58810 11.197801
0.2 13.79200 13.109142
0.6 14.75511 14.638214
predictorEffects(fitc2, 'Sector')
Sector predictor effect
Sector*ses*ses.cvar effect
, , ses.cvar = -1
ses
Sector -2 -1 -0.4 0.6 2
Catholic 7.549473 9.248870 10.268509 11.96791 14.34706
Public 2.854048 5.726538 7.450031 10.32252 14.34401
, , ses.cvar = -0.7
ses
Sector -2 -1 -0.4 0.6 2
Catholic 8.271808 9.971206 10.990844 12.69024 15.06940
Public 4.000853 6.873342 8.596835 11.46932 15.49081
, , ses.cvar = -0.3
ses
Sector -2 -1 -0.4 0.6 2
Catholic 9.234922 10.934320 11.95396 13.65336 16.03251
Public 5.529925 8.402414 10.12591 12.99840 17.01988
, , ses.cvar = 0.2
ses
Sector -2 -1 -0.4 0.6 2
Catholic 10.438815 12.13821 13.15785 14.85725 17.23640
Public 7.441266 10.31375 12.03725 14.90974 18.93122
, , ses.cvar = 0.6
ses
Sector -2 -1 -0.4 0.6 2
Catholic 11.401929 13.10133 14.12097 15.82036 18.19952
Public 8.970338 11.84283 13.56632 16.43881 20.46029
Plot:
predictorEffects(fitc2, 'ses') %>% plot
predictorEffects(fitc2, 'ses.cvar') %>% plot
predictorEffects(fitc2, 'Sector') %>% plot
We create a data frame with predictor values for which we want to visualize the predicted values for the model.
Since the data have two levels, we create a data set with school characteristics and deviations of students within schools
pred <- expand.grid(ses.mean = c(-1,0,1),
Sector = levels(dd$Sector), # this ensure that the factor is created
ses.dev = seq(-1,1,.1)) # properly
pred$ses <- with(pred, ses.mean + ses.dev)
pred$m.ses <- with(pred, paste0(Sector," mean ses: ", ses.mean))
some(pred)
ses.mean Sector ses.dev ses m.ses
2 0 Catholic -1.0 -1.0 Catholic mean ses: 0
17 0 Public -0.8 -0.8 Public mean ses: 0
30 1 Public -0.6 0.4 Public mean ses: 1
31 -1 Catholic -0.5 -1.5 Catholic mean ses: -1
32 0 Catholic -0.5 -0.5 Catholic mean ses: 0
42 1 Public -0.4 0.6 Public mean ses: 1
67 -1 Catholic 0.1 -0.9 Catholic mean ses: -1
78 1 Public 0.2 1.2 Public mean ses: 1
92 0 Catholic 0.5 0.5 Catholic mean ses: 0
107 0 Public 0.7 0.7 Public mean ses: 0
We can now use the wald function to generate predicted values and standard errors
The ‘getX’ function generates the design matrix over the data frame pred
Note that it is possible to use the ‘predict’ method but it does not provide standard errors
round( head( getX(fitc, pred)),2)
Error in capply.default(x, id, mean, na.rm = na.rm, ...): object 'id' not found
ww <- as.data.frame( wald( fitc, getX(fitc, pred)) )
Error in capply.default(x, id, mean, na.rm = na.rm, ...): object 'id' not found
head(ww)
Error in head(ww): object 'ww' not found
xyplot( coef ~ ses | m.ses, ww, type = 'l')
Error in eval(substitute(groups), data, environment(x)): object 'ww' not found
gd(6, lwd = 2) # select colours
xyplot( coef ~ ses | Sector, ww, groups = m.ses, type = 'l')
Error in eval(substitute(groups), data, environment(x)): object 'ww' not found
xyplot( coef ~ ses | Sector, ww, groups = m.ses,
auto.key = list(columns = 2, lines = T, points = F), type = 'l')
Error in eval(substitute(groups), data, environment(x)): object 'ww' not found
xyplot( coef ~ ses , ww, groups = m.ses,
auto.key = list(columns = 2, lines = T, points = F), type = 'l')
Error in eval(substitute(groups), data, environment(x)): object 'ww' not found
gd(col=rep(c('red','blue'),each = 3), lty = rep(1:3,2), lwd = 2)
xyplot( coef ~ ses , ww, groups = m.ses,
ylab = 'predicted math achievement',
auto.key = list(columns = 2, lines = T, points = F), type = 'l') +
layer(panel.abline(v=c(-1,0,1), lty = 2))
Error in eval(substitute(groups), data, environment(x)): object 'ww' not found
EXERCISE:
The data frame created by wald includes the information necessary to plot error bounds on the predicted lines
The ‘panel function’ panel.fit will produce the error bars. The easiest way to use it is with ‘layers’ provided by the ‘latticeExtra’ package
In the call to xyplot, provide arguments for the following parameters:
Then you need to add a ‘+’ at the end of the ‘xyplot’ command and follow that with:
layer(panel.fit(…)) if you did not use groups, and
glayer(panel.fit(…)) if you did use groups
head(ww)
Error in head(ww): object 'ww' not found
xyplot( coef ~ ses | Sector,
ww, groups = m.ses,
ylab = 'predicted math achievement',
fit = ww$coef,
upper = ww$U2, lower = ww$L2,
subscripts = T,
sub = 'predicted mean with appx. 95% error bars (NOT prediction bars)',
auto.key = list(columns = 2, lines = T, points = F), type = 'l') +
glayer(panel.fit(...))
Error in xyplot.formula(coef ~ ses | Sector, ww, groups = m.ses, ylab = "predicted math achievement", : object 'ww' not found
xyplot( coef ~ ses | ses.mean,
ww, groups = m.ses,
ylab = 'predicted math achievement',
fit = ww$coef,
upper = ww$U2, lower = ww$L2,
subscripts = T,
layout = c(1,3),
sub = 'predicted mean with appx. 95% error bars (NOT prediction bars)',
auto.key = list(columns = 2, lines = T, points = F), type = 'l') +
glayer(panel.fit(...))
Error in xyplot.formula(coef ~ ses | ses.mean, ww, groups = m.ses, ylab = "predicted math achievement", : object 'ww' not found
xyplot( coef ~ ses | paste0('Mean school ses = ',ses.mean),
ww, groups = m.ses,
ylab = 'predicted math achievement',
fit = ww$coef,
upper = ww$U2, lower = ww$L2,
subscripts = T,
layout = c(1,3),
sub = 'predicted mean with appx. 95% error bars (NOT prediction bars)',
auto.key = list(columns = 2, lines = T, points = F), type = 'l') +
glayer(panel.fit(...))
Error in xyplot.formula(coef ~ ses | paste0("Mean school ses = ", ses.mean), : object 'ww' not found
We would like to estimate the intersector gap at the same value of ses.mean and ses.
head(ww)
Error in head(ww): object 'ww' not found
vals <- ww[, c('ses','m.ses','ses.mean','Sector')]
Error in eval(expr, envir, enclos): object 'ww' not found
Construct an L matrix to estimate the gap
This is like estimating the ‘derivative’ of each term wrt Sector
wald(fitc)
numDF denDF F.value p.value
6 36 407.3664 <.00001
Coefficients Estimate Std.Error DF t-value p-value
(Intercept) 13.356053 0.375374 1935 35.580620 <.00001
ses 1.699397 0.309554 1935 5.489821 <.00001
cvar(ses, id) 2.407785 1.062930 36 2.265235 0.02961
SectorPublic -0.934345 0.562878 36 -1.659942 0.10561
ses:SectorPublic 1.173092 0.467702 1935 2.508206 0.01222
cvar(ses, id):SectorPublic 1.414896 1.465217 36 0.965656 0.34066
Coefficients Lower 0.95 Upper 0.95
(Intercept) 12.619872 14.092234
ses 1.092302 2.306492
cvar(ses, id) 0.252064 4.563507
SectorPublic -2.075914 0.207225
ses:SectorPublic 0.255840 2.090344
cvar(ses, id):SectorPublic -1.556703 4.386494
Lgap <- with(vals,
cbind(0, 0, 0, 1, ses, ses.mean)
)
Error in with(vals, cbind(0, 0, 0, 1, ses, ses.mean)): object 'vals' not found
some(Lgap)
Error in some(Lgap): object 'Lgap' not found
ww <- as.data.frame(wald(fitc, Lgap, data = vals ))
Error in wald(fitc, Lgap, data = vals): object 'Lgap' not found
some(ww)
Error in some(ww): object 'ww' not found
gd(col = 'blue')
xyplot( coef ~ ses | paste0('Mean school ses = ',ses.mean),
ww,
ylim = c(-8,8), xlim = c(-2,2),
ylab = 'predicted math achievement',
fit = ww$coef,
upper = ww$U2, lower = ww$L2,
subscripts = T,
layout = c(1,3),
type = 'l',
sub = 'estimated difference (Public - Catholic) with appx. 95% error bars')+
layer(panel.abline(h = 0))+
layer(panel.fit(...))
Error in xyplot.formula(coef ~ ses | paste0("Mean school ses = ", ses.mean), : object 'ww' not found
xyplot( coef ~ ses | paste0('Mean school ses = ',ses.mean),
ww,
ylim = c(-8,8), xlim = c(-2,2),
ylab = 'predicted math achievement',
fit = ww$coef,
upper = ww$U2, lower = ww$L2,
subscripts = T,
layout = c(3,1),
type = 'l',
sub = 'estimated difference (Public - Catholic) with appx. 95% error bars')+
layer(panel.abline(h = 0, lwd = 2 ))+
layer(panel.fit(...))
Error in xyplot.formula(coef ~ ses | paste0("Mean school ses = ", ses.mean), : object 'ww' not found
We can combine the bands in one panel, although it isn’t very effective here.
Note the changes:
gd(3)
xyplot( coef ~ ses ,
ww,
groups = paste0('Mean school ses = ',ses.mean),
ylim = c(-8,8), xlim = c(-2,2),
ylab = 'predicted math achievement',
fit = ww$coef,
upper = ww$U2, lower = ww$L2,
subscripts = T,
type = 'l',
auto.key = T,
sub = 'estimated difference (Public - Catholic) with appx. 95% error bars')+
glayer(panel.abline(h = 0, lwd = 2 ))+
glayer(panel.fit(...))
Error in xyplot.formula(coef ~ ses, ww, groups = paste0("Mean school ses = ", : object 'ww' not found
Figure: Combining bands in one panel. Tastes may vary!
Starting with the model above, test whether Sex improves prediction in the model. Consider including a contextual variable for Sex. What is its interpretation?
Is is reasonable to use the contextual variable for Sex as the only regressor for the contextual effect of Sex or should other variables be used as well? Can you create these variables and include them in your analysis? What do they reveal?
See Visualizing the coeficients
fitcd <- update( fitc, . ~ (cvar(ses,id) + dvar(ses,id)*Sector))
summary( fitcd )
Linear mixed-effects model fit by REML
Data: dd
AIC BIC logLik
12843.4 12893.68 -6412.7
Random effects:
Formula: ~1 + ses | id
Structure: General positive-definite, Log-Cholesky parametrization
StdDev Corr
(Intercept) 1.5088268 (Intr)
ses 0.4215203 -0.202
Residual 6.1173112
Fixed effects: mathach ~ cvar(ses, id) + dvar(ses, id) + Sector + dvar(ses, id):Sector
Value Std.Error DF t-value p-value
(Intercept) 13.257428 0.3803559 1935 34.85533 0.0000
cvar(ses, id) 5.477723 0.7136326 37 7.67583 0.0000
dvar(ses, id) 1.711278 0.3023289 1935 5.66032 0.0000
SectorPublic -0.981203 0.5765720 37 -1.70179 0.0972
dvar(ses, id):SectorPublic 1.161565 0.4571695 1935 2.54078 0.0111
Correlation:
(Intr) cv(,i) dv(,i) SctrPb
cvar(ses, id) -0.140
dvar(ses, id) -0.052 0.020
SectorPublic -0.682 0.251 0.038
dvar(ses, id):SectorPublic 0.033 -0.003 -0.661 -0.052
Standardized Within-Group Residuals:
Min Q1 Med Q3 Max
-3.12476554 -0.74613310 0.02214392 0.78204903 2.76676038
Number of Observations: 1977
Number of Groups: 40
summary( fitc )
Linear mixed-effects model fit by REML
Data: dd
AIC BIC logLik
12839.6 12895.47 -6409.801
Random effects:
Formula: ~1 + ses | id
Structure: General positive-definite, Log-Cholesky parametrization
StdDev Corr
(Intercept) 1.4561519 (Intr)
ses 0.5190639 -0.217
Residual 6.1140010
Fixed effects: mathach ~ (ses + cvar(ses, id)) * Sector
Value Std.Error DF t-value p-value
(Intercept) 13.356053 0.3753744 1935 35.58062 0.0000
ses 1.699397 0.3095543 1935 5.48982 0.0000
cvar(ses, id) 2.407785 1.0629298 36 2.26523 0.0296
SectorPublic -0.934345 0.5628780 36 -1.65994 0.1056
ses:SectorPublic 1.173092 0.4677016 1935 2.50821 0.0122
cvar(ses, id):SectorPublic 1.414896 1.4652174 36 0.96566 0.3407
Correlation:
(Intr) ses cv(,i) SctrPb ss:ScP
ses -0.071
cvar(ses, id) -0.186 -0.248
SectorPublic -0.667 0.047 0.124
ses:SectorPublic 0.047 -0.662 0.164 -0.065
cvar(ses, id):SectorPublic 0.135 0.180 -0.725 0.056 -0.284
Standardized Within-Group Residuals:
Min Q1 Med Q3 Max
-3.09882513 -0.75336271 0.03194452 0.78173508 2.79671295
Number of Observations: 1977
Number of Groups: 40
wald(fitc, L)
numDF denDF F.value p.value
1 4 36 39.23784 <.00001
Estimate Std.Error DF
Within-school effect of ses | Cath 1.699397 0.309554 1935
Contextual effect of ses | Cath 2.407785 1.062930 36
Between-school effect of ses | Cath 4.107183 1.030638 36
Within-school effect of ses | Pub 2.872489 0.350601 1935
Contextual effect of ses | Pub 3.822681 1.008485 36
Between-school effect of ses | Pub 6.695170 0.956243 36
Within-school effect of ses | Pub - Cath 1.173092 0.467702 1935
Contextual effect of ses | Pub - Cath 1.414896 1.465217 36
Between-school effect of ses | Pub - Cath 2.587987 1.405921 36
t-value p-value Lower 0.95
Within-school effect of ses | Cath 5.489821 <.00001 1.092302
Contextual effect of ses | Cath 2.265235 0.02961 0.252064
Between-school effect of ses | Cath 3.985090 0.00031 2.016953
Within-school effect of ses | Pub 8.193048 <.00001 2.184894
Contextual effect of ses | Pub 3.790518 0.00055 1.777378
Between-school effect of ses | Pub 7.001537 <.00001 4.755820
Within-school effect of ses | Pub - Cath 2.508206 0.01222 0.255840
Contextual effect of ses | Pub - Cath 0.965656 0.34066 -1.556703
Between-school effect of ses | Pub - Cath 1.840777 0.07391 -0.263353
Upper 0.95
Within-school effect of ses | Cath 2.306492
Contextual effect of ses | Cath 4.563507
Between-school effect of ses | Cath 6.197413
Within-school effect of ses | Pub 3.560084
Contextual effect of ses | Pub 5.867984
Between-school effect of ses | Pub 8.634521
Within-school effect of ses | Pub - Cath 2.090344
Contextual effect of ses | Pub - Cath 4.386494
Between-school effect of ses | Pub - Cath 5.439328
QUESTIONS:
Note: replacing ses with dvar(ses,id) in the RE model does give an mathematically equivalent model but NOT a numerically equivalent model.
That is, with calculations using infinite precision the the two models would give equivalent results but with finite precision the ‘dvar(ses,id)’ model will, in general, converge more easily.
Alternative but non-equivalent RE parametrization
Using ‘dvar(ses,id)’ or raw ‘ses’ in the FE model produces an equivalent model.
This is not so in the RE model. The
‘random = ~ 1 + ses | id’ (1)
model is essentially different from the
‘random = ~ 1 + dvar(ses,id) | id’ (2)
model.
In model (1) the ‘point of minimum variance’ of school regression lines corresponds to a common value of ses for all schools.
In model (2) the ‘point of minimum variance’ of school regression lines has a consistent value relative to each school’s mean ses.
fitca <- update( fitc, random = ~ 1 + dvar( ses, id ) | id)
summary( fitca )
Linear mixed-effects model fit by REML
Data: dd
AIC BIC logLik
12839.22 12895.09 -6409.612
Random effects:
Formula: ~1 + dvar(ses, id) | id
Structure: General positive-definite, Log-Cholesky parametrization
StdDev Corr
(Intercept) 1.4625200 (Intr)
dvar(ses, id) 0.6802261 -0.071
Residual 6.1084863
Fixed effects: mathach ~ (ses + cvar(ses, id)) * Sector
Value Std.Error DF t-value p-value
(Intercept) 13.374349 0.3732778 1935 35.82948 0.0000
ses 1.695976 0.3241495 1935 5.23208 0.0000
cvar(ses, id) 2.398991 1.0734653 36 2.23481 0.0317
SectorPublic -0.939334 0.5625274 36 -1.66985 0.1036
ses:SectorPublic 1.171880 0.4888990 1935 2.39698 0.0166
cvar(ses, id):SectorPublic 1.238194 1.4581049 36 0.84918 0.4014
Correlation:
(Intr) ses cv(,i) SctrPb ss:ScP
ses -0.028
cvar(ses, id) -0.159 -0.300
SectorPublic -0.664 0.019 0.106
ses:SectorPublic 0.019 -0.663 0.199 -0.026
cvar(ses, id):SectorPublic 0.117 0.221 -0.736 0.078 -0.336
Standardized Within-Group Residuals:
Min Q1 Med Q3 Max
-3.10199875 -0.75105077 0.03298316 0.78240512 2.80416484
Number of Observations: 1977
Number of Groups: 40
summary( fitc )
Linear mixed-effects model fit by REML
Data: dd
AIC BIC logLik
12839.6 12895.47 -6409.801
Random effects:
Formula: ~1 + ses | id
Structure: General positive-definite, Log-Cholesky parametrization
StdDev Corr
(Intercept) 1.4561519 (Intr)
ses 0.5190639 -0.217
Residual 6.1140010
Fixed effects: mathach ~ (ses + cvar(ses, id)) * Sector
Value Std.Error DF t-value p-value
(Intercept) 13.356053 0.3753744 1935 35.58062 0.0000
ses 1.699397 0.3095543 1935 5.48982 0.0000
cvar(ses, id) 2.407785 1.0629298 36 2.26523 0.0296
SectorPublic -0.934345 0.5628780 36 -1.65994 0.1056
ses:SectorPublic 1.173092 0.4677016 1935 2.50821 0.0122
cvar(ses, id):SectorPublic 1.414896 1.4652174 36 0.96566 0.3407
Correlation:
(Intr) ses cv(,i) SctrPb ss:ScP
ses -0.071
cvar(ses, id) -0.186 -0.248
SectorPublic -0.667 0.047 0.124
ses:SectorPublic 0.047 -0.662 0.164 -0.065
cvar(ses, id):SectorPublic 0.135 0.180 -0.725 0.056 -0.284
Standardized Within-Group Residuals:
Min Q1 Med Q3 Max
-3.09882513 -0.75336271 0.03194452 0.78173508 2.79671295
Number of Observations: 1977
Number of Groups: 40
Which is better? It will depend on the true state of nature.
What do the data tell us?
Neither model is nested in the other – in fact they have the same DFs – so we can’t get a p-value for a test. We can compare with AIC:
anova(fitca, fitc)
Model df AIC BIC logLik
fitca 1 10 12839.22 12895.09 -6409.612
fitc 2 10 12839.60 12895.47 -6409.801
fitca looks better, barely!
More importantly, it seems to make more sense.
Fit a model with interactions between cvar(ses,id) and other variables.
Is there evidence that these interactions are non-zero in the population?
BEWARE: Do not merely look at the individual p-value Either remove effects one at a time or test effects simultaneously with ‘wald’ or anova (need to refit with method = “ML” )
What do you conclude wrt interactions?
Previous fits with REML (default)
Here we use a ‘sub-par’ model (no contextual effect) to illustrate convergence issues.
FE: ~ 1 + ses + Sector + ses:Sector
RE: ~ 1 + ses | id
Fitting with ‘REML’
Maximum likelihood optimization is done only on the RE model, i.e. the G matrix and sigma – or G and R. The FE model is then estimated using GLS. This makes optimization easier because there are fewer parameters – in this case 4:
Fit with ‘ML’
Maximum likelihood optimization done on all paramters together. Generally more numerically difficult.
Pros and Cons:
REML: better estimates of variances, good for Wald tests. But, can use Likelihood-Ratio Tests (LRT with anova) only to compare models with identical FEs and nested RE models. However, beware that LRTs for RE models are problematic near the ‘edge’ of the model, e.g.
ML: Not so good estimates of variances, not so good for Wald tests. But LRTs can compare models that are nested wrt to FEs and or REs.
REML fit (default):
fit <- lme( mathach ~ ses * Sector, dd,
random = ~ 1 + ses | id,
na.action = na.exclude,
control = list(msMaxIter=200, msVerbose=T))
0: 11105.191: 1.22722 2.27855 -3.36583
1: 11105.172: 1.23706 3.08949 -3.39049
2: 11105.171: 1.19748 3.08072 -3.39188
3: 11105.161: 1.21737 3.07678 -3.39235
4: 11105.096: 1.22151 2.70896 -3.41493
5: 11105.089: 1.22011 2.64468 -3.43555
6: 11105.085: 1.22389 2.55034 -3.50479
7: 11105.083: 1.22385 2.57106 -3.54636
8: 11105.076: 1.22534 2.60016 -3.72983
9: 11105.057: 1.23381 2.66214 -4.47027
10: 11105.033: 1.25234 2.72319 -5.87758
11: 11105.019: 1.27696 2.75593 -7.28573
12: 11105.013: 1.30246 2.78019 -8.50888
13: 11105.011: 1.31965 2.79844 -9.36213
14: 11105.011: 1.33116 2.81675 -9.93866
15: 11105.011: 1.33714 2.82776 -10.3398
16: 11105.011: 1.33993 2.83733 -10.5086
17: 11105.011: 1.33984 2.84079 -10.5414
18: 11105.011: 1.33908 2.84283 -10.5393
19: 11105.011: 1.33903 2.84278 -10.5371
ML fit:
# fit.ml <- lme( mathach ~ ses * Sector, dd,
# random = ~ 1 + ses | id,
# na.action = na.exclude,
# method = "ML",
# control = list(msMaxIter=200, msVerbose=T))
equivalent:
fit.ml <- update(fit, method = 'ML')
0: 11116.488: 1.26387 2.36435 -3.50300
1: 11116.369: 1.27189 3.26343 -3.52690
2: 11116.362: 1.25371 3.26152 -3.52738
3: 11116.357: 1.26159 3.19060 -3.54350
4: 11116.344: 1.26820 3.00294 -3.58152
5: 11116.336: 1.27032 2.81688 -3.62716
6: 11116.332: 1.26359 2.81941 -3.65385
7: 11116.330: 1.25804 2.81817 -3.71167
8: 11116.329: 1.25586 2.81904 -3.76973
9: 11116.324: 1.25192 2.81640 -4.00208
10: 11116.314: 1.24864 2.82902 -4.61237
11: 11116.304: 1.25175 2.85724 -5.22213
12: 11116.277: 1.27266 2.93789 -7.00650
13: 11116.252: 1.30765 3.05638 -9.17520
14: 11116.240: 1.34226 3.01159 -11.3467
15: 11116.229: 1.37860 3.10898 -13.5164
16: 11116.224: 1.41455 3.14663 -15.6880
17: 11116.221: 1.45113 3.15186 -17.8599
18: 11116.220: 1.48586 3.18124 -20.0316
19: 11116.219: 1.51994 3.20406 -22.2035
20: 11116.218: 1.55064 3.22386 -24.2324
21: 11116.218: 1.57972 3.24631 -26.2613
22: 11116.217: 1.60724 3.26437 -28.2902
23: 11116.217: 1.63097 3.28564 -30.0739
24: 11116.217: 1.65191 3.30750 -31.8577
25: 11116.216: 1.67476 3.34690 -34.5078
26: 11116.215: 1.68926 3.38050 -36.5249
27: 11116.215: 1.68666 3.39839 -37.2087
28: 11116.215: 1.69364 3.40859 -37.8926
29: 11116.215: 1.69991 3.41753 -38.5765
30: 11116.215: 1.70677 3.42223 -39.1666
31: 11116.215: 1.73242 3.43322 -41.1845
32: 11116.215: 1.74434 3.43038 -41.8849
33: 11116.215: 1.76468 3.43539 -43.4545
34: 11116.214: 1.78907 3.44632 -45.5732
35: 11116.214: 1.81430 3.45958 -47.9453
36: 11116.214: 1.82514 3.46566 -48.9606
37: 11116.214: 1.86235 3.48610 -52.5741
38: 11116.214: 1.85876 3.48527 -52.3932
39: 11116.214: 1.87752 3.49341 -54.1692
40: 11116.214: 1.88572 3.49664 -54.9450
41: 11116.214: 1.90242 3.50224 -56.4968
42: 11116.214: 1.91953 3.50653 -58.0486
43: 11116.214: 1.93706 3.50907 -59.6003
44: 11116.214: 1.95337 3.51374 -61.1521
45: 11116.214: 1.96914 3.51822 -62.7039
46: 11116.214: 1.98756 3.52451 -64.6271
47: 11116.214: 2.00629 3.53088 -66.5502
48: 11116.214: 2.01839 3.53556 -67.9328
49: 11116.214: 2.03148 3.53962 -69.3154
50: 11116.214: 2.04463 3.54299 -70.6979
51: 11116.214: 2.06259 3.54637 -72.5577
52: 11116.214: 2.08052 3.55034 -74.4175
53: 11116.214: 2.08561 3.55078 -74.9460
54: 11116.214: 2.10570 3.55444 -77.0600
55: 11116.214: 2.12038 3.55726 -78.6633
56: 11116.214: 2.13299 3.56012 -80.1147
57: 11116.214: 2.14597 3.56298 -81.5662
58: 11116.214: 2.15858 3.56589 -83.0177
59: 11116.214: 2.16736 3.56817 -84.0919
60: 11116.214: 2.17658 3.57007 -85.1662
61: 11116.214: 2.19976 3.57416 -87.8635
62: 11116.214: 2.21172 3.57556 -89.2594
63: 11116.214: 2.22380 3.57736 -90.6552
64: 11116.214: 2.23579 3.57908 -92.0511
65: 11116.214: 2.23585 3.57893 -92.0595
Here ML takes more iterations but used to give up with ‘singular convergence’ perhaps because the estimated RE variance is closer to singular, which, combined with the larger number of parameter, leads ‘nlminb’ to declare singular convergence: the peak is too flat.
Note: increasing msMaxIter won’t help with ‘singular convergence’
REML maximizes only over RE parameters, while ML maximizes over all parameters, hence more challenging optimization
To see the fit at the last iteration, use ‘returnObject = TRUE’ in the ‘control’ list:
# fit.ml <- lme( mathach ~ ses * Sector, dd, random = ~ 1 + ses | id,
# na.action = na.exclude,
# method = "ML",
# control = list(msMaxIter=200,
# msVerbose=T,
# returnObject = TRUE))
summary(fit.ml)
Linear mixed-effects model fit by maximum likelihood
Data: dd
AIC BIC logLik
12854.79 12899.51 -6419.397
Random effects:
Formula: ~1 + ses | id
Structure: General positive-definite, Log-Cholesky parametrization
StdDev Corr
(Intercept) 1.8032583 (Intr)
ses 0.1707538 0.932
Residual 6.1189789
Fixed effects: mathach ~ ses * Sector
Value Std.Error DF t-value p-value
(Intercept) 13.489366 0.4345233 1935 31.044061 0.0000
ses 1.815089 0.2821217 1935 6.433711 0.0000
SectorPublic -1.590550 0.6400743 38 -2.484946 0.0175
ses:SectorPublic 1.398797 0.4234030 1935 3.303700 0.0010
Correlation:
(Intr) ses SctrPb
ses 0.076
SectorPublic -0.679 -0.052
ses:SectorPublic -0.051 -0.666 0.149
Standardized Within-Group Residuals:
Min Q1 Med Q3 Max
-3.06217668 -0.74613002 0.03736602 0.78804322 2.74091602
Number of Observations: 1977
Number of Groups: 40
Shows high correlation in the RE model between b_1 and b_ses. This suggests that the ‘point of minimum variance’ is far from the observed values of ses and that the random true school regression lines seem close to parallel.
Possible remedies for non-convergence:
Alas now ‘academic’ here because the model now converges
fit.ml <- lme( mathach ~ ses * Sector + Sex, dd,
random = ~ 1 + ses + Sex | id,
na.action = na.exclude,
method = "ML",
control = list(msMaxIter=200, msVerbose=T,
sing.tol=1e-20))
0: 11109.615: 1.41833 2.41824 1.88935 -3.96692 -0.973608 -1.36731
1: 11109.477: 1.44252 2.70884 2.15340 -3.97460 -0.990959 -1.37504
2: 11109.400: 1.35156 2.71273 2.13702 -3.97739 -0.998252 -1.37783
3: 11109.376: 1.36793 2.79269 2.14010 -3.99193 -1.03470 -1.39820
4: 11109.362: 1.40683 2.80828 2.01850 -4.03354 -1.14572 -1.46064
5: 11109.320: 1.42023 2.99532 2.08100 -4.13910 -1.43142 -1.62290
6: 11109.285: 1.43114 3.10075 2.08836 -4.24835 -1.75102 -1.80401
7: 11109.185: 1.48413 3.36028 2.12353 -4.68678 -3.05622 -2.55469
8: 11109.123: 1.53966 3.48380 2.21745 -5.12894 -4.36430 -3.32730
9: 11109.076: 1.59585 3.55853 2.24802 -5.56928 -5.67298 -4.11118
10: 11109.021: 1.71368 3.55344 2.39068 -6.49460 -8.40163 -5.77104
11: 11108.954: 1.81242 3.40201 2.40801 -7.25146 -10.6234 -7.15512
12: 11108.937: 1.90548 3.27424 2.47237 -8.01006 -12.8474 -8.53624
13: 11108.932: 1.90942 3.30006 2.46637 -8.09936 -13.0958 -8.69635
14: 11108.923: 1.93768 3.37975 2.48650 -8.44823 -14.1038 -9.32179
15: 11108.912: 2.04458 3.52641 2.55840 -9.63736 -17.5463 -11.4635
16: 11108.905: 2.05586 3.54962 2.56007 -9.83938 -18.1168 -11.8326
17: 11108.899: 2.11950 3.62610 2.59167 -10.6409 -20.4159 -13.2863
18: 11108.894: 2.18970 3.69114 2.62024 -11.5585 -23.0377 -14.9574
19: 11108.889: 2.24429 3.73449 2.63533 -12.3256 -25.2168 -16.3618
20: 11108.887: 2.30215 3.79270 2.65515 -13.0915 -27.4009 -17.7587
21: 11108.885: 2.34188 3.83961 2.66230 -13.6605 -29.0131 -18.8015
22: 11108.883: 2.39691 3.90956 2.66522 -14.4695 -31.2977 -20.2890
23: 11108.882: 2.45426 3.97424 2.67403 -15.2774 -33.5868 -21.7705
24: 11108.880: 2.50176 4.02582 2.67524 -15.9609 -35.5165 -23.0288
25: 11108.879: 2.55864 4.08471 2.66951 -16.7983 -37.8728 -24.5771
26: 11108.878: 2.61557 4.14578 2.67017 -17.6350 -40.2328 -26.1198
27: 11108.878: 2.64907 4.18248 2.66874 -18.1610 -41.7119 -27.0935
28: 11108.877: 2.69285 4.23159 2.66654 -18.9217 -43.8454 -28.5079
29: 11108.876: 2.73814 4.28140 2.66685 -19.6820 -45.9818 -29.9180
30: 11108.876: 2.77826 4.32515 2.66935 -20.4426 -48.1145 -31.3339
31: 11108.875: 2.81108 4.36009 2.67593 -21.2034 -50.2423 -32.7574
32: 11108.874: 2.86012 4.41024 2.68995 -22.4337 -53.6809 -35.0625
33: 11108.874: 2.87997 4.42669 2.71002 -23.3420 -56.2078 -36.7800
34: 11108.873: 2.91449 4.46188 2.72112 -24.2499 -58.7441 -38.4832
35: 11108.873: 2.93380 4.48241 2.72586 -24.7517 -60.1447 -39.4251
36: 11108.873: 2.93473 4.48449 2.72325 -24.7335 -60.0938 -39.3907
37: 11108.873: 2.97031 4.52788 2.71283 -25.3758 -61.8861 -40.5955
38: 11108.873: 3.03861 4.60421 2.70580 -26.8211 -65.9184 -43.3097
39: 11108.873: 3.03513 4.59758 2.70602 -26.7677 -65.7680 -43.2114
40: 11108.873: 3.03679 4.59937 2.70571 -26.8211 -65.9168 -43.3123
41: 11108.872: 3.05342 4.61500 2.70623 -27.2488 -67.1083 -44.1186
42: 11108.872: 3.11825 4.67979 2.70999 -28.9597 -71.8741 -47.3440
43: 11108.872: 3.11142 4.67338 2.71520 -28.9620 -71.8786 -47.3515
44: 11108.872: 3.11117 4.67349 2.71485 -28.9660 -71.8897 -47.3590
45: 11108.872: 3.13222 4.69632 2.71875 -29.6371 -73.7580 -48.6261
46: 11108.872: 3.15772 4.72426 2.72096 -30.3932 -75.8633 -50.0525
47: 11108.872: 3.18344 4.75266 2.72209 -31.1492 -77.9688 -51.4787
48: 11108.872: 3.20928 4.78158 2.72176 -31.9053 -80.0744 -52.9047
49: 11108.871: 3.24659 4.82278 2.71914 -32.9989 -83.1206 -54.9671
50: 11108.871: 3.28239 4.85920 2.71907 -34.0924 -86.1663 -57.0305
51: 11108.871: 3.31604 4.89126 2.71866 -35.1859 -89.2115 -59.0947
52: 11108.871: 3.34442 4.91913 2.72002 -36.1901 -92.0077 -60.9915
53: 11108.871: 3.36973 4.94546 2.72113 -37.0776 -94.4788 -62.6675
54: 11108.871: 3.39259 4.96998 2.72239 -37.9650 -96.9493 -64.3443
55: 11108.871: 3.41683 4.99579 2.72332 -38.8525 -99.4202 -66.0207
56: 11108.871: 3.44020 5.02127 2.72401 -39.7399 -101.891 -67.6972
57: 11108.871: 3.47088 5.05549 2.72465 -40.9619 -105.293 -70.0064
58: 11108.871: 3.50130 5.08730 2.72501 -42.1741 -108.668 -72.2971
59: 11108.870: 3.54083 5.12735 2.72505 -43.8354 -113.293 -75.4378
60: 11108.870: 3.58166 5.16897 2.72549 -45.4969 -117.919 -78.5780
61: 11108.870: 3.57103 5.15775 2.72541 -45.0932 -116.795 -77.8153
62: 11108.870: 3.58030 5.16713 2.72553 -45.4968 -117.919 -78.5785
63: 11108.870: 3.61731 5.20512 2.72598 -47.1114 -122.414 -81.6312
64: 11108.870: 3.63540 5.22371 2.72626 -47.9463 -124.738 -83.2102
65: 11108.870: 3.65738 5.24653 2.72665 -49.0452 -127.797 -85.2895
66: 11108.870: 3.68094 5.27097 2.72697 -50.1442 -130.856 -87.3681
67: 11108.870: 3.70419 5.29512 2.72724 -51.2432 -133.916 -89.4468
68: 11108.870: 3.70382 5.29489 2.72714 -51.2391 -133.904 -89.4392
69: 11108.870: 3.70372 5.29509 2.72680 -51.2499 -133.934 -89.4596
70: 11108.870: 3.72505 5.31734 2.72698 -52.3046 -136.870 -91.4551
71: 11108.870: 3.73523 5.32795 2.72702 -52.8152 -138.291 -92.4212
72: 11108.870: 3.75534 5.34876 2.72713 -53.8363 -141.134 -94.3533
73: 11108.870: 3.77507 5.36911 2.72724 -54.8574 -143.976 -96.2855
74: 11108.870: 3.79415 5.38864 2.72735 -55.8785 -146.819 -98.2178
75: 11108.870: 3.81913 5.41386 2.72772 -57.2752 -150.706 -100.861
76: 11108.870: 3.84466 5.43989 2.72809 -58.6719 -154.594 -103.505
77: 11108.870: 3.85358 5.44893 2.72833 -59.2238 -156.130 -104.549
78: 11108.870: 3.89183 5.48813 2.72887 -61.4314 -162.275 -108.728
79: 11108.870: 3.89233 5.48890 2.72878 -61.5124 -162.500 -108.882
80: 11108.870: 3.91404 5.51138 2.72888 -62.8092 -166.109 -111.337
81: 11108.870: 3.92430 5.52201 2.72888 -63.4305 -167.839 -112.513
82: 11108.870: 3.94457 5.54292 2.72886 -64.6732 -171.297 -114.865
83: 11108.870: 3.96442 5.56338 2.72879 -65.9159 -174.756 -117.218
84: 11108.870: 3.99363 5.59335 2.72864 -67.8061 -180.017 -120.797
85: 11108.870: 4.02246 5.62257 2.72898 -69.6964 -185.278 -124.376
86: 11108.870: 4.04654 5.64655 2.72954 -71.4205 -190.076 -127.642
87: 11108.870: 4.07169 5.67218 2.72980 -73.1446 -194.874 -130.907
88: 11108.870: 4.06953 5.67031 2.72970 -73.0381 -194.578 -130.705
89: 11108.870: 4.08436 5.68574 2.72974 -74.1011 -197.536 -132.719
90: 11108.870: 4.09548 5.69720 2.72976 -74.8991 -199.757 -134.230
91: 11108.870: 4.11748 5.71981 2.72981 -76.4953 -204.199 -137.253
92: 11108.870: 4.13498 5.73779 2.72978 -77.8038 -207.841 -139.732
93: 11108.870: 4.15109 5.75413 2.72975 -79.0754 -211.380 -142.141
94: 11108.870: 4.16779 5.77108 2.72982 -80.3471 -214.919 -144.549
95: 11108.870: 4.18428 5.78781 2.72992 -81.6187 -218.457 -146.958
96: 11108.870: 4.18484 5.78823 2.73003 -81.6855 -218.643 -147.085
97: 11108.870: 4.18716 5.79064 2.72995 -81.8857 -219.200 -147.464
98: 11108.870: 4.20695 5.81048 2.73027 -83.4878 -223.659 -150.499
99: 11108.870: 4.21345 5.81712 2.73028 -84.0168 -225.131 -151.502
100: 11108.870: 4.22635 5.83037 2.73025 -85.0748 -228.075 -153.506
101: 11108.870: 4.25192 5.85664 2.73020 -87.1908 -233.963 -157.515
102: 11108.870: 4.27673 5.88252 2.72986 -89.3067 -239.852 -161.524
103: 11108.870: 4.28524 5.89112 2.72998 -90.1149 -242.100 -163.055
104: 11108.870: 4.31403 5.92022 2.73029 -92.6455 -249.142 -167.850
105: 11108.870: 4.32456 5.93065 2.73053 -93.6039 -251.809 -169.666
106: 11108.870: 4.33436 5.93987 2.73109 -94.5623 -254.476 -171.483
107: 11108.870: 4.35277 5.95848 2.73121 -96.2805 -259.257 -174.739
108: 11108.870: 4.37086 5.97729 2.73092 -97.9988 -264.039 -177.995
109: 11108.870: 4.38919 5.99615 2.73079 -99.7171 -268.820 -181.251
110: 11108.870: 4.39342 6.00062 2.73068 -100.139 -269.994 -182.050
111: 11108.870: 4.42375 6.03185 2.73045 -103.092 -278.210 -187.646
112: 11108.870: 4.42003 6.02787 2.73060 -102.791 -277.374 -187.077
113: 11108.870: 4.43195 6.03990 2.73068 -103.993 -280.718 -189.355
114: 11108.870: 4.43902 6.04707 2.73071 -104.711 -282.716 -190.716
115: 11108.870: 4.45305 6.06125 2.73079 -106.147 -286.711 -193.437
116: 11108.870: 4.46681 6.07505 2.73093 -107.583 -290.706 -196.158
117: 11108.870: 4.47692 6.08503 2.73113 -108.687 -293.779 -198.251
118: 11108.870: 4.49525 6.10365 2.73116 -110.647 -299.232 -201.966
119: 11108.870: 4.51301 6.12197 2.73102 -112.606 -304.684 -205.680
120: 11108.870: 4.53107 6.14042 2.73098 -114.566 -310.137 -209.394
121: 11108.870: 4.53070 6.14001 2.73098 -114.554 -310.105 -209.373
122: 11108.870: 4.53074 6.14011 2.73092 -114.566 -310.137 -209.395
123: 11108.870: 4.54388 6.15342 2.73096 -116.044 -314.248 -212.195
124: 11108.870: 4.54910 6.15872 2.73095 -116.635 -315.893 -213.316
125: 11108.870: 4.55943 6.16922 2.73092 -117.817 -319.182 -215.556
126: 11108.870: 4.57993 6.19001 2.73092 -120.181 -325.760 -220.038
127: 11108.870: 4.59575 6.20608 2.73083 -122.064 -331.000 -223.608
128: 11108.870: 4.60914 6.21952 2.73098 -123.696 -335.540 -226.701
129: 11108.870: 4.62805 6.23841 2.73134 -126.095 -342.214 -231.249
130: 11108.870: 4.64748 6.25788 2.73166 -128.494 -348.890 -235.798
131: 11108.870: 4.65512 6.26588 2.73141 -129.446 -351.538 -237.602
132: 11108.870: 4.67425 6.28550 2.73119 -131.846 -358.215 -242.152
133: 11108.870: 4.69170 6.30388 2.73035 -134.085 -364.445 -246.396
134: 11108.870: 4.70907 6.32157 2.73021 -136.324 -370.674 -250.641
135: 11108.870: 4.70524 6.31710 2.73082 -135.913 -369.533 -249.863
136: 11108.870: 4.71413 6.32547 2.73148 -137.181 -373.060 -252.267
137: 11108.870: 4.72446 6.33566 2.73181 -138.605 -377.022 -254.968
138: 11108.870: 4.73464 6.34583 2.73208 -140.029 -380.985 -257.668
139: 11108.870: 4.74476 6.35618 2.73214 -141.454 -384.947 -260.368
140: 11108.870: 4.76631 6.37842 2.73178 -144.393 -393.126 -265.942
141: 11108.869: 4.76523 6.37779 2.73120 -144.227 -392.664 -265.627
142: 11108.869: 4.76643 6.37901 2.73119 -144.393 -393.126 -265.942
summary(fit.ml)
Linear mixed-effects model fit by maximum likelihood
Data: dd
AIC BIC logLik
12848.11 12915.18 -6412.053
Random effects:
Formula: ~1 + ses + Sex | id
Structure: General positive-definite, Log-Cholesky parametrization
StdDev Corr
(Intercept) 1.5507024 (Intr) ses
ses 0.1796016 0.998
SexMale 0.3973486 0.999 0.998
Residual 6.0997280
Fixed effects: mathach ~ ses * Sector + Sex
Value Std.Error DF t-value p-value
(Intercept) 12.951009 0.4218649 1934 30.699421 0.0000
ses 1.830092 0.2807716 1934 6.518079 0.0000
SectorPublic -1.618970 0.6103963 38 -2.652326 0.0116
SexMale 1.173682 0.3321072 1934 3.534045 0.0004
ses:SectorPublic 1.344947 0.4212879 1934 3.192466 0.0014
Correlation:
(Intr) ses SctrPb SexMal
ses 0.079
SectorPublic -0.655 -0.058
SexMale -0.220 0.019 -0.012
ses:SectorPublic -0.048 -0.667 0.169 -0.032
Standardized Within-Group Residuals:
Min Q1 Med Q3 Max
-3.06009142 -0.75212643 0.04604295 0.79450300 2.66713374
Number of Observations: 1977
Number of Groups: 40
The default for lme is fitting with ‘Residual Maximum Likelihood’ (REML) – it has other interpretations but everyone agrees on ‘REML’ – With REML, maximum likelihood is applied ONLY to the parameters for random effects, i.e. the G matrix and sigma (or R). The Betas play no direct role, they are only estimated with Generalized Least Squares as an afterthought. This means that the likelihood with REML is only informative for the the RE model: the G and R matrices. You can’t compare two models that have different FE models.
Full maximum likelihood (ML) does look at Betas, G and R together. So the likelihood with ML can be used to test two models with nested FE models.
The consequence is that:
If two models have been fitted with REML, the must have identical FE models to compare them with ‘anova’: p-values, AIC, BIC.
If two models have been fitted with ML, you can use ‘anova’ regardless of the FE or the RE models — PROVIDED they have the same response variable. This includes requiring that they have the same set of observations, hence the same omitted rows due to missing values.
Wald tests can be used for both ML or REML fits. They might be more accurate with REML fits. However, Wald test tend to useful mainly for the FE part of the model. ‘wald’ in ‘spida2’ only works for the FE model. To compare two RE models (with identical FE models) LRT is generally better than Wald. However, classic p-values tend to be too large in many cases and can be adjusted through simulation.
Summary:
Wald is ok for the FE model whether ML or REML
anova (with simulation adjustment) is ok for the RE model provided the FE models are identical
anova can be used to test different FE models or models that differ in both FEs and REs ONLY IF THE MODELS ARE fitted with ML.
The ‘car’ package has methods to conduct influence diagnostics with a wide range of models include ‘lme’ models. I plan to incorporate a discussion of these methods soon.
Level 1 residuals
Level 1 standardized residuals versus yhat:
gd(pch=1, alpha = .5)
plot(fitc)
plot(fitc) + layer(panel.loess(x,y, lwd=3,col='red'))
Not as generous as plot for ‘lm’.
In contrast with least-squares, it is possible to have a non-flat least-squares regression here.
diag.df <- data.frame( resid = resid(fitc), fitted = fitted(fitc))
summary( lm( resid ~ fitted, diag.df))
Call:
lm(formula = resid ~ fitted, data = diag.df)
Residuals:
Min 1Q Median 3Q Max
-19.1659 -4.5557 0.1715 4.7476 17.5492
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -0.84287 0.60009 -1.405 0.160
fitted 0.06594 0.04572 1.442 0.149
Residual standard error: 6.056 on 1975 degrees of freedom
Multiple R-squared: 0.001052, Adjusted R-squared: 0.0005463
F-statistic: 2.08 on 1 and 1975 DF, p-value: 0.1494
It could be a symptom of lack of independence between school-level random effects and level 1 errors or between school-level random effects and other predictors. One possible cure is adding a contextual effect if there isn’t one.
Normality:
qqnorm( fitc )
qqnorm( fitc , abline = c(0,1), id = .01 )
BEWARE: data on horizontal axis SO distribution is slightly platykurtic. Variance might be overestimated with extreme values thereby overajusting.
Using sqrt(abs(resid)) produces an approximate normal if everything is ok
plot( fitc , sqrt( abs( resid(.))) ~ fitted(.))
plot( fitc , sqrt( abs( resid(.))) ~ fitted(.)) +
layer(panel.loess(x, y, lwd = 2, col = 'red'))
plot( fitc ,
sqrt( abs( resid(.))) ~ fitted(.)| Sector,
id = .01, sub = "Scale-Location Plot") +
layer(panel.loess(x, y, lwd = 2, col = 'red'))
plot( fitc , sqrt( abs( resid(.))) ~ cvar(ses,school)| Sector,
id = .01, sub = "Scale-Location Plot") +
layer(panel.loess(x, y, lwd = 2, col = 'red'))
other views of residuals:
plot( fitc, Sex ~ resid(.) | Sector)
plot( fitc, id ~ resid(.) | Sector)
Omitted variables:
plot( fitc , resid(.) ~ fitted(.)| Sector*Sex, id = .01)
Why MM residual plots don’t look like OLS residual plots:
Question: Performing OLS regression diagnostics, you find that the residuals are correlated with the predicted values. What would this signify and what action should you take?
Answer: at end of script.
With MM residuals:
Why are residuals not uncorrelated with predicted values ( a canon of OLS )?
Reason: if we were using OLS on the pooled data, resids would be uncorrelated.
But MMs is roughly like doing OLS on pooled data, then adding an additional fit at the cluster level.
If unaccounted between cluster effects are in the same direction as within cluster effect then higher residuals with higher fitted values will get a second fit, This shrinks the high positive OLS residuals and stretches the corresponding fitted values creating a negative slope in the residual vs fitted plot.
Make cvar(ses,id) a variable in dd
dd$ses.m <- with( dd, cvar( ses, id))
fitc <- lme( mathach ~ (ses + ses.m) * Sector, dd,
random = ~ 1 + ses | id )
some( coef ( fitc ) ) # BLUP in each cluster
(Intercept) ses ses.m SectorPublic ses:SectorPublic
2208 13.53819 1.817657 2.407785 -0.934345 1.173092
2626 14.30123 1.714866 2.407785 -0.934345 1.173092
3610 14.49506 1.824884 2.407785 -0.934345 1.173092
3992 13.25713 1.564306 2.407785 -0.934345 1.173092
4292 14.42899 1.294163 2.407785 -0.934345 1.173092
5762 13.17761 1.514336 2.407785 -0.934345 1.173092
6074 14.52082 1.565613 2.407785 -0.934345 1.173092
6484 14.51768 1.368009 2.407785 -0.934345 1.173092
7890 12.86207 1.393502 2.407785 -0.934345 1.173092
9586 12.56447 1.694839 2.407785 -0.934345 1.173092
ses.m:SectorPublic
2208 1.414896
2626 1.414896
3610 1.414896
3992 1.414896
4292 1.414896
5762 1.414896
6074 1.414896
6484 1.414896
7890 1.414896
9586 1.414896
some( ranef ( fitc ) ) # RE in each cluster
(Intercept) ses
1906 0.36252477 0.050644237
2208 0.18213282 0.118259831
3013 0.35388133 0.046187778
3610 1.13900591 0.125486781
4868 -1.63966343 0.004294988
5619 0.02874504 0.502254674
5761 -0.63626758 0.270788614
6484 1.16162333 -0.331388396
7172 -2.88069356 0.202229471
8531 -1.15715031 0.079146909
Note: coef( fitc ) = fixed.effect + random.effect pairs( cbind( coef(fitc), ranef( fitc)) ) # can you interpret what this says?
re <- ranef( fitc, aug = T) # creates data frame with up( dd, ~ school) variables
some( re )
(Intercept) ses school mathach Sex Minority Size
1317 -1.1509678 -0.003523084 1317 13.177687 Female Yes 455
2658 -1.3121006 0.131984540 2658 13.396156 Female No 780
4292 1.0729371 -0.405233961 4292 12.864354 Male Yes 1328
6074 1.1647641 -0.133784197 6074 13.779089 Female No 2051
6484 1.1616233 -0.331388396 6484 12.912400 Female No 726
6897 0.1989012 0.106243527 6897 15.097633 Female No 1415
7172 -2.8806936 0.202229471 7172 8.066818 Female Yes 280
7342 -0.2737076 -0.042255077 7342 11.166414 Male No 1220
8707 -0.4475834 0.112155798 8707 12.883938 Female No 1133
9550 -1.0779843 0.176773458 9550 11.089138 Female No 1532
Sector PRACAD DISCLIM n ses.m ses.cwg ses.iqr
1317 Catholic 0.95 -1.694 48 0.34533333 1.502975e-17 0.7825
2658 Catholic 0.79 -0.961 45 0.43844444 -9.250654e-18 0.9100
4292 Catholic 0.76 -0.674 65 -0.48615385 -5.096167e-18 0.8600
6074 Catholic 0.32 -1.018 56 -0.27675000 -1.487390e-18 0.8175
6484 Public 0.19 0.218 35 -0.18514286 -2.762392e-18 0.7400
6897 Public 0.55 -0.361 49 0.34955102 -1.359125e-17 1.0700
7172 Catholic 0.05 1.013 44 -0.28981818 -3.027573e-17 1.0275
7342 Catholic 0.46 0.380 58 -0.44782759 -4.974128e-19 0.7125
8707 Public 0.48 1.542 48 0.15512500 8.673617e-19 1.1350
9550 Public 0.45 0.791 29 0.05303448 9.077389e-18 1.0200
ses.trange ses.sd ses.rank minority.diff id.sec ses.cvar
1317 1.166 0.5561583 24.5 -0.2676044 C1317 0.34533333
2658 1.698 0.6402846 23.0 -0.1830556 C2658 0.43844444
4292 1.708 0.6511382 33.0 -0.0150000 C4292 -0.48615385
6074 1.440 0.6271235 28.5 -0.3279630 C6074 -0.27675000
6484 1.932 0.6958345 18.0 -0.5227273 P6484 -0.18514286
6897 2.090 0.7445231 25.0 -0.7075000 P6897 0.34955102
7172 1.686 0.6764417 22.5 0.3915447 C7172 -0.28981818
7342 1.323 0.5648459 29.5 0.1027413 C7342 -0.44782759
8707 2.169 0.8042577 24.5 -0.5873333 P8707 0.15512500
9550 1.824 0.7847035 15.0 -0.5623913 P9550 0.05303448
plot( re ) # special plot method
qqnorm( fitc, ~ ranef(.) | Sector, id = .05)
if(interactive) {
Init3d()
Plot3d( `(Intercept)` ~ ses + ses.m | Sector, re) # note funny names in backquotes
Ell3d()
}
EXERCISE:
Here’s a quick dropone function
dropone <- function(fit) {
data <- getData(fit)
index <- 1:nrow(data)
lapply(index, function(i) fixef(update(fit,data=data[-i,])))
}
Here’s a fancier one that will drop clusters and return NA if there’s an error in fitting
dropone <- function(fit, form = NULL, FUN = fixef, data = getData(fit),...) {
if(is.null(form)) {
by <- factor(1:nrow(data))
data$by <- by
dframe <- data
} else {
by <- model.frame(form, data)
by <- do.call(paste, c(by, sep='/'))
data$by <- factor(by) # bad if by already exists in data
dframe <- up(data, form)
}
values <- dframe$by
names(values) <- values
ret <- lapply(values, function(v) {
ret <- try(update(fit, data = data[by != v, ,drop=FALSE],...))
if(class(ret) == 'try-error') NA else FUN(ret)
})
ret <- do.call(rbind,ret)
colnames(ret) <- paste0('b_',colnames(ret))
cbind(dframe, ret)
}
Ideas for improvement:
The following takes about 5 minutes so it’s been precooked so we can take it out of the oven.
# system.time(
# fits.dropone <- dropone(fitc)
# )
Take fits.dropone out of the oven:
con <- url('http://blackwell.math.yorku.ca/ICPSR/fits.dropone.rda')
load(con, verbose = T)
Loading objects:
fits.dropone
close(con)
str(fits.dropone)
'data.frame': 1977 obs. of 26 variables:
$ school : int 1317 1317 1317 1317 1317 1317 1317 1317 1317 1317 ...
$ mathach : num 12.86 8.96 4.76 21.41 20.75 ...
$ ses : num 0.882 0.932 -0.158 0.362 1.372 ...
$ Sex : Factor w/ 2 levels "Female","Male": 1 1 1 1 1 1 1 1 1 1 ...
$ Minority : Factor w/ 2 levels "No","Yes": 1 2 2 2 1 2 1 2 2 2 ...
$ Size : int 455 455 455 455 455 455 455 455 455 455 ...
$ Sector : Factor w/ 2 levels "Catholic","Public": 1 1 1 1 1 1 1 1 1 1 ...
$ PRACAD : num 0.95 0.95 0.95 0.95 0.95 0.95 0.95 0.95 0.95 0.95 ...
$ DISCLIM : num -1.69 -1.69 -1.69 -1.69 -1.69 ...
$ n : int 48 48 48 48 48 48 48 48 48 48 ...
$ ses.m : num 0.345 0.345 0.345 0.345 0.345 ...
$ ses.cwg : num 0.5367 0.5867 -0.5033 0.0167 1.0267 ...
$ ses.iqr : num 0.782 0.782 0.782 0.782 0.782 ...
$ ses.trange : num 1.17 1.17 1.17 1.17 1.17 ...
$ ses.sd : num 0.556 0.556 0.556 0.556 0.556 ...
$ ses.rank : num 39 40 6 25 47 20 20 14.5 8 33 ...
$ minority.diff : num -0.268 -0.268 -0.268 -0.268 -0.268 ...
$ id : int 1317 1317 1317 1317 1317 1317 1317 1317 1317 1317 ...
$ id.sec : Factor w/ 40 levels "P5762","P2639",..: 33 33 33 33 33 33 33 33 33 33 ...
..- attr(*, "scores")= num [1:40(1d)] -1.194 -0.964 -0.757 999.403 -0.523 ...
.. ..- attr(*, "dimnames")=List of 1
.. .. ..$ : chr "P5762" "P2639" "P8854" "C4530" ...
$ by : Factor w/ 1977 levels "1","2","3","4",..: 1 2 3 4 5 6 7 8 9 10 ...
$ b_(Intercept) : num 13.4 13.4 13.4 13.3 13.4 ...
$ b_ses : num 1.7 1.71 1.69 1.7 1.69 ...
$ b_ses.m : num 2.41 2.42 2.43 2.39 2.4 ...
$ b_SectorPublic : num -0.935 -0.937 -0.942 -0.928 -0.933 ...
$ b_ses:SectorPublic : num 1.17 1.16 1.18 1.17 1.19 ...
$ b_ses.m:SectorPublic: num 1.41 1.41 1.39 1.43 1.42 ...
# scatterplotMatrix(fits.dropone[,grep('^b_',names(fits.dropone))], id = TRUE)
Dropping one school at a time
system.time(
fits.dropschool <- dropone(fitc, ~ id) # only 6 seconds
)
user system elapsed
4.67 0.06 5.16
fitc$call
lme.formula(fixed = mathach ~ (ses + ses.m) * Sector, data = dd,
random = ~1 + ses | id)
fitc <- update(fitc, control = list(returnObject = TRUE, msMaxIter = 1000) )
fitc$call
lme.formula(fixed = mathach ~ (ses + ses.m) * Sector, data = dd,
random = ~1 + ses | id, control = list(returnObject = TRUE,
msMaxIter = 1000))
system.time(
fits.dropschool <- dropone(fitc, ~ id) # only 6 seconds
)
user system elapsed
4.06 0.00 4.30
str(fits.dropschool)
'data.frame': 40 obs. of 21 variables:
$ school : int 1317 1906 2208 2458 2626 2629 2639 2658 2771 3013 ...
$ Size : int 455 400 1061 545 2142 1314 2713 780 415 760 ...
$ Sector : Factor w/ 2 levels "Catholic","Public": 1 1 1 1 2 1 2 1 2 2 ...
$ PRACAD : num 0.95 0.87 0.68 0.89 0.4 0.81 0.14 0.79 0.24 0.56 ...
$ DISCLIM : num -1.694 -0.939 -0.864 -1.484 0.142 ...
$ n : int 48 53 60 57 38 57 42 45 55 53 ...
$ ses.m : num 0.3453 0.5116 0.4232 0.2278 -0.0648 ...
$ ses.iqr : num 0.782 1.01 0.983 0.79 0.597 ...
$ ses.trange : num 1.17 1.59 1.5 1.95 1.38 ...
$ ses.sd : num 0.556 0.614 0.598 0.658 0.56 ...
$ minority.diff : num -0.268 -0.366 NaN -0.582 NaN ...
$ id : Factor w/ 40 levels "1317","1906",..: 1 2 3 4 5 6 7 8 9 10 ...
$ id.sec : Factor w/ 40 levels "P5762","P2639",..: 33 39 37 32 10 26 2 38 6 11 ...
$ ses.cvar : num 0.3453 0.5116 0.4232 0.2278 -0.0648 ...
$ by : Factor w/ 40 levels "1317","1906",..: 1 2 3 4 5 6 7 8 9 10 ...
$ b_(Intercept) : num 13.4 13.3 13.3 13.4 13.4 ...
$ b_ses : num 1.71 1.68 1.65 1.63 1.7 ...
$ b_ses.m : num 2.58 2.31 2.39 2.47 2.42 ...
$ b_SectorPublic : num -0.999 -0.917 -0.925 -0.954 -1.012 ...
$ b_ses:SectorPublic : num 1.16 1.19 1.22 1.25 1.13 ...
$ b_ses.m:SectorPublic: num 1.26 1.52 1.44 1.33 1.45 ...
scatterplotMatrix(fits.dropschool[,grep('^b_',names(fits.dropschool))], id = TRUE)
What does this show?
Which points represent Catholic schools and which Public schools?
if(interactive) {
names(fits.dropschool)
Init3d()
Plot3d(`b_(Intercept)` ~ b_ses + `b_ses:SectorPublic` | Sector,
fits.dropschool, sphere.size = 1.5)
Id3d() # to identify schools
}
id_miss <- is.na(fits.dropschool$b_ses)
categ <- ifelse(dd$id %in% c('7688','7172'), 'out', 'ok')
subset( fits.dropschool, id %in% c('7688','7172'))
school Size Sector PRACAD DISCLIM n ses.m ses.iqr ses.trange
7172 7172 280 Catholic 0.05 1.013 44 -0.2898182 1.0275 1.686
7688 7688 1410 Catholic 0.65 -0.575 54 0.1858889 0.8275 1.378
ses.sd minority.diff id id.sec ses.cvar by b_(Intercept)
7172 0.6764417 0.3915447 7172 C7172 -0.2898182 7172 13.60385
7688 0.5644347 -0.1387347 7688 C7688 0.1858889 7688 13.15648
b_ses b_ses.m b_SectorPublic b_ses:SectorPublic
7172 1.729063 1.792919 -1.185338 1.141840
7688 1.761263 2.115580 -0.745562 1.114109
b_ses.m:SectorPublic
7172 2.005783
7688 1.635031
gd(alpha=c(.1,.1))
xyplot(mathach ~ ses | paste(id), dd, groups = categ) +
glayer(panel.lmline(x,y, lwd = 2, col = 'red'))
Not surprising!
# Choose the problem that seems most appropriate:
#
# 1) Add the variable 'Sex' to analysis above. Note that Sex has
# a contextual variable: Sex composition, a variable with
# interestingly different features in Catholic vs Public school.
#
# If you wish to gain some experience with convergence problems
# use the full Level 1 model ( ~ ses*Sex | id ) for your RE model.
# If this fails to converge, try to use the advice given above to
# correct the problem. If you prefer not to bother for now,
# just use the additive model:
#
# random = ~1 + ses + Sector |id
#
# Try to estimate the Gender Gap under various circumstances:
# ses and Sector if the effect of Sex interacts with these variables.
#
# Which circumstances are associated with large gender gaps,
# with small gender gaps?
#
# Make graphs of the gender gap as a function of its moderators,
# preferably with confidence bounds.
#
# 2) Analyze the data in 'bdf' in 'nlme':
# > data (bdf)
# > ?bdf
# on Language Scores of 8-Graders in The Netherlands
#
# This is similar to the 'hs' dataset but different enough
# to be interesting.
#
# The response of interest is 'langPOST' a measure of language
# achievement at the end of the school year.
#
# Potential predictors include: ses, sex, denominat (Sector),
# Minority, IQ.verb as well as a pretest: langPRE.
#
# This raises the interesting question of the best use for the
# pretest. Should it be used as a covariate or should you analyze
# the gain score: langPOST - langPRE as the response.
#
# As usual, the best course of action may depend on the questions
# and intended interpretation of the results.
#
# 3) Study the role of 'Minority' in the 'hs' data following the
# script below
#
# 4) Validate the model above with the other half of the data 'hs2'
# What differences do you note?
#