mixle.inference.bayesian_network module

Heterogeneous Bayesian network learning – a directed graph over mixed-type fields with parametric edges.

This deepens mixle.inference.structure (a single-parent forest with quantile-binned continuous parents) into the real thing: a DAG where a field may have several parents, and continuous dependence is a parametric conditional, not a binning. A continuous child is a conditional-linear-Gaussian node – child ~ N(w . [continuous parents, one-hot(discrete parents)] + b, sigma^2) – so a real driven by two reals, or by a category and a real, is modeled exactly and cheaply (closed-form least squares). A discrete/count child with all-discrete parents conditions on their joint configuration (marginal backoff for unseen configs); with a continuous driver it becomes a GLM node (logistic / Poisson log-link / multinomial softmax), so category<->real dependence is representable in BOTH orientations and BIC picks the cheaper one.

learn_bayesian_network grows each node’s parent set greedily by description-length gain, up to max_parents, keeping the graph acyclic. The result scores, samples, and composes like any mixle distribution – the moat: automatic discovery and fitting of a heterogeneous graphical model across arbitrary families, which no mainstream tool does (Stan/PyMC: you write it; sklearn/pomegranate: independence; bnlearn/pgmpy: discrete-or-Gaussian).

class HeterogeneousBayesianNetwork(factors)[source]

Bases: object

A DAG joint over a heterogeneous record: log p(x) = sum_i log P(x_i | parents(i)) over fitted factors.

Parameters:

factors (Sequence[Any])

edges()[source]

Return DAG edges as (parent_field, child_field) pairs.

Return type:

list[tuple[int, int]]

describe(field_names=None)[source]

A real, structured explanation of what the fit found – every field’s per-factor report (_MarginalFactor.describe() and friends: fitted regression coefficients, GLM weights, the conditional table, or the marginal summary), split into edges (fields with >=1 parent) and roots (independent fields). Pass field_names (e.g. the tuple position -> column name map) to label everything by name instead of integer index. Every value is read off this network’s own fitted parameters – nothing here is a placeholder or a re-derived approximation.

Parameters:

field_names (Sequence[str] | None)

Return type:

dict[str, Any]

log_density(x)[source]

Evaluate the joint log density of one record.

Parameters:

x (tuple)

Return type:

float

seq_log_density(encoded)[source]

Evaluate joint log density for encoded records.

Parameters:

encoded (Any)

Return type:

ndarray

dist_to_encoder()[source]

Return the encoder for record batches consumed by seq_log_density.

Return type:

Any

sampler(seed=None)[source]

Return a sampler for the fitted Bayesian network.

Parameters:

seed (int | None)

Return type:

Any

class MixtureOfBayesianNetworks(components, weights)[source]

Bases: object

A latent mixture whose components each carry their own heterogeneous DAG (regression edges and all).

The fullest form of the moat: it discovers the clustering and each cluster’s cross-field graphical model, so the slope of one field on another (or the whole dependency graph) can differ between clusters – which a single network cannot represent. log p(x) = logsumexp_k(log w_k + log p_k(x)) over HeterogeneousBayesianNetwork components. Fit by learn_mixture_bayesian_network().

Parameters:
  • components (Sequence[HeterogeneousBayesianNetwork])

  • weights (Sequence[float])

log_density(x)[source]

Evaluate the mixture joint log density of one record.

Parameters:

x (tuple)

Return type:

float

seq_log_density(encoded)[source]

Evaluate mixture joint log density for encoded records.

Parameters:

encoded (Any)

Return type:

ndarray

dist_to_encoder()[source]

Return the record encoder shared by all mixture components.

Return type:

Any

responsibilities(data)[source]

Return posterior component probabilities for each record.

Parameters:

data (Sequence[tuple])

Return type:

ndarray

sampler(seed=None)[source]

Return a sampler for the mixture of Bayesian networks.

Parameters:

seed (int | None)

Return type:

Any

property n_components: int

Return the number of mixture components.

learn_mixture_bayesian_network(data, n_components, *, restarts=3, max_iter=12, seed=0, max_parents=2, min_gain=0.0, max_its=30, em='hard')[source]

Fit a MixtureOfBayesianNetworks by EM: discover clusters and each cluster’s DAG.

em="hard" (default): each iteration re-learns a network per cluster on its assigned points and reassigns every record to its most-probable cluster, until assignments stabilize. em="soft": proper EM – every record contributes to EVERY cluster with its responsibility, each component’s structure search and factor fits are responsibility-weighted (learn_bayesian_network(weights=)), and convergence is on the observed-data log-likelihood; boundary points shape both clusters instead of whipsawing between them. Both run from the same restarts (k-means-seeded + random); best final log-likelihood wins. Starved clusters are re-seeded (hard) / responsibility-floored (soft).

Parameters:
Return type:

MixtureOfBayesianNetworks

bayesian_network_bic(model, data)[source]

BIC (lower is better) for a fitted network or mixture: -2 LL + n_params log n.

Parameters:
Return type:

float

select_mixture_components(data, k_values=(1, 2, 3, 4), *, em='hard', seed=0, **kwargs)[source]

Model selection over the number of clusters by BIC.

Fits learn_bayesian_network() for k=1 and learn_mixture_bayesian_network() for each larger k, scores each by bayesian_network_bic(), and returns (best_model, report) where report = {"k": chosen, "bic": {k: score, ...}}. BIC’s n_params log n penalty is what stops a mixture of per-cluster DAGs from always preferring more clusters.

Parameters:
Return type:

tuple[Any, dict[str, Any]]

learn_bayesian_network(data, *, max_parents=2, min_gain=0.0, max_its=30, weights=None, _routing=None)[source]

Discover a heterogeneous DAG for data and return the fitted network.

Each field greedily gains up to max_parents parents by BIC-penalized conditional likelihood, keeping the graph acyclic. Continuous children become conditional-linear-Gaussian factors (regression on continuous + one-hot discrete parents); discrete/count children condition on the joint config of their discrete parents, or become GLM nodes when a driver is continuous. With weights (soft-EM responsibilities), every factor fit and the BIC search itself are responsibility-weighted, with effective sample size sum(weights).

A field the conditional-linear-Gaussian machinery cannot read as a real number – a string column with more than 64 distinct values, or a numeric column carrying missing values – is modeled by the same automatic detector mixle.utils.automatic.get_estimator() uses (a categorical/ignored leaf, or an Optional-wrapped one) and takes no part in the edge search; every other field still gets the full search. Such a column used to be handed a Gaussian template and crash inside the Gaussian encoder. See _column_routing(), which makes that decision; _routing is a private hook letting a caller that fits many components on SLICES of one dataset decide it once on the whole dataset instead.

Parameters:
Return type:

HeterogeneousBayesianNetwork