6
\$\begingroup\$

I'm removing outliers from a set of data using the method described here.

The method removes the outliers and returns the quartile values of the set after all outliers have been removed.

(Quartiles is a class I wrote with properties for Q1, Q2, Q3, and the Inter-quartile range, IQR. The .Quartiles() returns these values for a sequence of decimals.)

public Quartiles RemoveOutliers(ICollection<MyObject> objects)
{
    if (objects.Count == 0)
    {
        return new Quartiles();
    }

    // Calculate Mean value of the set.
    decimal meanValue = objects.Average(o => o.MyProperty);

    // Find the Object whose value is farthest from the Mean.
    MyObject objectFarthestFromMean = objects.OrderByDescending(o => Math.Abs(o.MyProperty - meanValue)).First();
    Quartiles quartiles = objects.Quartiles(o => o.MyProperty);

    // Remove Object if its value is more than 1.5*IQR from the Mean.
    decimal minValue = meanValue - (1.5m * quartiles.IQR);
    decimal maxValue = meanValue + (1.5m * quartiles.IQR);

    if ((objectFarthestFromMean.MyProperty < minValue) || (objectFarthestFromMean.MyProperty > maxValue))
    {
        objects.Remove(objectFarthestFromMean);

        return RemoveOutliers(objects);
    }

    // No outlier found, we're finished.
    return quartiles;
}

Looking for any ways to improve or fix any pitfalls I haven't encountered in testing.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ This code is broken or isn't your real code because this is not valid C# object.OrderByDescending(..) besides MyObject and MyProperty also indicate it's not real, am I right? \$\endgroup\$
    – t3chb0t
    Commented Apr 25, 2018 at 17:14
  • \$\begingroup\$ Yes the class and property names were made generic for this. It should be objects.OrderByDescending(...), though. Good catch. \$\endgroup\$
    – Valuator
    Commented Apr 25, 2018 at 17:52
  • 3
    \$\begingroup\$ I have rolledback your last edit. Please do not change the code after receiving answers. It invalidates them. \$\endgroup\$
    – t3chb0t
    Commented Apr 26, 2018 at 6:16

2 Answers 2

3
\$\begingroup\$

You used Math.Abs(o.MyProperty - meanValue) to check both upper and lower cases.

But then you do specify minValue and maxValue separately. You could apply the same logic here:

decimal maxRange = (1.5m * quartiles.IQR);

if (Math.Abs(objectFarthestFromMean.MyProperty - meanValue) > maxRange)
{

Other than that, the calculation logic seems about as efficient as it can be (keeping readability in mind).

\$\endgroup\$
1
  • \$\begingroup\$ Code updated with your suggestion. \$\endgroup\$
    – Valuator
    Commented Apr 25, 2018 at 22:12
2
\$\begingroup\$

You should be able to remove the recursion rather easily and keep a readable iterative version like so:

public Quartiles RemoveOutliers(ICollection<MyObject> objects)
{
    while (objects.Count > 0)
    {
        // Calculate Mean value of the set.
        decimal meanValue = objects.Average(o => o.MyProperty);

        // Find the Object whose value is farthest from the Mean.
        MyObject objectFarthestFromMean = objects.OrderByDescending(o => Math.Abs(o.MyProperty - meanValue)).First();
        Quartiles quartiles = objects.Quartiles(o => o.MyProperty);

        // Remove Object if its value is more than 1.5*IQR from the Mean.
        decimal maxRange = 1.5m * quartiles.IQR;

        if (Math.Abs(objectFarthestFromMean.MyProperty - meanValue) <= maxRange)
        {
            // No outlier found, we're finished.
            return quartiles;
        }

        objects.Remove(objectFarthestFromMean);
    }

    return new Quartiles();
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.