| Name | Modified | Size | Downloads / 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, sopyproject.tomlremains 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 onewithblock;HYPERTOOLS_AUTO_INSTALL=0sets the starting value for prebuilt images); a missing extra then raisesImportErrornaming the manualpip install "hypertools[<extra>]"command, as before. Guide: Optional dependencies in the docs.
New top-level names
hyp.set_autoinstall(above),hyp.FrameContext(theon_frame=argument),hyp.subplots, and the helpershyp.text_windows,hyp.damageandhyp.stack.hyp.HypertoolsOfflineError, raised byhyp.load(..., offline=True)when a file is not cached (also importable fromhypertools.io).hyp.HypertoolsTrustError, raised when a remote payload could only be loaded by relaxing a security policy andtrust=Truewas not passed. It existed in 1.0.0 ashypertools.io.sources.HypertoolsTrustError, which still works, and it remains aValueErrorsubclass.
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=TrueraisingHypertoolsOfflineError). - Helpers:
hyp.text_windows(sliding word/sentence/character windows),hyp.damage(reproducible NaN knock-out on arrays/DataFrames), andhyp.stack(builds a column-hierarchical DataFrame from nested dicts or lists, withaggregate=). - Forecasting/imputation:
hyp.predict(x, model=[...])returning{name: forecast};holdout=kbacktests 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 boxcarSmooth(kernel='boxcar', center=False, min_periods=)and aDelay(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)andhyp.subplots(nrows, ncols, ndims=3);title_kwargs=/title_color=/title_wrap=; per-datasethue=andlabels=(withlabel_anchor=); legends under matrix/mixture hue (legend_kwargs=,legend_colors=);bundle['colors']. - Animation:
FrameContext.progress/window_boundsand populatedrevealed_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 decodingClassLabelcolumns (decode_labels=Falseto keep integers);hyp.load('wiki')/hyp.load('nips')returning a flat list of strings; HF text/dataset paths defaultingHF_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, withxlim=/ylim=;'unit'unchanged and now documented),ndims=1as a time-series mode (withreduce=None, one line per column against the row index, DatetimeIndex aware, 2+ columns; the defaultreduce=first reduces to one component),truth=drawing the actual continuation beside apredict=forecast, andpredict=['Kalman', 'ARIMA', 'GP']drawing one legend-labeled overlay per model.
Hierarchical DataFrames
- A column
MultiIndexframe 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 alllen(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 oflen(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.predictaccepts hierarchical frames. A bare frame with aMultiIndexon one axis is split into groups and forecast group by group, returning[f0, f1, ...](or([f0, ...], [m0, ...])withreturn_model=True). Before this, a column-hierarchical frame was silently flattened into one wide series and a row-hierarchical one died inside pandas withTypeError: 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.predictis 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 andValueErrors are re-emitted with the group key prepended, and the horizontandmodel=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, andbundle['predict']['forecasts'][i] == hyp.predict(bundle['trace_data'][i], model, t)for everyi. It works withanimate=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, andplot()says so directly, naming a row-count-changing analysis stage (manip='Resample', an aggregatingreduce=) when that is what made a trace short.return_model=Truenow also returnstrace_dataandtrace_metadatadescribing every plotted trajectory.trace_dataholds the final pre-center/pre-scale trajectories;xform_datais unchanged (the analyzed pipeline output per input dataset, with no derived means). The two differ when areduce=spec pins more than three components (n_components=5leavesxform_data5-D whiletrace_datais 3-D). Bundled forecasts always correspond totrace_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'scoll._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.rstgains hierarchy expansion and mean construction in the prose and the regenerated diagram.
Animation
alpha=now reaches the traveling cloud ofanimate='morph'on both backends (it previously applied only to the static holds).plot(..., on_frame=...): a public per-frame hook on both backends.on_frameis called once per drawn frame with a singleFrameContextargument (frame index and total, axes and drawn artists, animated arrays, serial-reveal counts, andsegment_index/segment_kindforanimate='morph').FrameContextis exported ashypertools.FrameContext. On matplotlib, callbacks can also be attached after construction viaHyperAnimation.on_frame(callback)(chainable); this is not available on plotly, so passon_frame=toplot()for backend-portable code. Callbacks must be deterministic and idempotent for a given frame context.ctx.figure/ctx.axes/ctx.artistsare backend-native (ctx.axesisNoneon plotly).order='parallel'|'serial'onplot(), orthogonal toanimate=, so trail styles compose with a serial reveal (animate=True, order='serial', chemtrails=True).animate='serial'remains a permanent alias foranimate=True, order='serial'.animate='serial'composes withchemtrails/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-datasetcolor=/linewidth=. Inputs that assign alpha internally (rowMultiIndexframes, 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. Foranimate='morph'the holds are named and the transitions left blank automatically. Anywhere else atitle=that is neither a string nor a callable raisesTypeError. - 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=Truebundles, 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 anyframe_rate. Passantialias=Falseto restore the previous straight-segment rendering exactly. simplify=onplot()(defaultTrue). Today it governsanimate='morph'only: over clouds larger than 2000 points, a morph withoutmorph_samples=is downsampled to 2000 silently, because an uncapped morph was measured as killed at 10 minutes versus 8.2 s atmorph_samples=2000. Passsimplify=Falsefor an explanatoryValueErrorinstead, which restores the guarantee that no data point is ever dropped. An explicitmorph_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). Aprecog=trail on a not-yet-reached run now shows that run's whole future. HyperAnimationaccessors:HyperAnimation.save()forwardsdpi=andfps=and rejects other keywords by name;HyperAnimation.on_frame(callback)attaches per-frame callbacks;anim.animationis ahypertools.plot.animate.HyperFuncAnimation, still amatplotlib.animation.FuncAnimationsubclass.
Forecasting (predict= and hyp.predict)
predict=works withanimate='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, andani.save()andto_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 raisesNotImplementedError, 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 ofchemtrails=. Withpredict=and a time-progressing animation,forecast_trail=Trueretains 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. Withoutpredict=it raisesValueError.- 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 exceedsslow_warning_seconds=(default 10;Nonesilences), before the wait rather than after it. forecast_hue=,forecast_cluster=,forecast_n_clusters=,forecast_palette=andforecast_fmt=style forecasts separately from the data. Inheritance stays the default, and each of these replaces exactly one aspect of it; a forecast recolored byforecast_palette=,forecast_hue=orforecast_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 afterreduce=/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=andforecast_cluster=are mutually exclusive;forecast_n_clusters=is separate fromn_clusters=. All five requirepredict=and raiseValueErrorwithout it.forecast_fmt=is validated with matplotlib's ownfmt=parser;forecast_hue=rejects a bare string.predict=works withhue=/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 continuoushue=the overlays were drawn and then deleted by theLineCollectionswap, and plotly raisedIndexErrorunder a regroupinghue=.predict=works withhue=/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 retainedforecast_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=Truereports forecasts it could not draw:bundle['predict']gainsdrawn(bool) anddraw_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_roleon matplotlib ('static','live'or'trail', plus_hyp_forecast_ageon trails) andtrace.meta['hyp_forecast_role']on plotly. Every forecast artist also names its series:artist._hyp_forecast_dataseton matplotlib, matching plotly'strace.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 ispixel_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 aspalette='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=onplot()('mixture','rgb', or the defaultNone) says how a 2-D matrixhueis interpreted.'mixture'blends each row throughpaletteas 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'withcolor_reduce=raises.- A missing categorical
hue=label means one unlabeled group.None, NaN andpd.NAall 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')(alsohyp.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 defaultmode='minmax'is unchanged. The morph examples use it.hyp.plot(x, pipeline=p)accepts hand-builthyp.Pipelines whose steps are raw scikit-learn estimators (fitted or not) and bare fitted stage objects: an unfitted pipeline is fit onx, raw steps apply to each dataset in turn, andPipeline.is_fittedisTruefor a pipeline assembled from already-fitted steps.hyp.loadpasses already-loaded data through: a DataFrame, a numpy array, or a list/tuple of those comes back unchanged (with the samereduce/ndims/align/normalizepost-processing loaded data gets), so one call can be the entry point over mixed names and in-memory data.hyp.io.lsl_stream()returns anLSLStreamthat destroys its inlet onclose(), on leaving awithblock, on garbage collection and at interpreter exit.
Data and IO
- Video exports (
save_path=andHyperAnimation.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; passwriter=toHyperAnimation.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 apredict='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 updateddocs/pipeline_order.rst, described above. docs/animation.rstdocumentsmeta['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')andhyp.load('nips')return a flat list of strings (one document per entry), likehyp.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
ClassLabelcolumns to their string names, matching a streaming load. Passdecode_labels=Falseto keep the integers. - The Hugging Face text and dataset paths set
HF_HUB_DISABLE_PROGRESS_BARS,HF_HUB_VERBOSITYandTOKENIZERS_PARALLELISM(viasetdefault, 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 theplot()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.predictrejects a hierarchical frame in a list on either axis. This is deliberately asymmetric: a row-hierarchical frame inside a list passed tohyp.plotkeeps its documented warn-and-flatten behavior. Fix: pass the frame bare; hierarchy expansion is defined for a bare frame only. hyp.predictrejects 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 withgroup_columns(df, feature_correspondence='position')(from hypertools.core.hierarchy import group_columns) and forecast the leaves.hyp.predictrejects a time-like index with duplicate entries, including on flat input. A plainDataFrame/Serieson aDatetimeIndex,TimedeltaIndexorPeriodIndexwith repeated stamps now raisesValueError: 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 aMultiIndexframe no longer raises blanketly. It previously raisedValueError: 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,Noneandpd.NAnormalize 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=andforecast_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 withanimate=. An animated plot owns its own figure, so the axes passed in were left empty. Fix: dropax=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 tofigand returns it (the caller's layout untouched;animate=is refused with it). A matplotlib Axes under plotly raisesValueErrorbefore any analysis runs; before 1.1 it was silently ignored and the axes left empty. A plotly Figure passed to a matplotlib draw is aTypeError.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 is0.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 byforecast_palette=/forecast_hue=/forecast_cluster=keeps its trace's alpha. This is a visible change to existing forecast figures: every forecast used to be drawnlinestyle='--'at a hard-codedalpha=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 fixed0.08. Code that located forecast artists by their dashed linestyle must switch to the role tags (artist._hyp_forecast_roleon matplotlib,trace.meta['hyp_forecast_role']on plotly).- Animated continuous-hue line plots with no explicit
linewidth=render at1.0instead of1.5, matching animated no-hue lines. This is a visible change to existing animated hue figures. Fix: passlinewidth=1.5to keep the old look. hyp.describereturns'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, ashyp.plot(show=False)does; it used to returnfig=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 andanimate='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=withoutforecast_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. Passforecast_palette=to choose the colors.- Matplotlib
axis_scale='data'axes no longer draw gridlines, matching plotly. A grid on your ownax=is kept. - Model comparisons rank complete models only.
hyp.impute(x, model=[...], truth=)andhyp.predict(x, model=[...], holdout=)used to name the lowest-scoring modelbesteven when it had left values unscored, so a model scored on fewer, easier values could win. A model with anyunscoredvalue keeps its row but is excluded from the verdict and listed in the newscores.attrs['incomplete']. When no model is complete,attrs['best']andattrs['beats_baseline']areNone;beats_baselineis alsoNonewhen 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 raiseValueError. Bare arrays are still compared by position.hyp.describesweeps 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 non_components, andreduce=NoneraiseValueError.- 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, andbackend='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
gensimanddensity3dextras 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. Delaycolumn collisions.Delayrejects 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=onHyperAnimation.save()still takes a specific bitrate. - The "Animation was deleted without rendering anything" warning could still fire from a discarded
HyperAnimationwhen 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 exceptfps=, soanim.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 raisesTypeErrornaming it.- Closing an animated figure under matplotlib's notebook backend raised
AttributeError: 'NoneType' object has no attribute 'remove_callback'.nbAggprocesses a figure's close event twice, so on Colab every displayed animation made the next static-plot cell fail. Animations are now aFuncAnimationsubclass (hypertools.plot.animate.HyperFuncAnimation) whose_stopignores the repeat call. - A plotly
save_path=to a raster/PDF format on a machine without Chrome failed with kaleido's bareRuntimeError. 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 runapt-get. When that fails, orhyp.set_autoinstall(False)is in force, the failure is aHypertoolsIOErrorstating 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 withbackend='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, wherebackend='auto'resolves to plotly, 29 of the feature tour's plot cells were blank) because the backend never calledfig.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 IPythonpost_executecallback that runs after matplotlib-inline's flush and skips any figure the rich-display hook already showed. The returned figure is still aplotly.graph_objects.Figuresubclass. 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%matplotlibmagic imports the toolkit itself, so'TkAgg'without_tkinteror'GTK3Agg'withoutgiescaped as a rawModuleNotFoundError(a missing display asTclError), while a plain script already raisedHypertoolsBackendError. The notebook path now raisesHypertoolsBackendErrortoo, with the toolkit's error chained as the cause. - Drawing into a caller-supplied
ax=warnedGlyph 8722 (MINUS SIGN) missing from font(s) Noto Sansfor 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 thesans-serifalias, 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_linesnever read alpha from its per-trace kwargs. - The
return_model=Truepipeline 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 atalpha=0.7rendered fully opaque on plotly. Line colors now carry the alpha; marker colors deliberately do not, matching matplotlib. - With
ndims=1, matplotlib drew thepredict=overlay at x = 0..t instead of continuing the observed series (forecast x0..3against an observed line at0..59). Both backends now draw the continuation. - A marker-only
fmt(e.g.'o') withhue=andpredict=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=[...], andlegend=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 raisesValueErrornaming both counts), andlegend=Falsesuppresses 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 instructiveValueErrorpointing atlegend=[...].- matplotlib's
'_nolegend_'sentinel leaked into plotly trace names, rendered in hover labels and written into exported HTML. Any leading-underscore label now becomesname=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)versusrgb(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, andlegend=('A', 'B'), colorbar=Truedrew a colorbar reading1,2. Every accepted container is now normalized to a list; a 0-d array counts as one label, aslegend='a'does.- A caller-supplied
pipeline=came back from a hierarchical plot unable to re-apply. Only the pipelineplot()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
chemtrailstrail and its head), frame 0 of aprecogtrail 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_raterounded below 1,animate='serial'andanimate='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 rawframe_rate * durationproduct rather than the drawn frame count (atframe_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-
MultiIndexDataFramewithchemtrails/precog/bullettimeraisedTypeError: ... got multiple values for keyword argument 'alpha'. The 0.3 trail fade is now folded into whateveralphathe trace already carries. cluster=/hue=line plots spanning more than one run per dataset no longer crash, or silently misplace, pointlabels=. Bridging two same-dataset runs duplicated a data point without a matchinglabels=entry, crashingannotate_plotwithIndexError: list index out of rangeor 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-stringtitle=raisesTypeErrorinstead of drawing the literal"['a', 'b', 'c']", and the check runs before the analyze pipeline soplot_streamgets it too.linewidth=is honored in animated continuous-hue line plots. The overlay previously fell back torcParams['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 namingmorph_samples=andsimplify=Truewhen you passsimplify=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-segmenttitle=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-segmenttitle=schedule) one extra time before the animation has started. The measurement draws are now guarded the same way matplotlib's ownAnimation.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-segmenttitle=andFrameContext.current_indexpreviously indexed by position within the morph sequence, shifting every title and index down by one when a leading dataset was untagged. ctx.datasetsforanimate='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-datasetalpha=. They previously hardcoded a flat 0.3 opacity; a per-datasetalpha=list now fades plotly trails as matplotlib does (0.3 * alpha). animate='spin'/'window',order='serial', and a per-datasettitle=list together now raise immediately with an accurate message. Previously the combination ran the whole pipeline, warned thatorder='serial'was ignored, and then raisedTypeErroradvisingorder='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 readhue 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 byanimate='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 withset_alphaafter 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 scalaralpha=is constant throughout. Plots that pass noalpha=are unchanged.- An LSL stream left open logged a liblsl error at exit.
hyp.io.lsl_stream()onlyclose_stream()ed itspylsl.StreamInlet, and only when the generator was closed, so a notebook or script that just moved on sawERR| Stream transmission broke offat teardown. The stream is now anLSLStreamthat destroys the inlet onclose(), on leaving awithblock, on garbage collection, on the silent-source abort, and at interpreter exit. - PPCA imputation warned
divide by zero encountered in logon data with a few dozen or more features (hyp.impute(model='PPCA')and the NaN fillhyp.plotapplies at format time). The EM objective computedlog(det(Sx)), which underflowed tolog(0)and then fell back to a sign-flipped value; it now usesslogdet, identical to rounding wherever the old value was representable. labels=annotations were drawn on pyplot's current axes, not theax=passed in, so panel labels stacked on one axes; and a color-list colorbar (color=['red', ...]) raisedIndexError.hyp.plot(docs, vectorizer='all-MiniLM-L6-v2')crashed with the defaultsemantic=/corpus=. Pretrained embedding vectorizers now resolve the default semantic stage to none silently, never load or embed a corpus, and an explicitsemantic='NMF'raises a clear hypertoolsValueErrorbefore 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 raisesHypertoolsOfflineErrornaming the file. - Installer.
hyp.set_autoinstall(False)also reaches the worker process that renders plotly animation exports, and that export raisesImportErrororHypertoolsIOErrorrather than a wrappedRuntimeError. Overlappingwith 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 thereturn_model=Truebundle. 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 inforecast_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 raisesValueError, NaN in a continuoushue=no longer affects the color range, a caller'sax=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=Truehover labels open toward the center of the axes, so a label near the edge of the window is not cut off. - Pipelines.
sklearn.base.cloneworks on ahyp.Pipelineand 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 repeatedmetrics=entry,holdout=Truewitht=0,hyp.load(..., streaming=True)on a non-Hugging-Face source and degeneratealignment_scoreinput each raise aValueErrorsaying what is wrong; synthetic datasets accept more seed types;hyp.text_windowsaccepts NumPy integers;text2matreads 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; ahyp.predictcaller groups withgroup_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'ongroup_columnsis the opt-in, and it is not a positional hierarchy mode: passing its arrays tohyp.plotgives a plain list of datasets (no per-level means, no hierarchy styling,trace_metadataisNone). There is no publicplot(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 defaultIncrementalPCAand underTSNE, whilePCA,TruncatedSVD,FactorAnalysis,IsomapandSpectralEmbeddingpreserved it up to numerical and sign equivalence.hyp.plot([A, B, C])andhyp.plot([C, B, A])differ the same way, and did before 1.1. Passreduce='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 continuoushue=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.
Links
- Documentation: https://hypertools.readthedocs.io/en/stable/
- Full changelog on the tag: https://github.com/ContextLab/hypertools/blob/v1.1.0/CHANGELOG.md
- PyPI: https://pypi.org/project/hypertools/1.1.0/
- Launch tutorials:
- A quarter century of the market: https://hypertools.readthedocs.io/en/stable/tutorials/market_sectors.html
- A century of weather: https://hypertools.readthedocs.io/en/stable/tutorials/weather_decades.html
- Five paintings, described in words: https://hypertools.readthedocs.io/en/stable/tutorials/painting_embeddings.html
- The shape of a conversation: https://hypertools.readthedocs.io/en/stable/tutorials/conversation_shape.html
- Morphing through the shapes zoo: https://hypertools.readthedocs.io/en/stable/tutorials/morph_shapes_zoo.html
Upgrading from 1.0.0
- Lines are drawn smoothed by default. Pass
antialias=Falseto 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 atalpha=0.6(a forecast recolored byforecast_hue=,forecast_cluster=orforecast_palette=keeps the full alpha). Useforecast_fmt=,forecast_hue=orforecast_palette=to restyle, and find forecast artists throughartist._hyp_forecast_role/trace.meta['hyp_forecast_role']rather than their linestyle.- Animated continuous-hue lines default to
linewidth=1.0instead of1.5. Passlinewidth=1.5to keep the old look. hyp.predicton aDatetimeIndex,TimedeltaIndexorPeriodIndexwith duplicate stamps raises. Aggregate the repeats (df.groupby(level=-1).mean()) or give them distinct times.ax=together withanimate=raises. Dropax=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 earlierhyp.plotreturned, or draw that call withbackend='matplotlib'.- A frame with a
MultiIndexon both axes raises. Keep one hierarchy. - A column-hierarchical frame inside a list raises in
hyp.plot; a hierarchical frame inside a list raises inhyp.predicton either axis. Pass the frame bare. hyp.predicton a column hierarchy whose groups name different features raises. Rename the innermost level so every group carries the same labels, or usegroup_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=andforecast_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 thanNone.- Code that reads
scores.attrs['best']or['beats_baseline']fromhyp.impute/hyp.predictmust allowNone, and can readattrs['incomplete']for the models left out of the ranking. - A
truth=ormask=DataFrame forhyp.imputeis matched to the data by label. Pass.to_numpy()to compare by position. hyp.describeno longer accepts an already fitted reducer orreduce=None. Pass a name, a{'model': ..., 'kwargs': ...}spec or an unfitted instance.- In an animation,
truth=points appear as the forecast reaches them, and defaultforecast_hue=/forecast_cluster=colors no longer repeat the observed lines' colors. Passforecast_palette=to fix the colors. HyperAnimation.save()now raisesTypeErroron keywords other thanfps=anddpi=(it used to drop them silently). Passwriter=to delegate every keyword to matplotlib.- A
title=that is neither a string nor a callable raisesTypeError, and a list of strings is accepted only for a serial-style animation (one per dataset). Pass one string, a callablectx -> str, or that list. animate='morph'over more than 2000 points is downsampled to 2000 unless you setmorph_samples=; passsimplify=Falseto 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
ClassLabelcolumns to their string names. Passdecode_labels=Falseto keep the integer codes.