Skip to content

BinRule

Mojo struct 🡭

BinRule

@memory_only
struct BinRule

Let a rule choose how many bins a sample gets, instead of naming a count. Pass one to bin_edges(), shared_bin_edges(), or histogram()’s bins.

A fixed count is a guess that is wrong in both directions: ten bins oversmooth a five-thousand-point sample and turn a twelve-point one into a comb. Every rule below reads the sample and answers for you.

With n the sample size, ptp the sample’s max minus its min, sigma its standard deviation and IQR its interquartile range, each rule states a bin width, which is then turned into a count:

Rule Width Reads
SQRT ptp / sqrt(n) size only
STURGES ptp / (log2(n) + 1) size only
RICE ptp / (2 * cbrt(n)) size only
SCOTT cbrt(24 * sqrt(pi) / n) * sigma spread
FREEDMAN_DIACONIS 2 * IQR / cbrt(n) spread, robustly
AUTO min(max(fd, sqrt / 2), sturges) both

The definitions and the width-to-count step are numpy.histogram’s, so bin_edges(data, BinRule.SCOTT) and numpy.histogram_bin_edges(data, bins="scott") give the same number of bins over the same range. Matching an implementation rather than a paper is deliberate: the papers state widths up to a constant and say nothing about rounding a width back into a whole number of bins, which is where two faithful readings of the same rule diverge.

Widths rather than counts throughout, again following numpy. The three size-only rules are usually quoted as counts – ceil(sqrt(n)) bins, ceil(log2(n)) + 1 bins – and for those the two forms agree. They stop agreeing for the two spread-based rules, which have no count form at all, and a single representation means AUTO can compare Freedman-Diaconis against Sturges without converting first.

Which to reach for:

  • AUTO unless you have a reason. It is Freedman-Diaconis with two guards, and it is what numpy’s own bins="auto" does.
  • SQRT is what most spreadsheets do. Cheap and unopinionated.
  • STURGES assumes the data are roughly normal and undercounts badly past a few thousand points; RICE grows faster and is the usual stand-in.
  • SCOTT is optimal for normal data but follows the standard deviation, so one far outlier widens every bin.
  • FREEDMAN_DIACONIS uses the IQR instead, which outliers barely move – at the cost of collapsing to a single bin when more than half the sample is identical and the IQR is 0.

Aliases

  • SQRT = BinRule(Int(0)): ptp / sqrt(n) – ceil(sqrt(n)) bins. Spreadsheet default.
  • STURGES = BinRule(Int(1)): ptp / (log2(n) + 1) – assumes normality; undercounts past a few thousand points.
  • RICE = BinRule(Int(2)): ptp / (2 * cbrt(n)) – no normality assumption; grows faster than Sturges.
  • SCOTT = BinRule(Int(3)): cbrt(24 * sqrt(pi) / n) * sigma – optimal for normal data, but one outlier widens every bin.
  • FREEDMAN_DIACONIS = BinRule(Int(4)): 2 * IQR / cbrt(n) – robust to outliers; collapses to one bin when the IQR is 0.
  • AUTO = BinRule(Int(5)): min(max(fd, sqrt / 2), sturges) – the default choice.

Implemented traits

AnyType, Copyable, Deinitable, ImplicitlyCopyable, Movable

Methods

__init__

fn def __init__(out self, value: Int)

Prefer the SQRT/STURGES/RICE/SCOTT/ FREEDMAN_DIACONIS/AUTO comptime constants over constructing one directly.

Args:

  • value (Int): 0 for SQRT, 1 for STURGES, 2 for RICE, 3 for SCOTT, 4 for FREEDMAN_DIACONIS, 5 for AUTO.
  • self (Self)

Returns:

Self

__eq__

fn def __eq__(self, other: Self) -> Bool

Whether both name the same rule.

Args:

  • self (Self)
  • other (Self): The rule to compare against.

Returns:

Bool: True when they match.

__ne__

fn def __ne__(self, other: Self) -> Bool

Whether the two name different rules.

Args:

  • self (Self)
  • other (Self): The rule to compare against.

Returns:

Bool: True when they differ.

name

fn def name(self) -> String

This rule’s constant name, for the error messages that have to say which rule produced an unusable bin count.

Args:

  • self (Self)

Returns:

String: “SQRT”, “STURGES”, “RICE”, “SCOTT”, “FREEDMAN_DIACONIS”, “AUTO”, or “BinRule()” for a value outside the six constants.