bundles / scipy latest / scipy / stats / _circstats / circmedian
function
scipy.stats._circstats:circmedian
source: build-install/usr/lib/python3.14/site-packages/scipy/stats/_circstats.py:84
Signature
def circmedian(sample, *, convention = arc-distance, high = 6.283185307179586, low = 0, axis = 0, nan_policy = propagate, keepdims = False)Summary
Compute the circular median of an angular sample.
Extended Summary
According to [1] and [2], a circular median is an angle that bisects the data: half of the observations lie within 180 degrees clockwise and half lie within 180 degrees counter-clockwise. The implementation of circmedian agrees with these references but follows the details of [3], which defines this "bisecting property" more rigorously with consideration for edge cases (e.g. ties, symmetry).
Parameters
sample: array_likeInput array of angle observations.
convention: {'arc-distance', 'bisecting', 'geometric'}Definition of the circular median, following the terminology of [3].
An
'arc-distance'median minimizes the circular mean deviation: the average arc distance between the observations and a common reference angle. Any arc-distance median also has the "bisecting property": the chord between a median and its antipode divide the circle in two such that at least half the observations lie in each semicircle.A
'bisecting'median has the bisecting property, but does not necessarily minimize the circular mean deviation. Instead, the number of observations within 90 degrees of a bisecting median is greater than the number of observations within 90 degrees of its antipode.A
'geometric'median treats angle observations extrinsically: it maps them to points on the unit circle in the ambient two-dimensional vector space, computes their vector median, and extracts the angular phase of the result. A geometric median does not necessarily have the bisecting property, but it is unique unless all points lie on a line, and it is the only convention here that is defined extrinsically, like the circular mean and circular variance.
See [3] for precise mathematical definitions.
high: float, optionalUpper boundary of the principal value of an angle. Default is
2*pi.low: float, optionalLower boundary of the principal value of an angle. Default is
0.axis: int or None, default: NoneIf an int, the axis of the input along which to compute the statistic. The statistic of each axis-slice (e.g. row) of the input will appear in a corresponding element of the output. If
None, the input will be raveled before computing the statistic.nan_policy: {'propagate', 'omit', 'raise'}Defines how to handle input NaNs.
propagate: if a NaN is present in the axis slice (e.g. row) along which the statistic is computed, the corresponding entry of the output will be NaN.omit: NaNs will be omitted when performing the calculation. If insufficient data remains in the axis slice along which the statistic is computed, the corresponding entry of the output will be NaN.raise: if a NaN is present, aValueErrorwill be raised.
nan_policy='raise'andnan_policy='omit'are not supported by lazy backends, andnan_policy='omit'is not compatible with MArray input. For multidimensional input,nan_policy='omit'is supported only by the NumPy backend. Instead, mask NaN values using MArray; and use the defaultnan_policy. See Notes for support information.keepdims: bool, default: FalseIf this is set to True, the axes which are reduced are left in the result as dimensions with size one. With this option, the result will broadcast correctly against the input array.
Returns
circmedian: floatCircular median, restricted to the range
[low, high].
Notes
There are several definitions of "circular median" in the literature. Seminal references for circular statistics [1] and [2] mention the bisecting property of the circular median and its connection with minimization of the circular mean deviation. However, [1] prioritizes minimization of the circular mean deviation as the defining property, whereas [2] requires that "the majority of the data points are nearer to [the median] than [its antipode]".
Although the two definitions produce the same circular median in many cases (unimodal distribution, no ties), [3] demonstrates that the two definitions can lead to different - and in fact, entirely opposite - results. [3] also compares the arc-distance, bisecting, and geometric medians with one other definition from vector-space literature, and it concludes that the arc-distance median is the only one that provides all four properties under consideration. This, and the fact that the arc-distance definition is used by default in other circular statistics software (e.g. [4], [5], [6]), contributed to the choice of 'arc-distance' as the default convention.
[7] addresses the fact that many points - even continuous arcs of the unit circle - may satisfy the definition of an arc-distance or bisecting circular median. It proposes that "the estimate of the population circular median be the average (circular mean) of all angles satisfying the definition of median", and that "for odd samples, the candidate values are the observations themselves". However, it suggests that "for even samples, the candidate values are the midpoints of all neighboring observation". circmedian always finds all points among the observations (and their antipodes, in the 'bisecting' case) that satisfy the chosen definition of a circular median and returns their circular mean. If the circular mean is poorly defined (i.e. the circular variance is withing a small tolerance of 1.0), circmedian returns NaN.
Beginning in SciPy 1.9, np.matrix inputs (not recommended for new code) are converted to np.ndarray before the calculation is performed. In this case, the output will be a scalar or np.ndarray of appropriate shape rather than a 2D np.matrix. Similarly, while masked elements of masked arrays are ignored, the output will be a scalar or np.ndarray rather than a masked array with mask=False.
Examples
Consider Example 1 from [3]_, expressed in degrees for readability.import numpy as np from scipy import stats sample = np.array([101.25, 101.25, 0, -101.25, -101.25])✓
displacements = (sample[:, np.newaxis] - sample[np.newaxis, :]) % 360 distances = np.minimum(displacements, 360 - displacements) mean_deviations = np.mean(distances, axis=-1) sample[mean_deviations == np.min(mean_deviations)]✓
array([0.])As expected,
stats.circmedian(sample, low=-180, high=180)
✓np.float64(0.0)Note that the chord from *any* observation to its antipode bisects the observations in this example. Furthermore, each antipode is within 90 degrees of three or four observations, whereas each observation has either two or zero other observations within 90 degrees. Therefore, the antipodes ``[-78.75, -78.75, 180, 78.75, 78.75]`` all satisfy the definition of a bisecting median. The circular mean of these is 180 degrees,
stats.circmean([-78.75, -78.75, 180, 78.75, 78.75], low=-180, high=180)
✓np.float64(179.99999999999994)and so:
stats.circmedian(sample, low=-180, high=180, convention='bisecting')
✓np.float64(179.99999999999994)The discrepancy between the two conventions is extreme in this constructed example, but for data from a unimodal, continuous distribution, the two definitions tend to produce similar or identical results.
rng = np.random.default_rng(8180124976) sample = rng.vonmises(mu=0, kappa=1, size=10) stats.circmedian(sample), stats.circmedian(sample, convention='bisecting')✓
(np.float64(0.44401177698735417), np.float64(0.44401177698735417))
See also
Aliases
scipy.stats.circmedian