我经常需要计算数值数组的均值和标准差。所以我为一些似乎有用的数字类型编写了一个小协议和扩展。如果我这样做有什么不妥,我想反馈一下。具体来说,我想知道是否有更好的方法来检查类型是否可以转换为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].mean
和sd
是没有意义的,但我可能会在其他地方使用数字,所以这是为了保持一致..
编辑:正如 @Severin Pappadeux 所指出的那样,方差可以用避免数组三次传递的方式表示 - 然后是map然后表示平均值。这是最终的标准偏差扩展
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)))
}}
}
答案 0 :(得分: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/
答案 1 :(得分:3)
在Swift 3中,您可能(或可能不会)使用FloatingPoint协议保存自己的一些重复,但除此之外您正在做的事情是完全正确的。
答案 2 :(得分:3)
带有FloatingPoint元素的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))
}
}
答案 3 :(得分:1)
并不是说我知道斯威夫特,但是从数字POV来看,你做得有点低效
基本上,你在数组上进行两次传递(实际上是三次)来计算两个值,其中一次传递就足够了。 Vairance可以表示为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));
}
答案 4 :(得分:1)
要跟进马特的观察,我会在FloatingPoint
上执行主算法,照顾Double
,Float
,CGFloat
然后我然后在BinaryInteger
上对此进行另一种排列,以处理所有整数类型。
E.g。在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:)
。您可以以任何方式处理此方案。但它说明了这个想法。
答案 5 :(得分:0)
单挑,但是当我测试Severin Pappadeux概述的代码时,结果是“人口标准偏差”,而不是“样本标准偏差”。如果您有100%的相关数据可供使用,例如在计算班上所有20名学生的平均成绩方差时,您将使用第一个。如果您无法通用访问所有相关数据,并且不得不从一个较小的样本中估算出方差,例如估算一个大国家中所有男性的身高,那么您将使用第二个。
总体标准偏差通常表示为StDevP。我使用的Swift 5.0代码如下所示。请注意,由于“和”变大,因此丢失了“小值”位,因此这不适用于非常大的阵列。特别是当方差接近零时,您可能会遇到运行时错误。对于如此艰巨的工作,您可能必须引入一种称为compensated summation的算法
Execute Script | return Number (${i}) + 1; | i |