Closed slacouture closed 4 years ago
To get a notion where the problem is this could help:
If I specify only
cXs <- c("age", "unemployment")
and don't include them in 2nd and 3rd order, so the iv-like
specification is
RF <- "YD ~ 1 + age + unemployment + female + age*female + unemployment*female + unemployment*age + (Z==2) + (Z==1) + (Z==2)*age + (Z==1)*age + (Z==2)*unemployment + (Z==1)*unemployment + (Z==2)*female + (Z==1)*female + (Z==2)*age*female + (Z==1)*age*female + (Z==2)*unemployment*female + (Z==1)*unemployment*female + (Z==2)*unemployment*age + (Z==1)*unemployment*age",
the program works.
But if I try to add another covariate, say "pop"
, so the iv-like
specification takes the form
RF<- "YD ~ 1 + age + unemployment + pop + female + age*female + unemployment*female + pop*female + unemployment*age + pop*age + pop*unemployment + (Z==2) + (Z==1) + (Z==2)*age + (Z==1)*age + (Z==2)*unemployment + (Z==1)*unemployment + (Z==2)*pop + (Z==1)*pop + (Z==2)*female + (Z==1)*female + (Z==2)*age*female + (Z==1)*age*female + (Z==2)*unemployment*female + (Z==1)*unemployment*female + (Z==2)*pop*female + (Z==1)*pop*female + (Z==2)*unemployment*age + (Z==1)*unemployment*age + (Z==2)*pop*age + (Z==1)*pop*age + (Z==2)*pop*unemployment + (Z==1)*pop*unemployment"
or if I add only the 2nd order continuous variables so
RF <- "YD ~ 1 + age + unemployment + female + age*female + unemployment*female + unemployment*age + age2 + unemployment2 + (Z==2) + (Z==1) + (Z==2)*age + (Z==1)*age + (Z==2)*unemployment + (Z==1)*unemployment + (Z==2)*female + (Z==1)*female + (Z==2)*age*female + (Z==1)*age*female + (Z==2)*unemployment*female + (Z==1)*unemployment*female + (Z==2)*unemployment*age + (Z==1)*unemployment*age + (Z==2)*age2 + (Z==1)*age2 + (Z==2)*unemployment2 + (Z==1)*unemployment2"
the program breaks.
Sorry for being slow to get back to you on this.
There is something peculiar about the design matrix implied by your IV-like specification.
Call that design matrix X
.
Using R's qr()
command, one can confirm that X is full column rank.
However, this is not the case for t(X) %*% X
.
> qr(X)$rank
[1] 72
> qr(t(X) %*% X)$rank
[1] 5
Trying to invert t(X) %*% X
using solve()
or qr.solve
isn't going to work.
However, you can perform a Cholesky decomposition, and then invert that instead.
> invXX <- chol2inv(chol(t(X) %*% X))
> invXX[1:4, 1:4]
[,1] [,2] [,3] [,4]
[1,] 7.492741e+00 -0.0078332681 -1.503016e+02 -101.37416922
[2,] -7.833268e-03 0.0003881945 7.115998e-02 0.01031024
[3,] -1.503016e+02 0.0711599824 1.790002e+04 796.97611202
[4,] -1.013742e+02 0.0103102391 7.969761e+02 1531.45891953
Making that change resolved the problem above. However, I'm not entirely sure what this means for your IV-like specification.
Running your code (see below, though), the model eventually became infeasible after a sufficient number of audits. The design of the package should limit such cases, so I'm looking into that (issue #178).
Give the repo a pull, and let me know if (i) this issue is resolved on your end, too; (ii) you run into the an infeasibility issue.
If you restrict the number of audits to be sufficiently small, e.g. audit.max = 2
, you should still get a bound, though.
(The code that was uploaded wouldn't run, since the object Z.form
was not declared.
I assume you meant Zs
.
Making that change, I was able to replicate the error.
So let me know if Z.form
and Zs
are actually different things.)
What does lm
do for this type of inversion?
If lm
is able to do it, then we also want our code to be able to do it.
Maybe there is a function somewhere in the lm
code that we can use directly?
I wonder if it is this: https://github.com/SurajGupta/r-source/blob/a28e609e72ed7c47f6ddfbb86c85279a0750f0b7/src/library/stats/R/lm.R#L114
z <- .Call(C_Cdqrls, x, y, tol, FALSE)
Looks like z
is a list with some stuff.
Can we use that?
Getting down to the C function quickly here is probably the fastest way to go as well.
Sure, I can give that a shot.
Also, this page does a deep dive into the lm()
command, discussing the C and Fortran components.
That's very useful. We should be doing it the same way base R
is.
Actually, I missed some details earlier, and am wondering if changes really need to be made.
The regressions by the package already use base R, i.e. lm.fit
.
So getting the beta estimates is not a problem, even for the example above.
The error @slacouture found was was to do with the matrix inversion when constructing the S-weights, e.g.
The lm()
code we looked at calls C to perform the regression using a QR decomposition.
But the QR decomposition is to avoid having to invert a matrix.
So I won't get back (X'X)^{-1}
from the C output.
Let me know if we can indeed benefit from using the C command, though. (In addition to the coefficients and residuals from the regression, the C output includes the QR decomposition for the design matrix.)
Thanks for looking into this @jkcshea. First of all, regarding the mistake in the replication code, the correct replacement for Z.form
is not Zs
but paste(Zs, collapse = " + ")
. Sorry about that. This shouldn't affect the reproduction of the error as you noticed but potentially the estimation as it will be supplying an incorrect specification of propensity
.
I pulled the repo and the problem of singularity is solved. However, although I am getting the same error of infeasibility reported in #178, I am receiving a different error message:
Error: Automatic grid expansion limit reached. The LP problem is still unbounded. Either impose additional shape constraints, or increase the size of the initial grid for the audit. Since the initial grid must be a subset of the audit grid, it may be necessary to increase the size of the audit grid also. The most recent options after automatic expansion were:
initgrid.nx = 68
initgrid.nu = 25
audit.nx = 2500
audit.nu = 25
Only if I expand the audit grid (audit.nu = 50
for instance) I get the same message as in #178.
If I restrict the number of audits as you suggest I do get a bound, but this bound is just crazy. Does this mean I am incorrectly using the program?
What are the Xtilde's that you have there in the S-weight problem? Once we run the OLS we should be able to construct the S-weighting function with any additional inversion steps.
If I restrict the number of audits as you suggest I do get a bound, but this bound is just crazy. Does this mean I am incorrectly using the program?
Oh no, that's not what I meant. I realize what I said is misleading. As the audit procedure continues, there are more and more constraints in the LP problem. So it makes sense that the problem becomes infeasible later in the audit procedure. However, these infeasibility errors have become uncommon since the package allows for some slack in the equality constraints (that is what the 'criterion minimization' problem refers to). So this just warrants some investigation.
If each iteration doesn't take too long to run, you can increase the number of audits so that your estimates will better satisfy the shape constraints, e.g. until you have 0 violations of the shape constraints. But here we see that only leads to infeasibility.
What are the Xtilde's that you have there in the S-weight problem?
Sorry, the Xtilde is just the matrix of covariates for the IV-like specification.
Once we run the OLS we should be able to construct the S-weighting function with any additional inversion steps.
I interpret the last part as "without any additional inversion steps."
So is there another way I can construct the S-weighting functions from the matrix of covariates and coefficient estimates, without inverting anything?
Yes without -- sorry!
Ok, so the issue as I understand it is that lm
never actually computes the inverse of the design matrix.
And yet we apparently need this object for our S--weights.
However, I think that need is really just apparent, not actual. That is, if we code it differently, we do not ever need to invert the design matrix.
I'm going to use the notation in our working paper.
The only thing we need the function s for is to compute \Gamma{sdk}.
(Please correct me if I am wrong here --- it's been a while since I looked at this.)
The \beta{s} we get directly from the lm
/ivregress
routine.
Now look at the definition of \Gamma{sdk} on pg. 4. It has the form: E[s(d,X,Z) \times stuff] where stuff is an integral that depends on the basis functions and propensity score. In your example above, this should be the same as regressing stuff against \tilde{X}, then the e{j} part just picks out the jth component of the resulting vector of coefficients.
That means we could just use lm
to compute \Gamma_{sdk} without having to ever construct the s function at all.
Let me know if that makes sense.
Ah, that's fantastic! Very simple, too. Sure, I can implement that.
Ok, great. Some modification is probably needed since our IV--like estimands are of the TSLS form, right? But I believe that should just mean the same idea works with "regressing stuff against \tilde{X}" replaced by "run TSLS with stuff as the outcome variable" --- but please check.
Are the unit tests sufficiently mature that they will catch unforeseen problems with this new formulations? Just skimming through them, I don't see any tests of the form "we know this is the right estimate/bound to get, let's check that we got it." Might be good to put in one of those as a sanity check before making the change.
Some modification is probably needed since our IV--like estimands are of the TSLS form, right? But I believe that should just mean the same idea works with "regressing stuff against \tilde{X}" replaced by "run TSLS with stuff as the outcome variable" --- but please check.
Yep, that looks right.
Are the unit tests sufficiently mature that they will catch unforeseen problems with this new formulations? Just skimming through them, I don't see any tests of the form "we know this is the right estimate/bound to get, let's check that we got it." Might be good to put in one of those as a sanity check before making the change.
Ah, but that is indeed what the testthat
checks do.
e.g. they end with
##-------------------------
## Test bounds
##-------------------------
test_that("LP problem", {
expect_equal(result$bound, bound)
})
result$bound
is estimated using the package.
bound
is manually calculated.
A relevant test is also that the gamma moments are constructed correctly.
##-------------------------
## Test equivalence of Gamma terms
##-------------------------
test_that("Gamma moments", {
expect_equal(as.numeric(c(result$gstar$g0, result$gstar$g1)),
as.numeric(unlist(g.star.late)))
expect_equal(as.numeric(c(result$s.set$s1$g0, result$s.set$s1$g1)),
as.numeric(unlist(g.ols.d)))
expect_equal(as.numeric(c(result$s.set$s2$g0, result$s.set$s2$g1)),
as.numeric(unlist(g.ols.x1)))
})
This check should be more meaningful now, since the testthat
code will have constructed the gamma moments using the S-weighting functions, rather than this new regression method.
Question: @johnnybonney and @cblandhol have requested the S-weights be included in the output before. So should this become an option?
Ah ok, I just skimmed the tests so didn't look closely enough. That's great then, should be easy to do the modification without breaking anything. A big win for disciplined unit testing!
@johnnybonney and @cblandhol have requested the S-weights be included in the output before.
And we're okay with keeping the old method as an option?
e.g. ivmte(..., return.weights = TRUE)
.
Ah yes, good point, yeah I suppose we should preserve that. However, the new method should definitely be the default.
@slacouture The cause of the error was due to scaling of certain variables.
The pop
variable is in the order of 1e6
.
This variable is then squared and cubed, resulting in a range of magnitude beyond double precision.
This document by Gurobi briefly explains the issue with scaling.
And this other document by Gurobi provides some examples.
I rescaled your pop
variable as follows:
data[, pop := pop / 1e5]
The package was able to provide reasonable bounds in under a minute. However, the minimum criterion becomes enormous, which may indicate a problem with the model. Let me know if the changes work on your end.
Thank you, @jkcshea I was not aware of that issue with scaling on Gurobi. It is working for me now. I am going to check what may be driving the large minimum criterion.
Hi! In advance, sorry for the long post: I am trying to be as detailed as I can:
When running the package for estimating the MTRs using splines, I am sometimes receiving a particular error message as if the IV-like spec was perfectly collinear. You can find here a zip-file with the data and the code reproducing the error.
What the code is doing is to try to add more covariates to the estimation to get more variation in the propensity score. In particular, for a set of covariates
I add linear interactions and 2nd and 3rd order values and interact with the instrument
Z
to have the following formulae:that I use in the
iv-like
argument;that I use to specify the
propensity
argument; andused for the MTR specification
The exact arguments used are:
I then receive the following output:
However if I run an standard
lm
model on the RF:Just as a check you can see that the
lm
package estimates all values, thus it is not dropping any redundantOne detail that might be relevant is that the
unemployment
,elderly
andpop
variables are observed at the municipality level. Is this driving the problem? Is there a way to overcome it?Thank you!