View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.statistics.distribution;
18  
19  /**
20   * Implementation of the logistic distribution.
21   *
22   * <p>The probability density function of \( X \) is:
23   *
24   * <p>\[ f(x; \mu, s) = \frac{e^{-(x-\mu)/s}} {s\left(1+e^{-(x-\mu)/s}\right)^2} \]
25   *
26   * <p>for \( \mu \) the location,
27   * \( s &gt; 0 \) the scale, and
28   * \( x \in (-\infty, \infty) \).
29   *
30   * @see <a href="https://en.wikipedia.org/wiki/Logistic_distribution">Logistic distribution (Wikipedia)</a>
31   * @see <a href="https://mathworld.wolfram.com/LogisticDistribution.html">Logistic distribution (MathWorld)</a>
32   */
33  public final class LogisticDistribution extends AbstractContinuousDistribution {
34      /** Support lower bound. */
35      private static final double SUPPORT_LO = Double.NEGATIVE_INFINITY;
36      /** Support upper bound. */
37      private static final double SUPPORT_HI = Double.POSITIVE_INFINITY;
38      /** &pi;<sup>2</sup>/3. https://oeis.org/A195055. */
39      private static final double PI_SQUARED_OVER_THREE = 3.289868133696452872944830;
40      /** Location parameter. */
41      private final double mu;
42      /** Scale parameter. */
43      private final double scale;
44      /** Logarithm of "scale". */
45      private final double logScale;
46  
47      /**
48       * @param mu Location parameter.
49       * @param scale Scale parameter (must be positive).
50       */
51      private LogisticDistribution(double mu,
52                                   double scale) {
53          this.mu = mu;
54          this.scale = scale;
55          this.logScale = Math.log(scale);
56      }
57  
58      /**
59       * Creates a logistic distribution.
60       *
61       * @param mu Location parameter.
62       * @param scale Scale parameter (must be positive).
63       * @return the distribution
64       * @throws IllegalArgumentException if {@code scale <= 0}.
65       */
66      public static LogisticDistribution of(double mu,
67                                            double scale) {
68          if (scale <= 0) {
69              throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE,
70                                              scale);
71          }
72          return new LogisticDistribution(mu, scale);
73      }
74  
75      /**
76       * Gets the location parameter of this distribution.
77       *
78       * @return the location parameter.
79       */
80      public double getLocation() {
81          return mu;
82      }
83  
84      /**
85       * Gets the scale parameter of this distribution.
86       *
87       * @return the scale parameter.
88       */
89      public double getScale() {
90          return scale;
91      }
92  
93      /** {@inheritDoc} */
94      @Override
95      public double density(double x) {
96          if (x <= SUPPORT_LO ||
97              x >= SUPPORT_HI) {
98              return 0;
99          }
100 
101         // Ensure symmetry around location by using the absolute.
102         // This also ensures exp(z) is between 1 and 0 and avoids
103         // overflow for large negative values of (x - mu).
104         // Exploits the reciprocal relation: exp(-x) == 1 / exp(x)
105         //     exp(-z)                   1                exp(z)     exp(z)
106         // --------------- = -------------------------- * ------ = --------------
107         // (1 + exp(-z))^2    exp(z) (1 + 1 / exp(z))^2   exp(z)   (1 + exp(z))^2
108         final double z = -Math.abs(x - mu) / scale;
109         final double v = Math.exp(z);
110         return v / ((1 + v) * (1 + v)) / scale;
111     }
112 
113     /** {@inheritDoc} */
114     @Override
115     public double logDensity(double x) {
116         if (x <= SUPPORT_LO ||
117             x >= SUPPORT_HI) {
118             return Double.NEGATIVE_INFINITY;
119         }
120 
121         // Ensure symmetry around location by using the absolute
122         final double z = -Math.abs(x - mu) / scale;
123         final double v = Math.exp(z);
124         return z - 2 * Math.log1p(v) - logScale;
125     }
126 
127     /** {@inheritDoc} */
128     @Override
129     public double cumulativeProbability(double x) {
130         final double z = (x - mu) / scale;
131         return 1 / (1 + Math.exp(-z));
132     }
133 
134     /** {@inheritDoc} */
135     @Override
136     public double survivalProbability(double x) {
137         final double z = (x - mu) / scale;
138         return 1 / (1 + Math.exp(z));
139     }
140 
141     /** {@inheritDoc} */
142     @Override
143     public double inverseCumulativeProbability(double p) {
144         ArgumentUtils.checkProbability(p);
145         if (p == 0) {
146             return SUPPORT_LO;
147         } else if (p == 1) {
148             return SUPPORT_HI;
149         } else {
150             return scale * Math.log(p / (1 - p)) + mu;
151         }
152     }
153 
154     /** {@inheritDoc} */
155     @Override
156     public double inverseSurvivalProbability(double p) {
157         ArgumentUtils.checkProbability(p);
158         if (p == 1) {
159             return SUPPORT_LO;
160         } else if (p == 0) {
161             return SUPPORT_HI;
162         } else {
163             return scale * -Math.log(p / (1 - p)) + mu;
164         }
165     }
166 
167     /**
168      * {@inheritDoc}
169      *
170      * <p>The mean is equal to the {@linkplain #getLocation() location}.
171      */
172     @Override
173     public double getMean() {
174         return getLocation();
175     }
176 
177     /**
178      * {@inheritDoc}
179      *
180      * <p>For scale parameter \( s \), the variance is:
181      *
182      * <p>\[ \frac{s^2 \pi^2}{3} \]
183      */
184     @Override
185     public double getVariance() {
186         return scale * scale * PI_SQUARED_OVER_THREE;
187     }
188 
189     /**
190      * {@inheritDoc}
191      *
192      * <p>The lower bound of the support is always negative infinity.
193      *
194      * @return {@linkplain Double#NEGATIVE_INFINITY negative infinity}.
195      */
196     @Override
197     public double getSupportLowerBound() {
198         return SUPPORT_LO;
199     }
200 
201     /**
202      * {@inheritDoc}
203      *
204      * <p>The upper bound of the support is always positive infinity.
205      *
206      * @return {@linkplain Double#POSITIVE_INFINITY positive infinity}.
207      */
208     @Override
209     public double getSupportUpperBound() {
210         return SUPPORT_HI;
211     }
212 
213     /** {@inheritDoc} */
214     @Override
215     double getMedian() {
216         // Overridden for the probability(double, double) method.
217         // This is intentionally not a public method.
218         return mu;
219     }
220 }