Repository navigation
BUG: Raise a ValueError for a debiased factor model without residual … - #802
Merged
Merged
Conversation
…degrees of freedom
With as many observations as regressors in the time-series regressions, nobs
equal to nfactor + 1, the regressions fit perfectly and have no residual degrees
of freedom. The debiased covariance, which is the default, divides by
nobs - (nfactor + 1) = 0, and TradedFactorModel.fit and LinearFactorModel.fit
raised a bare ZeroDivisionError ("division by zero").
They now raise a ValueError that says why and what to do:
The covariance cannot be debiased when the number of observations (3) is not
larger than the number of factors plus one (3), the number of regressors of
each time-series regression, since there are no residual degrees of freedom.
Use debiased=False or more observations.
Fitting with debiased=False, or with at least one more observation, is not
changed. The model already requires nobs >= nfactor + 1.
LinearFactorModelGMM.fit estimates the starting values with LinearFactorModel,
and used its default, debiased covariance, which the starting values do not need.
It raised the same ZeroDivisionError for a saturated model even with
debiased=False, and would have raised this ValueError, which refers to an option
that the user did not choose. The starting values are now estimated with
debiased=False. Such a model has too few observations for the GMM weight matrix,
and the GMM estimator now fails with the error of the singular weight matrix
instead of a ZeroDivisionError.
test_saturated_model.py checks the error, the models that work, and that the
starting values of the GMM are not debiased.
The factor models check the factors for a constant and then for full column rank:
if has_constant(f)[0]:
raise ValueError("factors must not contain a constant or equivalent.")
if np.linalg.matrix_rank(f) < f.shape[1]:
raise ValueError("Model cannot be estimated. factors do not have full column rank.")
has_constant also returns True when the matrix does not have full column rank, so
the first check caught collinear factors and the message for them could only be
reached by a matrix with more columns than rows. Factors that were a duplicated
column, or a linear combination of the others, raised "factors must not contain a
constant or equivalent."
The rank is now computed first. If has_constant is True and the factors do not
have full column rank, they are reported as containing a constant only if a
column is constant, or, for a matrix with more rows than columns, if a constant is
in the span of the columns, the cases that has_constant finds besides the lack of
rank. Otherwise they raise "Model cannot be estimated. factors do not have full
column rank." The messages of factors with a constant, including two constant
columns, a constant that is not one and a constant in the span of the factors, do
not change, and neither do those of factors with more columns than rows. The
has_constant function is not changed.
test_factor_collinearity.py checks the messages of duplicated and combined
columns, of the combinations of a constant and collinear columns, and of valid
factors, for the three models, with NumPy and pandas. The test of test_model.py
that expected the message of a constant for a duplicated factor expects the
message of the rank.
sigma, the covariance that weights the test portfolios in the second stage, was
not validated. The constructor accepted an array of the wrong shape and the
error appeared later, from the first use of sigma, or not at all:
- an array that is not nportfolio by nportfolio failed in fit with
"matmul: Input operand 1 has a mismatch in its core dimension 0";
- a matrix that is not positive definite failed in fit with
LinAlgError("SVD did not converge in Linear Least Squares"), or gave a
NaN with a RuntimeWarning from the square root of a negative eigenvalue;
- a matrix that is not symmetric was used through its lower triangle by the
eigendecomposition and through its full self by the inverse.
The constructor, and so from_formula, now raises a ValueError that names the
problem if sigma
- does not have shape (nportfolio, nportfolio): "sigma must be a square array
with shape (6, 6), where 6 is the number of test portfolios. The shape of
sigma is (5, 5).";
- contains missing or infinite values;
- is not symmetric, up to a relative difference of 1e-8 of the largest element,
so that a product of matrices that is symmetric up to rounding is accepted;
- is not positive definite, which includes a covariance that is singular, such
as the covariance of 6 portfolios from 4 observations.
Valid values are not changed: the arrays, data frames, nested lists and integer
arrays that are accepted give the same results, and the existing GLS tests pass
unchanged.
test_sigma_validation.py checks each error and the accepted inputs, and that
the second stage uses the sigma that was given. The docstrings of sigma in the
class and in from_formula describe the requirements.
_str, the number formatter of the summaries of the IV, panel, system and asset
pricing results, takes the base 10 logarithm of the absolute value to choose the
format, and int(np.ceil(np.inf)) raised
OverflowError: cannot convert float infinity to integer
A parameter with a standard error of 0 has an infinite t-statistic, which made
summary fail. A system whose equation fits exactly has a standard error of 0:
SUR({"a": {"dependent": 2 * x, "exog": x}, ...}).fit(method="ols").summary
_str now formats an infinite value with the format of the large numbers,
" inf" and " -inf", which have the width of the other numbers. Missing
values are blank, as before, and the format of the finite values does not change.
test_io.py checks the two infinities, the width, the finite formats that did not
change, and the summary of a system with a zero standard error.
…orModel.fit The check that the covariance can be debiased moves from the first statement of LinearFactorModel.fit to after the sizes of the model are unpacked. There is no change in behavior: the check still runs before any computation. It leaves the line that unpacks the sizes unchanged, which another change edits.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #802 +/- ##
==========================================
+ Coverage 99.60% 99.62% +0.01%
==========================================
Files 136 143 +7
Lines 26057 28244 +2187
Branches 1873 1985 +112
==========================================
+ Hits 25954 28138 +2184
- Misses 38 41 +3
Partials 65 65
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…degrees of freedom
With as many observations as regressors in the time-series regressions, nobs
equal to nfactor + 1, the regressions fit perfectly and have no residual degrees
of freedom. The debiased covariance, which is the default, divides by
nobs - (nfactor + 1) = 0, and TradedFactorModel.fit and LinearFactorModel.fit
raised a bare ZeroDivisionError ("division by zero").
They now raise a ValueError that says why and what to do:
Fitting with debiased=False, or with at least one more observation, is not
changed. The model already requires nobs >= nfactor + 1.
LinearFactorModelGMM.fit estimates the starting values with LinearFactorModel,
and used its default, debiased covariance, which the starting values do not need.
It raised the same ZeroDivisionError for a saturated model even with
debiased=False, and would have raised this ValueError, which refers to an option
that the user did not choose. The starting values are now estimated with
debiased=False. Such a model has too few observations for the GMM weight matrix,
and the GMM estimator now fails with the error of the singular weight matrix
instead of a ZeroDivisionError.
test_saturated_model.py checks the error, the models that work, and that the
starting values of the GMM are not debiased.