循环平均值
您可以将单位半径圆上对应点的向量代入角度,然后将均值定义为向量和的角度。
但请注意,这给出了 [0°, 0°, 90°] 的 26.5° 平均值,因为 26.5° = arctan(1/2) 而 [0°, 180°] 没有平均值。
异常值
离群值是离均值越远的角度,即角度差的绝对值越大。
标准差
标准差可用于定义异常值。
@coproc 在其回答中给出了相应的代码。
四分位数
也可以使用四分位距值,它对异常值的依赖程度低于标准差,但在循环情况下它可能无关紧要。
无论如何:
from functools import reduce
from math import degrees, radians, sin, cos, atan2, pi
def norm_angle(angle, degree_unit = True):
""" Normalize an angle return in a value between ]180, 180] or ]pi, pi]."""
mpi = 180 if degree_unit else pi
angle = angle % (2 * mpi)
return angle if abs(angle) <= mpi else angle - (1 if angle >= 0 else -1) * 2 * mpi
def circular_mean(angles, degree_unit = True):
""" Returns the circular mean from a collection of angles. """
angles = [radians(a) for a in angles] if degree_unit else angles
x_sum, y_sum = reduce(lambda tup, ang: (tup[0]+cos(ang), tup[1]+sin(ang)), angles, (0,0))
if x_sum == 0 and y_sum == 0: return None
return (degrees if degree_unit else lambda x:x)(atan2(y_sum, x_sum))
def circular_interquartiles_value(angles, degree_unit = True):
""" Returns the circular interquartiles value from a collection of angles."""
mean = circular_mean(angles, degree_unit=degree_unit)
deltas = tuple(sorted([norm_angle(a - mean, degree_unit=degree_unit) for a in angles]))
nb = len(deltas)
nq1, nq3, direct = nb // 4, nb - nb // 4, (nb % 4) // 2
q1 = deltas[nq1] if direct else (deltas[nq1-1] + deltas[nq1]) / 2
q3 = deltas[nq3-1] if direct else(deltas[nq3-1] + deltas[nq3]) / 2
return q3-q1
def circular_outliers(angles, coef = 1.5, values=True, degree_unit=True):
""" Returns outliers from a collection of angles. """
mean = circular_mean(angles, degree_unit=degree_unit)
maxdelta = coef * circular_interquartiles_value(angles, degree_unit=degree_unit)
deltas = [norm_angle(a - mean, degree_unit=degree_unit) for a in angles]
return [z[0] if values else i for i, z in enumerate(zip(angles, deltas)) if abs(z[1]) > maxdelta]
让我们试一试:
angles = [-179, -20, 350, 720, 10, 20, 179] # identical to [-179, -20, -10, 0, 10, 20, 179]
circular_mean(angles), circular_interquartiles_value(angles), circular_outliers(angles)
输出:
(-1.1650923760388311e-14, 40.000000000000014, [-179, 179])
正如我们所料:
-
circular_mean 接近 0,因为列表对于 0° 轴是对称的;
-
circular_interquartiles_value 是 40°,因为第一个四分位数是 -20°,第三个四分位数是 20°;
- 异常值被正确检测到,350 和 720 被取为其归一化值。