LINQ में Standard Deviation


80

LINQ क्या कुल एसक्यूएल फ़ंक्शन STDDEV() (मानक विचलन) को मॉडल करता है ?

यदि नहीं, तो इसकी गणना करने का सबसे सरल / सर्वोत्तम तरीका क्या है?

उदाहरण:

  SELECT test_id, AVERAGE(result) avg, STDDEV(result) std 
    FROM tests
GROUP BY test_id


@ सही, आप यहां दिए गए उत्तर को फिर से देखना पसंद कर सकते हैं। वर्तमान में चयनित दृष्टिकोण के साथ समस्याएं हैं जो लोग स्क्रॉल नहीं करते हैं और आगे पढ़ते हैं वे नहीं देख सकते हैं।
ड्रू नोकें

LINQ का उपयोग करके कोई भी ऐसा क्यों करना चाहेगा ?
Ant_222

जवाबों:


98

आप अपने खुद के विस्तार की गणना कर सकते हैं

public static class Extensions
{
    public static double StdDev(this IEnumerable<double> values)
    {
       double ret = 0;
       int count = values.Count();
       if (count  > 1)
       {
          //Compute the Average
          double avg = values.Average();

          //Perform the Sum of (value-avg)^2
          double sum = values.Sum(d => (d - avg) * (d - avg));

          //Put it all together
          ret = Math.Sqrt(sum / count);
       }
       return ret;
    }
}

यदि आपके पास पूरी आबादी के बजाय आबादी का एक नमूना है, तो आपको उपयोग करना चाहिए ret = Math.Sqrt(sum / (count - 1));

क्रिस बेनेट द्वारा LINQ में मानक विचलन जोड़ने से विस्तार में परिवर्तित ।


3
मैं उस परीक्षण को "मान। मान" (1>) कहूंगा, क्योंकि यदि यह ठीक 1 है तो आपके पास वापसी मूल्य की गणना करते समय शून्य त्रुटि से विभाजित होगा।
duffymo

3
गणित। Pow (d-avg, 2)? मैं फ़ंक्शन कॉल और उपयोग (d-avg) * (d-avg) को छोड़
दूंगा

2
लाइन रिट = Math.Sqrt ((sum) / मान.काउंट () - 1); मानों के आसपास कोष्ठक याद कर रहा है ।ाउंट () - 1, यह रिटेन होना चाहिए = Math.Sqrt (योग / (मान.काउंट -) - 1));
एलेक्स पेक

1
मैं इस लिए खोज रहा था और यह मुझे कैसे एक्सटेंशन का उपयोग करने पता लगाने की कुछ समय लिया, लेकिन यहां जिस तरह से ऊपर दिए गए तरीकों को लागू करने के लिए है: stdev = g.Select(o => o.number).StdDev()
एंड्रयू माओ

2
@ यवगेनी रोझकोव - आपने क्यों हटाया - 1? के अनुसार इस- 1 की आवश्यकता है।
जॉन मिल्स

61

डायनेमी का उत्तर काम करता है लेकिन परिणाम प्राप्त करने के लिए डेटा से कई गुजरता है। यह एकल पास विधि है जो नमूना मानक विचलन की गणना करती है :

public static double StdDev(this IEnumerable<double> values)
{
    // ref: http://warrenseen.com/blog/2006/03/13/how-to-calculate-standard-deviation/
    double mean = 0.0;
    double sum = 0.0;
    double stdDev = 0.0;
    int n = 0;
    foreach (double val in values)
    {
        n++;
        double delta = val - mean;
        mean += delta / n;
        sum += delta * (val - mean);
    }
    if (1 < n)
        stdDev = Math.Sqrt(sum / (n - 1));

    return stdDev;
}

यह नमूना मानक विचलन है क्योंकि यह विभाजित होता है n - 1। सामान्य मानक विचलन के लिए आपको nइसके बजाय विभाजित करने की आवश्यकता होती है ।

यह Welford की विधि का उपयोग करता है जिसमें विधि की तुलना में उच्च संख्यात्मक सटीकता है Average(x^2)-Average(x)^2


1
आपने पूरे अनुक्रम को एक से अधिक बार पुनरावृत्त नहीं किया होगा, लेकिन आपका तरीका अभी भी GetEnumerator (जो एक जटिल SQL क्वेरी को ट्रिगर कर सकता है) को दो कॉल करेगा। स्थिति को क्यों न छोड़ें और लूप के अंत में n जांचें?
गिदोन एंगेलबर्ट

धन्यवाद गिदोन, घोंसले के शिकार के स्तर को भी हटा देता है। आप एसक्यूएल के बारे में सही हैं, यह प्रासंगिक नहीं है कि मैं क्या काम कर रहा हूं इसलिए मैंने निहितार्थ पर विचार नहीं किया था।
डेविड क्लार्क

3
आपको n की परिभाषा याद आ रही है। यह भी ध्यान दिया जाना चाहिए कि n के बजाय (n-1) के योग को विभाजित करने से यह एक नमूना मानक विचलन हो जाता है
नील

3
इसे और ध्यान से बनाने के लिए SQL मेथड को दोहराएं, मैं बदल गया this IEnumerable<double?> valuesऔर val in values.Where(val => val != null)। इसके अलावा, मैं ध्यान दूंगा कि यह विधि (Welford का तरीका) ऊपर की विधि से अधिक सटीक और तेज है।
एंड्रयू माओ

2
मैंने आपके उत्तर को यह स्पष्ट करने के लिए संपादित किया है कि आप नमूना मानक विचलन की गणना कर रहे हैं , सामान्य मानक विचलन की नहीं
कोडइन्चौस

31

यह डेविड क्लार्क के उत्तर को एक ऐसे विस्तार में परिवर्तित करता है जो अन्य एग्रीगेट LINQ फ़ंक्शंस की तरह ही फॉर्म का अनुसरण करता है।

उपयोग होगा: var stdev = data.StdDev(o => o.number)

public static class Extensions
{
    public static double StdDev<T>(this IEnumerable<T> list, Func<T, double> values)
    {
        // ref: /programming/2253874/linq-equivalent-for-standard-deviation
        // ref: http://warrenseen.com/blog/2006/03/13/how-to-calculate-standard-deviation/ 
        var mean = 0.0;
        var sum = 0.0;
        var stdDev = 0.0;
        var n = 0;
        foreach (var value in list.Select(values))
        {
            n++;
            var delta = value - mean;
            mean += delta / n;
            sum += delta * (value - mean);
        }
        if (1 < n)
            stdDev = Math.Sqrt(sum / (n - 1));

        return stdDev; 

    }
} 

1
ध्यान दें कि Average/ Min/ Max/ etc का चयनकर्ता कार्यों के साथ और बिना ओवरलोड है। उनके पास अभिन्न प्रकार, फ्लोट आदि के लिए ओवरलोड भी हैं
ड्रू नोक


2

सीधे बिंदु पर (और C #> 6.0), डायनामिस का उत्तर यह बन जाता है:

    public static double StdDev(this IEnumerable<double> values)
    {
        var count = values?.Count() ?? 0;
        if (count <= 1) return 0;

        var avg = values.Average();
        var sum = values.Sum(d => Math.Pow(d - avg, 2));

        return Math.Sqrt(sum / count);
    }

संपादित करें 2020-08-27:

मैंने कुछ प्रदर्शन परीक्षण करने के लिए @ डेविड क्लार्क की टिप्पणियाँ लीं और यह परिणाम हैं:

    public static (double stdDev, double avg) StdDevFast(this List<double> values)
    {
        var count = values?.Count ?? 0;
        if (count <= 1) return (0, 0);

        var avg = GetAverage(values);
        var sum = GetSumOfSquareDiff(values, avg);

        return (Math.Sqrt(sum / count), avg);
    }

    private static double GetAverage(List<double> values)
    {
        double sum = 0.0;
        for (int i = 0; i < values.Count; i++) 
            sum += values[i];
        
        return sum / values.Count;
    }
    private static double GetSumOfSquareDiff(List<double> values, double avg)
    {
        double sum = 0.0;
        for (int i = 0; i < values.Count; i++)
        {
            var diff = values[i] - avg;
            sum += diff * diff;
        }
        return sum;
    }

मैंने इसे एक मिलियन रैंडम डबल्स
की एक सूची के साथ परीक्षण किया था मूल कार्यान्वयन में ~ 48ms
का रनटाइम था प्रदर्शन को अनुकूलित कार्यान्वयन 2-3ms
तो यह एक महत्वपूर्ण सुधार है।

कुछ दिलचस्प विवरण:
Math.Pow से छुटकारा पाने से 33ms की वृद्धि होती है!
IEnumerable 6ms की बजाय
मैन्युअल रूप से सूची करें। औसत गणना 4ms
फॉर-लूप के बजाय फॉरेस्ट-लूप्स 2ms
एरे के बजाय लिस्ट में केवल ~ 2% का सुधार लाता है इसलिए मैंने इसे
डबल के बजाय सिंगल का उपयोग करके छोड़ दिया है।

इसके अलावा कोड को कम करना और गोटो (हाँ गोटो ... 90 के दशक के बाद से इसका इस्तेमाल नहीं किया है ...) के बजाय-छोरों के लिए भुगतान नहीं करता है, धन्यवाद!

मैंने समानांतर गणना का भी परीक्षण किया है, यह सूची> 200.000 वस्तुओं पर समझ में आता है। ऐसा लगता है कि हार्डवेयर और सॉफ्टवेयर को बहुत अधिक प्रारंभिक करने की आवश्यकता है और यह छोटी सूची के लिए है जो उत्पादक-उत्पादक है।

वार्मअप-टाइम से छुटकारा पाने के लिए सभी परीक्षणों को लगातार दो बार निष्पादित किया गया।


ध्यान रखें इस डेटा के माध्यम से कई गुजरता बनाता है जब मूल्यांकन कर रहो Count(), Average()और Sum()। यह छोटे मूल्यों के लिए ठीक है, countलेकिन अगर प्रदर्शन countबड़ा है, तो प्रदर्शन को प्रभावित करने की क्षमता है।
डेविड क्लार्क

@ डेविड, इसलिए मेरी राय में सबसे सरल समाधान यह होगा कि हस्ताक्षर को बदलने के लिए (this IList<double> values), प्रदर्शन परीक्षण प्रभाव दिखाएगा, और कितने आइटम एक महत्वपूर्ण अंतर बनाते हैं
अर्नस्ट ग्रीनर

हाँ है कि इस मुद्दे को हल नहीं करता है - उन विस्तार के तरीकों ( Count, Average, Sum) प्रत्येक पुनरावृति संग्रह ताकि आप अभी भी तीन पूर्ण पुनरावृत्तियों एक परिणाम का उत्पादन किया है।
डेविड क्लार्क

0
public static double StdDev(this IEnumerable<int> values, bool as_sample = false)
{
    var count = values.Count();
    if (count > 0) // check for divide by zero
    // Get the mean.
    double mean = values.Sum() / count;

    // Get the sum of the squares of the differences
    // between the values and the mean.
    var squares_query =
        from int value in values
        select (value - mean) * (value - mean);
    double sum_of_squares = squares_query.Sum();
    return Math.Sqrt(sum_of_squares / (count - (as_sample ? 1 : 0)))
}

ध्यान दें कि यह अभी भी डेटा के माध्यम से कई पास कर रहा है - ठीक है अगर एक छोटा डेटासेट लेकिन बड़े मूल्यों के लिए अच्छा नहीं है count
डेविड क्लार्क

0

सरल 4 लाइनों, मैंने डबल्स की एक सूची का उपयोग किया लेकिन एक का उपयोग कर सकता है IEnumerable<int> values

public static double GetStandardDeviation(List<double> values)
{
    double avg = values.Average();
    double sum = values.Sum(v => (v - avg) * (v - avg));
    double denominator = values.Count - 1;
    return denominator > 0.0 ? Math.Sqrt(sum / denominator) : -1;
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.