Source code for mrv.models.hmm
"""mrv.models.hmm -- Gaussian Hidden Markov Model."""
from __future__ import annotations
import logging
from typing import Optional
import numpy as np
import pandas as pd
# Default RNG seed for reproducible model fitting.
_DEFAULT_SEED = 1
logger = logging.getLogger(__name__)
__all__ = ["fit_hmm"]
[docs]
def fit_hmm(features: pd.DataFrame, K: int = 3, **kwargs) -> Optional[np.ndarray]:
"""Fit a Gaussian Hidden Markov Model and return Viterbi-decoded regime labels.
Parameters
----------
features : pd.DataFrame
Normalised feature matrix. Rows with NaN are dropped before fitting.
K : int, default 3
Number of hidden states (regime states).
**kwargs
``covariance_type`` (default ``"full"``), ``n_iter`` (default 200),
and ``random_state`` (default 1) are forwarded to
``hmmlearn.hmm.GaussianHMM``.
Returns
-------
np.ndarray or None
Integer label array of shape ``(n_obs,)`` where ``n_obs = len(features.dropna())``,
or ``None`` when the input is too short to fit (fewer than
``max(K * 5, 20)`` rows after NaN removal).
Raises
------
ImportError
If ``hmmlearn`` is not installed (``pip install hmmlearn``).
"""
try:
from hmmlearn.hmm import GaussianHMM
except ImportError:
raise ImportError(
"HMM requires hmmlearn. Install with: pip install hmmlearn"
) from None
X = features.dropna().values
if len(X) < max(K * 5, 20):
logger.warning("HMM: insufficient data (%d rows)", len(X))
return None
hmm = GaussianHMM(
n_components=K,
covariance_type=kwargs.get("covariance_type", "full"),
n_iter=kwargs.get("n_iter", 200),
random_state=kwargs.get("random_state", _DEFAULT_SEED),
)
hmm.fit(X)
return hmm.predict(X)