Repository navigation
To be released together with welt-client 0.14 (API-164): decoding with domain knowledge
(API-151), graph evaluation (API-152) and the Welt Chronos-2 forecaster (API-163).
Added
-
Soft domain knowledge.
require,forbid,forbid_adjacency,forbid_between,
order,with_tiers,root,sinkandrestrict_totakeconfidence=. Left
out (or 1) a statement is the hard constraint it always was. A value in[0.5, 1)
makes it soft: it lands in the newDomainKnowledge.softfield as a
KnowledgeStatement(checked on construction), and 0.5 changes nothing. Values
below 0.5, above 1, NaN or a bool raiseValueError(state the opposite instead).
The confidence applies to each pair a statement covers.confoundstays hard
only.&pools soft statements as a set, so a statement made twice counts once. -
DomainKnowledge.log_odds(variables): the soft statements' summed log likelihood
ratios on each directed edge, against "no edge" on its pair.log_prior(graph)and
energy(graph), which used to raise, now score a graph under the soft statements
(minus infinity when it breaks a hard constraint). Soft orders are weighed pair by
pair and are not closed transitively.DomainKnowledge.statements()lists every
statement, read from the stored fields (hard) and the soft set, so equal knowledge
lists equal statements.DomainKnowledge.hard()drops the soft statements. -
decode(..., knowledge=)weighs soft statements: each pair's directed states are
multiplied by the statement ratios and renormalized before any decoder reads them,
so all six decoders run unchanged andexact_mapis exact on the combined score.
A soft weight on what a hard constraint settles is dropped. Greedy with
threshold=multiplies the input value's odds by the ratio on that direction.
diagnostics["knowledge_objective"]is the score on the weighted states;
objectivekeeps scoring the input. -
decodewithconfound(a, b)returns an ADMG: the directed part is decoded as
before anda <-> bis added, to the graph and every candidate. A pair that also
got a directed edge (a bow) carries its confounder as an explicit latent node
L__a__b.identify_effectreads both.diagnosticsaddgraph_kind,
confounded_pairsandlatent_confounders; the repr counts observed variables and
names the latent confounders. -
A knowledge audit on every decode with knowledge:
diagnostics["knowledge_audit"]
has one entry per statement (the model's probability that it holds, from direct
edges; the weakest of its pairs and the pairs below 0.1; whether it holds, an order
read through directed paths, in the decoded graph and in a knowledge-free decode
with the same settings and seed),knowledge_costthe score the knowledge cost,
andknowledge_conflictsthe statements with a pair the model gives under 0.1.
knowledge_violations_without_knowledgelists whatviolations()finds on the
knowledge-free graph,confoundpairs left out, so an order broken only through
the closure shows too.
n_hard_statementsandn_soft_statementscount them.decode(..., audit=False)
andDecoder(..., audit=False)skip it. -
ergodic.KnowledgeConflictWarning(aUserWarning), raised once by a decode whose
knowledge strongly conflicts with the model, pointing at your call. -
test_graph(candidate, data)(alsoergodic.discovery.test_graph) tests a
candidate graph against data (API-152, DEC-082). It lists one implied
independence per pair of observed variables without an edge, tests each with the
conditional-independence testdiscoverwould pick from the columns (ortest=),
and combines the p-values with Fisher's C on2kdegrees of freedom. A DAG uses
Shipley's basis set (the parents of both); a CPDAG uses the same set in its
consistent extension; an ADMG, a MAG, a DAG withlatent=orselection=nodes
and a PAG (through one member MAG) use m-separation, with the parents of both when
they separate and otherwise a minimal set of the pair's observed ancestors. Every
conditioning set is checked against the graph's separation routine, and a pair no
observed set separates is reported inuntestable. The result, a frozen
GraphTest, carries the statistic, p-value,k, the test,alpha, notes, and the
claims (ImpliedIndependence) ranked by p-value with Holm-adjusted p-values and
decisions;to_dict()andto_frame()export it. The combined p-value is the
level-alphadecision (rejected); the Holm decisions locate the failing claims.
Claims whose test has zero degrees of freedom go tovacuousand are left out of
k. An infinite value in an observed continuous column, or a non-finite p-value or
statistic from the test, raises instead of reading as a pass; missing rows are
dropped with a note. Only the default partial-correlation test is calibrated; the
result notes any other test, and says thatGCMwith a nearest-neighbour learner
and G squared with fewer than 1,000 rows over-reject true graphs. Claims whose
p-values tie (for example, underflowed to 0) rank by the larger|statistic|. The
p-value cannot separate Markov-equivalent graphs and a pass does not confirm the
graph; the new guide "Test a graph against data" says so. Size with the default
partial-correlation test on six-variable linear-Gaussian worlds atalpha = 0.05:
the registered run (200 worlds per sample size) rejected the true graph in 9.5% of
worlds with 200 rows, outside its [2%, 8%] band, and 4.5% with 1,000; a
pre-registered replication on 2,000 fresh worlds per size gave 4.65% [3.77%, 5.67%]
and 4.85% [3.95%, 5.88%], inside its [3.5%, 6.5%] band. Power against one missing
edge of weight at least 0.5 is close to 1. -
ergodic.welt.WeltForecaster(also importable asergodic.welt.Chronos) runs
Welt's hosted Chronos-2 as an ergodicForecastModel(API-163, DEC-087).fit
checks the columns and keeps the window locally; eachforecast(horizon, *, seed, future=None)is one paidPOST /v1/forecastsjob (DEC-085), sent with
welt.Client.submit_forecastand read with the SDK'sJob.result. Every target column
is one series of a panel by default (cross_learning=False), or the targets of one
series withmultivariate=True.past_covariates=andfuture_covariates=map to
Welt's covariate roles, categorical by the dataset's schema (cells sent as strings
or integers,Trueas1; other cells as finite numbers, a boolean as 0.0/1.0);
future covariates needfuture=(a dataframe orTimeSeriesData, only those
columns are read). A
HierarchicalTSDatais forecast at every node (an aggregate without a column from
its children's summed history, a node left out oftargets=from its children's
draws), andreconcile=reconciles insideforecast. The first forecast reads the
catalogue entry (free) and refuses, before paying, a model without theforecast
operation or a request outside every measuredforecast_envelopescorner;
max_context=sends only the most recent rows. Tables always go inline, as
welt-client sends them, and a request overwelt.forecast.MAX_REQUEST_BYTES
(the API's 10 MiB limit) is refused before sending. The first job's model and
configuration versions are required of every later job of the forecaster and its
copies (and checked against each result), which also share one client and one
max_forecastsbudget (10) of jobs Welt accepted. Each node's
WeltForecastDiagnosticsrecords the job id, versions, quantile levels, Welt's own
quantiles,context_lengthandrearranged_steps. A numeric time index, or a
datetime index on a calendar Welt has no unit for (B,C,BME,SMEand the
like, declared or inferred by pandas; a declared one is trusted as given), is sent as
consecutive days from 2000-01-01; any other irregular datetime index
(daily data with missing days, say) is refused with a hint to reindex and leave
the gaps as NaN.NATIVE_QUANTILE_LEVELSlists the 21 levels requested by default. -
ergodic.forecast.quantile_draws(levels, quantiles, *, draws, seed)turns
per-step quantiles into(S, H)draws by inverse-CDF sampling: rows sorted
(monotone rearrangement), linear between levels, exponential tails matched to the
density at the outer levels, each step sampled independently. The draws reproduce
the per-step marginals only, so sums over steps or series, and the intervals,
coverage and CRPS of abottom_upormintreconciliation, treat the errors as
independent. -
ergodic.forecast.CovariateForecastModel: aForecastModelthat names
future_covariatesand takes their values over the horizon as
forecast(..., future=...).backtesthands such a model, at each origin, those
columns over the held-out window and nothing else, and records them in
BacktestReport.future_covariates. It refuses, before any fit, a declared
covariate that is not a column, a string in place of a sequence, and one the model
names among itstargets; a model that forecasts a declared covariate without
naming it is refused on the first fold's result. Models without the attribute are called
exactly as before. -
New guide "Forecast with Welt's Chronos-2": forecast with a known promotion
calendar, backtest against baselines, and reconcile a hierarchy. -
A posterior over DAGs under a method (API-152, DEC-082).
graph_posterior(probabilities)
weighs each DAG by its pair log score (the pair states the decoders read) plus the
knowledge'slog_prior, over the DAGs the hard knowledge allows; order statements are
read ancestrally, asviolationsreads them.graph_posterior(data, score=...)uses a
decomposable score with a prior uniform over DAGs times the knowledge; BGe and BDeu are
marginal likelihoods, the BIC family is labelled approximate. The result,
GraphSamples, holds the distinct DAGs with weights,edge_probabilities()(pair
states) withedge_errors(),probability(feature)with a Monte Carlo error,
log_target(graph),best_graphandtop(k). It is exact when it can be: every DAG
enumerated up to 4 variables (5 for a score), and for edge probabilities without a
path-level order constraint a subset recursion over sink sets (about3^nsteps, up
tomax_exact=15) that gives the exact normalizer, exact edge marginals, exact draws
and the exact best graph. Otherwise multi-chain structure MCMC (single-pair add,
delete and reverse moves; each chain a four-rung parallel-tempering ladder; chains
from the greedy decode and random DAGs) with split
R-hat and bulk effective sample sizes, Rao-Blackwellized edge marginals for edge
probabilities, and aPosteriorConvergenceWarning. -
evaluate_graph(candidate, probabilities_or_samples)reads a DAG, CPDAG, PAG, MAG or
ADMG against that posterior (GraphEvaluation): the log-ratio to the best graph, the
credible level (the mass ranked strictly above, ties half; a Bayesian tail
probability, not a p-value), oneGraphClaimper statement with its probability and
Monte Carlo error, the class probability of a CPDAG, the expected SHD against the
between-draw baseline (with the former's Monte Carlo error), and P(acyclic) under raw
independent marginals. When every draw agrees, a Monte Carlo error is the rule-of-three
bound3 / draws(boundonFeatureProbability,GraphClaimand
AdjustmentGroup) rather than 0, and under the exact
recursion the candidate's own share of its credible level is exact. A bidirected
edge or latent node is listed as not assessable. -
Effects over a posterior over DAGs (DEC-082 decision 6).
adjustment_probability(samples, treatment, outcome, adjustment)is the posterior probability that a set satisfies the
adjustment criterion.average_effect(samples, data, treatment, outcome)groups the
DAGs by the adjustment set identification picks, estimates once per set (the family
default, seeded;strategy=,learner=,rule="minimal") and returns an
AveragedEffect: theAdjustmentGroups (set, mass with its Monte Carlo error, DAG
count, estimate, standard error), the mixture mean with its Monte Carlo error from the
masses, and its variance by the law of
total variance, and the zero-effect (no directed path), unidentified (a variable the
data lacks makes every set invalid) and truncated (beyondmax_sets) masses, the last
two left out of the mixture and reported. -
The
BGescore (score="bge"): the linear-Gaussian marginal likelihood of Geiger and
Heckerman with the Kuipers, Moffa and Heckerman correction and an empirical-Bayes prior
mean (the sample mean; BiDAG uses zero, which agrees on centred data), decomposable and
score-equivalent, forgraph_posteriorand the score-based searches.
Score.marginal_likelihoodsays whether a score is one (BDeu, BGe).
Changed
- A decode with knowledge, hard-only knowledge included, now audits it: one extra
knowledge-free decode, which can cost more than the constrained one, and a
KnowledgeConflictWarningon strong conflicts. Code that turns warnings into
errors will see it; passaudit=Falseor filter the category. - The decoder aliases
"expected_count"and"soft_f1"now resolve to the new
expected_count_v2andsoft_f1_v2, which take their edge budget from the pair
states with the hard knowledge applied (a forbidden direction holds no mass, a
required one all of it), so a soft statement near 1 gives the hard result's budget.
expected_count_v1andsoft_f1_v1keep their 0.2 results; without knowledge the
two versions agree. Glasso.fitvalidates the knowledge first and raises on a contradiction (a
confounded pair under an adjacency ban or outside arestrict_tosupport, for
example) instead of dropping part of it.- The discovery searches (
PC,FCI,RFCI,GES,BOSS,GRaSP,HillClimb,
AStar,MMPC,DirectLiNGAM,Glasso,PCMCI) andbootstrapraise
ValueErroron soft statements instead of dropping them; passknowledge.hard(). forbid_adjacency(a, b)also records the pair in a newnonadjacentfield: an
adjacency ban rules out a bidirected edge too, whileforbid(a, b)with
forbid(b, a)no longer does. Soconfound(a, b)with the two directed bans means
"confounded, and neither causes the other": it is not a conflict, the decoders
returna <-> b, FCI and RFCI keep the pair adjacent instead of pruning it, MMPC
keeps it in its skeleton and glasso no longer zeroes it.forbid_adjacency(a, b)
and the two directed bans no longer compare equal. The repr is unchanged. Every
nonadjacentpair also forbids both directed edges, however the object is built.- A
DomainKnowledgebuilt directly with both directions of a pair inforbidden
(the constructor ordataclasses.replace) no longer bans a bidirected edge on
that pair; useforbid_adjacencyornonadjacent=for that. DomainKnowledge.apply_tokeeps a bidirected edge on a pair forbidden in both
directions; only an adjacency ban removes every mark.DomainKnowledge.conflicts()reports a confounded pair under an adjacency ban.
violations()reads a node ingraph.latentas a hidden cause: a latent node with
two or more observed children joins each pair of them as a bidirected edge would
(forconfound, adjacency bans and a skeleton restriction), and edges at a latent
node count against neither a root nor a sink.ergodic.welt.discover(data, model, knowledge=k)withoutdecoder=decodes with
greedy at the model's native threshold (greedy's default rule when the model
records none), asdecoder="greedy"does, instead of raising. Confounded knowledge
is no longer refused before the paid run; knowledge naming unknown variables or
contradicting itself still is. Without knowledge and without a decoder nothing
changes.- Pickles of
DomainKnowledgefrom 0.2.x load, with no soft statements. A pair the
pickle forbids in both directions becomes an adjacency ban (nonadjacent), its
0.2 meaning, so it still rules out a bidirected edge. - The
weltextra requireswelt-client>=0.14, the release that serves Chronos-2
forecasting (DEC-085):WeltForecastersubmits through
welt.Client.submit_forecastand waits with the SDK'sJob.result.