Skip to main content

PyPI version Python versions License Tests Downloads DOI

kronikas

Principled election forecasting from opinion polls, powered by hierarchical Bayesian inference.

Project site →

Most poll aggregators reduce rich, noisy data into a single point estimate and call it a day. kronikas does the opposite: it builds a full generative model of how public opinion evolves, learns each pollster's systematic biases, and propagates every source of uncertainty into honest probability distributions. The result is not just "Party A is at 42 %," but "Party A wins with 73 % probability, and here is the full distribution behind that number." If you are a political scientist, data journalist, election analyst, or anyone who needs defensible, reproducible forecasts from polling data, kronikas gives you a statistically rigorous engine you can trust, and customize, in a single pip install.

Why kronikas?

  • 🎯 Probability over point estimates. Full posterior distributions and win probabilities, not just a number. Every forecast comes with calibrated uncertainty so you know exactly what you don't know.
  • 🔧 Systematic bias correction. Automatically detects and corrects per-pollster house effects, cleanly separating genuine opinion shifts from firm-specific methodological bias.
  • 📐 Structurally sound. Dirichlet observations and softmax constraints guarantee that predicted vote shares are always non-negative and sum to exactly 100 %. No ad-hoc normalization needed.
  • ⚙️ Highly configurable. Flexible priors, per-pollster overrides, adjustable time grids, correlated random walks, and an escape hatch to any pymc.sample() kwarg. Shape the model to your domain knowledge.

Used in production

kronikas is the forecasting engine behind százkilencvenkilenc.hu, where it powers live Hungarian election forecasts and tracks real-world polling shifts as they happen.

Not a programmer? Start here

Everything below assumes Python. If you would rather answer questions than write code, kronikas ships a guided workflow — a settings file in plain words, a one-command run, and a report written to be read rather than decoded.

pip install kronikas
kronikas skill install     # hand the workflow to an AI assistant

kronikas skill install copies a skill into ~/.claude/skills/, after which Claude Code (or any assistant you paste SKILL.md into) runs the whole thing as a conversation: it asks what the election is, reads your poll file, asks what you believe about pollster bias, runs the model, and explains the result. kronikas skill path prints where the files live.

To drive it yourself, without an assistant:

  1. Describe the race. Either fill in a browser form built from your own poll file —

    kronikas form polls.csv --election-date 2026-04-12
    

    which writes a self-contained page with a control for every party and pollster it found, and the settings file updating live as you click — or copy forecast.template.yaml and edit it. Every setting is a word, not a parameter:

    election:
      date: 2026-04-12
      country: Hungary
      system: mixed                 # what "most votes" is actually worth
    polls:
      file: polls.csv
    beliefs:
      volatility: normal            # calm | normal | volatile
      pollsters:
        Meridian:
          leans: {Progress: 1.5}    # percentage points, + means overstates
          trust: low                # high | normal | low
      industry_error:
        uncertainty_pp: 2.5         # how wrong every firm could be at once
    run:
      effort: standard              # quick | standard | thorough
    
  2. Check, then run.

    kronikas guided forecast.yaml --check   # validates and reads back in English
    kronikas guided forecast.yaml           # fits the model
    

    --check catches a misspelled party or pollster name — and suggests the right one — before you spend the sampling time, not after.

  3. Read the report. You get report.html: a single self-contained page with win probabilities, forecast ranges, the trend with the polls behind it, house effects, the break-even polling error, and a model-health verdict in plain language. No internet connection to view it, nothing to install to share it. kronikas report <dir>/report_data.json rebuilds the page without refitting.

Word settings map onto the same ModelConfig documented below — volatility: volatile is sigma_walk_prior=0.10, trust: low is sigma_house=0.6 — and an advanced: block passes anything through untouched, so the guided path is a front door rather than a walled garden.

Installation

The fastest way to get started:

pip install kronikas

For development, with uv (recommended):

uv sync --group dev

Or with pip:

pip install -e ".[dev]"

Quick start

1. Prepare a CSV

Each row is one poll. Required columns: date, pollster, sample_size, plus one column per candidate with their support value (any scale; values are normalised to 100 %).

If undecided respondents are included, pass undecided_column="Undecided". That column is excluded from the candidate shares and reduces the effective sample size by the decided fraction; otherwise renormalising decided shares would make the poll look more precise than it is.

date,pollster,sample_size,Alice,Bob,Carol
2024-01-15,PollCo,1000,45,40,10
2024-02-01,SurveyInc,1200,44,42,11
2024-02-15,PollCo,800,46,39,12
2024-03-01,SurveyInc,1500,43,43,10
2024-03-15,PollCo,1000,47,38,13

2. Run a forecast

from kronikas import ElectionForecast, ModelConfig

forecast = ElectionForecast(
    polls_csv="polls.csv",
    election_date="2024-11-05",
    # today defaults to date.today(); override for reproducibility:
    today="2024-03-20",
)
result = forecast.run()
print(result.summary())

3. Inspect the output

result is a ForecastResult with:

# Per-candidate estimates for today and election day
for est in result.today_estimates:
    print(f"{est.name}: {est.mean:.1f}% (90% CI: {est.ci_lower:.1f}%-{est.ci_upper:.1f}%)")

# Plurality probabilities (election day): P(this candidate polls highest)
for name, prob in result.win_probabilities.items():
    print(f"{name}: {prob:.1%}")

# Probability of clearing a vote-share threshold (e.g. an electoral threshold)
for name, prob in result.threshold_probabilities(5.0).items():
    print(f"{name} reaches 5%: {prob:.1%}")

# Head-to-head comparison
print(result.lead_probability("Alice", "Bob"))

# Sampler convergence diagnostics
print(result.diagnostics.summary())

# Full ArviZ InferenceData for custom analysis
result.trace

What win_probabilities measures. It is the probability of holding a plurality of the vote share — of polling higher than every other candidate on election day. Under electoral systems that are not simple national plurality (runoffs, district seat allocation, electoral colleges, coalition formation), that is not the probability of winning office. Treat it as a vote-share statistic and build any seat or office model on top of party_forecast_dataframe().

4. Party forecast as a DataFrame

party_forecast_dataframe() returns all posterior draws as a pandas.DataFrame: one row per draw, one column per party named after the party. Values are vote shares in percentage points.

# Posterior draws at the reference date (today)
df_today = result.party_forecast_dataframe(day="today")

# Posterior draws at election day
df_election = result.party_forecast_dataframe(day="election_day")

# df.columns == ["Alice", "Bob", "Carol"]
# df.shape   == (num_draws * num_chains, num_candidates)
# Each row sums to 100.0 (percentage points)

Warmup iterations are never included. Only the post-tuning posterior draws that PyMC keeps after sampling.

Use these DataFrames for custom downstream analysis:

import matplotlib.pyplot as plt

# Histogram of Alice's election-day support
df_election["Alice"].plot.hist(bins=40, edgecolor="white")
plt.xlabel("Vote share (%)")
plt.title("Alice – election-day forecast")
plt.show()

# Correlations between parties
print(df_election.corr().round(2))

# Probability that Alice leads Bob on election day
p = (df_election["Alice"] > df_election["Bob"]).mean()
print(f"P(Alice > Bob) = {p:.1%}")

5. House effects as a DataFrame

house_effects_dataframe() returns all posterior draws of the per-pollster house effects as a pandas.DataFrame. Each value is the percentage-point deviation from a neutral equal-support baseline (all candidates at 1/K) produced by that pollster's bias term. Positive values mean the pollster over-estimates a candidate; negative values mean under-estimation. Within each draw and pollster, values across all candidates sum to zero.

The DataFrame uses a two-level column MultiIndex: the outer level is the pollster name and the inner level is the candidate name.

df_he = result.house_effects_dataframe()
# df_he.columns is a MultiIndex with levels ["pollster", "candidate"]
# df_he.shape == (num_draws * num_chains, num_pollsters * num_candidates)

# Inspect PollCo's bias for all candidates
print(df_he["PollCo"].mean())

# Plot the posterior distribution of PollCo's Alice bias
df_he["PollCo"]["Alice"].plot.hist(bins=40, edgecolor="white")

A RuntimeError is raised when the model was run with a single pollster (house effects are not identifiable in that case).

6. Save a forecast and reload it later

Sampling takes minutes, so a run worth keeping should not have to be repeated. save() writes the full posterior and the result's authoritative sample matrices to netCDF; load() rebuilds an equivalent ForecastResult, including any scenario created with assume_shared_bias().

result.save("forecast-2024-03-20.nc")

from kronikas import ForecastResult
restored = ForecastResult.load("forecast-2024-03-20.nc")

For publishing just the numbers, to_dict() returns a small JSON-serialisable summary with no posterior draws attached:

import json

payload = result.to_dict(thresholds=[5.0])
print(json.dumps(payload, indent=2))

7. Customise priors and sampler

config = ModelConfig(
    # --- Sampler ---
    num_tune=2000,                 # warmup iterations per chain
    num_draws=2000,                # posterior draws per chain
    num_chains=4,                  # independent MCMC chains
    cores=4,                       # CPU cores for parallel sampling
    target_accept=0.99,            # higher = fewer divergences
    init_method="adapt_full",      # better for correlated posteriors
    progressbar=True,              # show progress bar

    # --- Time grid ---
    time_step_days=3,              # finer time resolution

    # --- Priors (logit scale) ---
    sigma_walk_prior=0.03,         # smoother trend, per `walk_reference_days`
    sigma_house_prior=0.2,         # tighter house-effect prior
    initial_sigma=0.3,             # tighter prior on initial support
    kappa_log_sigma=0.3,           # tighter poll-precision prior

    # --- Escape hatch for any pymc.sample() kwarg ---
    sampler_kwargs={"nuts_sampler": "nutpie"},
)

result = ElectionForecast(
    polls_csv="polls.csv",
    election_date="2024-11-05",
    config=config,
).run()

CSV format

Column Type Description
date date string Poll date (any format pandas.to_datetime can parse, or specify date_format).
pollster string Polling firm identifier.
sample_size positive int Number of respondents.
candidate columns numeric Raw support for each candidate (normalised internally).

Column names for date, pollster, and sample_size can be overridden:

ElectionForecast(
    polls_csv="polls.csv",
    election_date="2024-11-05",
    date_column="poll_date",
    pollster_column="firm",
    sample_size_column="n",
    candidate_columns=["Dem", "Rep"],   # explicit subset
    date_format="%d/%m/%Y",             # non-ISO dates
    decimal=",",                        # European-style decimal separator
)

European-style CSVs

Some locales write numbers with a comma as the decimal point (e.g. 45,3 instead of 45.3). Use the decimal parameter to tell the reader which character to treat as the decimal separator:

ElectionForecast(
    polls_csv="polls_eu.csv",
    election_date="2024-11-05",
    decimal=",",
)

decimal defaults to "." and accepts any single character.

Polls already in memory

When the data comes from a database or an upstream cleaning step, skip the CSV round-trip:

from kronikas import ElectionForecast, polls_from_dataframe

forecast = ElectionForecast.from_dataframe(polls_frame, election_date="2024-11-05")
result = forecast.run()

# Or validate/normalise a frame on its own:
poll_data = polls_from_dataframe(polls_frame)

from_dataframe accepts the same column-name overrides as the constructor, and never mutates the frame you pass it.

Command line

Installing the package puts a kronikas executable on your path, so a scheduled job can produce machine-readable output without a Python wrapper:

# Human-readable summary
kronikas forecast polls.csv --election-date 2024-11-05

# JSON to a file, with threshold probabilities, quiet enough for cron
kronikas forecast polls.csv \
    --election-date 2024-11-05 \
    --threshold 5 --threshold 10 \
    --json forecast.json \
    --save-trace forecast.nc \
    --quiet

# Report how fragile the call is to an industry-wide polling error
kronikas forecast polls.csv \
    --election-date 2024-11-05 \
    --shared-bias 2 --shared-bias 4

# Non-ISO dates, European decimals, renamed columns
kronikas forecast polls.csv \
    --election-date 2024-11-05 \
    --date-column poll_date --pollster-column firm --sample-size-column n \
    --date-format "%d/%m/%Y" --decimal ,

# Score the model against a past election
kronikas backtest polls.csv \
    --election-date 2024-11-05 \
    --as-of 2024-08-01 --as-of 2024-10-01 \
    --actual "Alice=48.2,Bob=47.1,Carol=4.7"

--shared-bias PP reports the forecast under an assumed industry-wide error of PP points, moved from the front-runner to the runner-up; repeat it for several sizes. JSON output also carries shared_bias_breakeven_pp, the smallest such error that would erase the lead. See Shared polling error for why this cannot be measured from the polls themselves.

kronikas forecast exits non-zero when the sampler reports a convergence problem, so a scheduled run fails loudly instead of publishing bad numbers. Run kronikas forecast --help for the full option list, including sampler settings and CSV schema overrides.

Four further subcommands serve the guided workflow described above: kronikas form builds a browser form for writing a settings file, kronikas guided runs a forecast from one, kronikas report rebuilds the HTML page from a finished run, and kronikas skill install installs the assistant skill.

Backtesting

A forecast that has never been scored is an assertion, not a measurement. backtest() replays a campaign: for each as-of date it discards every later poll, refits, and records what the model would have said about election day knowing only what was available then.

from datetime import date
from kronikas import backtest

report = backtest(
    "polls.csv",
    election_date=date(2024, 11, 5),
    as_of_dates=[date(2024, 8, 1), date(2024, 9, 1), date(2024, 10, 1)],
    actual={"Alice": 48.2, "Bob": 47.1, "Carol": 4.7},
)

print(report.summary())
report.to_dataframe()   # tidy: one row per (as-of date, candidate)
report.metrics()        # MAE, RMSE, mean CRPS, 90% interval hit rate, bias

actual accepts any scale — percentages, fractions, or raw vote counts — and is normalised the same way polls are.

Two notes on reading the output. The interval hit rate is descriptive, not a calibration estimate from one election. Reliable coverage claims require many sufficiently independent elections. CRPS is the proper score for the full marginal predictive distributions. Bias is reported per candidate, never pooled — forecast and actual shares both sum to 100, so signed errors cancel exactly across candidates and a pooled mean would be identically zero.

Each as-of date costs one full MCMC fit, so lower num_draws for exploratory runs.

Convergence diagnostics

Every result carries the headline sampler statistics, and a ConvergenceWarning is raised when something looks wrong — so a script cannot quietly go on to print confident-looking numbers from chains that never mixed.

result = forecast.run()

print(result.diagnostics.summary())
result.diagnostics.converged      # False if R-hat, ESS, or divergences look bad
result.diagnostics.issues         # problems detected
result.diagnostics.notes          # caveats, e.g. R-hat needs >= 2 chains

Thresholds are configurable via ModelConfig(r_hat_threshold=..., ess_threshold=...).

A single-chain run reports converged=True with a note, not a failure: one chain leaves convergence unverified rather than demonstrating a problem. Use num_chains >= 2 to actually check it.

For model comparison, set compute_log_likelihood=True to populate the trace's log_likelihood group for arviz.loo / arviz.waic:

config = ModelConfig(compute_log_likelihood=True)
result = ElectionForecast("polls.csv", "2024-11-05", config=config).run()

import arviz as az
print(az.loo(result.trace))

Shared polling error

A bias that every pollster shares is invisible to this model, and to any model fitted to a single election's polls. The likelihood is exactly unchanged by shifting the latent trend one way and all house effects the other, so no quantity of polls or pollsters can measure it. Worse, sigma_house is learned from how much pollsters differ from each other: when they all lean the same way, the model concludes they are all accurate and passes their common error into the forecast one-for-one — while reporting a narrower interval, because it reads their agreement as precision.

On a synthetic dead heat (truth 45–45) where every pollster shaded 3 pp toward one side, the model reported 47.3 and put the probability of that side leading at 97.8 %, with a 90 % interval that excluded the truth.

House effects cannot show you this: they capture only the differences between pollsters, so their average is pinned near zero no matter how large the common error is. There is no diagnostic for it. What there is instead:

Ask what it would cost — no refit

result.assume_shared_bias({"Alice": 3.0})       # "polls overstate Alice by 3 pp"
result.shared_bias_breakeven()                   # smallest error that erases the lead

The ridge is what makes this legitimate: a shifted posterior fits the observed polls exactly as well, so this reports a different point the data cannot rule out. Offsets are in percentage points, positive meaning the polls over-state that candidate. Candidates you do not name take up the remainder in proportion to their support, so {"Alice": 3.0} moves Alice by a full 3 pp.

This is an approximation to a refit — on synthetic checks the mean matched to within 0.05 pp, though tail probabilities can differ by several points. For numbers you intend to publish, use the model term below.

Or build it into the model

from kronikas import ModelConfig, SharedBiasPrior

config = ModelConfig(
    shared_bias=SharedBiasPrior(
        mean={"Alice": 2.0, "Bob": -2.0},   # directional belief, in pp
        sd={"Alice": 1.5, "Bob": 1.5},      # and how sure you are of it
        default_sd=2.5,                     # for candidates not listed
    )
)

Both the centre and the spread are yours to set, per candidate. There is no reason to assume the industry's error is symmetric about zero — that is exactly the assumption that produced the 97.8 % call above — so if you believe polls have historically understated a party, say so.

Non-zero sd values are calibrated as marginal percentage-point standard deviations around the initial support vector. The errors are joint and zero-sum; they are not independent candidate logits. A candidate configured with zero direct error can still move indirectly because all shares must sum to 100 %.

shared_bias defaults to None, which reproduces earlier behaviour exactly: the industry is assumed collectively unbiased, with no uncertainty attached. House effects always sum to zero across candidates and pollsters, so delta means "this pollster relative to the industry". This keeps the latent trend identified even when no shared-bias prior is configured.

The scale must be supplied, not learned. A hierarchical scale estimated from the same polls collapses toward zero for the very reason above, leaving the term inert. Take the number from historical polling error in comparable races — often around 2–3 pp — or estimate it from past election results, which is the only data that can identify it.

Model

The model has three components:

  1. Latent support (logistic-normal random walk). Candidate proportions are parameterised as K-1 log-ratios relative to a reference candidate. These evolve as a Gaussian random walk on a discretised time grid (default: weekly steps). Softmax maps the log-ratios back to the probability simplex, guaranteeing non-negative shares that sum to 1.

    The grid is anchored backwards from election day, so its final node falls exactly on the election date and the election-day forecast is not contaminated by an extra partial step of drift. The grid therefore starts on or just before the first poll.

    The random-walk prior sigma_walk_prior is expressed per walk_reference_days (7 by default) rather than per time step, so changing time_step_days changes the resolution of the trend without also changing how volatile the prior says it is.

  2. House effects. Each pollster gets a bias term in log-ratio space, drawn from a zero-sum Normal prior around mu_house. House effects are strictly zero-sum constrained across both candidates and pollsters. This removes unidentifiable sampler dimensions and defines each effect relative to the polling-industry average. The mean mu_house defaults to zero (no assumed direction of bias) but can be set per pollster and per candidate to encode prior knowledge about a specific pollster's lean. When only a single pollster is present, house effects are omitted (not identifiable). Per-pollster prior overrides can replace the hierarchical sigma_house with a fixed SD for individual pollsters (see Per-pollster priors).

  3. Dirichlet observations. Each poll is modelled as Dirichlet(kappa_scale * sample_size * latent_proportions). The learnt kappa_scale absorbs overdispersion beyond pure multinomial sampling (design effects, non-response, etc.). When per-pollster kappa_log_sigma overrides are specified, each pollster receives its own kappa_scale.

Non-centred parameterisation is used for the random walk to avoid divergences.

Configuration reference

All fields on ModelConfig with their defaults:

Sampler settings

Parameter Default Description
num_tune 1500 Warmup (tuning) iterations per chain
num_draws 1000 Posterior draws per chain (total samples = draws × chains)
num_chains 2 Independent MCMC chains (≥ 2 recommended for R-hat)
cores None CPU cores for parallel sampling (None = auto-detect)
target_accept 0.95 NUTS target acceptance rate (0.90–0.99)
random_seed 42 Reproducibility seed
init_method "jitter+adapt_diag" NUTS initialisation ("adapt_diag", "adapt_full", …)
progressbar True Show progress bar during sampling
compute_log_likelihood False Store pointwise log-likelihood for arviz.loo / waic
sampler_kwargs {} Extra kwargs forwarded to pymc.sample()

Time discretisation

Parameter Default Description
time_step_days 7 Time-grid granularity in days (grid ends exactly on election day)

Priors (logit / log-ratio scale)

Parameter Default Description
sigma_walk_prior 0.05 HalfNormal scale for random-walk SD, per walk_reference_days (~1 pp/week at 50 %)
walk_reference_days 7 Calendar window sigma_walk_prior refers to; keeps implied volatility grid-invariant
sigma_house_prior 0.3 HalfNormal scale for house-effect SD (~5 pp max bias)
initial_sigma 0.5 Normal SD for initial latent support
kappa_log_sigma 0.5 SD of log-normal prior on poll precision scaling factor
r_hat_threshold 1.01 R-hat above this triggers a ConvergenceWarning
ess_threshold 400.0 Minimum bulk ESS below which a ConvergenceWarning is raised
correlated_walk False Enables LKJ-correlated random walk innovations rather than independent ones
lkj_eta 2.0 Shape parameter for LKJ matrix prior (used when correlated_walk=True)

Per-pollster overrides

Parameter Default Description
pollster_priors {} Dict mapping pollster name to PollsterPrior (see below)
shared_bias None SharedBiasPrior for industry-wide polling error (see Shared polling error)

Per-pollster priors

Use PollsterPrior to set different priors for individual pollsters. This is useful when you have external knowledge about a pollster's reliability or known biases.

from kronikas import ElectionForecast, ModelConfig, PollsterPrior

config = ModelConfig(
    pollster_priors={
        # PollCo has a known small bias, constrain its house effect
        "PollCo": PollsterPrior(sigma_house=0.1),
        # SurveyInc uses an online panel, allow more overdispersion
        "SurveyInc": PollsterPrior(kappa_log_sigma=1.0),
    },
)

result = ElectionForecast(
    polls_csv="polls.csv",
    election_date="2024-11-05",
    config=config,
).run()

Each PollsterPrior field is optional; None (the default) inherits the global value from ModelConfig:

Field Default Description
sigma_house None (uses sigma_house_prior) Fixed house-effect SD for this pollster in logit space. Lower = more trusted.
kappa_log_sigma None (uses kappa_log_sigma) SD of log-normal prior on this pollster's precision scaling. Higher = allow more overdispersion.
mu_house None (all zeros) Dict mapping candidate name to expected bias in percentage points. Positive = over-estimates, negative = under-estimates. Omitted candidates default to 0 pp. Converted to logit space relative to that candidate's own support level, so the bias must not push it outside (0 %, 100 %).

How it works:

  • House effects: Pollsters with a sigma_house override use that value directly as the SD for their house-effect prior, bypassing the hierarchical sigma_house parameter. Pollsters without an override continue to share the learnt hierarchical sigma_house. If all pollsters have overrides, the hierarchical sigma_house is omitted entirely.
  • Kappa (precision): When any pollster has a kappa_log_sigma override, the model switches from a single shared kappa_log to per-pollster kappa_log values. Pollsters without overrides use the global kappa_log_sigma as their prior SD.
  • Unknown names: Pollster names in pollster_priors that don't match any pollster in the data trigger a warning and are ignored.

Setting prior means for pollster–party bias

Use mu_house when you have external knowledge that a pollster systematically leans toward or against a specific candidate. Values are in percentage points. Specify the expected bias directly. You only need to list the candidates you want to set; the rest default to 0 pp.

from kronikas import ElectionForecast, ModelConfig, PollsterPrior

config = ModelConfig(
    pollster_priors={
        # PollCo is believed to over-estimate Alice by 3 pp
        "PollCo": PollsterPrior(mu_house={"Alice": 3}),

        # SurveyInc tends to under-estimate Bob by 4 pp; also allow a wider SD
        "SurveyInc": PollsterPrior(
            mu_house={"Bob": -4},
            sigma_house=0.4,
        ),

        # YouGov: set means for two candidates, keep default sigma
        "YouGov": PollsterPrior(mu_house={"Alice": 2, "Bob": -2}),
    },
)

result = ElectionForecast(
    polls_csv="polls.csv",
    election_date="2024-11-05",
    config=config,
).run()

Pollsters without a mu_house entry keep the default zero mean; only the pollsters you explicitly configure are affected. Values are converted relative to each candidate's initial support level.

Lower-level API

For more control, use the building blocks directly:

load_polls() and polls_from_dataframe() return a PollData, which exposes poll_dates, last_poll_date, and up_to(cutoff) — the last of these returns a copy restricted to polls on or before a date, and is what the backtester uses to reconstruct what was known at a past moment.

from kronikas import ModelConfig, load_polls
from kronikas.model import build_model, run_inference, extract_results
from datetime import date

poll_data = load_polls("polls.csv")
poll_data.last_poll_date              # most recent poll
earlier = poll_data.up_to(date(2024, 8, 1))   # only polls up to that date
config = ModelConfig(num_draws=500)

model, metadata = build_model(poll_data, date(2024, 11, 5), date.today(), config)
trace = run_inference(model, config)
result = extract_results(trace, poll_data, metadata)

# Direct access to ArviZ trace
import arviz as az
az.summary(result.trace)
az.plot_trace(result.trace, var_names=["sigma_walk", "kappa_log"])

Contributing

We welcome contributions of all kinds: bug reports, feature ideas, documentation improvements, and code. Whether you're fixing a typo or building a new feature, we'd love to have you involved.

👉 See CONTRIBUTING.md for setup instructions, coding guidelines, and how to submit a pull request.

Citation

DOI

If you use kronikas in your research, please cite it:

@software{Tisza_kronikas_2026,
  author = {Tisza, Viktor},
  title = {kronikas},
  month = {3},
  year = {2026},
  publisher = {Zenodo},
  version = {0.1.2},
  doi = {10.5281/zenodo.19163741},
  url = {https://github.com/vtisza/kronikas}
}

License

This project is licensed under the Apache License 2.0.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

kronikas-0.2.0.tar.gz (227.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

kronikas-0.2.0-py3-none-any.whl (115.2 kB view details)

Uploaded Python 3

File details

Details for the file kronikas-0.2.0.tar.gz.

File metadata

  • Download URL: kronikas-0.2.0.tar.gz
  • Upload date:
  • Size: 227.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for kronikas-0.2.0.tar.gz
Algorithm Hash digest
SHA256 72ca24cfaef0736f4216473c4b3205f4071fe75d688c715b082657d3d7013b4d
MD5 fe2ea2b06e4b66c93f84d4101fe784e9
BLAKE2b-256 ab0099835bf74471fc5dbb397874048d01bc74ca51daa2a1e728ba9b6fe6f5e5

See more details on using hashes here.

Provenance

The following attestation bundles were made for kronikas-0.2.0.tar.gz:

Publisher: publish.yml on vtisza/kronikas

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file kronikas-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: kronikas-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 115.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for kronikas-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2c0744bca7c5f1c41c94f06e3db734e9ec78c4e53aa1ed2bd48a087edce673b3
MD5 03b7ab0436cacbff1396da728ea6b368
BLAKE2b-256 14f9f19b4831fd6778bd00f436c8972b1769164b38dc1c13e064bd57553c19a9

See more details on using hashes here.

Provenance

The following attestation bundles were made for kronikas-0.2.0-py3-none-any.whl:

Publisher: publish.yml on vtisza/kronikas

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

0.1.2

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page