/*
* Apache License
* Version 2.0, January 2004
* http://www.apache.org/licenses/
*
* Copyright 2013 Aurelian Tutuianu
* Copyright 2014 Aurelian Tutuianu
* Copyright 2015 Aurelian Tutuianu
* Copyright 2016 Aurelian Tutuianu
*
* Licensed 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 rapaio.core.distributions;
import static rapaio.math.MathTools.combinations;
/**
* Created by andrei on 13.11.2015.
*/
public class Hypergeometric implements Distribution {
private final int m;
private final int n;
private final int k;
public Hypergeometric(int m, int n, int k) {
if ( m < 0 ) {
throw new IllegalArgumentException( " m parameter should not be negative" );
}
if ( n < 0 ) {
throw new IllegalArgumentException( " n parameter should not be negative" );
}
if ( n + m < 1 ) {
throw new IllegalArgumentException( " m + n should be at least 1." );
}
this.m = m;
this.n = n;
this.k = k;
}
@Override
public String name() {
return "Hypergeometric(m=" + m + ", n=" + n + ", k=" + k + ")";
}
@Override
public boolean discrete() {
return true;
}
@Override
public double pdf( double x ) {
if (x != Math.floor(x) || Double.isInfinite(x)) {
throw new IllegalArgumentException( "x should be an integer since the hypergeometric" +
" repartition is a discrete repartion." );
}
double a = combinations(m, (int) x);
double b = combinations(n, k - (int) x );
double c = combinations(m + n, k );
return a * b / c;
}
@Override
public double cdf( double x ) {
if (x != Math.floor(x) || Double.isInfinite(x)) {
throw new IllegalArgumentException( "x should be an integer since the hypergeometric" +
" repartition is a discrete repartion." );
}
double cdf = 0;
for ( int i = 0; i <= x; ++i ) {
cdf += pdf(x);
}
return cdf;
}
@Override
public double quantile( double p ) {
double cdf = 0;
if (p == 0) {
return 0;
}
for ( int i = 0; i <= m; ++i ) {
cdf += pdf(i);
if ( cdf >= p ) {
return i;
}
}
return m;
}
@Override
public double min() {
return 0;
}
@Override
public double max() {
return m;
}
@Override
public double mean() {
return (m * k) / (m + n);
}
@Override
public double mode() {
return Math.floor( (k + 1) * (m + 1) / ( n + m + 2) );
}
/*
According to http://mathworld.wolfram.com/HypergeometricDistribution.html
the variance of a random variable X ~ Hypergeometric(m, n, k) is
var(X) = ( n * m * k * ( n + m - k ) ) / (( (n + m) ^ 2) * (n + m - 1))
*/
@Override
public double var() {
return ( n * m * k * ( n + m - k ) ) / (Math.pow( ( n + m ), 2 ) * (n + m - 1) );
}
@Override
public double skewness() {
return Math.sqrt( (n + m - 1) / (n * m * k * (n + m - k)) );
}
/*
Computing the kurtosis using the formula from this Wikipedia page:
https://en.wikipedia.org/wiki/Hypergeometric_distribution
*/
@Override
public double kurtosis() {
double total = m + n;
double firstTerm = k * m * n * ( total - k) * ( total - 2) * ( total - 3);
double secondTerm = (total - 1) * Math.pow( total, 2 ) * (total * (total + 1)
- 6 * m * n - 6 * k * (total - k)) + 6 * k * m * n * (total - k)
* (5 * total - 6);
return secondTerm / firstTerm;
}
@Override
public double entropy() {
return Double.NaN;
}
}