How to Use PyCausalSim to Improve LNG and Natural Gas Forecasting

Machine learning could not beat the NYMEX natural gas futures curve. A causal simulation built on it can still improve the decisions that ride on Henry Hub: hedging, LNG cargo economics and winter budgets. A step-by-step guide with Python code, 27 years of EIA data and a forecast for winter 2026–27.
Note: I am the creator of PyCausalSim. You can find it on GitHub at github.com/Bodhi8/pycausalsim.
Every U.S. LNG cargo starts as Henry Hub gas. Long-term contracts at Cheniere’s Sabine Pass and Corpus Christi terminals, for example, price feedgas at 115% of Henry Hub plus a fixed liquefaction fee, so the forecast of one monthly price sits underneath cargo economics, utility fuel budgets, industrial hedges and, increasingly, the cost of powering data centers. Yet most natural gas forecasting still produces a single number, and a single number cannot tell a buyer how much to hedge, what a cold January would cost, or whether a faster LNG ramp should change the plan.
This guide shows how to fix that with PyCausalSim, the open-source Python library I maintain for causal inference and simulation. The method is seven steps: anchor on the futures curve, model the physical cause of price surprises, build a causal simulation, backtest it, run scenarios, turn the distributions into decisions, and publish dated claims you can score. I tested each step on public EIA data and report what worked and what did not.
The short answer, in numbers:
- Machine learning on price data lost to the futures curve at every horizon from 1 to 4 months, by 2.5% to 12%. The curve is the center to build on, not the forecast to beat.
- Storage surprises explain about half of the curve’s winter misses. Each 100 Bcf of tighter-than-normal storage put winter prices about 8.6% above the curve.
- The PyCausalSim simulation was calibrated: 88% of realized winter prices fell inside its 90% ranges over ten winters. Its point forecasts beat the curve, but no better than a simple correction for the curve’s winter premium. Its value is the range, the scenarios and the decisions.
- Hedging to a budget ceiling with the simulation saved $0.20 per MMBtu a winter against hedging everything, without a single budget breach in ten winters.
- For winter 2026–27, the simulation centers below the October 23 strip of $2.97, puts a January LNG cargo’s feedgas-linked cost at about $6.71 per MMBtu with a 90% range of $5.26 to $8.63, and says only a cold winter or a faster LNG ramp pushes the winter above the curve.
Step 1: Anchor on the futures curve
The NYMEX Henry Hub futures curve prices gas for delivery next month, the month after and years beyond. It is the consensus of producers, utilities, LNG exporters and traders with money on the line. Before building anything on top of it, I checked whether machine learning could simply beat it.
I used monthly Henry Hub spot prices and NYMEX Contracts 1 to 4 from the U.S. Energy Information Administration, 1997 to April 2024, when EIA stopped publishing the futures series. Ridge regression and gradient boosting learned from 14 features: the shape of the curve, spot momentum, volatility, seasonality and the curve’s latest miss. One more gradient boosting model saw spot prices only. Every model was refit each month using only data it could have known at the time, for 196 forecasts per horizon from 2008 to 2024. The rule that makes this honest is one line: a model trains only on rows whose target month had already happened, df.index + h <= t.

The curve won at every horizon. Ridge regression was 2.5% worse one month out and 12% worse four months out. The spot-only model was 16% to 24% worse, a statistically significant gap, and lost even to a random walk. Giving the models the curve as a free input instead of an anchor made boosting worse, and a two-stage version that fed a classifier’s probabilities back into the final model did worse again, because the classifier itself scored below a coin flip on whether spot would land above or below the curve.
What the models did find was a bias in the curve itself. From 1998 to 2024, spot settled below the futures price 56% of the time one month out and 62% of the time four months out, by an average of 1.3% and 6.9%. That is a risk premium: buyers pay a little extra, on average, to lock in a price. A regression of realized moves on the curve’s implied moves says the same thing from another angle: only about 72% to 90% of the move the curve implies actually happens.
The lesson for Step 1 is to stop trying to beat the curve with price history. Use it as the center of the forecast, and spend the modeling effort on what moves the price after the curve is set.
Step 2: Model the cause of price surprises
In natural gas, most of what moves price after the curve is set reaches the market through storage. A cold month, a production freeze-off or a faster LNG ramp all pull more gas out of storage than traders expected, and price rises to ration what is left. So the second step is to measure that link.
For each month I took EIA’s Lower 48 working gas in underground storage and computed a storage surprise: the month’s actual change in storage minus the average change for the same calendar month over the previous five years (at least three). A negative surprise means a tighter month than normal. Then I regressed the curve’s miss, log S(t+h) − log F(t,h), on the cumulative storage surprise between the forecast date and the delivery month, for 496 forecasts made from January 2014 to April 2024.
For winter delivery months, November to March, every 100 Bcf that storage ran tighter than normal put the monthly average spot price about 8.6% above the curve, with a standard error of 1.0%. Storage surprises explained 55% of the variance in the curve’s one-month winter misses and 39% four months out. Outside winter the effect was about half as large.
Three more numbers from the same work set up the simulation:
- Winter is when surprises are big. The standard deviation of the monthly storage surprise was about 180 to 200 Bcf in December, January and February, against 65 Bcf in November.
- The winter premium is large. With storage exactly normal, winter spot settled below the curve by about 3% one month out and 14% four months out over this period.
- The slope fades with distance: from 9.7% per 100 Bcf one month out to 7.3% four months out, as the market adjusts to news along the way.
This is not a forecast, because the storage surprise is unknown when the curve is set. It is a causal sensitivity, the price response to a tighter physical market, and that is what a simulation needs.
Step 3: Build the causal simulation in PyCausalSim
PyCausalSim’s futures module is built for forward-looking causal questions. It unrolls a causal graph month by month, runs Monte Carlo draws over uncertain parameters, runs every named scenario on the same draws so comparisons are paired, and flags any scenario that pushes a relationship outside the range it was estimated on. Install it from GitHub:
pip install git+https://github.com/Bodhi8/pycausalsim.git
The causal graph for a Henry Hub winter is short, and every edge is a stated judgment rather than something the library discovers:
- Weather, LNG feedgas and production freeze-offs cause the month’s storage surprise, in Bcf against normal.
- The storage surprise accumulates from November on. It is a state variable, so a cold December still matters in February.
- The cumulative surprise and the futures curve set the monthly price: the curve, shifted by the estimated winter premium, moved by the estimated slope for each 100 Bcf of tightness, plus residual noise that grows with the horizon.
Each draw samples the premium and the slope from their estimated distributions and picks one of the last 12 winters’ storage-surprise paths as its weather. Scenarios replace that random weather with a specific past winter or add an LNG or production shock, expressed in Bcf per day. The whole thing, from EIA data to a hedge decision, is one script:
"""Henry Hub winter simulation with PyCausalSim.
pip install git+https://github.com/Bodhi8/pycausalsim.git
Data in ./data, each EIA history table saved as CSV with columns year,jan,...,dec:
spot.csv Henry Hub spot price, monthly (RNGWHHDm)
c1.csv-c4.csv NYMEX natural gas futures, Contracts 1 to 4, monthly (RNGC1m-RNGC4m)
storage.csv Lower 48 working gas in underground storage, MMcf (NGM_EPG0_SAO_R48_MMCF)
"""
import numpy as np
import pandas as pd
from pycausalsim.futures import TemporalSCM, Scenario, compare
MONTHS_ABBR = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"]
def load(path):
w = pd.read_csv(path).melt(id_vars="year", value_vars=MONTHS_ABBR, var_name="m", value_name="v")
w["t"] = pd.PeriodIndex.from_fields(year=w["year"], month=w["m"].map(MONTHS_ABBR.index) + 1, freq="M")
return w.dropna(subset=["v"]).set_index("t")["v"].sort_index()
# ---- Step 2: the storage surprise and its price response -------------------
spot = load("data/spot.csv")
fut = {h: load(f"data/c{h}.csv") for h in (1, 2, 3, 4)}
storage = load("data/storage.csv") / 1000.0 # Bcf, end of month
dw = storage.diff()
surprise = pd.Series(np.nan, index=dw.index) # negative = tighter than normal
for t in dw.index:
prior = [dw[t - 12 * k] for k in range(1, 6) if (t - 12 * k) in dw.index and not np.isnan(dw[t - 12 * k])]
if len(prior) >= 3:
surprise[t] = dw[t] - np.mean(prior)
def ols(y, x):
X = np.column_stack([np.ones(len(x)), x])
b, *_ = np.linalg.lstsq(X, y, rcond=None)
e = y - X @ b
cov = np.linalg.inv(X.T @ X) * e.var(ddof=2)
return b, np.sqrt(np.diag(cov)), e.std(ddof=2)
calib = {}
idx = spot.index
for h in (1, 2, 3, 4):
miss = np.log(spot.reindex(idx + h).to_numpy()) - np.log(fut[h].reindex(idx).to_numpy())
cum = sum(surprise.reindex(idx + k).to_numpy() for k in range(1, h + 1)) / 100.0
winter = np.isin((idx + h).month, (11, 12, 1, 2, 3))
ok = winter & ~np.isnan(miss) & ~np.isnan(cum)
b, se, sd = ols(miss[ok], cum[ok])
calib[h] = dict(const=b[0], const_se=se[0], beta=-b[1], beta_se=se[1], sd=sd)
calib[5] = dict(const=2 * calib[4]["const"] - calib[3]["const"], const_se=calib[4]["const_se"] * 1.2,
beta=calib[4]["beta"], beta_se=calib[4]["beta_se"] * 1.2,
sd=2 * calib[4]["sd"] - calib[3]["sd"]) # March: one step past the data
paths = [] # past winters' Nov-Mar surprises
for y in range(2012, 2026):
p = [surprise.get(pd.Period(f"{y}-{m:02d}", "M")) for m in (11, 12)] + \
[surprise.get(pd.Period(f"{y + 1}-{m:02d}", "M")) for m in (1, 2, 3)]
if all(v is not None and not np.isnan(v) for v in p):
paths.append(p)
paths = np.array(paths)
# ---- Step 3: the causal simulation -----------------------------------------
MONTHS = [202611, 202612, 202701, 202702, 202703]
CURVE = [2.640, 3.050, 3.506, 3.170, 2.504] # NYMEX settles, Oct 23, 2026
DAYS = {11: 30, 12: 31, 1: 31, 2: 28, 3: 31}
def build_scm(calib, paths, draws=20000, seed=2026):
scm = TemporalSCM(horizon=MONTHS, step="month", draws=draws, seed=seed)
for h in range(1, 6):
r = calib[h]
scm.parameter(f"const_{h}", "normal", loc=r["const"], scale=r["const_se"])
scm.parameter(f"beta_{h}", "normal", loc=r["beta"], scale=r["beta_se"])
scm.parameter(f"z_{h}", "normal", loc=0.0, scale=1.0)
scm.parameter("pick", "uniform", low=0, high=len(paths)) # which past winter
scm.lever("weather", default=0.0) # Bcf vs normal, negative = colder
scm.lever("random_weather", default=1.0) # 1 = resample past winters
scm.lever("lng_extra", default=0.0) # Bcf/d of feedgas above expectations
scm.lever("freeze_off", default=0.0) # Bcf/d of production lost
scm.exogenous("weather_draw", lambda c: paths[np.floor(c.p.pick).astype(int), c.t])
scm.add_equation(
"storage_surprise",
lambda c: (c["weather"] + c["random_weather"] * c["weather_draw"]
- (c["lng_extra"] + c["freeze_off"]) * DAYS[MONTHS[c.t] % 100]),
parents=["weather", "random_weather", "weather_draw", "lng_extra", "freeze_off"])
scm.state("cum_surprise", lambda c: c.lag("cum_surprise", 1) + c["storage_surprise"],
parents=["cum_surprise:lag(1)", "storage_surprise"], init=lambda p: 0.0)
sds = [calib[h]["sd"] for h in range(1, 6)]
def resid(c):
step_sd = np.sqrt(sds[c.t] ** 2 - (sds[c.t - 1] ** 2 if c.t else 0.0))
return c.lag("resid", 1) + step_sd * getattr(c.p, f"z_{c.t + 1}")
scm.state("resid", resid, parents=["resid:lag(1)"], init=lambda p: 0.0)
def price(c):
h = c.t + 1
tight = -c["cum_surprise"] / 100.0
miss = getattr(c.p, f"const_{h}") + getattr(c.p, f"beta_{h}") * tight + c["resid"]
return CURVE[c.t] * np.exp(miss)
scm.add_equation("price", price, parents=["cum_surprise", "resid"])
# Declared extrapolation: flag any draw outside the range the slope was estimated on.
scm.mechanism("price ~ cum_surprise", form="log_linear", valid_range=(-433, 575), outside="flag")
return scm
def replay(name, path):
"""A past winter's storage surprises, replayed onto this winter's curve."""
s = Scenario(name)
s.set("random_weather", 0.0)
for m, v in zip(MONTHS, path):
s.at(m).set("weather", to=float(v))
return s
if __name__ == "__main__":
scm = build_scm(calib, paths)
market = Scenario("Market view")
vortex = replay("2013-14 polar vortex", [-165, -330, -328, -192, -166])
lng = Scenario("LNG +1.5 Bcf/d")
lng.set("lng_extra", 1.5)
result = compare(scm, [market, vortex, lng], target="price")
print(result.table(year=202701))
print(result["2013-14 polar vortex"].support.summary())
# ---- Step 6: from distribution to decision ------------------------------
run = result["Market view"]
sims = np.stack([run.at("price", m) for m in MONTHS]) # (months, draws)
curve = np.array(CURVE)
budget = 1.10 * curve.mean() # strip + 10%
for k in np.round(np.arange(0, 1.0001, 0.05), 2):
cost = (k * curve[:, None] + (1 - k) * sims).mean(axis=0) # winter cost per MMBtu
if np.percentile(cost, 95) <= budget:
print(f"Hedge {k:.0%}: expected ${cost.mean():.2f}, 95th percentile ${np.percentile(cost, 95):.2f}")
break
jan = run.at("price", 202701)
fob = 1.15 * jan + 3.00 # 115% of Henry Hub + fixed fee
print(f"January cargo cost ${fob.mean():.2f}, 90% range ${np.percentile(fob, 5):.2f} to ${np.percentile(fob, 95):.2f}")Two design choices matter more than the code. First, the price equation starts from the curve, so the simulation inherits everything the market already knows and only adds what the storage link explains. Second, every month in a draw comes from the same run, so a cold December and an expensive February arrive together, as they do in real winters. Drawing months independently would make the winter ranges far too narrow, a mistake I made in a first pass and caught in the decision backtest.
Step 4: Backtest it, and compare against the simplest alternative
A simulation has no standing to forecast this winter unless it can retrodict past ones. I ran it from the October futures curve of each winter from 2014–15 to 2023–24, re-estimating the storage link with that winter left out each time, and scored the November to February forecasts against realized spot, 40 winter months in all. PyCausalSim’s backtest function reports the error and the share of outcomes inside the 90% range.
The benchmark that matters is not only the curve. It is also the simplest thing a desk could do instead: shift the curve down by the winter premium from Step 2 and stop there.
- October futures curve: typical error 31% (log RMSE 0.31), average miss 28.7%.
- Curve shifted down by the winter premium only: typical error 26%, average miss 20.3%.
- PyCausalSim, this winter’s weather unknown: typical error 28%, average miss 22.3%, closer than the curve in 7 of 10 winters.
- PyCausalSim, storage surprises known: typical error 21%, average miss 16.9%.
- Calibration: 88% of realized prices fell inside the simulation’s 90% range, close to the 90% it should be. February was the weak month, at 70%, so treat late-winter ranges as too narrow.
Read those lines carefully, because they say exactly what the framework does and does not do. For a single-number forecast, the simulation beat the curve, but the gain came from the winter premium, and the premium-only shift did slightly better, beating the simulation in 9 of 10 winters. Random weather adds spread, not accuracy.
The simulation earns its place in three other ways. It produces a calibrated range, which neither the curve nor the premium shift can. It prices specific scenarios. And the fourth line measures the size of the prize: a perfect forecast of the storage surprise cuts winter forecast error by about a third. That is the most a weather-driven model could deliver, and it tells you where a machine learning budget belongs.
Step 5: Run scenarios for winter 2026–27
On October 23, 2026, NYMEX settled November at $2.64, December $3.05, January $3.51, February $3.17 and March $2.50, a winter strip of $2.97. Storage entered the season in good shape: EIA reported working gas 45 Bcf above the five-year average for the week ending October 9. The open question for LNG is demand. EIA expects U.S. LNG exports to average 17.0 Bcf/d in 2026 and rise another 1.5 Bcf/d in 2027 as Port Arthur LNG Phase 1, the first two Rio Grande LNG trains and the last Golden Pass train start up.

The market-view run, with weather drawn from the last 12 winters, puts the November to March average at $2.71, with a 90% range of $1.82 to $3.81. January carries the widest tail: a median of $3.09 against $3.51 on the curve, and a 90% range of $1.96 to $4.91. Most of the gap below the curve is the winter premium, not a weather call. With perfectly normal weather the winter average is $2.73.

The scenarios translate physical events into prices on the same draws:
- Replay of the 2013–14 polar vortex: winter average $5.10, January $6.50. That path is tighter than anything in the calibration window, and PyCausalSim flags every draw from December on as extrapolated, so it is a stress test, not a forecast.
- Replay of the 2017–18 bomb cyclone: $3.26, January $4.23.
- Replay of the 2024–25 cold January: $3.13, January $4.04.
- LNG feedgas 1.5 Bcf/d above what the market expects, all winter: $3.01, about 30 cents above the market view. That is roughly one large train, such as Port Arthur Phase 1, running months ahead of schedule.
- Replay of 2020–21, the Winter Storm Uri year: $2.91. Monthly averages understate Uri, whose spike lasted days.
- A one-week January freeze-off losing 10 Bcf/d: $2.80, only 9 cents above the market view, because a week is a small share of a five-month season.
- Replays of the warm 2023–24 and El Niño 2015–16 winters: $2.32 and $2.07.
For LNG specifically, the scenario that moves Henry Hub most is not a single storm. It is sustained extra feedgas demand, because it compounds through storage every day of the winter.
Step 6: Turn distributions into decisions
A forecast only improves outcomes when it changes a decision. The two decisions that ride most directly on Henry Hub are how much winter gas to lock in, and what an LNG cargo will cost to load.
Hedging to a budget ceiling. A fuel buyer, whether a utility, an industrial plant or an LNG marketer buying feedgas, can lock any share of its November to March volume at the October strip and pay the monthly spot average on the rest. The curve alone says nothing about which share to choose, because it offers one number and no range. The simulation gives each hedge ratio an expected cost and a bad-winter cost, the 95th percentile.

Under the market view, staying fully unhedged has the lowest expected cost, $2.71 per MMBtu, but a 95th percentile of $3.81. Hedging everything locks $2.97 with no tail. Most finance teams do not minimize expected cost; they set a ceiling and want to stay under it in nearly every winter. So the decision rule I tested is simple: lock the smallest share whose simulated 95th percentile stays within the budget.
- Budget at the strip plus 5% ($3.12): hedge 85%, expected cost $2.93.
- Budget at the strip plus 10% ($3.27): hedge 65%, expected cost $2.88.
- Budget at the strip plus 20% ($3.57): hedge 30%, expected cost $2.79.
The scenarios change the answer when there is a reason to believe them. Under a 2017–18 replay, staying unhedged costs $3.26 in expectation, so a buyer with a cold outlook should hedge more. Under the LNG upside scenario, every hedge ratio costs about the same in expectation, so the decision is purely about risk tolerance.
Did it improve outcomes? I ran the same rule in each winter from 2014–15 to 2023–24, using a simulation that never saw the winter it was hedging, and compared realized costs:
- Always hedge 100%: average cost $3.75 per MMBtu, worst winter $6.41.
- Budget rule at strip plus 10%: hedged 66% on average, average cost $3.55, worst winter $5.51, saving $0.20 per MMBtu against hedging everything. It never breached its budget.
- Budget rule at strip plus 20%: hedged 29% on average, saved $0.41, never breached.
- Never hedge: saved $0.57, and breached a strip-plus-5% budget once, in 2018–19.
The savings in this window come mostly from the curve’s winter premium, because 2014 to 2024 was a decade in which the curve usually priced winter too high. A colder decade would have rewarded more hedging. What the rule delivers in any decade is a hedge ratio tied to an explicit risk limit instead of habit, with the expected cost of that limit stated in advance.
LNG cargo cost. For a contract that prices feedgas at 115% of Henry Hub plus a fixed fee, the simulation converts directly into a cargo-cost distribution. With a $3.00 fee, a January 2027 loading has an expected feedgas-linked cost of $6.71 per MMBtu, a median of $6.55 and a 90% range of $5.26 to $8.63, against $7.03 implied by the curve. The fee is a fixed shift: a $2.25 fee lowers every number by 75 cents and a $3.50 fee raises them by 50. The simulation puts an 11.7% probability on that cost exceeding $8.00. An offtaker can set that distribution against forward European and Asian prices plus shipping to see how often a January cargo is in the money, and how much margin a cold U.S. winter would erase, before committing to lift or resell it.
Step 7: Publish dated claims and score them
Forecasts improve when they are scored. PyCausalSim turns a run into probabilities on statements that resolve on public data, so a model’s record can be checked rather than remembered selectively. Each claim names a test, a resolution date and a source:
from pycausalsim.futures import ClaimSet
scm.claim("jan_above_400", "January 2027 Henry Hub spot averages above $4.00",
test=lambda r: r["price"] > 4.00, resolve_by=202701, source="EIA RNGWHHDm")
result = compare(scm, [market], target="price")
claims = result.falsifiable_claims(scenario="Market view")
claims.export("claims_winter_2026_27.json")
# April 2027, once EIA publishes the monthly averages:
ClaimSet.load("claims_winter_2026_27.json").score({"jan_above_400": False})Here are the market-view probabilities for this winter, resolving on EIA’s monthly Henry Hub spot average:
- December 2026 averages above $3.50: 14%.
- January 2027 averages above $4.00: 19%.
- January 2027 averages above $6.00: 1%.
- February 2027 averages below $3.00: 62%.
- The November to March average settles below the October 23 strip of $2.97: 70%.
I will score these with a Brier score in April 2027, when EIA publishes March. A score of 0 is perfect and 0.25 is a coin flip. For a desk, the same habit works internally: every forecast that informs a hedge or a cargo decision gets logged as a dated probability, and the model that keeps the lowest Brier score earns more weight next season.
What the framework does not do, and where machine learning fits next
The limits are part of the method, so here they are plainly. The storage link was estimated on ten winters, 2014 to 2024, an era of fast production growth in which the curve consistently priced winter too high. A larger LNG export market could shrink that premium, and the simulation would keep assuming it until it is re-estimated. The model works in monthly averages, so it cannot see a three-day price spike. March sits one step beyond the horizons I could estimate from an October curve, so its range is extrapolated. And every edge in the graph is a stated judgment. PyCausalSim quantifies uncertainty over the coefficients, not over the structure, which is why the code is published for anyone to rewire.
The backtest also says where the next gain is. Knowing the storage surprise in advance cut winter forecast error by about a third, so the highest-value use of machine learning in natural gas is forecasting that surprise: weather, freeze-offs and LNG feedgas, weeks ahead. The natural next input is NOAA’s seasonal temperature outlook, which is itself a set of probabilities. Fed into PyCausalSim, it would reweight the weather draws toward the past winters that match the outlook, and the price ranges, hedge ratios and claims would update with it. That is the version of a probabilistic input that should help, because unlike probabilities built from prices, it carries information the curve may price slowly.
Why this matters in Kansas City
Here in Kansas City, Kansas regulators approved two new 710 MW combined-cycle gas plants for Evergy in July 2025, scheduled for 2029 and 2030, and the region’s main utility has signed about 2.5 gigawatts of data center load, as I covered in my piece on the future of AI in Kansas City. More of the region’s electricity cost will move with Henry Hub over the next decade. The same tools that help an LNG offtaker decide how to price a January cargo can help a utility fuel desk set a winter budget with a stated risk limit, and help a regulator or a large customer ask the right question of either.
The practical takeaway fits on one line: anchor on the futures curve, model the physical cause, simulate the range, and make every hedge and cargo decision against a stated risk limit, then score the forecast when the season ends.
This is part of my commodities and LNG series at Vector1 Research. Next up is LNG netbacks, the math that connects Henry Hub to buyers in Asia and Europe, followed by a deep dive on optimizing an LNG liquefaction train in Python. If you run PyCausalSim on your own book, I would like to hear what you find. Subscribe on Substack to get the deep dives as they publish.
About the Author
Brian James Curry is a Kansas City AI researcher, applied scientist and data scientist with more than twenty years in marketing modeling and measurement. His work today spans marketing mix modeling, causal inference, machine learning and healthcare intelligence. He is the founder of Vector1 Labs, an independent applied AI research lab. His marketing measurement work covers marketing mix modeling, incrementality testing and budget allocation, and his Causal Care series applies causal machine learning to health system decisions: which programs work, for whom, and what they are worth. He also founded KC AI Lab, which began in 2016 as the Kansas City Machine Learning Group and has grown to more than 1,500 members.
His career includes leading analytics, data science and machine learning at Hallmark, Garmin, Koch Industries, Tractor Supply Company and Vail Resorts. His open-source research tools on GitHub include Papilon (marketing mix modeling and complex systems), PyCausalSim (causal discovery through simulation) and tincast (forecasting for AI-critical commodities), along with MeaningFlow and Daedalus. A Kansas State University alum from a small Kansas farming town, he writes about marketing mix modeling, causal inference, agentic AI, healthcare AI and the economics of AI on Medium and Substack. Contact: brian@vector1.ai.
Sources
- U.S. Energy Information Administration, Henry Hub Natural Gas Spot Price, monthly (RNGWHHDm).
- U.S. Energy Information Administration, Natural Gas Futures Contracts 1 to 4, monthly, with contract definitions and the note that futures prices after April 5, 2024 are not available.
- U.S. Energy Information Administration, Lower 48 Working Gas in Underground Storage, monthly.
- U.S. Energy Information Administration (April 16, 2026), U.S. natural gas exports to grow nearly 30% by 2027 as LNG facilities ramp up.
- Cheniere Energy, Form 10-K for fiscal 2014, on SPA pricing at 115% of Henry Hub plus a fixed fee.
- Farmbucks, Henry Hub natural gas futures pricing, October 23, 2026 settles; cross-checked against RBN Energy’s 12-month strip.
- Rigzone (October 21, 2026), What Direction Will the Henry Hub Gas Price Take in 2026, 2027?, for EIA storage figures.
- Chinn, M. D. and Coibion, O. (2014). The Predictive Content of Commodity Futures. Journal of Futures Markets, 34(7).
- POWER Magazine (2025), Kansas regulators back plan for two new gas-fired plants, along with solar farm.
- PyCausalSim on GitHub.
- Curry, B. (2026). The Future of AI in Kansas City: Gigawatts, Models and the Math of Who Gets Paid. Medium.


