import numpy as np
import pytest
import glmm
_LABELS = ["low", "high", "med", "low", "high", "med"]
_Y = [1.0, 3.0, 2.0, 1.1, 3.1, 2.1]
class _FakeCategorical:
def __init__(self, categories, codes):
self.categories = categories
self.codes = codes
def __iter__(self):
return iter([self.categories[c] for c in self.codes])
def __len__(self):
return len(self.codes)
def test_declared_level_order_sets_the_reference_level():
data = {
"y": _Y,
"f": _FakeCategorical(["low", "med", "high"], [0, 2, 1, 0, 2, 1]),
}
result = glmm.fit(data, "y ~ f")
assert result.names == ["(Intercept)", "fmed", "fhigh"]
assert result.beta[0] == pytest.approx(1.05, abs=1e-6)
def test_plain_string_column_still_sorts_lexicographically():
result = glmm.fit({"y": _Y, "f": _LABELS}, "y ~ f")
assert result.names == ["(Intercept)", "flow", "fmed"]
assert result.beta[0] == pytest.approx(3.05, abs=1e-6)
def test_categorical_of_non_strings_is_not_fit_as_numeric():
data = {"y": _Y, "f": _FakeCategorical([10, 20, 30], [0, 2, 1, 0, 2, 1])}
result = glmm.fit(data, "y ~ f")
assert result.names == ["(Intercept)", "f20", "f30"]
def test_missing_category_code_is_rejected():
data = {"y": _Y, "f": _FakeCategorical(["low", "med"], [0, 1, -1, 0, 1, 0])}
with pytest.raises(ValueError, match="missing values"):
glmm.fit(data, "y ~ f")
def test_pandas_categorical_round_trips():
pd = pytest.importorskip("pandas")
df = pd.DataFrame(
{
"y": _Y,
"f": pd.Categorical(_LABELS, categories=["low", "med", "high"], ordered=True),
}
)
result = glmm.fit(df, "y ~ f")
assert result.names == ["(Intercept)", "fmed", "fhigh"]
assert result.beta[0] == pytest.approx(1.05, abs=1e-6)
df2 = pd.DataFrame({"y": _Y, "f": _LABELS})
assert glmm.fit(df2, "y ~ f").names == ["(Intercept)", "flow", "fmed"]
def test_vcov_matches_se_and_is_symmetric():
result = glmm.fit({"y": _Y, "f": _LABELS}, "y ~ f")
p = len(result.beta)
assert result.vcov.shape == (p, p)
assert np.allclose(np.sqrt(np.diag(result.vcov)), result.se)
assert np.allclose(result.vcov, result.vcov.T)