Repository navigation
FIX: Normalise stacked histograms by the total of their stack - #174
Merged
Merged
Conversation
Normalise the members of a stacked column of `hists` by the pooled total of the column, so that the top of the stack is the normalised histogram of the pooled sample and each layer is its member's share of it, as Matplotlib's `hist(stacked=True, density=True)` and seaborn's default `common_norm=True` do. Members in columns of their own are still normalised by their own totals. Add `HistsMut::common_norm(bool)`, which normalises every member by the pooled total of all the members, or each by its own, whatever the layout. Record in each member's `Binning.total` the total its values were normalised by, so that a datatip recovers the member's own count.
thclark
added a commit
that referenced
this pull request
Oct 26, 2026
Normalise the members of a stacked column of `hists` by the pooled total of the column, so that the top of the stack is the normalised histogram of the pooled sample and each layer is its member's share of it, as Matplotlib's `hist(stacked=True, density=True)` and seaborn's default `common_norm=True` do. Members in columns of their own are still normalised by their own totals. Add `HistsMut::common_norm(bool)`, which normalises every member by the pooled total of all the members, or each by its own, whatever the layout. Record in each member's `Binning.total` the total its values were normalised by, so that a datatip recovers the member's own count.
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.
Summary
histsnormalised every member by its own total. For a stacked column this was wrong: each layer integrated to 1, so a stack of m density histograms enclosed an area of m, and its outline was not the density of anything.The members of a stacked column are now normalised by the pooled total of that column. This is what Matplotlib's
hist(stacked=True, density=True)does, and what seaborn'shistplot(multiple="stack")does with its defaultcommon_norm=True. The top of the stack is therefore the normalised histogram of the pooled sample, and each layer is its member's share of it:Members that stand side by side, each in a column of its own, are still normalised by their own totals, because they are placed side by side to compare their shapes.
The new setter
HistsMut::common_norm(bool)overrides either rule, as seaborn's argument of that name does.truenormalises every member by the pooled total of all the members, andfalsenormalises each member by its own total. The result is the same whether it is called before or after the members are added.Each member's
Binning.totalnow holds the total its values were normalised by, which for a member of a stack is the pooled total of the stack. A datatip therefore still recovers the member's own count, and reports as its density the member's contribution to the density of the stack. Theexcluded_non_finiteandexcluded_out_of_rangecounts remain those of the member's own sample.Tests
hists_normalize_and_range_apply_to_every_member_whenever_they_are_setnow requires a stack, rather than each layer, to enclose unit area, and the records of a stack to hold the pooled total.the_top_of_a_stack_is_the_normalised_histogram_of_the_pooled_samplechecks every normalisation on bins of unequal width.common_norm_overrides_the_layout_whenever_it_is_setchecks both layouts with the setter unset,trueandfalse, called before and after the members are added.the_members_of_a_stack_record_the_pooled_total_of_their_stack_and_their_own_exclusionschecks that a stack pools only its own column and that exclusions stay per member.the_datatip_of_a_stacked_member_reports_its_own_count_and_its_share_of_the_densitychecks the datatip under density, probability and CDF normalisation.Documentation
The getting-started guide has a new subsection, "Normalising stacked and side-by-side histograms", with examples. The equivalent-functions reference maps Matplotlib's and seaborn's stacked normalisation and
common_normtohists. The Binning section of the figure schema states thattotalis the total the values were normalised by.Gallery
The rendered gallery is unchanged. The only difference is in
histogram.fig, whose stacked count histograms now record the pooled total of 800 samples instead of 400 each; their drawn values are the same.