Swift 标准差的数组扩展
Swift Array extension for standard deviation
我经常需要计算数值数组的均值和标准差。因此,我为数字类型编写了一个小协议和扩展,似乎可行。如果我这样做有任何问题,我只是想得到反馈。具体来说,我想知道是否有更好的方法来检查类型是否可以转换为 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: {[=11=].asDouble + .asDouble}) / Double(self.count))}}
var sd : Element { get {
let mu = self.reduce(0, combine: {[=11=].asDouble + .asDouble}) / Double(self.count)
let variances = self.map{pow(([=11=].asDouble - mu), 2)}
return Element(sqrt(variances.mean))
}}
}
编辑:我知道获得 [Int].mean
和 sd
有点毫无意义,但我可能会在其他地方使用数字,以便保持一致性..
编辑:正如 @Severin Pappadeux 指出的那样,方差可以用一种方式表示,避免对数组进行三重传递 - 均值然后映射然后均值。这是最终的标准偏差扩展
extension Array where Element: Numeric {
var sd : Element { get {
let sss = self.reduce((0.0, 0.0)){ return ([=12=].0 + .asDouble, [=12=].1 + (.asDouble * .asDouble))}
let n = Double(self.count)
return Element(sqrt(sss.1/n - (sss.0/n * sss.0/n)))
}}
}
在 Swift 3 中,您可能(或可能不会)使用 FloatingPoint 协议避免一些重复,但除此之外您所做的是完全正确的。
据我所知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));
}
实际上已经有一个 class 提供了此功能 - 称为 NSExpression
。您可以通过使用它来减少代码大小和复杂性。这个 class 有很多东西,但是您想要的一个简单实现如下。
let expression = NSExpression(forFunction: "stddev:", arguments: [NSExpression(forConstantValue: [1,2,3,4,5])])
let standardDeviation = expression.expressionValueWithObject(nil, context: nil)
您也可以计算均值等等。此处信息:http://nshipster.com/nsexpression/
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, { [=10=] + (-mean)*(-mean) })
return sqrt(v / (Element(self.count) - 1))
}
}
为了跟进 ,我会在 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 = [=10=] - average
return difference * difference
}.reduce(Element(0), +)
}
}
但是 BinaryInteger
:
extension Array where Element: BinaryInteger {
var mean: Double { return map { Double(exactly: [=11=])! }.mean }
var stdev: Double? { return map { Double(exactly: [=11=])! }.stdev }
var stdevp: Double? { return map { Double(exactly: [=11=])! }.stdevp }
}
注意,在我的场景中,即使在处理整数输入数据时,我通常也想要浮点数 mean
和标准差,所以我随意选择了 Double
。你可能想要更安全地展开 Double(exactly:)
。您可以按照自己的方式处理这种情况。但它说明了这个想法。
只是一个 heads-up,但是当我测试 时,结果是“总体标准差”而不是“样本标准差”。您可以在 100% 的相关数据可用的情况下使用第一个,例如当您计算 class 中所有 20 名学生的平均成绩周围的方差时。如果您不能普遍访问所有相关数据,并且必须从更小的样本中估计方差,例如估计一个大国家内所有男性的身高,则可以使用第二种方法。
总体标准差通常表示为 StDevP。我使用的 Swift 5.0 代码如下所示。请注意,这不适用于非常大的数组,因为随着总和变大,“小值”位会丢失。特别是当方差接近于零时,您可能 运行 变成 run-times 错误。对于如此严肃的工作,您可能必须引入一种名为 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 ) )
}
}
}
我经常需要计算数值数组的均值和标准差。因此,我为数字类型编写了一个小协议和扩展,似乎可行。如果我这样做有任何问题,我只是想得到反馈。具体来说,我想知道是否有更好的方法来检查类型是否可以转换为 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: {[=11=].asDouble + .asDouble}) / Double(self.count))}}
var sd : Element { get {
let mu = self.reduce(0, combine: {[=11=].asDouble + .asDouble}) / Double(self.count)
let variances = self.map{pow(([=11=].asDouble - mu), 2)}
return Element(sqrt(variances.mean))
}}
}
编辑:我知道获得 [Int].mean
和 sd
有点毫无意义,但我可能会在其他地方使用数字,以便保持一致性..
编辑:正如 @Severin Pappadeux 指出的那样,方差可以用一种方式表示,避免对数组进行三重传递 - 均值然后映射然后均值。这是最终的标准偏差扩展
extension Array where Element: Numeric {
var sd : Element { get {
let sss = self.reduce((0.0, 0.0)){ return ([=12=].0 + .asDouble, [=12=].1 + (.asDouble * .asDouble))}
let n = Double(self.count)
return Element(sqrt(sss.1/n - (sss.0/n * sss.0/n)))
}}
}
在 Swift 3 中,您可能(或可能不会)使用 FloatingPoint 协议避免一些重复,但除此之外您所做的是完全正确的。
据我所知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));
}
实际上已经有一个 class 提供了此功能 - 称为 NSExpression
。您可以通过使用它来减少代码大小和复杂性。这个 class 有很多东西,但是您想要的一个简单实现如下。
let expression = NSExpression(forFunction: "stddev:", arguments: [NSExpression(forConstantValue: [1,2,3,4,5])])
let standardDeviation = expression.expressionValueWithObject(nil, context: nil)
您也可以计算均值等等。此处信息:http://nshipster.com/nsexpression/
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, { [=10=] + (-mean)*(-mean) })
return sqrt(v / (Element(self.count) - 1))
}
}
为了跟进 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 = [=10=] - average
return difference * difference
}.reduce(Element(0), +)
}
}
但是 BinaryInteger
:
extension Array where Element: BinaryInteger {
var mean: Double { return map { Double(exactly: [=11=])! }.mean }
var stdev: Double? { return map { Double(exactly: [=11=])! }.stdev }
var stdevp: Double? { return map { Double(exactly: [=11=])! }.stdevp }
}
注意,在我的场景中,即使在处理整数输入数据时,我通常也想要浮点数 mean
和标准差,所以我随意选择了 Double
。你可能想要更安全地展开 Double(exactly:)
。您可以按照自己的方式处理这种情况。但它说明了这个想法。
只是一个 heads-up,但是当我测试
总体标准差通常表示为 StDevP。我使用的 Swift 5.0 代码如下所示。请注意,这不适用于非常大的数组,因为随着总和变大,“小值”位会丢失。特别是当方差接近于零时,您可能 运行 变成 run-times 错误。对于如此严肃的工作,您可能必须引入一种名为 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 ) )
}
}
}