Skip to content

Commit 17e4858

Browse files
author
evgkanias
committed
Update plotting and computational models.
Implementation of eigenvectors and four zeros is also included.
1 parent 98cf2bc commit 17e4858

7 files changed

Lines changed: 2507 additions & 524 deletions

File tree

templates/circstats.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
from scipy.stats import circstd
2+
3+
import numpy as np
4+
5+
6+
def circ_mean(samples, high=np.pi, low=-np.pi, axis=None):
7+
c = to_complex(samples)
8+
return circ_norm(np.angle(np.nanmean(c, axis=axis)), high, low)
9+
10+
11+
def circ_r(samples, bias=None, axis=None):
12+
c = to_complex(samples)
13+
r = abs(np.nanmean(c, axis=axis))
14+
if bias is not None:
15+
r *= bias / (2 * np.sin(bias / 2))
16+
return r
17+
18+
19+
def circ_var(samples, bias=None, axis=None):
20+
r = circ_r(samples, bias=bias, axis=axis)
21+
var = -np.log(r)
22+
var[np.isclose(r, 0)] = 1 - r[np.isclose(r, 0)] # linear variance
23+
return var
24+
25+
26+
def circ_std(samples, bias=None, axis=None):
27+
s = circ_var(samples, bias, axis)
28+
return np.sqrt(2 * s)
29+
# return circstd(samples, axis=axis, nan_policy='omit')
30+
31+
32+
def circ_quantiles(samples, quantiles=None, high=np.pi, low=-np.pi, axis=None):
33+
34+
if quantiles is None:
35+
quantiles = [0.25, 0.50, 0.75]
36+
37+
mu = circ_mean(samples, axis=None)
38+
samples = circ_norm(samples, mu + np.pi, mu - np.pi)
39+
40+
q = np.nanquantile(samples, quantiles, axis=axis)
41+
42+
return circ_norm(q, high, low)
43+
44+
45+
def circ_median(samples, high=np.pi, low=-np.pi, axis=None):
46+
return circ_quantiles(samples, [0.5], high, low, axis)
47+
48+
49+
# def circ_median(samples, high=np.pi, low=-np.pi, axis=None):
50+
# c = to_complex(samples)
51+
# return circ_norm(np.angle(np.nanmedian(c, axis=axis)), high, low)
52+
#
53+
#
54+
# def circ_quantiles_custom(samples, quantiles=None, high=np.pi, low=-np.pi, axis=None):
55+
#
56+
# if quantiles is None:
57+
# quantiles = [0.25, 0.50, 0.75]
58+
# if not isinstance(quantiles, list):
59+
# quantiles = [quantiles]
60+
#
61+
# if axis is None:
62+
# samples = np.reshape(samples, -1)
63+
# elif not isinstance(axis, Iterable):
64+
# if axis < 0:
65+
# axis = np.arange(samples.ndim)[axis]
66+
#
67+
# keep = set(range(samples.ndim)) - {axis}
68+
# samples = np.transpose(samples, (axis,) + tuple(keep))
69+
# else:
70+
# keep = set(range(samples.ndim)) - set(axis)
71+
# samples = np.transpose(samples, tuple(axis) + tuple(keep))
72+
#
73+
# shape = samples.shape[-len(keep)]
74+
#
75+
# samples = np.reshape(samples, (-1,) + tuple(shape))
76+
#
77+
# n = samples.shape[0]
78+
#
79+
# d = circ_dist(samples[None, ...], samples[:, None, ...])
80+
#
81+
# m1 = np.sum(d > 0, axis=1)
82+
# m2 = np.sum(d < 0, axis=1)
83+
#
84+
# # calculate the mean of the samples
85+
# md_mean = circ_mean(samples, axis=0)
86+
#
87+
# dm = m2 - m1
88+
# idx = np.argsort(dm, axis=0)
89+
# md = []
90+
# for q in quantiles:
91+
# qq = q * (n - 1)
92+
#
93+
# frac = qq % 1
94+
# md.append((1 - frac) * np.take_along_axis(samples, idx[int(qq)][None, ...], axis=0)[0])
95+
#
96+
# if not np.isclose(frac, 0):
97+
# # take the average of the two sides
98+
# md[-1] += frac * np.take_along_axis(samples, idx[int(qq) + 1][None, ...], axis=0)[0]
99+
#
100+
# # shift the mean towards the quantile
101+
# md_mean_q = md_mean + np.pi * (q - 0.5)
102+
#
103+
# # if opposite side of the diameter is closer to the mean, use it as the median instead
104+
# flip = abs(circ_dist(md_mean_q, md[-1])) > abs(circ_dist(md_mean_q, md[-1] + np.pi))
105+
# md[-1][flip] += np.pi
106+
#
107+
# return circ_norm(np.asanyarray(md), high, low)
108+
109+
110+
def to_complex(angle):
111+
return np.exp(1j * angle)
112+
113+
114+
def circ_dist(a, b, high=np.pi, low=-np.pi):
115+
return circ_norm(a - b, high, low)
116+
117+
118+
def circ_norm(samples, high=np.pi, low=-np.pi):
119+
return (samples - low) % (high - low) + low

0 commit comments

Comments
 (0)