Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/distribution/beta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,30 @@ impl Distribution<f64> for Beta {
}
}

impl Median<f64> for Beta {
/// Returns the median of the beta distribution.
///
/// # Formula
///
/// ```text
/// I^-1(0.5; α, β)
/// ```
///
/// the inverse of the regularized incomplete beta function at `0.5`.
///
/// # Remarks
///
/// The beta median has no closed form except in special cases, so this is
/// computed by inverting the cdf -- here via
/// [`inv_beta_reg`](crate::function::beta::inv_beta_reg), a root-find rather
/// than the `O(1)` arithmetic that the closed-form `median` impls elsewhere
/// in the crate use. Accurate to a few ulp; see the tests, which check
/// `cdf(median()) == 0.5`.
fn median(&self) -> f64 {
self.inverse_cdf(0.5)
}
}

impl Mode<Option<f64>> for Beta {
/// Returns the mode of the Beta distribution. Returns `None` if `α <= 1`
/// or `β <= 1`.
Expand Down
48 changes: 48 additions & 0 deletions src/distribution/categorical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,31 @@ impl Median<f64> for Categorical {
}
}

impl Mode<Option<u64>> for Categorical {
/// Returns the mode of the categorical distribution, the index of the
/// largest probability.
///
/// # Remarks
///
/// Always `Some`, since a categorical distribution is constructed from at
/// least one non-zero weight. The `Option` is for consistency with the other
/// discrete distributions.
///
/// The mode of a categorical distribution is not unique when two categories
/// tie for the largest probability, as they do for a fair die. This returns
/// the *lowest* such index, which is the only choice that does not depend on
/// iteration order.
fn mode(&self) -> Option<u64> {
let mut best = 0;
for (i, &p) in self.norm_pmf.iter().enumerate().skip(1) {
if p > self.norm_pmf[best] {
best = i;
}
}
Some(best as u64)
}
}

impl Discrete<u64, f64> for Categorical {
/// Calculates the probability mass function for the categorical
/// distribution at `x`
Expand Down Expand Up @@ -394,6 +419,29 @@ mod tests {
test_exact(&[4.0, 2.5, 2.5, 1.0], 1.0, median);
}

#[test]
fn test_mode() {
let mode = |x: Categorical| x.mode();
test_exact(&[1.0, 2.0, 3.0], Some(2), mode);
test_exact(&[0.0, 3.0, 1.0, 1.0], Some(1), mode);
test_exact(&[4.0, 2.5, 2.5, 1.0], Some(0), mode);
// Weights need not be normalized, and the largest may sit anywhere.
test_exact(&[1.0, 9.0, 1.0, 1.0], Some(1), mode);
}

/// A tie has no unique mode; the documented convention is the lowest index.
#[test]
fn test_mode_breaks_ties_by_lowest_index() {
let mode = |x: Categorical| x.mode();
test_exact(&[1.0, 1.0], Some(0), mode);
test_exact(&[1.0; 6], Some(0), mode);
test_exact(&[0.0, 5.0, 5.0, 1.0], Some(1), mode);
// The tie is genuine: the two candidates really do have equal mass.
let d = create_ok(&[0.0, 5.0, 5.0, 1.0]);
assert_eq!(d.pmf(1), d.pmf(2));
assert!(d.pmf(1) > d.pmf(3));
}

#[test]
fn test_min_max() {
let min = |x: Categorical| x.min();
Expand Down
21 changes: 21 additions & 0 deletions src/distribution/chi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,27 @@ impl Distribution<f64> for Chi {
}
}

impl Median<f64> for Chi {
/// Returns the median of the chi distribution.
///
/// # Formula
///
/// ```text
/// CDF^-1(0.5)
/// ```
///
/// # Remarks
///
/// No closed form exists, so this inverts the cdf by numerical search and
/// therefore costs many cdf evaluations rather than `O(1)`. Note this is
/// more accurate than the approximation
/// [`ChiSquared::median`](crate::distribution::ChiSquared::median) uses,
/// despite the two being related by a square root.
fn median(&self) -> f64 {
self.inverse_cdf(0.5)
}
}

impl Mode<Option<f64>> for Chi {
/// Returns the mode for the chi distribution
///
Expand Down
Loading