Scala:计算标准偏差的通用方法是什么

Mik*_*der 9 generics scala

我很好奇我如何编写一个通用方法来计算scala中的标准偏差和方差.我有一个计算均值的通用方法(从这里被盗:在Scala中编写通用的平均函数)

我试图将平均值计算转换为标准偏差和方差但我看起来不对.此外,泛型在Scala编程方面超越了我的技能.

计算均值,标准差和方差的代码如下:

package ca.mikelavender

import scala.math.{Fractional, Integral, Numeric, _}

package object genericstats {

  def stdDev[T: Numeric](xs: Iterable[T]): Double = sqrt(variance(xs))

  def variance[T: Numeric](xs: Iterable[T]): Double = implicitly[Numeric[T]] match {
    case num: Fractional[_] => {
      val avg = mean(xs)
      num.toDouble(
        xs.foldLeft(num.zero)((b, a) =>
          num.plus(b, num.times(num.minus(a, avg), num.minus(a, avg))))) /
        xs.size
    }
    case num: Integral[_] => {
      val avg = mean(xs)
      num.toDouble(
        xs.foldLeft(num.zero)((b, a) =>
          num.plus(b, num.times(num.minus(a, avg), num.minus(a, avg))))) /
        xs.size
    }
  }

  /**
    * https://stackoverflow.com/questions/6188990/writing-a-generic-mean-function-in-scala
    */
  def mean[T: Numeric](xs: Iterable[T]): T = implicitly[Numeric[T]] match {
    case num: Fractional[_] => import num._; xs.sum / fromInt(xs.size)
    case num: Integral[_] => import num._; xs.sum / fromInt(xs.size)
    case _ => sys.error("Undivisable numeric!")
  }

}
Run Code Online (Sandbox Code Playgroud)

我觉得不需要方差方法中的匹配情况,或者可能更优雅.也就是说,代码的重复性对我来说似乎非常错误,我应该能够使用匹配来获取数字类型,然后将其传递给执行计算的单个代码块.

我不喜欢的另一件事是它总是返回一个Double.我觉得应该返回相同的输入数字类型,至少对于Fractional值.

那么,有没有关于如何改进代码并使其更漂亮的建议?

Tra*_*own 20

类型类的目标是为类型Numeric提供一组操作,以便您可以编写在具有类型类实例的任何类型上一般工作的代码.Numeric提供了一组操作及其子类,IntegralFractional另外提供了更具体的操作(但它们也表征了更少的类型).如果您不需要这些更具体的操作,您可以简单地工作Numeric,但不幸的是在这种情况下您可以.

让我们开始吧mean.这里的问题是除法对于整数和分数类型意味着不同的东西,并且根本不提供类型Numeric.从Daniel 链接答案通过调度Numeric实例的运行时类型来解决此问题,并且如果实例不是a Fractional或者,则只是崩溃(在运行时)Integral.

我将不同意丹尼尔(或者至少是五年前的丹尼尔),并说这不是一个很好的方法 - 它既是真正的差异,也是同时抛出大量的类型安全.我认为有三种更好的解决方案.

仅为小数类型提供这些操作

您可能会认为取整数对于整数类型没有意义,因为积分除失了结果的小数部分,并且仅为小数类型提供:

def mean[T: Fractional](xs: Iterable[T]): T = {
  val T = implicitly[Fractional[T]]

  T.div(xs.sum, T.fromInt(xs.size))
}
Run Code Online (Sandbox Code Playgroud)

或者使用漂亮的隐式语法:

def mean[T: Fractional](xs: Iterable[T]): T = {
  val T = implicitly[Fractional[T]]
  import T._

  xs.sum / T.fromInt(xs.size)
}
Run Code Online (Sandbox Code Playgroud)

最后一个句法点:如果我发现我必须编写implicitly[SomeTypeClass[A]]以获取对类型类实例的引用,我倾向于去除上下文绑定([A: SomeTypeClass]部分)以清理一些东西:

def mean[T](xs: Iterable[T])(implicit T: Fractional[T]): T =
  T.div(xs.sum, T.fromInt(xs.size))
Run Code Online (Sandbox Code Playgroud)

不过,这完全是品味问题.

返回具体的小数类型

您还可以mean返回一个具体的小数类型Double,并Numeric在执行操作之前将值转换为该类型:

def mean[T](xs: Iterable[T])(implicit T: Numeric[T]): Double =
  T.toDouble(xs.sum) / xs.size
Run Code Online (Sandbox Code Playgroud)

或者,等效但使用以下toDouble语法Numeric:

import Numeric.Implicits._

def mean[T: Numeric](xs: Iterable[T]): Double = xs.sum.toDouble / xs.size
Run Code Online (Sandbox Code Playgroud)

这为积分和分数类型(最高精度Double)提供了正确的结果,但代价是使您的操作不那么通用.

创建一个新的类类

最后,你可以创建一个新类型的类,用于提供共享的除法运算FractionalIntegral:

trait Divisible[T] {
  def div(x: T, y: T): T
}

object Divisible {
  implicit def divisibleFromIntegral[T](implicit T: Integral[T]): Divisible[T] =
    new Divisible[T] {
      def div(x: T, y: T): T = T.quot(x, y)
    }

  implicit def divisibleFromFractional[T](implicit T: Fractional[T]): Divisible[T] =
    new Divisible[T] {
      def div(x: T, y: T): T = T.div(x, y)
    }
}
Run Code Online (Sandbox Code Playgroud)

然后:

def mean[T: Numeric: Divisible](xs: Iterable[T]): T =
  implicitly[Divisible[T]].div(xs.sum, implicitly[Numeric[T]].fromInt(xs.size))
Run Code Online (Sandbox Code Playgroud)

这本质上是原始的更原则的版本 - 而mean不是在运行时调度子类型,您使用新类型来表征子类型.有更多的代码,但没有运行时错误的可能性(除非当然xs是空的等等,但这是所有这些方法都遇到的正交问题).

结论

这三种方式,我可能会选择第二种,而你的情况似乎是,因为你特别适合variancestdDev已经返回Double.在这种情况下,整个事情看起来像这样:

import Numeric.Implicits._

def mean[T: Numeric](xs: Iterable[T]): Double = xs.sum.toDouble / xs.size

def variance[T: Numeric](xs: Iterable[T]): Double = {
  val avg = mean(xs)

  xs.map(_.toDouble).map(a => math.pow(a - avg, 2)).sum / xs.size
}

def stdDev[T: Numeric](xs: Iterable[T]): Double = math.sqrt(variance(xs))
Run Code Online (Sandbox Code Playgroud)

......你已经完成了.

在实际代码中,我可能会查看像Spire这样的库,而不是使用标准库的类型类.