BotSharp/BotSharp.Algorithm/HiddenMarkovModel/MathUtils/Statistics/MANOVA.cs

288 lines
10 KiB
C#
Raw Normal View History

2018-09-17 12:31:54 +00:00
using BotSharp.Algorithm.HiddenMarkovModel.MathUtils.Distribution;
using BotSharp.Algorithm.HiddenMarkovModel.MathUtils.LinearAlgebra;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BotSharp.Algorithm.HiddenMarkovModel.MathUtils.Statistics
{
/// <summary>
/// Multivariate ANOVA.
/// Status: waiting to be tested
///
/// The implementation is based on https://onlinecourses.science.psu.edu/stat505/node/162
///
/// In ANOVA, we have:
/// y = y_grand_mean + group_effect_j
/// where j is the group id
/// And we have the hypotheses:
/// H_0 : group_effect_j = 0 for all j \in J
/// H_A : group_effect_j != 0 for some j \in J
///
/// In MANOVA, instead of single variable y, we have multiple variable y(k), we have:
/// y(k) = y_grand_mean(k) + group_effect_j(k)
/// where y(k) is the k-th variable
/// And we have the hypotheses:
/// H_0 : group_effect_j(k) = 0 for all j, k
/// H_A : group_effect_j(k) != 0 for some j, k
/// Or equivalently
/// H_0 : mu_1(k) = mu_2(k) = ... = mu_j(k) = ... for all j, k
/// H_A : some mu_j(k) are not equal.
/// </summary>
public class MANOVA
{
public double[] GrandMeansY; //grand_means for each variable var_k
public Dictionary<int, double[]> SampleMeans; //sample means for each variable var_k (i.e. the column) in each group (i.e. row)
public double[][] T; // The total sum of squares is a cross products matrix
public double[][] E; // Error Sum of Squares and Cross Products
public double[][] H; // Hypothesis Sum of Squares and Cross Products.
public int dfT; //degree of freedom total
public int dfE; //degree of freedom error
public int dfH; //degree of freedom hypothesis
public double pValue;
public double F_crit;
public static MANOVA RunMANOVA(double[][] y, int[] grpCat, double signficance_level = 0.05)
{
MANOVA output = new MANOVA();
RunMANOVA(y, grpCat, output, signficance_level);
return output;
}
public static void RunMANOVA(double[][] y, int[] grpCat, MANOVA output, double signficance_level = 0.05)
{
output.GrandMeansY = GetGrandMeans(y);
int N = grpCat.Length; // number of data points
int p = y[0].Length; // number of variables
Dictionary<int, List<double[]>> groupped_y = new Dictionary<int, List<double[]>>();
for (int i = 0; i < N; ++i)
{
int grpId = grpCat[i];
double[] y_i = y[i];
List<double[]> group_y = null;
if (groupped_y.ContainsKey(grpId))
{
group_y = groupped_y[grpId];
}
else
{
group_y = new List<double[]>();
groupped_y[grpId] = group_y;
}
group_y.Add(y_i);
}
output.SampleMeans = GetSampleMeans(groupped_y);
output.T = GetT(groupped_y, output.GrandMeansY, p);
output.E = GetE(groupped_y, output.SampleMeans, p);
output.H = GetH(output.SampleMeans, output.GrandMeansY, p);
int g = output.SampleMeans.Count;
output.dfT = N - 1;
output.dfH = g - 1;
output.dfE = N - g;
output.pValue = GetWilksLambda(output.H, output.E, N, p, g, out output.F_crit);
}
/// <summary>
/// Approximate Wilk's lambda by F statistics
/// </summary>
/// <param name="H">Hypothesis Sum of Squares and Cross Products.</param>
/// <param name="E">Error Sum of Squares and Cross Products</param>
/// <param name="N">number of data points</param>
/// <param name="p">number of variables</param>
/// <param name="g">number of groups</param>
/// <param name="F_crit">The F critical value</param>
/// <returns>The p-value</returns>
public static double GetWilksLambda(double[][] H, double[][] E, int N, int p, int g, out double F_crit)
{
double[][] H_plus_E = Add(H, E);
double E_determinant = MatrixOp.GetDeterminant(E);
double H_plus_E_determinant = MatrixOp.GetDeterminant(H_plus_E);
double lambda = E_determinant / H_plus_E_determinant;
double a = N - g - (p - g + 2) / 2.0;
int b_threshold = p * p + (g - 1) * (g - 1) - 5;
double b = 1;
if (b_threshold > 0)
{
b = System.Math.Sqrt((p * p * (g - 1) - 4) / b_threshold);
}
double c = (p * (g - 1) - 2) / 2.0;
F_crit = ((1 - System.Math.Pow(lambda, 1 / b)) / (System.Math.Pow(lambda, 1 / b))) * ((a * b - c) / (p * (g - 1)));
double DF1 = p * (g - 1);
double DF2 = a * b - c;
double pValue = 1 - FDistribution.GetPercentile(F_crit, DF1, DF2);
return pValue;
}
public static double[][] Add(double[][] A, double[][] B)
{
int rowCount = A.Length;
int colCount = A[0].Length;
double[][] C = new double[rowCount][];
for (int i = 0; i < rowCount; ++i)
{
for (int j = 0; j < colCount; ++j)
{
C[i][j] = A[i][j] + B[i][j];
}
}
return C;
}
/// <summary>
/// Get between-group variance
/// </summary>
/// <param name="groupped_y"></param>
/// <param name="sample_means"></param>
/// <param name="p"></param>
/// <returns></returns>
private static double[][] GetH(Dictionary<int, double[]> sample_means, double[] grand_means, int p)
{
double[][] H = new double[p][];
for (int k = 0; k < p; ++k)
{
H[k] = new double[p];
for (int l = 0; l < p; ++l)
{
double sum = 0;
foreach (int i in sample_means.Keys)
{
sum += (sample_means[i][k] - grand_means[k]) * (sample_means[i][l] - grand_means[l]); // sample in the group i
}
H[k][l] = sum;
}
}
return H;
}
/// <summary>
/// Get within-group variance
/// </summary>
/// <param name="groupped_y"></param>
/// <param name="sample_means"></param>
/// <param name="p"></param>
/// <returns></returns>
private static double[][] GetE(Dictionary<int, List<double[]>> groupped_y, Dictionary<int, double[]> sample_means, int p)
{
double[][] E = new double[p][];
for (int k = 0; k < p; ++k)
{
E[k] = new double[p];
for (int l = 0; l < p; ++l)
{
double sum = 0;
foreach (int i in groupped_y.Keys)
{
List<double[]> group_y = groupped_y[i]; // sample in the group i
int n_i = group_y.Count;
for (int j = 0; j < n_i; ++j)
{
double num1 = (group_y[j][k] - sample_means[i][k]);
double num2 = (group_y[j][l] - sample_means[i][l]);
sum += num1 * num2;
}
}
E[k][l] = sum;
}
}
return E;
}
/// <summary>
/// Get total variance
/// </summary>
/// <param name="groupped_y"></param>
/// <param name="grand_means"></param>
/// <param name="p"></param>
/// <returns></returns>
private static double[][] GetT(Dictionary<int, List<double[]>> groupped_y, double[] grand_means, int p)
{
double[][] T = new double[p][];
for (int k = 0; k < p; ++k)
{
T[k] = new double[p];
for (int l = 0; l < p; ++l)
{
double sum = 0;
foreach (int i in groupped_y.Keys)
{
List<double[]> group_y = groupped_y[i]; // sample in the group i
int n_i = group_y.Count;
for (int j = 0; j < n_i; ++j)
{
double num1 = (group_y[j][k] - grand_means[k]);
double num2 = (group_y[j][l] - grand_means[l]);
sum += num1 * num2;
}
}
T[k][l] = sum;
}
}
return T;
}
private static Dictionary<int, double[]> GetSampleMeans(Dictionary<int, List<double[]>> groupped_y)
{
Dictionary<int, double[]> sample_means = new Dictionary<int, double[]>();
foreach (int i in groupped_y.Keys)
{
List<double[]> group_y = groupped_y[i];
int n_i = group_y.Count; // number of data points in group with group id = i
int p = group_y[0].Length; // number of variables
double[] sample_means_for_group_i = new double[p];
for (int k = 0; k < p; ++k)
{
double sum = 0;
for (int j = 0; j < n_i; ++j)
{
sum += group_y[j][k];
}
sample_means_for_group_i[k] = sum / n_i;
}
sample_means[i] = sample_means_for_group_i;
}
return sample_means;
}
private static double[] GetGrandMeans(double[][] y)
{
int N = y.Length; //number of data points
int p = y[0].Length; //number of variables
double[] grand_means = new double[p];
for (int k = 0; k < p; ++k)
{
double sum = 0;
for (int i = 0; i < N; ++i)
{
sum += (y[i][k]);
}
grand_means[k] = sum / N;
}
return grand_means;
}
}
}