Skip to content
Open
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
10 changes: 10 additions & 0 deletions linearmodels/panel/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,11 @@ def entity_ids(self) -> linearmodels.typing.data.IntArray:
2d array containing entity ids corresponding dataframe view
"""
index = self.index
if np.any(index.codes[0] < 0):
raise ValueError(
"The entity index contains missing values (NaN), which are not "
"supported and are encoded as -1 in the entity ids."
)
return np.asarray(index.codes[0])[:, None]

@property
Expand All @@ -381,6 +386,11 @@ def time_ids(self) -> linearmodels.typing.data.IntArray:
2d array containing time ids corresponding dataframe view
"""
index = self.index
if np.any(index.codes[1] < 0):
raise ValueError(
"The time index contains missing values (NaN), which are not "
"supported and are encoded as -1 in the time ids."
)
return np.asarray(index.codes[1])[:, None]

def _demean_both_low_mem(self, weights: PanelData | None) -> PanelData:
Expand Down
6 changes: 6 additions & 0 deletions linearmodels/panel/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ def _lstsq(


def panel_structure_stats(ids: linearmodels.typing.data.IntArray, name: str) -> Series:
ids = np.asarray(ids)
if np.any(ids < 0):
raise ValueError(
f"{name} cannot be computed: ids must be non-negative integers. "
"Negative ids indicate missing values (NaN) in the panel index."
)
bc = np.bincount(ids)
bc = bc[bc > 0]
index = ["mean", "median", "max", "min", "total"]
Expand Down
31 changes: 29 additions & 2 deletions linearmodels/tests/panel/test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
import numpy as np
from numpy.linalg import lstsq, pinv
from numpy.testing import assert_allclose, assert_equal
from pandas import Categorical, DataFrame, Series, date_range, get_dummies
from pandas import Categorical, DataFrame, MultiIndex, Series, date_range, get_dummies
from pandas.api.types import is_string_dtype
from pandas.testing import assert_frame_equal, assert_index_equal
import pytest

from linearmodels.panel.data import PanelData, _Panel
from linearmodels.panel.model import PanelOLS
from linearmodels.panel.model import PanelOLS, panel_structure_stats
from linearmodels.shared.utility import panel_to_frame
from linearmodels.tests.panel._utility import MISSING_XARRAY, datatypes, generate_data

Expand Down Expand Up @@ -223,6 +223,33 @@ def test_ids(mi_df):
assert np.ptp(tids[i::7]) == 0


def test_entity_ids_with_missing_index():
n, t = 4, 3
index = MultiIndex.from_tuples(
[(np.nan if i == 0 else f"e{i}", j) for i in range(n) for j in range(1, t + 1)],
names=["entity", "time"],
)
data = PanelData(DataFrame(np.random.standard_normal((n * t, 2)), index=index))
with pytest.raises(ValueError, match="entity index contains missing"):
_ = data.entity_ids


def test_time_ids_with_missing_index():
n, t = 4, 3
index = MultiIndex.from_tuples(
[(f"e{i}", np.nan if j == 1 else j) for i in range(n) for j in range(1, t + 1)],
names=["entity", "time"],
)
data = PanelData(DataFrame(np.random.standard_normal((n * t, 2)), index=index))
with pytest.raises(ValueError, match="time index contains missing"):
_ = data.time_ids


def test_panel_structure_stats_negative_ids():
with pytest.raises(ValueError, match="non-negative"):
panel_structure_stats(np.array([0, 1, -1]), "test")


def test_str_repr(mi_df):
data = PanelData(mi_df)
assert "PanelData" in str(data)
Expand Down