【问题标题】:Returning the middle n (values not index) from a collection从集合中返回中间的 n(值不是索引)
【发布时间】:2011-08-08 01:00:22
【问题描述】:

我有一个List<int>,我需要删除异常值,所以我想使用一种只取中间 n 的方法。我想要中间的值,而不是索引。

例如,给定以下列表,如果我想要中间的 80%,我希望删除 11 和 100。

11,22,22,33,44,44,55,55,55,100。

在 LINQ 中是否有一种简单/内置的方法可以做到这一点?

【问题讨论】:

  • 当你想要集合 1,2,3 的中间 80% 时应该发生什么?结果会是 2 吗?
  • 这并没有让我感到震惊,因为它甚至接近统计上有效的去除异常值的方法。您的先验统计模型是什么?它是正态分布的吗?如果是这样,那么这种技术如何考虑三西格玛规则?这整件事让我觉得完全狡猾。为什么“11”在这里是异常值?如果您将这些数据绘制成图表,它似乎不会超出您想到的任何特定模型。你能告诉我们更多关于你在这里尝试做什么的细节吗?为什么?这看起来真的很奇怪。
  • @Eric:这当然不奇怪。例如,它可能是计算Truncated mean 的准备步骤。这是一种防止在较小集合中操纵平均值的常用技术 - 例如,它用于计算 LIBOR。
  • @Ani:好点子。也许“离奇”太强了。但是知道代码的目的是什么以及预期的分布是什么肯定会有所帮助。如果目标是在花样滑冰决赛中丢掉最低和最高分,我真的不在乎。如果被删除的异常值与制造过程或影响人类生命安全的决策有关,或者是某些财务计算的一部分,那么情况就更严重了。未能正确管理异常统计数据已经摧毁了公司。
  • @Jason:例子比比皆是。最近福岛核电站的大规模安全壳失效; 9 级地震并非之前假设的异常值。由于未能了解峰态风险而对长期资本管理公司进行救助。挑战者号航天飞机的毁坏是由于未能理解低温下对重要 O 环造成的漏气损坏并非异常。事实证明,即使是聪明的人也非常不善于理解异常值,结果导致人们死亡。

标签: c# linq algorithm


【解决方案1】:

我有一个List<int>,我需要删除异常值,所以我想使用一种只取中间 n 的方法。我想要中间的值,而不是索引。

正确去除异常值完全取决于准确描述数据分布的统计模型——您没有提供给我们。

假设它是一个正态(高斯)分布,这就是你想要做的。

首先计算 mean。这很简单;它只是总和除以项目数。

其次,计算标准差。标准差是衡量数据在均值附近的“分散”程度。计算方式:

  • 取每个点与平均值的差
  • 平方差
  • 取平方的平均值——这就是方差
  • 取方差的平方根 -- 这是标准差

在正态分布中,80% 的项目在均值的 1.2 个标准差范围内。因此,例如,假设平均值为 50,标准差为 20。您预计 80% 的样本将落在 50 - 1.2 * 20 和 50 + 1.2 * 20 之间。然后您可以从列表中过滤掉项目超出该范围。

但是请注意,这不是删除“异常值”。这是删除与平均值相差超过 1.2 个标准差的元素,以便在平均值附近获得 80% 的区间。在正态分布中,人们希望定期看到“异常值”。 99.73% 的项目在均值的三个标准差范围内,这意味着如果您有 1000 个观察值,看到两个或三个观察值超过均值之外的三个标准差是完全正常的!事实上,在给定一千个观测值的情况下,如果有 5 个观测值与平均值相差超过三个标准差,则可能并不表示异常值

我认为您需要非常仔细地定义 异常值 的含义,并描述您尝试消除它们的原因。看起来像异常值的事物可能根本不是异常值,它们是您应该关注的真实数据。

另外,请注意,如果正态分布不正确,则这些分析都不正确!当你实际上把整个统计模型弄错时,你可能会遇到很大的麻烦来消除看起来像异常值的东西。如果模型比正态分布更“重”,则异常值很常见,实际上并不是异常值。当心!如果您的分布不正常,那么您需要告诉我们分布是什么,然后我们才能推荐如何识别异常值并消除它们。

【讨论】:

  • 在stddev计算中不应该有另一个步骤:取平方的平均值,然后取平方根。
【解决方案2】:

您可以使用Enumerable.OrderBy 方法对列表进行排序,然后使用Enumerable.SkipEnumerable.Take 函数,例如:

var result = nums.OrderBy(x => x).Skip(1).Take(8);

nums 是您的整数列表。

如果您只想要“中间的n 值”,那么确定将哪些值用作SkipTake 的参数应该如下所示:

nums.OrderBy(x => x).Skip((nums.Count - n) / 2).Take(n);

但是,当(nums.Count - n) / 2 的结果不是整数时,您希望代码表现如何?

【讨论】:

    【解决方案3】:

    假设你没有做任何加权平均有趣的事情:

    List<int> ints = new List<int>() { 11,22,22,33,44,44,55,55,55,100 };
    
    int min = ints.Min();
    double range = (ints.Max() - min);
    
    var results = ints.Select(o => new { IntegralValue = o, Weight = (o - ints.Min()) / range} );
    
    results.Where(o => o.Weight >= .1 && o.Weight < .9);
    

    然后,您可以根据需要过滤权重。根据需要删除顶部/底部 n%。

    在你的情况下:

    results.Where(o => o.Weight >= .1 && o.Weight < .9)
    

    编辑:作为扩展方法,因为我喜欢扩展方法:

    public static class Lulz
    {
        public static List<int> MiddlePercentage(this List<int> ints, double Percentage)
        {
            int min = ints.Min();
            double range = (ints.Max() - min);
    
            var results = ints.Select(o => new { IntegralValue = o, Weight = (o - ints.Min()) / range} );
    
            double tolerance = (1 - Percentage) / 2;
            return results.Where(o => o.Weight >= tolerance && o.Weight < 1 - tolerance).Select(o => o.IntegralValue).ToList();
        }
    }
    

    用法:

    List<int> ints = new List<int>() { 11,22,22,33,44,44,55,55,55,100 };
    var results = ints.MiddlePercentage(.8);
    

    【讨论】:

      【解决方案4】:

      通常,如果您想从一组值中排除统计异常值,您需要计算该组的算术平均值和标准差,然后删除比您想要的距离平均值更远的值(以标准测量偏差)。正态分布(经典的钟形曲线)具有以下特性:

      • 大约 68% 的数据与平均值相差 +/- 1 个标准差。
      • 大约 95% 的数据与平均值相差 +/- 2 个标准差。
      • 大约 99.7% 的数据将位于平均值的 +/- 3 个标准差范围内。

      您可以在http://www.codeproject.com/KB/linq/LinqStatistics.aspx 获得用于计算标准差(和其他统计函数)的 Linq 扩展方法

      【讨论】:

        【解决方案5】:

        我不会质疑计算异常值的有效性,因为我也有类似的需要来进行这种选择。取中间n的具体问题的答案是:

        List<int> ints = new List<int>() { 11,22,22,33,44,44,55,55,55,100 };
        var result = ints.Skip(1).Take(ints.Count() - 2);
        

        这会跳过第一项,并在最后一项之前停止,只给你中间的 n 项。这是演示此查询的 .NET Fiddle 的链接。

        https://dotnetfiddle.net/p1z7em

        【讨论】:

          【解决方案6】:

          我有一个列表,我需要删除异常值,所以我想使用一种只取中间 n 的方法。我想要中间的值,而不是索引。

          如果我理解正确,我们希望保留任何落在 11-100 范围的中间 80% 的值,或者

          min + (max - min - (max - min) * 0.8) / 2 < x < max - (max - min - (max - min) * 0.8) / 2
          

          假设一个有序列表,我们可以 SkipWhile 值低于lowerBound,然后 TakeWhile 值低于upperBound

          public void Calculalte()
          {
              var numbers = new[] { 11, 22, 22, 33, 44, 44, 55, 55, 55, 100 };
          
              var percentage = 0.8;
          
              var result = RemoveOutliers(numbers, percentage);
          }
          
          private IEnumerable<int> RemoveOutliers(int[] numbers, double percentage)
          {
              int min = numbers.First();
              int max = numbers.Last();
              double range = (max - min);
              double lowerBound = min + (range - range * percentage) / 2;
              double upperBound = max - (range - range * percentage) / 2;
              return numbers.SkipWhile(n => n < lowerBound).TakeWhile(n => n < upperBound);   
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2022-01-09
            • 1970-01-01
            • 1970-01-01
            • 2011-03-19
            • 1970-01-01
            • 2023-03-31
            • 1970-01-01
            相关资源
            最近更新 更多