LogisticDistribution.java
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.apache.commons.statistics.distribution;
- /**
- * Implementation of the logistic distribution.
- *
- * <p>The probability density function of \( X \) is:
- *
- * <p>\[ f(x; \mu, s) = \frac{e^{-(x-\mu)/s}} {s\left(1+e^{-(x-\mu)/s}\right)^2} \]
- *
- * <p>for \( \mu \) the location,
- * \( s > 0 \) the scale, and
- * \( x \in (-\infty, \infty) \).
- *
- * @see <a href="https://en.wikipedia.org/wiki/Logistic_distribution">Logistic distribution (Wikipedia)</a>
- * @see <a href="https://mathworld.wolfram.com/LogisticDistribution.html">Logistic distribution (MathWorld)</a>
- */
- public final class LogisticDistribution extends AbstractContinuousDistribution {
- /** Support lower bound. */
- private static final double SUPPORT_LO = Double.NEGATIVE_INFINITY;
- /** Support upper bound. */
- private static final double SUPPORT_HI = Double.POSITIVE_INFINITY;
- /** π<sup>2</sup>/3. https://oeis.org/A195055. */
- private static final double PI_SQUARED_OVER_THREE = 3.289868133696452872944830;
- /** Location parameter. */
- private final double mu;
- /** Scale parameter. */
- private final double scale;
- /** Logarithm of "scale". */
- private final double logScale;
- /**
- * @param mu Location parameter.
- * @param scale Scale parameter (must be positive).
- */
- private LogisticDistribution(double mu,
- double scale) {
- this.mu = mu;
- this.scale = scale;
- this.logScale = Math.log(scale);
- }
- /**
- * Creates a logistic distribution.
- *
- * @param mu Location parameter.
- * @param scale Scale parameter (must be positive).
- * @return the distribution
- * @throws IllegalArgumentException if {@code scale <= 0}.
- */
- public static LogisticDistribution of(double mu,
- double scale) {
- if (scale <= 0) {
- throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE,
- scale);
- }
- return new LogisticDistribution(mu, scale);
- }
- /**
- * Gets the location parameter of this distribution.
- *
- * @return the location parameter.
- */
- public double getLocation() {
- return mu;
- }
- /**
- * Gets the scale parameter of this distribution.
- *
- * @return the scale parameter.
- */
- public double getScale() {
- return scale;
- }
- /** {@inheritDoc} */
- @Override
- public double density(double x) {
- if (x <= SUPPORT_LO ||
- x >= SUPPORT_HI) {
- return 0;
- }
- // Ensure symmetry around location by using the absolute.
- // This also ensures exp(z) is between 1 and 0 and avoids
- // overflow for large negative values of (x - mu).
- // Exploits the reciprocal relation: exp(-x) == 1 / exp(x)
- // exp(-z) 1 exp(z) exp(z)
- // --------------- = -------------------------- * ------ = --------------
- // (1 + exp(-z))^2 exp(z) (1 + 1 / exp(z))^2 exp(z) (1 + exp(z))^2
- final double z = -Math.abs(x - mu) / scale;
- final double v = Math.exp(z);
- return v / ((1 + v) * (1 + v)) / scale;
- }
- /** {@inheritDoc} */
- @Override
- public double logDensity(double x) {
- if (x <= SUPPORT_LO ||
- x >= SUPPORT_HI) {
- return Double.NEGATIVE_INFINITY;
- }
- // Ensure symmetry around location by using the absolute
- final double z = -Math.abs(x - mu) / scale;
- final double v = Math.exp(z);
- return z - 2 * Math.log1p(v) - logScale;
- }
- /** {@inheritDoc} */
- @Override
- public double cumulativeProbability(double x) {
- final double z = (x - mu) / scale;
- return 1 / (1 + Math.exp(-z));
- }
- /** {@inheritDoc} */
- @Override
- public double survivalProbability(double x) {
- final double z = (x - mu) / scale;
- return 1 / (1 + Math.exp(z));
- }
- /** {@inheritDoc} */
- @Override
- public double inverseCumulativeProbability(double p) {
- ArgumentUtils.checkProbability(p);
- if (p == 0) {
- return SUPPORT_LO;
- } else if (p == 1) {
- return SUPPORT_HI;
- } else {
- return scale * Math.log(p / (1 - p)) + mu;
- }
- }
- /** {@inheritDoc} */
- @Override
- public double inverseSurvivalProbability(double p) {
- ArgumentUtils.checkProbability(p);
- if (p == 1) {
- return SUPPORT_LO;
- } else if (p == 0) {
- return SUPPORT_HI;
- } else {
- return scale * -Math.log(p / (1 - p)) + mu;
- }
- }
- /**
- * {@inheritDoc}
- *
- * <p>The mean is equal to the {@linkplain #getLocation() location}.
- */
- @Override
- public double getMean() {
- return getLocation();
- }
- /**
- * {@inheritDoc}
- *
- * <p>For scale parameter \( s \), the variance is:
- *
- * <p>\[ \frac{s^2 \pi^2}{3} \]
- */
- @Override
- public double getVariance() {
- return scale * scale * PI_SQUARED_OVER_THREE;
- }
- /**
- * {@inheritDoc}
- *
- * <p>The lower bound of the support is always negative infinity.
- *
- * @return {@linkplain Double#NEGATIVE_INFINITY negative infinity}.
- */
- @Override
- public double getSupportLowerBound() {
- return SUPPORT_LO;
- }
- /**
- * {@inheritDoc}
- *
- * <p>The upper bound of the support is always positive infinity.
- *
- * @return {@linkplain Double#POSITIVE_INFINITY positive infinity}.
- */
- @Override
- public double getSupportUpperBound() {
- return SUPPORT_HI;
- }
- /** {@inheritDoc} */
- @Override
- double getMedian() {
- // Overridden for the probability(double, double) method.
- // This is intentionally not a public method.
- return mu;
- }
- }