Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions linearmodels/asset_pricing/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
LinearFactorModelResults,
)
from linearmodels.iv.data import IVData, IVDataLike
from linearmodels.shared.alignment import check_alignment, row_labels
from linearmodels.shared.exceptions import missing_warning
from linearmodels.shared.hypotheses import WaldTestStatistic
from linearmodels.shared.linalg import has_constant
Expand Down Expand Up @@ -82,6 +83,11 @@ class _FactorModelBase:
def __init__(self, portfolios: IVDataLike, factors: IVDataLike):
self.portfolios = IVData(portfolios, var_name="portfolio")
self.factors = IVData(factors, var_name="factor")
check_alignment(
row_labels(self.portfolios),
{"factors": self.factors},
reference="portfolios",
)
self._name = self.__class__.__name__
self._formula: str | None = None
self._validate_data()
Expand Down Expand Up @@ -214,6 +220,18 @@ class TradedFactorModel(_FactorModelBase):
tested using the estimated values :math:`\hat{\alpha}_i`. Risk premia,
:math:`\lambda_i` are estimated using the sample averages of the factors,
which must be excess returns on traded portfolios.

Rows are paired by position, so that the first row of ``factors`` is the
factor return in the period of the first row of ``portfolios``. If both
``portfolios`` and ``factors`` carry labels (a pandas Series or DataFrame
with an index other than the default 0, 1, ..., n-1, or an xarray
DataArray with coordinates), ``factors`` must use the same index, in the
same order, as ``portfolios``, so that a return cannot be attached to the
wrong period. A ``ValueError`` is raised if the index differs. Either
reindex ``factors`` to the index of ``portfolios``, or call
``to_numpy()`` to pair its values by position. NumPy arrays and pandas
objects with a default index do not carry labels and are always paired by
position.
"""

def __init__(self, portfolios: IVDataLike, factors: IVDataLike):
Expand Down Expand Up @@ -523,6 +541,18 @@ class LinearFactorModel(_LinearFactorModelBase):

The model is tested using the estimated values
:math:`\hat{\alpha}_i=\hat{\eta}_i`.

Rows are paired by position, so that the first row of ``factors`` is the
factor return in the period of the first row of ``portfolios``. If both
``portfolios`` and ``factors`` carry labels (a pandas Series or DataFrame
with an index other than the default 0, 1, ..., n-1, or an xarray
DataArray with coordinates), ``factors`` must use the same index, in the
same order, as ``portfolios``, so that a return cannot be attached to the
wrong period. A ``ValueError`` is raised if the index differs. Either
reindex ``factors`` to the index of ``portfolios``, or call
``to_numpy()`` to pair its values by position. NumPy arrays and pandas
objects with a default index do not carry labels and are always paired by
position.
"""

def __init__(
Expand Down Expand Up @@ -832,6 +862,18 @@ class LinearFactorModelGMM(_LinearFactorModelBase):

The model is tested using the optimized objective function using the
usual GMM J statistic.

Rows are paired by position, so that the first row of ``factors`` is the
factor return in the period of the first row of ``portfolios``. If both
``portfolios`` and ``factors`` carry labels (a pandas Series or DataFrame
with an index other than the default 0, 1, ..., n-1, or an xarray
DataArray with coordinates), ``factors`` must use the same index, in the
same order, as ``portfolios``, so that a return cannot be attached to the
wrong period. A ``ValueError`` is raised if the index differs. Either
reindex ``factors`` to the index of ``portfolios``, or call
``to_numpy()`` to pair its values by position. NumPy arrays and pandas
objects with a default index do not carry labels and are always paired by
position.
"""

def __init__(
Expand Down
44 changes: 43 additions & 1 deletion linearmodels/iv/absorbing.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
not_absorbed,
preconditioner,
)
from linearmodels.shared.alignment import check_alignment, is_default_index, row_labels
from linearmodels.shared.exceptions import missing_warning
from linearmodels.shared.hypotheses import InvalidTestStatistic, WaldTestStatistic
from linearmodels.shared.utility import DataFrameWrapper, SeriesWrapper
Expand Down Expand Up @@ -304,6 +305,11 @@ class Interaction:
For each variable in `cont`, computes the interaction of the variable
and the cartesian product of the categories.

If both ``cat`` and ``cont`` carry labels (a pandas Series or DataFrame
with an index other than the default 0, 1, ..., n-1, or an xarray
DataArray with coordinates), they must use the same index, in the same
order.

Examples
--------
>>> import numpy as np
Expand Down Expand Up @@ -369,6 +375,9 @@ def _check_data(self) -> None:
self._cont_data = IVData(cont, "cont", nobs=nobs, convert_dummies=False)
if self._cat_data.shape[1] == self._cont_data.shape[1] == 0:
raise ValueError("Both cat and cont are empty arrays")
check_alignment(
row_labels(self._cat_data), {"cont": self._cont_data}, reference="cat"
)
cat_data = self._cat_data.pandas
convert = [
col for col in cat_data if not (isinstance(cat_data[col], CategoricalDtype))
Expand All @@ -391,7 +400,11 @@ def cont(self) -> DataFrame:

@property
def isnull(self) -> Series:
return self.cat.isnull().any(axis=1) | self.cont.isnull().any(axis=1)
cat_null = self.cat.isnull().any(axis=1)
cont_null = self.cont.isnull().any(axis=1)
# Rows are paired by position since cat and cont need not have labels
index = cont_null.index if is_default_index(cat_null.index) else cat_null.index
return Series(cat_null.to_numpy() | cont_null.to_numpy(), index=index)

def drop(self, locs: linearmodels.typing.data.BoolArray) -> None:
self._cat_data.drop(locs)
Expand Down Expand Up @@ -660,6 +673,18 @@ class AbsorbingLS:
estimates a particular restricted set of parameters that captures the
effect of non-redundant components in z.

Rows are paired by position. An input that carries labels (a pandas
Series or DataFrame with an index other than the default 0, 1, ..., n-1,
or an xarray DataArray with coordinates) must use the same index, in the
same order, as ``dependent``, so that a value cannot be attached to the
wrong observation. A ``ValueError`` is raised if the index of ``exog``,
``absorb``, ``interactions`` or ``weights`` differs from the index of
``dependent``. Either reindex the input to the index of ``dependent``, or
call ``to_numpy()`` to pair its values by position. NumPy arrays and
pandas objects with a default index do not carry labels and are always
paired by position. The same rule applies to ``clusters`` passed to
``fit``.

See also
--------
Interaction
Expand Down Expand Up @@ -725,6 +750,7 @@ def __init__(
self._absorbed_exog: DataFrame | None = None

self._check_shape()
self._check_alignment()
self._original_index = self._dependent.pandas.index
self._drop_locs = self._drop_missing()
self._columns = self._exog.cols
Expand Down Expand Up @@ -786,6 +812,19 @@ def _check_shape(self) -> None:
"observations".format(str(interact))
)

def _check_alignment(self) -> None:
"""Check that inputs with labels use the labels of dependent"""
expected = row_labels(self._dependent)
check_alignment(expected, {"exog": self._exog, "weights": self._weights})
interactions: list[tuple[str, Interaction]] = [("absorb", self._absorb_inter)]
single = isinstance(self._interactions, (DataFrame, Interaction))
for i, interact in enumerate(self._interaction_list):
name = "interactions" if single else f"interactions[{i}]"
interactions.append((name, interact))
for name, interact in interactions:
check_alignment(expected, {name: interact.cat})
check_alignment(expected, {name: interact.cont})

@property
def absorbed_dependent(self) -> DataFrame:
"""
Expand Down Expand Up @@ -1094,6 +1133,9 @@ def fit(
stacklevel=2,
)
absorb_options = dict(lsmr_options.items())
check_alignment(
row_labels(self._dependent), {"clusters": cov_config.get("clusters")}
)
if self._absorbed_dependent is None:
self._first_time_fit(use_cache, absorb_options, method)

Expand Down
Loading
Loading