feat(analyses): compute analyses in transformed scale space#773
Merged
Conversation
Setting a scale function on a positional aesthetic via `scales` (e.g. `scales(Y = (; scale = log10))`) now makes analyses fit in the transformed space and back-transform their output, so a fit on log-scaled data is computed in log space and lines up with the display. This is distinct from `axis = (; yscale = ...)`, which stays display-only and leaves the fit in data space. Covers linear, smooth, density, histogram, expectation and filled_contours. The transform sits inside the existing unit-stripping bracket, so units compose. Data outside the scale's domain (e.g. log of zero) errors early, naming the analysis and the offending value. Because the transform now has to happen up front, the positional aesthetics an analysis assumes are a hard requirement: a guard catches a downstream `visual` that flips data onto or off a scaled aesthetic the analysis did not fit for. `histogram` gains a `direction` keyword for 1D histograms, selecting the aesthetic (and thus the scale space) the bins are computed in.
Replace the vague `all(> 0)` positivity check with a concrete assertion that the fit equals the exponential the data was generated from.
Key the transform dict by aesthetic type with a `Base.Callable` forward value instead of a `(; forward, inverse, sym)` tuple. The inverse comes from `Makie.inverse_transform` at use, and the aesthetic for a domain error is recovered from the dict key.
`positional_transform` now returns the matching `aes => forward` dict entry, so the aesthetic travels to the domain error with the forward instead of being reverse-looked-up (which was ambiguous when the same scale function is set on more than one aesthetic, e.g. a log/log plot).
Match the axis_transforms key type; the values are aesthetic types.
Move the scale-vs-axis explanation and the linear-fit-in-log-y example from a standalone section in continuous-scales.md into the existing `scales` X/Y/Z options section in draw.md, where `ticks`/`tickformat`/`scale` are documented.
Recipes like visual(Density)/visual(Hist) compute their statistic on the raw data inside the recipe, so the scale does not reach them; use the AlgebraOfGraphics analysis to compute in the scaled space.
Merged
jkrumbiegel
added a commit
that referenced
this pull request
Jun 25, 2026
Minor release. Highlights: - Analyses (`linear`, `smooth`, `density`, `histogram`, `expectation`, `filled_contours`) now fit in transformed space and back-transform when the aesthetic carries a `scale` function via `scales` (e.g. `scales(Y = (; scale = log10))`) [#773](#773). - `histogram` now accepts a `direction` (`:x` or `:y`) keyword for 1D histograms [#773](#773). - `smooth` with the default `degree = 2` no longer collapses on `Date` (and other large-magnitude) x axes [#774](#774). The transformed-space analyses feature warrants the minor (`0.12.13` → `0.13.0`) bump.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
People often plot data on a log y axis and want a
linearorsmoothfit on top, but if you treat the data as gaussian in log space the fit has to be computed in log space too. Settingaxis = (; yscale = log10)doesn't do that, it's a Makie axis attribute that warps the coordinates at draw time, after the analysis has already run in data space, so the fit is computed on the raw values and bends away from the data on the log axis.This PR makes the
scaleset viascalesactually affect analyses.scales(Y = (; scale = log10))fits in the transformed space and back-transforms the result, so the fit lines up with the log-scaled display. It's a first-class scale, distinct from the display-only axis attribute.That's the same split ggplot2 makes between
scale_y_log10()(transforms before the stat) andcoord_trans(y = "log10")(display only), so this is well-established practice. Quick check that AoG withscales(Y = (; scale = log10))matches ggplot2'sscale_y_log10()on identical data (left AoG, right ggplot2;lmtop, loess bottom):Covers
linear,smooth,density,histogram,expectationandfilled_contours. The transform sits inside the existing unit-stripping bracket so units still compose, and data outside the scale's domain (logof zero or a negative) errors early, naming the analysis and the offending value.One consequence worth calling out: the positional aesthetics an analysis maps to (which arg is x, which is y) were already mostly assumed fixed in the existing code. This just turns that into a hard requirement, because the transform has to be chosen up front based on which aesthetic each positional lands on. So if a downstream
visualchanges the plot type or orientation such that data moves onto or off a scaled aesthetic the analysis didn't fit for, that's now an error instead of a silent mismatch.This only kicks in when such a scale is actually set. Without one (or with
identity), no pretransform happens, so there's no possible mismatch and nothing is checked: plots that worked before this PR behave exactly as before.histogramalso gains adirectionkeyword for 1D histograms, which sets the bar orientation and picks the aesthetic (so the scale space) the bins are computed in. Previously you'd flip orientation with a downstreamvisual(direction = :x), which can't carry the binning into the right scale space, so it lives on the analysis instead, likedensity'sdirection.