The aim of the (hypothetical) trial was to investigate the effect of three different treatments in terms of thickness and redness of the skin in psoriasis patients. There were 10 subjects per treatment arm and the effectiveness of the drug was measured in terms of redness and thickness of the skin. Thickness of the skin in the lesion was measured with a special device, whereas redness was evaluated by an investigator: a score ranging from 0 to 30. The data has a form of ADaM data ADXD with paramcd having two values: “Redness”, “Thickness” and aval contains the values of these parameters. ADY variable contains days and AVISIT contains visit days in a character format.
library(readr)
## Warning: package 'readr' was built under R version 3.4.2
dat <- read_csv("C:/Users/dkxdk/OneDrive - Leo Pharma A S/stat/R course/dat_mmrm2.csv")
## Parsed with column specification:
## cols(
## param = col_character(),
## paramcd = col_character(),
## aval = col_double(),
## ADY = col_integer(),
## avisit = col_character(),
## subjid = col_integer(),
## fasfl = col_character(),
## trt01p = col_character()
## )
str(dat)
## Classes 'tbl_df', 'tbl' and 'data.frame': 360 obs. of 8 variables:
## $ param : chr "Thickness" "Thickness" "Thickness" "Thickness" ...
## $ paramcd: chr "THICKNESS" "THICKNESS" "THICKNESS" "THICKNESS" ...
## $ aval : num 491 406 392 328 539 ...
## $ ADY : int 1 4 6 8 1 4 6 8 1 4 ...
## $ avisit : chr "Day 1" "Day 4" "Day 6" "Day 8" ...
## $ subjid : int 21 21 21 21 22 22 22 22 23 23 ...
## $ fasfl : chr "Y" "Y" "Y" "Y" ...
## $ trt01p : chr "drug 1%" "drug 1%" "drug 1%" "drug 1%" ...
## - attr(*, "spec")=List of 2
## ..$ cols :List of 8
## .. ..$ param : list()
## .. .. ..- attr(*, "class")= chr "collector_character" "collector"
## .. ..$ paramcd: list()
## .. .. ..- attr(*, "class")= chr "collector_character" "collector"
## .. ..$ aval : list()
## .. .. ..- attr(*, "class")= chr "collector_double" "collector"
## .. ..$ ADY : list()
## .. .. ..- attr(*, "class")= chr "collector_integer" "collector"
## .. ..$ avisit : list()
## .. .. ..- attr(*, "class")= chr "collector_character" "collector"
## .. ..$ subjid : list()
## .. .. ..- attr(*, "class")= chr "collector_integer" "collector"
## .. ..$ fasfl : list()
## .. .. ..- attr(*, "class")= chr "collector_character" "collector"
## .. ..$ trt01p : list()
## .. .. ..- attr(*, "class")= chr "collector_character" "collector"
## ..$ default: list()
## .. ..- attr(*, "class")= chr "collector_guess" "collector"
## ..- attr(*, "class")= chr "col_spec"
Now we check the data structure and the variable formats.
str(dat)
## Classes 'tbl_df', 'tbl' and 'data.frame': 360 obs. of 8 variables:
## $ param : chr "Thickness" "Thickness" "Thickness" "Thickness" ...
## $ paramcd: chr "THICKNESS" "THICKNESS" "THICKNESS" "THICKNESS" ...
## $ aval : num 491 406 392 328 539 ...
## $ ADY : int 1 4 6 8 1 4 6 8 1 4 ...
## $ avisit : chr "Day 1" "Day 4" "Day 6" "Day 8" ...
## $ subjid : int 21 21 21 21 22 22 22 22 23 23 ...
## $ fasfl : chr "Y" "Y" "Y" "Y" ...
## $ trt01p : chr "drug 1%" "drug 1%" "drug 1%" "drug 1%" ...
## - attr(*, "spec")=List of 2
## ..$ cols :List of 8
## .. ..$ param : list()
## .. .. ..- attr(*, "class")= chr "collector_character" "collector"
## .. ..$ paramcd: list()
## .. .. ..- attr(*, "class")= chr "collector_character" "collector"
## .. ..$ aval : list()
## .. .. ..- attr(*, "class")= chr "collector_double" "collector"
## .. ..$ ADY : list()
## .. .. ..- attr(*, "class")= chr "collector_integer" "collector"
## .. ..$ avisit : list()
## .. .. ..- attr(*, "class")= chr "collector_character" "collector"
## .. ..$ subjid : list()
## .. .. ..- attr(*, "class")= chr "collector_integer" "collector"
## .. ..$ fasfl : list()
## .. .. ..- attr(*, "class")= chr "collector_character" "collector"
## .. ..$ trt01p : list()
## .. .. ..- attr(*, "class")= chr "collector_character" "collector"
## ..$ default: list()
## .. ..- attr(*, "class")= chr "collector_guess" "collector"
## ..- attr(*, "class")= chr "col_spec"
We are going to look at the redness of the skin. First we subset the data.
dat_red <- dat[dat$param == "Redness",]
head(dat_red)
## # A tibble: 6 x 8
## param paramcd aval ADY avisit subjid fasfl trt01p
## <chr> <chr> <dbl> <int> <chr> <int> <chr> <chr>
## 1 Redness REDNESS 17.91924 1 Day 1 1 Y drug 3%
## 2 Redness REDNESS 17.73705 2 Day 2 1 Y drug 3%
## 3 Redness REDNESS 15.75879 3 Day 3 1 Y drug 3%
## 4 Redness REDNESS 17.19799 4 Day 4 1 Y drug 3%
## 5 Redness REDNESS 15.46497 5 Day 5 1 Y drug 3%
## 6 Redness REDNESS 15.28378 6 Day 6 1 Y drug 3%
THen we plot redness scores versus days for each subject, colouring by treatment group :
library(ggplot2)
## Warning: package 'ggplot2' was built under R version 3.4.4
dat_red$subjid<- as.factor(dat_red$subjid)
dat_red$trt01p <- as.factor(dat_red$trt01p)
ggplot(dat_red, aes(x=ADY, y=aval, group=subjid, colour=trt01p)) +
geom_line()
It is also of interest to plot treatment group average time profiles:
require(plyr)
## Loading required package: plyr
## Warning: package 'plyr' was built under R version 3.4.4
dat_red$avisit <- as.factor(dat_red$avisit)
mns <- ddply(dat_red, ~ trt01p + avisit + ADY, summarize,
redness = mean(aval))
ggplot(mns, aes(x=ADY, y=redness, group=trt01p, colour=trt01p)) +
geom_point() + geom_line()
Plot data points with a fitted curve:
ggplot(dat_red, aes(x=ADY, y=aval, colour = trt01p, group = trt01p)) +
geom_point(shape=1) + # Use hollow circles
geom_smooth(method = "lm", formula = y ~ splines::ns(x, 2), se = FALSE)
It seems like there is a difference in treatment groups. It is also clear that there is a between subjects variation.
First we fit data with a linear mixed model with one random subject effect by using lme4 package. The covariance matrix \(V\) has the following form:
\[ V_{y_{i_1}, y_{i_2}} = \begin{cases} 0 & \quad \text{if } subj_{i1} \neq subj_{i2} \text{ and } i_1 \neq i_2\\ \sigma_{subj}^2 & \quad \text{if } subj_{i1} = subj_{i2} \text{ and } i_1 \neq i_2 \\ \sigma_{subj}^2+ \sigma^2 & \quad \text{if } i_1 = i_2 \end{cases} \]
In this model two measurements on the subject are correlated, but equally correlated (no matter how far observations are taken). This type of model can be fit in R in different ways. Using the nlme or lme4 packages. With lme4:
library(lme4)
## Loading required package: Matrix
model1 <- lmer(aval ~ avisit*trt01p + (1 | subjid), data = dat_red)
summary(model1)
## Linear mixed model fit by REML ['lmerMod']
## Formula: aval ~ avisit * trt01p + (1 | subjid)
## Data: dat_red
##
## REML criterion at convergence: 853.9
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -2.22636 -0.64102 -0.03963 0.61653 2.47603
##
## Random effects:
## Groups Name Variance Std.Dev.
## subjid (Intercept) 1.202 1.096
## Residual 1.884 1.373
## Number of obs: 240, groups: subjid, 30
##
## Fixed effects:
## Estimate Std. Error t value
## (Intercept) 19.49493 0.55553 35.09
## avisitDay 2 -0.68020 0.61390 -1.11
## avisitDay 3 -1.72135 0.61390 -2.80
## avisitDay 4 -1.66418 0.61390 -2.71
## avisitDay 5 -2.12188 0.61390 -3.46
## avisitDay 6 -1.88220 0.61390 -3.07
## avisitDay 7 -1.62350 0.61390 -2.64
## avisitDay 8 -3.38599 0.61390 -5.52
## trt01pdrug 2% -0.22719 0.78564 -0.29
## trt01pdrug 3% 0.03475 0.78564 0.04
## avisitDay 2:trt01pdrug 2% -1.24566 0.86819 -1.43
## avisitDay 3:trt01pdrug 2% -0.63570 0.86819 -0.73
## avisitDay 4:trt01pdrug 2% -1.99746 0.86819 -2.30
## avisitDay 5:trt01pdrug 2% -2.40608 0.86819 -2.77
## avisitDay 6:trt01pdrug 2% -2.46675 0.86819 -2.84
## avisitDay 7:trt01pdrug 2% -2.71482 0.86819 -3.13
## avisitDay 8:trt01pdrug 2% -4.32495 0.86819 -4.98
## avisitDay 2:trt01pdrug 3% -0.76171 0.86819 -0.88
## avisitDay 3:trt01pdrug 3% -1.17534 0.86819 -1.35
## avisitDay 4:trt01pdrug 3% -1.91081 0.86819 -2.20
## avisitDay 5:trt01pdrug 3% -3.14694 0.86819 -3.62
## avisitDay 6:trt01pdrug 3% -1.78482 0.86819 -2.06
## avisitDay 7:trt01pdrug 3% -3.05396 0.86819 -3.52
## avisitDay 8:trt01pdrug 3% -3.06099 0.86819 -3.53
##
## Correlation matrix not shown by default, as p = 24 > 12.
## Use print(x, correlation=TRUE) or
## vcov(x) if you need it
anova(model1)
## Analysis of Variance Table
## Df Sum Sq Mean Sq F value
## avisit 7 647.02 92.431 49.0508
## trt01p 2 36.37 18.187 9.6512
## avisit:trt01p 14 85.17 6.083 3.2283
Now we will try to fit mixed model with repeated measurements:
\[redness \sim N(\mu, V)\] with
\[\mu_i = \mu + \alpha(treat_i) + \beta(day_i) + \gamma(treat_i, day_i)\] and covariance \(V\) having the following form:
\[ V_{i_1, i_2} = \begin{cases} 0 & \quad \text{if } subj_{i1} \neq subj_{i2} \text{ and } i_1 \neq i_2\\ \nu^2 + \tau^2*exp(\frac{-(day_{i_1} - day_{i_2})^2}{\rho^2}) & \quad \text{if } subj_{i1} = subj_{i2} \text{ and } i_1 \neq i_2 \\ \nu^2 + \tau^2 + \sigma^2 & \quad \text{if } i_1 = i_2 \end{cases} \]
In this model two observations very close to each other have covariance \(\nu^2 + \tau^2\), two observations very far have covariance \(\nu^2\)
This type of model can be fit with nlme package
library(nlme)
## Warning: package 'nlme' was built under R version 3.4.4
##
## Attaching package: 'nlme'
## The following object is masked from 'package:lme4':
##
## lmList
?corClasses
## starting httpd help server ...
## done
str(dat_red)
## Classes 'tbl_df', 'tbl' and 'data.frame': 240 obs. of 8 variables:
## $ param : chr "Redness" "Redness" "Redness" "Redness" ...
## $ paramcd: chr "REDNESS" "REDNESS" "REDNESS" "REDNESS" ...
## $ aval : num 17.9 17.7 15.8 17.2 15.5 ...
## $ ADY : int 1 2 3 4 5 6 7 8 1 2 ...
## $ avisit : Factor w/ 8 levels "Day 1","Day 2",..: 1 2 3 4 5 6 7 8 1 2 ...
## $ subjid : Factor w/ 30 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 2 2 ...
## $ fasfl : chr "Y" "Y" "Y" "Y" ...
## $ trt01p : Factor w/ 3 levels "drug 1%","drug 2%",..: 3 3 3 3 3 3 3 3 3 3 ...
model2 <- lme(aval ~ avisit*trt01p,
random= ~ 1|subjid,
correlation=corGaus(form= ~ ADY|subjid, nugget=TRUE),
data=dat_red)
model2
## Linear mixed-effects model fit by REML
## Data: dat_red
## Log-restricted-likelihood: -399.1451
## Fixed: aval ~ avisit * trt01p
## (Intercept) avisitDay 2
## 19.49493395 -0.68019970
## avisitDay 3 avisitDay 4
## -1.72135278 -1.66418045
## avisitDay 5 avisitDay 6
## -2.12187918 -1.88219555
## avisitDay 7 avisitDay 8
## -1.62350126 -3.38598872
## trt01pdrug 2% trt01pdrug 3%
## -0.22718582 0.03474644
## avisitDay 2:trt01pdrug 2% avisitDay 3:trt01pdrug 2%
## -1.24565963 -0.63569630
## avisitDay 4:trt01pdrug 2% avisitDay 5:trt01pdrug 2%
## -1.99745680 -2.40608341
## avisitDay 6:trt01pdrug 2% avisitDay 7:trt01pdrug 2%
## -2.46674762 -2.71482070
## avisitDay 8:trt01pdrug 2% avisitDay 2:trt01pdrug 3%
## -4.32494765 -0.76171299
## avisitDay 3:trt01pdrug 3% avisitDay 4:trt01pdrug 3%
## -1.17534468 -1.91081325
## avisitDay 5:trt01pdrug 3% avisitDay 6:trt01pdrug 3%
## -3.14694029 -1.78482245
## avisitDay 7:trt01pdrug 3% avisitDay 8:trt01pdrug 3%
## -3.05396441 -3.06099087
##
## Random effects:
## Formula: ~1 | subjid
## (Intercept) Residual
## StdDev: 0.8457847 1.554617
##
## Correlation Structure: Gaussian spatial correlation
## Formula: ~ADY | subjid
## Parameter estimate(s):
## range nugget
## 2.1578670 0.2814681
## Number of Observations: 240
## Number of Groups: 30
anova(model2)
## numDF denDF F-value p-value
## (Intercept) 1 189 5570.580 <.0001
## avisit 7 189 37.082 <.0001
## trt01p 2 27 9.660 7e-04
## avisit:trt01p 14 189 3.009 3e-04
#sighat <- extract.lme.cov(model2, data = sim)
#sighat2 <- extract.lme.cov2(model2, data = sim)
# model with unstructured covariance structure does not converge
# model3 <- lme(aval ~ avisit*trt01p,
# random= ~ 1|subjid,
# correlation=corSymm(),
# data=dat_red)
Validation plots
plot(model2)
plot(model2, resid(., type="p") ~ fitted(.) | trt01p)
qqnorm(model2)
Differences of least squares means at Day 8
library(emmeans)
## Warning: package 'emmeans' was built under R version 3.4.4
emm <- emmeans(model2, pairwise ~ trt01p| avisit)
emm$contrasts
## avisit = Day 1:
## contrast estimate SE df t.ratio p.value
## drug 1% - drug 2% 0.22718582 0.7914779 27 0.287 0.9557
## drug 1% - drug 3% -0.03474644 0.7914779 27 -0.044 0.9989
## drug 2% - drug 3% -0.26193226 0.7914779 27 -0.331 0.9415
##
## avisit = Day 2:
## contrast estimate SE df t.ratio p.value
## drug 1% - drug 2% 1.47284545 0.7914779 27 1.861 0.1695
## drug 1% - drug 3% 0.72696655 0.7914779 27 0.918 0.6335
## drug 2% - drug 3% -0.74587890 0.7914779 27 -0.942 0.6188
##
## avisit = Day 3:
## contrast estimate SE df t.ratio p.value
## drug 1% - drug 2% 0.86288212 0.7914779 27 1.090 0.5282
## drug 1% - drug 3% 1.14059825 0.7914779 27 1.441 0.3347
## drug 2% - drug 3% 0.27771612 0.7914779 27 0.351 0.9345
##
## avisit = Day 4:
## contrast estimate SE df t.ratio p.value
## drug 1% - drug 2% 2.22464262 0.7914779 27 2.811 0.0239
## drug 1% - drug 3% 1.87606681 0.7914779 27 2.370 0.0630
## drug 2% - drug 3% -0.34857581 0.7914779 27 -0.440 0.8990
##
## avisit = Day 5:
## contrast estimate SE df t.ratio p.value
## drug 1% - drug 2% 2.63326923 0.7914779 27 3.327 0.0069
## drug 1% - drug 3% 3.11219386 0.7914779 27 3.932 0.0015
## drug 2% - drug 3% 0.47892462 0.7914779 27 0.605 0.8186
##
## avisit = Day 6:
## contrast estimate SE df t.ratio p.value
## drug 1% - drug 2% 2.69393344 0.7914779 27 3.404 0.0057
## drug 1% - drug 3% 1.75007601 0.7914779 27 2.211 0.0873
## drug 2% - drug 3% -0.94385743 0.7914779 27 -1.193 0.4677
##
## avisit = Day 7:
## contrast estimate SE df t.ratio p.value
## drug 1% - drug 2% 2.94200653 0.7914779 27 3.717 0.0026
## drug 1% - drug 3% 3.01921797 0.7914779 27 3.815 0.0020
## drug 2% - drug 3% 0.07721144 0.7914779 27 0.098 0.9948
##
## avisit = Day 8:
## contrast estimate SE df t.ratio p.value
## drug 1% - drug 2% 4.55213347 0.7914779 27 5.751 <.0001
## drug 1% - drug 3% 3.02624443 0.7914779 27 3.824 0.0020
## drug 2% - drug 3% -1.52588904 0.7914779 27 -1.928 0.1503
##
## P value adjustment: tukey method for comparing a family of 3 estimates
emm_options()
## $contrast
## $contrast$infer
## [1] FALSE TRUE
##
##
## $disable.lmerTest
## [1] FALSE
##
## $disable.pbkrtest
## [1] FALSE
##
## $emmeans
## $emmeans$infer
## [1] TRUE FALSE
##
##
## $estble.tol
## [1] 1e-08
##
## $graphics.engine
## [1] "ggplot"
##
## $lmer.df
## [1] "kenward-roger"
##
## $lmerTest.limit
## [1] 3000
##
## $msg.interaction
## [1] TRUE
##
## $msg.nesting
## [1] TRUE
##
## $pbkrtest.limit
## [1] 3000
##
## $ref_grid
## $ref_grid$is.new.rg
## [1] TRUE
##
## $ref_grid$infer
## [1] FALSE FALSE
##
##
## $save.ref_grid
## [1] TRUE
##
## $simplify.names
## [1] TRUE