Download Latest Version HyperTools 1.1.0 source code.zip (130.2 MB) Google Add to Preferred Sources
Home / v1.1.0
Name Modified Size InfoDownloads / Week
Parent folder
HyperTools 1.1.0 source code.tar.gz 2026-10-10 129.5 MB
HyperTools 1.1.0 source code.zip 2026-10-10 130.2 MB
README.md 2026-10-10 62.8 kB
hypertools-1.1.0-py3-none-any.whl 2026-10-10 1.6 MB
hypertools-1.1.0.tar.gz 2026-10-10 2.5 MB
Totals: 5 Items   263.8 MB 0

HyperTools 1.1.0

HyperTools 1.1.0 makes hierarchical (MultiIndex) DataFrames a first-class input: a frame whose columns carry a hierarchy expands into one trace per group plus per-level means, hyp.predict forecasts a hierarchy one group at a time, and predict= forecasts every plotted trajectory, means included. The animation API grows a public per-frame hook (on_frame= with FrameContext), an order= argument that is orthogonal to animate=, per-dataset alpha=, per-segment title=, trails under animate='serial', and predict= on every animation mode (with forecast_trail= fans and forecast_hue=/forecast_cluster= styling), including under hue= and cluster=. Lines are now drawn smoothed by default (antialias=True), and a plot can take its colors from an image (image_palette and palette='image:<path>'). Seven previously accepted inputs are now rejected with errors that say what to do instead; see "Changed behavior and new validation" and "Upgrading from 1.0.0". This release also folds many features written by hand in the library's own examples back into hypertools itself (GH [#285]): synthetic datasets and web sources for hyp.load, a URL cache, hyp.text_windows/damage/stack, multi-model forecasting and imputation scoring, a trailing Smooth and a Delay manipulator, an alignment score, image-derived and per-dataset palettes, multi-panel plotting (panels=/hyp.subplots), several animation hooks, and Hugging Face loading fixes. It carries the bug fixes listed under "Bug fixes", plus the fixes from a pre-publication review of the 1.1.0 draft (summarized under "Fixed during the release review"). Five rebuilt launch examples ship as executed tutorial notebooks with mp4 clips, the gallery is consolidated and ordered by topic, and five new tutorials (hierarchical DataFrames, loading and saving, fitted models and pipelines, manipulation, an animated forecast) join the rebuilt core ones.

Every change is listed in the changelog; these notes summarize it.

Install

:::bash
pip install --upgrade hypertools

Python 3.10 through 3.13. Optional extras install themselves on demand: the first call that needs one runs its pip install and prints a one-line notice (hyp.set_autoinstall(False) disables this and restores the ImportError with the manual command). The base install covers plotting, dimensionality reduction, alignment, clustering, normalization, Kalman/ARIMA forecasting, and missing-data imputation (PPCA, Kalman, scikit-learn imputers). Optional extras:

Extra Adds
hypertools[interactive] the plotly backend (backend='plotly'; auto-selected on Colab and Kaggle) plus kaleido for static image export
hypertools[predict] the skaters Laplace forecaster for hyp.predict(model='Laplace')
hypertools[predict-hf] the Hugging Face Chronos forecaster (pulls torch)
hypertools[text] transformer / sentence-transformers text embeddings via pydata-wrangler[hf]
hypertools[lsl] hyp.io.lsl_stream() Lab Streaming Layer input via pylsl
hypertools[dev] the test and notebook toolchain (pytest, nbclient, build) plus most optional backends: plotly, kaleido, skaters, torch, gensim, pylsl, scikit-image, datasets, kagglehub and openpyxl (not the [text] or [predict-hf] extras)

Further extras in pyproject.toml: io (.xlsx loading), density3d (3-D iso-surfaces), torch (autoencoder reducers), kaggle (hyp.load('kaggle/...')), and gensim (Word2Vec/Doc2Vec/FastText vectorizers and LDA/LSI/HDP semantic models).

conda-forge: the hypertools-feedstock bot opens a version-bump PR automatically after the PyPI upload, usually within hours, so the conda package follows once that PR is merged.

Highlights

Optional extras install themselves

  • The first call that needs an optional dependency installs the hypertools extra that provides it and carries on, printing a one-line hypertools: installing ... notice: plotly and kaleido ([interactive]), Hugging Face text embeddings and datasets ([text]), skaters ([predict], Laplace), chronos-forecasting ([predict-hf], Chronos), torch ([torch], autoencoder reducers), gensim ([gensim]), kagglehub ([kaggle]), pylsl ([lsl]), scikit-image ([density3d]) and openpyxl ([io]). The requirement strings come from the installed package metadata, so pyproject.toml remains the one declaration of every extra, and hypertools itself is never reinstalled.
  • Static image export with the plotly backend provisions what kaleido needs on first use: a Chrome build and, on Debian/Ubuntu images such as Colab and Kaggle, the four system libraries a fresh image lacks.
  • hyp.set_autoinstall(False) turns this off (session-wide, or for one with block; HYPERTOOLS_AUTO_INSTALL=0 sets the starting value for prebuilt images); a missing extra then raises ImportError naming the manual pip install "hypertools[<extra>]" command, as before. Guide: Optional dependencies in the docs.

New top-level names

  • hyp.set_autoinstall (above), hyp.FrameContext (the on_frame= argument), hyp.subplots, and the helpers hyp.text_windows, hyp.damage and hyp.stack.
  • hyp.HypertoolsOfflineError, raised by hyp.load(..., offline=True) when a file is not cached (also importable from hypertools.io).
  • hyp.HypertoolsTrustError, raised when a remote payload could only be loaded by relaxing a security policy and trust=True was not passed. It existed in 1.0.0 as hypertools.io.sources.HypertoolsTrustError, which still works, and it remains a ValueError subclass.

Folded in from the examples (GH [#285])

  • Data loading: synthetic generators (hyp.load('random_walk'|'helix'|'lorenz'|'blobs'|'moons'|'swiss_roll'|'s_curve', random_state=, n_datasets=)), web sources (hyp.load('wikipedia:<Title>'), hyp.load('yahoo:<TICKER>', start=, end=, interval=), hyp.load('sec:<TICKER>', concept=)), and a URL download cache (hyp.load(url, cache=True), HYPERTOOLS_URL_CACHE, offline=True raising HypertoolsOfflineError).
  • Helpers: hyp.text_windows (sliding word/sentence/character windows), hyp.damage (reproducible NaN knock-out on arrays/DataFrames), and hyp.stack (builds a column-hierarchical DataFrame from nested dicts or lists, with aggregate=).
  • Forecasting/imputation: hyp.predict(x, model=[...]) returning {name: forecast}; holdout=k backtests against a naive baseline (per_column=, return_forecasts=, scores.attrs['best']/['beats_baseline']); hyp.impute(x, model=[...], truth=full) scoring against a column-mean baseline; a trailing boxcar Smooth(kernel='boxcar', center=False, min_periods=) and a Delay(tau=, dims=) manipulator; hyp.align(..., return_score=True) / hypertools.align.score.alignment_score.
  • Plotting: palette= as a {category: color} dict or a per-dataset list of palette specs; panels=True|'auto'|ncols|(nrows, ncols) and hyp.subplots(nrows, ncols, ndims=3); title_kwargs=/title_color=/title_wrap=; per-dataset hue= and labels= (with label_anchor=); legends under matrix/mixture hue (legend_kwargs=, legend_colors=); bundle['colors'].
  • Animation: FrameContext.progress/window_bounds and populated revealed_counts; title= accepting a callable or an index-pattern string; animate='morph', loop=True; dataset_fade={'floor':, 'decay':}; companion= panels (matplotlib only); HyperAnimation.drawn_extent(frames=None).
  • Text/fonts: bundled Noto Sans Bold (fontweight='bold'); hypertools.io.synthetic_outlet(name, n_channels=, rate=); non-streaming Hugging Face loads decoding ClassLabel columns (decode_labels=False to keep integers); hyp.load('wiki')/hyp.load('nips') returning a flat list of strings; HF text/dataset paths defaulting HF_HUB_DISABLE_PROGRESS_BARS/HF_HUB_VERBOSITY/TOKENIZERS_PARALLELISM.
  • Axis units and forecast overlays: axis_scale='data' (real coordinates for 1-D/2-D plots, with xlim=/ylim=; 'unit' unchanged and now documented), ndims=1 as a time-series mode (with reduce=None, one line per column against the row index, DatetimeIndex aware, 2+ columns; the default reduce= first reduces to one component), truth= drawing the actual continuation beside a predict= forecast, and predict=['Kalman', 'ARIMA', 'GP'] drawing one legend-labeled overlay per model.

Hierarchical DataFrames

  • A column MultiIndex frame expands into one trace per group. The innermost column level is the feature axis and every level above it groups, so a (Market, Sector, Ticker) frame draws one trajectory per sector plus a heavier market-mean trajectory. Widths, opacities, colors and legend labels follow the same formulas as row expansion. Column grouping never shortens a trace: every group keeps all len(df) rows. A two-level (Group, Feature) hierarchy has no aggregate mean, so every leaf is treated as top-level and carries its own legend label. The frame you pass in is never modified.
  • Feature correspondence across groups is by name. Every group must carry the same innermost labels; later groups are permuted into the first group's order, so a permuted group plots identically. Mismatches, including unequal widths, are refused by an error naming the missing and unexpected features. Duplicate feature names inside a group are permitted and matched across groups by (label, occurrence).
  • Continuous hue= propagates through a column hierarchy as a per-trace value: pass a flat sequence of len(df) values (broadcast to every leaf) or one sequence per leaf. A mean trace takes the element-wise mean of its leaves' hue. A flat array sized to the total drawn observations is rejected rather than reinterpreted. Categorical hue still defers to the grouping, with a warning.
  • hyp.predict accepts hierarchical frames. A bare frame with a MultiIndex on one axis is split into groups and forecast group by group, returning [f0, f1, ...] (or ([f0, ...], [m0, ...]) with return_model=True). Before this, a column-hierarchical frame was silently flattened into one wide series and a row-hierarchical one died inside pandas with TypeError: cannot perform __sub__ with this index type: MultiIndex. Row hierarchies keep the innermost (time) level as each group's flat index, with its name and dtype intact.
  • Model ownership in hyp.predict is explicit: a name/class/dict spec constructs one model per group; an unfitted instance is deep-copied per group and each copy fits independently; a fitted instance is deep-copied and replays its learned parameters. The caller's instance and frame are never mutated. Per-group warnings and ValueErrors are re-emitted with the group key prepended, and the horizon t and model= spec are validated once, before the group loop.
  • predict= works with hierarchies, forecasting every plotted trajectory including per-level means. A mean is forecast from its own averaged trajectory, and bundle['predict']['forecasts'][i] == hyp.predict(bundle['trace_data'][i], model, t) for every i. It works with animate= too, where each frame's forecast is fit from exactly the rows that frame has revealed. Every plotted trace needs at least 2 rows on either axis, and plot() says so directly, naming a row-count-changing analysis stage (manip='Resample', an aggregating reduce=) when that is what made a trace short.
  • return_model=True now also returns trace_data and trace_metadata describing every plotted trajectory. trace_data holds the final pre-center/pre-scale trajectories; xform_data is unchanged (the analyzed pipeline output per input dataset, with no derived means). The two differ when a reduce= spec pins more than three components (n_components=5 leaves xform_data 5-D while trace_data is 3-D). Bundled forecasts always correspond to trace_data.
  • Full plotly parity for all of the above: trace counts and order, widths, opacities, legend labels, continuous hue, the colorbar, per-trace forecasts on both axes, and animated hierarchy forecasts. plotly data traces carry meta['hyp_trace_index'], the counterpart of matplotlib's coll._hyp_trace_index, propagated to per-segment 2-D traces so a multicolored 2-D line still reads as one trajectory.
  • New guide: Hierarchical DataFrames (docs/hierarchy.rst), covering row versus column semantics, the plot/predict divergence, hue forms, mean construction, limitations, dual-axis and list inputs, return shapes, the unfitted/fitted ownership table, backend parity and feature correspondence. Its worked examples are doctests, run by Sphinx's doctest builder in the docs CI job. docs/pipeline_order.rst gains hierarchy expansion and mean construction in the prose and the regenerated diagram.

Animation

  • alpha= now reaches the traveling cloud of animate='morph' on both backends (it previously applied only to the static holds).
  • plot(..., on_frame=...): a public per-frame hook on both backends. on_frame is called once per drawn frame with a single FrameContext argument (frame index and total, axes and drawn artists, animated arrays, serial-reveal counts, and segment_index/segment_kind for animate='morph'). FrameContext is exported as hypertools.FrameContext. On matplotlib, callbacks can also be attached after construction via HyperAnimation.on_frame(callback) (chainable); this is not available on plotly, so pass on_frame= to plot() for backend-portable code. Callbacks must be deterministic and idempotent for a given frame context. ctx.figure/ctx.axes/ctx.artists are backend-native (ctx.axes is None on plotly).
  • order='parallel'|'serial' on plot(), orthogonal to animate=, so trail styles compose with a serial reveal (animate=True, order='serial', chemtrails=True). animate='serial' remains a permanent alias for animate=True, order='serial'.
  • animate='serial' composes with chemtrails/precog/bullettime, in 2-D and 3-D and on both backends: the dataset currently being drawn carries its trail while already-revealed datasets stay fully drawn. This also fixes plotly, which previously warned and dropped the trails for a serial reveal.
  • Per-dataset alpha=, alongside the existing per-dataset color=/linewidth=. Inputs that assign alpha internally (row MultiIndex frames, nested lists of varying depth) keep their own values and now warn instead of losing yours silently.
  • Per-segment title= for serial-style animations, on both backends: pass a list of strings, one per dataset. For animate='morph' the holds are named and the transitions left blank automatically. Anywhere else a title= that is neither a string nor a callable raises TypeError.
  • Lines are smoothed by default (antialias=True). Every drawn line, static or animated, on both backends, is upsampled along a monotone PCHIP interpolant, so every original sample stays an exact vertex and the curve bends smoothly through it. This changes only how data is drawn: returned arrays, return_model=True bundles, forecasts, hulls, densities and per-point labels/markers are unaffected. Marker-only styles are never touched. In an animation each frame draws the smooth curve for exactly the portion that frame would have shown, at any frame_rate. Pass antialias=False to restore the previous straight-segment rendering exactly.
  • simplify= on plot() (default True). Today it governs animate='morph' only: over clouds larger than 2000 points, a morph without morph_samples= is downsampled to 2000 silently, because an uncapped morph was measured as killed at 10 minutes versus 8.2 s at morph_samples=2000. Pass simplify=False for an explanatory ValueError instead, which restores the guarantee that no data point is ever dropped. An explicit morph_samples= always wins.
  • A regrouped trajectory animates in row order. With hue=/cluster=, runs of one input dataset now share a single reveal clock, so the head sweeps the trajectory once and changes color at each category boundary (previously every run advanced at once). A precog= trail on a not-yet-reached run now shows that run's whole future.
  • HyperAnimation accessors: HyperAnimation.save() forwards dpi= and fps= and rejects other keywords by name; HyperAnimation.on_frame(callback) attaches per-frame callbacks; anim.animation is a hypertools.plot.animate.HyperFuncAnimation, still a matplotlib.animation.FuncAnimation subclass.

Forecasting (predict= and hyp.predict)

  • predict= works with animate='spin': the forecast traces are drawn once and rotate with the scene.
  • predict= works with the time-progressing animations (animate=True/'parallel'/'serial'/'window'). The forecast is recomputed from the history revealed so far and drawn from the endpoint of the current frame. Every forecast the animation will draw is computed up front, so the fan is folded into the plot's center/scale statistics and lands inside the cube by construction, and ani.save() and to_jshtml() replay identically. Fits are memoized per (dataset, revealed-count), so a 900-frame animation of a 60-row dataset costs at most 59 fits. animate='morph' still raises NotImplementedError, now with a stated reason: a morph interpolates between point clouds, so there is no time axis to forecast along.
  • forecast_trail=: keep earlier forecasts on screen as a fading fan, the forecast analog of chemtrails=. With predict= and a time-progressing animation, forecast_trail=True retains the last 16 forecasts (an int sets the cap), each in its dataset's style at an alpha that decays with age. The fan is recomputed from the frame index rather than accumulated, so a saved GIF and an interactive playback are identical. Without predict= it raises ValueError.
  • An animated predict= says when it will be slow to start. Cost grows with the data rather than the frame count (3 datasets x 60 rows x 900 frames is 177 fits, about 5 s; 3 x 500 x 900 is 1497 fits, about 330 s). plot() times the fits as they run and, once fits at two or more history lengths (one of them at least 10 rows) have been timed, warns if its projection exceeds slow_warning_seconds= (default 10; None silences), before the wait rather than after it.
  • forecast_hue=, forecast_cluster=, forecast_n_clusters=, forecast_palette= and forecast_fmt= style forecasts separately from the data. Inheritance stays the default, and each of these replaces exactly one aspect of it; a forecast recolored by forecast_palette=, forecast_hue= or forecast_cluster= is drawn at its trace's own alpha rather than half of it. forecast_cluster= clusters the forecast endpoints, taken in the space the figure draws after reduce=/align=, so a forecast's color answers which series are heading to the same place; in an animation the endpoint groups are resolved once from the full-history forecasts and stay fixed. forecast_hue= and forecast_cluster= are mutually exclusive; forecast_n_clusters= is separate from n_clusters=. All five require predict= and raise ValueError without it. forecast_fmt= is validated with matplotlib's own fmt= parser; forecast_hue= rejects a bare string.
  • predict= works with hue=/cluster= on static plots. A forecast belongs to a dataset and is anchored at its last observation, so it is matched to whichever drawn trace holds that observation and inherits its style. Two further ways a forecast could vanish were closed along the way: under a continuous hue= the overlays were drawn and then deleted by the LineCollection swap, and plotly raised IndexError under a regrouping hue=.
  • predict= works with hue=/cluster= on animated plots. Each frame's forecast is fit from exactly the observations visible for that dataset. A live forecast inherits the color of the run drawing the head; a retained forecast_trail= member keeps the color it was fit with; forecast_hue=/forecast_cluster=/forecast_palette= override both with a grouping fixed for the whole animation. Marker-only categorical regrouping still draws no overlay and still says so.
  • return_model=True reports forecasts it could not draw: bundle['predict'] gains drawn (bool) and draw_reason (None, or a sentence naming the limitation). A fit that succeeded is returned whether or not the figure could render it.
  • Forecast artists and traces are tagged so callbacks can find them: artist._hyp_forecast_role on matplotlib ('static', 'live' or 'trail', plus _hyp_forecast_age on trails) and trace.meta['hyp_forecast_role'] on plotly. Every forecast artist also names its series: artist._hyp_forecast_dataset on matplotlib, matching plotly's trace.meta['hyp_dataset'].

Color and hue

  • A plot can take its colors from an image. hypertools.plot.colors.image_palette(image, n_colors=6) extracts a palette from a local image (a path, a PIL image, or an (H, W, 3) array), ordered most visually salient first, where salience is pixel_fraction * chroma, so a painting's vivid subject leads and its muted background follows. A grayscale image falls back to population order. The same extraction is reachable from any plotting call as palette='image:<path>', on both backends and on every color path (categorical, continuous, matrix hue, and the colorbar). An image with fewer distinct colors than there are categories is interpolated up rather than cycled; a single-color image raises. hypertools never downloads the image: fetch it yourself and pass the path.
  • hue_mode= on plot() ('mixture', 'rgb', or the default None) says how a 2-D matrix hue is interpreted. 'mixture' blends each row through palette as weights, one palette color per column, whatever the width; 'rgb' reduces the matrix to three min-max scaled channels used directly as (r, g, b). The default stays width-based, so existing figures that pass a wide matrix hue are not repainted. Combining 'mixture' with color_reduce= raises.
  • A missing categorical hue= label means one unlabeled group. None, NaN and pd.NA all normalize to the same sentinel: one group, neutral gray, no legend entry, no palette slot consumed. Previously two missing labels became two separate saturated categories, and which happened depended on how the caller spelled NaN. forecast_hue= follows the same rule.

Manipulation, pipelines and loading

  • Normalize(mode='isotropic') (also hyp.manip(x, model='Normalize', mode='isotropic')) scales a whole array with one centroid and one scalar, so a shape keeps its proportions inside a shared frame; the default mode='minmax' is unchanged. The morph examples use it.
  • hyp.plot(x, pipeline=p) accepts hand-built hyp.Pipelines whose steps are raw scikit-learn estimators (fitted or not) and bare fitted stage objects: an unfitted pipeline is fit on x, raw steps apply to each dataset in turn, and Pipeline.is_fitted is True for a pipeline assembled from already-fitted steps.
  • hyp.load passes already-loaded data through: a DataFrame, a numpy array, or a list/tuple of those comes back unchanged (with the same reduce/ndims/align/normalize post-processing loaded data gets), so one call can be the entry point over mixed names and in-memory data.
  • hyp.io.lsl_stream() returns an LSLStream that destroys its inlet on close(), on leaving a with block, on garbage collection and at interpreter exit.

Data and IO

  • Video exports (save_path= and HyperAnimation.save()) are now a quality-targeted encode (x264 CRF 23, hypertools.plot.animate.VIDEO_CRF) instead of a fixed 1800 kbit/s, so file size follows content; pass writer= to HyperAnimation.save() for a specific bitrate. Details under "Bug fixes".

Docs, examples and tutorials

  • The gallery merges its duplicate pages (digits/TSNE/UMAP into one; PPCA into missing-data; MDS into the animation page; chemtrails and precog into one trails page; the three cluster pages into one), drops the shape-morph subset of the morph zoo, orders pages by topic, computes the story-trajectories page live, and uses public calls throughout. New gallery example: animate_forecast (three regions' weather with a predict='Kalman' fan).
  • Five new tutorials: Hierarchical DataFrames, Loading and saving data, Fitted models and pipelines, Manipulating data and animating windows and trails, and Forecasting three regions' weather while it is drawn. The plot, align, analyze and reduce tutorials were rebuilt to exercise every argument they describe; the stock tutorial adds Chronos and the projectile tutorial compares scikit-learn imputers with Kalman; every tutorial is executed and ships its outputs (mp4 clips for animations).
  • A new Optional dependencies guide explains the on-demand installer's architecture.
  • The Hierarchical DataFrames guide (docs/hierarchy.rst) and the updated docs/pipeline_order.rst, described above.
  • docs/animation.rst documents meta['hyp_trace_index'] beside the forecast tags, and has an "Identifying forecast artists" section for the role tags.
  • Five launch examples were rebuilt for this release and ship as executed tutorial notebooks with mp4 clips: "A quarter century of the market: six sectors, one space" (market_sectors), "A century of weather: twenty cities as twenty features, one hot path" (weather_decades), "Five paintings, described in words, drawn in their own colors" (painting_embeddings), "The shape of a conversation: per-speaker paths, revealed one turn at a time" (conversation_shape), and "Morphing through the shapes zoo, with titles" (morph_shapes_zoo). Links are under "Links".

Changed behavior and new validation

  • hyp.load('wiki') and hyp.load('nips') return a flat list of strings (one document per entry), like hyp.load('sotus'); previously a list holding one (n, 1) object array. Update any [str(p) for p in x[0].ravel()] call site to use the list directly.
  • Non-streaming Hugging Face loads decode ClassLabel columns to their string names, matching a streaming load. Pass decode_labels=False to keep the integers.
  • The Hugging Face text and dataset paths set HF_HUB_DISABLE_PROGRESS_BARS, HF_HUB_VERBOSITY and TOKENIZERS_PARALLELISM (via setdefault, so an explicit setting wins) before importing the libraries that read them, so no notebook needs the env-var preamble.

Each of these turns previously accepted input into rejected input, or changes what an existing call draws.

  • A pipeline= that already carries a column-hierarchy record is checked against the frame being plotted, while the leaves still have labels. Under 'name' correspondence a mismatch now raises at the plot() call, naming the missing and unexpected features, and the leaves are restored to fit-time order (the fitted steps are positional, so the frame's own order produced silently wrong coordinates). feature_correspondence='position' is unaffected.
  • Frames carrying a hierarchy on both axes are rejected (x has both a row and a column MultiIndex ...). Before 1.1 such a frame followed the row path and its column hierarchy was silently ignored. Fix: drop one of the two hierarchies before plotting.
  • A column-hierarchical DataFrame nested inside a list is rejected by hyp.plot (before 1.1 it was flattened to a single line, silently). hyp.predict rejects a hierarchical frame in a list on either axis. This is deliberately asymmetric: a row-hierarchical frame inside a list passed to hyp.plot keeps its documented warn-and-flatten behavior. Fix: pass the frame bare; hierarchy expansion is defined for a bare frame only.
  • hyp.predict rejects a column hierarchy whose groups do not name the same features. Groups carrying different, or differently many, innermost labels raise an error naming the missing and unexpected features (before 1.1 the frame was flattened into one wide series and forecast). Groups that share their labels in a different order are still accepted and come back permuted into the first group's feature order. Fix: rename the innermost level so every group carries the same labels, or group with group_columns(df, feature_correspondence='position') (from hypertools.core.hierarchy import group_columns) and forecast the leaves.
  • hyp.predict rejects a time-like index with duplicate entries, including on flat input. A plain DataFrame/Series on a DatetimeIndex, TimedeltaIndex or PeriodIndex with repeated stamps now raises ValueError: the dataset index has N duplicated entries ... the forecast horizon is ill-defined (before 1.1 it forecast with a step inferred from the surviving gaps). Non-time indexes are unaffected. Fix: aggregate the repeats (df.groupby(level=-1).mean()) or give them distinct times.
  • predict= with a MultiIndex frame no longer raises blanketly. It previously raised ValueError: predict= is not supported with MultiIndex expansion in this release; it now forecasts every plotted trajectory. A hierarchy whose traces are shorter than 2 rows still raises, and the message now names the offending trace, its row count and the cause.
  • Hierarchy groups whose label is missing (NaN) are no longer dropped, and a missing label is one group. Labels are canonicalized NA-aware (np.nan, None and pd.NA normalize to one sentinel) for grouping, top-level uniqueness and style lookup, on both axes; the original label values are preserved in the returned keys and in the legend.
  • forecast_hue=, forecast_cluster=, forecast_n_clusters=, forecast_palette= and forecast_fmt= count final traces, not input datasets. Under a hierarchy their unit is every plotted trajectory, leaves and derived means, so a three-sector frame needs four values, not three. Nothing changes for flat input.
  • ax= is rejected together with animate=. An animated plot owns its own figure, so the axes passed in were left empty. Fix: drop ax= and style the returned animation's .figure; for several panels in one animation, translate each group into its own region of one shared frame and draw with a single call.
  • ax= under the plotly backend draws into a plotly Figure, and a matplotlib Axes is refused. fig = hyp.plot(A); hyp.plot(B, ax=fig) appends B's traces to fig and returns it (the caller's layout untouched; animate= is refused with it). A matplotlib Axes under plotly raises ValueError before any analysis runs; before 1.1 it was silently ignored and the axes left empty. A plotly Figure passed to a matplotlib draw is a TypeError.
  • predict= forecast overlays inherit the style of the observed trace they continue: its color, linestyle and linewidth, differing only in transparency (forecast_alpha = observed_alpha * 0.5, so the default forecast alpha is 0.5; alpha=[1.0, 0.4] gives forecasts at [0.5, 0.2]). A collection of models (predict=['Kalman', 'ARIMA']) keeps each dataset's color and cycles the linestyle per model, and a forecast recolored by forecast_palette=/forecast_hue=/forecast_cluster= keeps its trace's alpha. This is a visible change to existing forecast figures: every forecast used to be drawn linestyle='--' at a hard-coded alpha=0.6. Both backends share one constant, hypertools.plot.forecast.FORECAST_ALPHA_SCALE. forecast_trail= now fades from that dataset's live forecast alpha down to a floor proportional to it rather than a fixed 0.08. Code that located forecast artists by their dashed linestyle must switch to the role tags (artist._hyp_forecast_role on matplotlib, trace.meta['hyp_forecast_role'] on plotly).
  • Animated continuous-hue line plots with no explicit linewidth= render at 1.0 instead of 1.5, matching animated no-hue lines. This is a visible change to existing animated hue figures. Fix: pass linewidth=1.5 to keep the old look.
  • hyp.describe returns 'average' as the mean of the per-dataset curves. It used to be the curve for all datasets stacked into one point cloud, which is a different number whenever there is more than one dataset; that curve is still returned, as 'pooled'. hyp.describe(show=False) now draws and returns the figure without displaying it, as hyp.plot(show=False) does; it used to return fig=None. The component axis is ticked at whole numbers.
  • In an animation, each truth= point appears on the first frame whose drawn forecast reaches it and then stays. It used to be drawn in full from the first frame. Static plots and animate='spin' are unchanged.
  • An animated forecast starts at the point the line has reached in that frame. It used to start at the last observation behind it, so it stood still while the line moved and then jumped.
  • forecast_hue=/forecast_cluster= without forecast_palette= take the palette colors after the ones the observed data uses. They used to restart the palette, so the first forecast group was drawn in the first dataset's color. Pass forecast_palette= to choose the colors.
  • Matplotlib axis_scale='data' axes no longer draw gridlines, matching plotly. A grid on your own ax= is kept.
  • Model comparisons rank complete models only. hyp.impute(x, model=[...], truth=) and hyp.predict(x, model=[...], holdout=) used to name the lowest-scoring model best even when it had left values unscored, so a model scored on fewer, easier values could win. A model with any unscored value keeps its row but is excluded from the verdict and listed in the new scores.attrs['incomplete']. When no model is complete, attrs['best'] and attrs['beats_baseline'] are None; beats_baseline is also None when the baseline itself is incomplete. Each case warns.
  • hyp.impute(truth=, mask=) matches labeled data by label. A truth or mask DataFrame whose rows or columns are in another order is reindexed to the data's order; it used to be compared by position, which silently changed the score. Different label sets raise ValueError. Bare arrays are still compared by position.
  • hyp.describe sweeps the dimensionality of a reducer instance. reduce=PCA(n_components=2) used to be evaluated at 2 components for every point of the curve (also in 1.0); an unfitted instance is now cloned for each point. An already fitted model, an instance with no n_components, and reduce=None raise ValueError.
  • An installed optional package that is older than hypertools requires is upgraded on first use, with a notice, or reported with its installed and required versions. It used to be accepted as it was, and a plotly below 6.1.1 failed later with a message about kaleido. With hyp.set_autoinstall(False), backend='plotly' on a too-old plotly raises, and backend='auto' warns and uses matplotlib.
  • Core dependency floors are raised to the first releases with Python 3.10 through 3.13 wheels: numpy 2.1.0, pandas 2.2.3, scipy 1.14.1, matplotlib 3.9.2, scikit-learn 1.5.2, statsmodels 0.14.3 and pillow 10.4.0.

Bug fixes

The fixes from the changelog's 1.1.0 and 1.0.1 sections. Those from the 1.1.0 section came out of the hierarchy work, the Colab validation of the feature tour, the folded-in examples and the tutorial rebuild, and each affects flat input too. The fixes made during the pre-publication review follow in their own section.

  • NumPy 2-compatible optional dependency floors. The gensim and density3d extras require gensim>=4.4.0 and scikit-image>=0.25.0; the earlier advertised minimums predate upstream NumPy 2 support.
  • Backtests fit an independent copy of an unfitted model for each dataset, instead of reusing the first dataset's learned parameters on the later ones. Forecast and imputation scoring leave caller-owned instances unchanged and reject already-fitted instances, which may have seen the held-out truth.
  • Concurrent URL caching. Threads downloading the same URL use distinct temporary files, so the atomic replacement no longer raises FileNotFoundError.
  • Delay column collisions. Delay rejects duplicate column labels, and distinct labels with identical string representations, instead of silently overwriting embedded features.
  • Bundled font precedence. The bundled Noto Sans faces take precedence over same-family system fonts, so rendering stays the same on machines with another Noto Sans installed.
  • Release documentation. Plotting and scoring features shipped in 1.1 are labeled 1.1 in their API documentation (leftover 1.2 labels are corrected), and the installation guidance distinguishes Kalman imputation from Kalman/ARIMA forecasting.
  • Video exports were written at a fixed 1800 kbit/s. A file's size followed its duration and nothing else (a two-minute clip was 27 MB at 1400 x 700 and 26 MB at 980 x 490). Video is now a quality-targeted encode (x264 CRF 23, hypertools.plot.animate.VIDEO_CRF); the same 5-second test clip went from 0.96 MB to 0.38 MB with no visible difference. writer= on HyperAnimation.save() still takes a specific bitrate.
  • The "Animation was deleted without rendering anything" warning could still fire from a discarded HyperAnimation when the wrapper died inside a reference cycle. The wrapper now marks the animation as draw-started when it is constructed, so finalizer order no longer matters.
  • HyperAnimation.save() silently discarded every keyword except fps=, so anim.save('clip.gif', dpi=75) wrote the GIF at the figure's own dpi. dpi= is now forwarded to the raster and video writers, and any other keyword raises TypeError naming it.
  • Closing an animated figure under matplotlib's notebook backend raised AttributeError: 'NoneType' object has no attribute 'remove_callback'. nbAgg processes a figure's close event twice, so on Colab every displayed animation made the next static-plot cell fail. Animations are now a FuncAnimation subclass (hypertools.plot.animate.HyperFuncAnimation) whose _stop ignores the repeat call.
  • A plotly save_path= to a raster/PDF format on a machine without Chrome failed with kaleido's bare RuntimeError. kaleido 1.x renders through a headless Chrome, which a fresh Colab or Kaggle kernel does not have. With installation on (the default), hypertools now fetches a Chrome on first use, plus the system libraries it needs where it can run apt-get. When that fails, or hyp.set_autoinstall(False) is in force, the failure is a HypertoolsIOError stating the cause and the ways out: import plotly.io as pio; pio.get_chrome() (about 150 MB) plus, on Colab and Kaggle, the four system libraries the downloaded Chrome needs (apt-get install -y libatk1.0-0 libatk-bridge2.0-0 libatspi2.0-0 libxcomposite1), installing Chrome, or saving with backend='matplotlib'.
  • Under the plotly backend, a figure kept in a variable was not displayed in a notebook. fig = hyp.plot(x) drew nothing (on Colab, where backend='auto' resolves to plotly, 29 of the feature tour's plot cells were blank) because the backend never called fig.show() inside IPython: doing so drew a figure that was also the cell's last expression twice, and mid-cell, ahead of matplotlib figures flushed at the end. plot() now queues the figure for a one-shot IPython post_execute callback that runs after matplotlib-inline's flush and skips any figure the rich-display hook already showed. The returned figure is still a plotly.graph_objects.Figure subclass. Both usages draw exactly once, in cell order.
  • Inside IPython, a matplotlib backend that cannot be switched to raised the GUI toolkit's own error instead of HypertoolsBackendError. The %matplotlib magic imports the toolkit itself, so 'TkAgg' without _tkinter or 'GTK3Agg' without gi escaped as a raw ModuleNotFoundError (a missing display as TclError), while a plain script already raised HypertoolsBackendError. The notebook path now raises HypertoolsBackendError too, with the toolkit's error chained as the cause.
  • Drawing into a caller-supplied ax= warned Glyph 8722 (MINUS SIGN) missing from font(s) Noto Sans for every negative tick. The bundled Noto Sans has no U+2212. hypertools' own axes carry the whole font stack, so matplotlib's per-glyph fallback reaches DejaVu Sans, but axes created outside hypertools' rc context keep the sans-serif alias, which resolves to one font with no fallback. hypertools now formats negatives with an ASCII minus (axes.unicode_minus = False) while it draws, and gives a caller-supplied axes' tick labels the same font list its own axes use, so a label formatted before the call (measured again by a later panel's layout) falls back to DejaVu for the glyph.
  • Every continuous-hue matplotlib plot rendered fully opaque, whatever alpha= was set to: _apply_multicolor_lines never read alpha from its per-trace kwargs.
  • The return_model=True pipeline could not be re-applied to a column-hierarchical frame. bundle['pipeline'].transform(df) failed inside scikit-learn (X has 20 features, but IncrementalPCA is expecting 5 features as input) or silently returned the ungrouped frame. The pipeline now records the grouping (Pipeline(..., input_hierarchy=)) and returns one array per group, matching features to the fitted steps by name.
  • plotly discarded the per-trace alpha under a continuous hue=, so a hue plot matplotlib drew at alpha=0.7 rendered fully opaque on plotly. Line colors now carry the alpha; marker colors deliberately do not, matching matplotlib.
  • With ndims=1, matplotlib drew the predict= overlay at x = 0..t instead of continuing the observed series (forecast x 0..3 against an observed line at 0..59). Both backends now draw the continuation.
  • A marker-only fmt (e.g. 'o') with hue= and predict= drew the forecast in a different color on each backend. The final-observed-hue anchor is now applied on both the line and marker paths on both backends.
  • plotly's 1-D marker branch drew a continuous hue in one flat color. It now passes the per-point color array through like the 2-D and 3-D branches.
  • A hierarchy silently discarded legend=[...], and legend=False. legend= is now honored under a hierarchy: a list renames the top-level groups (one entry per unique top-level index value, in first-appearance order; any other length raises ValueError naming both counts), and legend=False suppresses the automatic legend but not the colorbar.
  • names= ALONE raised "pass dataset names via names= OR a legend= list, not both" on a hierarchy. The conflict now tracks what the caller passed; names= on a hierarchy raises an instructive ValueError pointing at legend=[...].
  • matplotlib's '_nolegend_' sentinel leaked into plotly trace names, rendered in hover labels and written into exported HTML. Any leading-underscore label now becomes name=None; which traces appear in the legend is unchanged.
  • plotly and matplotlib serialized colors differently. One helper truncated each channel where the other rounded (rgb(219,95,87) versus rgb(219,94,86)). Both round now.
  • legend= as an ndarray/Series/Index mislabeled every trace. The whole container was handed to matplotlib as each artist's label, and legend=('A', 'B'), colorbar=True drew a colorbar reading 1, 2. Every accepted container is now normalized to a list; a 0-d array counts as one label, as legend='a' does.
  • A caller-supplied pipeline= came back from a hierarchical plot unable to re-apply. Only the pipeline plot() built for itself recorded the column grouping. The grouping is now recorded on the passed-in pipeline too, unless it already carries one of its own.
  • The plotly backend's sliding animation window now matches matplotlib's, frame for frame. Plotly paced every dataset against the longest dataset and clamped the shorter ones, so a shorter dataset went blank for most of its own animation (a 5-row dataset beside a 15-row one drew nothing for 9 of 15 frames), the head window ran one point short (opening a gap between a chemtrails trail and its head), frame 0 of a precog trail was one point short, and a sub-frame request produced a 2-frame plotly animation against matplotlib's single still. Both backends now call one shared function, hypertools.plot.trails.anim_window_bounds.
  • A very short animation no longer comes out empty. When duration * frame_rate rounded below 1, animate='serial' and animate='spin' asked matplotlib for zero frames. Every style now floors at one frame on both backends.
  • animate='spin' paces its orbit over the frames it actually draws. The matplotlib spin camera divided by the raw frame_rate * duration product rather than the drawn frame count (at frame_rate=7, duration=2.5, matplotlib's last frame sat at 289.7 degrees and plotly's at 280.0). Both backends now divide by the rounded frame count.
  • Animated MultiIndex plots with trails no longer crash. A row-MultiIndex DataFrame with chemtrails/precog/bullettime raised TypeError: ... got multiple values for keyword argument 'alpha'. The 0.3 trail fade is now folded into whatever alpha the trace already carries.
  • cluster=/hue= line plots spanning more than one run per dataset no longer crash, or silently misplace, point labels=. Bridging two same-dataset runs duplicated a data point without a matching labels= entry, crashing annotate_plot with IndexError: list index out of range or attributing labels to the wrong point. Bridged labels now grow in lockstep with the bridged data.
  • title= no longer stringifies a list onto the axes. A non-string title= raises TypeError instead of drawing the literal "['a', 'b', 'c']", and the check runs before the analyze pipeline so plot_stream gets it too.
  • linewidth= is honored in animated continuous-hue line plots. The overlay previously fell back to rcParams['lines.linewidth'] regardless of what you passed.
  • animate='morph' over clouds larger than 2000 points no longer appears to hang. It is capped at 2000 points by default, or raises naming morph_samples= and simplify=True when you pass simplify=False.
  • title= is now actually visible on animated 3-D matplotlib plots. The maximized 3-D axes left zero margin for the title, so both a scalar and a per-segment title= rendered off-canvas. Animated 3-D plots now reserve a top strip sized to the measured title-line height, growing the figure rather than shrinking the axes, whenever a title will be drawn.
  • Fitting a legend/colorbar/title around an animated plot no longer fires on_frame= (or a per-segment title= schedule) one extra time before the animation has started. The measurement draws are now guarded the same way matplotlib's own Animation.save() guards its internal draws.
  • A partial-tag animate='morph' list (e.g. animate=[None, 'morph', 'morph']) now names and reports the DATASET actually being shown, on both backends. Per-segment title= and FrameContext.current_index previously indexed by position within the morph sequence, shifting every title and index down by one when a leading dataset was untagged.
  • ctx.datasets for animate='morph' is now the same, morph-sampled arrays on both backends. plotly recorded the raw, uncapped input arrays; matplotlib recorded the Hungarian-matched, morph_samples-capped clouds. The two now agree.
  • plotly trail traces (chemtrails/precog/bullettime) honor per-dataset alpha=. They previously hardcoded a flat 0.3 opacity; a per-dataset alpha= list now fades plotly trails as matplotlib does (0.3 * alpha).
  • animate='spin'/'window', order='serial', and a per-dataset title= list together now raise immediately with an accurate message. Previously the combination ran the whole pipeline, warned that order='serial' was ignored, and then raised TypeError advising order='serial'. The error now fires before the pipeline and names the reason (the style has no serial ordering to name segments by).
  • Kalman forecasts no longer diverge from a near-saturated fit. hyp.predict(x, model='Kalman', t=...) could return values up to 1e7 times the range of the data (19 of 432 fits on 40x3 drifting random walks exceeded 100x the data range), because the delay-embedded transition operator was estimated with nothing checking that it was non-explosive.
  • A singleton-hue= warning now names the category the caller passed. It read hue category '_nolegend_' has only one observation ... for any singleton run after the first of its category. The warning now reads the real per-run category names.
  • alpha= was ignored by animate='morph'. The value landed on the per-dataset line artists, which a morph keeps hidden, and never on the one traveling point cloud that is drawn (plotly dropped it the same way), so fading the cloud meant reaching into the figure with set_alpha after the call. The cloud now takes the alpha on the same hold/transition schedule as its color: a hold draws the held dataset's alpha, a transition eases from the departing dataset's alpha to the arriving one's, and a scalar alpha= is constant throughout. Plots that pass no alpha= are unchanged.
  • An LSL stream left open logged a liblsl error at exit. hyp.io.lsl_stream() only close_stream()ed its pylsl.StreamInlet, and only when the generator was closed, so a notebook or script that just moved on saw ERR| Stream transmission broke off at teardown. The stream is now an LSLStream that destroys the inlet on close(), on leaving a with block, on garbage collection, on the silent-source abort, and at interpreter exit.
  • PPCA imputation warned divide by zero encountered in log on data with a few dozen or more features (hyp.impute(model='PPCA') and the NaN fill hyp.plot applies at format time). The EM objective computed log(det(Sx)), which underflowed to log(0) and then fell back to a sign-flipped value; it now uses slogdet, identical to rounding wherever the old value was representable.
  • labels= annotations were drawn on pyplot's current axes, not the ax= passed in, so panel labels stacked on one axes; and a color-list colorbar (color=['red', ...]) raised IndexError.
  • hyp.plot(docs, vectorizer='all-MiniLM-L6-v2') crashed with the default semantic=/corpus=. Pretrained embedding vectorizers now resolve the default semantic stage to none silently, never load or embed a corpus, and an explicit semantic='NMF' raises a clear hypertools ValueError before any corpus work.

Fixed during the release review

The 1.1.0 draft was reviewed against 1.0.0 before publication, and the fixes ship in 1.1.0. The full list is in the changelog's "Fixed during the release review" section; in summary:

  • Offline loading. hyp.load(..., offline=True) never downloads and opens no network connection: built-in datasets are served only from a hash-valid local copy, and anything missing raises HypertoolsOfflineError naming the file.
  • Installer. hyp.set_autoinstall(False) also reaches the worker process that renders plotly animation exports, and that export raises ImportError or HypertoolsIOError rather than a wrapped RuntimeError. Overlapping with hyp.set_autoinstall(...) blocks keep the newest setting in force.
  • Multi-panel plots. panels= hands every per-dataset and per-forecast argument to the right panel, reuses the clustering it fitted instead of re-clustering each panel, picks each cell's projection from the analyzed data, chooses its grid from the figure's aspect ratio, and returns the shared pipeline in the return_model=True bundle. On plotly, each panel keeps its own frame, legend, colorbar and title, and the grid is laid out like the matplotlib one.
  • Forecast display. A legend lists every predict= forecast under its model's name (a single model was left out before); a collection of models keeps each dataset's color and takes one linestyle per model; recolored forecasts keep their trace's alpha; plotly honors a color letter and markers in forecast_fmt=; repeated calls into one axes, figure or grid cell compose their forecasts, truth= overlays and legends.
  • Colors and layout. palette= color lists cycle as in 1.0.0, an empty palette raises ValueError, NaN in a continuous hue= no longer affects the color range, a caller's ax= draws in the hypertools palette and a second call continues it, the 2-D frame square has a margin around the data, and a 3-D figure's axis labels stay inside a tight bounding box.
  • Streaming and text. Streaming plots work when plotly is the render backend (the Colab and Kaggle default), and axis labels join the font-coverage scan, so non-Latin axis labels no longer draw as empty boxes.
  • Plotly rendering. Hover labels name each trace (they read "trace 0"), and the leaves of a nested dataset list are named and grouped by their group. Markers sit on observations only and draw at matplotlib's size in 3-D; 3-D lines draw at full width; colors hold under mixed transparency; animated legends no longer grow entry by entry; an animated continuous hue stays on the moving data; date axes are not shifted by the viewer's time zone.
  • Surfaces. A surface= takes its color from the points nearest each vertex, so it matches the dots under it.
  • Animation. Animated lines keep every observation instead of one row per frame; a morph shows its in-between frames; on_frame= titles stay on the canvas; companion panels use the right color.
  • Forecasting. Business-day data are no longer interpolated onto weekends; a fitted forecaster can be reused on a different kind of index; 1-D forecasts are drawn at the data's scale.
  • Explore mode. explore=True hover labels open toward the center of the axes, so a label near the edge of the window is not cut off.
  • Pipelines. sklearn.base.clone works on a hyp.Pipeline and returns an unfitted, independent copy (it raised in 1.0 too).
  • Input handling and errors. pandas and polars Series keep their index and name through the Manipulator classes and hyp.Pipeline; a repeated metrics= entry, holdout=True with t=0, hyp.load(..., streaming=True) on a non-Hugging-Face source and degenerate alignment_score input each raise a ValueError saying what is wrong; synthetic datasets accept more seed types; hyp.text_windows accepts NumPy integers; text2mat reads a flat list of strings as one dataset; predict='ARIMA' animates.

Known limitations

  • Ragged groups (unequal feature counts per group) are rejected by both entry points, by an error naming the missing and unexpected features. The error's remedy is spelled for hyp.plot; a hyp.predict caller groups with group_columns(df, feature_correspondence='position') (from hypertools.core.hierarchy import group_columns) and forecasts the leaves (hyp.predict([leaf.to_numpy() for leaf in leaves], model, t)).
  • Unequal-length row groups are averaged over their overlapping prefix, with one aggregated warning.
  • Feature correspondence across groups is established by name, so groups with disjoint innermost labels are refused rather than silently stacked. feature_correspondence='position' on group_columns is the opt-in, and it is not a positional hierarchy mode: passing its arrays to hyp.plot gives a plain list of datasets (no per-level means, no hierarchy styling, trace_metadata is None). There is no public plot(feature_correspondence=...) in 1.1.
  • The order of the groups is not neutralized the way the order of features within a group is. Groups become datasets, reduce= row-stacks every dataset and fits one model on the stack, so a reducer whose fit depends on row order embeds a block-reordered frame differently. On a 40-row frame of 4 sector blocks x 5 measures, reordering the blocks produced a different embedding under the default IncrementalPCA and under TSNE, while PCA, TruncatedSVD, FactorAnalysis, Isomap and SpectralEmbedding preserved it up to numerical and sign equivalence. hyp.plot([A, B, C]) and hyp.plot([C, B, A]) differ the same way, and did before 1.1. Pass reduce='PCA' when block order must not matter.
  • Continuous hue= over a row hierarchy is still warned-and-ignored; only column hierarchies honor it in 1.1.
  • Under a categorical hue=/cluster= regrouping, an animated live forecast takes the color of the run drawing the head, so its color can change as the head crosses a category boundary. Under a continuous hue= a forecast takes its source trajectory's final observed hue color, animated and static alike.
  • Duplicate innermost feature names inside one group are kept rather than rejected or de-duplicated, and matched across groups by (label, occurrence). Rename the innermost level first if you need them distinguishable in a legend.
  • predict= needs at least 2 rows per plotted trace, on either axis. Over a row hierarchy this is the binding constraint: expansion draws one trace per unique full index tuple, so a frame whose innermost index level is unique per row cannot be forecast; flatten it (df.reset_index(drop=True)) or move the grouping to the column axis. Over a column hierarchy it applies only when the frame itself has a single row.

Also in this release: the unpublished 1.0.1 changes

The changelog keeps a separate 1.0.1 (unreleased) section and explains: "1.0.1 was never published on its own. These changes were developed as a patch release and now ship as part of 1.1.0, which is what pyproject.toml declares; they are kept in their own section because they are separable from the hierarchy work above." Every guide and docstring dates those behaviors to 1.1.0. Its features, changed behaviors and fixes are already merged into the sections above rather than repeated here.

Upgrading from 1.0.0

  • Lines are drawn smoothed by default. Pass antialias=False to get the 1.0.0 straight-segment rendering back.
  • predict= overlays now take the observed trace's color, linestyle and linewidth at half its alpha, instead of a dashed line at alpha=0.6 (a forecast recolored by forecast_hue=, forecast_cluster= or forecast_palette= keeps the full alpha). Use forecast_fmt=, forecast_hue= or forecast_palette= to restyle, and find forecast artists through artist._hyp_forecast_role / trace.meta['hyp_forecast_role'] rather than their linestyle.
  • Animated continuous-hue lines default to linewidth=1.0 instead of 1.5. Pass linewidth=1.5 to keep the old look.
  • hyp.predict on a DatetimeIndex, TimedeltaIndex or PeriodIndex with duplicate stamps raises. Aggregate the repeats (df.groupby(level=-1).mean()) or give them distinct times.
  • ax= together with animate= raises. Drop ax= and style the returned animation's .figure.
  • ax= under the plotly backend now takes a plotly Figure to draw into (it used to ignore a matplotlib Axes and leave it empty; a matplotlib Axes now raises). Pass the Figure an earlier hyp.plot returned, or draw that call with backend='matplotlib'.
  • A frame with a MultiIndex on both axes raises. Keep one hierarchy.
  • A column-hierarchical frame inside a list raises in hyp.plot; a hierarchical frame inside a list raises in hyp.predict on either axis. Pass the frame bare.
  • hyp.predict on a column hierarchy whose groups name different features raises. Rename the innermost level so every group carries the same labels, or use group_columns(df, feature_correspondence='position') (from hypertools.core.hierarchy import group_columns) and forecast the leaves.
  • A pipeline= fit on a column hierarchy is checked by feature name against the frame being plotted. Plot a frame whose groups carry the feature names the pipeline was fit on; feature_correspondence='position' is unaffected.
  • forecast_hue=, forecast_cluster=, forecast_n_clusters=, forecast_palette= and forecast_fmt= count plotted traces (leaves plus means) under a hierarchy, so supply one value per drawn trajectory.
  • hyp.describe(...)['average'] is now the mean of the per-dataset curves; use ['pooled'] for the stacked curve it used to hold. hyp.describe(show=False)['fig'] is now the figure rather than None.
  • Code that reads scores.attrs['best'] or ['beats_baseline'] from hyp.impute/hyp.predict must allow None, and can read attrs['incomplete'] for the models left out of the ranking.
  • A truth= or mask= DataFrame for hyp.impute is matched to the data by label. Pass .to_numpy() to compare by position.
  • hyp.describe no longer accepts an already fitted reducer or reduce=None. Pass a name, a {'model': ..., 'kwargs': ...} spec or an unfitted instance.
  • In an animation, truth= points appear as the forecast reaches them, and default forecast_hue=/forecast_cluster= colors no longer repeat the observed lines' colors. Pass forecast_palette= to fix the colors.
  • HyperAnimation.save() now raises TypeError on keywords other than fps= and dpi= (it used to drop them silently). Pass writer= to delegate every keyword to matplotlib.
  • A title= that is neither a string nor a callable raises TypeError, and a list of strings is accepted only for a serial-style animation (one per dataset). Pass one string, a callable ctx -> str, or that list.
  • animate='morph' over more than 2000 points is downsampled to 2000 unless you set morph_samples=; pass simplify=False to get an error instead.
  • hyp.load('wiki')/hyp.load('nips') now return a flat list of strings. Replace [str(p) for p in x[0].ravel()] with the list itself.
  • Non-streaming Hugging Face loads now decode ClassLabel columns to their string names. Pass decode_labels=False to keep the integer codes.
Source: README.md, updated 2026-10-10