【问题标题】:Calculating Standard Deviation of Angles?计算角度的标准偏差?
【发布时间】:2012-12-18 07:39:18
【问题描述】:

所以我正在开发一个使用罗盘角度(以度为单位)的应用程序。我已经设法确定了角度平均值的计算,方法是使用以下(在http://en.wikipedia.org/wiki/Directional_statistics#The_fundamental_difference_between_linear_and_circular_statistics 找到):

 double calcMean(ArrayList<Double> angles){
      double sin = 0;
      double cos = 0;
      for(int i = 0; i < angles.size(); i++){
           sin += Math.sin(angles.get(i) * (Math.PI/180.0));
           cos += Math.cos(angles.get(i) * (Math.PI/180.0)); 
      }
      sin /= angles.size();
      cos /= angles.size();

      double result =Math.atan2(sin,cos)*(180/Math.PI);

      if(cos > 0 && sin < 0) result += 360;
      else if(cos < 0) result += 180;

      return result;
 }

所以我得到了正确的平均值/平均值,但我无法得到正确的方差/标准差值。我相当确定我的方差计算不正确,但想不出正确的方法。

这是我计算方差的方法:

 double calcVariance(ArrayList<Double> angles){

      //THIS IS WHERE I DON'T KNOW WHAT TO PUT
      ArrayList<Double> normalizedList = new ArrayList<Double>();
      for(int i = 0; i < angles.size(); i++){
           double sin = Math.sin(angles.get(i) * (Math.PI/180));
           double cos = Math.cos(angles.get(i) * (Math.PI/180));
           normalizedList.add(Math.atan2(sin,cos)*(180/Math.PI));
      }

      double mean = calcMean(angles);
      ArrayList<Double> squaredDifference = new ArrayList<Double>();
      for(int i = 0; i < normalizedList.size(); i++){
           squaredDifference.add(Math.pow(normalizedList.get(i) - mean,2));
      }

      double result = 0;
      for(int i = 0; i < squaredDifference.size(); i++){
           result+=squaredDifference.get(i);
      }

      return result/squaredDifference.size();
 }

虽然这是计算方差的正确方法,但我不是我应该使用的。我认为我应该使用反正切,但标准偏差/方差值似乎不正确。帮忙?

编辑: 示例:输入值 0,350,1,0,0,0,1,358,9,1 的平均角度为 0.0014(因为角度非常接近于零),但如果您只是进行非角度平均,您会得到 72 分……这还差得远。由于我不知道如何将单个值操作为应有的值,因此计算出的方差为 25074,导致标准偏差为 158 度,这太疯狂了! (应该只有几度)我认为我需要做的是正确标准化各个值,以便我可以获得正确的方差/标准差值。

【问题讨论】:

  • 我没有分析完整,但是这段代码好像需要Math.atan2(y,x)
  • @maniek - 我最初是这样做的(最近又把它放回去了),结果是一样的。我尝试了上面的方法和atan2,我得到的结果在12或13个数量级之内是相同的。
  • 编辑:看起来像使用 atan2 地址 Chechulin 的帖子。我将编辑我的问题。

标签: java algorithm math angle


【解决方案1】:

通过维基百科页面,您链接到循环标准差是sqrt(-log R²),其中 R = |样本的平均值|,如果您将样本视为单位圆上的复数。所以标准差的计算和平均角的计算很相似:

double calcStddev(ArrayList<Double> angles){
      double sin = 0;
      double cos = 0;
      for(int i = 0; i < angles.size(); i++){
           sin += Math.sin(angles.get(i) * (Math.PI/180.0));
           cos += Math.cos(angles.get(i) * (Math.PI/180.0)); 
      }
      sin /= angles.size();
      cos /= angles.size();

      double stddev = Math.sqrt(-Math.log(sin*sin+cos*cos));

      return stddev;
 }

如果你想一想它是有道理的:当你在单位圆上平均一堆彼此接近的点时,结果离圆不太远,所以 R 将接近 1 并且stddev 接近 0。如果点沿圆均匀分布,则它们的平均值将接近 0,因此 R 将接近 0,并且 stddev 非常大。

【讨论】:

  • @Joni - 我不太喜欢数学。要使用此处的方差计算标准偏差,我将使用“double stddev = Math.sqrt(Math.log(1/Math.pow(Math.sqrt(sinsin + coscos), 2)))*180/数学.PI;" ?从 wiki 页面上,您似乎以相同的方式获得 R,但您不​​会从 1 中减去。
  • 你可以通过去掉平方和平方根来简化你得到stddev = sqrt(-log(sin*sin+cos*cos))*180/pi
  • 注意单位。所写的函数以度为单位的角度作为输入,并以弧度为单位返回标准偏差。
  • 小心!圆形标准偏差不是一个角度量!它的值范围从 0 到无穷大,因此“转换为弧度/从弧度转换”没有任何意义,并且会错误地缩放结果。
  • 目前解决这个问题的好方法是通过 scipy 已经实现的功能,请参阅下面的答案:stackoverflow.com/a/67622278/6401987
【解决方案2】:

当您使用 Math.atan(sin/cosine) 时,您会得到 -90 到 90 度之间的角度。如果你有 120 度角,你得到 cos=-0.5 和 sin=0.866,那么你得到 atan(-1.7)=-60度。因此,您在标准化列表中放置了错误的角度。

假设 variance 是线性偏差,我建议您将 angles 数组旋转 -calcMean(angles) 并添加/减去 360 到/从高于/低于 180/-180 的角度(该死的我的写作!))同时找到最大和最小角度。它会给你想要的偏差。像这样:

    Double meanAngle = calcMean(angles)
    Double positiveDeviation = new Double(0);
    Double negativeDeviation = new Double(0);
    Iterator<Double> it = angles.iterator();
    while (it.hasNext())
    {
        Double deviation = it.next() - meanAngle;
        if (deviation > 180) deviation -= 180;
        if (deviation <= -180) deviation += 180;
        if (deviation > positiveDeviation) positiveDeviation = deviation;
        if (deviation > negativeDeviation) negativeDeviation = deviation;
    }
    return positiveDeviation - negativeDeviation;

对于平均平方偏差,您应该使用您的方法(使用角度,而不是“标准化”方法),并继续寻找 (-180, 180) 范围!

【讨论】:

  • 使用 atan2 解决了这个答案中提出的问题。它不会改变我得到的方差结果。谢谢。
  • 你检查 normalizedList.get(i) - mean 是否在 -180:180 范围内?因为如果你有它是 300 这意味着你应该把它当作 -60。
  • 它看起来可能已经成功了。我不是肯定的,但当我有机会时,我会更仔细地检查......或者我会检查未来的回复。谢谢!
  • 您的代码似乎在计算最小和最大绝对角度差之间的差值。这不等同于标准差的数学定义。
【解决方案3】:

数学库函数余数对于处理角度非常方便。

一个简单的改变就是替换

normalizedList.get(i) - mean

remainder( normalizedList.get(i) - mean, 360.0)

但是,您的第一个循环是多余的,因为对剩余的调用将负责所有规范化。此外,将平方差相加比存储它们更简单。我个人喜欢在算术可行时避免使用 pow() 。所以你的功能可能是:

double calcVariance(ArrayList<Double> angles){
 double mean = calcMean(angles);

  double result = 0;
  for(int i = 0; i < angles.size(); i++){
   double diff = remainder( angles.get(i) - mean, 360.0);
        result += diff*diff;
  }

  return result/angles.size();
 }

【讨论】:

    【解决方案4】:

    目前处理这个问题的好方法是已经在 scipy 中实现的两个函数:

    包括几个很棒的东西:

    • 矢量化以实现快速计算
    • 南交易
    • 高、低阈值,通常用于 0 到 360 度之间的角度与 0 到 2 Pi 之间的角度。

    【讨论】:

      【解决方案5】:

      The accepted answer by Joni 在回答这个问题方面做得很好,但就像Brian Hawkins noted

      注意单位。编写的函数以度为单位的角度作为输入,并以弧度为单位返回标准偏差。

      这是一个通过使用度数作为其参数和返回值来解决该问题的版本。它还具有更大的灵活性,因为它允许variable number of arguments

      public static double calcStdDevDegrees(double... angles) {
          double sin = 0;
          double cos = 0;
          for (int i = 0; i < angles.length; i++) {
              sin += Math.sin(angles[i] * (Math.PI/180.0));
              cos += Math.cos(angles[i] * (Math.PI/180.0)); 
          }
          sin /= angles.length;
          cos /= angles.length;
      
          double stddev = Math.sqrt(-Math.log(sin*sin+cos*cos));
      
          return Math.toDegrees(stddev);
      }
      

      【讨论】:

      • 圆形标准偏差不是角度量!它的值范围从 0 到无穷大,因此“转换为弧度/从弧度转换”没有任何意义,并且会错误地缩放结果。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-26
      相关资源
      最近更新 更多