14

我经常需要计算数值数组的均值和标准差。所以我为数字类型编写了一个小协议和扩展,似乎可以工作。如果我这样做有什么问题,我只想得到反馈。具体来说,我想知道是否有更好的方法来检查类型是否可以转换为 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].meanand有点毫无意义sd,但我可能会在其他地方使用数字,所以这是为了保持一致性..

编辑:正如@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)))
    }}
}
4

6 回答 6

18

带有浮点元素的 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))
    }

}
于 2017-11-09T20:16:54.013 回答
8

实际上有一个类已经提供了这个功能——叫做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/

于 2016-07-21T01:20:31.253 回答
4

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

于 2016-07-17T14:28:43.357 回答
2

为了跟进马特的观察,我会在 上执行主要算法FloatingPoint,处理Double, Float,CGFloat等。但是然后我在 上进行另一个排列BinaryInteger,以处理所有整数类型。

例如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:). 您可以以任何方式处理这种情况。但它说明了这个想法。

于 2018-04-04T20:33:24.593 回答
2

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

基本上,您在数组上执行两遍(实际上是三遍)来计算两个值,其中一遍就足够了。方差可以表示为 E(X 2 ) - 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));
}
于 2016-07-21T00:38:00.797 回答
0

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

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

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 ) )
        }
    }
}
于 2019-07-12T17:09:30.947 回答