/*
* Copyright (C) 2011-2013 Dr. John Lindsay <jlindsay@uoguelph.ca>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package whitebox.stats;
/**
*
* @author johnlindsay
*/
public class RayleighTest {
public enum AngularUnit {
DEGREE, RADIANS;
}
double[] directionData;
double[] magnitudeData;
boolean axialData = false;
double pvalue = -1.0;
double resultantVectorLength = -1.0;
double resultantDirection = -9999.0;
int n = 0;
double rayleighsR = -1.0;
double zStat = -1.0;
AngularUnit au = AngularUnit.RADIANS;
boolean transformationToRadianComplete = false;
boolean transformationAxialComplete = false;
final double twoPi = Math.PI * 2;
// constructors
public RayleighTest() {
// zero-parameter constructor
}
public RayleighTest(double[] directionData, AngularUnit au, boolean axialData) {
this.axialData = axialData;
this.directionData = directionData.clone();
this.n = directionData.length;
this.au = au;
this.magnitudeData = new double[n];
for (int a = 0; a < n; a++) {
this.magnitudeData[a] = 1;
}
}
public RayleighTest(double[] directionData, AngularUnit au, boolean axialData, double[] magnitudeData) {
this.axialData = axialData;
this.directionData = directionData.clone();
this.n = directionData.length;
this.au = au;
this.magnitudeData = magnitudeData.clone();
}
// properties
public double getPvalue() throws Exception {
if (resultantVectorLength < 0) {
calculateResultantVector();
}
if (pvalue < 0) {
calculatePValue();
}
return pvalue;
}
public double getResultantVectorLength() throws Exception {
if (resultantVectorLength < 0) {
calculateResultantVector();
}
return resultantVectorLength;
}
public double getResultantDirection() throws Exception {
if (resultantVectorLength < 0) {
calculateResultantVector();
}
return resultantDirection;
}
public double getRayleighsR() throws Exception {
if (resultantVectorLength < 0) {
calculateResultantVector();
}
if (pvalue < 0) {
calculatePValue();
}
return rayleighsR;
}
public double getzStat() throws Exception {
if (resultantVectorLength < 0) {
calculateResultantVector();
}
if (pvalue < 0) {
calculatePValue();
}
return zStat;
}
public int getN() {
return n;
}
public double[] getDirectionData() {
return directionData;
}
public void setDirectionData(double[] directionData) {
this.directionData = directionData.clone();
this.n = directionData.length;
}
public double[] getMagnitudeData() {
return magnitudeData;
}
public void setMagnitudeData(double[] magnitudeData) {
this.magnitudeData = magnitudeData.clone();
}
public boolean isAxialData() {
return axialData;
}
public void setAxialData(boolean axialData) {
this.axialData = axialData;
}
public AngularUnit getAngularUnit() {
return au;
}
public void setAngularUnit(AngularUnit au) {
this.au = au;
}
public String getTestOutput(double alpha) throws Exception {
if (resultantVectorLength < 0) {
calculateResultantVector();
}
if (pvalue < 0) {
calculatePValue();
}
if (pvalue <= alpha) {
return "The null hypothesis was rejected (alpha = " + alpha + "); the data are not distributed uniformly around the circle.";
}
return "The null hypothesis, that the data are distributed uniformly around the circle, could not be rejected (alpha = " + alpha + "). ";
}
// methods
private void transformationAxial() {
if (this.axialData) {
for (int a = 0; a < n; a++) {
this.directionData[a] = (2 * directionData[a]) % twoPi;
}
transformationAxialComplete = true;
}
}
private void transformationToRadians() {
if (au == AngularUnit.DEGREE) {
for (int a = 0; a < n; a++) {
this.directionData[a] = Math.toRadians(this.directionData[a]);
}
transformationToRadianComplete = true;
}
}
private void calculateResultantVector() throws Exception {
if (directionData == null) { throw new Exception("Error: direction data not specified."); }
if (magnitudeData == null) { throw new Exception("Error: magnitude data not specified."); }
if (directionData.length != magnitudeData.length) { throw new Exception("Error: direction and magnitude arrays must be the same size."); }
if (au == AngularUnit.DEGREE) { transformationToRadians(); }
if (axialData) { transformationAxial(); }
double xComponent = 0;
double yComponent = 0;
double sumWeights = 0;
for (int a = 0; a < n; a++) {
xComponent += magnitudeData[a] * Math.cos(directionData[a]);
yComponent += magnitudeData[a] * Math.sin(directionData[a]);
sumWeights += magnitudeData[a];
}
resultantVectorLength = Math.sqrt(xComponent * xComponent + yComponent * yComponent) / sumWeights;
resultantDirection = Math.atan2(yComponent, xComponent);
if (au == AngularUnit.DEGREE) {
resultantDirection = Math.toDegrees(resultantDirection);
}
if (this.axialData) {
resultantDirection = resultantDirection / 2.0;
}
}
private void calculatePValue() throws Exception {
if (directionData == null) { throw new Exception("Error: direction data not specified."); }
if (magnitudeData == null) { throw new Exception("Error: magnitude data not specified."); }
if (directionData.length != magnitudeData.length) { throw new Exception("Error: direction and magnitude arrays must be the same size."); }
if (resultantVectorLength < 0) { calculateResultantVector(); }
rayleighsR = resultantVectorLength * n;
zStat = rayleighsR * rayleighsR / n;
pvalue = Math.exp(Math.sqrt(1 + 4 * n + 4 * (n * n - rayleighsR * rayleighsR)) - (1 + 2 * n));
}
public static void main(String[] args) {
try {
/* This is used for testing purposes. */
//double[] testData = {150, 180, 210};
//double[] testData = {150, 0, 210}; // axially equivalent to the above array
//double[] testData = {15, 45, 75};
double[] testData = {66, 75, 86, 88, 88, 93, 97, 101, 118, 130};
//double[] testData = { 120, 180, 240 };
RayleighTest rt = new RayleighTest(testData, AngularUnit.DEGREE, true);
System.out.println("Resultant direction: " + rt.getResultantDirection());
System.out.println("Resultant length: " + rt.getResultantVectorLength());
System.out.println("N = " + rt.getN());
System.out.println("Rayleigh's R: " + rt.getRayleighsR());
System.out.println("p-value: " + rt.getPvalue());
System.out.println(rt.getTestOutput(0.05));
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}