All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.sandia.cognition.statistics.distribution.GammaDistribution Maven / Gradle / Ivy

There is a newer version: 4.0.1
Show newest version
/*
 * File:                GammaDistribution.java
 * Authors:             Kevin R. Dixon
 * Company:             Sandia National Laboratories
 * Project:             Cognitive Foundry
 *
 * Copyright October 2, 2007, Sandia Corporation.  Under the terms of Contract
 * DE-AC04-94AL85000, there is a non-exclusive license for use of this work by
 * or on behalf of the U.S. Government. Export of this program may require a
 * license from the United States Government. See CopyrightHistory.txt for
 * complete details.
 *
 */

package gov.sandia.cognition.statistics.distribution;

import gov.sandia.cognition.annotation.PublicationReference;
import gov.sandia.cognition.annotation.PublicationType;
import gov.sandia.cognition.math.MathUtil;
import gov.sandia.cognition.math.matrix.VectorFactory;
import gov.sandia.cognition.math.matrix.Vector;
import gov.sandia.cognition.math.UnivariateStatisticsUtil;
import gov.sandia.cognition.statistics.AbstractClosedFormSmoothUnivariateDistribution;
import gov.sandia.cognition.statistics.DistributionEstimator;
import gov.sandia.cognition.statistics.DistributionWeightedEstimator;
import gov.sandia.cognition.statistics.EstimableDistribution;
import gov.sandia.cognition.statistics.UnivariateProbabilityDensityFunction;
import gov.sandia.cognition.statistics.SmoothCumulativeDistributionFunction;
import gov.sandia.cognition.util.AbstractCloneableSerializable;
import gov.sandia.cognition.util.ArgumentChecker;
import gov.sandia.cognition.util.Pair;
import gov.sandia.cognition.util.WeightedValue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Random;

/**
 * Class representing the Gamma distribution.  This is a two-parameter family
 * of continuous distributions. The well-known exponential and chi-square
 * distributions are special cases of the Gamma distribution.  Please note that
 * we use the "shape" and "scale" parameters to describe the PDF/CDF, whereas
 * octave/MATLAB use "shape" and "1.0/scale" to parameterize a Gamma
 * distribution, so please beware when comparing results to octave/MATLAB.
 *
 * @author Kevin R. Dixon
 * @since  2.0
 *
 */
@PublicationReference(
    author="Wikipedia",
    title="Gamma distribution",
    type=PublicationType.WebPage,
    year=2009,
    url="http://en.wikipedia.org/wiki/Gamma_distribution"
)
public class GammaDistribution
    extends AbstractClosedFormSmoothUnivariateDistribution
    implements EstimableDistribution
{

    /**
     * Default shape, {@value}.
     */
    public static final double DEFAULT_SHAPE = 1.0;

    /**
     * Default scale, {@value}.
     */
    public static final double DEFAULT_SCALE = 1.0;

    /**
     * Shape parameter of the Gamma distribution, often written as "k",
     * must be greater than zero
     */
    private double shape;

    /**
     * Scale parameters of the Gamma distribution, often written as "theta",
     * must be greater than zero.
     * Note that this is the INVERSE of what octave uses!!
     */
    private double scale;

    /**
     * Default constructor.
     */
    public GammaDistribution()
    {
        this( DEFAULT_SHAPE, DEFAULT_SCALE );
    }

    /**
     * Creates a new instance of GammaDistribution
     * @param shape
     * Shape parameter of the Gamma distribution, often written as "k",
     * must be greater than zero
     * @param scale
     * Scale parameters of the Gamma distribution, often written as "theta",
     * must be greater than zero.
     * Note that this is the INVERSE of what octave uses!!
     */
    public GammaDistribution(
        final double shape,
        final double scale )
    {
        this.setShape( shape );
        this.setScale( scale );
    }

    /**
     * Copy constructor
     * @param other
     * GammaDistribution to copy
     */
    public GammaDistribution(
        final GammaDistribution other  )
    {
        this( other.getShape(), other.getScale() );
    }

    @Override
    public GammaDistribution clone()
    {
        return (GammaDistribution) super.clone();
    }

    /**
     * Getter for shape
     * @return
     * Shape parameter of the Gamma distribution, often written as "k",
     * must be greater than zero
     */
    public double getShape()
    {
        return this.shape;
    }

    /**
     * Setter for shape
     * @param shape
     * Shape parameter of the Gamma distribution, often written as "k",
     * must be greater than zero
     */
    public void setShape(
        final double shape )
    {
        if (shape <= 0.0)
        {
            throw new IllegalArgumentException( "Shape must be > 0.0" );
        }
        this.shape = shape;
    }

    /**
     * Getter for scale
     * @return
     * Scale parameters of the Gamma distribution, often written as "theta",
     * must be greater than zero.
     * Note that this is the INVERSE of what octave uses!!
     */
    public double getScale()
    {
        return this.scale;
    }

    /**
     * Setter for scale
     * @param scale
     * Scale parameters of the Gamma distribution, often written as "theta",
     * must be greater than zero.
     * Note that this is the INVERSE of what octave uses!!
     */
    public void setScale(
        final double scale )
    {
        ArgumentChecker.assertIsPositive("scale", scale);
        this.scale = scale;
    }
    
    /**
     * Gets the rate parameter, which is just the inverse of the scale parameter.
     * It is commonly referred to as beta.
     * 
     * @return 
     *      The rate parameter (1.0 / scale). Must be greater than 0.0.
     */
    public double getRate()
    {
        return 1.0 / this.getScale();
    }
    
    /**
     * Sets the rate parameter, which is just the inverse of the scale parameter.
     * It is commonly referred to as beta.
     * 
     * @param   rate 
     *      The rate parameter (1.0 / scale). Must be greater than 0.0.
     */
    public void setRate(
        final double rate)
    {
        ArgumentChecker.assertIsPositive("rate", rate);
        this.setScale(1.0 / rate);
    }

    @Override
    public double getMeanAsDouble()
    {
        return this.getShape() * this.getScale();
    }

    @Override
    public double getVariance()
    {
        return this.getShape() * this.getScale() * this.getScale();
    }

    /**
     * Gets the parameters of the distribution
     * @return
     * 2-dimensional Vector with (shape scale)
     */
    @Override
    public Vector convertToVector()
    {
        return VectorFactory.getDefault().copyValues(
            this.getShape(), this.getScale() );
    }

    /**
     * Sets the parameters of the distribution
     * @param parameters
     * 2-dimensional Vector with (shape scale)
     */
    @Override
    public void convertFromVector(
        final Vector parameters )
    {
        if (parameters.getDimensionality() != 2)
        {
            throw new IllegalArgumentException(
                "Expected a 2-dimensional Vector!" );
        }

        this.setShape( parameters.getElement( 0 ) );
        this.setScale( parameters.getElement( 1 ) );

    }

    /**
     * Efficiently samples from a Gamma distribution given by the
     * shape and scale parameters.
     * @param shape
     * Shape parameter of the Gamma distribution, often written as "k",
     * must be greater than zero
     * @param scale
     * Scale parameters of the Gamma distribution, often written as "theta",
     * must be greater than zero.
     * Note that this is the INVERSE of what octave uses!!
     * @param random
     * Random number generator
     * @param numSamples
     * Number of samples to generate
     * @return
     * Samples simulated from the Gamma distribution.
     */
    public static ArrayList sample(
        final double shape,
        final double scale,
        final Random random,
        final int numSamples )
    {
        final double[] values = sampleAsDoubles(shape, scale, random, numSamples);
        final ArrayList result = new ArrayList(numSamples);
        for (final double value : values)
        {
            result.add(value);
        }
        return result;
    }
    
    /**
     * Efficiently samples from a Gamma distribution given by the
     * shape and scale parameters.
     * @param shape
     * Shape parameter of the Gamma distribution, often written as "k",
     * must be greater than zero
     * @param scale
     * Scale parameters of the Gamma distribution, often written as "theta",
     * must be greater than zero.
     * Note that this is the INVERSE of what octave uses!!
     * @param random
     * Random number generator
     * @return
     * Samples simulated from the Gamma distribution.
     */
    public static double sampleAsDouble(
        final double shape,
        final double scale,
        final Random random)
    {
        return sample(shape, scale, random);
    }
    
    /**
     * Efficiently samples from a Gamma distribution given by the
     * shape and scale parameters.
     * @param shape
     * Shape parameter of the Gamma distribution, often written as "k",
     * must be greater than zero
     * @param scale
     * Scale parameters of the Gamma distribution, often written as "theta",
     * must be greater than zero.
     * Note that this is the INVERSE of what octave uses!!
     * @param random
     * Random number generator
     * @param numSamples
     * Number of samples to generate
     * @return
     * Samples simulated from the Gamma distribution.
     */
    public static double[] sampleAsDoubles(
        final double shape,
        final double scale,
        final Random random,
        final int numSamples)
    {
        final double[] result = new double[numSamples];
        sampleInto(shape, scale, random, result, 0, numSamples);
        return result;
    }

    /**
     * Efficiently samples from a Gamma distribution given by the
     * shape and scale parameters.
     * @param shape
     * Shape parameter of the Gamma distribution, often written as "k",
     * must be greater than zero
     * @param scale
     * Scale parameters of the Gamma distribution, often written as "theta",
     * must be greater than zero.
     * Note that this is the INVERSE of what octave uses!!
     * @param random
     * Random number generator
     * @param output Array to write samples from Gamma distribution into.
     * @param start Starting point in output array to add samples.
     * @param length Number of samples to generate
     */
    public static void sampleInto(
        final double shape,
        final double scale,
        final Random random,
        final double[] output,
        final int start,
        final int length)
    {
        final int end = start + length;
        for (int i = start; i < end; i++)
        {
            output[i] = sample(shape, scale, random);
        }
    }
    
    /**
     * Provides a single sample from a Gamma distribution with the given shape
     * and scale.
     * 
     * @param shape
     *      Shape parameter of the Gamma distribution, often written as "k",
     *      must be greater than zero.
     * @param scale
     *      Scale parameters of the Gamma distribution, often written as "theta",
     *      must be greater than zero.
     * @param random
     *      Random number generator to use.
     * @return 
     *      A value sampled from a Gamma distribution.
     */
    public static double sample(
        final double shape,
        final double scale,
        final Random random)
    {
        // Shape is checked in the next function.
        ArgumentChecker.assertIsPositive("scale", scale);
        return scale * sampleStandard(shape, random);
    }
    
    /**
     * Provides a single sample from a Gamma distribution with the given shape
     * and a scale of 1.
     * 
     * @param shape
     *      Shape parameter of the Gamma distribution, often written as "k",
     *      must be greater than zero.
     * @param random
     *      Random number generator to use.
     * @return 
     *      A value sampled from a Gamma distribution.
     */
    public static double sampleStandard(
        final double shape,
        final Random random)
    {
        ArgumentChecker.assertIsPositive("shape", shape);
        
        // This is based on the gamma distribution algorithm used in numpy:
        // https://github.com/numpy/numpy/blob/master/numpy/random/mtrand/distributions.c
        if (shape == 1.0)
        {
            // Sample standard exponential:
            return -Math.log(random.nextDouble());
        }
        else if (shape < 1.0)
        {
            while (true)
            {
                final double u = random.nextDouble();
                final double v = -Math.log(random.nextDouble());
                if (u <= 1.0 - shape)
                {
                    final double x = Math.pow(u, 1.0 / shape);
                    if (x <= v)
                    {
                        return x;
                    }
                }
                else
                {
                    final double y = -Math.log((1.0 - u) / shape);
                    final double x = Math.pow(1.0 - shape + shape * y, 1.0 / shape);
                    if (x <= (v + y))
                    {
                        return x;
                    }
                }
            }
        }
        else
        {
            // Marsaglia's method.
            final double b = shape - 1.0 / 3.0;
            final double c = 1.0 / Math.sqrt(9.0 * b);
            while (true)
            {
                double x = 0.0;
                double v = 0.0;
                do
                {
                    x = random.nextGaussian();
                    v = 1.0 + c * x;
                }
                while (v <= 0.0);

                v = v * v * v;
                final double xx = x * x;
                final double u = random.nextDouble();
                if (u < (1.0 - 0.0331 * xx * xx)
                    || Math.log(u) < ((0.5 * xx) + (b * (1.0 - v + Math.log(v)))))
                {
                    return b * v;
                }
            }
        }
    }

    @Override
    public double sampleAsDouble(
        final Random random)
    {
        return sample(this.getShape(), this.getScale(), random);
    }
    
    @Override
    public void sampleInto(
        final Random random,
        final double[] output,
        final int start,
        final int length)
    {
        sampleInto(this.getShape(), this.getScale(), random, 
            output, start, length);
    }

    @Override
    public GammaDistribution.CDF getCDF()
    {
        return new GammaDistribution.CDF( this );
    }

    @Override
    public GammaDistribution.PDF getProbabilityFunction()
    {
        return new GammaDistribution.PDF( this );
    }

    @Override
    public String toString()
    {
        return "Shape = " + this.getShape() + ", Scale = " + this.getScale();
    }

    @Override
    public Double getMinSupport()
    {
        return 0.0;
    }

    @Override
    public Double getMaxSupport()
    {
        return Double.POSITIVE_INFINITY;
    }

    @Override
    public GammaDistribution.MomentMatchingEstimator getEstimator()
    {
        return new GammaDistribution.MomentMatchingEstimator();
    }

    /**
     * Closed-form PDF of the Gamma distribution
     */
    public static class PDF
        extends GammaDistribution
        implements UnivariateProbabilityDensityFunction
    {
     
        /**
         * Default constructor.
         */
        public PDF()
        {
            super();
        }

        /**
         * Creates a new instance of PDF
         * @param shape
         * Shape parameter of the Gamma distribution, often written as "k",
         * must be greater than zero
         * @param scale
         * Scale parameters of the Gamma distribution, often written as "theta",
         * must be greater than zero.
         * Note that this is the INVERSE of what octave uses!!
         */
        public PDF(
            final double shape,
            final double scale )
        {
            super( shape, scale );
        }

        /**
         * Copy constructor
         * @param other
         * GammaDistribution to copy
         */
        public PDF(
            final GammaDistribution other  )
        {
            super( other );
        }

        @Override
        public Double evaluate(
            final Double input )
        {
            return this.evaluate( input.doubleValue() );
        }
        
        @Override
        public double evaluateAsDouble(
            final Double input)
        {
            return this.evaluate(input.doubleValue());
        }
        
        @Override
        public double evaluate(
            final double input )
        {
            return evaluate( input, this.getShape(), this.getScale() );
        }

        /**
         * Evaluates the Gamma PDF about the input "x", using the given
         * shape and scale
         * @param x
         * Input to the PDF
         * @param shape
         * Shape parameter of the Gamma distribution, often written as "k",
         * must be greater than zero
         * @param scale
         * Scale parameters of the Gamma distribution, often written as "theta",
         * must be greater than zero.
         * Note that this is the INVERSE of what octave uses!!
         * @return
         * p(x;shape,scale)
         */
        public static double evaluate(
            final double x,
            final double shape,
            final double scale )
        {
            
            double p;
            if (x > 0.0)
            {
                p =  Math.exp( logEvaluate(x, shape, scale) );
            }
            else
            {
                p = 0.0;
            }

            return p;
        }

        @Override
        public double logEvaluate(
            final Double input)
        {
            return this.logEvaluate((double) input);
        }

        @Override
        public double logEvaluate(
            final double input)
        {
            return logEvaluate( input, this.getShape(), this.getScale() );
        }

        /**
         * Evaluates the natural logarithm of the PDF.
         * @param input
         * Input to consider.
         * @param shape
         * Shape factor.
         * @param scale
         * Scale factor.
         * @return
         * Natural logarithm of the PDF.
         */
        public static double logEvaluate(
            final double input,
            final double shape,
            final double scale )
        {

            if( input <= 0.0 )
            {
                return Math.log(0.0);
            }
            else
            {
                final double n1 = (shape-1.0) * Math.log(input);
                final double n2 = -input / scale;
                final double d1 = MathUtil.logGammaFunction(shape);
                final double d2 = shape * Math.log(scale);
                return n1 + n2 - d1 - d2;
            }

        }

        @Override
        public GammaDistribution.PDF getProbabilityFunction()
        {
            return this;
        }

    }
    
    /**
     * CDF of the Gamma distribution
     */
    public static class CDF
        extends GammaDistribution
        implements SmoothCumulativeDistributionFunction
    {
        
        /**
         * Default constructor.
         */
        public CDF()
        {
            super();
        }

        /**
         * Creates a new instance of CDF
         * @param shape
         * Shape parameter of the Gamma distribution, often written as "k",
         * must be greater than zero
         * @param scale
         * Scale parameters of the Gamma distribution, often written as "theta",
         * must be greater than zero.
         * Note that this is the INVERSE of what octave uses!!
         */
        public CDF(
            final double shape,
            final double scale )
        {
            super( shape, scale );
        }

        /**
         * Copy constructor
         * @param other
         * GammaDistribution to copy
         */
        public CDF(
            final GammaDistribution other  )
        {
            super( other );
        }
        

        @Override
        public Double evaluate(
            final Double input )
        {
            return this.evaluate( input.doubleValue() );
        }
        
        @Override
        public double evaluateAsDouble(
            final Double input)
        {
            return this.evaluate(input.doubleValue());
        }
        
        @Override
        public double evaluate(
            final double input )
        {
            return evaluate( input, this.getShape(), this.getScale() );
        }

        /**
         * Evaluates the CDF of the Gamma distribution about x, given 
         * the shape and scale parameters
         * @param x
         * Input to the CDF
         * @param shape
         * Shape parameter of the Gamma distribution, often written as "k",
         * must be greater than zero
         * @param scale
         * Scale parameters of the Gamma distribution, often written as "theta",
         * must be greater than zero.
         * Note that this is the INVERSE of what octave uses!!
         * @return
         * Pr(y le x;shape,scale)
         */
        public static double evaluate(
            final double x,
            final double shape,
            final double scale )
        {
            double p;
            if (x <= 0.0)
            {
                p = 0.0;
            }
            else
            {
                p = MathUtil.lowerIncompleteGammaFunction( shape, x / scale );
            }
            return p;
        }

        @Override
        public GammaDistribution.CDF getCDF()
        {
            return this;
        }

        @Override
        public GammaDistribution.PDF getDerivative()
        {
            return this.getProbabilityFunction();
        }

        @Override
        public Double differentiate(
            final Double input)
        {
            return this.getDerivative().evaluate(input);
        }

    }

    /**
     * Computes the parameters of a Gamma distribution by the
     * Method of Moments
     */
    public static class MomentMatchingEstimator
        extends AbstractCloneableSerializable
        implements DistributionEstimator
    {

        /**
         * Default constructor
         */
        public MomentMatchingEstimator()
        {
        }

        @Override
        public GammaDistribution learn(
            final Collection data)
        {
            Pair pair =
                UnivariateStatisticsUtil.computeMeanAndVariance(data);
            return learn( pair.getFirst(), pair.getSecond() );
        }

        /**
         * Computes the Gamma distribution describes by the given moments
         * @param mean
         * Mean of the distribution
         * @param variance
         * Variance of the distribution
         * @return
         * Gamma distribution that has the same mean/variance as the
         * given parameters.
         */
        public static GammaDistribution learn(
            final double mean,
            final double variance )
        {
            double scale = variance / mean;
            double shape = mean*mean / variance;
            return new GammaDistribution(shape, scale);
        }

    }

    /**
     * Estimates the parameters of a Gamma distribution using the matching
     * of moments, not maximum likelihood.
     */
    public static class WeightedMomentMatchingEstimator
        extends AbstractCloneableSerializable
        implements DistributionWeightedEstimator
    {

        /**
         * Default constructor
         */
        public WeightedMomentMatchingEstimator()
        {
        }

        @Override
        public GammaDistribution learn(
            final Collection> data)
        {
            Pair pair =
                UnivariateStatisticsUtil.computeWeightedMeanAndVariance(data);
            return MomentMatchingEstimator.learn(
                pair.getFirst(), pair.getSecond());
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy