Table of Contents
周期変数の平均の計算方法
HH:mm:ss
形式で表現される時刻の変数や角度といった周期性を持った変数の平均を導出したいケースを考えます.
例として, 以下のような日別睡眠時間データが与えられたとき, 00:30:00
という平均時間を返してほしいというケースがモチベーションとなります.
1
2
3
4
5
6
7
8
9
## 睡眠時間 list
T = ['23:30:00',
'00:30:00',
'22:30:00',
'01:30:00',
'02:30:00']
circlemean(T)
>>> '00:30:00'
CIRCLE MEAN: PRML 2.3.8 周期変数より
周期変数 $\theta_i \in [0, 2\pi)$の観測値の集合 $\theta_1, \cdots, \theta_n$の平均 $\overline \theta$を求めたいとする.
$\theta_i$から単位円上の点への写像 $F$ を以下のように設定する:
\[F(\theta) = (\cos \theta, \sin \theta)\]つまり, $\theta_i$ に対応する直交座標上の観測値 $\pmb{x_i}= (\cos \theta_i, \sin \theta_i)$とする.
直交座標上の観測値の単純平均を以下のように定義する
\[\overline{\pmb{x}} = (\overline{\pmb{x_1}}, \overline{\pmb{x_2}} ) = \left(\frac{1}{N}\sum^{N}_i\cos \theta_i, \frac{1}{N}\sum^{N}_i\sin \theta_i\right)\]なお, このとき $\overline{\pmb{x}}$は単位円の内部に必ず存在する.
$\overline{\pmb{x}}$がなす角度を $\overline\theta$ とみなすことができるとき, $\overline\theta$ は以下のように計算できる
\[\begin{align*} \tan{\overline{\theta}} &= \frac{\overline{\pmb{x_2}}}{\overline{\pmb{x_1}} } = \frac{\sum_{i=1}^{n} \sin{\theta_{i}}}{\sum_{i=1}^{n} \cos{\theta_{i}}} \\ \overline{\theta} &= \arctan\left[ \frac{\sum_{i=1}^{n} \sin{\theta_{i}}}{\sum_{i=1}^{n} \cos{\theta_{i}}} \right] \end{align*}\]導出終了
注意!!
- 平均を中心に左右対称の確率変数の場合は上記の方法でcircle meanは適切に計算されると思いますが, skewの場合は誤差が発生します
- 関数 $F: R \to R^2$が今回非線形関数なので$F^{-1}F(\bar x)$と $F^{-1}\overline{F(x)}$は計算結果が異なるというのが直感的理解
Pythonでの実装
以下の実装は基本的には scipy.stats.circmean
のsourceコードと同じです.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def circle_mean(samples, high=2*np.pi, low=0.0, nan_policy='raise', na_val=0.0):
"""Compute the circular mean for samples in a range.
Parameters
----------
samples : array_like
Input array.
high : float or int, optional
High boundary for the sample range. Default is ``2*pi``.
low : float or int, optional
Low boundary for the sample range. Default is 0.
nan_policy : {'impute', 'drop', 'raise'}, optional
Defines how to handle when input contains nan.
'impute' fills nan values with the user specified value, 'raise' throws an error, 'drop' performs the calculations ignoring nan values. Default is 'raise'.
na_val: float or int, optional
Returns
-------
circmean : float
Circular mean.
"""
# Ensure samples are array-like and size is not zero
samples = np.asarray(samples)
if samples.size == 0:
return np.nan, np.asarray(np.nan), np.asarray(np.nan), None
# Recast samples as radians that range between 0 and 2 pi and calculate
# the sine and cosine
sin_samp = np.sin((samples - low)*2.*np.pi / (high - low))
cos_samp = np.cos((samples - low)*2.*np.pi / (high - low))
# Apply the NaN policy
contains_nan = np.max(np.isnan(samples))
if contains_nan and nan_policy == 'impute':
mask = np.isnan(samples)
# Set the sines and cosines that are NaN to zero
sin_samp[mask] = na_val
cos_samp[mask] = na_val
elif contains_nan and nan_policy == 'drop':
sin_samp = sin_samp[~mask]
cos_samp = cos_samp[~mask]
samples = samples[~mask]
else:
if contains_nan:
raise ValueError("The input contains nan values")
# Compute circle mean
sin_sum = sin_samp.sum()
cos_sum = cos_samp.sum()
res = np.arctan2(sin_sum, cos_sum)
return res*(high - low)/2.0/np.pi + low
等差数列とCIRLCE MEAN
Sum of sines & cosines
Theorem
正の整数 $N$ と 実数 $a, b$について, $\sin \frac{b}{2} \neq 0$のとき,
\[\begin{align*} \sum_{n=0}^{N-1} \cos(a + nd) &= R \cos (a + (N - 1) \frac{1}{2} b)\\ \sum_{n=0}^{N-1} \sin(a + nd) &= R \sin (a + (N - 1) \frac{1}{2} b) \end{align*}\]ただし,
\[R \triangleq \frac{\sin(N \frac{1}{2}d)}{\sin(\frac{1}{2} b)}\]証明
\[C \triangleq \sum_{n=0}^{N-1}\cos(a + nb)\]このとき, 両辺に $\sin \frac{b}{2} \neq 0$ を掛けると
\[\begin{align*} 2 \sin\left(\frac{1}{2} b\right) C &= \sum_{n=0}^{N-1}2 \cos(a + nb) \sin\left(\frac{1}{2}b\right)\\ &= \sum_{n=0}^{N-1} \bigg \{ \sin\bigg(a + \bigg(n + \frac{1}{2}\bigg) b\bigg) - \sin\bigg(a + \bigg(n - \frac{1}{2}\bigg) b\bigg) \bigg\}\\ &= \sin\bigg(a + \bigg(N - \frac{1}{2}\bigg) b\bigg) - \sin\bigg(a - \frac{1}{2}b\bigg) \end{align*}\]$\sin(x+y)−\sin(x−y)=2\cos x \sin y$ であることに留意すると
\[2 \sin(\frac{1}{2} b) C = 2 \cos\bigg( a + (N - 1) \frac{1}{2} b \bigg) \sin\bigg( N \frac{1}{2} b \bigg)\]Sine sumも同様の方法で
\[S \triangleq \sum_{n=0}^{N-1}\sin(a + nb)\]と定義すると
\[2 \sin(\frac{1}{2} b) S = 2 \sin\bigg( a + (N - 1) \frac{1}{2} b \bigg) \sin\bigg( N \frac{1}{2} b \bigg)\]を得る.
証明終了
等差数列のinputに対するCIRCLE MEANの性質
上の証明より, 正の整数 $N$ と 実数 $a, b$について, $\sin \frac{b}{2} \neq 0$の場合,
\[\begin{align*} \sum_{n=0}^{N-1} \cos(a + nd) &= R \cos (a + (N - 1) \frac{1}{2} b)\\ \sum_{n=0}^{N-1} \sin(a + nd) &= R \sin (a + (N - 1) \frac{1}{2} b) \end{align*}\]となることがわかった. これに倣って, ${a + (i-1)d}_{i=1}^N$という観測値集合を考えると, Bishop 2.3.8 で紹介されていたCIRCLE MEANは
\[\overline{\theta} = \arctan\left[ \frac{\sum_{i=1}^{n} \sin{\theta_{i}}}{\sum_{i=1}^{n} \cos{\theta_{i}}} \right]\]なので
\[\begin{align*} \arctan\left[ \frac{\sum_{i=1}^{n} \sin{a + (i-1)d}}{\sum_{i=1}^{n} \cos{a + (i-1)d}} \right] &= \arctan\bigg[ \frac{\sin (a + (N - 1) \frac{1}{2} b)}{\cos (a + (N - 1) \frac{1}{2} b)}\bigg]\\ &= \arctan\bigg[\tan\left(a + (N - 1) \frac{1}{2} b\right)\bigg]\\ &= \frac{1}{N}\sum_{i=1}^N [a + (i-1)d] \end{align*}\]となり, 「観測値の平均」と「変数変換した変数の平均比率の arctan」が一致することがわかる(厳密ではないですが).
References
関連ポスト
公式ドキュメント
書籍
- Pattern Recognition and Machine Learning by Christopher Bishop > 2.3.8 Periodic variables
- Sines and Cosines of Angles in Arithmetic Progression
(注意:GitHub Accountが必要となります)