U
    Qvf                     @   s   d dl Z d dlZd dlZd dlZd dlmZ d dl	m
Z d dl	mZmZ ddddd	d
gZd dddddZdddZd ddZddddZd!ddZd"ddZd#dd	Zd$dd
ZdS )%    N)
namedtuple)_flatten_list)	remove_na_postprocess_dataframegzscore	normalityhomoscedasticityandersonepsilon
sphericity   	propagateaxisddof
nan_policyc                C   sL   t d t| } t| tjjr*tjjntj}tj	j
|| |||d}|S )a  Geometric standard (Z) score.

    Parameters
    ----------
    x : array_like
        Array of raw values.
    axis : int or None, optional
        Axis along which to operate. Default is 0. If None, compute over
        the whole array `x`.
    ddof : int, optional
        Degrees of freedom correction in the calculation of the
        standard deviation. Default is 1.
    nan_policy : {'propagate', 'raise', 'omit'}, optional
        Defines how to handle when input contains nan. 'propagate' returns nan,
        'raise' throws an error, 'omit' performs the calculations ignoring nan
        values. Default is 'propagate'.  Note that when the value is 'omit',
        nans in the input also propagate to the output, but they do not affect
        the geometric z scores computed for the non-nan values.

    Returns
    -------
    gzscore : array_like
        Array of geometric z-scores (same shape as x).

    Notes
    -----
    Geometric Z-scores are better measures of dispersion than arithmetic
    z-scores when the sample data come from a log-normally distributed
    population [1]_.

    Given the raw scores :math:`x`, the geometric mean :math:`\mu_g` and
    the geometric standard deviation :math:`\sigma_g`,
    the standard score is given by the formula:

    .. math:: z = \frac{log(x) - log(\mu_g)}{log(\sigma_g)}

    References
    ----------
    .. [1] https://en.wikipedia.org/wiki/Geometric_standard_deviation

    Examples
    --------
    Standardize a lognormal-distributed vector:

    >>> import numpy as np
    >>> from pingouin import gzscore
    >>> np.random.seed(123)
    >>> raw = np.random.lognormal(size=100)
    >>> z = gzscore(raw)
    >>> print(round(z.mean(), 3), round(z.std(), 3))
    -0.0 0.995
    z]gzscore is deprecated and will be removed in pingouin 0.7.0; use scipy.stats.gzscore instead.r   )warningswarnnpZ
asanyarray
isinstancemaZMaskedArraylogscipystatsZzscore)xr   r   r   r   z r   9/tmp/pip-unpacked-wheel-2te3nxqf/pingouin/distribution.pyr      s    5
shapiro皙?c                    s  t | tjtjttjfst|dks(tt | tjr<|  } ddg}t	t
j| t | ttjfrt| } | jdks|td| jdkstdt| } t | j}||_t|d |kdd	|d
< n&|dkr"|dkr"|  }|j fdddddj}||_t|d |kdd	|d
< ntg }|| jks<t|| jksLt| j|dd	d}|j }	|D ]v\}
}||  dkrtd|
 d tjtjtjd	d|
gd}nt||  ||d}tj||gddd}qj|	|_||j_ t!|S )u+  Univariate normality test.

    Parameters
    ----------
    data : :py:class:`pandas.DataFrame`, series, list or 1D np.array
        Iterable. Can be either a single list, 1D numpy array,
        or a wide- or long-format pandas dataframe.
    dv : str
        Dependent variable (only when ``data`` is a long-format dataframe).
    group : str
        Grouping variable (only when ``data`` is a long-format dataframe).
    method : str
        Normality test. `'shapiro'` (default) performs the Shapiro-Wilk test
        using :py:func:`scipy.stats.shapiro`, `'normaltest'` performs the
        omnibus test of normality using :py:func:`scipy.stats.normaltest`, `'jarque_bera'` performs
        the Jarque-Bera test using :py:func:`scipy.stats.jarque_bera`.
        The Omnibus and Jarque-Bera tests are more suitable than the Shapiro test for
        large samples.
    alpha : float
        Significance level.

    Returns
    -------
    stats : :py:class:`pandas.DataFrame`

        * ``'W'``: Test statistic.
        * ``'pval'``: p-value.
        * ``'normal'``: True if ``data`` is normally distributed.

    See Also
    --------
    homoscedasticity : Test equality of variance.
    sphericity : Mauchly's test for sphericity.

    Notes
    -----
    The Shapiro-Wilk test calculates a :math:`W` statistic that tests whether a
    random sample :math:`x_1, x_2, ..., x_n` comes from a normal distribution.

    The :math:`W` statistic is calculated as follows:

    .. math::

        W = \frac{(\sum_{i=1}^n a_i x_{i})^2}
        {\sum_{i=1}^n (x_i - \overline{x})^2}

    where the :math:`x_i` are the ordered sample values (in ascending
    order) and the :math:`a_i` are constants generated from the means,
    variances and covariances of the order statistics of a sample of size
    :math:`n` from a standard normal distribution. Specifically:

    .. math:: (a_1, ..., a_n) = \frac{m^TV^{-1}}{(m^TV^{-1}V^{-1}m)^{1/2}}

    with :math:`m = (m_1, ..., m_n)^T` and :math:`(m_1, ..., m_n)` are the
    expected values of the order statistics of independent and identically
    distributed random variables sampled from the standard normal distribution,
    and :math:`V` is the covariance matrix of those order statistics.

    The null-hypothesis of this test is that the population is normally
    distributed. Thus, if the p-value is less than the
    chosen alpha level (typically set at 0.05), then the null hypothesis is
    rejected and there is evidence that the data tested are not normally
    distributed.

    The result of the Shapiro-Wilk test should be interpreted with caution in
    the case of large sample sizes. Indeed, quoting from
    `Wikipedia <https://en.wikipedia.org/wiki/Shapiro%E2%80%93Wilk_test>`_:

        *"Like most statistical significance tests, if the sample size is
        sufficiently large this test may detect even trivial departures from
        the null hypothesis (i.e., although there may be some statistically
        significant effect, it may be too small to be of any practical
        significance); thus, additional investigation of the effect size is
        typically advisable, e.g., a Q–Q plot in this case."*

    Note that missing values are automatically removed (casewise deletion).

    References
    ----------
    * Shapiro, S. S., & Wilk, M. B. (1965). An analysis of variance test
      for normality (complete samples). Biometrika, 52(3/4), 591-611.

    * https://www.itl.nist.gov/div898/handbook/prc/section2/prc213.htm

    Examples
    --------
    1. Shapiro-Wilk test on a 1D array.

    >>> import numpy as np
    >>> import pingouin as pg
    >>> np.random.seed(123)
    >>> x = np.random.normal(size=100)
    >>> pg.normality(x)
             W      pval  normal
    0  0.98414  0.274886    True

    2. Omnibus test on a wide-format dataframe with missing values

    >>> data = pg.read_dataset('mediation')
    >>> data.loc[1, 'X'] = np.nan
    >>> pg.normality(data, method='normaltest').round(3)
                W   pval  normal
    X       1.792  0.408    True
    M       0.492  0.782    True
    Y       0.349  0.840    True
    Mbin  839.716  0.000   False
    Ybin  814.468  0.000   False
    W1     24.816  0.000   False
    W2     43.400  0.000   False

    3. Pandas Series

    >>> pg.normality(data['X'], method='normaltest')
              W      pval  normal
    X  1.791839  0.408232    True

    4. Long-format dataframe

    >>> data = pg.read_dataset('rm_anova2')
    >>> pg.normality(data, dv='Performance', group='Time')
                 W      pval  normal
    Time
    Pre   0.967718  0.478773    True
    Post  0.940728  0.095157    True

    5. Same but using the Jarque-Bera test

    >>> pg.normality(data, dv='Performance', group='Time', method="jarque_bera")
                 W      pval  normal
    Time
    Pre   0.304021  0.858979    True
    Post  1.265656  0.531088    True
    )r   Z
normaltestZjarque_beraWpvalr   zData must be 1D.   z#Data must have more than 3 samples.TFnormalNc                    s    |   S N)dropna)r   funcr   r   <lambda>       znormality.<locals>.<lambda>expandr   )Zresult_typer   )observedsortzGroup z. has less than 4 valid samples. Returning NaN.)r    r!   r#   index)methodalpha)r   Zignore_index)"r   pd	DataFrameZSerieslistr   ndarrayAssertionErrorZto_framegetattrr   r   Zasarrayndimsizer   Tcolumnswhere_get_numeric_dataapplygroupbygroupskeyscountr   r   nanr   to_numpyconcatr.   namer   )datadvgroupr/   r0   Z	col_namesr   numdatagrpcolsidxtmpZst_grpr   r&   r   r   L   sJ     


 levenec                 K   s  t | tjttfst| dks&tttj	|}t | tjr|dkr|dkr| 
 }|jd dksltd|| j|\}}	nT|| jkst|| jkst| j|dd| }
|
jdkstd||
t|\}}	nt | tr tdd | D stt| dkstd	|| |\}}	nDtd
d |  D s<tt| dksRtd	||  |\}}	|	|krrdnd}| dkrdnd}tj||d|	d|i|gd}t|S )a+  Test equality of variance.

    Parameters
    ----------
    data : :py:class:`pandas.DataFrame`, list or dict
        Iterable. Can be either a list / dictionnary of iterables
        or a wide- or long-format pandas dataframe.
    dv : str
        Dependent variable (only when ``data`` is a long-format dataframe).
    group : str
        Grouping variable (only when ``data`` is a long-format dataframe).
    method : str
        Statistical test. `'levene'` (default) performs the Levene test
        using :py:func:`scipy.stats.levene`, and `'bartlett'` performs the
        Bartlett test using :py:func:`scipy.stats.bartlett`.
        The former is more robust to departure from normality.
    alpha : float
        Significance level.
    **kwargs : optional
        Optional argument(s) passed to the lower-level :py:func:`scipy.stats.levene` function.

    Returns
    -------
    stats : :py:class:`pandas.DataFrame`

        * ``'W/T'``: Test statistic ('W' for Levene, 'T' for Bartlett)
        * ``'pval'``: p-value
        * ``'equal_var'``: True if ``data`` has equal variance

    See Also
    --------
    normality : Univariate normality test.
    sphericity : Mauchly's test for sphericity.

    Notes
    -----
    The **Bartlett** :math:`T` statistic [1]_ is defined as:

    .. math::

        T = \frac{(N-k) \ln{s^{2}_{p}} - \sum_{i=1}^{k}(N_{i} - 1)
        \ln{s^{2}_{i}}}{1 + (1/(3(k-1)))((\sum_{i=1}^{k}{1/(N_{i} - 1))}
        - 1/(N-k))}

    where :math:`s_i^2` is the variance of the :math:`i^{th}` group,
    :math:`N` is the total sample size, :math:`N_i` is the sample size of the
    :math:`i^{th}` group, :math:`k` is the number of groups,
    and :math:`s_p^2` is the pooled variance.

    The pooled variance is a weighted average of the group variances and is
    defined as:

    .. math:: s^{2}_{p} = \sum_{i=1}^{k}(N_{i} - 1)s^{2}_{i}/(N-k)

    The p-value is then computed using a chi-square distribution:

    .. math:: T \sim \chi^2(k-1)

    The **Levene** :math:`W` statistic [2]_ is defined as:

    .. math::

        W = \frac{(N-k)} {(k-1)}
        \frac{\sum_{i=1}^{k}N_{i}(\overline{Z}_{i.}-\overline{Z})^{2} }
        {\sum_{i=1}^{k}\sum_{j=1}^{N_i}(Z_{ij}-\overline{Z}_{i.})^{2} }

    where :math:`Z_{ij} = |Y_{ij} - \text{median}({Y}_{i.})|`,
    :math:`\overline{Z}_{i.}` are the group means of :math:`Z_{ij}` and
    :math:`\overline{Z}` is the grand mean of :math:`Z_{ij}`.

    The p-value is then computed using a F-distribution:

    .. math:: W \sim F(k-1, N-k)

    .. warning:: Missing values are not supported for this function.
        Make sure to remove them before using the
        :py:meth:`pandas.DataFrame.dropna` or :py:func:`pingouin.remove_na`
        functions.

    References
    ----------
    .. [1] Bartlett, M. S. (1937). Properties of sufficiency and statistical
           tests. Proc. R. Soc. Lond. A, 160(901), 268-282.

    .. [2] Brown, M. B., & Forsythe, A. B. (1974). Robust tests for the
           equality of variances. Journal of the American Statistical
           Association, 69(346), 364-367.

    Examples
    --------
    1. Levene test on a wide-format dataframe

    >>> import numpy as np
    >>> import pingouin as pg
    >>> data = pg.read_dataset('mediation')
    >>> pg.homoscedasticity(data[['X', 'Y', 'M']])
                   W      pval  equal_var
    levene  1.173518  0.310707       True

    2. Same data but using a long-format dataframe

    >>> data_long = data[['X', 'Y', 'M']].melt()
    >>> pg.homoscedasticity(data_long, dv="value", group="variable")
                   W      pval  equal_var
    levene  1.173518  0.310707       True

    3. Same but using a mean center

    >>> pg.homoscedasticity(data_long, dv="value", group="variable", center="mean")
                   W      pval  equal_var
    levene  1.572239  0.209303       True

    4. Bartlett test using a list of iterables

    >>> data = [[4, 8, 9, 20, 14], np.array([5, 8, 15, 45, 12])]
    >>> pg.homoscedasticity(data, method="bartlett", alpha=.05)
                     T      pval  equal_var
    bartlett  2.873569  0.090045       True
    )rN   ZbartlettNr   z$Data must have at least two columns.T)r+   c                 s   s   | ]}t |ttjfV  qd S r$   r   r3   r   r4   .0elr   r   r   	<genexpr>  s     z#homoscedasticity.<locals>.<genexpr>z&Data must have at least two iterables.c                 s   s   | ]}t |ttjfV  qd S r$   rO   rP   r   r   r   rS     s     FrN   r    r9   r!   	equal_varr-   )r   r1   r2   r3   dictr5   lowerr6   r   r   r<   shaperC   r9   r:   r>   Zngroupsr=   alllenvaluesr   )rF   rG   rH   r/   r0   kwargsr'   rI   Z	statisticprJ   rT   Z	stat_namer   r   r   r   r      s0    xZnormdistc           	      G   s   t |}tj|dd}t|}t|D ]R}tjj|| | d\}}}||k  rVdnd||< |tt	||  ||< q(|dkr|d }|d }||fS )aK  Anderson-Darling test of distribution.

    The Anderson-Darling test tests the null hypothesis that a sample is drawn from a population
    that follows a particular distribution. For the Anderson-Darling test, the critical values
    depend on which distribution is being tested against.

    This function is a wrapper around :py:func:`scipy.stats.anderson`.

    Parameters
    ----------
    sample1, sample2,... : array_like
        Array of sample data. They may be of different lengths.
    dist : string
        The type of distribution to test against. The default is 'norm'.
        Must be one of 'norm', 'expon', 'logistic', 'gumbel'.

    Returns
    -------
    from_dist : boolean
        A boolean indicating if the data comes from the tested distribution (True) or not (False).
    sig_level : float
        The significance levels for the corresponding critical values, in %.
        See :py:func:`scipy.stats.anderson` for more details.

    Examples
    --------
    1. Test that an array comes from a normal distribution

    >>> from pingouin import anderson
    >>> import numpy as np
    >>> np.random.seed(42)
    >>> x = np.random.normal(size=100)
    >>> y = np.random.normal(size=10000)
    >>> z = np.random.random(1000)
    >>> anderson(x)
    (True, 15.0)

    2. Test that multiple arrays comes from the normal distribution

    >>> anderson(x, y, z)
    (array([ True,  True, False]), array([15., 15.,  1.]))

    3. Test that an array comes from the exponential distribution

    >>> x = np.random.exponential(size=1000)
    >>> anderson(x, dist="expon")
    (True, 15.0)
    bool)dtyper]   TFr   r   )
rY   r   zerosranger   r   r	   anyZargminabs)	r^   argsk	from_distZ	sig_leveljstZcrsigr   r   r   r	     s    1
c                 C   s   | j jdkr| S | j jdkr| j  j}| jt|dd} t|}|d dksZtd|d dkrv| j	ddd} nh|d dkr| j
ddd} | jddddd	jddjdd} | j	ddd} n|d
krtd ntd| S tddS )zzCheck if data has multilevel columns for wide-format repeated measures.
    ``func`` can be either epsilon or mauchly
    r      )r   z%Factor must have at least two levels.r   )levelr   TF)rl   r   r+   Z
group_keysr
   zEpsilon values might be innaccurate in two-way repeated measures design where each  factor has more than 2 levels. Please  double-check your results.zIf using two-way repeated measures design, at least one factor must have exactly two levels. More complex designs are not yet supported.z.Only one-way or two-way designs are supported.N)r:   nlevelsZremove_unused_levelslevshapeZreorder_levelsr   Zargsortr,   r5   Z	droplevelZ
sort_indexr>   Zdiffr%   r   r   
ValueError)rF   r'   rn   r   r   r   _check_multilevel_rm  s6    
rp   c              	   C   s   || j kstd| | | jjdks2td| || j ksHtd| | |   rdtd| t|ttfrx|g}|D ]}|| j ks|td| q|| t	|||g } t
j| |||dddd} | S )zConvert long-format dataframe to wide-format.
    This internal function is used in pingouin.epsilon and pingouin.sphericity.
    z%s not in dataZbfiuz%s must be numericz Cannot have missing values in %smeanT)r.   rZ   r:   Zaggfuncr%   r+   )r:   r5   r`   kindZisnullrc   r   strint_flr1   Zpivot_table)rF   rG   withinsubjectwr   r   r   _long_to_wide_rm  s&          ry   ggc                 C   s  t | tjstdtdd |||fD r<t| |||d} |  } t| dd} | jdd}| j	\}}|d	krrd
S |j
jdkr|d }n|j
j\}	}
|	d |
d  }|dkrd| S t| }|  }|d	   }|dd	   }|||  d	 }|d |d	| |  |d	 |d	    }t|| dg}|dkr~|| | d	 }||d ||   }t|| dg}|S )a  Epsilon adjustement factor for repeated measures.

    Parameters
    ----------
    data : :py:class:`pandas.DataFrame`
        DataFrame containing the repeated measurements.
        Both wide and long-format dataframe are supported for this function.
        To test for an interaction term between two repeated measures factors
        with a wide-format dataframe, ``data`` must have a two-levels
        :py:class:`pandas.MultiIndex` columns.
    dv : string
        Name of column containing the dependent variable (only required if
        ``data`` is in long format).
    within : string
        Name of column containing the within factor (only required if ``data``
        is in long format).
        If ``within`` is a list with two strings, this function computes
        the epsilon factor for the interaction between the two within-subject
        factor.
    subject : string
        Name of column containing the subject identifier (only required if
        ``data`` is in long format).
    correction : string
        Specify the epsilon version:

        * ``'gg'``: Greenhouse-Geisser
        * ``'hf'``: Huynh-Feldt
        * ``'lb'``: Lower bound

    Returns
    -------
    eps : float
        Epsilon adjustement factor.

    See Also
    --------
    sphericity : Mauchly and JNS test for sphericity.
    homoscedasticity : Test equality of variance.

    Notes
    -----
    The lower bound epsilon is:

    .. math:: lb = \frac{1}{\text{dof}},

    where the degrees of freedom :math:`\text{dof}` is the number of groups
    :math:`k` minus 1 for one-way design and :math:`(k_1 - 1)(k_2 - 1)`
    for two-way design

    The Greenhouse-Geisser epsilon is given by:

    .. math::

        \epsilon_{GG} = \frac{k^2(\overline{\text{diag}(S)} -
        \overline{S})^2}{(k-1)(\sum_{i=1}^{k}\sum_{j=1}^{k}s_{ij}^2 -
        2k\sum_{j=1}^{k}\overline{s_i}^2 + k^2\overline{S}^2)}

    where :math:`S` is the covariance matrix, :math:`\overline{S}` the
    grandmean of S and :math:`\overline{\text{diag}(S)}` the mean of all the
    elements on the diagonal of S (i.e. mean of the variances).

    The Huynh-Feldt epsilon is given by:

    .. math::

        \epsilon_{HF} = \frac{n(k-1)\epsilon_{GG}-2}{(k-1)
        (n-1-(k-1)\epsilon_{GG})}

    where :math:`n` is the number of observations.

    Missing values are automatically removed from data (listwise deletion).

    Examples
    --------
    Using a wide-format dataframe

    >>> import pandas as pd
    >>> import pingouin as pg
    >>> data = pd.DataFrame({'A': [2.2, 3.1, 4.3, 4.1, 7.2],
    ...                      'B': [1.1, 2.5, 4.1, 5.2, 6.4],
    ...                      'C': [8.2, 4.5, 3.4, 6.2, 7.2]})
    >>> gg = pg.epsilon(data, correction='gg')
    >>> hf = pg.epsilon(data, correction='hf')
    >>> lb = pg.epsilon(data, correction='lb')
    >>> print("%.2f %.2f %.2f" % (lb, gg, hf))
    0.50 0.56 0.62

    Now using a long-format dataframe

    >>> data = pg.read_dataset('rm_anova2')
    >>> data.head()
       Subject Time   Metric  Performance
    0        1  Pre  Product           13
    1        2  Pre  Product           12
    2        3  Pre  Product           17
    3        4  Pre  Product           12
    4        5  Pre  Product           19

    Let's first calculate the epsilon of the *Time* within-subject factor

    >>> pg.epsilon(data, dv='Performance', subject='Subject',
    ...            within='Time')
    1.0

    Since *Time* has only two levels (Pre and Post), the sphericity assumption
    is necessarily met, and therefore the epsilon adjustement factor is 1.

    The *Metric* factor, however, has three levels:

    >>> round(pg.epsilon(data, dv='Performance', subject='Subject',
    ...                  within=['Metric']), 3)
    0.969

    The epsilon value is very close to 1, meaning that there is no major
    violation of sphericity.

    Now, let's calculate the epsilon for the interaction between the two
    repeated measures factor:

    >>> round(pg.epsilon(data, dv='Performance', subject='Subject',
    ...                  within=['Time', 'Metric']), 3)
    0.727

    Alternatively, we could use a wide-format dataframe with two column
    levels:

    >>> # Pivot from long-format to wide-format
    >>> piv = data.pivot(index='Subject', columns=['Time', 'Metric'], values='Performance')
    >>> piv.head()
    Time        Pre                  Post
    Metric  Product Client Action Product Client Action
    Subject
    1            13     12     17      18     30     34
    2            12     19     18       6     18     30
    3            17     19     24      21     31     32
    4            12     25     25      18     39     40
    5            19     27     19      18     28     27

    >>> round(pg.epsilon(piv), 3)
    0.727

    which gives the same epsilon value as the long-format dataframe.
     Data must be a pandas Dataframe.c                 S   s   g | ]}|d k	qS r$   r   rQ   vr   r   r   
<listcomp>  s     zepsilon.<locals>.<listcomp>rG   rv   rw   r
   r&   TZnumeric_onlyrk         ?r   ZlbZhf)r   r1   r2   r5   rX   ry   r%   rp   covrW   r:   rm   rn   r   Zdiagrq   summin)rF   rG   rv   rw   
correctionSnrf   dofkakbZmean_varZS_meanZss_matZss_rowsnumZdenepsr   r   r   r
   +  s8     

(
mauchlyc                 C   sx  t | tjstdtdd |||fD r<t| |||d} |  } t| dd} | j\}}|d }|dkr|d	t	j
t	j
dd
fS ||d  d d }	|	dkrdn|	}	| dkr| jd	d }
|
|
ddddf  |
ddddf  |
  }t	j|dd }||dk }t	|| | |  }t	|}dd|d  | d d| |d    }|d |d  |d  d|d  d|d   d|  d  d|d | | d   }|d  | | }tjj||	}tjj||	d }||||   }n@t| dd}|| }d| |d  |d|   }tjj||	}||krLd	nd}tddddddg}||||t|	|S )a`  Mauchly and JNS test for sphericity.

    Parameters
    ----------
    data : :py:class:`pandas.DataFrame`
        DataFrame containing the repeated measurements.
        Both wide and long-format dataframe are supported for this function.
        To test for an interaction term between two repeated measures factors
        with a wide-format dataframe, ``data`` must have a two-levels
        :py:class:`pandas.MultiIndex` columns.
    dv : string
        Name of column containing the dependent variable (only required if
        ``data`` is in long format).
    within : string
        Name of column containing the within factor (only required if ``data``
        is in long format).
        If ``within`` is a list with two strings, this function computes
        the epsilon factor for the interaction between the two within-subject
        factor.
    subject : string
        Name of column containing the subject identifier (only required if
        ``data`` is in long format).
    method : str
        Method to compute sphericity:

        * `'jns'`: John, Nagao and Sugiura test.
        * `'mauchly'`: Mauchly test (default).

    alpha : float
        Significance level

    Returns
    -------
    spher : boolean
        True if data have the sphericity property.
    W : float
        Test statistic.
    chi2 : float
        Chi-square statistic.
    dof : int
        Degrees of freedom.
    pval : float
        P-value.

    Raises
    ------
    ValueError
        When testing for an interaction, if both within-subject factors have
        more than 2 levels (not yet supported in Pingouin).

    See Also
    --------
    epsilon : Epsilon adjustement factor for repeated measures.
    homoscedasticity : Test equality of variance.
    normality : Univariate normality test.

    Notes
    -----
    The **Mauchly** :math:`W` statistic [1]_ is defined by:

    .. math::

        W = \frac{\prod \lambda_j}{(\frac{1}{k-1} \sum \lambda_j)^{k-1}}

    where :math:`\lambda_j` are the eigenvalues of the population
    covariance matrix (= double-centered sample covariance matrix) and
    :math:`k` is the number of conditions.

    From then, the :math:`W` statistic is transformed into a chi-square
    score using the number of observations per condition :math:`n`

    .. math:: f = \frac{2(k-1)^2+k+1}{6(k-1)(n-1)}
    .. math:: \chi_w^2 = (f-1)(n-1) \text{log}(W)

    The p-value is then approximated using a chi-square distribution:

    .. math:: \chi_w^2 \sim \chi^2(\frac{k(k-1)}{2}-1)

    The **JNS** :math:`V` statistic ([2]_, [3]_, [4]_) is defined by:

    .. math::

        V = \frac{(\sum_j^{k-1} \lambda_j)^2}{\sum_j^{k-1} \lambda_j^2}

    .. math:: \chi_v^2 = \frac{n}{2}  (k-1)^2 (V - \frac{1}{k-1})

    and the p-value approximated using a chi-square distribution

    .. math:: \chi_v^2 \sim \chi^2(\frac{k(k-1)}{2}-1)

    Missing values are automatically removed from ``data`` (listwise deletion).

    References
    ----------
    .. [1] Mauchly, J. W. (1940). Significance test for sphericity of a normal
           n-variate distribution. The Annals of Mathematical Statistics,
           11(2), 204-209.

    .. [2] Nagao, H. (1973). On some test criteria for covariance matrix.
           The Annals of Statistics, 700-709.

    .. [3] Sugiura, N. (1972). Locally best invariant test for sphericity and
           the limiting distributions. The Annals of Mathematical Statistics,
           1312-1316.

    .. [4] John, S. (1972). The distribution of a statistic used for testing
           sphericity of normal distributions. Biometrika, 59(1), 169-173.

    See also http://www.real-statistics.com/anova-repeated-measures/sphericity/

    Examples
    --------
    Mauchly test for sphericity using a wide-format dataframe

    >>> import pandas as pd
    >>> import pingouin as pg
    >>> data = pd.DataFrame({'A': [2.2, 3.1, 4.3, 4.1, 7.2],
    ...                      'B': [1.1, 2.5, 4.1, 5.2, 6.4],
    ...                      'C': [8.2, 4.5, 3.4, 6.2, 7.2]})
    >>> spher, W, chisq, dof, pval = pg.sphericity(data)
    >>> print(spher, round(W, 3), round(chisq, 3), dof, round(pval, 3))
    True 0.21 4.677 2 0.096

    John, Nagao and Sugiura (JNS) test

    >>> round(pg.sphericity(data, method='jns')[-1], 3)  # P-value only
    0.046

    Now using a long-format dataframe

    >>> data = pg.read_dataset('rm_anova2')
    >>> data.head()
       Subject Time   Metric  Performance
    0        1  Pre  Product           13
    1        2  Pre  Product           12
    2        3  Pre  Product           17
    3        4  Pre  Product           12
    4        5  Pre  Product           19

    Let's first test sphericity for the *Time* within-subject factor

    >>> pg.sphericity(data, dv='Performance', subject='Subject',
    ...            within='Time')
    (True, nan, nan, 1, 1.0)

    Since *Time* has only two levels (Pre and Post), the sphericity assumption
    is necessarily met.

    The *Metric* factor, however, has three levels:

    >>> round(pg.sphericity(data, dv='Performance', subject='Subject',
    ...                     within=['Metric'])[-1], 3)
    0.878

    The p-value value is very large, and the test therefore indicates that
    there is no violation of sphericity.

    Now, let's calculate the epsilon for the interaction between the two
    repeated measures factor. The current implementation in Pingouin only works
    if at least one of the two within-subject factors has no more than two
    levels.

    >>> spher, _, chisq, dof, pval = pg.sphericity(data, dv='Performance',
    ...                                            subject='Subject',
    ...                                            within=['Time', 'Metric'])
    >>> print(spher, round(chisq, 3), dof, round(pval, 3))
    True 3.763 2 0.152

    Here again, there is no violation of sphericity acccording to Mauchly's
    test.

    Alternatively, we could use a wide-format dataframe with two column
    levels:

    >>> # Pivot from long-format to wide-format
    >>> piv = data.pivot(index='Subject', columns=['Time', 'Metric'], values='Performance')
    >>> piv.head()
    Time        Pre                  Post
    Metric  Product Client Action Product Client Action
    Subject
    1            13     12     17      18     30     34
    2            12     19     18       6     18     30
    3            17     19     24      21     31     32
    4            12     25     25      18     39     40
    5            19     27     19      18     28     27

    >>> spher, _, chisq, dof, pval = pg.sphericity(piv)
    >>> print(spher, round(chisq, 3), dof, round(pval, 3))
    True 3.763 2 0.152

    which gives the same output as the long-format dataframe.
    r{   c                 S   s   g | ]}|d k	qS r$   r   r|   r   r   r   r~     s     zsphericity.<locals>.<listcomp>r   r   r&   r   rk   Tr   r   r   NgMbP?   r"   i      rz   )r   g      ?FSpherResultsspherr    chi2r   r!   )r   r1   r2   r5   rX   ry   r%   rp   rW   r   rB   rV   r   rC   rq   ZlinalgZeigvalshprodr   r   r   r   r   Zsfr
   r   rt   )rF   rG   rv   rw   r/   r0   r   rf   dr   r   ZS_popZeigr    ZlogWfZw2Zchi_sqp1p2r!   r   r   r   r   r   r   r     sR     B
8
(")NNr   r   )NNrN   r   )r
   )NNN)NNNrz   )NNNr   r   )r   Zscipy.statsr   Znumpyr   Zpandasr1   collectionsr   Zpingouin.utilsr   ru   r   r   __all__r   r   r   r	   rp   ry   r
   r   r   r   r   r   <module>   s$   ?
 3
 D
7

 N