Specify a model in R given coefficients and covariance matrices - r

I'm trying to implement a predictive model from a publication see here for reference
The paper specifies predictive models that are derived from a previous clinical study and provides the coefficients and covariance matrices for each.
I'm fairly familiar with fitting a model to data in R - but I've never had to specify one.
Specifically, I am looking to create the model so that I can leverage predict() to generate predictive outcomes for a different set of patients while accounting of the model's variability.
For convenience I've provided the one of the two models and related coefficients and covariance matrices, both are of a similar form. Any help is greatly appreciated:
# Model 1
# TKV model
#
# delta_TKV = exp(intercept + a x age + b x Ln(TKV_t) + c x female + d x age x Ln(TKV_t)) - 500
# delta_TKV - the change in total kidney volume (TKV) over a period of time in years
# age - age of patient in years
# Ln(TKV_t) - natural log of total kidney volume at time t
# female - boolean value for gender
# age:Ln(TKV_t) - interaction term between age and Ln(TKV)
# Coefficients Estimate SE
# intercept 0.7889 1.1313
# age 0.1107 0.0287
# Ln(TKV) 0.8207 0.1556
# Female -0.0486 0.0266
# Age:Ln(TKV) -0.0160 0.0039
# Covariance intercept age Ln(TKV) Female Age:Ln(TKV)
# intercept 1.279758 -0.031790 -0.175654 -0.001306 0.004362
# age -0.031790 0.00823 0.004361 -0.000016 -0.000113
# Ln(TKV) -0.175651 0.004361 0.024207 -0.000155 -0.000601
# Female -0.001306 -0.000016 0.000155 0.000708 0.000002
# Age:Ln(TKV) 0.004362 -0.000113 -0.000601 0.000002 0.000016

I don't known wether you can generate a model to be used with predict with custom coefficients. But you can use model.frame or model.matrix to generate a design matrix based on your formula e.g.
data = data.frame(delta_TKV = 1:3 , TKV_t = 3.5, female = c(T,F,T), age = 40:42 )
model = model.frame(log(delta_TKV + 500) ~ age + log(TKV_t) + female + age:log(TKV_t),
data)
model
#> (Intercept) age log(TKV_t) femaleTRUE age:log(TKV_t)
#> 1 1 40 1.252763 1 50.11052
#> 2 1 41 1.252763 0 51.36328
#> 3 1 42 1.252763 1 52.61604
#> attr(,"assign")
#> [1] 0 1 2 3 4
#> attr(,"contrasts")
#> attr(,"contrasts")$female
#> [1] "contr.treatment"
coefs = c(
intercept = 0.7889 ,
age = 0.1107 ,
`log(TKV)` = 0.8207 ,
female = -0.0486 ,
`Age:log(TKV)` = -0.0160
)
model %*% coefs
#> [,1]
#> 1 5.394674
#> 2 5.533930
#> 3 5.575986
i transformed the formula to make it like lm spec, thus the response is the logarithm of y + 500 and you must obtain y doing the reverse, the same apply if you were to use lm

Related

Formula with interaction terms in event-study designs using R

I am estimating what's often called the "event-study" specification of a difference-in-differences model in R. Basically, we observe treated and control units over time and estimate a two-way fixed effects model with parameters for the "effect" of being treated in each time period (omitting one period, usually the one before treatment, as the reference period). I am struggling with how to compactly specify this model with R formulas.
For example, here is the model...
library(lfe)
library(tidyverse)
library(dummies)
N <- 100
df <- tibble(
id = rep(1:N, 5),
treat = id >= ceiling(N / 2),
time = rep(1:5, each=N),
x = rnorm(5 * N)
)
# produce an outcome variable
df <- df %>% mutate(
y = x - treat * (time == 5) + time + rnorm(5*N)
)
head(df)
# easily recover the parameters with the true model...
summary(felm(
y ~ x + I(treat * (time == 5)) | id + time, data = df
))
Now, I want to do an event-study design using period 4 as the baseline because treatment happens in period 5. We expect coefficients near zero on the pre-periods (1–4), and a negative treatment effect for the treated in the treated period (time == 5)
df$timefac <- factor(df$time, levels = c(4, 1, 2, 3, 5))
summary(felm(
y ~ x + treat * timefac | id + time, data = df
))
That looks good, but produces lots of NAs because several of the coefficients are absorbed by the unit and time effects. Ideally, I can specify the model without those coefficients...
# create dummy for each time period for treated units
tdum <- dummy(df$time)
df <- bind_cols(df, as.data.frame(tdum))
df <- df %>% mutate_at(vars(time1:time5), ~ . * treat)
# estimate model, manually omitting one dummy
summary(felm(
y ~ x + time1 + time2 + time3 + time5 | id + time, data = df
))
Now, the question is how to specify this model in a compact way. I thought the following would work, but it produces very unpredictable output...
summary(felm(
y ~ x + treat:timefac | id + time, data = df
))
With the above, R does not use period 4 as the reference period and sometimes chooses to include the interaction with untreated rather than treated. The output is...
Coefficients:
Estimate Std. Error t value Pr(>|t|)
x 0.97198 0.05113 19.009 < 2e-16 ***
treatFALSE:timefac4 NA NA NA NA
treatTRUE:timefac4 -0.19607 0.28410 -0.690 0.49051
treatFALSE:timefac1 NA NA NA NA
treatTRUE:timefac1 -0.07690 0.28572 -0.269 0.78796
treatFALSE:timefac2 NA NA NA NA
treatTRUE:timefac2 NA NA NA NA
treatFALSE:timefac3 0.15525 0.28482 0.545 0.58601
treatTRUE:timefac3 NA NA NA NA
treatFALSE:timefac5 0.97340 0.28420 3.425 0.00068 ***
treatTRUE:timefac5 NA NA NA NA
Is there a way to specify this model without having to manually produce dummies and interactions for treated units for every time period?
If you know Stata, I'm essentially looking for something as easy as:
areg y x i.treat##ib4.time, absorb(id)
(Note how simple it is to tell Stata to treat the variable as categorical — the i prefix —without making dummies for time and also indicate that period 4 should be the base period — the b4 prefix.)
The package fixest performs fixed-effects estimations (like lfe) and includes utilities to deal with interactions. The function i (or interact) is what you're looking for.
Here is an example where the treatment is interacted with the year and year 5 is dropped out:
library(fixest)
data(base_did)
est_did = feols(y ~ x1 + i(treat, period, 5) | id + period, base_did)
est_did
#> OLS estimation, Dep. Var.: y
#> Observations: 1,080
#> Fixed-effects: id: 108, period: 10
#> Standard-errors: Clustered (id)
#> Estimate Std. Error t value Pr(>|t|)
#> x1 0.973490 0.045678 21.312000 < 2.2e-16 ***
#> treat:period::1 -1.403000 1.110300 -1.263700 0.206646
#> treat:period::2 -1.247500 1.093100 -1.141200 0.254068
#> treat:period::3 -0.273206 1.106900 -0.246813 0.805106
#> treat:period::4 -1.795700 1.088000 -1.650500 0.099166 .
#> treat:period::6 0.784452 1.028400 0.762798 0.445773
#> treat:period::7 3.598900 1.101600 3.267100 0.001125 **
#> treat:period::8 3.811800 1.247500 3.055500 0.002309 **
#> treat:period::9 4.731400 1.097100 4.312600 1.8e-05 ***
#> treat:period::10 6.606200 1.120500 5.895800 5.17e-09 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> Log-likelihood: -2,984.58 Adj. R2: 0.48783
The nice thing is that you can plot the interacted coefficients out of the estimation to have a quick visual representation of the results (if you find the graph too sober, no worries you can customize almost everything in it):
coefplot(est_did)
If you don't want to use fixest for estimation, you can still use the function i to create interactions. Its syntax is i(var, f, ref, drop, keep): it interacts the variable var with a dummy variable for each value in f. You can select which values of f to retain with the arguments ref, drop and keep. drop well... drops values from f and ref is the same as drop, but the references are shown in the coefplot (while the values in drop don't appear in the graph).
Here's an example of what i does:
head(with(base_did, i(treat, period, keep = 3:7)))
#> treat:period::3 treat:period::4 treat:period::5 treat:period::6 treat:period::7
#> 1 0 0 0 0 0
#> 2 0 0 0 0 0
#> 3 1 0 0 0 0
#> 4 0 1 0 0 0
#> 5 0 0 1 0 0
#> 6 0 0 0 1 0
head(with(base_did, i(treat, period, drop = 3:7)))
#> treat:period::1 treat:period::2 treat:period::8 treat:period::9 treat:period::10
#> 1 1 0 0 0 0
#> 2 0 1 0 0 0
#> 3 0 0 0 0 0
#> 4 0 0 0 0 0
#> 5 0 0 0 0 0
#> 6 0 0 0 0 0
You can find more information on fixest here.
You can redefine the timefac so that untreated observations are coded as the omitted time category.
df %>%
mutate(time = ifelse(treat == 0, 4, time),
timefac = factor(time, levels = c(4, 1, 2, 3, 5)))
Then, you can use timefac without interactions and get a regression table with no NAs.
summary(felm(
y ~ x + timefac | id + time, data = df
))
Coefficients:
Estimate Std. Error t value Pr(>|t|)
x 0.98548 0.05028 19.599 < 2e-16 ***
time_fac1 -0.01335 0.27553 -0.048 0.961
time_fac2 -0.10332 0.27661 -0.374 0.709
time_fac3 0.24169 0.27575 0.876 0.381
time_fac5 -1.16305 0.27557 -4.221 3.03e-05 ***
This idea came from: https://blogs.worldbank.org/impactevaluations/econometrics-sandbox-event-study-designs-co

Average Marginal Effects in R with complex interaction terms

I am using R to compute the linear regression on the following model, as well as find the marginal effects of age on pizza at specific points (20,30,40,50,55).
mod6.22c <- lm(pizza ~ age + income + age*income +
I((age*age)*income), data = piz4)
The problem I am running into is when using the margins command, R does not see interaction terms that are inserted into the lm with I((age x age) x income). The margins command will only produce accurate average marginal effects when the interaction terms are in the form of variable1 x variable1. I also can't create a new variable in my table table$newvariable <- table$variable1^2, because the margins command won't identify newvariable as related to variable1.
This has been fine up until now, where my interaction terms have only been a quadratic, or an xy interaction, but now I am at a point where I need to calculate the average marginal effects with the interaction term AGE^2xINCOME included in the model, but the only way I can seem to get the summary lm output to be correct is by using I(age^2*(income)) or by creating a new variable in my table. As stated before, the margins command can't read I(age^2*(income)), and if I create a new variable, the margins command doesn't recognize the variables are related, and the average marginal effects produced are incorrect.
The error I am receiving:
> summary(margins(mod6.22c, at = list(age= c(20,30,40,50,55)),
variables = "income"))
Error in names(classes) <- clean_terms(names(classes)) :
'names' attribute [4] must be the same length as the vector [3]
I appreciate any help in advance.
Summary of data:
Pizza is annual expenditure on pizza, female, hs, college and grad are dummy variables, income is in thousands of dollars per year, age is years old.
> head(piz4)
pizza female hs college grad income age agesq
1 109 1 0 0 0 19.5 25 625
2 0 1 0 0 0 39.0 45 2025
3 0 1 0 0 0 15.6 20 400
4 108 1 0 0 0 26.0 28 784
5 220 1 1 0 0 19.5 25 625
6 189 1 1 0 0 39.0 35 1225
Libraries used:
library(data.table)
library(dplyr)
library(margins)
tldr
This works:
mod6.22 <- lm(pizza ~ age + income + age*income, data = piz4)
**summary(margins(mod6.22, at = list(age= c(20,30,40,50,55)), variables = "income"))**
factor age AME SE z p lower upper
income 20.0000 4.5151 1.5204 2.9697 0.0030 1.5352 7.4950
income 30.0000 3.2827 0.9049 3.6276 0.0003 1.5091 5.0563
income 40.0000 2.0503 0.4651 4.4087 0.0000 1.1388 2.9618
income 50.0000 0.8179 0.7100 1.1520 0.2493 -0.5736 2.2095
income 55.0000 0.2017 0.9909 0.2036 0.8387 -1.7403 2.1438
This doesn't work:
mod6.22c <- lm(pizza ~ age + income + age*income + I((age * age)*income), data = piz4)
**summary(margins(mod6.22c, at = list(age= c(20,30,40,50,55)), variables = "income"))**
Error in names(classes) <- clean_terms(names(classes)) :
'names' attribute [4] must be the same length as the vector [3]
How do I get margins to read my interaction variable I((age*age)*income)?

covariance structure for multilevel modelling

I have a multilevel repeated measures dataset of around 300 patients each with up to 10 repeated measures predicting troponin rise. There are other variables in the dataset, but I haven't included them here.
I am trying to use nlme to create a random slope, random intercept model where effects vary between patients, and effect of time is different in different patients. When I try to introduce a first-order covariance structure to allow for the correlation of measurements due to time I get the following error message.
Error in `coef<-.corARMA`(`*tmp*`, value = value[parMap[, i]]) : Coefficient matrix not invertible
I have included my code and a sample of the dataset, and I would be very grateful for any words of wisdom.
#baseline model includes only the intercept. Random slopes - intercept varies across patients
randomintercept <- lme(troponin ~ 1,
data = df, random = ~1|record_id, method = "ML",
na.action = na.exclude,
control = list(opt="optim"))
#random intercept and time as fixed effect
timeri <- update(randomintercept,.~. + day)
#random slopes and intercept: effect of time is different in different people
timers <- update(timeri, random = ~ day|record_id)
#model covariance structure. corAR1() first order autoregressive covariance structure, timepoints equally spaced
armodel <- update(timers, correlation = corAR1(0, form = ~day|record_id))
Error in `coef<-.corARMA`(`*tmp*`, value = value[parMap[, i]]) : Coefficient matrix not invertible
Data:
record_id day troponin
1 1 32
2 0 NA
2 1 NA
2 2 NA
2 3 8
2 4 6
2 5 7
2 6 7
2 7 7
2 8 NA
2 9 9
3 0 14
3 1 1167
3 2 1935
4 0 19
4 1 16
4 2 29
5 0 NA
5 1 17
5 2 47
5 3 684
6 0 46
6 1 45440
6 2 47085
7 0 48
7 1 87
7 2 44
7 3 20
7 4 15
7 5 11
7 6 10
7 7 11
7 8 197
8 0 28
8 1 31
9 0 NA
9 1 204
10 0 NA
10 1 19
You can fit this if you change your optimizer to "nlminb" (or at least it works with the reduced data set you posted).
armodel <- update(timers,
correlation = corAR1(0, form = ~day|record_id),
control=list(opt="nlminb"))
However, if you look at the fitted model, you'll see you have problems - the estimated AR1 parameter is -1 and the random intercept and slope terms are correlated with r=0.998.
I think the problem is with the nature of the data. Most of the data seem to be in the range 10-50, but there are excursions by one or two orders of magnitude (e.g. individual 6, up to about 45000). It might be hard to fit a model to data this spiky. I would strongly suggest log-transforming your data; the standard diagnostic plot (plot(randomintercept)) looks like this:
whereas fitting on the log scale
rlog <- update(randomintercept,log10(troponin) ~ .)
plot(rlog)
is somewhat more reasonable, although there is still some evidence of heteroscedasticity.
The AR+random-slopes model fits OK:
ar.rlog <- update(rlog,
random = ~day|record_id,
correlation = corAR1(0, form = ~day|record_id))
## Linear mixed-effects model fit by maximum likelihood
## ...
## Random effects:
## Formula: ~day | record_id
## Structure: General positive-definite, Log-Cholesky parametrization
## StdDev Corr
## (Intercept) 0.1772409 (Intr)
## day 0.6045765 0.992
## Residual 0.4771523
##
## Correlation Structure: ARMA(1,0)
## Formula: ~day | record_id
## Parameter estimate(s):
## Phi1
## 0.09181557
## ...
A quick glance at intervals(ar.rlog) shows that the confidence intervals on the autoregressive parameter are (-0.52,0.65), so it may not be worth keeping ...
With the random slopes in the model the heteroscedasticity no longer seems problematic ...
plot(rlog,sqrt(abs(resid(.)))~fitted(.),type=c("p","smooth"))

Use a loop, perform regression, predict new values for each person

I have a data set with 20 variables. 10 of them are variables of great interest but these variables need to be adjusted for group differences in terms of age and sex. I do this by using regression, to predict values depending on age and sex.
There are many variables, and many persons, so I want a loop or similar.
Here is an example of what I'm attempting
# Load example data
library(survival)
library(dplyr)
data(lung) # example data
# I want to obtain adjusted values for the following two variables, called "dependents"
dependents <- names(select(lung, 7:8))
new_data <- lung # copies data set
for (i in seq_along(dependents)) {
eq <- paste(dependents[i],"~ age + sex")
fit <- lm(as.formula(eq), data= new_data)
new_data$predicted_value <- predict(fit, newdata=new_data, type='response')
new_data <- rename(new_data, paste(dependents[i], "_predicted", sep="") = predicted_value)
}
View(new_data)
This failed to provide me with the "dependents" in adjusted (i.e predicted) form.
Any ideas?
Thanks in advance
Here is an alternative approach, using the tidyr package and the augment function from my broom package:
library(tidyr)
library(broom)
new_data <- lung %>%
gather(dependent, value, ph.karno:pat.karno) %>%
group_by(dependent) %>%
do(augment(lm(value ~ age + sex, data = .)))
This reorganizes the data so that each dependent (ph.karno and pat.karno) is stacked on top of each other, distinguished by a dependent column. The augment function turns each model into a data frame with columns for fitted values, residuals, and other values you care about (see ?lm_tidiers for more). The .fitted column then gives the fitted values:
new_data
#> Source: local data frame [452 x 12]
#> Groups: dependent
#>
#> dependent .rownames value age sex .fitted .se.fit .resid
#> 1 ph.karno 1 90 74 1 78.86709 1.406553 11.132915
#> 2 ph.karno 2 90 68 1 80.53347 1.115994 9.466530
#> 3 ph.karno 3 90 56 1 83.86624 1.226463 6.133759
#> 4 ph.karno 4 90 57 1 83.58851 1.181024 6.411490
#> 5 ph.karno 5 100 60 1 82.75532 1.078170 17.244683
#> 6 ph.karno 6 50 74 1 78.86709 1.406553 -28.867085
#> 7 ph.karno 7 70 68 2 80.18860 1.419744 -10.188596
#> 8 ph.karno 8 60 71 2 79.35540 1.555365 -19.355404
#> 9 ph.karno 9 70 53 1 84.69943 1.388600 -14.699433
#> 10 ph.karno 10 70 61 1 82.47759 1.056850 -12.477586
#> .. ... ... ... ... ... ... ... ...
#> Variables not shown: .hat (dbl), .sigma (dbl), .cooksd (dbl), .std.resid
#> (dbl)
As one way you could use this data, you could graph how the predictions for the dependent variables differ:
ggplot(new_data, aes(age, .fitted, color = dependent, lty = factor(sex))) +
geom_line()
If you're looking to control for the age and sex, however, you probably want to work with the .resid column.
Can't you just do this?
dependents <- names(lung)[7:8]
fit <- lm(as.formula(sprintf("cbind(%s) ~ age + sex",
paste(dependents, collapse = ", "))),
data = lung)
predict(fit)
Maybe I'm misunderstanding. Your question isn't very clear.
And a third approach.
new_data <- na.omit(lung[,c("sex","age",dependents)])
result <- lapply(new_data[,dependents],
function(y)predict(lm(y~age+sex,data.frame(y=y,new_data[,c("age","sex")]))))
names(result) <- paste(names(result),"predicted",sep="_")
result <- cbind(new_data,as.data.frame(result))
head(result)
# sex age ph.karno pat.karno ph.karno_predicted pat.karno_predicted
# 1 1 74 90 100 78.83030 77.34670
# 2 1 68 90 90 80.59974 78.53841
# 3 1 56 90 90 84.13862 80.92183
# 4 1 57 90 60 83.84371 80.72321
# 5 1 60 100 90 82.95899 80.12736
# 6 1 74 50 80 78.83030 77.34670
Your original code has a couple of subtle problems (other than the fact that it doesn't run). The response variables have a few NAs, which are removed automatically by lm(...), so the prediction has fewer rows that the original data set, and when you try to add the new column with, e.g.
new_data$predicted_value <- predict(fit, newdata=new_data, type='response')
you get an error. You have to remove the NAs from new_data first, as shown in the code above.
I'm also wondering, since your data seems to be counts of something, if you should be using a poisson glm instead of lm?

ANCOVA in R: ANCOVA vs. Error regression = should they not be the same?

I have a question that is really intriguing me. I'm not sure if the problem is with my understanding of an analysis of covariance (ancova) or it is within R. Probably the former is the what is happening here. In summary, my concern is that a regular ancova should output the same results (sums of sq. and F value) as if I would do calculations by hand (doing anova's and them regressing error terms), right? Let's go for an example:
Assume this basic dataset
> datas
Pen Sex Ration X Y
1 1 M A 38 9.52
2 1 F A 48 9.94
3 1 M B 39 8.51
4 1 F B 48 10.00
5 1 M C 48 9.11
6 1 F C 48 9.75
7 2 M A 35 8.21
8 2 F A 32 9.48
9 2 M B 38 9.95
10 2 F B 32 9.24
11 2 M C 37 8.50
12 2 F C 28 8.66
13 3 M A 41 9.32
14 3 F A 35 9.32
15 3 M B 46 8.43
16 3 F B 41 9.34
17 3 M C 42 8.90
18 3 F C 33 7.63
>
Now, perform an ancova
ancova1 = lm(Y ~ Pen + X + Sex + Ration, data=datas)
> anova(ancova1)
Analysis of Variance Table
Response: Y
Df Sum Sq Mean Sq F value Pr(>F)
Pen 2 1.3403 0.67017 2.1851 0.1588
X 1 0.7337 0.73372 2.3923 0.1502
Sex 1 0.8773 0.87728 2.8604 0.1189
Ration 2 1.1741 0.58703 1.9140 0.1935
Residuals 11 3.3737 0.30670
Keep in mind those F values (Fsex and Fration). Now, let's go for a hand computation.
First, an anova for Y
Yreg = lm(Y ~ Pen + Sex + Ration, data=datas)
> anova(Yreg)
Analysis of Variance Table
Response: Y
Df Sum Sq Mean Sq F value Pr(>F)
Pen 2 1.3403 0.67017 1.7386 0.2172
Sex 1 0.4705 0.47045 1.2204 0.2909
Ration 2 1.0626 0.53129 1.3783 0.2892
Residuals 12 4.6257 0.38548
Second, an anova for X
Xreg = lm(X ~ Pen + Sex + Ration, data=datas)
> anova(Xreg)
Analysis of Variance Table
Response: X
Df Sum Sq Mean Sq F value Pr(>F)
Pen 2 374.78 187.389 8.4325 0.005162 **
Sex 1 20.06 20.056 0.9025 0.360854
Ration 2 18.78 9.389 0.4225 0.664787
Residuals 12 266.67 22.222
Third, let's regress errorY on errorX, so we get a slope for covariate adjustment.
errorY = resid(Yreg)
errorX = resid(Xreg)
errorreg = lm(errorY ~ errorX)
> coef(errorreg)
(Intercept) errorX
5.465170e-18 6.852083e-02
You may want to note that the slope for errorX is the same as the one calculated by our first ancova (i.e. 0.06852083).
Now, it is time to go for a change of variable:
Z = datas$Y - 0.06852083*(datas$X)
datas = cbind(datas, Z)
Finally, let's perform an anova in Z
Zreg = lm(Z ~ Pen + Sex + Ration, data=datas)
> anova(Zreg)
Analysis of Variance Table
Response: Z
Df Sum Sq Mean Sq F value Pr(>F)
Pen 2 1.0602 0.53009 1.8855 0.19406
Sex 1 0.9856 0.98556 3.5056 0.08573 .
Ration 2 1.1821 0.59105 2.1023 0.16491
Residuals 12 3.3737 0.28114
Here, it comes my doubt. Should not the F value calculated for Sex and Ration be the same as the one obtained from our first regular ancova? They look different, although residuals are the same. Sum Sq. for Sex and Ration are different in anova over Z from that in ancova. Why is that?
Ok, I understand that Sum Sq for Pen is going to be different, once in the first case I did not adjusted Pen effect by covariate X. But, I would not expect different values for Sex and Ration.
Is this an R issue or am I missing something here?
Many thanks for your attention!
UPDATED
Ok, I got the problem here. Actually, the problem is like Dason said - the different degrees of freedom and something more - a portion of the effect of covariate X on Y not accounted for the change of variable Z. There seem to be a left over effect of X over Y (0.4128). Note that (now Zreg anova look the same as ancova in Y):
Zreg = lm(Z ~ X + Pen + Ration + Sex + interaction(Ration, Sex), data=datas)
> anova(Zreg)
Analysis of Variance Table
Response: Z
Df Sum Sq Mean Sq F value Pr(>F)
X 1 0.4128 0.41277 1.3459 0.2706
Pen 2 0.7637 0.38187 1.2451 0.3255
Sex 1 0.8773 0.87728 2.8604 0.1189
Ration 2 1.1741 0.58703 1.9140 0.1935
Residuals 11 3.3737 0.30670
Perhaps, a more thorough comparison would be between anova in Z and anova in Y. But a question remains... is least squares over a general linear model with both categorical and a continuous covariate different from regression of errorY on errorX and then performing a change of variable (finding Z and conducting anova in Z)? In theory, they should look the same, I guess. If not, I would expect that at least they yield the same interpretation of effects.
An ANCOVA (or linear model with categorical factor/s and quantitative covariate/s) is similar but different from ANOVA of residuals, the latter being an ad-hoc procedure that has been well criticized (e.g. different d.f., the slope estimate and adjusted values change, not a maximum likelihood procedure, etc.). For further explaination please read:
Darlington, R. B., & Smulders, T. V. (2001). Problems with residual analysis. Animal Behaviour, 62, 599-602.
Freckleton, R. P. (2002). On the misuse of residuals in ecology: regression of residuals vs. multiple regression. Journal of Animal Ecology, 542-545.
García-Berthou, E. (2001). On the misuse of residuals in ecology: testing regression residuals vs. the analysis of covariance. Journal of Animal Ecology, 708-711.

Resources