using System.Collections.Generic;
namespace MedianMADExample
public class WTJobTypeLabel
public string JobTypeLabel { get; set; }
public Guid MasterID { get; set; }
public int Count { get; set; }
public class JobTypeLabelType
public string JobTypeLabel { get; set; }
public bool NonWater { get; set; }
public static readonly List<JobTypeLabelType> JobTypeLabelList = new List<JobTypeLabelType>
{ new JobTypeLabelType { JobTypeLabel = "Water Mitigation", NonWater = false } },
{ new JobTypeLabelType { JobTypeLabel = "Rebuild", NonWater = true } },
{ new JobTypeLabelType { JobTypeLabel = "Mold", NonWater = true } },
{ new JobTypeLabelType { JobTypeLabel = "Asbestos", NonWater = true } },
{ new JobTypeLabelType { JobTypeLabel = "Abatement", NonWater = true } },
{ new JobTypeLabelType { JobTypeLabel = "General", NonWater = true } }
static void Main(string[] args)
Guid masterId1 = Guid.Parse("11111111-1111-1111-1111-111111111111");
Guid masterId2 = Guid.Parse("22222222-2222-2222-2222-222222222222");
Guid masterId3 = Guid.Parse("22222222-2222-2222-2222-222222222223");
var data = new List<WTJobTypeLabel>
new WTJobTypeLabel { JobTypeLabel = "LabelA", MasterID = masterId1, Count = 2 },
new WTJobTypeLabel { JobTypeLabel = "LabelB", MasterID = masterId1, Count = 5 },
new WTJobTypeLabel { JobTypeLabel = "LabelC", MasterID = masterId1, Count = 12 },
new WTJobTypeLabel { JobTypeLabel = "LabelD", MasterID = masterId1, Count = 0 },
new WTJobTypeLabel { JobTypeLabel = "LabelE", MasterID = masterId1, Count = 100 },
new WTJobTypeLabel { JobTypeLabel = "LabelF", MasterID = masterId1, Count = 1 },
new WTJobTypeLabel { JobTypeLabel = "LabelG", MasterID = masterId1, Count = 250 },
new WTJobTypeLabel { JobTypeLabel = "LabelH", MasterID = masterId1, Count = 9 },
new WTJobTypeLabel { JobTypeLabel = "LabelI", MasterID = masterId1, Count = 300 },
new WTJobTypeLabel { JobTypeLabel = "LabelJ", MasterID = masterId1, Count = 50 },
new WTJobTypeLabel { JobTypeLabel = "LabelK", MasterID = masterId2, Count = 7 },
new WTJobTypeLabel { JobTypeLabel = "LabelL", MasterID = masterId2, Count = 8 },
new WTJobTypeLabel { JobTypeLabel = "LabelM", MasterID = masterId2, Count = 24 },
new WTJobTypeLabel { JobTypeLabel = "LabelN", MasterID = masterId2, Count = 4 },
new WTJobTypeLabel { JobTypeLabel = "LabelO", MasterID = masterId2, Count = 20 },
new WTJobTypeLabel { JobTypeLabel = "LabelP", MasterID = masterId2, Count = 10 },
new WTJobTypeLabel { JobTypeLabel = "LabelQ", MasterID = masterId2, Count = 2 },
new WTJobTypeLabel { JobTypeLabel = "LabelR", MasterID = masterId2, Count = 4 },
new WTJobTypeLabel { JobTypeLabel = "LabelS", MasterID = masterId2, Count = 1 },
new WTJobTypeLabel { JobTypeLabel = "LabelT", MasterID = masterId2, Count = 4 },
new WTJobTypeLabel { JobTypeLabel = "LabelK", MasterID = masterId3, Count = 1 },
new WTJobTypeLabel { JobTypeLabel = "LabelL", MasterID = masterId3, Count = 2 },
new WTJobTypeLabel { JobTypeLabel = "LabelM", MasterID = masterId3, Count = 4 },
new WTJobTypeLabel { JobTypeLabel = "LabelN", MasterID = masterId3, Count = 3 },
new WTJobTypeLabel { JobTypeLabel = "LabelO", MasterID = masterId3, Count = 5 },
new WTJobTypeLabel { JobTypeLabel = "LabelP", MasterID = masterId3, Count = 7 },
new WTJobTypeLabel { JobTypeLabel = "LabelQ", MasterID = masterId3, Count = 6 },
new WTJobTypeLabel { JobTypeLabel = "LabelR", MasterID = masterId3, Count = 100 },
new WTJobTypeLabel { JobTypeLabel = "LabelS", MasterID = masterId3, Count = 1000 },
new WTJobTypeLabel { JobTypeLabel = "LabelS", MasterID = masterId3, Count = 900 },
Console.WriteLine("=== Filtered & Sorted Results for MasterID #1 ===");
List<WTJobTypeLabel> filteredForMaster1 = GetSmartFilteredJobTypeLabelsWithMAD(data, masterId1);
PrintResults(filteredForMaster1);
Console.WriteLine("=== Filtered & Sorted Results for MasterID #1 ===");
List<WTJobTypeLabel> filteredForMaster2 = GetSmartFilteredJobTypeLabels2(data, masterId1);
PrintResults(filteredForMaster2);
Console.WriteLine("=== Filtered & Sorted Results for MasterID #1 ===");
List<WTJobTypeLabel> filteredForMaster22 = GetSmartFilteredJobTypeLabels3(data, masterId1);
PrintResults(filteredForMaster22);
Console.WriteLine("=== Filtered & Sorted Results for MasterID #2 ===");
List<WTJobTypeLabel> filteredForMaster3 = GetSmartFilteredJobTypeLabelsWithMAD(data, masterId2);
PrintResults(filteredForMaster3);
Console.WriteLine("=== Filtered & Sorted Results for MasterID #2 ===");
List<WTJobTypeLabel> filteredForMaster4 = GetSmartFilteredJobTypeLabels2(data, masterId2);
PrintResults(filteredForMaster4);
Console.WriteLine("=== Filtered & Sorted Results for MasterID #2 ===");
List<WTJobTypeLabel> filteredForMaster44 = GetSmartFilteredJobTypeLabels3(data, masterId2);
PrintResults(filteredForMaster44);
Console.WriteLine("=== Filtered & Sorted Results for MasterID #3 ===");
List<WTJobTypeLabel> filteredForMaster333 = GetSmartFilteredJobTypeLabels3(data, masterId3);
PrintResults(filteredForMaster333);
Console.WriteLine("=== Filtered & Sorted Results for MasterID #3 ===");
List<WTJobTypeLabel> filteredForMaster3333 = GetSmartFilteredJobTypeLabelsWithIQR(data, masterId3);
PrintResults(filteredForMaster3333);
public static List<WTJobTypeLabel> GetSmartFilteredJobTypeLabelsWithMAD(
List<WTJobTypeLabel> allData,
.Where(label => label.MasterID == masterId)
.OrderByDescending(label => label.Count)
if (rawResults.Count <= 1)
return rawResults.Take(10).ToList();
var sortedCounts = rawResults
.Select(r => (double)r.Count)
double median = GetMedian(sortedCounts);
var absoluteDeviations = sortedCounts
.Select(c => Math.Abs(c - median))
double mad = GetMedian(absoluteDeviations);
double scaledMAD = mad * 1.4826;
Console.WriteLine(median);
Console.WriteLine(absoluteDeviations);
Console.WriteLine(scaledMAD);
double lowerCutoff = median - 2.0 * scaledMAD;
var filteredResults = rawResults
.Where(r => r.Count >= lowerCutoff)
return filteredResults.Take(10).ToList();
public static List<WTJobTypeLabel> GetSmartFilteredJobTypeLabels2(
List<WTJobTypeLabel> allData,
.Where(label => label.MasterID == masterId)
.OrderByDescending(label => label.Count)
if (rawResults.Count <= 1)
return rawResults.Take(10).ToList();
var counts = rawResults.Select(r => (double)r.Count).ToArray();
double mean = counts.Average();
double sumOfSquares = counts.Sum(v => Math.Pow(v - mean, 2));
double variance = sumOfSquares / (counts.Length - 1);
double stdDev = Math.Sqrt(variance);
double lowerCutoff = mean - 1.5 * stdDev;
var filteredResults = rawResults
.Where(r => r.Count >= lowerCutoff)
return filteredResults.Take(10).ToList();
private static double GetMedian(List<double> sortedValues)
int count = sortedValues.Count;
if (count == 0) return 0.0;
return (sortedValues[count / 2 - 1] + sortedValues[count / 2]) / 2.0;
return sortedValues[count / 2];
private static void PrintResults(List<WTJobTypeLabel> results)
foreach (var item in results)
$"Label: {item.JobTypeLabel}, MasterID: {item.MasterID}, Count: {item.Count}");
public static List<WTJobTypeLabel> GetSmartFilteredJobTypeLabels3(
List<WTJobTypeLabel> allData,
.Where(label => label.MasterID == masterId)
.OrderByDescending(label => label.Count)
if (rawResults.Count <= 1)
var ascendingCounts = rawResults
.Select(r => (double) r.Count)
var highOutlierThreshold = GetHighOutlierThresholdLogIQR(ascendingCounts, 1.5);
var highOutlierIndices = new HashSet<int>();
for (int i = 0; i < rawResults.Count; i++)
if (rawResults[i].Count > highOutlierThreshold)
highOutlierIndices.Add(i);
int firstNonOutlierIndex = Enumerable.Range(0, rawResults.Count)
.FirstOrDefault(idx => !highOutlierIndices.Contains(idx));
if (firstNonOutlierIndex < rawResults.Count)
ratioMax = rawResults[firstNonOutlierIndex].Count;
ratioMax = rawResults[0].Count;
double cutoff = fraction * ratioMax;
var filtered = rawResults
highOutlierIndices.Contains(index) || (item.Count >= cutoff)
return filtered.Take(10).ToList();
private static double GetHighOutlierThresholdLogIQR(List<double> sortedValues, double kIqrMultiplier)
if (sortedValues == null || sortedValues.Count == 0)
var logValues = sortedValues.Select(v => Math.Log(v + 1.0)).ToList();
double q1 = GetPercentile(logValues, 0.25);
double q3 = GetPercentile(logValues, 0.75);
double upperFenceLog = q3 + kIqrMultiplier * iqr;
double threshold = Math.Exp(upperFenceLog) - 1.0;
private static double GetPercentile(List<double> sortedValues, double p)
if (sortedValues == null || sortedValues.Count == 0) return 0;
if (p <= 0) return sortedValues.First();
if (p >= 1) return sortedValues.Last();
double n = sortedValues.Count;
double pos = (n - 1) * p;
if (i + 1 < sortedValues.Count)
return sortedValues[i] * (1 - frac) + sortedValues[i + 1] * frac;
public static List<WTJobTypeLabel> GetSmartFilteredJobTypeLabelsWithIQR(
List<WTJobTypeLabel> allData,
.Where(label => label.MasterID == masterId)
.OrderByDescending(label => label.Count)
if (rawResults.Count <= 1)
return rawResults.Take(10).ToList();
var sortedCountsAsc = rawResults
.Select(r => (double)r.Count)
var iqr = GetIQR(sortedCountsAsc, out double q1, out double q3);
double lowerFence = q1 - 1.5 * iqr;
var lowOutlierCounts = sortedCountsAsc
.Where(c => c < lowerFence)
double upperFence = q3 + 1.5 * iqr;
var highOutlierThreshold = upperFence;
var highOutlierCounts = sortedCountsAsc
.Where(c => c > upperFence)
var nonHighOutlierCounts = sortedCountsAsc
.Where(c => c <= upperFence)
double ratioMax = nonHighOutlierCounts.Any() ? nonHighOutlierCounts.Max() : rawResults.Max(r => r.Count);
double cutoff = fraction * ratioMax;
var filtered = rawResults
r.Count > upperFence || r.Count >= cutoff
private static double GetIQR(List<double> sortedValues, out double q1, out double q3)
q1 = GetPercentile(sortedValues, 0.25);
q3 = GetPercentile(sortedValues, 0.75);