Skip to content
// morris-labs
bayesianagriculturestatistics

Eleven months of warning at a small-town cattle auction

Steer prices at one Alabama sale barn tripled in seven years, and the model dates every break. The interesting one happened eleven months earlier, in a series nobody watches.

13 min read

Every Tuesday, in Clay County, Alabama, cattle go through a ring and a USDA market reporter writes down what they sold for. The report goes out as a PDF the same day — head count, weight range, price range, one line per lot. It has done this since at least May 2019, on 298 Tuesdays and, for reasons I have not established, one Wednesday and one Saturday.

It is a small barn. The median sale day moves about 170 head. Nobody is setting a national price in Lineville. But the national price arrives there anyway, every week, and gets written down.

I pulled 15,583 usable rows out of 300 of those PDFs — May 2019 to March 2026 — and fit the same class of model I used on Nashville’s 311 data: a Bayesian changepoint model that is handed a series and asked where it breaks, without being told where to look.

Last time I went looking for a date and found a slope. This time the dates are real. That turned out to be the less interesting half of it.

Four regimes in the steer price

Here is the headline series — the average price of feeder steers, in dollars per hundredweight, monthly, 82 months.

feeder steers · $/cwt · Clay County Livestock Auction0100200300400500+0.85/mo+5.28/mo+55.7%−4.70/mo+26.3%+8.71/mo+34.3%P(regime boundary here)1.0020192020202120222023202420252026
Monthly average steer price with the fitted regime segmentation. Each regime carries its own level and its own monthly drift, so the fitted line is sloped rather than flat, and it is deliberately broken at each boundary — the model inferred a change of trajectory, not a gradual transition between two levels. The bottom panel is the posterior probability that a boundary falls in each month. Three boundaries are reported: 2022-12, 2024-01 and 2024-12.
regimes
regimemonthsmean $/cwtdrift/movs prior
2019-05 — 2022-1143136.46+0.85
2022-12 — 2023-1213212.48+5.28+55.7%
2024-01 — 2024-1111268.38−4.70+26.3%
2024-12 — 2026-0215360.49+8.71+34.3%
data table
monthobservedfitP(boundary)
2019-05135.35118.570.000
2019-06120.72119.420.000
2019-07125.42120.270.000
2019-08125.45121.120.005
2019-09117.91121.980.004
2019-10120.97122.830.003
2019-11131.03123.680.003
2019-12111.62124.530.002
2020-01143.96125.380.003
2020-02145.95126.240.002
2020-03123.74127.090.009
2020-04114.52127.940.007
2020-05123.43128.790.003
2020-06123.26129.640.003
2020-07123.68130.500.003
2020-08120.42131.350.002
2020-09123.72132.200.003
2020-10133.66133.050.003
2020-11128.14133.900.002
2020-12135.90134.760.002
2021-01132.83135.610.002
2021-02137.01136.460.002
2021-03138.89137.310.002
2021-04130.84138.160.002
2021-05125.41139.020.002
2021-06132.86139.870.002
2021-07143.20140.720.003
2021-08142.68141.570.002
2021-09135.05142.420.002
2021-10142.11143.280.002
2021-11140.10144.130.003
2021-12144.38144.980.004
2022-01148.75145.830.005
2022-02157.84146.680.006
2022-03165.76147.540.004
2022-04151.71148.390.008
2022-05151.99149.240.021
2022-06145.25150.090.053
2022-07143.79150.940.061
2022-08162.95151.800.063
2022-09158.07152.650.085
2022-10153.50153.500.100
2022-11154.14154.350.133
2022-12186.75180.780.409
2023-01173.73186.060.030
2023-02182.17191.350.028
2023-03201.88196.630.024
2023-04199.16201.910.004
2023-05206.58207.200.005
2023-06223.84212.480.009
2023-07229.27217.760.006
2023-08232.03223.050.007
2023-09223.11228.330.021
2023-10230.39233.610.016
2023-11234.31238.890.016
2023-12239.00244.180.018
2024-01282.00291.880.602
2024-02282.70287.180.053
2024-03318.23282.480.032
2024-04285.67277.780.144
2024-05245.91273.080.433
2024-06259.91268.380.007
2024-07257.59263.680.005
2024-08274.89258.980.010
2024-09244.71254.280.038
2024-10248.72249.580.010
2024-11251.86244.880.021
2024-12289.70299.560.846
2025-01307.92308.260.029
2025-02314.66316.970.002
2025-03346.31325.670.005
2025-04353.33334.380.003
2025-05341.25343.080.018
2025-06333.32351.790.004
2025-07371.42360.490.001
2025-08368.49369.190.001
2025-09379.49377.900.003
2025-10353.75386.610.047
2025-11390.06395.310.003
2025-12385.86404.010.004
2026-01433.12412.720.000
2026-02438.60421.430.000

Read left to right:

regime months mean $/cwt drift/mo vs prior
2019-05 — 2022-11 43 136.46 +0.85
2022-12 — 2023-12 13 212.48 +5.28 +55.7%
2024-01 — 2024-11 11 268.38 −4.70 +26.3%
2024-12 — 2026-02 15 360.49 +8.71 +34.3%

Forty-three months of essentially nothing — $0.85 a month on a $136 base, which over three and a half years is drift you would not notice standing at the rail. Then a breakout, a genuine pullback through 2024, and then the steepest stretch in the record: +$8.71 per hundredweight per month, sustained for fifteen months and still going at the end of the data. First month in the series, $135.35. Last month, $438.60.

The model is most confident about the most recent break. P(boundary at 2024-12) = 0.846, and 0.895 of the posterior mass sits in the three months around it. The 2024-01 turn carries 0.602. The original 2022-12 breakout is the weakest of the three at 0.409 — the mass smears across late 2022 — though 0.699 of it lands in the five months from October 2022 to February 2023.

All three survive the obvious robustness check. The fit ships with a sensitivity grid — the same model re-fit at six different priors on expected regime length — and 2022-12, 2024-01 and 2024-12 appear at every single setting. Only the tightest prior adds a fourth boundary. Nothing here is an artefact of how long I told the model regimes ought to be.

This is, so far, an unremarkable finding. Cattle prices went up; anyone in the business knows that. The model has recovered something a producer could have told me over the fence, which is mostly evidence that the model works.

The series nobody watches

Underneath the price series there are volume series, and one of them is doing something.

Replacement cattle are animals sold to go into a breeding herd rather than to be grown out or harvested — at Clay County, overwhelmingly bred cows. The distinction matters more than it sounds. A feeder steer is this year’s output. A bred cow is the machine. Producers buying them are expanding; producers selling them are liquidating, and once a cow leaves the herd the calf she would have had is not merely delayed, it does not exist.

replacement cattle · head per sale-day · Clay County Livestock Auction02040603214−55.8%P(regime boundary here)1.0020192020202120222023202420252026
Replacement-cattle volume, normalised to head per auction sale-day. This series is fit with a negative-binomial model that estimates a level per regime and no slope, so the fitted line is flat by construction rather than by finding. The model reports a single boundary at 2022-01, a −55.8% level drop, and it recovers exactly the same two regimes with the same two means at all six prior settings. Note how low the probability panel stays — the break is stable but its date is not, which is the subject of the next section. Two observations sit above the axis ceiling and are marked with arrows: November 2019 (94 head/sale-day) and March 2020 (121).
regimes
regimemonthsmean head/sale-daylevelvs prior
2019-05 — 2021-123232.0932.1
2022-01 — 2026-025014.1814.2−55.8%
data table
monthobservedfitP(boundary)
2019-0550.0032.090.000
2019-0616.0032.090.000
2019-0724.0032.090.000
2019-0818.0032.090.011
2019-097.0032.090.011
2019-1023.0032.090.029
2019-1194.0032.090.047
2019-1223.0032.090.007
2020-0115.0032.090.008
2020-0236.0032.090.010
2020-03121.0032.090.009
2020-0418.0032.090.026
2020-0524.0032.090.014
2020-0632.0032.090.011
2020-0747.0032.090.011
2020-0844.0032.090.015
2020-0943.0032.090.022
2020-1034.0032.090.039
2020-1124.0032.090.049
2020-1215.0032.090.037
2021-0131.0032.090.019
2021-0212.0032.090.023
2021-0318.0032.090.013
2021-0444.0032.090.011
2021-0525.0032.090.018
2021-0628.0032.090.018
2021-0716.0032.090.020
2021-0853.0032.090.013
2021-0923.0032.090.118
2021-1021.0032.090.129
2021-1116.0032.090.117
2021-1232.0032.090.067
2022-0113.0014.180.215
2022-0215.0014.180.094
2022-0313.0014.180.055
2022-0417.0014.180.030
2022-0522.0014.180.025
2022-0621.0014.180.030
2022-0718.0014.180.036
2022-0821.0014.180.034
2022-0914.0014.180.043
2022-103.0014.180.031
2022-1126.0014.180.012
2022-124.0014.180.021
2023-0114.0014.180.011
2023-0211.0014.180.010
2023-038.0014.180.008
2023-0417.0014.180.007
2023-0511.0014.180.007
2023-0617.0014.180.007
2023-0715.0014.180.007
2023-0830.0014.180.006
2023-0922.0014.180.009
2023-108.0014.180.012
2023-114.0014.180.009
2023-1219.0014.180.006
2024-0115.0014.180.007
2024-0212.0014.180.007
2024-0316.0014.180.007
2024-0419.0014.180.007
2024-0510.0014.180.008
2024-0630.0014.180.007
2024-0717.0014.180.013
2024-088.0014.180.016
2024-095.0014.180.011
2024-1017.0014.180.008
2024-1113.0014.180.009
2024-1217.0014.180.009
2025-012.0014.180.011
2025-0228.0014.180.007
2025-034.0014.180.014
2025-045.0014.180.008
2025-056.0014.180.007
2025-0621.0014.180.008
2025-0713.0014.180.007
2025-088.0014.180.007
2025-0933.0014.180.007
2025-1010.0014.180.018
2025-1113.0014.180.017
2025-129.0014.180.022
2026-016.0014.180.000
2026-029.0014.180.000

One break, at 2022-01. Volume falls from 32.1 head per sale-day to 14.2 — a −55.8% level drop — and in four subsequent years it does not recover.

Steer prices broke out at 2022-12.

That is eleven months between the breeding herd leaving this barn and the calf price taking off, in the right order, with the right sign, at exactly the lag the biology implies: a cow bred today produces a calf in about nine months and a feeder animal several months after that. Fewer cows sold into herds in early 2022 means fewer calves reaching the ring from late 2022 onward, and a market short of calves pays more for them.

It is the national cattle cycle — the herd contraction that has been the whole story of the American beef market this decade — visible in one county’s weekly PDF, with the supply signal legible almost a year before the price signal.

Why I do not get to call this a leading indicator

Three reasons, and I would rather state them than let the graph do the arguing.

The model cannot date the volume break. The boundary at 2022-01 is stable — it is there at every prior setting, with an identical −55.8% — but no single month carries more than P = 0.215. The evidence is spread: 0.825 of the posterior mass falls somewhere in the eight months from September 2021 to April 2022. So the model is fairly sure something broke in that window and genuinely unsure which month it was. “Eleven months of lead” is really “somewhere between about eight and fifteen months of lead”, and I should write it that way.

Two dates are not a relationship. I have one break in one volume series and one break in one price series, and I have arranged them in an order that flatters a mechanism I already believed. That is not a fitted lead–lag. It is an anecdote with error bars on it. To claim the link I would need to model the two series jointly, and to test it I would need other barns — the same lead ought to show up in Tennessee and Missouri, and if it doesn’t, I have found a fact about Clay County rather than about cattle.

One barn is one barn. 15,583 rows sounds like a lot and is roughly 300 Tuesday afternoons in a single Alabama county. Clay County’s replacement volume falling by half is consistent with a national herd contraction and equally consistent with a nearby barn opening, one large seller retiring, or the sale barn changing which day it runs its cow sale.

The thing that never moved

Here is the series I expected to be boring, which turned out to be the best argument in the post.

all cattle · head per sale-day · Clay County Livestock Auction0100200300164P(regime boundary here)1.0020192020202120222023202420252026
Total throughput, all categories, head per auction sale-day. The model returns a single regime across all 82 months — no boundary anywhere — at the default prior and at every looser one, four of the six settings in the grid. The two tightest priors instead carve out a mild 2020-03 → 2021-12 elevation, around 195 head/sale-day against roughly 157 either side. No month in this series carries a changepoint probability above 0.14 at the default setting, so there is nothing here the model will defend. The August 2019 spike that used to dominate this panel is gone: it was a typo in the source report, and it is the subject of the last section.
regimes
regimemonthsmean head/sale-daylevelvs prior
2019-05 — 2026-0282164.16164.2
data table
monthobservedfitP(boundary)
2019-05112.00164.160.000
2019-06102.00164.160.000
2019-07109.00164.160.000
2019-08121.00164.160.050
2019-09154.00164.160.085
2019-10148.00164.160.042
2019-11201.00164.160.035
2019-12115.00164.160.009
2020-01112.00164.160.019
2020-02134.00164.160.065
2020-03211.00164.160.139
2020-04126.00164.160.024
2020-05157.00164.160.079
2020-06220.00164.160.123
2020-07238.00164.160.025
2020-08266.00164.160.006
2020-09246.00164.160.008
2020-10210.00164.160.019
2020-11200.00164.160.025
2020-12189.00164.160.027
2021-01198.00164.160.025
2021-0297.00164.160.029
2021-03172.00164.160.006
2021-04156.00164.160.006
2021-05181.00164.160.005
2021-06157.00164.160.005
2021-07209.00164.160.005
2021-08246.00164.160.005
2021-09218.00164.160.010
2021-10194.00164.160.018
2021-11196.00164.160.024
2021-12196.00164.160.035
2022-01171.00164.160.051
2022-02156.00164.160.048
2022-03105.00164.160.034
2022-04137.00164.160.011
2022-05141.00164.160.008
2022-06162.00164.160.006
2022-07172.00164.160.006
2022-08185.00164.160.005
2022-09190.00164.160.006
2022-1080.00164.160.007
2022-11197.00164.160.004
2022-12138.00164.160.004
2023-01191.00164.160.004
2023-02158.00164.160.004
2023-03119.00164.160.004
2023-04146.00164.160.003
2023-05121.00164.160.003
2023-06164.00164.160.004
2023-07184.00164.160.004
2023-08214.00164.160.004
2023-09234.00164.160.003
2023-10133.00164.160.005
2023-11171.00164.160.004
2023-12202.00164.160.004
2024-01212.00164.160.005
2024-02216.00164.160.009
2024-03107.00164.160.020
2024-04143.00164.160.009
2024-05128.00164.160.007
2024-06168.00164.160.005
2024-07177.00164.160.006
2024-08162.00164.160.007
2024-09138.00164.160.007
2024-10170.00164.160.006
2024-11200.00164.160.007
2024-12229.00164.160.011
2025-01146.00164.160.057
2025-02138.00164.160.057
2025-0378.00164.160.054
2025-04108.00164.160.009
2025-05100.00164.160.006
2025-06114.00164.160.016
2025-07145.00164.160.027
2025-08151.00164.160.015
2025-09201.00164.160.009
2025-10117.00164.160.004
2025-11183.00164.160.006
2025-12192.00164.160.005
2026-01150.00164.160.000
2026-02126.00164.160.000

One regime. Eighty-two months. No boundary anywhere.

Total throughput at Clay County has been flat — 164.2 head per sale-day, drifting at −0.19 a month, which over seven years is nothing — across the entire period in which the price of a steer tripled.

Underneath that flat total, the composition churned completely:

series break level change
feeder 2020-05 +44.4%
replacement 2022-01 −55.8%
slaughter 2025-01 −51.0%
all cattle none

Three components with breaks in three different years and two different directions, summing to a total with no break at all. Had I only looked at headline volume — the number the barn itself would quote you — I would have concluded that nothing whatsoever happened here since 2019, during the largest cattle price move in living memory.

Aggregates are where signals go to cancel.

The corrections that made any of this legible

Four, and all of them changed the answer rather than tidying it.

Volume is head per sale-day, never head per month. Clay County does not hold a fixed number of sales a month — the median is four, the range is one to six. March 2026 had a single sale day, and on raw monthly counts the model dutifully reported a −76% market collapse that was nothing but a short month. Divide by sale-days, drop trailing partial months, and every spurious tail changepoint disappears at once.

Regimes have slopes. A model where each regime gets only a level treats a sustained climb as a staircase: it cut the steer series into ten regimes, several of them three months long. Letting each regime carry its own drift won on marginal likelihood for all five price series — up to Δ log-evidence +13.3 — and roughly halved the regime count. “Prices rose steadily for 43 months” is one regime, not eight.

A regime has to last three months. Overdispersion alone was not enough. A single freak month still claimed its own regime at P = 0.985 until a minimum length was imposed; the floor dropped it to 0.176 and removed every one-month artefact across all nine series simultaneously. A regime that lasts one month is an event, not a regime.

One tempting number is worthless and had to be thrown out. Online changepoint detection offers P(run length = 0) as a natural alarm: has the regime just ended? Under a constant hazard the recursion sets the numerator to H·Σ(R·π) against a normaliser of Σ(R·π), so the whole thing collapses algebraically to exactly H — the prior hazard — for any model and any data. It was observed returning 0.081 against a fitted hazard of 0.0810. It looks like a result, it updates every week, and it contains precisely zero information about the data.

The one-cell disaster

Which brings me to the month I could not explain, and now can.

August 2019 showed 496 head per sale-day against a series mean of 169, and in the slaughter category alone 384 head per sale-day against a neighbouring regime mean of 9.6 — about forty times. It was the only month in seven years that the model ever wanted to make into a regime of its own, and the sole reason the tightest prior setting put any boundary at all in the total-volume series.

I went back to the source PDF for 21 August 2019. In the COWS - Boner 80-85% block:

Head   Wt Range     Avg Wt   Price Range    Avg Price   Dressing
2      1250-1400    1325     55.00-57.00    56.06       Average
1      1175         1175     1.00           1.00        Average  Muddy
1125   1125         1125     45.00          45.00       Low

One lot of 1,125 head of boner cows, at an average weight of 1,125 lb.

The head count and the average weight are the same number, which is the tell. And the header of that very same report declares the day’s receipts:

Total Receipts:      127
Slaughter Cattle:    10(7.9%)

Ten head of slaughter cattle. On a line claiming 1,125 of them.

My first assumption was that my own parser had smeared two columns together. It had not. Pulling the word positions off the page: on a normal single-head row the head-count token 1 occupies x 34.9–40.0, and on this row the token 1125 spans x 27.3–47.5 — a four-digit number typeset into the head-count column, with the weight column holding its own separate 1125 at x 91.7. There is no 1 anywhere on the line. The error is in USDA’s published report, not in my extraction of it, and the document contradicts itself four inches further up the same page.

The damage is one cell. Dropping that single lot:

as published row rejected
all cattle, 2019-08 496.0/sale-day 121.0
slaughter, 2019-08 384.0/sale-day 9.0

At 9.0 the month sits essentially on the level either side of it. Which means the slaughter series’ entire first regime — May to August 2019, a level of 106 head per sale-day and a spectacular +112.3/month drift — existed because of one mistyped cell in one PDF from 2019. It was never a liquidation event. It was never anything. With the row gone that series drops from four regimes to three, and the stretch becomes an unremarkable 2019-05 → 2020-04 sitting at 10.6.

I did not hard-code a fix to that row, because a patch to one cell is a lie of a different kind — the next such cell will not announce itself. What went in instead is a validation rule: no lot’s head count may exceed the receipts its own report declares for that day. Across all 300 reports it rejects exactly one row out of 15,584, with no false positives, and what it rejects is quarantined to a file rather than silently dropped. The looser version of the same idea — checking the day’s summed head count against receipts — additionally flags six days at 1.01–1.06×, all of them legitimate, so the per-lot form is the one worth having. It would have caught this in 2019 and will catch the next one without my help.

What I actually learned

The model dated the price breaks, and it dated them robustly, and that was the part I had expected to be hard. It was not. What was hard was everything around it: knowing that volume had to be divided by sale-days, that regimes needed slopes, that a tempting alarm statistic was algebraically empty, and that one cell in one 2019 PDF was quietly manufacturing a market event.

The interesting finding was in the series I nearly did not plot. Replacement volume is small, noisy, and the one series where the model cannot tell me the month. It also broke first, by the better part of a year, in the direction the biology predicts. That is either the cattle cycle showing up in a small Alabama barn about eleven months early, or a coincidence between two dates I lined up myself.

I do not yet get to say which. What I can say is that the record was sitting there, in weekly PDFs, the whole time — and that the barn changed its name from Ashland to Lineville somewhere in the middle without anybody’s series noticing.


The dashboard is a marimo notebook exported to WASM, so it runs entirely in the browser and every fit is precomputed: Pyodide has no jax, numpyro, torch or pymc, which is a constraint worth designing around rather than fighting. 54 fits, zero divergences, worst r-hat 1.0054, 108 KB of posterior shipped as JSON.