Skip to content

Density curve grid starts exactly at the sample minimum; round the endpoints instead #100

Description

@hmgaudecker

Summary

The density curves produced by univariate_densities start exactly at the sample
minimum, so the curve's own x-coordinates carry an individual observation. The grid
endpoints should be rounded (or otherwise detached from the data) rather than taken from
the data's min and max.

Observation

With plotly 6.9.0 and the configuration show_hist=False, show_rug=False,
show_curve=True, curve_type="kde", the first grid point of the curve is bit-for-bit
equal to the minimum of the input data:

seed=0  curve_min==data_min: True  curve_min=-3.8994217301  data_min=-3.8994217301
        curve_max=3.052106  data_max=3.066037  n_grid=500
seed=1  curve_min==data_min: True  curve_min=-2.7112854374  data_min=-2.7112854374
        curve_max=3.088420  data_max=3.100042  n_grid=500
seed=2  curve_min==data_min: True  curve_min=-2.6855952897  data_min=-2.6855952897
        curve_max=3.044859  data_max=3.056342  n_grid=500

The maximum is not hit exactly because the grid steps past it, but it is bounded to
within one step, so it is effectively disclosed too.

Measured on 500 standard normal draws per seed; the relationship follows from how the
grid is constructed, not from these particular samples.

Why it matters

Under statistical disclosure rules, a density estimate is usually fine but an extreme
value is not. Statistics Netherlands, for example, refuses to release maxima and minima
outright, on the grounds that they reveal that no higher or lower value occurs in the
group and typically identify one specific unit.

The density heights are not the problem: with a Gaussian kernel every plotted point is
a function of all n observations, so any "minimum number of observations" rule is
satisfied by construction. The leak is entirely in the x-axis, which is easy to miss
precisely because the curve looks like an aggregate.

This bites anyone exporting the plot data out of a secure environment, and it also means
the rendered figure itself reveals the sample extremes to anyone who reads the axis.

Suggested fix

Choose grid endpoints that cannot coincide with an observation. Any of:

  • round the data range outward to a sensible precision before building the grid, e.g.
    floor(min / step) * step and ceil(max / step) * step;
  • pad the range by a multiple of the kernel bandwidth, which is also the more standard
    choice for a KDE since it lets the tails decay rather than truncating them mid-slope;
  • derive the endpoints from quantiles rather than extremes.

Padding by bandwidth is probably the best default on statistical grounds alone: starting
a kernel density estimate exactly at the smallest observation truncates the curve where
it still has mass, so the plotted density is misleading at both ends regardless of any
disclosure consideration.

An option to set the range explicitly would cover the cases where a caller needs
something specific.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions