package de.lmu.ifi.dbs.elki.math.statistics;
/*
This file is part of ELKI:
Environment for Developing KDD-Applications Supported by Index-Structures
Copyright (C) 2013
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
import de.lmu.ifi.dbs.elki.utilities.datastructures.arraylike.NumberArrayAdapter;
import de.lmu.ifi.dbs.elki.utilities.documentation.Reference;
/**
* Estimate the L-Moments of a sample.
*
* Reference:
*
* J. R. M. Hosking, J. R. Wallis, and E. F. Wood
* Estimation of the generalized extreme-value distribution by the method of
* probability-weighted moments.
* Technometrics 27.3
*
*
* Also based on:
*
* J. R. M. Hosking
* Fortran routines for use with the method of L-moments Version 3.03
* IBM Research.
*
*
* @author Erich Schubert
*/
@Reference(authors = "J.R.M. Hosking, J. R. Wallis, and E. F. Wood", title = "Estimation of the generalized extreme-value distribution by the method of probability-weighted moments.", booktitle = "Technometrics 27.3", url = "http://dx.doi.org/10.1080/00401706.1985.10488049")
public class ProbabilityWeightedMoments {
/**
* Compute the alpha_r factors using the method of probability-weighted
* moments.
*
* @param data Presorted data array.
* @param adapter Array adapter.
* @param nmom Number of moments to compute
* @return Alpha moments (0-indexed)
*/
public static double[] alphaPWM(A data, NumberArrayAdapter, A> adapter, final int nmom) {
final int n = adapter.size(data);
final double[] xmom = new double[nmom];
double weight = 1. / n;
for (int i = 0; i < n; i++) {
final double val = adapter.getDouble(data, i);
xmom[0] += weight * val;
for (int j = 1; j < nmom; j++) {
weight *= (n - i - j + 1) / (n - j + 1);
xmom[j] += weight * val;
}
}
return xmom;
}
/**
* Compute the beta_r factors using the method of probability-weighted
* moments.
*
* @param data Presorted data array.
* @param adapter Array adapter.
* @param nmom Number of moments to compute
* @return Beta moments (0-indexed)
*/
public static double[] betaPWM(A data, NumberArrayAdapter, A> adapter, final int nmom) {
final int n = adapter.size(data);
final double[] xmom = new double[nmom];
double weight = 1. / n;
for (int i = 0; i < n; i++) {
final double val = adapter.getDouble(data, i);
xmom[0] += weight * val;
for (int j = 1; j < nmom; j++) {
weight *= (i - j + 1) / (n - j + 1);
xmom[j] += weight * val;
}
}
return xmom;
}
/**
* Compute the alpha_r and beta_r factors in parallel using the method of
* probability-weighted moments. Usually cheaper than computing them
* separately.
*
* @param data Presorted data array.
* @param adapter Array adapter.
* @param nmom Number of moments to compute
* @return Alpha and Beta moments (0-indexed, interleaved)
*/
public static double[] alphaBetaPWM(A data, NumberArrayAdapter, A> adapter, final int nmom) {
final int n = adapter.size(data);
final double[] xmom = new double[nmom << 1];
double aweight = 1. / n, bweight = aweight;
for (int i = 0; i < n; i++) {
final double val = adapter.getDouble(data, i);
xmom[0] += aweight * val;
xmom[1] += bweight * val;
for (int j = 1, k = 2; j < nmom; j++, k += 2) {
aweight *= (n - i - j + 1) / (n - j + 1);
bweight *= (i - j + 1) / (n - j + 1);
xmom[k + 1] += aweight * val;
xmom[k + 1] += bweight * val;
}
}
return xmom;
}
/**
* Compute the sample L-Moments using probability weighted moments.
*
* @param sorted Presorted data array.
* @param adapter Array adapter.
* @param nmom Number of moments to compute
* @return Array containing Lambda1, Lambda2, Tau3 ... TauN
*/
public static double[] samLMR(A sorted, NumberArrayAdapter, A> adapter, final int nmom) {
final int n = adapter.size(sorted);
if (nmom >= n) {
throw new ArithmeticException("Can't compute higher order moments for just" + n + " observations.");
}
final double[] sum = new double[nmom];
// Estimate probability weighted moments (unbiased)
for (int i = 0; i < n; i++) {
double term = adapter.getDouble(sorted, i);
// Robustness: skip bad values
if (Double.isInfinite(term) || Double.isNaN(term)) {
continue;
}
sum[0] += term;
for (int j = 1, z = i; j < nmom; j++, z--) {
term *= z;
sum[j] += term;
}
}
// Normalize by "n choose (j + 1)"
sum[0] /= n;
double z = n;
for (int j = 1; j < nmom; j++) {
z *= n - j;
sum[j] /= z;
}
for (int k = nmom - 1; k >= 1; --k) {
double p = ((k & 1) == 0) ? +1 : -1;
double temp = p * sum[0];
for (int i = 0; i < k; i++) {
double ai = i + 1.;
p *= -(k + ai) * (k - i) / (ai * ai);
temp += p * sum[i + 1];
}
sum[k] = temp;
}
if (nmom > 2 && !(sum[1] > 0)) {
throw new ArithmeticException("Can't compute higher order moments for constant data. Sum: " + sum[1]);
}
// Map lambda3...lambdaN to tau3...tauN
for (int i = 2; i < nmom; i++) {
sum[i] /= sum[1];
}
return sum;
}
}