TabPFN-TS has an appealing premise: turn forecasting into a supervised table, add temporal features, and let a pretrained tabular model reason over the result. That makes familiar forecasting choices unusually visible. Lags become columns. Calendar position becomes data. A prediction pipeline that can feel opaque when expressed as a sequence model becomes a set of transformations that can be inspected one feature at a time.
Calendar features look like the easy part. An hour is an integer, a day has a known rhythm, and sine-cosine encoding is standard practice. Yet a tiny choice in the denominator changes the geometry of the clock. With 24 zero-based hour values, dividing by 23 places the last level almost on top of the first. Dividing by 24 keeps 24 positions around the circle. The formula fits on one line; deciding what the change means for an existing forecasting system does not.
This article follows the gap between those questions. The useful lesson extends beyond one encoder: source-level correctness, compatibility with a pretrained checkpoint, and evidence for a release are separate questions. A clean mathematical proof can settle the first while leaving the other two open.
TabPFN-TS starts with a table
TabPFN is built for supervised tabular prediction. TabPFN-TS adapts that capability to forecasting by constructing a table from each series: past target values, temporal context, and other generated features become columns; future target values become the quantities to predict. The public pipeline exposes that conversion instead of hiding it behind a time-series-only interface.
The repository’s basic example creates a TabPFNTSPipeline, gives it a dataframe with item, timestamp, and target columns, and requests a forecast horizon. Underneath that small API sits a feature pipeline. At the pinned main revision, the default temporal list is exactly RunningIndexFeature(), CalendarFeature(), and AutoSeasonalFeature(). The calendar encoding is therefore ordinary plumbing with an unusually direct route into the model input.
This table-first view is useful even when a different forecaster ultimately wins. It forces several questions into the open. Which historical values are available at a forecast origin? Which covariates are actually known for the future? Is a timestamp represented as a linear count, a category, a cyclic coordinate, or several of those at once? A pretrained model can supply a powerful prior over tabular tasks, but it still sees the representation handed to it.
There is a practical debugging advantage too. A suspicious prediction can be traced backward from the forecast, through the generated feature matrix, to the function that made a particular column. Calendar geometry can be inspected without downloading a checkpoint or running a benchmark. This cheap inspection becomes useful later, because it lets a representation invariant be tested while model-quality questions remain expensive.
The library-first mental model is simple: TabPFN-TS is a forecasting pipeline whose model consumes a table plus engineered context. Calendar position is one part of that context. Once the timestamp becomes two numeric columns, the shape of those columns is part of the model contract.
Calendar coordinates have one especially useful forecasting property: they can be computed for future timestamps without observing future targets. A requested horizon already determines which hours, weekdays, or months its rows occupy. The encoder can therefore give the model known temporal context at every step, while the target remains hidden. That context is modest - it identifies position in a cycle, not the event that will happen there - but it is available consistently. This is why the feature deserves more scrutiny than its two-column footprint suggests. A small geometric defect repeats across every row where the calendar column is generated, and any correction changes a model input across the same horizon.
A clock belongs on a circle
Raw hour numbers have the wrong neighborhood. On a line, hour 23 is far from hour 0 even though they are adjacent across midnight. The usual repair maps each level onto a unit circle. For a period p and a zero-based level v, use
(sin(2πv/p), cos(2πv/p))
The coordinate order here is (sin, cos) because that is the order used by the inspected implementation. Swapping the two axes only rotates or reflects the picture; changing the denominator changes which points exist.
The circle offers a crisp invariant. Consecutive levels should be equally spaced, including the wrap from the final level back to zero. For p levels, the angular step is 2π/p, so every adjacent chord has ideal length
2 sin(π/p)
This equal-chord invariant is stronger than checking that two columns contain numbers in [-1, 1]. It tests the property the encoding is meant to express: every tick advances by one equal step around a cycle. It also avoids arguing from a plot. A chart is helpful for intuition, while the chord calculation supplies the decision rule.
The denominator follows directly from the input contract. If the values are 0, 1, ..., p-1, dividing by p assigns angles from zero through 2π(p-1)/p. The next conceptual step lands back at zero. Dividing by p-1 instead assigns the final observed level the nominal angle 2π, placing it at the cycle’s starting direction.
The endpoint needs numerical care. Floating-point sin(2π) is a tiny residual rather than exact zero in ordinary computation. The two endpoint tuples are therefore distinct machine values. Under a practical absolute tolerance, however, they occupy the same cluster. “Nearly coincident” describes the result better than pretending floating-point arithmetic produced exact equality.
Equal chords, plus one visible residual
The following standard-library script reproduces the 24-hour case. It needs no TabPFN installation and no network. The grouping rule scans levels in order and retains a point as a new representative only when it lies more than 1e-9 from every earlier representative.
from math import cos, hypot, pi, sin
p = 24
atol = 1e-9
def encode(denominator):
return [
(sin(2 * pi * level / denominator), cos(2 * pi * level / denominator))
for level in range(p)
]
def chord(left, right):
return hypot(left[0] - right[0], left[1] - right[1])
def cluster_count(points):
representatives = []
for point in points:
if not any(chord(point, representative) <= atol for representative in representatives):
representatives.append(point)
return len(representatives)
for denominator in (23, 24):
points = encode(denominator)
print(
f"d={denominator}: 0->1={chord(points[0], points[1])}, "
f"23->0={chord(points[23], points[0])}, clusters={cluster_count(points)}"
)
print(f"signed sine at level 23 with d=23: {encode(23)[23][0]}")
d=23: 0->1=0.2723332981924932, 23->0=2.4492935982947064e-16, clusters=23
d=24: 0->1=0.26105238444010315, 23->0=0.26105238444010403, clusters=24
signed sine at level 23 with d=23: -2.4492935982947064e-16
With d = 23, the 0→1 chord is 0.2723332981924932, the 23→0 wrap chord is 2.4492935982947064e-16, and the 24 levels form 23 tolerance-level clusters at atol = 1e-9 with rtol = 0.
With d = 24, the 0→1 chord is 0.26105238444010315, the 23→0 wrap chord is 0.26105238444010403, and the same inputs form 24 tolerance-level clusters.
The signed sine coordinate at level 23 under d = 23 is -2.4492935982947064e-16; the Euclidean wrap chord is the nonnegative 2.4492935982947064e-16.
Those signs carry different meanings. A coordinate may be negative because it sits infinitesimally to one side of an axis. A distance cannot be negative. Preserving both values makes it harder to accidentally convert a floating-point detail into an exaggerated claim about the representation.
Figure 1. For p = 24, dividing by 23 places levels 23 and 0 nearly together at the stated tolerance, while dividing by 24 restores equal cyclic spacing. The epsilon-scale signed sine residual remains visible and is distinct from the nonnegative wrap chord.
The proposed coordinates satisfy the intended circle. The claim stays deliberately narrow: it says nothing yet about the response of a pretrained predictor when one pair of input columns moves.
Where the zero-based values enter
A denominator can only be judged against the values it receives. The relevant dependency is GluonTS, whose time-feature functions produce zero-based indices. The declared dependency floor in the inspected project is GluonTS v0.16.0. Its pinned source and the separately checked v0.17.0 source are byte-identical for this file and describe the same index ranges: seconds and minutes run from 0 through 59, hours from 0 through 23, weekdays from 0 through 6, and months from 0 through 11.
The two checked GluonTS revisions expose zero-based hour values 0..23; the pinned TabPFN-TS main revision passes those values through a default CalendarFeature() path whose current encoding divides by 24 - 1.
The dependency-to-source trace is direct. The calendar feature obtains a raw time-feature value, reads the declared period, subtracts one from that period, and uses the result in its sine and cosine angles. The explainability path contains a separate helper for partial-dependence values and applies the same period-minus-one choice. A corresponding explainability test at the pinned main revision expects the hour grid to use 23.
Duplicated representation logic deserves attention because fixes can otherwise split the system. Prediction features and explanation features must agree about the coordinates attached to an hour. A plot that explains a different transformation from the one used during prediction is worse than a cosmetic inconsistency: it breaks the meaning of the displayed feature axis.
The pinned trail also guards against an easy off-by-one defense. A denominator of 23 would make sense for values 0..22, or for a deliberately closed curve that repeats its first point as an additional endpoint. It does not give equal spacing to 24 distinct zero-based levels 0..23. The input and the period have to be considered together.
At this point the source argument is complete: the inputs are zero-based, the current denominator is one less than the number of levels, and the resulting 24-hour geometry violates equal spacing. The remaining questions belong to integration and empirical evaluation.
A bounded case study in one divisor
Issue #145 records this edge case in the TabPFN-TS repository. Its small reproduction checks the hour cycle and traces the denominator through the feature code, the explanation helper, and the existing test expectation. PR #146 proposes using the declared period in both implementation paths, updating the explainability expectation, and adding direct regression coverage.
The proposed patch is one commit, changes four files with 50 additions and 3 deletions, updates the feature and explainer paths, and adds representative hour, weekday, and minute regression tests.
Those tests ask two focused questions for each representative period. First, does each input level occupy its own rounded coordinate? Second, does the wrap chord match the first interior chord within the stated tolerance? Together they protect distinctness at the chosen numerical precision and the equal-spacing invariant. The weekday and minute cases show that the test design is parameterized, but they do not support a claim about calendar constructions outside the covered inputs.
The focused selected run ended with 15 passed and 1 deselected; that narrow result is the only test-run outcome used here.
The focused result has a limited job. It demonstrates that the intended regression behaves as designed in the captured setup. It is not a substitute for project CI, model evaluation, or a release decision. Likewise, a source patch can be mathematically straightforward and still deserve careful review when it changes values fed to a pretrained model.
The best description of the work is therefore a proposed representation correction with focused regression tests. Calling it a forecast improvement would jump past the data. Calling it available to users would jump past repository state. The interesting engineering begins precisely where the one-line formula ends.
What the project reproduced and triaged
The public issue response did more than apply a label. The project triage actor anuragg1209 ran a reproduction, observed 23 unique tolerance-level hour pairs rather than 24 under the current formula, and agreed that the divisor should be the period for the zero-based input contract. The response also raised the model-quality question and supplied an initial A/B comparison.
The calendar-geometry contribution turned the algebra into an upstream discussion. The project response reproduced the 24-to-23 tolerance-level collision, agreed that the divisor should be the period, and applied the triaged label. The proposed patch remains open for further evaluation.
The issue, commit, and project response form a useful case study in how a small representation correction can uncover a broader release question.
The A/B result went the other way
The empirical response is uncomfortable in exactly the way good engineering evidence often is. The corrected geometry did not win the reported average comparison. Hiding that result would turn a useful case study into advocacy; treating it as a refutation of the geometry would collapse two different questions.
In the project triage actor’s public comment, one 12-dataset GIFT-Eval A/B of the true-period divisor versus the period-minus-one divisor was approximately +0.6% MASE and +1.3% weighted quantile loss on average; lower is better.
The same comment reports every per-dataset MASE delta within ±2.5% and shows an angular-noise condition U(-0.2π, 0.2π), while its placement, sampling granularity, seeds, complete configuration, and relation to training remain unknown.
Those numbers are prose-rounded observations attributed to one public comment. The attached images are useful provenance for the reported experiment, but their table cells are not promoted here into an exact dataset. There is also no independent reproduction, seed set, raw-prediction archive, or uncertainty estimate in this article.
Why might a geometrically correct input fail to improve an existing checkpoint? One plausible category of explanation is compatibility: a pretrained system may respond to a coordinate distribution in ways that cannot be inferred from the encoder alone. Another is ordinary evaluation variation across datasets and runs. The missing checkpoint and configuration details prevent choosing between explanations, and the public record does not establish which calendar representation appeared during training.
The result does establish something valuable: mathematical cleanliness does not automatically buy lower forecast error. For a model-facing transformation, the relevant release question is empirical and paired. The same tasks, splits, seeds, preprocessing, checkpoint, and evaluation code should compare the two encodings. Until that exists, the honest summary is asymmetric: the geometry has a proof; the reported single A/B was slightly unfavorable on average.
Three gates, three different answers
A correct circle is only the first gate. Treating the work as three decisions keeps both the proof and the uncertainty intact.
- Geometry - proved. Verify the input levels, denominator, equal-chord invariant, tolerance, and regression oracle.
- Checkpoint compatibility - unknown. Identify the evaluated checkpoint and its training and inference encodings before treating a coordinate change as behavior-preserving.
- Paired multi-seed release evidence - pending. Re-run both encodings with shared data splits and seeds, retain raw predictions, report uncertainty, and only then decide whether a release should change.
Pinned sources and the small script settle the cheap part without model weights. The work becomes historical and experimental after that: identify the checkpoint and the representation around it, then compare both encoders on the same tasks, splits, and seeds. Retaining raw predictions and reporting uncertainty turns a small average delta into something that can be inspected rather than merely quoted. None of those steps predetermines the release decision.
Figure 2. Geometry, checkpoint compatibility, and paired multi-seed release evidence answer different questions. The reviewed state is issue open and triaged, PR open and unmerged, with no submitted human review or inline review comment.
This framework generalizes. A tokenizer repair, normalization change, category remapping, or image preprocessing correction can be right at the representation layer while remaining risky for a model trained or evaluated under a previous convention. Proof determines whether the transformation satisfies its specification. Compatibility and release evidence determine how to deploy it.
What remains open
At the publication gate, issue #145 is open with the triaged label; PR #146 is open, non-draft, unmerged, and has zero submitted human reviews and zero inline review comments.
The current main revision is still a756ae3fb3af82c903c39e1cd71864ff5252bc4d, which is also the parent of the submitted patch commit 8e38bbd950781498e297171638fb51511b9eab60. That makes the patch easy to locate and the source comparison precise, while conveying no approval.
The latest release at that gate is v1.2.0 at revision 73a134f1dc1ded17d2c079c11c137faa8be58d56, and its immutable CalendarFeature source retains the period-minus-one implementation.
The next useful work is concrete. Pin the exact checkpoint and full evaluation configuration. Establish how calendar coordinates relate to the checkpoint’s training and inference path. Run a paired protocol over shared datasets, splits, and multiple seeds. Save raw predictions, compute uncertainty for both MASE and weighted quantile loss, and document where the angular noise is sampled and applied. Then examine dataset-level patterns instead of asking one aggregate to settle the entire question.
The evaluation also needs an explicit checkpoint axis. An inference-only swap around the existing checkpoint answers the immediate compatibility question and stays closest to the project-reported comparison. A separately identified retraining or fine-tuning arm would ask whether matched geometry during training and inference behaves differently. Results from those arms should remain separate because they describe different interventions. Should the next evaluation only swap the inference encoder around the existing checkpoint, or also include a checkpoint retrained or fine-tuned with matched training and inference geometry? The public record does not answer that question.
The duplicated helper offers a smaller engineering question that does not need benchmark rhetoric: can prediction and explanation call one shared calendar transformation so their coordinates cannot drift? Keeping that refactor distinct from the divisor decision would make each change easier to test and future comparisons easier to interpret.
The circle can be corrected in one line. The trustworthy part is keeping the next line of evidence just as explicit: which checkpoint, which encoder, which seeds, and which release.
Primary sources
- TabPFN-Time-Series repository
- README at pinned main revision
- Pipeline at pinned main revision
- Project metadata and dependency floor at pinned main revision
CalendarFeatureat pinned main revision- Explainability helper at pinned main revision
- Explainability test at pinned main revision
- Issue #145
- Project-reported reproduction and A/B comment
- PR #146
- Submitted patch commit
- Submitted calendar regression tests
- TabPFN-Time-Series v1.2.0 release
CalendarFeaturein v1.2.0- GluonTS v0.16.0 time-feature source
- GluonTS v0.17.0 time-feature source