171 lines
5.6 KiB
C#
171 lines
5.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace Unity.PerformanceTesting.Statistics
|
|
{
|
|
/// <summary>
|
|
/// Provides basic measurement statistics calculated for a sample collection.
|
|
/// </summary>
|
|
readonly ref struct MeasurementsStatistics
|
|
{
|
|
/// <summary>
|
|
/// Mean of the samples.
|
|
/// </summary>
|
|
public double Mean { get; }
|
|
|
|
/// <summary>
|
|
/// Margin of error within the requested confidence interval.
|
|
/// </summary>
|
|
public double MarginOfError { get; }
|
|
|
|
MeasurementsStatistics(double mean, double marginOfError)
|
|
{
|
|
Mean = mean;
|
|
MarginOfError = marginOfError;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates basic measurement statistics for a sample collection.
|
|
/// </summary>
|
|
/// <param name="measurements">The sample collection.</param>
|
|
/// <param name="outlierMode">Outlier removal mode.</param>
|
|
/// <param name="confidenceLevel">Confidence interval for calculating the margin of error.</param>
|
|
/// <returns>A MeasurementStatistics instance calculated for the provided data.</returns>
|
|
/// <exception cref="InvalidOperationException">Thrown if the sample count is zero.</exception>
|
|
public static MeasurementsStatistics Calculate(List<double> measurements, OutlierMode outlierMode, ConfidenceLevel confidenceLevel)
|
|
{
|
|
var n = measurements.Count;
|
|
if (n == 0)
|
|
throw new InvalidOperationException("Requesting statistic measurements of a sequence which contains no elements.");
|
|
|
|
double sum;
|
|
double mean;
|
|
double variance;
|
|
double standardDeviation;
|
|
double standardError;
|
|
double marginOfError;
|
|
|
|
if (outlierMode == OutlierMode.DontRemove)
|
|
{
|
|
sum = Sum(measurements);
|
|
mean = sum / n;
|
|
variance = Variance(measurements, n, mean);
|
|
standardDeviation = Math.Sqrt(variance);
|
|
standardError = standardDeviation / Math.Sqrt(n);
|
|
marginOfError = n <= 2 ? double.NaN : standardError * confidenceLevel.GetZValue(n);
|
|
|
|
return new MeasurementsStatistics(mean, marginOfError);
|
|
}
|
|
|
|
measurements.Sort();
|
|
|
|
double q1, q3;
|
|
if (n == 1)
|
|
q1 = q3 = measurements[0];
|
|
else
|
|
{
|
|
q1 = GetQuartile(measurements, measurements.Count / 2);
|
|
q3 = GetQuartile(measurements, measurements.Count * 3 / 2);
|
|
}
|
|
|
|
var interquartileRange = q3 - q1;
|
|
var lowerFence = q1 - 1.5 * interquartileRange;
|
|
var upperFence = q3 + 1.5 * interquartileRange;
|
|
|
|
SumWithoutOutliers(outlierMode, measurements, lowerFence, upperFence, out sum, out n);
|
|
mean = sum / n;
|
|
variance = VarianceWithoutOutliers(outlierMode, measurements, n, mean, lowerFence, upperFence);
|
|
standardDeviation = Math.Sqrt(variance);
|
|
standardError = standardDeviation / Math.Sqrt(n);
|
|
marginOfError = n <= 2 ? double.NaN : standardError * confidenceLevel.GetZValue(n);
|
|
|
|
return new MeasurementsStatistics(mean, marginOfError);
|
|
}
|
|
|
|
static double Sum(List<double> measurements)
|
|
{
|
|
var sum = 0d;
|
|
foreach (var m in measurements)
|
|
{
|
|
sum += m;
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
|
|
static void SumWithoutOutliers(OutlierMode outlierMode, List<double> measurements,
|
|
double lowerFence, double upperFence, out double sum, out int n)
|
|
{
|
|
sum = 0;
|
|
n = 0;
|
|
|
|
foreach (var m in measurements)
|
|
{
|
|
if (!IsOutlier(outlierMode, m, lowerFence, upperFence))
|
|
{
|
|
sum += m;
|
|
++n;
|
|
}
|
|
}
|
|
}
|
|
|
|
static double Variance(List<double> measurements, int n, double mean)
|
|
{
|
|
if (n == 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
double variance = 0;
|
|
foreach (var m in measurements)
|
|
{
|
|
variance += (m - mean) * (m - mean) / (n - 1);
|
|
}
|
|
|
|
return variance;
|
|
}
|
|
|
|
static double VarianceWithoutOutliers(OutlierMode outlierMode, List<double> measurements, int n, double mean, double lowerFence, double upperFence)
|
|
{
|
|
if (n == 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
double variance = 0;
|
|
foreach (var m in measurements)
|
|
{
|
|
if (!IsOutlier(outlierMode, m, lowerFence, upperFence))
|
|
{
|
|
variance += (m - mean) * (m - mean) / (n - 1);
|
|
}
|
|
}
|
|
|
|
return variance;
|
|
}
|
|
|
|
static double GetQuartile(List<double> measurements, int count)
|
|
{
|
|
if (count % 2 == 0)
|
|
{
|
|
return (measurements[count / 2 - 1] + measurements[count / 2]) / 2;
|
|
}
|
|
|
|
return measurements[count / 2];
|
|
}
|
|
|
|
static bool IsOutlier(OutlierMode outlierMode, double value, double lowerFence, double upperFence)
|
|
{
|
|
switch (outlierMode)
|
|
{
|
|
case OutlierMode.DontRemove:
|
|
return false;
|
|
case OutlierMode.Remove:
|
|
return value < lowerFence || value > upperFence;
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(outlierMode), outlierMode, "Unknown OutlierMode value.");
|
|
}
|
|
}
|
|
}
|
|
}
|