U
    Qvfî_  ã                   @   sŽ   d dl Zd dlmZ ddlmZ ddddd	d
ddgZddd„Zddd„Zdd„ Z	ddd„Z
ddd„Zddd	„Zdd
„ Zddd„Zd dd„ZdS )!é    N)Únormé   )Ú	remove_naÚconvert_anglesÚ
circ_axialÚ	circ_meanÚcirc_rÚcirc_corrccÚcirc_corrclÚcirc_rayleighÚ
circ_vtestc                 C   sB   d}t j| |dt j| |d }|dt j k}| ¡ s>t|ƒ‚dS )z6Internal function to check that angles are in radians.zAngles are not in unit of radians. Please use the `pingouin.convert_angles` function to map your angles to the [-pi, pi] range.©Úaxisé   N)ÚnpÚnanmaxÚnanminÚpiÚallÚ
ValueError)Úanglesr   ÚmsgZptp_radZptp_mask© r   ú5/tmp/pip-unpacked-wheel-2te3nxqf/pingouin/circular.pyÚ_checkangles   s    ÿr   éh  Fc                 C   s¼   t |tƒst‚t |ttfƒs$tdƒ‚t |ttfƒs:tdƒ‚|| }|dksRtdƒ‚t | ¡} t | ¡|ksrtdƒ‚t | ¡|ksˆtdƒ‚| dtj	  | }|s¸|tj	 dtj	  tj	 }|S )a“	  Element-wise conversion of arbitrary-unit circular quantities
    to radians.

    .. versionadded:: 0.3.4

    Parameters
    ----------
    angles : array_like
        Circular data.
    low : float or int, optional
        Low boundary for ``angles`` range.  Default is 0.
    high : float or int, optional
        High boundary for ``angles`` range.  Default is 360
        (for degrees to radians conversion).
    positive : boolean
        If True, radians are mapped on the :math:`[0, 2\pi]`. Otherwise,
        the resulting angles are mapped from :math:`[-\pi, \pi)` (default).

    Returns
    -------
    radians : array_like
        Circular data in radians.

    Notes
    -----
    The formula to convert a set of angles :math:`\alpha` from an arbitrary
    range :math:`[\text{high},\text{low}]` to radians
    :math:`[0, 2\pi]` is:

    .. math::

        \alpha_r = \frac{2\pi\alpha}{\text{high} - \text{low}}

    If ``positive=False`` (default), the resulting angles in
    radians :math:`\alpha_r` are then wrapped to the :math:`[-\pi, \pi)`
    range:

    .. math::

        (\text{angle} + \pi) \mod 2 \pi - \pi

    Examples
    --------
    1. Convert degrees to radians

    >>> from pingouin import convert_angles
    >>> a = [0, 360, 180, 90, 45, 270]
    >>> convert_angles(a, low=0, high=360)
    array([ 0.        ,  0.        , -3.14159265,  1.57079633,  0.78539816,
           -1.57079633])

    with ``positive=True``:

    >>> convert_angles(a, low=0, high=360, positive=True)
    array([0.        , 6.28318531, 3.14159265, 1.57079633, 0.78539816,
           4.71238898])

    2. Convert hours (24h-format) to radians

    >>> sleep_onset = [22.5, 23.25, 24, 0.5, 1]
    >>> convert_angles(sleep_onset, low=0, high=24)
    array([-0.39269908, -0.19634954,  0.        ,  0.13089969,  0.26179939])

    3. Convert radians from :math:`[0, 2\pi]` to :math:`[-\pi, \pi)`:

    >>> import numpy as np
    >>> rad = [0.1, 3.14, 5, 2, 6]
    >>> convert_angles(rad, low=0, high=2*np.pi)
    array([ 0.1       ,  3.14      , -1.28318531,  2.        , -0.28318531])

    4. Convert degrees from a 2-D array

    >>> np.random.seed(123)
    >>> deg = np.random.randint(low=0, high=360, size=(3, 4))
    >>> convert_angles(deg)
    array([[-0.66322512,  1.71042267, -2.26892803,  0.29670597],
           [ 1.44862328,  1.85004901,  2.14675498,  0.99483767],
           [-2.54818071, -2.35619449,  1.67551608,  1.97222205]])
    zhigh must be numericzlow must be numericr   z%high - low must be strictly positive.zangles cannot be >= low.zangles cannot be <= high.r   )
Ú
isinstanceÚboolÚAssertionErrorÚintÚfloatr   Úasarrayr   r   r   )r   ÚlowÚhighZpositiveZptpZradr   r   r   r   +   s    P
c                 C   s    t  | ¡} t  | | dt j ¡S )a*  Transforms n-axial data to a common scale.

    Parameters
    ----------
    angles : array
        Sample of angles in radians
    n : int
        Number of modes

    Returns
    -------
    angles : float
        Transformed angles

    Notes
    -----
    Tranform data with multiple modes (known as axial data) to a unimodal
    sample, for the purpose of certain analysis such as computation of a
    mean resultant vector (see Berens 2009).

    Examples
    --------
    Transform degrees to unimodal radians in the Berens 2009 neuro dataset.

    >>> import numpy as np
    >>> from pingouin import read_dataset
    >>> from pingouin.circular import circ_axial
    >>> df = read_dataset('circular')
    >>> angles = df['Orientation'].to_numpy()
    >>> angles = circ_axial(np.deg2rad(angles), 2)
    r   )r   r!   Ú	remainderr   )r   Únr   r   r   r      s     
c              
   C   sj   t  | ¡} t| ƒ |dk	r$t  |¡n
t  | j¡}| j|jksDtdƒ‚t  t jt  |t  	d|  ¡¡|d¡S )u§  Mean direction for (binned) circular data.

    Parameters
    ----------
    angles : array_like
        Samples of angles in radians. The range of ``angles`` must be either
        :math:`[0, 2\pi]` or :math:`[-\pi, \pi]`. If ``angles`` is not
        expressed in radians (e.g. degrees or 24-hours), please use the
        :py:func:`pingouin.convert_angles` function prior to using the present
        function.
    w : array_like
        Number of incidences per bins (i.e. "weights"), in case of binned angle
        data.
    axis : int or None
        Compute along this dimension. Default is the first axis (0).

    Returns
    -------
    mu : float
        Circular mean, in radians.

    See also
    --------
    scipy.stats.circmean, scipy.stats.circstd, pingouin.circ_r

    Notes
    -----
    From Wikipedia:

    *In mathematics, a mean of circular quantities is a mean which is sometimes
    better-suited for quantities like angles, daytimes, and fractional parts
    of real numbers. This is necessary since most of the usual means may not be
    appropriate on circular quantities. For example, the arithmetic mean of 0Â°
    and 360Â° is 180Â°, which is misleading because for most purposes 360Â° is
    the same thing as 0Â°.
    As another example, the "average time" between 11 PM and 1 AM is either
    midnight or noon, depending on whether the two times are part of a single
    night or part of a single calendar day.*

    The circular mean of a set of angles :math:`\alpha` is defined by:

    .. math::

        \bar{\alpha} =  \text{angle} \left ( \sum_{j=1}^n \exp(i \cdot
        \alpha_j) \right )

    For binned angles with weights :math:`w`, this becomes:

    .. math::

        \bar{\alpha} =  \text{angle} \left ( \sum_{j=1}^n w \cdot
        \exp(i \cdot \alpha_j) \right )

    Missing values in ``angles`` are omitted from the calculations.

    References
    ----------
    * https://en.wikipedia.org/wiki/Mean_of_circular_quantities

    * Berens, P. (2009). CircStat: A MATLAB Toolbox for Circular
      Statistics. Journal of Statistical Software, Articles, 31(10),
      1â€“21. https://doi.org/10.18637/jss.v031.i10

    Examples
    --------
    1. Circular mean of a 1-D array of angles, in radians

    >>> import pingouin as pg
    >>> angles = [0.785, 1.570, 3.141, 0.839, 5.934]
    >>> round(pg.circ_mean(angles), 4)
    1.013

    Compare with SciPy:

    >>> from scipy.stats import circmean
    >>> import numpy as np
    >>> round(circmean(angles, low=0, high=2*np.pi), 4)
    1.013

    2. Using a 2-D array of angles in degrees

    >>> np.random.seed(123)
    >>> deg = np.random.randint(low=0, high=360, size=(3, 5))
    >>> deg
    array([[322,  98, 230,  17,  83],
           [106, 123,  57, 214, 225],
           [ 96, 113, 126,  47,  73]])

    We first need to convert from degrees to radians:

    >>> rad = np.round(pg.convert_angles(deg, low=0, high=360), 4)
    >>> rad
    array([[-0.6632,  1.7104, -2.2689,  0.2967,  1.4486],
           [ 1.85  ,  2.1468,  0.9948, -2.5482, -2.3562],
           [ 1.6755,  1.9722,  2.1991,  0.8203,  1.2741]])

    >>> pg.circ_mean(rad)  # On the first axis (default)
    array([1.27532162, 1.94336576, 2.23195927, 0.52110503, 1.80240563])
    >>> pg.circ_mean(rad, axis=-1)  # On the last axis (default)
    array([0.68920819, 2.49334852, 1.5954149 ])
    >>> round(pg.circ_mean(rad, axis=None), 4)  # Across the entire array
    1.6954

    Missing values are omitted from the calculations:

    >>> rad[0, 0] = np.nan
    >>> pg.circ_mean(rad)
    array([1.76275   , 1.94336576, 2.23195927, 0.52110503, 1.80240563])

    3. Using binned angles

    >>> np.random.seed(123)
    >>> nbins = 18  # Number of bins to divide the unit circle
    >>> angles_bins = np.linspace(0, 2 * np.pi, nbins)
    >>> # w represents the number of incidences per bins, or "weights".
    >>> w = np.random.randint(low=0, high=5, size=angles_bins.size)
    >>> round(pg.circ_mean(angles_bins, w), 4)
    0.606
    NúInput dimensions do not matchù              ð?r   )
r   r!   r   ÚonesÚshaper   ZangleÚnansumÚmultiplyÚexp)r   Úwr   r   r   r   r   ¸   s
    x
c                 C   sÀ   t  | ¡} t| ƒ |dk	r$t  |¡n
t  | j¡}| j|jksDtdƒ‚| t¡}t j|t  	| ¡< t j
t  |t  d|  ¡¡|d}t  |¡t j
||d }|dk	r¼|d t  |d ¡ }|| }|S )u§  Mean resultant vector length for circular data.

    Parameters
    ----------
    angles : array_like
        Samples of angles in radians. The range of ``angles`` must be either
        :math:`[0, 2\pi]` or :math:`[-\pi, \pi]`. If ``angles`` is not
        expressed in radians (e.g. degrees or 24-hours), please use the
        :py:func:`pingouin.convert_angles` function prior to using the present
        function.
    w : array_like
        Number of incidences per bins (i.e. "weights"), in case of binned angle
        data.
    d : float
        Spacing (in radians) of bin centers for binned data. If supplied,
        a correction factor is used to correct for bias in the estimation
        of r.
    axis : int or None
        Compute along this dimension. Default is the first axis (0).

    Returns
    -------
    r : float
        Circular mean vector length.

    See also
    --------
    pingouin.circ_mean

    Notes
    -----
    The length of the mean resultant vector is a crucial quantity for the
    measurement of circular spread or hypothesis testing in directional
    statistics. The closer it is to one, the more concentrated the data
    sample is around the mean direction (Berens 2009).

    The circular vector length of a set of angles :math:`\alpha` is defined
    by:

    .. math::

        \bar{\alpha} =  \frac{1}{N}\left \| \sum_{j=1}^n
        \exp(i \cdot \alpha_j) \right \|

    Missing values in ``angles`` are omitted from the calculations.

    References
    ----------
    * https://en.wikipedia.org/wiki/Mean_of_circular_quantities

    * Berens, P. (2009). CircStat: A MATLAB Toolbox for Circular
      Statistics. Journal of Statistical Software, Articles, 31(10),
      1â€“21. https://doi.org/10.18637/jss.v031.i10

    Examples
    --------
    1. Mean resultant vector length of a 1-D array of angles, in radians

    >>> import pingouin as pg
    >>> angles = [0.785, 1.570, 3.141, 0.839, 5.934]
    >>> r = pg.circ_r(angles)
    >>> round(r, 4)
    0.4972

    Note that there is a close relationship between the vector length and the
    circular standard deviation, i.e. :math:`\sigma = \sqrt{-2 \ln R}`:

    >>> import numpy as np
    >>> round(np.sqrt(-2 * np.log(r)), 4)
    1.1821

    which gives similar result as SciPy built-in function:

    >>> from scipy.stats import circstd
    >>> round(circstd(angles), 4)
    1.1821

    Sanity check: if all angles are the same, the vector length should be one:

    >>> angles = [3.14, 3.14, 3.14, 3.14]
    >>> round(pg.circ_r(angles), 4)
    1.0

    2. Using a 2-D array of angles in degrees

    >>> np.random.seed(123)
    >>> deg = np.random.randint(low=0, high=360, size=(3, 5))
    >>> deg
    array([[322,  98, 230,  17,  83],
           [106, 123,  57, 214, 225],
           [ 96, 113, 126,  47,  73]])

    We first need to convert from degrees to radians:

    >>> rad = np.round(pg.convert_angles(deg, low=0, high=360), 4)
    >>> rad
    array([[-0.6632,  1.7104, -2.2689,  0.2967,  1.4486],
           [ 1.85  ,  2.1468,  0.9948, -2.5482, -2.3562],
           [ 1.6755,  1.9722,  2.1991,  0.8203,  1.2741]])

    >>> pg.circ_r(rad)  # On the first axis (default)
    array([0.46695499, 0.98398294, 0.3723287 , 0.31103746, 0.42527149])
    >>> pg.circ_r(rad, axis=-1)  # On the last axis (default)
    array([0.28099998, 0.45456096, 0.88261161])
    >>> round(pg.circ_r(rad, axis=None), 4)  # Across the entire array
    0.4486

    Missing values are omitted from the calculations:

    >>> rad[0, 0] = np.nan
    >>> pg.circ_r(rad)
    array([0.99619613, 0.98398294, 0.3723287 , 0.31103746, 0.42527149])

    3. Using binned angles

    >>> np.random.seed(123)
    >>> nbins = 18  # Number of bins to divide the unit circle
    >>> angles_bins = np.linspace(0, 2 * np.pi, nbins)
    >>> # w represents the number of incidences per bins, or "weights".
    >>> w = np.random.randint(low=0, high=5, size=angles_bins.size)
    >>> round(pg.circ_r(angles_bins, w), 4)
    0.3642
    NzInput dimensions do not match.r'   r   r   )r   r!   r   r(   r)   r   Zastyper    ÚnanÚisnanr*   r+   r,   ÚabsÚsin)r   r-   Údr   ÚrÚcr   r   r   r   7  s    |

 c                 C   s^  t  | ¡} t  |¡}| j|jks(tdƒ‚t| |dd\} }| j}t  | t| ƒ ¡}t  |t|ƒ ¡}|sšt  || ¡t  t  |d ¡t  |d ¡ ¡ }nnt  	t  t  
| | d ¡¡¡}t  	t  t  
| | d ¡¡¡}dt  t  |d ¡t  |d ¡ ¡ }	|| |	 }t  ||d  ¡  |d  ¡  t  |d |d  ¡ ¡| }
dt t	|
ƒ¡ }||fS )u  Correlation coefficient between two circular variables.

    Parameters
    ----------
    x : 1-D array_like
        First circular variable (expressed in radians).
    y : 1-D array_like
        Second circular variable (expressed in radians).
    correction_uniform : bool
        Use correction for uniform marginals.

    Returns
    -------
    r : float
        Correlation coefficient.
    pval : float
        Uncorrected p-value.

    Notes
    -----
    Adapted from the CircStats MATLAB toolbox [1]_.

    The range of ``x`` and ``y`` must be either
    :math:`[0, 2\pi]` or :math:`[-\pi, \pi]`. If ``angles`` is not
    expressed in radians (e.g. degrees or 24-hours), please use the
    :py:func:`pingouin.convert_angles` function prior to using the present
    function.

    Please note that NaN are automatically removed.

    If the ``correction_uniform`` is True, an alternative equation from
    [2]_ (p. 177) is used. If the marginal distribution of ``x`` or ``y`` is
    uniform, the mean is not well defined, which leads to wrong estimates
    of the circular correlation. The alternative equation corrects for this
    by choosing the means in a way that maximizes the positive or negative
    correlation.

    References
    ----------
    .. [1] Berens, P. (2009). CircStat: A MATLAB Toolbox for Circular
           Statistics. Journal of Statistical Software, Articles, 31(10), 1â€“21.
           https://doi.org/10.18637/jss.v031.i10

    .. [2] Jammalamadaka, S. R., & Sengupta, A. (2001). Topics in circular
           statistics (Vol. 5). world scientific.

    Examples
    --------
    Compute the r and p-value of two circular variables

    >>> from pingouin import circ_corrcc
    >>> x = [0.785, 1.570, 3.141, 3.839, 5.934]
    >>> y = [0.593, 1.291, 2.879, 3.892, 6.108]
    >>> r, pval = circ_corrcc(x, y)
    >>> print(round(r, 3), round(pval, 4))
    0.942 0.0658

    With the correction for uniform marginals

    >>> r, pval = circ_corrcc(x, y, correction_uniform=True)
    >>> print(round(r, 3), round(pval, 4))
    0.547 0.2859
    ú"x and y must have the same length.T©Zpairedr   r'   )r   r!   Úsizer   r   r1   r   ÚsumÚsqrtr0   r,   Zmeanr   Úsf)ÚxÚyZcorrection_uniformr%   Zx_sinZy_sinr3   Zr_minusZr_plusZdenomZtvalÚpvalr   r   r   r	   Î  s&    @

2&6ÿÿc           
      C   sà   ddl m}m} t | ¡} t |¡}| j|jks8tdƒ‚t| |dd\} }| j}||t | ¡ƒd }||t 	| ¡ƒd }|t | ¡t 	| ¡ƒd }t 
|d |d  d| | |  d|d   ¡}| ||d  d¡}	||	fS )aF  Correlation coefficient between one circular and one linear variable
    random variables.

    Parameters
    ----------
    x : 1-D array_like
        First circular variable (expressed in radians).
        The range of ``x`` must be either :math:`[0, 2\pi]` or
        :math:`[-\pi, \pi]`. If ``angles`` is not
        expressed in radians (e.g. degrees or 24-hours), please use the
        :py:func:`pingouin.convert_angles` function prior to using the present
        function.
    y : 1-D array_like
        Second circular variable (linear)

    Returns
    -------
    r : float
        Correlation coefficient
    pval : float
        Uncorrected p-value

    Notes
    -----
    Please note that NaN are automatically removed from datasets.

    Examples
    --------
    Compute the r and p-value between one circular and one linear variables.

    >>> from pingouin import circ_corrcl
    >>> x = [0.785, 1.570, 3.141, 0.839, 5.934]
    >>> y = [1.593, 1.291, -0.248, -2.892, 0.102]
    >>> r, pval = circ_corrcl(x, y)
    >>> print(round(r, 3), round(pval, 3))
    0.109 0.971
    r   )ÚpearsonrÚchi2r5   Tr6   r   r   )Úscipy.statsr>   r?   r   r!   r7   r   r   r1   Úcosr9   r:   )
r;   r<   r>   r?   r%   ZrxsZrxcÚrcsr3   r=   r   r   r   r
   .  s    &

2c              	   C   s®   t  | ¡} t| ƒ |dkr,t| ƒ}t| ƒ}n.t| ƒt|ƒksDtdƒ‚t| ||ƒ}t  |¡}|| }|d | }t  t  dd|  d|d |d    ¡dd|   ¡}||fS )aŸ  Rayleigh test for non-uniformity of circular data.

    Parameters
    ----------
    angles : 1-D array_like
        Samples of angles in radians. The range of ``angles`` must be either
        :math:`[0, 2\pi]` or :math:`[-\pi, \pi]`. If ``angles`` is not
        expressed in radians (e.g. degrees or 24-hours), please use the
        :py:func:`pingouin.convert_angles` function prior to using the present
        function.
    w : array_like
        Number of incidences per bins (i.e. "weights"), in case of binned angle
        data.
    d : float
        Spacing (in radians) of bin centers for binned data. If supplied,
        a correction factor is used to correct for bias in the estimation
        of r.

    Returns
    -------
    z : float
        Z-statistic
    pval : float
        P-value

    Notes
    -----
    The Rayleigh test asks how large the resultant vector length R must be
    to indicate a non-uniform  distribution (Fisher 1995).

    H0: the population is uniformly distributed around the circle
    HA: the populatoin is not distributed uniformly around the circle

    The assumptions for the Rayleigh test are that (1) the distribution has
    only one mode and (2) the data is sampled from a von Mises distribution.

    Examples
    --------
    1. Simple Rayleigh test for non-uniformity of circular data.

    >>> from pingouin import circ_rayleigh
    >>> x = [0.785, 1.570, 3.141, 0.839, 5.934]
    >>> z, pval = circ_rayleigh(x)
    >>> print(round(z, 3), round(pval, 6))
    1.236 0.304844

    2. Specifying w and d

    >>> z, pval = circ_rayleigh(x, w=[.1, .2, .3, .4, .5], d=0.2)
    >>> print(round(z, 3), round(pval, 6))
    0.278 0.806997
    Nr&   r   r   é   )	r   r!   r   r   Úlenr   r8   r,   r9   )r   r-   r2   r3   r%   ÚRÚzr=   r   r   r   r   k  s    5


8ç        c                 C   s¦   t  | ¡} |dkr,t| ƒ}t| ƒ}t| ƒ}n8t| ƒt|ƒksDtdƒ‚t| ||ƒ}t| |ƒ}t  |¡}|| }|t  || ¡ }|t  d| ¡ }	dt	 
|	¡ }
||
fS )ac  V test for non-uniformity of circular data with a specified
    mean direction.

    Parameters
    ----------
    angles : 1-D array_like
        Samples of angles in radians. The range of ``angles`` must be either
        :math:`[0, 2\pi]` or :math:`[-\pi, \pi]`. If ``angles`` is not
        expressed in radians (e.g. degrees or 24-hours), please use the
        :py:func:`pingouin.convert_angles` function prior to using the present
        function.
    dir : float
        Suspected mean direction (angle in radians).
    w : array_like
        Number of incidences per bins (i.e. "weights"), in case of binned angle
        data.
    d : float
        Spacing (in radians) of bin centers for binned data. If supplied,
        a correction factor is used to correct for bias in the estimation
        of r.

    Returns
    -------
    V : float
        V-statistic
    pval : float
        P-value

    Notes
    -----
    H0: the population is uniformly distributed around the circle.
    HA: the population is not distributed uniformly around the circle but
    has a mean of dir.

    Note: Not rejecting H0 may mean that the population is uniformly
    distributed around the circle OR that it has a mode but that this mode
    is not centered at dir.

    The V test has more power than the Rayleigh test and is preferred if
    there is reason to believe in a specific mean direction.

    Adapted from the Matlab Circular Statistics Toolbox.

    Examples
    --------
    1. V-test for non-uniformity of circular data.

    >>> from pingouin import circ_vtest
    >>> x = [0.785, 1.570, 3.141, 0.839, 5.934]
    >>> v, pval = circ_vtest(x, dir=1)
    >>> print(round(v, 3), pval)
    2.486 0.05794648732225438

    2. Specifying w and d

    >>> v, pval = circ_vtest(x, dir=0.5, w=[.1, .2, .3, .4, .5], d=0.2)
    >>> print(round(v, 3), round(pval, 5))
    0.637 0.23086
    Nr&   r   r   )r   r!   r   r   rD   r   r8   rA   r9   r   Zcdf)r   Údirr-   r2   r3   Úmur%   rE   ÚvÚur=   r   r   r   r   ³  s    <



)N)r   r   F)Nr   )NNr   )F)NN)rG   NN)Znumpyr   r@   r   Úutilsr   Ú__all__r   r   r   r   r   r	   r
   r   r   r   r   r   r   Ú<module>   s*   ø

d)

 
`=
H