【问题标题】:Swift Array extension for standard deviation标准差的 Swift 数组扩展
【发布时间】:2016-11-20 04:49:33
【问题描述】:

我经常需要计算数值数组的均值和标准差。所以我为数字类型编写了一个小协议和扩展,似乎可以工作。如果我这样做有什么问题,我只想得到反馈。具体来说,我想知道是否有更好的方法来检查类型是否可以转换为 Double 以避免需要 asDouble 变量和 init(_:Double) 构造函数。

我知道允许算术的协议存在问题,但这似乎工作正常,并且使我不必将标准差函数放入需要它的类中。

protocol Numeric {
    var asDouble: Double { get }
    init(_: Double)
}

extension Int: Numeric {var asDouble: Double { get {return Double(self)}}}
extension Float: Numeric {var asDouble: Double { get {return Double(self)}}}
extension Double: Numeric {var asDouble: Double { get {return Double(self)}}}
extension CGFloat: Numeric {var asDouble: Double { get {return Double(self)}}}

extension Array where Element: Numeric {

    var mean : Element { get { return Element(self.reduce(0, combine: {$0.asDouble + $1.asDouble}) / Double(self.count))}}

    var sd : Element { get {
        let mu = self.reduce(0, combine: {$0.asDouble + $1.asDouble}) / Double(self.count)
        let variances = self.map{pow(($0.asDouble - mu), 2)}
        return Element(sqrt(variances.mean))
    }}
}

编辑:我知道获取[Int].meansd 是没有意义的,但我可能会在其他地方使用数字,所以这是为了保持一致性..

编辑:正如 @Severin Pappadeux 所指出的,方差可以通过避免数组上的三次传递的方式表示 - 均值然后映射然后均值。这是最终的标准差扩展

extension Array where Element: Numeric {

    var sd : Element { get {
        let sss = self.reduce((0.0, 0.0)){ return ($0.0 + $1.asDouble, $0.1 + ($1.asDouble * $1.asDouble))}
        let n = Double(self.count)
        return Element(sqrt(sss.1/n - (sss.0/n * sss.0/n)))
    }}
}

【问题讨论】:

  • Int 通常与较新设备上的Int64 大小相同(>= iPhone 5S,它引入了 64 位处理器),因此除非您处理非常大的数字,否则应该这样做这不是问题:但只要知道init(_: Double) 可能导致整数溢出(运行时异常),在Element = Int 类型无法存储给定(巨大)Double 值的整数表示的情况下。如果您只是自己使用 Swift 应用程序,这可能不是问题,但如果您向客户发货,请记住这一点。
  • 好的,很有趣,谢谢。我不太可能将它与整数一起使用,并且我正在使用的值在生理上被限制在此应用程序中
  • @dfri 非常有用的评论!我想没有办法“捕捉”这种溢出?
  • @matt 谢谢!我想我们可以在Numeric 中包含一个静态minmax 属性并检查双重表示(假设所有数值都可以被视为有效@987654336 范围的子集) @ 值;即,始终可以转换为 Double,而没有任何溢出风险,但我猜在最坏的情况下,我们会得到此属性的 Double.infinity) 与上面 reduce 操作中的 Double 值总和。例如。某事along these lines.
  • @dfri 我可能是错的,但是通过阅读 Swift 3 中的新 FloatingPoint 协议,我认为它可能会为您节省一些工作。 — 有趣的是,如何在添加两个 Int 时“捕捉”溢出(通过使用特殊运算符),但在强制为 Double 时却无法“捕捉”它。

标签: arrays swift mean numeric deviation


【解决方案1】:

只是提醒一下,但是当我测试 Severin Pappadeux 概述的代码时,结果是“总体标准差”而不是“样本标准差”。您可以在 100% 的相关数据可供您使用的情况下使用第一个,例如当您计算班级中所有 20 名学生的平均成绩的方差时。如果您无法普遍访问所有相关数据,您将使用第二种方法,并且必须从小得多的样本中估计方差,例如估计一个大国内所有男性的身高。

总体标准差通常表示为 StDevP。我使用的 Swift 5.0 代码如下所示。请注意,这不适用于非常大的数组,因为总和变大时会丢失“小值”位。特别是当方差接近于零时,您可能会遇到运行时错误。对于如此严肃的工作,您可能必须引入一种称为compensated summation 的算法

import Foundation

extension Array where Element: FloatingPoint
{

    var sum: Element {
        return self.reduce( 0, + )
    }

    var average: Element {
        return self.sum / Element( count )
    }

    /**
     (for a floating point array) returns a tuple containing the average and the "standard deviation for populations"
     */
    var averageAndStandardDeviationP: ( average: Element, stDevP: Element ) {

        let sumsTuple = sumAndSumSquared

        let populationSize = Element( count )
        let average = sumsTuple.sum / populationSize

        let expectedXSquared = sumsTuple.sumSquared / populationSize
        let variance = expectedXSquared - (average * average )

        return ( average, sqrt( variance ) )
    }

    /**
     (for a floating point array) returns a tuple containing the sum of all the values and the sum of all the values-squared
     */
    private var sumAndSumSquared: ( sum: Element, sumSquared: Element ) {
        return self.reduce( (Element(0), Element(0) ) )
        {
            ( arg0, x) in
            let (sumOfX, sumOfSquaredX) = arg0
            return ( sumOfX + x, sumOfSquaredX + ( x * x ) )
        }
    }
}

【讨论】:

    【解决方案2】:

    为了跟进 Matt 的观察,我会在 FloatingPoint 上执行主要算法,同时处理 DoubleFloatCGFloat 等。但然后我会在 @ 上做另一个排列987654325@,负责处理所有整数类型。

    例如在FloatingPoint:

    extension Array where Element: FloatingPoint {
    
        /// The mean average of the items in the collection.
    
        var mean: Element { return reduce(Element(0), +) / Element(count) }
    
        /// The unbiased sample standard deviation. Is `nil` if there are insufficient number of items in the collection.
    
        var stdev: Element? {
            guard count > 1 else { return nil }
    
            return sqrt(sumSquaredDeviations() / Element(count - 1))
        }
    
        /// The population standard deviation. Is `nil` if there are insufficient number of items in the collection.
    
        var stdevp: Element? {
            guard count > 0 else { return nil }
    
            return sqrt(sumSquaredDeviations() / Element(count))
        }
    
        /// Calculate the sum of the squares of the differences of the values from the mean
        ///
        /// A calculation common for both sample and population standard deviations.
        ///
        /// - calculate mean
        /// - calculate deviation of each value from that mean
        /// - square that
        /// - sum all of those squares
    
        private func sumSquaredDeviations() -> Element {
            let average = mean
            return map {
                let difference = $0 - average
                return difference * difference
            }.reduce(Element(0), +)
        }
    }
    

    然后在BinaryInteger:

    extension Array where Element: BinaryInteger {
        var mean: Double { return map { Double(exactly: $0)! }.mean }
        var stdev: Double? { return map { Double(exactly: $0)! }.stdev }
        var stdevp: Double? { return map { Double(exactly: $0)! }.stdevp }
    }
    

    注意,在我的场景中,即使在处理整数输入数据时,我一般也想要浮点mean和标准差,所以我随意选择了Double。您可能想要更安全地解开Double(exactly:)。您可以以任何方式处理这种情况。但它说明了这个想法。

    【讨论】:

      【解决方案3】:

      带有浮点元素的 Swift 4 数组扩展:

      extension Array where Element: FloatingPoint {
      
          func sum() -> Element {
              return self.reduce(0, +)
          }
      
          func avg() -> Element {
              return self.sum() / Element(self.count)
          }
      
          func std() -> Element {
              let mean = self.avg()
              let v = self.reduce(0, { $0 + ($1-mean)*($1-mean) })
              return sqrt(v / (Element(self.count) - 1))
          }
      
      }
      

      【讨论】:

      【解决方案4】:

      实际上已经有一个类提供了这个功能——叫做NSExpression。您可以改用它来减少代码大小和复杂性。这个类有很多东西,但是你想要的一个简单的实现如下。

      let expression = NSExpression(forFunction: "stddev:", arguments: [NSExpression(forConstantValue: [1,2,3,4,5])])
      let standardDeviation = expression.expressionValueWithObject(nil, context: nil)
      

      您也可以计算均值,等等。信息在这里:http://nshipster.com/nsexpression/

      【讨论】:

      • 如果您打算移植到 Linux,请小心 - 那里没有实现 NSExpression。
      【解决方案5】:

      不是我知道 Swift,而是从数字 POV 来看,你这样做效率有点低

      基本上,您在数组上执行两遍(实际上是三遍)来计算两个值,其中一遍就足够了。方差可以表示为 E(X2) - E(X)2,所以在一些伪代码中:

      tuple<float,float> get_mean_sd(data) {
          float s  = 0.0f;
          float s2 = 0.0f;
          for(float v: data) {
              s  += v;
              s2 += v*v;
          }
          s  /= count;
          s2 /= count;
      
          s2 -= s*s;
          return tuple(s, sqrt(s2 > 0.0 ? s2 : 0.0));
      }
      

      【讨论】:

      • 你是对的。谢谢,这确实避免了三次通过。
      • @twiz_ 不客气,不过我很好奇它是否可以通过reduce()表达出来
      • 知道了:let s = self.reduce((0.0, 0.0)){ return ($0.0 + $1.asDouble, $0.1 + ($1.asDouble * $1.asDouble))} 然后 s.1/n - s.0/n * s.0/n。对不起,可怕的格式。新手。
      【解决方案6】:

      在 Swift 3 中,您可能(也可能不会)使用 FloatingPoint 协议为自己省去一些重复,但除此之外,您所做的是完全正确的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-08-25
        • 2021-07-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-08
        相关资源
        最近更新 更多