AIM-PIbd-32-Kurbanova-A-A/aimenv/Lib/site-packages/scipy/stats/tests/test_stats.py
2024-10-02 22:15:59 +04:00

9040 lines
370 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

""" Test functions for stats module
WRITTEN BY LOUIS LUANGKESORN <lluang@yahoo.com> FOR THE STATS MODULE
BASED ON WILKINSON'S STATISTICS QUIZ
https://www.stanford.edu/~clint/bench/wilk.txt
Additional tests by a host of SciPy developers.
"""
import os
import re
import warnings
from collections import namedtuple
from itertools import product
import hypothesis.extra.numpy as npst
import hypothesis
import contextlib
from numpy.testing import (assert_, assert_equal,
assert_almost_equal, assert_array_almost_equal,
assert_array_equal, assert_approx_equal,
assert_allclose, suppress_warnings,
assert_array_less)
import pytest
from pytest import raises as assert_raises
import numpy.ma.testutils as mat
from numpy import array, arange, float32, float64, power
import numpy as np
import scipy.stats as stats
import scipy.stats.mstats as mstats
import scipy.stats._mstats_basic as mstats_basic
from scipy.stats._ksstats import kolmogn
from scipy.special._testutils import FuncData
from scipy.special import binom
from scipy import optimize
from .common_tests import check_named_results
from scipy.stats._axis_nan_policy import (_broadcast_concatenate, SmallSampleWarning,
too_small_nd_omit, too_small_nd_not_omit,
too_small_1d_omit, too_small_1d_not_omit)
from scipy.stats._stats_py import (_permutation_distribution_t, _chk_asarray, _moment,
LinregressResult)
from scipy._lib._util import AxisError
from scipy.conftest import array_api_compatible, skip_xp_invalid_arg
from scipy._lib._array_api import (xp_assert_close, xp_assert_equal, array_namespace,
copy, is_numpy, is_torch, SCIPY_ARRAY_API,
size as xp_size)
skip_xp_backends = pytest.mark.skip_xp_backends
""" Numbers in docstrings beginning with 'W' refer to the section numbers
and headings found in the STATISTICS QUIZ of Leland Wilkinson. These are
considered to be essential functionality. True testing and
evaluation of a statistics package requires use of the
NIST Statistical test data. See McCoullough(1999) Assessing The Reliability
of Statistical Software for a test methodology and its
implementation in testing SAS, SPSS, and S-Plus
"""
# Datasets
# These data sets are from the nasty.dat sets used by Wilkinson
# For completeness, I should write the relevant tests and count them as failures
# Somewhat acceptable, since this is still beta software. It would count as a
# good target for 1.0 status
X = array([1,2,3,4,5,6,7,8,9], float)
ZERO = array([0,0,0,0,0,0,0,0,0], float)
BIG = array([99999991,99999992,99999993,99999994,99999995,99999996,99999997,
99999998,99999999], float)
LITTLE = array([0.99999991,0.99999992,0.99999993,0.99999994,0.99999995,0.99999996,
0.99999997,0.99999998,0.99999999], float)
HUGE = array([1e+12,2e+12,3e+12,4e+12,5e+12,6e+12,7e+12,8e+12,9e+12], float)
TINY = array([1e-12,2e-12,3e-12,4e-12,5e-12,6e-12,7e-12,8e-12,9e-12], float)
ROUND = array([0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5,8.5], float)
class TestTrimmedStats:
# TODO: write these tests to handle missing values properly
dprec = np.finfo(np.float64).precision
def test_tmean(self):
y = stats.tmean(X, (2, 8), (True, True))
assert_approx_equal(y, 5.0, significant=self.dprec)
y1 = stats.tmean(X, limits=(2, 8), inclusive=(False, False))
y2 = stats.tmean(X, limits=None)
assert_approx_equal(y1, y2, significant=self.dprec)
x_2d = arange(63, dtype=float64).reshape(9, 7)
y = stats.tmean(x_2d, axis=None)
assert_approx_equal(y, x_2d.mean(), significant=self.dprec)
y = stats.tmean(x_2d, axis=0)
assert_array_almost_equal(y, x_2d.mean(axis=0), decimal=8)
y = stats.tmean(x_2d, axis=1)
assert_array_almost_equal(y, x_2d.mean(axis=1), decimal=8)
y = stats.tmean(x_2d, limits=(2, 61), axis=None)
assert_approx_equal(y, 31.5, significant=self.dprec)
y = stats.tmean(x_2d, limits=(2, 21), axis=0)
y_true = [14, 11.5, 9, 10, 11, 12, 13]
assert_array_almost_equal(y, y_true, decimal=8)
y = stats.tmean(x_2d, limits=(2, 21), inclusive=(True, False), axis=0)
y_true = [10.5, 11.5, 9, 10, 11, 12, 13]
assert_array_almost_equal(y, y_true, decimal=8)
x_2d_with_nan = np.array(x_2d)
x_2d_with_nan[-1, -3:] = np.nan
y = stats.tmean(x_2d_with_nan, limits=(1, 13), axis=0)
y_true = [7, 4.5, 5.5, 6.5, np.nan, np.nan, np.nan]
assert_array_almost_equal(y, y_true, decimal=8)
with suppress_warnings() as sup:
sup.record(RuntimeWarning, "Mean of empty slice")
y = stats.tmean(x_2d, limits=(2, 21), axis=1)
y_true = [4, 10, 17, 21, np.nan, np.nan, np.nan, np.nan, np.nan]
assert_array_almost_equal(y, y_true, decimal=8)
y = stats.tmean(x_2d, limits=(2, 21),
inclusive=(False, True), axis=1)
y_true = [4.5, 10, 17, 21, np.nan, np.nan, np.nan, np.nan, np.nan]
assert_array_almost_equal(y, y_true, decimal=8)
def test_tvar(self):
y = stats.tvar(X, limits=(2, 8), inclusive=(True, True))
assert_approx_equal(y, 4.6666666666666661, significant=self.dprec)
y = stats.tvar(X, limits=None)
assert_approx_equal(y, X.var(ddof=1), significant=self.dprec)
x_2d = arange(63, dtype=float64).reshape((9, 7))
y = stats.tvar(x_2d, axis=None)
assert_approx_equal(y, x_2d.var(ddof=1), significant=self.dprec)
y = stats.tvar(x_2d, axis=0)
assert_array_almost_equal(y[0], np.full((1, 7), 367.50000000), decimal=8)
y = stats.tvar(x_2d, axis=1)
assert_array_almost_equal(y[0], np.full((1, 9), 4.66666667), decimal=8)
y = stats.tvar(x_2d[3, :])
assert_approx_equal(y, 4.666666666666667, significant=self.dprec)
with suppress_warnings() as sup:
sup.record(RuntimeWarning, "Degrees of freedom <= 0 for slice.")
# Limiting some values along one axis
y = stats.tvar(x_2d, limits=(1, 5), axis=1, inclusive=(True, True))
assert_approx_equal(y[0], 2.5, significant=self.dprec)
# Limiting all values along one axis
y = stats.tvar(x_2d, limits=(0, 6), axis=1, inclusive=(True, True))
assert_approx_equal(y[0], 4.666666666666667, significant=self.dprec)
assert_equal(y[1], np.nan)
def test_tstd(self):
y = stats.tstd(X, (2, 8), (True, True))
assert_approx_equal(y, 2.1602468994692865, significant=self.dprec)
y = stats.tstd(X, limits=None)
assert_approx_equal(y, X.std(ddof=1), significant=self.dprec)
def test_tmin(self):
assert_equal(stats.tmin(4), 4)
x = np.arange(10)
assert_equal(stats.tmin(x), 0)
assert_equal(stats.tmin(x, lowerlimit=0), 0)
assert_equal(stats.tmin(x, lowerlimit=0, inclusive=False), 1)
x = x.reshape((5, 2))
assert_equal(stats.tmin(x, lowerlimit=0, inclusive=False), [2, 1])
assert_equal(stats.tmin(x, axis=1), [0, 2, 4, 6, 8])
assert_equal(stats.tmin(x, axis=None), 0)
x = np.arange(10.)
x[9] = np.nan
with suppress_warnings() as sup:
sup.record(RuntimeWarning, "invalid value*")
assert_equal(stats.tmin(x), np.nan)
assert_equal(stats.tmin(x, nan_policy='omit'), 0.)
assert_raises(ValueError, stats.tmin, x, nan_policy='raise')
assert_raises(ValueError, stats.tmin, x, nan_policy='foobar')
msg = "nan_policy must be one of..."
with assert_raises(ValueError, match=msg):
stats.tmin(x, nan_policy='foo')
# check that that if a full slice is masked, the output returns a
# nan instead of a garbage value.
with suppress_warnings() as sup:
sup.filter(RuntimeWarning, "All-NaN slice encountered")
x = np.arange(16).reshape(4, 4)
res = stats.tmin(x, lowerlimit=4, axis=1)
assert_equal(res, [np.nan, 4, 8, 12])
def test_tmax(self):
assert_equal(stats.tmax(4), 4)
x = np.arange(10)
assert_equal(stats.tmax(x), 9)
assert_equal(stats.tmax(x, upperlimit=9), 9)
assert_equal(stats.tmax(x, upperlimit=9, inclusive=False), 8)
x = x.reshape((5, 2))
assert_equal(stats.tmax(x, upperlimit=9, inclusive=False), [8, 7])
assert_equal(stats.tmax(x, axis=1), [1, 3, 5, 7, 9])
assert_equal(stats.tmax(x, axis=None), 9)
x = np.arange(10.)
x[6] = np.nan
with suppress_warnings() as sup:
sup.record(RuntimeWarning, "invalid value*")
assert_equal(stats.tmax(x), np.nan)
assert_equal(stats.tmax(x, nan_policy='omit'), 9.)
assert_raises(ValueError, stats.tmax, x, nan_policy='raise')
assert_raises(ValueError, stats.tmax, x, nan_policy='foobar')
# check that that if a full slice is masked, the output returns a
# nan instead of a garbage value.
with suppress_warnings() as sup:
sup.filter(RuntimeWarning, "All-NaN slice encountered")
x = np.arange(16).reshape(4, 4)
res = stats.tmax(x, upperlimit=11, axis=1)
assert_equal(res, [3, 7, 11, np.nan])
def test_tsem(self):
y = stats.tsem(X, limits=(3, 8), inclusive=(False, True))
y_ref = np.array([4, 5, 6, 7, 8])
assert_approx_equal(y, y_ref.std(ddof=1) / np.sqrt(y_ref.size),
significant=self.dprec)
assert_approx_equal(stats.tsem(X, limits=[-1, 10]),
stats.tsem(X, limits=None),
significant=self.dprec)
class TestPearsonrWilkinson:
""" W.II.D. Compute a correlation matrix on all the variables.
All the correlations, except for ZERO and MISS, should be exactly 1.
ZERO and MISS should have undefined or missing correlations with the
other variables. The same should go for SPEARMAN correlations, if
your program has them.
"""
def test_pXX(self):
y = stats.pearsonr(X,X)
r = y[0]
assert_approx_equal(r,1.0)
def test_pXBIG(self):
y = stats.pearsonr(X,BIG)
r = y[0]
assert_approx_equal(r,1.0)
def test_pXLITTLE(self):
y = stats.pearsonr(X,LITTLE)
r = y[0]
assert_approx_equal(r,1.0)
def test_pXHUGE(self):
y = stats.pearsonr(X,HUGE)
r = y[0]
assert_approx_equal(r,1.0)
def test_pXTINY(self):
y = stats.pearsonr(X,TINY)
r = y[0]
assert_approx_equal(r,1.0)
def test_pXROUND(self):
y = stats.pearsonr(X,ROUND)
r = y[0]
assert_approx_equal(r,1.0)
def test_pBIGBIG(self):
y = stats.pearsonr(BIG,BIG)
r = y[0]
assert_approx_equal(r,1.0)
def test_pBIGLITTLE(self):
y = stats.pearsonr(BIG,LITTLE)
r = y[0]
assert_approx_equal(r,1.0)
def test_pBIGHUGE(self):
y = stats.pearsonr(BIG,HUGE)
r = y[0]
assert_approx_equal(r,1.0)
def test_pBIGTINY(self):
y = stats.pearsonr(BIG,TINY)
r = y[0]
assert_approx_equal(r,1.0)
def test_pBIGROUND(self):
y = stats.pearsonr(BIG,ROUND)
r = y[0]
assert_approx_equal(r,1.0)
def test_pLITTLELITTLE(self):
y = stats.pearsonr(LITTLE,LITTLE)
r = y[0]
assert_approx_equal(r,1.0)
def test_pLITTLEHUGE(self):
y = stats.pearsonr(LITTLE,HUGE)
r = y[0]
assert_approx_equal(r,1.0)
def test_pLITTLETINY(self):
y = stats.pearsonr(LITTLE,TINY)
r = y[0]
assert_approx_equal(r,1.0)
def test_pLITTLEROUND(self):
y = stats.pearsonr(LITTLE,ROUND)
r = y[0]
assert_approx_equal(r,1.0)
def test_pHUGEHUGE(self):
y = stats.pearsonr(HUGE,HUGE)
r = y[0]
assert_approx_equal(r,1.0)
def test_pHUGETINY(self):
y = stats.pearsonr(HUGE,TINY)
r = y[0]
assert_approx_equal(r,1.0)
def test_pHUGEROUND(self):
y = stats.pearsonr(HUGE,ROUND)
r = y[0]
assert_approx_equal(r,1.0)
def test_pTINYTINY(self):
y = stats.pearsonr(TINY,TINY)
r = y[0]
assert_approx_equal(r,1.0)
def test_pTINYROUND(self):
y = stats.pearsonr(TINY,ROUND)
r = y[0]
assert_approx_equal(r,1.0)
def test_pROUNDROUND(self):
y = stats.pearsonr(ROUND,ROUND)
r = y[0]
assert_approx_equal(r,1.0)
@array_api_compatible
@pytest.mark.usefixtures("skip_xp_backends")
@skip_xp_backends(cpu_only=True)
class TestPearsonr:
@skip_xp_backends(np_only=True)
def test_pearsonr_result_attributes(self):
res = stats.pearsonr(X, X)
attributes = ('correlation', 'pvalue')
check_named_results(res, attributes)
assert_equal(res.correlation, res.statistic)
@skip_xp_backends('jax.numpy',
reasons=['JAX arrays do not support item assignment'],
cpu_only=True)
def test_r_almost_exactly_pos1(self, xp):
a = xp.arange(3.0)
r, prob = stats.pearsonr(a, a)
xp_assert_close(r, xp.asarray(1.0), atol=1e-15)
# With n = len(a) = 3, the error in prob grows like the
# square root of the error in r.
xp_assert_close(prob, xp.asarray(0.0), atol=np.sqrt(2*np.spacing(1.0)))
@skip_xp_backends('jax.numpy',
reasons=['JAX arrays do not support item assignment'],
cpu_only=True)
def test_r_almost_exactly_neg1(self, xp):
a = xp.arange(3.0)
r, prob = stats.pearsonr(a, -a)
xp_assert_close(r, xp.asarray(-1.0), atol=1e-15)
# With n = len(a) = 3, the error in prob grows like the
# square root of the error in r.
xp_assert_close(prob, xp.asarray(0.0), atol=np.sqrt(2*np.spacing(1.0)))
@skip_xp_backends('jax.numpy',
reasons=['JAX arrays do not support item assignment'],
cpu_only=True)
def test_basic(self, xp):
# A basic test, with a correlation coefficient
# that is not 1 or -1.
a = xp.asarray([-1, 0, 1])
b = xp.asarray([0, 0, 3])
r, prob = stats.pearsonr(a, b)
xp_assert_close(r, xp.asarray(3**0.5/2))
xp_assert_close(prob, xp.asarray(1/3))
@skip_xp_backends('jax.numpy',
reasons=['JAX arrays do not support item assignment'],
cpu_only=True)
def test_constant_input(self, xp):
# Zero variance input
# See https://github.com/scipy/scipy/issues/3728
msg = "An input array is constant"
with pytest.warns(stats.ConstantInputWarning, match=msg):
x = xp.asarray([0.667, 0.667, 0.667])
y = xp.asarray([0.123, 0.456, 0.789])
r, p = stats.pearsonr(x, y)
xp_assert_close(r, xp.asarray(xp.nan))
xp_assert_close(p, xp.asarray(xp.nan))
@skip_xp_backends('jax.numpy',
reasons=['JAX arrays do not support item assignment'],
cpu_only=True)
@pytest.mark.parametrize('dtype', ['float32', 'float64'])
def test_near_constant_input(self, xp, dtype):
npdtype = getattr(np, dtype)
dtype = getattr(xp, dtype)
# Near constant input (but not constant):
x = xp.asarray([2, 2, 2 + np.spacing(2, dtype=npdtype)], dtype=dtype)
y = xp.asarray([3, 3, 3 + 6*np.spacing(3, dtype=npdtype)], dtype=dtype)
msg = "An input array is nearly constant; the computed"
with pytest.warns(stats.NearConstantInputWarning, match=msg):
# r and p are garbage, so don't bother checking them in this case.
# (The exact value of r would be 1.)
stats.pearsonr(x, y)
@skip_xp_backends('jax.numpy',
reasons=['JAX arrays do not support item assignment'],
cpu_only=True)
def test_very_small_input_values(self, xp):
# Very small values in an input. A naive implementation will
# suffer from underflow.
# See https://github.com/scipy/scipy/issues/9353
x = xp.asarray([0.004434375, 0.004756007, 0.003911996, 0.0038005, 0.003409971],
dtype=xp.float64)
y = xp.asarray([2.48e-188, 7.41e-181, 4.09e-208, 2.08e-223, 2.66e-245],
dtype=xp.float64)
r, p = stats.pearsonr(x, y)
# The expected values were computed using mpmath with 80 digits
# of precision.
xp_assert_close(r, xp.asarray(0.7272930540750450, dtype=xp.float64))
xp_assert_close(p, xp.asarray(0.1637805429533202, dtype=xp.float64))
@skip_xp_backends('jax.numpy',
reasons=['JAX arrays do not support item assignment'],
cpu_only=True)
def test_very_large_input_values(self, xp):
# Very large values in an input. A naive implementation will
# suffer from overflow.
# See https://github.com/scipy/scipy/issues/8980
x = 1e90*xp.asarray([0, 0, 0, 1, 1, 1, 1], dtype=xp.float64)
y = 1e90*xp.arange(7, dtype=xp.float64)
r, p = stats.pearsonr(x, y)
# The expected values were computed using mpmath with 80 digits
# of precision.
xp_assert_close(r, xp.asarray(0.8660254037844386, dtype=xp.float64))
xp_assert_close(p, xp.asarray(0.011724811003954638, dtype=xp.float64))
@skip_xp_backends('jax.numpy',
reasons=['JAX arrays do not support item assignment'],
cpu_only=True)
def test_extremely_large_input_values(self, xp):
# Extremely large values in x and y. These values would cause the
# product sigma_x * sigma_y to overflow if the two factors were
# computed independently.
x = xp.asarray([2.3e200, 4.5e200, 6.7e200, 8e200], dtype=xp.float64)
y = xp.asarray([1.2e199, 5.5e200, 3.3e201, 1.0e200], dtype=xp.float64)
r, p = stats.pearsonr(x, y)
# The expected values were computed using mpmath with 80 digits
# of precision.
xp_assert_close(r, xp.asarray(0.351312332103289, dtype=xp.float64))
xp_assert_close(p, xp.asarray(0.648687667896711, dtype=xp.float64))
def test_length_two_pos1(self, xp):
# Inputs with length 2.
# See https://github.com/scipy/scipy/issues/7730
x = xp.asarray([1., 2.])
y = xp.asarray([3., 5.])
res = stats.pearsonr(x, y)
r, p = res
one = xp.asarray(1.)
xp_assert_equal(r, one)
xp_assert_equal(p, one)
low, high = res.confidence_interval()
xp_assert_equal(low, -one)
xp_assert_equal(high, one)
def test_length_two_neg1(self, xp):
# Inputs with length 2.
# See https://github.com/scipy/scipy/issues/7730
x = xp.asarray([2., 1.])
y = xp.asarray([3., 5.])
res = stats.pearsonr(x, y)
r, p = res
one = xp.asarray(1.)
xp_assert_equal(r, -one)
xp_assert_equal(p, one)
low, high = res.confidence_interval()
xp_assert_equal(low, -one)
xp_assert_equal(high, one)
# Expected values computed with R 3.6.2 cor.test, e.g.
# options(digits=16)
# x <- c(1, 2, 3, 4)
# y <- c(0, 1, 0.5, 1)
# cor.test(x, y, method = "pearson", alternative = "g")
# correlation coefficient and p-value for alternative='two-sided'
# calculated with mpmath agree to 16 digits.
@pytest.mark.skip_xp_backends(np_only=True)
@pytest.mark.parametrize('alternative, pval, rlow, rhigh, sign',
[('two-sided', 0.325800137536, -0.814938968841, 0.99230697523, 1),
('less', 0.8370999312316, -1, 0.985600937290653, 1),
('greater', 0.1629000687684, -0.6785654158217636, 1, 1),
('two-sided', 0.325800137536, -0.992306975236, 0.81493896884, -1),
('less', 0.1629000687684, -1.0, 0.6785654158217636, -1),
('greater', 0.8370999312316, -0.985600937290653, 1.0, -1)])
def test_basic_example(self, alternative, pval, rlow, rhigh, sign):
x = [1, 2, 3, 4]
y = np.array([0, 1, 0.5, 1]) * sign
result = stats.pearsonr(x, y, alternative=alternative)
assert_allclose(result.statistic, 0.6741998624632421*sign, rtol=1e-12)
assert_allclose(result.pvalue, pval, rtol=1e-6)
ci = result.confidence_interval()
assert_allclose(ci, (rlow, rhigh), rtol=1e-6)
@skip_xp_backends('jax.numpy',
reasons=['JAX arrays do not support item assignment'],
cpu_only=True)
def test_negative_correlation_pvalue_gh17795(self, xp):
x = xp.arange(10.)
y = -x
test_greater = stats.pearsonr(x, y, alternative='greater')
test_less = stats.pearsonr(x, y, alternative='less')
xp_assert_close(test_greater.pvalue, xp.asarray(1.))
xp_assert_close(test_less.pvalue, xp.asarray(0.), atol=1e-20)
@skip_xp_backends('jax.numpy',
reasons=['JAX arrays do not support item assignment'],
cpu_only=True)
def test_length3_r_exactly_negative_one(self, xp):
x = xp.asarray([1., 2., 3.])
y = xp.asarray([5., -4., -13.])
res = stats.pearsonr(x, y)
# The expected r and p are exact.
r, p = res
one = xp.asarray(1.0)
xp_assert_close(r, -one)
xp_assert_close(p, 0*one, atol=1e-7)
low, high = res.confidence_interval()
xp_assert_equal(low, -one)
xp_assert_equal(high, one)
@pytest.mark.skip_xp_backends(np_only=True)
def test_input_validation(self):
x = [1, 2, 3]
y = [4, 5]
message = '`x` and `y` must have the same length along `axis`.'
with pytest.raises(ValueError, match=message):
stats.pearsonr(x, y)
x = [1]
y = [2]
message = '`x` and `y` must have length at least 2.'
with pytest.raises(ValueError, match=message):
stats.pearsonr(x, y)
x = [-1j, -2j, -3.0j]
y = [-1j, -2j, -3.0j]
message = 'This function does not support complex data'
with pytest.raises(ValueError, match=message):
stats.pearsonr(x, y)
message = "`method` must be an instance of..."
with pytest.raises(ValueError, match=message):
stats.pearsonr([1, 2], [3, 4], method="asymptotic")
res = stats.pearsonr([1, 2], [3, 4])
with pytest.raises(ValueError, match=message):
res.confidence_interval(method="exact")
@pytest.mark.fail_slow(2)
@pytest.mark.skip_xp_backends(np_only=True)
@pytest.mark.xfail_on_32bit("Monte Carlo method needs > a few kB of memory")
@pytest.mark.parametrize('alternative', ('less', 'greater', 'two-sided'))
@pytest.mark.parametrize('method', ('permutation', 'monte_carlo'))
def test_resampling_pvalue(self, method, alternative):
rng = np.random.default_rng(24623935790378923)
size = (2, 100) if method == 'permutation' else (2, 1000)
x = rng.normal(size=size)
y = rng.normal(size=size)
methods = {'permutation': stats.PermutationMethod(random_state=rng),
'monte_carlo': stats.MonteCarloMethod(rvs=(rng.normal,)*2)}
method = methods[method]
res = stats.pearsonr(x, y, alternative=alternative, method=method, axis=-1)
ref = stats.pearsonr(x, y, alternative=alternative, axis=-1)
assert_allclose(res.statistic, ref.statistic, rtol=1e-15)
assert_allclose(res.pvalue, ref.pvalue, rtol=1e-2, atol=1e-3)
@pytest.mark.skip_xp_backends(np_only=True)
@pytest.mark.parametrize('alternative', ('less', 'greater', 'two-sided'))
def test_bootstrap_ci(self, alternative):
rng = np.random.default_rng(2462935790378923)
x = rng.normal(size=(2, 100))
y = rng.normal(size=(2, 100))
res = stats.pearsonr(x, y, alternative=alternative, axis=-1)
method = stats.BootstrapMethod(random_state=rng)
res_ci = res.confidence_interval(method=method)
ref_ci = res.confidence_interval()
assert_allclose(res_ci, ref_ci, atol=1.5e-2)
@pytest.mark.skip_xp_backends(np_only=True)
@pytest.mark.parametrize('axis', [0, 1])
def test_axis01(self, axis, xp):
rng = np.random.default_rng(38572345825)
shape = (9, 10)
x, y = rng.normal(size=(2,) + shape)
res = stats.pearsonr(x, y, axis=axis)
ci = res.confidence_interval()
if axis == 0:
x, y = x.T, y.T
for i in range(x.shape[0]):
res_i = stats.pearsonr(x[i], y[i])
ci_i = res_i.confidence_interval()
assert_allclose(res.statistic[i], res_i.statistic)
assert_allclose(res.pvalue[i], res_i.pvalue)
assert_allclose(ci.low[i], ci_i.low)
assert_allclose(ci.high[i], ci_i.high)
@pytest.mark.skip_xp_backends(np_only=True)
def test_axis_None(self, xp):
rng = np.random.default_rng(38572345825)
shape = (9, 10)
x, y = rng.normal(size=(2,) + shape)
res = stats.pearsonr(x, y, axis=None)
ci = res.confidence_interval()
ref = stats.pearsonr(x.ravel(), y.ravel())
ci_ref = ref.confidence_interval()
assert_allclose(res.statistic, ref.statistic)
assert_allclose(res.pvalue, ref.pvalue)
assert_allclose(ci, ci_ref)
def test_nd_input_validation(self, xp):
x = y = xp.ones((2, 5))
message = '`axis` must be an integer.'
with pytest.raises(ValueError, match=message):
stats.pearsonr(x, y, axis=1.5)
message = '`x` and `y` must have the same length along `axis`'
with pytest.raises(ValueError, match=message):
stats.pearsonr(x, xp.ones((2, 1)), axis=1)
message = '`x` and `y` must have length at least 2.'
with pytest.raises(ValueError, match=message):
stats.pearsonr(xp.ones((2, 1)), xp.ones((2, 1)), axis=1)
message = '`x` and `y` must be broadcastable.'
with pytest.raises(ValueError, match=message):
stats.pearsonr(x, xp.ones((3, 5)), axis=1)
message = '`method` must be `None` if arguments are not NumPy arrays.'
if not is_numpy(xp):
x = xp.arange(10)
with pytest.raises(ValueError, match=message):
stats.pearsonr(x, x, method=stats.PermutationMethod())
@skip_xp_backends('jax.numpy',
reasons=['JAX arrays do not support item assignment'],
cpu_only=True)
def test_nd_special_cases(self, xp):
rng = np.random.default_rng(34989235492245)
x0 = xp.asarray(rng.random((3, 5)))
y0 = xp.asarray(rng.random((3, 5)))
message = 'An input array is constant'
with pytest.warns(stats.ConstantInputWarning, match=message):
x = copy(x0)
x[0, ...] = 1
res = stats.pearsonr(x, y0, axis=1)
ci = res.confidence_interval()
nan = xp.asarray(xp.nan, dtype=xp.float64)
xp_assert_equal(res.statistic[0], nan)
xp_assert_equal(res.pvalue[0], nan)
xp_assert_equal(ci.low[0], nan)
xp_assert_equal(ci.high[0], nan)
assert not xp.any(xp.isnan(res.statistic[1:]))
assert not xp.any(xp.isnan(res.pvalue[1:]))
assert not xp.any(xp.isnan(ci.low[1:]))
assert not xp.any(xp.isnan(ci.high[1:]))
message = 'An input array is nearly constant'
with pytest.warns(stats.NearConstantInputWarning, match=message):
x[0, 0] = 1 + 1e-15
stats.pearsonr(x, y0, axis=1)
# length 2 along axis
x = xp.asarray([[1, 2], [1, 2], [2, 1], [2, 1.]])
y = xp.asarray([[1, 2], [2, 1], [1, 2], [2, 1.]])
ones = xp.ones(4)
res = stats.pearsonr(x, y, axis=-1)
ci = res.confidence_interval()
xp_assert_close(res.statistic, xp.asarray([1, -1, -1, 1.]))
xp_assert_close(res.pvalue, ones)
xp_assert_close(ci.low, -ones)
xp_assert_close(ci.high, ones)
@skip_xp_backends('jax.numpy',
reasons=['JAX arrays do not support item assignment'],
cpu_only=True)
@pytest.mark.parametrize('axis', [0, 1, None])
@pytest.mark.parametrize('alternative', ['less', 'greater', 'two-sided'])
def test_array_api(self, xp, axis, alternative):
x, y = rng.normal(size=(2, 10, 11))
res = stats.pearsonr(xp.asarray(x), xp.asarray(y),
axis=axis, alternative=alternative)
ref = stats.pearsonr(x, y, axis=axis, alternative=alternative)
xp_assert_close(res.statistic, xp.asarray(ref.statistic))
xp_assert_close(res.pvalue, xp.asarray(ref.pvalue))
res_ci = res.confidence_interval()
ref_ci = ref.confidence_interval()
xp_assert_close(res_ci.low, xp.asarray(ref_ci.low))
xp_assert_close(res_ci.high, xp.asarray(ref_ci.high))
class TestFisherExact:
"""Some tests to show that fisher_exact() works correctly.
Note that in SciPy 0.9.0 this was not working well for large numbers due to
inaccuracy of the hypergeom distribution (see #1218). Fixed now.
Also note that R and SciPy have different argument formats for their
hypergeometric distribution functions.
R:
> phyper(18999, 99000, 110000, 39000, lower.tail = FALSE)
[1] 1.701815e-09
"""
def test_basic(self):
fisher_exact = stats.fisher_exact
res = fisher_exact([[14500, 20000], [30000, 40000]])[1]
assert_approx_equal(res, 0.01106, significant=4)
res = fisher_exact([[100, 2], [1000, 5]])[1]
assert_approx_equal(res, 0.1301, significant=4)
res = fisher_exact([[2, 7], [8, 2]])[1]
assert_approx_equal(res, 0.0230141, significant=6)
res = fisher_exact([[5, 1], [10, 10]])[1]
assert_approx_equal(res, 0.1973244, significant=6)
res = fisher_exact([[5, 15], [20, 20]])[1]
assert_approx_equal(res, 0.0958044, significant=6)
res = fisher_exact([[5, 16], [20, 25]])[1]
assert_approx_equal(res, 0.1725862, significant=6)
res = fisher_exact([[10, 5], [10, 1]])[1]
assert_approx_equal(res, 0.1973244, significant=6)
res = fisher_exact([[5, 0], [1, 4]])[1]
assert_approx_equal(res, 0.04761904, significant=6)
res = fisher_exact([[0, 1], [3, 2]])[1]
assert_approx_equal(res, 1.0)
res = fisher_exact([[0, 2], [6, 4]])[1]
assert_approx_equal(res, 0.4545454545)
res = fisher_exact([[2, 7], [8, 2]])
assert_approx_equal(res[1], 0.0230141, significant=6)
assert_approx_equal(res[0], 4.0 / 56)
def test_precise(self):
# results from R
#
# R defines oddsratio differently (see Notes section of fisher_exact
# docstring), so those will not match. We leave them in anyway, in
# case they will be useful later on. We test only the p-value.
tablist = [
([[100, 2], [1000, 5]], (2.505583993422285e-001, 1.300759363430016e-001)),
([[2, 7], [8, 2]], (8.586235135736206e-002, 2.301413756522114e-002)),
([[5, 1], [10, 10]], (4.725646047336584e+000, 1.973244147157190e-001)),
([[5, 15], [20, 20]], (3.394396617440852e-001, 9.580440012477637e-002)),
([[5, 16], [20, 25]], (3.960558326183334e-001, 1.725864953812994e-001)),
([[10, 5], [10, 1]], (2.116112781158483e-001, 1.973244147157190e-001)),
([[10, 5], [10, 0]], (0.000000000000000e+000, 6.126482213438734e-002)),
([[5, 0], [1, 4]], (np.inf, 4.761904761904762e-002)),
([[0, 5], [1, 4]], (0.000000000000000e+000, 1.000000000000000e+000)),
([[5, 1], [0, 4]], (np.inf, 4.761904761904758e-002)),
([[0, 1], [3, 2]], (0.000000000000000e+000, 1.000000000000000e+000))
]
for table, res_r in tablist:
res = stats.fisher_exact(np.asarray(table))
np.testing.assert_almost_equal(res[1], res_r[1], decimal=11,
verbose=True)
def test_gh4130(self):
# Previously, a fudge factor used to distinguish between theoeretically
# and numerically different probability masses was 1e-4; it has been
# tightened to fix gh4130. Accuracy checked against R fisher.test.
# options(digits=16)
# table <- matrix(c(6, 108, 37, 200), nrow = 2)
# fisher.test(table, alternative = "t")
x = [[6, 37], [108, 200]]
res = stats.fisher_exact(x)
assert_allclose(res[1], 0.005092697748126)
# case from https://github.com/brentp/fishers_exact_test/issues/27
# That package has an (absolute?) fudge factor of 1e-6; too big
x = [[22, 0], [0, 102]]
res = stats.fisher_exact(x)
assert_allclose(res[1], 7.175066786244549e-25)
# case from https://github.com/brentp/fishers_exact_test/issues/1
x = [[94, 48], [3577, 16988]]
res = stats.fisher_exact(x)
assert_allclose(res[1], 2.069356340993818e-37)
def test_gh9231(self):
# Previously, fisher_exact was extremely slow for this table
# As reported in gh-9231, the p-value should be very nearly zero
x = [[5829225, 5692693], [5760959, 5760959]]
res = stats.fisher_exact(x)
assert_allclose(res[1], 0, atol=1e-170)
@pytest.mark.slow
def test_large_numbers(self):
# Test with some large numbers. Regression test for #1401
pvals = [5.56e-11, 2.666e-11, 1.363e-11] # from R
for pval, num in zip(pvals, [75, 76, 77]):
res = stats.fisher_exact([[17704, 496], [1065, num]])[1]
assert_approx_equal(res, pval, significant=4)
res = stats.fisher_exact([[18000, 80000], [20000, 90000]])[1]
assert_approx_equal(res, 0.2751, significant=4)
def test_raises(self):
# test we raise an error for wrong shape of input.
assert_raises(ValueError, stats.fisher_exact,
np.arange(6).reshape(2, 3))
def test_row_or_col_zero(self):
tables = ([[0, 0], [5, 10]],
[[5, 10], [0, 0]],
[[0, 5], [0, 10]],
[[5, 0], [10, 0]])
for table in tables:
oddsratio, pval = stats.fisher_exact(table)
assert_equal(pval, 1.0)
assert_equal(oddsratio, np.nan)
def test_less_greater(self):
tables = (
# Some tables to compare with R:
[[2, 7], [8, 2]],
[[200, 7], [8, 300]],
[[28, 21], [6, 1957]],
[[190, 800], [200, 900]],
# Some tables with simple exact values
# (includes regression test for ticket #1568):
[[0, 2], [3, 0]],
[[1, 1], [2, 1]],
[[2, 0], [1, 2]],
[[0, 1], [2, 3]],
[[1, 0], [1, 4]],
)
pvals = (
# from R:
[0.018521725952066501, 0.9990149169715733],
[1.0, 2.0056578803889148e-122],
[1.0, 5.7284374608319831e-44],
[0.7416227, 0.2959826],
# Exact:
[0.1, 1.0],
[0.7, 0.9],
[1.0, 0.3],
[2./3, 1.0],
[1.0, 1./3],
)
for table, pval in zip(tables, pvals):
res = []
res.append(stats.fisher_exact(table, alternative="less")[1])
res.append(stats.fisher_exact(table, alternative="greater")[1])
assert_allclose(res, pval, atol=0, rtol=1e-7)
def test_gh3014(self):
# check if issue #3014 has been fixed.
# before, this would have risen a ValueError
odds, pvalue = stats.fisher_exact([[1, 2], [9, 84419233]])
@pytest.mark.parametrize("alternative", ['two-sided', 'less', 'greater'])
def test_result(self, alternative):
table = np.array([[14500, 20000], [30000, 40000]])
res = stats.fisher_exact(table, alternative=alternative)
assert_equal((res.statistic, res.pvalue), res)
class TestCorrSpearmanr:
""" W.II.D. Compute a correlation matrix on all the variables.
All the correlations, except for ZERO and MISS, should be exactly 1.
ZERO and MISS should have undefined or missing correlations with the
other variables. The same should go for SPEARMAN correlations, if
your program has them.
"""
def test_scalar(self):
y = stats.spearmanr(4., 2.)
assert_(np.isnan(y).all())
def test_uneven_lengths(self):
assert_raises(ValueError, stats.spearmanr, [1, 2, 1], [8, 9])
assert_raises(ValueError, stats.spearmanr, [1, 2, 1], 8)
def test_uneven_2d_shapes(self):
# Different number of columns should work - those just get concatenated.
np.random.seed(232324)
x = np.random.randn(4, 3)
y = np.random.randn(4, 2)
assert stats.spearmanr(x, y).statistic.shape == (5, 5)
assert stats.spearmanr(x.T, y.T, axis=1).pvalue.shape == (5, 5)
assert_raises(ValueError, stats.spearmanr, x, y, axis=1)
assert_raises(ValueError, stats.spearmanr, x.T, y.T)
def test_ndim_too_high(self):
np.random.seed(232324)
x = np.random.randn(4, 3, 2)
assert_raises(ValueError, stats.spearmanr, x)
assert_raises(ValueError, stats.spearmanr, x, x)
assert_raises(ValueError, stats.spearmanr, x, None, None)
# But should work with axis=None (raveling axes) for two input arrays
assert_allclose(stats.spearmanr(x, x, axis=None),
stats.spearmanr(x.flatten(), x.flatten(), axis=0))
def test_nan_policy(self):
x = np.arange(10.)
x[9] = np.nan
assert_array_equal(stats.spearmanr(x, x), (np.nan, np.nan))
assert_array_equal(stats.spearmanr(x, x, nan_policy='omit'),
(1.0, 0.0))
assert_raises(ValueError, stats.spearmanr, x, x, nan_policy='raise')
assert_raises(ValueError, stats.spearmanr, x, x, nan_policy='foobar')
def test_nan_policy_bug_12458(self):
np.random.seed(5)
x = np.random.rand(5, 10)
k = 6
x[:, k] = np.nan
y = np.delete(x, k, axis=1)
corx, px = stats.spearmanr(x, nan_policy='omit')
cory, py = stats.spearmanr(y)
corx = np.delete(np.delete(corx, k, axis=1), k, axis=0)
px = np.delete(np.delete(px, k, axis=1), k, axis=0)
assert_allclose(corx, cory, atol=1e-14)
assert_allclose(px, py, atol=1e-14)
def test_nan_policy_bug_12411(self):
np.random.seed(5)
m = 5
n = 10
x = np.random.randn(m, n)
x[1, 0] = np.nan
x[3, -1] = np.nan
corr, pvalue = stats.spearmanr(x, axis=1, nan_policy="propagate")
res = [[stats.spearmanr(x[i, :], x[j, :]).statistic for i in range(m)]
for j in range(m)]
assert_allclose(corr, res)
def test_sXX(self):
y = stats.spearmanr(X,X)
r = y[0]
assert_approx_equal(r,1.0)
def test_sXBIG(self):
y = stats.spearmanr(X,BIG)
r = y[0]
assert_approx_equal(r,1.0)
def test_sXLITTLE(self):
y = stats.spearmanr(X,LITTLE)
r = y[0]
assert_approx_equal(r,1.0)
def test_sXHUGE(self):
y = stats.spearmanr(X,HUGE)
r = y[0]
assert_approx_equal(r,1.0)
def test_sXTINY(self):
y = stats.spearmanr(X,TINY)
r = y[0]
assert_approx_equal(r,1.0)
def test_sXROUND(self):
y = stats.spearmanr(X,ROUND)
r = y[0]
assert_approx_equal(r,1.0)
def test_sBIGBIG(self):
y = stats.spearmanr(BIG,BIG)
r = y[0]
assert_approx_equal(r,1.0)
def test_sBIGLITTLE(self):
y = stats.spearmanr(BIG,LITTLE)
r = y[0]
assert_approx_equal(r,1.0)
def test_sBIGHUGE(self):
y = stats.spearmanr(BIG,HUGE)
r = y[0]
assert_approx_equal(r,1.0)
def test_sBIGTINY(self):
y = stats.spearmanr(BIG,TINY)
r = y[0]
assert_approx_equal(r,1.0)
def test_sBIGROUND(self):
y = stats.spearmanr(BIG,ROUND)
r = y[0]
assert_approx_equal(r,1.0)
def test_sLITTLELITTLE(self):
y = stats.spearmanr(LITTLE,LITTLE)
r = y[0]
assert_approx_equal(r,1.0)
def test_sLITTLEHUGE(self):
y = stats.spearmanr(LITTLE,HUGE)
r = y[0]
assert_approx_equal(r,1.0)
def test_sLITTLETINY(self):
y = stats.spearmanr(LITTLE,TINY)
r = y[0]
assert_approx_equal(r,1.0)
def test_sLITTLEROUND(self):
y = stats.spearmanr(LITTLE,ROUND)
r = y[0]
assert_approx_equal(r,1.0)
def test_sHUGEHUGE(self):
y = stats.spearmanr(HUGE,HUGE)
r = y[0]
assert_approx_equal(r,1.0)
def test_sHUGETINY(self):
y = stats.spearmanr(HUGE,TINY)
r = y[0]
assert_approx_equal(r,1.0)
def test_sHUGEROUND(self):
y = stats.spearmanr(HUGE,ROUND)
r = y[0]
assert_approx_equal(r,1.0)
def test_sTINYTINY(self):
y = stats.spearmanr(TINY,TINY)
r = y[0]
assert_approx_equal(r,1.0)
def test_sTINYROUND(self):
y = stats.spearmanr(TINY,ROUND)
r = y[0]
assert_approx_equal(r,1.0)
def test_sROUNDROUND(self):
y = stats.spearmanr(ROUND,ROUND)
r = y[0]
assert_approx_equal(r,1.0)
def test_spearmanr_result_attributes(self):
res = stats.spearmanr(X, X)
attributes = ('correlation', 'pvalue')
check_named_results(res, attributes)
assert_equal(res.correlation, res.statistic)
def test_1d_vs_2d(self):
x1 = [1, 2, 3, 4, 5, 6]
x2 = [1, 2, 3, 4, 6, 5]
res1 = stats.spearmanr(x1, x2)
res2 = stats.spearmanr(np.asarray([x1, x2]).T)
assert_allclose(res1, res2)
def test_1d_vs_2d_nans(self):
# Now the same with NaNs present. Regression test for gh-9103.
for nan_policy in ['propagate', 'omit']:
x1 = [1, np.nan, 3, 4, 5, 6]
x2 = [1, 2, 3, 4, 6, np.nan]
res1 = stats.spearmanr(x1, x2, nan_policy=nan_policy)
res2 = stats.spearmanr(np.asarray([x1, x2]).T, nan_policy=nan_policy)
assert_allclose(res1, res2)
def test_3cols(self):
x1 = np.arange(6)
x2 = -x1
x3 = np.array([0, 1, 2, 3, 5, 4])
x = np.asarray([x1, x2, x3]).T
actual = stats.spearmanr(x)
expected_corr = np.array([[1, -1, 0.94285714],
[-1, 1, -0.94285714],
[0.94285714, -0.94285714, 1]])
expected_pvalue = np.zeros((3, 3), dtype=float)
expected_pvalue[2, 0:2] = 0.00480466472
expected_pvalue[0:2, 2] = 0.00480466472
assert_allclose(actual.statistic, expected_corr)
assert_allclose(actual.pvalue, expected_pvalue)
def test_gh_9103(self):
# Regression test for gh-9103.
x = np.array([[np.nan, 3.0, 4.0, 5.0, 5.1, 6.0, 9.2],
[5.0, np.nan, 4.1, 4.8, 4.9, 5.0, 4.1],
[0.5, 4.0, 7.1, 3.8, 8.0, 5.1, 7.6]]).T
corr = np.array([[np.nan, np.nan, np.nan],
[np.nan, np.nan, np.nan],
[np.nan, np.nan, 1.]])
assert_allclose(stats.spearmanr(x, nan_policy='propagate').statistic,
corr)
res = stats.spearmanr(x, nan_policy='omit').statistic
assert_allclose((res[0][1], res[0][2], res[1][2]),
(0.2051957, 0.4857143, -0.4707919), rtol=1e-6)
def test_gh_8111(self):
# Regression test for gh-8111 (different result for float/int/bool).
n = 100
np.random.seed(234568)
x = np.random.rand(n)
m = np.random.rand(n) > 0.7
# bool against float, no nans
a = (x > .5)
b = np.array(x)
res1 = stats.spearmanr(a, b, nan_policy='omit').statistic
# bool against float with NaNs
b[m] = np.nan
res2 = stats.spearmanr(a, b, nan_policy='omit').statistic
# int against float with NaNs
a = a.astype(np.int32)
res3 = stats.spearmanr(a, b, nan_policy='omit').statistic
expected = [0.865895477, 0.866100381, 0.866100381]
assert_allclose([res1, res2, res3], expected)
class TestCorrSpearmanr2:
"""Some further tests of the spearmanr function."""
def test_spearmanr_vs_r(self):
# Cross-check with R:
# cor.test(c(1,2,3,4,5),c(5,6,7,8,7),method="spearmanr")
x1 = [1, 2, 3, 4, 5]
x2 = [5, 6, 7, 8, 7]
expected = (0.82078268166812329, 0.088587005313543798)
res = stats.spearmanr(x1, x2)
assert_approx_equal(res[0], expected[0])
assert_approx_equal(res[1], expected[1])
def test_empty_arrays(self):
assert_equal(stats.spearmanr([], []), (np.nan, np.nan))
def test_normal_draws(self):
np.random.seed(7546)
x = np.array([np.random.normal(loc=1, scale=1, size=500),
np.random.normal(loc=1, scale=1, size=500)])
corr = [[1.0, 0.3],
[0.3, 1.0]]
x = np.dot(np.linalg.cholesky(corr), x)
expected = (0.28659685838743354, 6.579862219051161e-11)
res = stats.spearmanr(x[0], x[1])
assert_approx_equal(res[0], expected[0])
assert_approx_equal(res[1], expected[1])
def test_corr_1(self):
assert_approx_equal(stats.spearmanr([1, 1, 2], [1, 1, 2])[0], 1.0)
def test_nan_policies(self):
x = np.arange(10.)
x[9] = np.nan
assert_array_equal(stats.spearmanr(x, x), (np.nan, np.nan))
assert_allclose(stats.spearmanr(x, x, nan_policy='omit'),
(1.0, 0))
assert_raises(ValueError, stats.spearmanr, x, x, nan_policy='raise')
assert_raises(ValueError, stats.spearmanr, x, x, nan_policy='foobar')
def test_unequal_lengths(self):
x = np.arange(10.)
y = np.arange(20.)
assert_raises(ValueError, stats.spearmanr, x, y)
def test_omit_paired_value(self):
x1 = [1, 2, 3, 4]
x2 = [8, 7, 6, np.nan]
res1 = stats.spearmanr(x1, x2, nan_policy='omit')
res2 = stats.spearmanr(x1[:3], x2[:3], nan_policy='omit')
assert_equal(res1, res2)
def test_gh_issue_6061_windows_overflow(self):
x = list(range(2000))
y = list(range(2000))
y[0], y[9] = y[9], y[0]
y[10], y[434] = y[434], y[10]
y[435], y[1509] = y[1509], y[435]
# rho = 1 - 6 * (2 * (9^2 + 424^2 + 1074^2))/(2000 * (2000^2 - 1))
# = 1 - (1 / 500)
# = 0.998
x.append(np.nan)
y.append(3.0)
assert_almost_equal(stats.spearmanr(x, y, nan_policy='omit')[0], 0.998)
def test_tie0(self):
# with only ties in one or both inputs
warn_msg = "An input array is constant"
with pytest.warns(stats.ConstantInputWarning, match=warn_msg):
r, p = stats.spearmanr([2, 2, 2], [2, 2, 2])
assert_equal(r, np.nan)
assert_equal(p, np.nan)
r, p = stats.spearmanr([2, 0, 2], [2, 2, 2])
assert_equal(r, np.nan)
assert_equal(p, np.nan)
r, p = stats.spearmanr([2, 2, 2], [2, 0, 2])
assert_equal(r, np.nan)
assert_equal(p, np.nan)
def test_tie1(self):
# Data
x = [1.0, 2.0, 3.0, 4.0]
y = [1.0, 2.0, 2.0, 3.0]
# Ranks of the data, with tie-handling.
xr = [1.0, 2.0, 3.0, 4.0]
yr = [1.0, 2.5, 2.5, 4.0]
# Result of spearmanr should be the same as applying
# pearsonr to the ranks.
sr = stats.spearmanr(x, y)
pr = stats.pearsonr(xr, yr)
assert_almost_equal(sr, pr)
def test_tie2(self):
# Test tie-handling if inputs contain nan's
# Data without nan's
x1 = [1, 2, 2.5, 2]
y1 = [1, 3, 2.5, 4]
# Same data with nan's
x2 = [1, 2, 2.5, 2, np.nan]
y2 = [1, 3, 2.5, 4, np.nan]
# Results for two data sets should be the same if nan's are ignored
sr1 = stats.spearmanr(x1, y1)
sr2 = stats.spearmanr(x2, y2, nan_policy='omit')
assert_almost_equal(sr1, sr2)
def test_ties_axis_1(self):
z1 = np.array([[1, 1, 1, 1], [1, 2, 3, 4]])
z2 = np.array([[1, 2, 3, 4], [1, 1, 1, 1]])
z3 = np.array([[1, 1, 1, 1], [1, 1, 1, 1]])
warn_msg = "An input array is constant"
with pytest.warns(stats.ConstantInputWarning, match=warn_msg):
r, p = stats.spearmanr(z1, axis=1)
assert_equal(r, np.nan)
assert_equal(p, np.nan)
r, p = stats.spearmanr(z2, axis=1)
assert_equal(r, np.nan)
assert_equal(p, np.nan)
r, p = stats.spearmanr(z3, axis=1)
assert_equal(r, np.nan)
assert_equal(p, np.nan)
def test_gh_11111(self):
x = np.array([1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0])
y = np.array([0, 0.009783728115345005, 0, 0, 0.0019759230121848587,
0.0007535430349118562, 0.0002661781514710257, 0, 0,
0.0007835762419683435])
warn_msg = "An input array is constant"
with pytest.warns(stats.ConstantInputWarning, match=warn_msg):
r, p = stats.spearmanr(x, y)
assert_equal(r, np.nan)
assert_equal(p, np.nan)
def test_index_error(self):
x = np.array([1.0, 7.0, 2.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0])
y = np.array([0, 0.009783728115345005, 0, 0, 0.0019759230121848587,
0.0007535430349118562, 0.0002661781514710257, 0, 0,
0.0007835762419683435])
assert_raises(ValueError, stats.spearmanr, x, y, axis=2)
def test_alternative(self):
# Test alternative parameter
# Simple test - Based on the above ``test_spearmanr_vs_r``
x1 = [1, 2, 3, 4, 5]
x2 = [5, 6, 7, 8, 7]
# strong positive correlation
expected = (0.82078268166812329, 0.088587005313543798)
# correlation > 0 -> large "less" p-value
res = stats.spearmanr(x1, x2, alternative="less")
assert_approx_equal(res[0], expected[0])
assert_approx_equal(res[1], 1 - (expected[1] / 2))
# correlation > 0 -> small "less" p-value
res = stats.spearmanr(x1, x2, alternative="greater")
assert_approx_equal(res[0], expected[0])
assert_approx_equal(res[1], expected[1] / 2)
with pytest.raises(ValueError, match="`alternative` must be 'less'..."):
stats.spearmanr(x1, x2, alternative="ekki-ekki")
@pytest.mark.parametrize("alternative", ('two-sided', 'less', 'greater'))
def test_alternative_nan_policy(self, alternative):
# Test nan policies
x1 = [1, 2, 3, 4, 5]
x2 = [5, 6, 7, 8, 7]
x1nan = x1 + [np.nan]
x2nan = x2 + [np.nan]
# test nan_policy="propagate"
assert_array_equal(stats.spearmanr(x1nan, x2nan), (np.nan, np.nan))
# test nan_policy="omit"
res_actual = stats.spearmanr(x1nan, x2nan, nan_policy='omit',
alternative=alternative)
res_expected = stats.spearmanr(x1, x2, alternative=alternative)
assert_allclose(res_actual, res_expected)
# test nan_policy="raise"
message = 'The input contains nan values'
with pytest.raises(ValueError, match=message):
stats.spearmanr(x1nan, x2nan, nan_policy='raise',
alternative=alternative)
# test invalid nan_policy
message = "nan_policy must be one of..."
with pytest.raises(ValueError, match=message):
stats.spearmanr(x1nan, x2nan, nan_policy='ekki-ekki',
alternative=alternative)
# W.II.E. Tabulate X against X, using BIG as a case weight. The values
# should appear on the diagonal and the total should be 899999955.
# If the table cannot hold these values, forget about working with
# census data. You can also tabulate HUGE against TINY. There is no
# reason a tabulation program should not be able to distinguish
# different values regardless of their magnitude.
# I need to figure out how to do this one.
def test_kendalltau():
# For the cases without ties, both variants should give the same
# result.
variants = ('b', 'c')
# case without ties, con-dis equal zero
x = [5, 2, 1, 3, 6, 4, 7, 8]
y = [5, 2, 6, 3, 1, 8, 7, 4]
# Cross-check with exact result from R:
# cor.test(x,y,method="kendall",exact=1)
expected = (0.0, 1.0)
for taux in variants:
res = stats.kendalltau(x, y)
assert_approx_equal(res[0], expected[0])
assert_approx_equal(res[1], expected[1])
# case without ties, con-dis equal zero
x = [0, 5, 2, 1, 3, 6, 4, 7, 8]
y = [5, 2, 0, 6, 3, 1, 8, 7, 4]
# Cross-check with exact result from R:
# cor.test(x,y,method="kendall",exact=1)
expected = (0.0, 1.0)
for taux in variants:
res = stats.kendalltau(x, y)
assert_approx_equal(res[0], expected[0])
assert_approx_equal(res[1], expected[1])
# case without ties, con-dis close to zero
x = [5, 2, 1, 3, 6, 4, 7]
y = [5, 2, 6, 3, 1, 7, 4]
# Cross-check with exact result from R:
# cor.test(x,y,method="kendall",exact=1)
expected = (-0.14285714286, 0.77261904762)
for taux in variants:
res = stats.kendalltau(x, y)
assert_approx_equal(res[0], expected[0])
assert_approx_equal(res[1], expected[1])
# case without ties, con-dis close to zero
x = [2, 1, 3, 6, 4, 7, 8]
y = [2, 6, 3, 1, 8, 7, 4]
# Cross-check with exact result from R:
# cor.test(x,y,method="kendall",exact=1)
expected = (0.047619047619, 1.0)
for taux in variants:
res = stats.kendalltau(x, y)
assert_approx_equal(res[0], expected[0])
assert_approx_equal(res[1], expected[1])
# simple case without ties
x = np.arange(10)
y = np.arange(10)
# Cross-check with exact result from R:
# cor.test(x,y,method="kendall",exact=1)
expected = (1.0, 5.511463844797e-07)
for taux in variants:
res = stats.kendalltau(x, y, variant=taux)
assert_approx_equal(res[0], expected[0])
assert_approx_equal(res[1], expected[1])
# swap a couple of values
b = y[1]
y[1] = y[2]
y[2] = b
# Cross-check with exact result from R:
# cor.test(x,y,method="kendall",exact=1)
expected = (0.9555555555555556, 5.511463844797e-06)
for taux in variants:
res = stats.kendalltau(x, y, variant=taux)
assert_approx_equal(res[0], expected[0])
assert_approx_equal(res[1], expected[1])
# swap a couple more
b = y[5]
y[5] = y[6]
y[6] = b
# Cross-check with exact result from R:
# cor.test(x,y,method="kendall",exact=1)
expected = (0.9111111111111111, 2.976190476190e-05)
for taux in variants:
res = stats.kendalltau(x, y, variant=taux)
assert_approx_equal(res[0], expected[0])
assert_approx_equal(res[1], expected[1])
# same in opposite direction
x = np.arange(10)
y = np.arange(10)[::-1]
# Cross-check with exact result from R:
# cor.test(x,y,method="kendall",exact=1)
expected = (-1.0, 5.511463844797e-07)
for taux in variants:
res = stats.kendalltau(x, y, variant=taux)
assert_approx_equal(res[0], expected[0])
assert_approx_equal(res[1], expected[1])
# swap a couple of values
b = y[1]
y[1] = y[2]
y[2] = b
# Cross-check with exact result from R:
# cor.test(x,y,method="kendall",exact=1)
expected = (-0.9555555555555556, 5.511463844797e-06)
for taux in variants:
res = stats.kendalltau(x, y, variant=taux)
assert_approx_equal(res[0], expected[0])
assert_approx_equal(res[1], expected[1])
# swap a couple more
b = y[5]
y[5] = y[6]
y[6] = b
# Cross-check with exact result from R:
# cor.test(x,y,method="kendall",exact=1)
expected = (-0.9111111111111111, 2.976190476190e-05)
for taux in variants:
res = stats.kendalltau(x, y, variant=taux)
assert_approx_equal(res[0], expected[0])
assert_approx_equal(res[1], expected[1])
# Check a case where variants are different
# Example values found from Kendall (1970).
# P-value is the same for the both variants
x = array([1, 2, 2, 4, 4, 6, 6, 8, 9, 9])
y = array([1, 2, 4, 4, 4, 4, 8, 8, 8, 10])
expected = 0.85895569
assert_approx_equal(stats.kendalltau(x, y, variant='b')[0], expected)
expected = 0.825
assert_approx_equal(stats.kendalltau(x, y, variant='c')[0], expected)
# check exception in case of ties and method='exact' requested
y[2] = y[1]
assert_raises(ValueError, stats.kendalltau, x, y, method='exact')
# check exception in case of invalid method keyword
assert_raises(ValueError, stats.kendalltau, x, y, method='banana')
# check exception in case of invalid variant keyword
assert_raises(ValueError, stats.kendalltau, x, y, variant='rms')
# tau-b with some ties
# Cross-check with R:
# cor.test(c(12,2,1,12,2),c(1,4,7,1,0),method="kendall",exact=FALSE)
x1 = [12, 2, 1, 12, 2]
x2 = [1, 4, 7, 1, 0]
expected = (-0.47140452079103173, 0.28274545993277478)
res = stats.kendalltau(x1, x2)
assert_approx_equal(res[0], expected[0])
assert_approx_equal(res[1], expected[1])
# test for namedtuple attribute results
attributes = ('correlation', 'pvalue')
for taux in variants:
res = stats.kendalltau(x1, x2, variant=taux)
check_named_results(res, attributes)
assert_equal(res.correlation, res.statistic)
# with only ties in one or both inputs in tau-b or tau-c
for taux in variants:
assert_equal(stats.kendalltau([2, 2, 2], [2, 2, 2], variant=taux),
(np.nan, np.nan))
assert_equal(stats.kendalltau([2, 0, 2], [2, 2, 2], variant=taux),
(np.nan, np.nan))
assert_equal(stats.kendalltau([2, 2, 2], [2, 0, 2], variant=taux),
(np.nan, np.nan))
# empty arrays provided as input
assert_equal(stats.kendalltau([], []), (np.nan, np.nan))
# check with larger arrays
np.random.seed(7546)
x = np.array([np.random.normal(loc=1, scale=1, size=500),
np.random.normal(loc=1, scale=1, size=500)])
corr = [[1.0, 0.3],
[0.3, 1.0]]
x = np.dot(np.linalg.cholesky(corr), x)
expected = (0.19291382765531062, 1.1337095377742629e-10)
res = stats.kendalltau(x[0], x[1])
assert_approx_equal(res[0], expected[0])
assert_approx_equal(res[1], expected[1])
# this should result in 1 for taub but not tau-c
assert_approx_equal(stats.kendalltau([1, 1, 2], [1, 1, 2], variant='b')[0],
1.0)
assert_approx_equal(stats.kendalltau([1, 1, 2], [1, 1, 2], variant='c')[0],
0.88888888)
# test nan_policy
x = np.arange(10.)
x[9] = np.nan
assert_array_equal(stats.kendalltau(x, x), (np.nan, np.nan))
assert_allclose(stats.kendalltau(x, x, nan_policy='omit'),
(1.0, 5.5114638e-6), rtol=1e-06)
assert_allclose(stats.kendalltau(x, x, nan_policy='omit', method='asymptotic'),
(1.0, 0.00017455009626808976), rtol=1e-06)
assert_raises(ValueError, stats.kendalltau, x, x, nan_policy='raise')
assert_raises(ValueError, stats.kendalltau, x, x, nan_policy='foobar')
# test unequal length inputs
x = np.arange(10.)
y = np.arange(20.)
assert_raises(ValueError, stats.kendalltau, x, y)
# test all ties
tau, p_value = stats.kendalltau([], [])
assert_equal(np.nan, tau)
assert_equal(np.nan, p_value)
tau, p_value = stats.kendalltau([0], [0])
assert_equal(np.nan, tau)
assert_equal(np.nan, p_value)
# Regression test for GitHub issue #6061 - Overflow on Windows
x = np.arange(2000, dtype=float)
x = np.ma.masked_greater(x, 1995)
y = np.arange(2000, dtype=float)
y = np.concatenate((y[1000:], y[:1000]))
assert_(np.isfinite(stats.kendalltau(x,y)[1]))
def test_kendalltau_vs_mstats_basic():
np.random.seed(42)
for s in range(2,10):
a = []
# Generate rankings with ties
for i in range(s):
a += [i]*i
b = list(a)
np.random.shuffle(a)
np.random.shuffle(b)
expected = mstats_basic.kendalltau(a, b)
actual = stats.kendalltau(a, b)
assert_approx_equal(actual[0], expected[0])
assert_approx_equal(actual[1], expected[1])
def test_kendalltau_nan_2nd_arg():
# regression test for gh-6134: nans in the second arg were not handled
x = [1., 2., 3., 4.]
y = [np.nan, 2.4, 3.4, 3.4]
r1 = stats.kendalltau(x, y, nan_policy='omit')
r2 = stats.kendalltau(x[1:], y[1:])
assert_allclose(r1.statistic, r2.statistic, atol=1e-15)
def test_kendalltau_gh18139_overflow():
# gh-18139 reported an overflow in `kendalltau` that appeared after
# SciPy 0.15.1. Check that this particular overflow does not occur.
# (Test would fail if warning were emitted.)
import random
random.seed(6272161)
classes = [1, 2, 3, 4, 5, 6, 7]
n_samples = 2 * 10 ** 5
x = random.choices(classes, k=n_samples)
y = random.choices(classes, k=n_samples)
res = stats.kendalltau(x, y)
# Reference value from SciPy 0.15.1
assert_allclose(res.statistic, 0.0011816493905730343)
# Reference p-value from `permutation_test` w/ n_resamples=9999 (default).
# Expected to be accurate to at least two digits.
assert_allclose(res.pvalue, 0.4894, atol=2e-3)
class TestKendallTauAlternative:
def test_kendalltau_alternative_asymptotic(self):
# Test alternative parameter, asymptotic method (due to tie)
# Based on TestCorrSpearman2::test_alternative
x1 = [1, 2, 3, 4, 5]
x2 = [5, 6, 7, 8, 7]
# strong positive correlation
expected = stats.kendalltau(x1, x2, alternative="two-sided")
assert expected[0] > 0
# rank correlation > 0 -> large "less" p-value
res = stats.kendalltau(x1, x2, alternative="less")
assert_equal(res[0], expected[0])
assert_allclose(res[1], 1 - (expected[1] / 2))
# rank correlation > 0 -> small "greater" p-value
res = stats.kendalltau(x1, x2, alternative="greater")
assert_equal(res[0], expected[0])
assert_allclose(res[1], expected[1] / 2)
# reverse the direction of rank correlation
x2.reverse()
# strong negative correlation
expected = stats.kendalltau(x1, x2, alternative="two-sided")
assert expected[0] < 0
# rank correlation < 0 -> large "greater" p-value
res = stats.kendalltau(x1, x2, alternative="greater")
assert_equal(res[0], expected[0])
assert_allclose(res[1], 1 - (expected[1] / 2))
# rank correlation < 0 -> small "less" p-value
res = stats.kendalltau(x1, x2, alternative="less")
assert_equal(res[0], expected[0])
assert_allclose(res[1], expected[1] / 2)
with pytest.raises(ValueError, match="`alternative` must be 'less'..."):
stats.kendalltau(x1, x2, alternative="ekki-ekki")
# There are a lot of special cases considered in the calculation of the
# exact p-value, so we test each separately. We also need to test
# separately when the observed statistic is in the left tail vs the right
# tail because the code leverages symmetry of the null distribution; to
# do that we use the same test case but negate one of the samples.
# Reference values computed using R cor.test, e.g.
# options(digits=16)
# x <- c(44.4, 45.9, 41.9, 53.3, 44.7, 44.1, 50.7, 45.2, 60.1)
# y <- c( 2.6, 3.1, 2.5, 5.0, 3.6, 4.0, 5.2, 2.8, 3.8)
# cor.test(x, y, method = "kendall", alternative = "g")
alternatives = ('less', 'two-sided', 'greater')
p_n1 = [np.nan, np.nan, np.nan]
p_n2 = [1, 1, 0.5]
p_c0 = [1, 0.3333333333333, 0.1666666666667]
p_c1 = [0.9583333333333, 0.3333333333333, 0.1666666666667]
p_no_correlation = [0.5916666666667, 1, 0.5916666666667]
p_no_correlationb = [0.5475694444444, 1, 0.5475694444444]
p_n_lt_171 = [0.9624118165785, 0.1194389329806, 0.0597194664903]
p_n_lt_171b = [0.246236925303, 0.4924738506059, 0.755634083327]
p_n_lt_171c = [0.9847475308925, 0.03071385306533, 0.01535692653267]
def exact_test(self, x, y, alternative, rev, stat_expected, p_expected):
if rev:
y = -np.asarray(y)
stat_expected *= -1
res = stats.kendalltau(x, y, method='exact', alternative=alternative)
res_expected = stat_expected, p_expected
assert_allclose(res, res_expected)
case_R_n1 = (list(zip(alternatives, p_n1, [False]*3))
+ list(zip(alternatives, reversed(p_n1), [True]*3)))
@pytest.mark.parametrize("alternative, p_expected, rev", case_R_n1)
def test_against_R_n1(self, alternative, p_expected, rev):
x, y = [1], [2]
stat_expected = np.nan
self.exact_test(x, y, alternative, rev, stat_expected, p_expected)
case_R_n2 = (list(zip(alternatives, p_n2, [False]*3))
+ list(zip(alternatives, reversed(p_n2), [True]*3)))
@pytest.mark.parametrize("alternative, p_expected, rev", case_R_n2)
def test_against_R_n2(self, alternative, p_expected, rev):
x, y = [1, 2], [3, 4]
stat_expected = 0.9999999999999998
self.exact_test(x, y, alternative, rev, stat_expected, p_expected)
case_R_c0 = (list(zip(alternatives, p_c0, [False]*3))
+ list(zip(alternatives, reversed(p_c0), [True]*3)))
@pytest.mark.parametrize("alternative, p_expected, rev", case_R_c0)
def test_against_R_c0(self, alternative, p_expected, rev):
x, y = [1, 2, 3], [1, 2, 3]
stat_expected = 1
self.exact_test(x, y, alternative, rev, stat_expected, p_expected)
case_R_c1 = (list(zip(alternatives, p_c1, [False]*3))
+ list(zip(alternatives, reversed(p_c1), [True]*3)))
@pytest.mark.parametrize("alternative, p_expected, rev", case_R_c1)
def test_against_R_c1(self, alternative, p_expected, rev):
x, y = [1, 2, 3, 4], [1, 2, 4, 3]
stat_expected = 0.6666666666666667
self.exact_test(x, y, alternative, rev, stat_expected, p_expected)
case_R_no_corr = (list(zip(alternatives, p_no_correlation, [False]*3))
+ list(zip(alternatives, reversed(p_no_correlation),
[True]*3)))
@pytest.mark.parametrize("alternative, p_expected, rev", case_R_no_corr)
def test_against_R_no_correlation(self, alternative, p_expected, rev):
x, y = [1, 2, 3, 4, 5], [1, 5, 4, 2, 3]
stat_expected = 0
self.exact_test(x, y, alternative, rev, stat_expected, p_expected)
case_no_cor_b = (list(zip(alternatives, p_no_correlationb, [False]*3))
+ list(zip(alternatives, reversed(p_no_correlationb),
[True]*3)))
@pytest.mark.parametrize("alternative, p_expected, rev", case_no_cor_b)
def test_against_R_no_correlationb(self, alternative, p_expected, rev):
x, y = [1, 2, 3, 4, 5, 6, 7, 8], [8, 6, 1, 3, 2, 5, 4, 7]
stat_expected = 0
self.exact_test(x, y, alternative, rev, stat_expected, p_expected)
case_R_lt_171 = (list(zip(alternatives, p_n_lt_171, [False]*3))
+ list(zip(alternatives, reversed(p_n_lt_171), [True]*3)))
@pytest.mark.parametrize("alternative, p_expected, rev", case_R_lt_171)
def test_against_R_lt_171(self, alternative, p_expected, rev):
# Data from Hollander & Wolfe (1973), p. 187f.
# Used from https://rdrr.io/r/stats/cor.test.html
x = [44.4, 45.9, 41.9, 53.3, 44.7, 44.1, 50.7, 45.2, 60.1]
y = [2.6, 3.1, 2.5, 5.0, 3.6, 4.0, 5.2, 2.8, 3.8]
stat_expected = 0.4444444444444445
self.exact_test(x, y, alternative, rev, stat_expected, p_expected)
case_R_lt_171b = (list(zip(alternatives, p_n_lt_171b, [False]*3))
+ list(zip(alternatives, reversed(p_n_lt_171b),
[True]*3)))
@pytest.mark.parametrize("alternative, p_expected, rev", case_R_lt_171b)
def test_against_R_lt_171b(self, alternative, p_expected, rev):
np.random.seed(0)
x = np.random.rand(100)
y = np.random.rand(100)
stat_expected = -0.04686868686868687
self.exact_test(x, y, alternative, rev, stat_expected, p_expected)
case_R_lt_171c = (list(zip(alternatives, p_n_lt_171c, [False]*3))
+ list(zip(alternatives, reversed(p_n_lt_171c),
[True]*3)))
@pytest.mark.parametrize("alternative, p_expected, rev", case_R_lt_171c)
def test_against_R_lt_171c(self, alternative, p_expected, rev):
np.random.seed(0)
x = np.random.rand(170)
y = np.random.rand(170)
stat_expected = 0.1115906717716673
self.exact_test(x, y, alternative, rev, stat_expected, p_expected)
case_gt_171 = (list(zip(alternatives, [False]*3)) +
list(zip(alternatives, [True]*3)))
@pytest.mark.parametrize("alternative, rev", case_gt_171)
def test_gt_171(self, alternative, rev):
np.random.seed(0)
x = np.random.rand(400)
y = np.random.rand(400)
res0 = stats.kendalltau(x, y, method='exact',
alternative=alternative)
res1 = stats.kendalltau(x, y, method='asymptotic',
alternative=alternative)
assert_equal(res0[0], res1[0])
assert_allclose(res0[1], res1[1], rtol=1e-3)
@pytest.mark.parametrize("method", ('exact', 'asymptotic'))
@pytest.mark.parametrize("alternative", ('two-sided', 'less', 'greater'))
def test_nan_policy(self, method, alternative):
# Test nan policies
x1 = [1, 2, 3, 4, 5]
x2 = [5, 6, 7, 8, 9]
x1nan = x1 + [np.nan]
x2nan = x2 + [np.nan]
# test nan_policy="propagate"
res_actual = stats.kendalltau(x1nan, x2nan,
method=method, alternative=alternative)
res_expected = (np.nan, np.nan)
assert_allclose(res_actual, res_expected)
# test nan_policy="omit"
res_actual = stats.kendalltau(x1nan, x2nan, nan_policy='omit',
method=method, alternative=alternative)
res_expected = stats.kendalltau(x1, x2, method=method,
alternative=alternative)
assert_allclose(res_actual, res_expected)
# test nan_policy="raise"
message = 'The input contains nan values'
with pytest.raises(ValueError, match=message):
stats.kendalltau(x1nan, x2nan, nan_policy='raise',
method=method, alternative=alternative)
# test invalid nan_policy
message = "nan_policy must be one of..."
with pytest.raises(ValueError, match=message):
stats.kendalltau(x1nan, x2nan, nan_policy='ekki-ekki',
method=method, alternative=alternative)
def test_weightedtau():
x = [12, 2, 1, 12, 2]
y = [1, 4, 7, 1, 0]
tau, p_value = stats.weightedtau(x, y)
assert_approx_equal(tau, -0.56694968153682723)
assert_equal(np.nan, p_value)
tau, p_value = stats.weightedtau(x, y, additive=False)
assert_approx_equal(tau, -0.62205716951801038)
assert_equal(np.nan, p_value)
# This must be exactly Kendall's tau
tau, p_value = stats.weightedtau(x, y, weigher=lambda x: 1)
assert_approx_equal(tau, -0.47140452079103173)
assert_equal(np.nan, p_value)
# test for namedtuple attribute results
res = stats.weightedtau(x, y)
attributes = ('correlation', 'pvalue')
check_named_results(res, attributes)
assert_equal(res.correlation, res.statistic)
# Asymmetric, ranked version
tau, p_value = stats.weightedtau(x, y, rank=None)
assert_approx_equal(tau, -0.4157652301037516)
assert_equal(np.nan, p_value)
tau, p_value = stats.weightedtau(y, x, rank=None)
assert_approx_equal(tau, -0.7181341329699029)
assert_equal(np.nan, p_value)
tau, p_value = stats.weightedtau(x, y, rank=None, additive=False)
assert_approx_equal(tau, -0.40644850966246893)
assert_equal(np.nan, p_value)
tau, p_value = stats.weightedtau(y, x, rank=None, additive=False)
assert_approx_equal(tau, -0.83766582937355172)
assert_equal(np.nan, p_value)
tau, p_value = stats.weightedtau(x, y, rank=False)
assert_approx_equal(tau, -0.51604397940261848)
assert_equal(np.nan, p_value)
# This must be exactly Kendall's tau
tau, p_value = stats.weightedtau(x, y, rank=True, weigher=lambda x: 1)
assert_approx_equal(tau, -0.47140452079103173)
assert_equal(np.nan, p_value)
tau, p_value = stats.weightedtau(y, x, rank=True, weigher=lambda x: 1)
assert_approx_equal(tau, -0.47140452079103173)
assert_equal(np.nan, p_value)
# Test argument conversion
tau, p_value = stats.weightedtau(np.asarray(x, dtype=np.float64), y)
assert_approx_equal(tau, -0.56694968153682723)
tau, p_value = stats.weightedtau(np.asarray(x, dtype=np.int16), y)
assert_approx_equal(tau, -0.56694968153682723)
tau, p_value = stats.weightedtau(np.asarray(x, dtype=np.float64),
np.asarray(y, dtype=np.float64))
assert_approx_equal(tau, -0.56694968153682723)
# All ties
tau, p_value = stats.weightedtau([], [])
assert_equal(np.nan, tau)
assert_equal(np.nan, p_value)
tau, p_value = stats.weightedtau([0], [0])
assert_equal(np.nan, tau)
assert_equal(np.nan, p_value)
# Size mismatches
assert_raises(ValueError, stats.weightedtau, [0, 1], [0, 1, 2])
assert_raises(ValueError, stats.weightedtau, [0, 1], [0, 1], [0])
# NaNs
x = [12, 2, 1, 12, 2]
y = [1, 4, 7, 1, np.nan]
tau, p_value = stats.weightedtau(x, y)
assert_approx_equal(tau, -0.56694968153682723)
x = [12, 2, np.nan, 12, 2]
tau, p_value = stats.weightedtau(x, y)
assert_approx_equal(tau, -0.56694968153682723)
# NaNs when the dtype of x and y are all np.float64
x = [12.0, 2.0, 1.0, 12.0, 2.0]
y = [1.0, 4.0, 7.0, 1.0, np.nan]
tau, p_value = stats.weightedtau(x, y)
assert_approx_equal(tau, -0.56694968153682723)
x = [12.0, 2.0, np.nan, 12.0, 2.0]
tau, p_value = stats.weightedtau(x, y)
assert_approx_equal(tau, -0.56694968153682723)
# NaNs when there are more than one NaN in x or y
x = [12.0, 2.0, 1.0, 12.0, 1.0]
y = [1.0, 4.0, 7.0, 1.0, 1.0]
tau, p_value = stats.weightedtau(x, y)
assert_approx_equal(tau, -0.6615242347139803)
x = [12.0, 2.0, np.nan, 12.0, np.nan]
tau, p_value = stats.weightedtau(x, y)
assert_approx_equal(tau, -0.6615242347139803)
y = [np.nan, 4.0, 7.0, np.nan, np.nan]
tau, p_value = stats.weightedtau(x, y)
assert_approx_equal(tau, -0.6615242347139803)
def test_segfault_issue_9710():
# https://github.com/scipy/scipy/issues/9710
# This test was created to check segfault
# In issue SEGFAULT only repros in optimized builds after calling the function twice
stats.weightedtau([1], [1.0])
stats.weightedtau([1], [1.0])
# The code below also caused SEGFAULT
stats.weightedtau([np.nan], [52])
def test_kendall_tau_large():
n = 172
# Test omit policy
x = np.arange(n + 1).astype(float)
y = np.arange(n + 1).astype(float)
y[-1] = np.nan
_, pval = stats.kendalltau(x, y, method='exact', nan_policy='omit')
assert_equal(pval, 0.0)
def test_weightedtau_vs_quadratic():
# Trivial quadratic implementation, all parameters mandatory
def wkq(x, y, rank, weigher, add):
tot = conc = disc = u = v = 0
for (i, j) in product(range(len(x)), range(len(x))):
w = weigher(rank[i]) + weigher(rank[j]) if add \
else weigher(rank[i]) * weigher(rank[j])
tot += w
if x[i] == x[j]:
u += w
if y[i] == y[j]:
v += w
if x[i] < x[j] and y[i] < y[j] or x[i] > x[j] and y[i] > y[j]:
conc += w
elif x[i] < x[j] and y[i] > y[j] or x[i] > x[j] and y[i] < y[j]:
disc += w
return (conc - disc) / np.sqrt(tot - u) / np.sqrt(tot - v)
def weigher(x):
return 1. / (x + 1)
np.random.seed(42)
for s in range(3,10):
a = []
# Generate rankings with ties
for i in range(s):
a += [i]*i
b = list(a)
np.random.shuffle(a)
np.random.shuffle(b)
# First pass: use element indices as ranks
rank = np.arange(len(a), dtype=np.intp)
for _ in range(2):
for add in [True, False]:
expected = wkq(a, b, rank, weigher, add)
actual = stats.weightedtau(a, b, rank, weigher, add).statistic
assert_approx_equal(expected, actual)
# Second pass: use a random rank
np.random.shuffle(rank)
class TestFindRepeats:
def test_basic(self):
a = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 5]
res, nums = stats.find_repeats(a)
assert_array_equal(res, [1, 2, 3, 4])
assert_array_equal(nums, [3, 3, 2, 2])
def test_empty_result(self):
# Check that empty arrays are returned when there are no repeats.
for a in [[10, 20, 50, 30, 40], []]:
repeated, counts = stats.find_repeats(a)
assert_array_equal(repeated, [])
assert_array_equal(counts, [])
class TestRegression:
def test_one_arg_deprecation(self):
x = np.arange(20).reshape((2, 10))
message = "Inference of the two sets..."
with pytest.deprecated_call(match=message):
stats.linregress(x)
stats.linregress(x[0], x[1])
def test_linregressBIGX(self):
# W.II.F. Regress BIG on X.
result = stats.linregress(X, BIG)
assert_almost_equal(result.intercept, 99999990)
assert_almost_equal(result.rvalue, 1.0)
# The uncertainty ought to be almost zero
# since all points lie on a line
assert_almost_equal(result.stderr, 0.0)
assert_almost_equal(result.intercept_stderr, 0.0)
def test_regressXX(self):
# W.IV.B. Regress X on X.
# The constant should be exactly 0 and the regression coefficient
# should be 1. This is a perfectly valid regression and the
# program should not complain.
result = stats.linregress(X, X)
assert_almost_equal(result.intercept, 0.0)
assert_almost_equal(result.rvalue, 1.0)
# The uncertainly on regression through two points ought to be 0
assert_almost_equal(result.stderr, 0.0)
assert_almost_equal(result.intercept_stderr, 0.0)
# W.IV.C. Regress X on BIG and LITTLE (two predictors). The program
# should tell you that this model is "singular" because BIG and
# LITTLE are linear combinations of each other. Cryptic error
# messages are unacceptable here. Singularity is the most
# fundamental regression error.
#
# Need to figure out how to handle multiple linear regression.
# This is not obvious
def test_regressZEROX(self):
# W.IV.D. Regress ZERO on X.
# The program should inform you that ZERO has no variance or it should
# go ahead and compute the regression and report a correlation and
# total sum of squares of exactly 0.
result = stats.linregress(X, ZERO)
assert_almost_equal(result.intercept, 0.0)
assert_almost_equal(result.rvalue, 0.0)
def test_regress_simple(self):
# Regress a line with sinusoidal noise.
x = np.linspace(0, 100, 100)
y = 0.2 * np.linspace(0, 100, 100) + 10
y += np.sin(np.linspace(0, 20, 100))
result = stats.linregress(x, y)
lr = LinregressResult
assert_(isinstance(result, lr))
assert_almost_equal(result.stderr, 2.3957814497838803e-3)
def test_regress_alternative(self):
# test alternative parameter
x = np.linspace(0, 100, 100)
y = 0.2 * np.linspace(0, 100, 100) + 10 # slope is greater than zero
y += np.sin(np.linspace(0, 20, 100))
with pytest.raises(ValueError, match="`alternative` must be 'less'..."):
stats.linregress(x, y, alternative="ekki-ekki")
res1 = stats.linregress(x, y, alternative="two-sided")
# slope is greater than zero, so "less" p-value should be large
res2 = stats.linregress(x, y, alternative="less")
assert_allclose(res2.pvalue, 1 - (res1.pvalue / 2))
# slope is greater than zero, so "greater" p-value should be small
res3 = stats.linregress(x, y, alternative="greater")
assert_allclose(res3.pvalue, res1.pvalue / 2)
assert res1.rvalue == res2.rvalue == res3.rvalue
def test_regress_against_R(self):
# test against R `lm`
# options(digits=16)
# x <- c(151, 174, 138, 186, 128, 136, 179, 163, 152, 131)
# y <- c(63, 81, 56, 91, 47, 57, 76, 72, 62, 48)
# relation <- lm(y~x)
# print(summary(relation))
x = [151, 174, 138, 186, 128, 136, 179, 163, 152, 131]
y = [63, 81, 56, 91, 47, 57, 76, 72, 62, 48]
res = stats.linregress(x, y, alternative="two-sided")
# expected values from R's `lm` above
assert_allclose(res.slope, 0.6746104491292)
assert_allclose(res.intercept, -38.4550870760770)
assert_allclose(res.rvalue, np.sqrt(0.95478224775))
assert_allclose(res.pvalue, 1.16440531074e-06)
assert_allclose(res.stderr, 0.0519051424731)
assert_allclose(res.intercept_stderr, 8.0490133029927)
# TODO: remove this test once single-arg support is dropped;
# deprecation warning tested in `test_one_arg_deprecation`
@pytest.mark.filterwarnings('ignore::DeprecationWarning')
def test_regress_simple_onearg_rows(self):
# Regress a line w sinusoidal noise,
# with a single input of shape (2, N)
x = np.linspace(0, 100, 100)
y = 0.2 * np.linspace(0, 100, 100) + 10
y += np.sin(np.linspace(0, 20, 100))
rows = np.vstack((x, y))
result = stats.linregress(rows)
assert_almost_equal(result.stderr, 2.3957814497838803e-3)
assert_almost_equal(result.intercept_stderr, 1.3866936078570702e-1)
# TODO: remove this test once single-arg support is dropped;
# deprecation warning tested in `test_one_arg_deprecation`
@pytest.mark.filterwarnings('ignore::DeprecationWarning')
def test_regress_simple_onearg_cols(self):
x = np.linspace(0, 100, 100)
y = 0.2 * np.linspace(0, 100, 100) + 10
y += np.sin(np.linspace(0, 20, 100))
columns = np.hstack((np.expand_dims(x, 1), np.expand_dims(y, 1)))
result = stats.linregress(columns)
assert_almost_equal(result.stderr, 2.3957814497838803e-3)
assert_almost_equal(result.intercept_stderr, 1.3866936078570702e-1)
# TODO: remove this test once single-arg support is dropped;
# deprecation warning tested in `test_one_arg_deprecation`
@pytest.mark.filterwarnings('ignore::DeprecationWarning')
def test_regress_shape_error(self):
# Check that a single input argument to linregress with wrong shape
# results in a ValueError.
assert_raises(ValueError, stats.linregress, np.ones((3, 3)))
def test_linregress(self):
# compared with multivariate ols with pinv
x = np.arange(11)
y = np.arange(5, 16)
y[[(1), (-2)]] -= 1
y[[(0), (-1)]] += 1
result = stats.linregress(x, y)
# This test used to use 'assert_array_almost_equal' but its
# formualtion got confusing since LinregressResult became
# _lib._bunch._make_tuple_bunch instead of namedtuple
# (for backwards compatibility, see PR #12983)
def assert_ae(x, y):
return assert_almost_equal(x, y, decimal=14)
assert_ae(result.slope, 1.0)
assert_ae(result.intercept, 5.0)
assert_ae(result.rvalue, 0.98229948625750)
assert_ae(result.pvalue, 7.45259691e-008)
assert_ae(result.stderr, 0.063564172616372733)
assert_ae(result.intercept_stderr, 0.37605071654517686)
def test_regress_simple_negative_cor(self):
# If the slope of the regression is negative the factor R tend
# to -1 not 1. Sometimes rounding errors makes it < -1
# leading to stderr being NaN.
a, n = 1e-71, 100000
x = np.linspace(a, 2 * a, n)
y = np.linspace(2 * a, a, n)
result = stats.linregress(x, y)
# Make sure propagated numerical errors
# did not bring rvalue below -1 (or were coersced)
assert_(result.rvalue >= -1)
assert_almost_equal(result.rvalue, -1)
# slope and intercept stderror should stay numeric
assert_(not np.isnan(result.stderr))
assert_(not np.isnan(result.intercept_stderr))
def test_linregress_result_attributes(self):
x = np.linspace(0, 100, 100)
y = 0.2 * np.linspace(0, 100, 100) + 10
y += np.sin(np.linspace(0, 20, 100))
result = stats.linregress(x, y)
# Result is of a correct class
lr = LinregressResult
assert_(isinstance(result, lr))
# LinregressResult elements have correct names
attributes = ('slope', 'intercept', 'rvalue', 'pvalue', 'stderr')
check_named_results(result, attributes)
# Also check that the extra attribute (intercept_stderr) is present
assert 'intercept_stderr' in dir(result)
def test_regress_two_inputs(self):
# Regress a simple line formed by two points.
x = np.arange(2)
y = np.arange(3, 5)
result = stats.linregress(x, y)
# Non-horizontal line
assert_almost_equal(result.pvalue, 0.0)
# Zero error through two points
assert_almost_equal(result.stderr, 0.0)
assert_almost_equal(result.intercept_stderr, 0.0)
def test_regress_two_inputs_horizontal_line(self):
# Regress a horizontal line formed by two points.
x = np.arange(2)
y = np.ones(2)
result = stats.linregress(x, y)
# Horizontal line
assert_almost_equal(result.pvalue, 1.0)
# Zero error through two points
assert_almost_equal(result.stderr, 0.0)
assert_almost_equal(result.intercept_stderr, 0.0)
def test_nist_norris(self):
x = [0.2, 337.4, 118.2, 884.6, 10.1, 226.5, 666.3, 996.3, 448.6, 777.0,
558.2, 0.4, 0.6, 775.5, 666.9, 338.0, 447.5, 11.6, 556.0, 228.1,
995.8, 887.6, 120.2, 0.3, 0.3, 556.8, 339.1, 887.2, 999.0, 779.0,
11.1, 118.3, 229.2, 669.1, 448.9, 0.5]
y = [0.1, 338.8, 118.1, 888.0, 9.2, 228.1, 668.5, 998.5, 449.1, 778.9,
559.2, 0.3, 0.1, 778.1, 668.8, 339.3, 448.9, 10.8, 557.7, 228.3,
998.0, 888.8, 119.6, 0.3, 0.6, 557.6, 339.3, 888.0, 998.5, 778.9,
10.2, 117.6, 228.9, 668.4, 449.2, 0.2]
result = stats.linregress(x, y)
assert_almost_equal(result.slope, 1.00211681802045)
assert_almost_equal(result.intercept, -0.262323073774029)
assert_almost_equal(result.rvalue**2, 0.999993745883712)
assert_almost_equal(result.pvalue, 0.0)
assert_almost_equal(result.stderr, 0.00042979684820)
assert_almost_equal(result.intercept_stderr, 0.23281823430153)
def test_compare_to_polyfit(self):
x = np.linspace(0, 100, 100)
y = 0.2 * np.linspace(0, 100, 100) + 10
y += np.sin(np.linspace(0, 20, 100))
result = stats.linregress(x, y)
poly = np.polyfit(x, y, 1) # Fit 1st degree polynomial
# Make sure linear regression slope and intercept
# match with results from numpy polyfit
assert_almost_equal(result.slope, poly[0])
assert_almost_equal(result.intercept, poly[1])
def test_empty_input(self):
assert_raises(ValueError, stats.linregress, [], [])
def test_nan_input(self):
x = np.arange(10.)
x[9] = np.nan
with np.errstate(invalid="ignore"):
result = stats.linregress(x, x)
# Make sure the result still comes back as `LinregressResult`
lr = LinregressResult
assert_(isinstance(result, lr))
assert_array_equal(result, (np.nan,)*5)
assert_equal(result.intercept_stderr, np.nan)
def test_identical_x(self):
x = np.zeros(10)
y = np.random.random(10)
msg = "Cannot calculate a linear regression"
with assert_raises(ValueError, match=msg):
stats.linregress(x, y)
def test_theilslopes():
# Basic slope test.
slope, intercept, lower, upper = stats.theilslopes([0,1,1])
assert_almost_equal(slope, 0.5)
assert_almost_equal(intercept, 0.5)
msg = ("method must be either 'joint' or 'separate'."
"'joint_separate' is invalid.")
with pytest.raises(ValueError, match=msg):
stats.theilslopes([0, 1, 1], method='joint_separate')
slope, intercept, lower, upper = stats.theilslopes([0, 1, 1],
method='joint')
assert_almost_equal(slope, 0.5)
assert_almost_equal(intercept, 0.0)
# Test of confidence intervals.
x = [1, 2, 3, 4, 10, 12, 18]
y = [9, 15, 19, 20, 45, 55, 78]
slope, intercept, lower, upper = stats.theilslopes(y, x, 0.07,
method='separate')
assert_almost_equal(slope, 4)
assert_almost_equal(intercept, 4.0)
assert_almost_equal(upper, 4.38, decimal=2)
assert_almost_equal(lower, 3.71, decimal=2)
slope, intercept, lower, upper = stats.theilslopes(y, x, 0.07,
method='joint')
assert_almost_equal(slope, 4)
assert_almost_equal(intercept, 6.0)
assert_almost_equal(upper, 4.38, decimal=2)
assert_almost_equal(lower, 3.71, decimal=2)
def test_cumfreq():
x = [1, 4, 2, 1, 3, 1]
cumfreqs, lowlim, binsize, extrapoints = stats.cumfreq(x, numbins=4)
assert_array_almost_equal(cumfreqs, np.array([3., 4., 5., 6.]))
cumfreqs, lowlim, binsize, extrapoints = stats.cumfreq(
x, numbins=4, defaultreallimits=(1.5, 5))
assert_(extrapoints == 3)
# test for namedtuple attribute results
attributes = ('cumcount', 'lowerlimit', 'binsize', 'extrapoints')
res = stats.cumfreq(x, numbins=4, defaultreallimits=(1.5, 5))
check_named_results(res, attributes)
def test_relfreq():
a = np.array([1, 4, 2, 1, 3, 1])
relfreqs, lowlim, binsize, extrapoints = stats.relfreq(a, numbins=4)
assert_array_almost_equal(relfreqs,
array([0.5, 0.16666667, 0.16666667, 0.16666667]))
# test for namedtuple attribute results
attributes = ('frequency', 'lowerlimit', 'binsize', 'extrapoints')
res = stats.relfreq(a, numbins=4)
check_named_results(res, attributes)
# check array_like input is accepted
relfreqs2, lowlim, binsize, extrapoints = stats.relfreq([1, 4, 2, 1, 3, 1],
numbins=4)
assert_array_almost_equal(relfreqs, relfreqs2)
class TestScoreatpercentile:
def setup_method(self):
self.a1 = [3, 4, 5, 10, -3, -5, 6]
self.a2 = [3, -6, -2, 8, 7, 4, 2, 1]
self.a3 = [3., 4, 5, 10, -3, -5, -6, 7.0]
def test_basic(self):
x = arange(8) * 0.5
assert_equal(stats.scoreatpercentile(x, 0), 0.)
assert_equal(stats.scoreatpercentile(x, 100), 3.5)
assert_equal(stats.scoreatpercentile(x, 50), 1.75)
def test_fraction(self):
scoreatperc = stats.scoreatpercentile
# Test defaults
assert_equal(scoreatperc(list(range(10)), 50), 4.5)
assert_equal(scoreatperc(list(range(10)), 50, (2,7)), 4.5)
assert_equal(scoreatperc(list(range(100)), 50, limit=(1, 8)), 4.5)
assert_equal(scoreatperc(np.array([1, 10,100]), 50, (10,100)), 55)
assert_equal(scoreatperc(np.array([1, 10,100]), 50, (1,10)), 5.5)
# explicitly specify interpolation_method 'fraction' (the default)
assert_equal(scoreatperc(list(range(10)), 50, interpolation_method='fraction'),
4.5)
assert_equal(scoreatperc(list(range(10)), 50, limit=(2, 7),
interpolation_method='fraction'),
4.5)
assert_equal(scoreatperc(list(range(100)), 50, limit=(1, 8),
interpolation_method='fraction'),
4.5)
assert_equal(scoreatperc(np.array([1, 10,100]), 50, (10, 100),
interpolation_method='fraction'),
55)
assert_equal(scoreatperc(np.array([1, 10,100]), 50, (1,10),
interpolation_method='fraction'),
5.5)
def test_lower_higher(self):
scoreatperc = stats.scoreatpercentile
# interpolation_method 'lower'/'higher'
assert_equal(scoreatperc(list(range(10)), 50,
interpolation_method='lower'), 4)
assert_equal(scoreatperc(list(range(10)), 50,
interpolation_method='higher'), 5)
assert_equal(scoreatperc(list(range(10)), 50, (2,7),
interpolation_method='lower'), 4)
assert_equal(scoreatperc(list(range(10)), 50, limit=(2,7),
interpolation_method='higher'), 5)
assert_equal(scoreatperc(list(range(100)), 50, (1,8),
interpolation_method='lower'), 4)
assert_equal(scoreatperc(list(range(100)), 50, (1,8),
interpolation_method='higher'), 5)
assert_equal(scoreatperc(np.array([1, 10, 100]), 50, (10, 100),
interpolation_method='lower'), 10)
assert_equal(scoreatperc(np.array([1, 10, 100]), 50, limit=(10, 100),
interpolation_method='higher'), 100)
assert_equal(scoreatperc(np.array([1, 10, 100]), 50, (1, 10),
interpolation_method='lower'), 1)
assert_equal(scoreatperc(np.array([1, 10, 100]), 50, limit=(1, 10),
interpolation_method='higher'), 10)
def test_sequence_per(self):
x = arange(8) * 0.5
expected = np.array([0, 3.5, 1.75])
res = stats.scoreatpercentile(x, [0, 100, 50])
assert_allclose(res, expected)
assert_(isinstance(res, np.ndarray))
# Test with ndarray. Regression test for gh-2861
assert_allclose(stats.scoreatpercentile(x, np.array([0, 100, 50])),
expected)
# Also test combination of 2-D array, axis not None and array-like per
res2 = stats.scoreatpercentile(np.arange(12).reshape((3,4)),
np.array([0, 1, 100, 100]), axis=1)
expected2 = array([[0, 4, 8],
[0.03, 4.03, 8.03],
[3, 7, 11],
[3, 7, 11]])
assert_allclose(res2, expected2)
def test_axis(self):
scoreatperc = stats.scoreatpercentile
x = arange(12).reshape(3, 4)
assert_equal(scoreatperc(x, (25, 50, 100)), [2.75, 5.5, 11.0])
r0 = [[2, 3, 4, 5], [4, 5, 6, 7], [8, 9, 10, 11]]
assert_equal(scoreatperc(x, (25, 50, 100), axis=0), r0)
r1 = [[0.75, 4.75, 8.75], [1.5, 5.5, 9.5], [3, 7, 11]]
assert_equal(scoreatperc(x, (25, 50, 100), axis=1), r1)
x = array([[1, 1, 1],
[1, 1, 1],
[4, 4, 3],
[1, 1, 1],
[1, 1, 1]])
score = stats.scoreatpercentile(x, 50)
assert_equal(score.shape, ())
assert_equal(score, 1.0)
score = stats.scoreatpercentile(x, 50, axis=0)
assert_equal(score.shape, (3,))
assert_equal(score, [1, 1, 1])
def test_exception(self):
assert_raises(ValueError, stats.scoreatpercentile, [1, 2], 56,
interpolation_method='foobar')
assert_raises(ValueError, stats.scoreatpercentile, [1], 101)
assert_raises(ValueError, stats.scoreatpercentile, [1], -1)
def test_empty(self):
assert_equal(stats.scoreatpercentile([], 50), np.nan)
assert_equal(stats.scoreatpercentile(np.array([[], []]), 50), np.nan)
assert_equal(stats.scoreatpercentile([], [50, 99]), [np.nan, np.nan])
class TestMode:
def test_empty(self):
with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
vals, counts = stats.mode([])
assert_equal(vals, np.array([]))
assert_equal(counts, np.array([]))
def test_scalar(self):
vals, counts = stats.mode(4.)
assert_equal(vals, np.array([4.]))
assert_equal(counts, np.array([1]))
def test_basic(self):
data1 = [3, 5, 1, 10, 23, 3, 2, 6, 8, 6, 10, 6]
vals = stats.mode(data1)
assert_equal(vals[0], 6)
assert_equal(vals[1], 3)
def test_axes(self):
data1 = [10, 10, 30, 40]
data2 = [10, 10, 10, 10]
data3 = [20, 10, 20, 20]
data4 = [30, 30, 30, 30]
data5 = [40, 30, 30, 30]
arr = np.array([data1, data2, data3, data4, data5])
vals = stats.mode(arr, axis=None, keepdims=True)
assert_equal(vals[0], np.array([[30]]))
assert_equal(vals[1], np.array([[8]]))
vals = stats.mode(arr, axis=0, keepdims=True)
assert_equal(vals[0], np.array([[10, 10, 30, 30]]))
assert_equal(vals[1], np.array([[2, 3, 3, 2]]))
vals = stats.mode(arr, axis=1, keepdims=True)
assert_equal(vals[0], np.array([[10], [10], [20], [30], [30]]))
assert_equal(vals[1], np.array([[2], [4], [3], [4], [3]]))
@pytest.mark.parametrize('axis', np.arange(-4, 0))
def test_negative_axes_gh_15375(self, axis):
np.random.seed(984213899)
a = np.random.rand(10, 11, 12, 13)
res0 = stats.mode(a, axis=a.ndim+axis)
res1 = stats.mode(a, axis=axis)
np.testing.assert_array_equal(res0, res1)
def test_mode_result_attributes(self):
data1 = [3, 5, 1, 10, 23, 3, 2, 6, 8, 6, 10, 6]
data2 = []
actual = stats.mode(data1)
attributes = ('mode', 'count')
check_named_results(actual, attributes)
with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
actual2 = stats.mode(data2)
check_named_results(actual2, attributes)
def test_mode_nan(self):
data1 = [3, np.nan, 5, 1, 10, 23, 3, 2, 6, 8, 6, 10, 6]
actual = stats.mode(data1)
assert_equal(actual, (6, 3))
actual = stats.mode(data1, nan_policy='omit')
assert_equal(actual, (6, 3))
assert_raises(ValueError, stats.mode, data1, nan_policy='raise')
assert_raises(ValueError, stats.mode, data1, nan_policy='foobar')
@pytest.mark.parametrize("data", [
[3, 5, 1, 1, 3],
[3, np.nan, 5, 1, 1, 3],
[3, 5, 1],
[3, np.nan, 5, 1],
])
@pytest.mark.parametrize('keepdims', [False, True])
def test_smallest_equal(self, data, keepdims):
result = stats.mode(data, nan_policy='omit', keepdims=keepdims)
if keepdims:
assert_equal(result[0][0], 1)
else:
assert_equal(result[0], 1)
@pytest.mark.parametrize('axis', np.arange(-3, 3))
def test_mode_shape_gh_9955(self, axis, dtype=np.float64):
rng = np.random.default_rng(984213899)
a = rng.uniform(size=(3, 4, 5)).astype(dtype)
res = stats.mode(a, axis=axis, keepdims=False)
reference_shape = list(a.shape)
reference_shape.pop(axis)
np.testing.assert_array_equal(res.mode.shape, reference_shape)
np.testing.assert_array_equal(res.count.shape, reference_shape)
def test_nan_policy_propagate_gh_9815(self):
# mode should treat np.nan as it would any other object when
# nan_policy='propagate'
a = [2, np.nan, 1, np.nan]
res = stats.mode(a)
assert np.isnan(res.mode) and res.count == 2
def test_keepdims(self):
# test empty arrays (handled by `np.mean`)
a = np.zeros((1, 2, 3, 0))
res = stats.mode(a, axis=1, keepdims=False)
assert res.mode.shape == res.count.shape == (1, 3, 0)
res = stats.mode(a, axis=1, keepdims=True)
assert res.mode.shape == res.count.shape == (1, 1, 3, 0)
# test nan_policy='propagate'
a = [[1, 3, 3, np.nan], [1, 1, np.nan, 1]]
res = stats.mode(a, axis=1, keepdims=False)
assert_array_equal(res.mode, [3, 1])
assert_array_equal(res.count, [2, 3])
res = stats.mode(a, axis=1, keepdims=True)
assert_array_equal(res.mode, [[3], [1]])
assert_array_equal(res.count, [[2], [3]])
a = np.array(a)
res = stats.mode(a, axis=None, keepdims=False)
ref = stats.mode(a.ravel(), keepdims=False)
assert_array_equal(res, ref)
assert res.mode.shape == ref.mode.shape == ()
res = stats.mode(a, axis=None, keepdims=True)
ref = stats.mode(a.ravel(), keepdims=True)
assert_equal(res.mode.ravel(), ref.mode.ravel())
assert res.mode.shape == (1, 1)
assert_equal(res.count.ravel(), ref.count.ravel())
assert res.count.shape == (1, 1)
# test nan_policy='omit'
a = [[1, np.nan, np.nan, np.nan, 1],
[np.nan, np.nan, np.nan, np.nan, 2],
[1, 2, np.nan, 5, 5]]
res = stats.mode(a, axis=1, keepdims=False, nan_policy='omit')
assert_array_equal(res.mode, [1, 2, 5])
assert_array_equal(res.count, [2, 1, 2])
res = stats.mode(a, axis=1, keepdims=True, nan_policy='omit')
assert_array_equal(res.mode, [[1], [2], [5]])
assert_array_equal(res.count, [[2], [1], [2]])
a = np.array(a)
res = stats.mode(a, axis=None, keepdims=False, nan_policy='omit')
ref = stats.mode(a.ravel(), keepdims=False, nan_policy='omit')
assert_array_equal(res, ref)
assert res.mode.shape == ref.mode.shape == ()
res = stats.mode(a, axis=None, keepdims=True, nan_policy='omit')
ref = stats.mode(a.ravel(), keepdims=True, nan_policy='omit')
assert_equal(res.mode.ravel(), ref.mode.ravel())
assert res.mode.shape == (1, 1)
assert_equal(res.count.ravel(), ref.count.ravel())
assert res.count.shape == (1, 1)
@pytest.mark.parametrize("nan_policy", ['propagate', 'omit'])
def test_gh16955(self, nan_policy):
# Check that bug reported in gh-16955 is resolved
shape = (4, 3)
data = np.ones(shape)
data[0, 0] = np.nan
res = stats.mode(a=data, axis=1, keepdims=False, nan_policy=nan_policy)
assert_array_equal(res.mode, [1, 1, 1, 1])
assert_array_equal(res.count, [2, 3, 3, 3])
# Test with input from gh-16595. Support for non-numeric input
# was deprecated, so check for the appropriate error.
my_dtype = np.dtype([('asdf', np.uint8), ('qwer', np.float64, (3,))])
test = np.zeros(10, dtype=my_dtype)
message = "Argument `a` is not....|An argument has dtype..."
with pytest.raises(TypeError, match=message):
stats.mode(test, nan_policy=nan_policy)
def test_gh9955(self):
# The behavior of mode with empty slices (whether the input was empty
# or all elements were omitted) was inconsistent. Test that this is
# resolved: the mode of an empty slice is NaN and the count is zero.
with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
res = stats.mode([])
ref = (np.nan, 0)
assert_equal(res, ref)
with pytest.warns(SmallSampleWarning, match=too_small_1d_omit):
res = stats.mode([np.nan], nan_policy='omit')
assert_equal(res, ref)
a = [[10., 20., 20.], [np.nan, np.nan, np.nan]]
with pytest.warns(SmallSampleWarning, match=too_small_nd_omit):
res = stats.mode(a, axis=1, nan_policy='omit')
ref = ([20, np.nan], [2, 0])
assert_equal(res, ref)
res = stats.mode(a, axis=1, nan_policy='propagate')
ref = ([20, np.nan], [2, 3])
assert_equal(res, ref)
z = np.array([[], []])
with pytest.warns(SmallSampleWarning, match=too_small_nd_not_omit):
res = stats.mode(z, axis=1)
ref = ([np.nan, np.nan], [0, 0])
assert_equal(res, ref)
@pytest.mark.filterwarnings('ignore::RuntimeWarning') # np.mean warns
@pytest.mark.parametrize('z', [np.empty((0, 1, 2)), np.empty((1, 1, 2))])
def test_gh17214(self, z):
if z.size == 0:
with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
res = stats.mode(z, axis=None, keepdims=True)
else:
res = stats.mode(z, axis=None, keepdims=True)
ref = np.mean(z, axis=None, keepdims=True)
assert res[0].shape == res[1].shape == ref.shape == (1, 1, 1)
def test_raise_non_numeric_gh18254(self):
message = ("...only boolean and numerical dtypes..." if SCIPY_ARRAY_API
else "Argument `a` is not recognized as numeric.")
class ArrLike:
def __init__(self, x):
self._x = x
def __array__(self, dtype=None, copy=None):
return self._x.astype(object)
with pytest.raises(TypeError, match=message):
stats.mode(ArrLike(np.arange(3)))
with pytest.raises(TypeError, match=message):
stats.mode(np.arange(3, dtype=object))
@array_api_compatible
class TestSEM:
testcase = [1., 2., 3., 4.]
scalar_testcase = 4.
def test_sem_scalar(self, xp):
# This is not in R, so used:
# sqrt(var(testcase)*3/4)/sqrt(3)
# y = stats.sem(self.shoes[0])
# assert_approx_equal(y,0.775177399)
scalar_testcase = xp.asarray(self.scalar_testcase)[()]
if is_numpy(xp):
with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
y = stats.sem(scalar_testcase)
else:
# Other array types can emit a variety of warnings.
with np.testing.suppress_warnings() as sup:
sup.filter(UserWarning)
sup.filter(RuntimeWarning)
y = stats.sem(scalar_testcase)
assert xp.isnan(y)
def test_sem(self, xp):
testcase = xp.asarray(self.testcase)
y = stats.sem(testcase)
xp_assert_close(y, xp.asarray(0.6454972244))
n = len(self.testcase)
xp_assert_close(stats.sem(testcase, ddof=0) * (n/(n-2))**0.5,
stats.sem(testcase, ddof=2))
x = xp.arange(10.)
x = xp.where(x == 9, xp.asarray(xp.nan), x)
xp_assert_equal(stats.sem(x), xp.asarray(xp.nan))
@skip_xp_backends(np_only=True,
reasons=['`nan_policy` only supports NumPy backend'])
def test_sem_nan_policy(self, xp):
x = np.arange(10.)
x[9] = np.nan
assert_equal(stats.sem(x, nan_policy='omit'), 0.9128709291752769)
assert_raises(ValueError, stats.sem, x, nan_policy='raise')
assert_raises(ValueError, stats.sem, x, nan_policy='foobar')
class TestZmapZscore:
@pytest.mark.parametrize(
'x, y',
[([1, 2, 3, 4], [1, 2, 3, 4]),
([1, 2, 3], [0, 1, 2, 3, 4])]
)
def test_zmap(self, x, y):
z = stats.zmap(x, y)
# For these simple cases, calculate the expected result directly
# by using the formula for the z-score.
expected = (x - np.mean(y))/np.std(y)
assert_allclose(z, expected, rtol=1e-12)
def test_zmap_axis(self):
# Test use of 'axis' keyword in zmap.
x = np.array([[0.0, 0.0, 1.0, 1.0],
[1.0, 1.0, 1.0, 2.0],
[2.0, 0.0, 2.0, 0.0]])
t1 = 1.0/np.sqrt(2.0/3)
t2 = np.sqrt(3.)/3
t3 = np.sqrt(2.)
z0 = stats.zmap(x, x, axis=0)
z1 = stats.zmap(x, x, axis=1)
z0_expected = [[-t1, -t3/2, -t3/2, 0.0],
[0.0, t3, -t3/2, t1],
[t1, -t3/2, t3, -t1]]
z1_expected = [[-1.0, -1.0, 1.0, 1.0],
[-t2, -t2, -t2, np.sqrt(3.)],
[1.0, -1.0, 1.0, -1.0]]
assert_array_almost_equal(z0, z0_expected)
assert_array_almost_equal(z1, z1_expected)
def test_zmap_ddof(self):
# Test use of 'ddof' keyword in zmap.
x = np.array([[0.0, 0.0, 1.0, 1.0],
[0.0, 1.0, 2.0, 3.0]])
z = stats.zmap(x, x, axis=1, ddof=1)
z0_expected = np.array([-0.5, -0.5, 0.5, 0.5])/(1.0/np.sqrt(3))
z1_expected = np.array([-1.5, -0.5, 0.5, 1.5])/(np.sqrt(5./3))
assert_array_almost_equal(z[0], z0_expected)
assert_array_almost_equal(z[1], z1_expected)
@pytest.mark.parametrize('ddof', [0, 2])
def test_zmap_nan_policy_omit(self, ddof):
# nans in `scores` are propagated, regardless of `nan_policy`.
# `nan_policy` only affects how nans in `compare` are handled.
scores = np.array([-3, -1, 2, np.nan])
compare = np.array([-8, -3, 2, 7, 12, np.nan])
z = stats.zmap(scores, compare, ddof=ddof, nan_policy='omit')
assert_allclose(z, stats.zmap(scores, compare[~np.isnan(compare)],
ddof=ddof))
@pytest.mark.parametrize('ddof', [0, 2])
def test_zmap_nan_policy_omit_with_axis(self, ddof):
scores = np.arange(-5.0, 9.0).reshape(2, -1)
compare = np.linspace(-8, 6, 24).reshape(2, -1)
compare[0, 4] = np.nan
compare[0, 6] = np.nan
compare[1, 1] = np.nan
z = stats.zmap(scores, compare, nan_policy='omit', axis=1, ddof=ddof)
expected = np.array([stats.zmap(scores[0],
compare[0][~np.isnan(compare[0])],
ddof=ddof),
stats.zmap(scores[1],
compare[1][~np.isnan(compare[1])],
ddof=ddof)])
assert_allclose(z, expected, rtol=1e-14)
def test_zmap_nan_policy_raise(self):
scores = np.array([1, 2, 3])
compare = np.array([-8, -3, 2, 7, 12, np.nan])
with pytest.raises(ValueError, match='input contains nan'):
stats.zmap(scores, compare, nan_policy='raise')
def test_zscore(self):
# not in R, so tested by using:
# (testcase[i] - mean(testcase, axis=0)) / sqrt(var(testcase) * 3/4)
y = stats.zscore([1, 2, 3, 4])
desired = ([-1.3416407864999, -0.44721359549996, 0.44721359549996,
1.3416407864999])
assert_array_almost_equal(desired, y, decimal=12)
def test_zscore_axis(self):
# Test use of 'axis' keyword in zscore.
x = np.array([[0.0, 0.0, 1.0, 1.0],
[1.0, 1.0, 1.0, 2.0],
[2.0, 0.0, 2.0, 0.0]])
t1 = 1.0/np.sqrt(2.0/3)
t2 = np.sqrt(3.)/3
t3 = np.sqrt(2.)
z0 = stats.zscore(x, axis=0)
z1 = stats.zscore(x, axis=1)
z0_expected = [[-t1, -t3/2, -t3/2, 0.0],
[0.0, t3, -t3/2, t1],
[t1, -t3/2, t3, -t1]]
z1_expected = [[-1.0, -1.0, 1.0, 1.0],
[-t2, -t2, -t2, np.sqrt(3.)],
[1.0, -1.0, 1.0, -1.0]]
assert_array_almost_equal(z0, z0_expected)
assert_array_almost_equal(z1, z1_expected)
def test_zscore_ddof(self):
# Test use of 'ddof' keyword in zscore.
x = np.array([[0.0, 0.0, 1.0, 1.0],
[0.0, 1.0, 2.0, 3.0]])
z = stats.zscore(x, axis=1, ddof=1)
z0_expected = np.array([-0.5, -0.5, 0.5, 0.5])/(1.0/np.sqrt(3))
z1_expected = np.array([-1.5, -0.5, 0.5, 1.5])/(np.sqrt(5./3))
assert_array_almost_equal(z[0], z0_expected)
assert_array_almost_equal(z[1], z1_expected)
def test_zscore_nan_propagate(self):
x = np.array([1, 2, np.nan, 4, 5])
z = stats.zscore(x, nan_policy='propagate')
assert all(np.isnan(z))
def test_zscore_nan_omit(self):
x = np.array([1, 2, np.nan, 4, 5])
z = stats.zscore(x, nan_policy='omit')
expected = np.array([-1.2649110640673518,
-0.6324555320336759,
np.nan,
0.6324555320336759,
1.2649110640673518
])
assert_array_almost_equal(z, expected)
def test_zscore_nan_omit_with_ddof(self):
x = np.array([np.nan, 1.0, 3.0, 5.0, 7.0, 9.0])
z = stats.zscore(x, ddof=1, nan_policy='omit')
expected = np.r_[np.nan, stats.zscore(x[1:], ddof=1)]
assert_allclose(z, expected, rtol=1e-13)
def test_zscore_nan_raise(self):
x = np.array([1, 2, np.nan, 4, 5])
assert_raises(ValueError, stats.zscore, x, nan_policy='raise')
def test_zscore_constant_input_1d(self):
x = [-0.087] * 3
z = stats.zscore(x)
assert_equal(z, np.full(len(x), np.nan))
def test_zscore_constant_input_2d(self):
x = np.array([[10.0, 10.0, 10.0, 10.0],
[10.0, 11.0, 12.0, 13.0]])
z0 = stats.zscore(x, axis=0)
assert_equal(z0, np.array([[np.nan, -1.0, -1.0, -1.0],
[np.nan, 1.0, 1.0, 1.0]]))
z1 = stats.zscore(x, axis=1)
assert_equal(z1, np.array([[np.nan, np.nan, np.nan, np.nan],
stats.zscore(x[1])]))
z = stats.zscore(x, axis=None)
assert_equal(z, stats.zscore(x.ravel()).reshape(x.shape))
y = np.ones((3, 6))
z = stats.zscore(y, axis=None)
assert_equal(z, np.full(y.shape, np.nan))
def test_zscore_constant_input_2d_nan_policy_omit(self):
x = np.array([[10.0, 10.0, 10.0, 10.0],
[10.0, 11.0, 12.0, np.nan],
[10.0, 12.0, np.nan, 10.0]])
z0 = stats.zscore(x, nan_policy='omit', axis=0)
s = np.sqrt(3/2)
s2 = np.sqrt(2)
assert_allclose(z0, np.array([[np.nan, -s, -1.0, np.nan],
[np.nan, 0, 1.0, np.nan],
[np.nan, s, np.nan, np.nan]]))
z1 = stats.zscore(x, nan_policy='omit', axis=1)
assert_allclose(z1, np.array([[np.nan, np.nan, np.nan, np.nan],
[-s, 0, s, np.nan],
[-s2/2, s2, np.nan, -s2/2]]))
def test_zscore_2d_all_nan_row(self):
# A row is all nan, and we use axis=1.
x = np.array([[np.nan, np.nan, np.nan, np.nan],
[10.0, 10.0, 12.0, 12.0]])
z = stats.zscore(x, nan_policy='omit', axis=1)
assert_equal(z, np.array([[np.nan, np.nan, np.nan, np.nan],
[-1.0, -1.0, 1.0, 1.0]]))
def test_zscore_2d_all_nan(self):
# The entire 2d array is nan, and we use axis=None.
y = np.full((2, 3), np.nan)
z = stats.zscore(y, nan_policy='omit', axis=None)
assert_equal(z, y)
@pytest.mark.parametrize('x', [np.array([]), np.zeros((3, 0, 5))])
def test_zscore_empty_input(self, x):
z = stats.zscore(x)
assert_equal(z, x)
def test_gzscore_normal_array(self):
x = np.array([1, 2, 3, 4])
z = stats.gzscore(x)
desired = np.log(x / stats.gmean(x)) / np.log(stats.gstd(x, ddof=0))
assert_allclose(desired, z)
@skip_xp_invalid_arg
def test_gzscore_masked_array(self):
x = np.array([1, 2, -1, 3, 4])
mx = np.ma.masked_array(x, mask=[0, 0, 1, 0, 0])
z = stats.gzscore(mx)
desired = ([-1.526072095151, -0.194700599824, np.inf, 0.584101799472,
1.136670895503])
assert_allclose(desired, z)
@skip_xp_invalid_arg
def test_zscore_masked_element_0_gh19039(self):
# zscore returned all NaNs when 0th element was masked. See gh-19039.
rng = np.random.default_rng(8675309)
x = rng.standard_normal(10)
mask = np.zeros_like(x)
y = np.ma.masked_array(x, mask)
y.mask[0] = True
ref = stats.zscore(x[1:]) # compute reference from non-masked elements
assert not np.any(np.isnan(ref))
res = stats.zscore(y)
assert_allclose(res[1:], ref)
res = stats.zscore(y, axis=None)
assert_allclose(res[1:], ref)
y[1:] = y[1] # when non-masked elements are identical, result is nan
res = stats.zscore(y)
assert_equal(res[1:], np.nan)
res = stats.zscore(y, axis=None)
assert_equal(res[1:], np.nan)
class TestMedianAbsDeviation:
def setup_class(self):
self.dat_nan = np.array([2.20, 2.20, 2.4, 2.4, 2.5, 2.7, 2.8, 2.9,
3.03, 3.03, 3.10, 3.37, 3.4, 3.4, 3.4, 3.5,
3.6, 3.7, 3.7, 3.7, 3.7, 3.77, 5.28, np.nan])
self.dat = np.array([2.20, 2.20, 2.4, 2.4, 2.5, 2.7, 2.8, 2.9, 3.03,
3.03, 3.10, 3.37, 3.4, 3.4, 3.4, 3.5, 3.6, 3.7,
3.7, 3.7, 3.7, 3.77, 5.28, 28.95])
def test_median_abs_deviation(self):
assert_almost_equal(stats.median_abs_deviation(self.dat, axis=None),
0.355)
dat = self.dat.reshape(6, 4)
mad = stats.median_abs_deviation(dat, axis=0)
mad_expected = np.asarray([0.435, 0.5, 0.45, 0.4])
assert_array_almost_equal(mad, mad_expected)
def test_mad_nan_omit(self):
mad = stats.median_abs_deviation(self.dat_nan, nan_policy='omit')
assert_almost_equal(mad, 0.34)
def test_axis_and_nan(self):
x = np.array([[1.0, 2.0, 3.0, 4.0, np.nan],
[1.0, 4.0, 5.0, 8.0, 9.0]])
mad = stats.median_abs_deviation(x, axis=1)
assert_equal(mad, np.array([np.nan, 3.0]))
def test_nan_policy_omit_with_inf(self):
z = np.array([1, 3, 4, 6, 99, np.nan, np.inf])
mad = stats.median_abs_deviation(z, nan_policy='omit')
assert_equal(mad, 3.0)
@pytest.mark.parametrize('axis', [0, 1, 2, None])
def test_size_zero_with_axis(self, axis):
x = np.zeros((3, 0, 4))
mad = stats.median_abs_deviation(x, axis=axis)
assert_equal(mad, np.full_like(x.sum(axis=axis), fill_value=np.nan))
@pytest.mark.parametrize('nan_policy, expected',
[('omit', np.array([np.nan, 1.5, 1.5])),
('propagate', np.array([np.nan, np.nan, 1.5]))])
def test_nan_policy_with_axis(self, nan_policy, expected):
x = np.array([[np.nan, np.nan, np.nan, np.nan, np.nan, np.nan],
[1, 5, 3, 6, np.nan, np.nan],
[5, 6, 7, 9, 9, 10]])
mad = stats.median_abs_deviation(x, nan_policy=nan_policy, axis=1)
assert_equal(mad, expected)
@pytest.mark.parametrize('axis, expected',
[(1, [2.5, 2.0, 12.0]), (None, 4.5)])
def test_center_mean_with_nan(self, axis, expected):
x = np.array([[1, 2, 4, 9, np.nan],
[0, 1, 1, 1, 12],
[-10, -10, -10, 20, 20]])
mad = stats.median_abs_deviation(x, center=np.mean, nan_policy='omit',
axis=axis)
assert_allclose(mad, expected, rtol=1e-15, atol=1e-15)
def test_center_not_callable(self):
with pytest.raises(TypeError, match='callable'):
stats.median_abs_deviation([1, 2, 3, 5], center=99)
def _check_warnings(warn_list, expected_type, expected_len):
"""
Checks that all of the warnings from a list returned by
`warnings.catch_all(record=True)` are of the required type and that the list
contains expected number of warnings.
"""
assert_equal(len(warn_list), expected_len, "number of warnings")
for warn_ in warn_list:
assert_(warn_.category is expected_type)
class TestIQR:
def test_basic(self):
x = np.arange(8) * 0.5
np.random.shuffle(x)
assert_equal(stats.iqr(x), 1.75)
def test_api(self):
d = np.ones((5, 5))
stats.iqr(d)
stats.iqr(d, None)
stats.iqr(d, 1)
stats.iqr(d, (0, 1))
stats.iqr(d, None, (10, 90))
stats.iqr(d, None, (30, 20), 1.0)
stats.iqr(d, None, (25, 75), 1.5, 'propagate')
stats.iqr(d, None, (50, 50), 'normal', 'raise', 'linear')
stats.iqr(d, None, (25, 75), -0.4, 'omit', 'lower', True)
@pytest.mark.parametrize('x', [[], np.arange(0)])
def test_empty(self, x):
with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
assert_equal(stats.iqr(x), np.nan)
def test_constant(self):
# Constant array always gives 0
x = np.ones((7, 4))
assert_equal(stats.iqr(x), 0.0)
assert_array_equal(stats.iqr(x, axis=0), np.zeros(4))
assert_array_equal(stats.iqr(x, axis=1), np.zeros(7))
assert_equal(stats.iqr(x, interpolation='linear'), 0.0)
assert_equal(stats.iqr(x, interpolation='midpoint'), 0.0)
assert_equal(stats.iqr(x, interpolation='nearest'), 0.0)
assert_equal(stats.iqr(x, interpolation='lower'), 0.0)
assert_equal(stats.iqr(x, interpolation='higher'), 0.0)
# 0 only along constant dimensions
# This also tests much of `axis`
y = np.ones((4, 5, 6)) * np.arange(6)
assert_array_equal(stats.iqr(y, axis=0), np.zeros((5, 6)))
assert_array_equal(stats.iqr(y, axis=1), np.zeros((4, 6)))
assert_array_equal(stats.iqr(y, axis=2), np.full((4, 5), 2.5))
assert_array_equal(stats.iqr(y, axis=(0, 1)), np.zeros(6))
assert_array_equal(stats.iqr(y, axis=(0, 2)), np.full(5, 3.))
assert_array_equal(stats.iqr(y, axis=(1, 2)), np.full(4, 3.))
def test_scalarlike(self):
x = np.arange(1) + 7.0
assert_equal(stats.iqr(x[0]), 0.0)
assert_equal(stats.iqr(x), 0.0)
assert_array_equal(stats.iqr(x, keepdims=True), [0.0])
def test_2D(self):
x = np.arange(15).reshape((3, 5))
assert_equal(stats.iqr(x), 7.0)
assert_array_equal(stats.iqr(x, axis=0), np.full(5, 5.))
assert_array_equal(stats.iqr(x, axis=1), np.full(3, 2.))
assert_array_equal(stats.iqr(x, axis=(0, 1)), 7.0)
assert_array_equal(stats.iqr(x, axis=(1, 0)), 7.0)
def test_axis(self):
# The `axis` keyword is also put through its paces in `test_keepdims`.
o = np.random.normal(size=(71, 23))
x = np.dstack([o] * 10) # x.shape = (71, 23, 10)
q = stats.iqr(o)
assert_equal(stats.iqr(x, axis=(0, 1)), q)
x = np.moveaxis(x, -1, 0) # x.shape = (10, 71, 23)
assert_equal(stats.iqr(x, axis=(2, 1)), q)
x = x.swapaxes(0, 1) # x.shape = (71, 10, 23)
assert_equal(stats.iqr(x, axis=(0, 2)), q)
x = x.swapaxes(0, 1) # x.shape = (10, 71, 23)
assert_equal(stats.iqr(x, axis=(0, 1, 2)),
stats.iqr(x, axis=None))
assert_equal(stats.iqr(x, axis=(0,)),
stats.iqr(x, axis=0))
d = np.arange(3 * 5 * 7 * 11)
# Older versions of numpy only shuffle along axis=0.
# Not sure about newer, don't care.
np.random.shuffle(d)
d = d.reshape((3, 5, 7, 11))
assert_equal(stats.iqr(d, axis=(0, 1, 2))[0],
stats.iqr(d[:,:,:, 0].ravel()))
assert_equal(stats.iqr(d, axis=(0, 1, 3))[1],
stats.iqr(d[:,:, 1,:].ravel()))
assert_equal(stats.iqr(d, axis=(3, 1, -4))[2],
stats.iqr(d[:,:, 2,:].ravel()))
assert_equal(stats.iqr(d, axis=(3, 1, 2))[2],
stats.iqr(d[2,:,:,:].ravel()))
assert_equal(stats.iqr(d, axis=(3, 2))[2, 1],
stats.iqr(d[2, 1,:,:].ravel()))
assert_equal(stats.iqr(d, axis=(1, -2))[2, 1],
stats.iqr(d[2, :, :, 1].ravel()))
assert_equal(stats.iqr(d, axis=(1, 3))[2, 2],
stats.iqr(d[2, :, 2,:].ravel()))
assert_raises(AxisError, stats.iqr, d, axis=4)
assert_raises(ValueError, stats.iqr, d, axis=(0, 0))
def test_rng(self):
x = np.arange(5)
assert_equal(stats.iqr(x), 2)
assert_equal(stats.iqr(x, rng=(25, 87.5)), 2.5)
assert_equal(stats.iqr(x, rng=(12.5, 75)), 2.5)
assert_almost_equal(stats.iqr(x, rng=(10, 50)), 1.6) # 3-1.4
assert_raises(ValueError, stats.iqr, x, rng=(0, 101))
assert_raises(ValueError, stats.iqr, x, rng=(np.nan, 25))
assert_raises(TypeError, stats.iqr, x, rng=(0, 50, 60))
def test_interpolation(self):
x = np.arange(5)
y = np.arange(4)
# Default
assert_equal(stats.iqr(x), 2)
assert_equal(stats.iqr(y), 1.5)
# Linear
assert_equal(stats.iqr(x, interpolation='linear'), 2)
assert_equal(stats.iqr(y, interpolation='linear'), 1.5)
# Higher
assert_equal(stats.iqr(x, interpolation='higher'), 2)
assert_equal(stats.iqr(x, rng=(25, 80), interpolation='higher'), 3)
assert_equal(stats.iqr(y, interpolation='higher'), 2)
# Lower (will generally, but not always be the same as higher)
assert_equal(stats.iqr(x, interpolation='lower'), 2)
assert_equal(stats.iqr(x, rng=(25, 80), interpolation='lower'), 2)
assert_equal(stats.iqr(y, interpolation='lower'), 2)
# Nearest
assert_equal(stats.iqr(x, interpolation='nearest'), 2)
assert_equal(stats.iqr(y, interpolation='nearest'), 1)
# Midpoint
assert_equal(stats.iqr(x, interpolation='midpoint'), 2)
assert_equal(stats.iqr(x, rng=(25, 80), interpolation='midpoint'), 2.5)
assert_equal(stats.iqr(y, interpolation='midpoint'), 2)
# Check all method= values new in numpy 1.22.0 are accepted
for method in ('inverted_cdf', 'averaged_inverted_cdf',
'closest_observation', 'interpolated_inverted_cdf',
'hazen', 'weibull', 'median_unbiased',
'normal_unbiased'):
stats.iqr(y, interpolation=method)
assert_raises(ValueError, stats.iqr, x, interpolation='foobar')
def test_keepdims(self):
# Also tests most of `axis`
x = np.ones((3, 5, 7, 11))
assert_equal(stats.iqr(x, axis=None, keepdims=False).shape, ())
assert_equal(stats.iqr(x, axis=2, keepdims=False).shape, (3, 5, 11))
assert_equal(stats.iqr(x, axis=(0, 1), keepdims=False).shape, (7, 11))
assert_equal(stats.iqr(x, axis=(0, 3), keepdims=False).shape, (5, 7))
assert_equal(stats.iqr(x, axis=(1,), keepdims=False).shape, (3, 7, 11))
assert_equal(stats.iqr(x, (0, 1, 2, 3), keepdims=False).shape, ())
assert_equal(stats.iqr(x, axis=(0, 1, 3), keepdims=False).shape, (7,))
assert_equal(stats.iqr(x, axis=None, keepdims=True).shape, (1, 1, 1, 1))
assert_equal(stats.iqr(x, axis=2, keepdims=True).shape, (3, 5, 1, 11))
assert_equal(stats.iqr(x, axis=(0, 1), keepdims=True).shape, (1, 1, 7, 11))
assert_equal(stats.iqr(x, axis=(0, 3), keepdims=True).shape, (1, 5, 7, 1))
assert_equal(stats.iqr(x, axis=(1,), keepdims=True).shape, (3, 1, 7, 11))
assert_equal(stats.iqr(x, (0, 1, 2, 3), keepdims=True).shape, (1, 1, 1, 1))
assert_equal(stats.iqr(x, axis=(0, 1, 3), keepdims=True).shape, (1, 1, 7, 1))
def test_nanpolicy(self):
x = np.arange(15.0).reshape((3, 5))
# No NaNs
assert_equal(stats.iqr(x, nan_policy='propagate'), 7)
assert_equal(stats.iqr(x, nan_policy='omit'), 7)
assert_equal(stats.iqr(x, nan_policy='raise'), 7)
# Yes NaNs
x[1, 2] = np.nan
with warnings.catch_warnings(record=True):
warnings.simplefilter("always")
assert_equal(stats.iqr(x, nan_policy='propagate'),
np.nan)
assert_equal(stats.iqr(x, axis=0, nan_policy='propagate'),
[5, 5, np.nan, 5, 5])
assert_equal(stats.iqr(x, axis=1, nan_policy='propagate'),
[2, np.nan, 2])
with warnings.catch_warnings(record=True):
warnings.simplefilter("always")
assert_equal(stats.iqr(x, nan_policy='omit'), 7.5)
assert_equal(stats.iqr(x, axis=0, nan_policy='omit'), np.full(5, 5))
assert_equal(stats.iqr(x, axis=1, nan_policy='omit'), [2, 2.5, 2])
assert_raises(ValueError, stats.iqr, x, nan_policy='raise')
assert_raises(ValueError, stats.iqr, x, axis=0, nan_policy='raise')
assert_raises(ValueError, stats.iqr, x, axis=1, nan_policy='raise')
# Bad policy
assert_raises(ValueError, stats.iqr, x, nan_policy='barfood')
def test_scale(self):
x = np.arange(15.0).reshape((3, 5))
# No NaNs
assert_equal(stats.iqr(x, scale=1.0), 7)
assert_almost_equal(stats.iqr(x, scale='normal'), 7 / 1.3489795)
assert_equal(stats.iqr(x, scale=2.0), 3.5)
# Yes NaNs
x[1, 2] = np.nan
with warnings.catch_warnings(record=True):
warnings.simplefilter("always")
assert_equal(stats.iqr(x, scale=1.0, nan_policy='propagate'), np.nan)
assert_equal(stats.iqr(x, scale='normal', nan_policy='propagate'), np.nan)
assert_equal(stats.iqr(x, scale=2.0, nan_policy='propagate'), np.nan)
# axis=1 chosen to show behavior with both nans and without
assert_equal(stats.iqr(x, axis=1, scale=1.0,
nan_policy='propagate'), [2, np.nan, 2])
assert_almost_equal(stats.iqr(x, axis=1, scale='normal',
nan_policy='propagate'),
np.array([2, np.nan, 2]) / 1.3489795)
assert_equal(stats.iqr(x, axis=1, scale=2.0, nan_policy='propagate'),
[1, np.nan, 1])
# Since NumPy 1.17.0.dev, warnings are no longer emitted by
# np.percentile with nans, so we don't check the number of
# warnings here. See https://github.com/numpy/numpy/pull/12679.
assert_equal(stats.iqr(x, scale=1.0, nan_policy='omit'), 7.5)
assert_almost_equal(stats.iqr(x, scale='normal', nan_policy='omit'),
7.5 / 1.3489795)
assert_equal(stats.iqr(x, scale=2.0, nan_policy='omit'), 3.75)
# Bad scale
assert_raises(ValueError, stats.iqr, x, scale='foobar')
class TestMoments:
"""
Comparison numbers are found using R v.1.5.1
note that length(testcase) = 4
testmathworks comes from documentation for the
Statistics Toolbox for Matlab and can be found at both
https://www.mathworks.com/help/stats/kurtosis.html
https://www.mathworks.com/help/stats/skewness.html
Note that both test cases came from here.
"""
testcase = [1., 2., 3., 4.]
scalar_testcase = 4.
np.random.seed(1234)
testcase_moment_accuracy = np.random.rand(42)
def _assert_equal(self, actual, expect, *, shape=None, dtype=None):
expect = np.asarray(expect)
if shape is not None:
expect = np.broadcast_to(expect, shape)
assert_array_equal(actual, expect)
if dtype is None:
dtype = expect.dtype
assert actual.dtype == dtype
@array_api_compatible
@pytest.mark.parametrize('size', [10, (10, 2)])
@pytest.mark.parametrize('m, c', product((0, 1, 2, 3), (None, 0, 1)))
def test_moment_center_scalar_moment(self, size, m, c, xp):
rng = np.random.default_rng(6581432544381372042)
x = xp.asarray(rng.random(size=size))
res = stats.moment(x, m, center=c)
c = xp.mean(x, axis=0) if c is None else c
ref = xp.sum((x - c)**m, axis=0)/x.shape[0]
xp_assert_close(res, ref, atol=1e-16)
@array_api_compatible
@pytest.mark.parametrize('size', [10, (10, 2)])
@pytest.mark.parametrize('c', (None, 0, 1))
def test_moment_center_array_moment(self, size, c, xp):
rng = np.random.default_rng(1706828300224046506)
x = xp.asarray(rng.random(size=size))
m = [0, 1, 2, 3]
res = stats.moment(x, m, center=c)
xp_test = array_namespace(x) # no `concat` in np < 2.0; no `newaxis` in torch
ref = xp_test.concat([stats.moment(x, i, center=c)[xp_test.newaxis, ...]
for i in m])
xp_assert_equal(res, ref)
@array_api_compatible
def test_moment(self, xp):
# mean((testcase-mean(testcase))**power,axis=0),axis=0))**power))
testcase = xp.asarray(self.testcase)
y = stats.moment(xp.asarray(self.scalar_testcase))
xp_assert_close(y, xp.asarray(0.0))
y = stats.moment(testcase, 0)
xp_assert_close(y, xp.asarray(1.0))
y = stats.moment(testcase, 1)
xp_assert_close(y, xp.asarray(0.0))
y = stats.moment(testcase, 2)
xp_assert_close(y, xp.asarray(1.25))
y = stats.moment(testcase, 3)
xp_assert_close(y, xp.asarray(0.0))
y = stats.moment(testcase, 4)
xp_assert_close(y, xp.asarray(2.5625))
# check array_like input for moment
y = stats.moment(testcase, [1, 2, 3, 4])
xp_assert_close(y, xp.asarray([0, 1.25, 0, 2.5625]))
# check moment input consists only of integers
y = stats.moment(testcase, 0.0)
xp_assert_close(y, xp.asarray(1.0))
message = 'All elements of `order` must be integral.'
with pytest.raises(ValueError, match=message):
stats.moment(testcase, 1.2)
y = stats.moment(testcase, [1.0, 2, 3, 4.0])
xp_assert_close(y, xp.asarray([0, 1.25, 0, 2.5625]))
def test_cases():
y = stats.moment(xp.asarray([]))
xp_assert_equal(y, xp.asarray(xp.nan))
y = stats.moment(xp.asarray([], dtype=xp.float32))
xp_assert_equal(y, xp.asarray(xp.nan, dtype=xp.float32))
y = stats.moment(xp.zeros((1, 0)), axis=0)
xp_assert_equal(y, xp.empty((0,)))
y = stats.moment(xp.asarray([[]]), axis=1)
xp_assert_equal(y, xp.asarray([xp.nan]))
y = stats.moment(xp.asarray([[]]), order=[0, 1], axis=0)
xp_assert_equal(y, xp.empty((2, 0)))
# test empty input
if is_numpy(xp):
with pytest.warns(SmallSampleWarning, match="See documentation for..."):
test_cases()
else:
with np.testing.suppress_warnings() as sup: # needed by array_api_strict
sup.filter(RuntimeWarning, "Mean of empty slice.")
sup.filter(RuntimeWarning, "invalid value")
test_cases()
def test_nan_policy(self):
x = np.arange(10.)
x[9] = np.nan
assert_equal(stats.moment(x, 2), np.nan)
assert_almost_equal(stats.moment(x, nan_policy='omit'), 0.0)
assert_raises(ValueError, stats.moment, x, nan_policy='raise')
assert_raises(ValueError, stats.moment, x, nan_policy='foobar')
@array_api_compatible
@pytest.mark.parametrize('dtype', ['float32', 'float64', 'complex128'])
@pytest.mark.parametrize('expect, order', [(0, 1), (1, 0)])
def test_constant_moments(self, dtype, expect, order, xp):
if dtype=='complex128' and is_torch(xp):
pytest.skip()
dtype = getattr(xp, dtype)
x = xp.asarray(np.random.rand(5), dtype=dtype)
y = stats.moment(x, order=order)
xp_assert_equal(y, xp.asarray(expect, dtype=dtype))
y = stats.moment(xp.broadcast_to(x, (6, 5)), axis=0, order=order)
xp_assert_equal(y, xp.full((5,), expect, dtype=dtype))
y = stats.moment(xp.broadcast_to(x, (1, 2, 3, 4, 5)), axis=2,
order=order)
xp_assert_equal(y, xp.full((1, 2, 4, 5), expect, dtype=dtype))
y = stats.moment(xp.broadcast_to(x, (1, 2, 3, 4, 5)), axis=None,
order=order)
xp_assert_equal(y, xp.full((), expect, dtype=dtype))
@skip_xp_backends('jax.numpy',
reasons=["JAX arrays do not support item assignment"])
@pytest.mark.usefixtures("skip_xp_backends")
@array_api_compatible
def test_moment_propagate_nan(self, xp):
# Check that the shape of the result is the same for inputs
# with and without nans, cf gh-5817
a = xp.reshape(xp.arange(8.), (2, -1))
a[1, 0] = np.nan
mm = stats.moment(a, 2, axis=1)
xp_assert_close(mm, xp.asarray([1.25, np.nan]), atol=1e-15)
@array_api_compatible
def test_moment_empty_order(self, xp):
# tests moment with empty `order` list
with pytest.raises(ValueError, match=r"'order' must be a scalar or a"
r" non-empty 1D list/array."):
stats.moment(xp.asarray([1, 2, 3, 4]), order=[])
@array_api_compatible
def test_rename_moment_order(self, xp):
# Parameter 'order' was formerly known as 'moment'. The old name
# has not been deprecated, so it must continue to work.
x = xp.arange(10)
res = stats.moment(x, moment=3)
ref = stats.moment(x, order=3)
xp_assert_equal(res, ref)
def test_moment_accuracy(self):
# 'moment' must have a small enough error compared to the slower
# but very accurate numpy.power() implementation.
tc_no_mean = (self.testcase_moment_accuracy
- np.mean(self.testcase_moment_accuracy))
assert_allclose(np.power(tc_no_mean, 42).mean(),
stats.moment(self.testcase_moment_accuracy, 42))
@array_api_compatible
@pytest.mark.parametrize('order', [0, 1, 2, 3])
@pytest.mark.parametrize('axis', [-1, 0, 1])
@pytest.mark.parametrize('center', [None, 0])
def test_moment_array_api(self, xp, order, axis, center):
rng = np.random.default_rng(34823589259425)
x = rng.random(size=(5, 6, 7))
res = stats.moment(xp.asarray(x), order, axis=axis, center=center)
ref = xp.asarray(_moment(x, order, axis, mean=center))
xp_assert_close(res, ref)
class SkewKurtosisTest:
scalar_testcase = 4.
testcase = [1., 2., 3., 4.]
testmathworks = [1.165, 0.6268, 0.0751, 0.3516, -0.6965]
class TestSkew(SkewKurtosisTest):
@array_api_compatible
@pytest.mark.parametrize('stat_fun', [stats.skew, stats.kurtosis])
def test_empty_1d(self, stat_fun, xp):
x = xp.asarray([])
if is_numpy(xp):
with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
res = stat_fun(x)
else:
with np.testing.suppress_warnings() as sup:
# array_api_strict produces these
sup.filter(RuntimeWarning, "Mean of empty slice")
sup.filter(RuntimeWarning, "invalid value encountered")
res = stat_fun(x)
xp_assert_equal(res, xp.asarray(xp.nan))
@skip_xp_backends('jax.numpy',
reasons=["JAX arrays do not support item assignment"])
@pytest.mark.usefixtures("skip_xp_backends")
@array_api_compatible
def test_skewness(self, xp):
# Scalar test case
y = stats.skew(xp.asarray(self.scalar_testcase))
xp_assert_close(y, xp.asarray(xp.nan))
# sum((testmathworks-mean(testmathworks,axis=0))**3,axis=0) /
# ((sqrt(var(testmathworks)*4/5))**3)/5
y = stats.skew(xp.asarray(self.testmathworks))
xp_assert_close(y, xp.asarray(-0.29322304336607), atol=1e-10)
y = stats.skew(xp.asarray(self.testmathworks), bias=0)
xp_assert_close(y, xp.asarray(-0.437111105023940), atol=1e-10)
y = stats.skew(xp.asarray(self.testcase))
xp_assert_close(y, xp.asarray(0.0), atol=1e-10)
def test_nan_policy(self):
# initially, nan_policy is ignored with alternative backends
x = np.arange(10.)
x[9] = np.nan
with np.errstate(invalid='ignore'):
assert_equal(stats.skew(x), np.nan)
assert_equal(stats.skew(x, nan_policy='omit'), 0.)
assert_raises(ValueError, stats.skew, x, nan_policy='raise')
assert_raises(ValueError, stats.skew, x, nan_policy='foobar')
def test_skewness_scalar(self):
# `skew` must return a scalar for 1-dim input (only for NumPy arrays)
assert_equal(stats.skew(arange(10)), 0.0)
@skip_xp_backends('jax.numpy',
reasons=["JAX arrays do not support item assignment"])
@pytest.mark.usefixtures("skip_xp_backends")
@array_api_compatible
def test_skew_propagate_nan(self, xp):
# Check that the shape of the result is the same for inputs
# with and without nans, cf gh-5817
a = xp.arange(8.)
a = xp.reshape(a, (2, -1))
a[1, 0] = xp.nan
with np.errstate(invalid='ignore'):
s = stats.skew(a, axis=1)
xp_assert_equal(s, xp.asarray([0, xp.nan]))
@array_api_compatible
def test_skew_constant_value(self, xp):
# Skewness of a constant input should be NaN (gh-16061)
with pytest.warns(RuntimeWarning, match="Precision loss occurred"):
a = xp.asarray([-0.27829495]*10) # xp.repeat not currently available
xp_assert_equal(stats.skew(a), xp.asarray(xp.nan))
xp_assert_equal(stats.skew(a*2.**50), xp.asarray(xp.nan))
xp_assert_equal(stats.skew(a/2.**50), xp.asarray(xp.nan))
xp_assert_equal(stats.skew(a, bias=False), xp.asarray(xp.nan))
# # similarly, from gh-11086:
a = xp.asarray([14.3]*7)
xp_assert_equal(stats.skew(a), xp.asarray(xp.nan))
a = 1. + xp.arange(-3., 4)*1e-16
xp_assert_equal(stats.skew(a), xp.asarray(xp.nan))
@skip_xp_backends('jax.numpy',
reasons=["JAX arrays do not support item assignment"])
@pytest.mark.usefixtures("skip_xp_backends")
@array_api_compatible
def test_precision_loss_gh15554(self, xp):
# gh-15554 was one of several issues that have reported problems with
# constant or near-constant input. We can't always fix these, but
# make sure there's a warning.
with pytest.warns(RuntimeWarning, match="Precision loss occurred"):
rng = np.random.default_rng(34095309370)
a = xp.asarray(rng.random(size=(100, 10)))
a[:, 0] = 1.01
stats.skew(a)
@skip_xp_backends('jax.numpy',
reasons=["JAX arrays do not support item assignment"])
@pytest.mark.usefixtures("skip_xp_backends")
@array_api_compatible
@pytest.mark.parametrize('axis', [-1, 0, 2, None])
@pytest.mark.parametrize('bias', [False, True])
def test_vectorization(self, xp, axis, bias):
# Behavior with array input is barely tested above. Compare
# against naive implementation.
rng = np.random.default_rng(1283413549926)
x = xp.asarray(rng.random((3, 4, 5)))
def skewness(a, axis, bias):
# Simple implementation of skewness
if axis is None:
a = xp.reshape(a, (-1,))
axis = 0
xp_test = array_namespace(a) # plain torch ddof=1 by default
mean = xp_test.mean(a, axis=axis, keepdims=True)
mu3 = xp_test.mean((a - mean)**3, axis=axis)
std = xp_test.std(a, axis=axis)
res = mu3 / std ** 3
if not bias:
n = a.shape[axis]
res *= ((n - 1.0) * n) ** 0.5 / (n - 2.0)
return res
res = stats.skew(x, axis=axis, bias=bias)
ref = skewness(x, axis=axis, bias=bias)
xp_assert_close(res, ref)
class TestKurtosis(SkewKurtosisTest):
@skip_xp_backends('jax.numpy',
reasons=['JAX arrays do not support item assignment'])
@pytest.mark.usefixtures("skip_xp_backends")
@array_api_compatible
def test_kurtosis(self, xp):
# Scalar test case
y = stats.kurtosis(xp.asarray(self.scalar_testcase))
assert xp.isnan(y)
# sum((testcase-mean(testcase,axis=0))**4,axis=0)
# / ((sqrt(var(testcase)*3/4))**4)
# / 4
#
# sum((test2-mean(testmathworks,axis=0))**4,axis=0)
# / ((sqrt(var(testmathworks)*4/5))**4)
# / 5
#
# Set flags for axis = 0 and
# fisher=0 (Pearson's defn of kurtosis for compatibility with Matlab)
y = stats.kurtosis(xp.asarray(self.testmathworks), 0, fisher=0, bias=1)
xp_assert_close(y, xp.asarray(2.1658856802973))
# Note that MATLAB has confusing docs for the following case
# kurtosis(x,0) gives an unbiased estimate of Pearson's skewness
# kurtosis(x) gives a biased estimate of Fisher's skewness (Pearson-3)
# The MATLAB docs imply that both should give Fisher's
y = stats.kurtosis(xp.asarray(self.testmathworks), fisher=0, bias=0)
xp_assert_close(y, xp.asarray(3.663542721189047))
y = stats.kurtosis(xp.asarray(self.testcase), 0, 0)
xp_assert_close(y, xp.asarray(1.64))
x = xp.arange(10.)
x = xp.where(x == 8, xp.asarray(xp.nan), x)
xp_assert_equal(stats.kurtosis(x), xp.asarray(xp.nan))
def test_kurtosis_nan_policy(self):
# nan_policy only for NumPy right now
x = np.arange(10.)
x[9] = np.nan
assert_almost_equal(stats.kurtosis(x, nan_policy='omit'), -1.230000)
assert_raises(ValueError, stats.kurtosis, x, nan_policy='raise')
assert_raises(ValueError, stats.kurtosis, x, nan_policy='foobar')
def test_kurtosis_array_scalar(self):
# "array scalars" do not exist in other backends
assert_equal(type(stats.kurtosis([1, 2, 3])), np.float64)
def test_kurtosis_propagate_nan(self):
# nan_policy only for NumPy right now
# Check that the shape of the result is the same for inputs
# with and without nans, cf gh-5817
a = np.arange(8).reshape(2, -1).astype(float)
a[1, 0] = np.nan
k = stats.kurtosis(a, axis=1, nan_policy="propagate")
np.testing.assert_allclose(k, [-1.36, np.nan], atol=1e-15)
@array_api_compatible
def test_kurtosis_constant_value(self, xp):
# Kurtosis of a constant input should be NaN (gh-16061)
a = xp.asarray([-0.27829495]*10)
with pytest.warns(RuntimeWarning, match="Precision loss occurred"):
assert xp.isnan(stats.kurtosis(a, fisher=False))
assert xp.isnan(stats.kurtosis(a * float(2**50), fisher=False))
assert xp.isnan(stats.kurtosis(a / float(2**50), fisher=False))
assert xp.isnan(stats.kurtosis(a, fisher=False, bias=False))
@skip_xp_backends('jax.numpy',
reasons=['JAX arrays do not support item assignment'])
@pytest.mark.usefixtures("skip_xp_backends")
@array_api_compatible
@pytest.mark.parametrize('axis', [-1, 0, 2, None])
@pytest.mark.parametrize('bias', [False, True])
@pytest.mark.parametrize('fisher', [False, True])
def test_vectorization(self, xp, axis, bias, fisher):
# Behavior with array input is not tested above. Compare
# against naive implementation.
rng = np.random.default_rng(1283413549926)
x = xp.asarray(rng.random((4, 5, 6)))
def kurtosis(a, axis, bias, fisher):
# Simple implementation of kurtosis
if axis is None:
a = xp.reshape(a, (-1,))
axis = 0
xp_test = array_namespace(a) # plain torch ddof=1 by default
mean = xp_test.mean(a, axis=axis, keepdims=True)
mu4 = xp_test.mean((a - mean)**4, axis=axis)
mu2 = xp_test.var(a, axis=axis, correction=0)
if bias:
res = mu4 / mu2**2 - 3
else:
n = a.shape[axis]
# https://en.wikipedia.org/wiki/Kurtosis#Standard_unbiased_estimator
res = (n-1) / ((n-2) * (n-3)) * ((n + 1) * mu4/mu2**2 - 3*(n-1))
# I know it looks strange to subtract then add 3,
# but it is simpler than the alternatives
return res if fisher else res + 3
res = stats.kurtosis(x, axis=axis, bias=bias, fisher=fisher)
ref = kurtosis(x, axis=axis, bias=bias, fisher=fisher)
xp_assert_close(res, ref)
@hypothesis.strategies.composite
def ttest_data_axis_strategy(draw):
# draw an array under shape and value constraints
elements = dict(allow_nan=False, allow_infinity=False)
shape = npst.array_shapes(min_dims=1, min_side=2)
# The test that uses this, `test_pvalue_ci`, uses `float64` to test
# extreme `alpha`. It could be adjusted to test a dtype-dependent
# range of `alpha` if this strategy is needed to generate other floats.
data = draw(npst.arrays(dtype=np.float64, elements=elements, shape=shape))
# determine axes over which nonzero variance can be computed accurately
ok_axes = []
# Locally, I don't need catch_warnings or simplefilter, and I can just
# suppress RuntimeWarning. I include all that in hope of getting the same
# behavior on CI.
with warnings.catch_warnings():
warnings.simplefilter("error")
for axis in range(len(data.shape)):
with contextlib.suppress(Exception):
var = stats.moment(data, order=2, axis=axis)
if np.all(var > 0) and np.all(np.isfinite(var)):
ok_axes.append(axis)
# if there are no valid axes, tell hypothesis to try a different example
hypothesis.assume(ok_axes)
# draw one of the valid axes
axis = draw(hypothesis.strategies.sampled_from(ok_axes))
return data, axis
@pytest.mark.skip_xp_backends(cpu_only=True,
reasons=['Uses NumPy for pvalue, CI'])
@pytest.mark.usefixtures("skip_xp_backends")
@array_api_compatible
class TestStudentTest:
# Preserving original test cases.
# Recomputed statistics and p-values with R t.test, e.g.
# options(digits=16)
# t.test(c(-1., 0., 1.), mu=2)
X1 = [-1., 0., 1.]
X2 = [0., 1., 2.]
T1_0 = 0.
P1_0 = 1.
T1_1 = -1.7320508075689
P1_1 = 0.2254033307585
T1_2 = -3.4641016151378
P1_2 = 0.07417990022745
T2_0 = 1.7320508075689
P2_0 = 0.2254033307585
P1_1_l = P1_1 / 2
P1_1_g = 1 - (P1_1 / 2)
def test_onesample(self, xp):
with suppress_warnings() as sup, \
np.errstate(invalid="ignore", divide="ignore"):
sup.filter(RuntimeWarning, "Degrees of freedom <= 0 for slice")
a = xp.asarray(4.) if not is_numpy(xp) else 4.
t, p = stats.ttest_1samp(a, 3.)
xp_assert_equal(t, xp.asarray(xp.nan))
xp_assert_equal(p, xp.asarray(xp.nan))
t, p = stats.ttest_1samp(xp.asarray(self.X1), 0.)
xp_assert_close(t, xp.asarray(self.T1_0))
xp_assert_close(p, xp.asarray(self.P1_0))
res = stats.ttest_1samp(xp.asarray(self.X1), 0.)
attributes = ('statistic', 'pvalue')
check_named_results(res, attributes, xp=xp)
t, p = stats.ttest_1samp(xp.asarray(self.X2), 0.)
xp_assert_close(t, xp.asarray(self.T2_0))
xp_assert_close(p, xp.asarray(self.P2_0))
t, p = stats.ttest_1samp(xp.asarray(self.X1), 1.)
xp_assert_close(t, xp.asarray(self.T1_1))
xp_assert_close(p, xp.asarray(self.P1_1))
t, p = stats.ttest_1samp(xp.asarray(self.X1), 2.)
xp_assert_close(t, xp.asarray(self.T1_2))
xp_assert_close(p, xp.asarray(self.P1_2))
def test_onesample_nan_policy(self, xp):
# check nan policy
if not is_numpy(xp):
x = xp.asarray([1., 2., 3., xp.nan])
message = "Use of `nan_policy` and `keepdims`..."
with pytest.raises(NotImplementedError, match=message):
stats.ttest_1samp(x, 1., nan_policy='omit')
return
x = stats.norm.rvs(loc=5, scale=10, size=51, random_state=7654567)
x[50] = np.nan
with np.errstate(invalid="ignore"):
assert_array_equal(stats.ttest_1samp(x, 5.0), (np.nan, np.nan))
assert_array_almost_equal(stats.ttest_1samp(x, 5.0, nan_policy='omit'),
(-1.6412624074367159, 0.107147027334048005))
assert_raises(ValueError, stats.ttest_1samp, x, 5.0, nan_policy='raise')
assert_raises(ValueError, stats.ttest_1samp, x, 5.0,
nan_policy='foobar')
def test_1samp_alternative(self, xp):
message = "`alternative` must be 'less', 'greater', or 'two-sided'."
with pytest.raises(ValueError, match=message):
stats.ttest_1samp(xp.asarray(self.X1), 0., alternative="error")
t, p = stats.ttest_1samp(xp.asarray(self.X1), 1., alternative="less")
xp_assert_close(p, xp.asarray(self.P1_1_l))
xp_assert_close(t, xp.asarray(self.T1_1))
t, p = stats.ttest_1samp(xp.asarray(self.X1), 1., alternative="greater")
xp_assert_close(p, xp.asarray(self.P1_1_g))
xp_assert_close(t, xp.asarray(self.T1_1))
@pytest.mark.parametrize("alternative", ['two-sided', 'less', 'greater'])
def test_1samp_ci_1d(self, xp, alternative):
# test confidence interval method against reference values
rng = np.random.default_rng(8066178009154342972)
n = 10
x = rng.normal(size=n, loc=1.5, scale=2)
popmean = rng.normal() # this shouldn't affect confidence interval
# Reference values generated with R t.test:
# options(digits=16)
# x = c(2.75532884, 0.93892217, 0.94835861, 1.49489446, -0.62396595,
# -1.88019867, -1.55684465, 4.88777104, 5.15310979, 4.34656348)
# t.test(x, conf.level=0.85, alternative='l')
dtype = xp.float32 if is_torch(xp) else xp.float64 # use default dtype
x = xp.asarray(x, dtype=dtype)
popmean = xp.asarray(popmean, dtype=dtype)
ref = {'two-sided': [0.3594423211709136, 2.9333455028290860],
'greater': [0.7470806207371626, np.inf],
'less': [-np.inf, 2.545707203262837]}
res = stats.ttest_1samp(x, popmean=popmean, alternative=alternative)
ci = res.confidence_interval(confidence_level=0.85)
xp_assert_close(ci.low, xp.asarray(ref[alternative][0]))
xp_assert_close(ci.high, xp.asarray(ref[alternative][1]))
xp_assert_equal(res.df, xp.asarray(n-1))
def test_1samp_ci_iv(self, xp):
# test `confidence_interval` method input validation
res = stats.ttest_1samp(xp.arange(10.), 0.)
message = '`confidence_level` must be a number between 0 and 1.'
with pytest.raises(ValueError, match=message):
res.confidence_interval(confidence_level=10)
@pytest.mark.xslow
@hypothesis.given(alpha=hypothesis.strategies.floats(1e-15, 1-1e-15),
data_axis=ttest_data_axis_strategy())
@pytest.mark.parametrize('alternative', ['less', 'greater'])
def test_pvalue_ci(self, alpha, data_axis, alternative, xp):
# test relationship between one-sided p-values and confidence intervals
data, axis = data_axis
data = xp.asarray(data)
res = stats.ttest_1samp(data, 0.,
alternative=alternative, axis=axis)
l, u = res.confidence_interval(confidence_level=alpha)
popmean = l if alternative == 'greater' else u
xp_test = array_namespace(l) # torch needs `expand_dims`
popmean = xp_test.expand_dims(popmean, axis=axis)
res = stats.ttest_1samp(data, popmean, alternative=alternative, axis=axis)
shape = list(data.shape)
shape.pop(axis)
# `float64` is used to correspond with extreme range of `alpha`
ref = xp.broadcast_to(xp.asarray(1-alpha, dtype=xp.float64), shape)
xp_assert_close(res.pvalue, ref)
class TestPercentileOfScore:
def f(self, *args, **kwargs):
return stats.percentileofscore(*args, **kwargs)
@pytest.mark.parametrize("kind, result", [("rank", 40),
("mean", 35),
("strict", 30),
("weak", 40)])
def test_unique(self, kind, result):
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
assert_equal(self.f(a, 4, kind=kind), result)
@pytest.mark.parametrize("kind, result", [("rank", 45),
("mean", 40),
("strict", 30),
("weak", 50)])
def test_multiple2(self, kind, result):
a = [1, 2, 3, 4, 4, 5, 6, 7, 8, 9]
assert_equal(self.f(a, 4, kind=kind), result)
@pytest.mark.parametrize("kind, result", [("rank", 50),
("mean", 45),
("strict", 30),
("weak", 60)])
def test_multiple3(self, kind, result):
a = [1, 2, 3, 4, 4, 4, 5, 6, 7, 8]
assert_equal(self.f(a, 4, kind=kind), result)
@pytest.mark.parametrize("kind, result", [("rank", 30),
("mean", 30),
("strict", 30),
("weak", 30)])
def test_missing(self, kind, result):
a = [1, 2, 3, 5, 6, 7, 8, 9, 10, 11]
assert_equal(self.f(a, 4, kind=kind), result)
@pytest.mark.parametrize("kind, result", [("rank", 40),
("mean", 35),
("strict", 30),
("weak", 40)])
def test_large_numbers(self, kind, result):
a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
assert_equal(self.f(a, 40, kind=kind), result)
@pytest.mark.parametrize("kind, result", [("rank", 50),
("mean", 45),
("strict", 30),
("weak", 60)])
def test_large_numbers_multiple3(self, kind, result):
a = [10, 20, 30, 40, 40, 40, 50, 60, 70, 80]
assert_equal(self.f(a, 40, kind=kind), result)
@pytest.mark.parametrize("kind, result", [("rank", 30),
("mean", 30),
("strict", 30),
("weak", 30)])
def test_large_numbers_missing(self, kind, result):
a = [10, 20, 30, 50, 60, 70, 80, 90, 100, 110]
assert_equal(self.f(a, 40, kind=kind), result)
@pytest.mark.parametrize("kind, result", [("rank", [0, 10, 100, 100]),
("mean", [0, 5, 95, 100]),
("strict", [0, 0, 90, 100]),
("weak", [0, 10, 100, 100])])
def test_boundaries(self, kind, result):
a = [10, 20, 30, 50, 60, 70, 80, 90, 100, 110]
assert_equal(self.f(a, [0, 10, 110, 200], kind=kind), result)
@pytest.mark.parametrize("kind, result", [("rank", [0, 10, 100]),
("mean", [0, 5, 95]),
("strict", [0, 0, 90]),
("weak", [0, 10, 100])])
def test_inf(self, kind, result):
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, +np.inf]
assert_equal(self.f(a, [-np.inf, 1, +np.inf], kind=kind), result)
cases = [("propagate", [], 1, np.nan),
("propagate", [np.nan], 1, np.nan),
("propagate", [np.nan], [0, 1, 2], [np.nan, np.nan, np.nan]),
("propagate", [1, 2], [1, 2, np.nan], [50, 100, np.nan]),
("omit", [1, 2, np.nan], [0, 1, 2], [0, 50, 100]),
("omit", [1, 2], [0, 1, np.nan], [0, 50, np.nan]),
("omit", [np.nan, np.nan], [0, 1, 2], [np.nan, np.nan, np.nan])]
@pytest.mark.parametrize("policy, a, score, result", cases)
def test_nans_ok(self, policy, a, score, result):
assert_equal(self.f(a, score, nan_policy=policy), result)
cases = [
("raise", [1, 2, 3, np.nan], [1, 2, 3],
"The input contains nan values"),
("raise", [1, 2, 3], [1, 2, 3, np.nan],
"The input contains nan values"),
]
@pytest.mark.parametrize("policy, a, score, message", cases)
def test_nans_fail(self, policy, a, score, message):
with assert_raises(ValueError, match=message):
self.f(a, score, nan_policy=policy)
@pytest.mark.parametrize("shape", [
(6, ),
(2, 3),
(2, 1, 3),
(2, 1, 1, 3),
])
def test_nd(self, shape):
a = np.array([0, 1, 2, 3, 4, 5])
scores = a.reshape(shape)
results = scores*10
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
assert_equal(self.f(a, scores, kind="rank"), results)
PowerDivCase = namedtuple('Case', # type: ignore[name-match]
['f_obs', 'f_exp', 'ddof', 'axis',
'chi2', # Pearson's
'log', # G-test (log-likelihood)
'mod_log', # Modified log-likelihood
'cr', # Cressie-Read (lambda=2/3)
])
# The details of the first two elements in power_div_1d_cases are used
# in a test in TestPowerDivergence. Check that code before making
# any changes here.
power_div_1d_cases = [
# Use the default f_exp.
PowerDivCase(f_obs=[4, 8, 12, 8], f_exp=None, ddof=0, axis=None,
chi2=4,
log=2*(4*np.log(4/8) + 12*np.log(12/8)),
mod_log=2*(8*np.log(8/4) + 8*np.log(8/12)),
cr=(4*((4/8)**(2/3) - 1) + 12*((12/8)**(2/3) - 1))/(5/9)),
# Give a non-uniform f_exp.
PowerDivCase(f_obs=[4, 8, 12, 8], f_exp=[2, 16, 12, 2], ddof=0, axis=None,
chi2=24,
log=2*(4*np.log(4/2) + 8*np.log(8/16) + 8*np.log(8/2)),
mod_log=2*(2*np.log(2/4) + 16*np.log(16/8) + 2*np.log(2/8)),
cr=(4*((4/2)**(2/3) - 1) + 8*((8/16)**(2/3) - 1) +
8*((8/2)**(2/3) - 1))/(5/9)),
# f_exp is a scalar.
PowerDivCase(f_obs=[4, 8, 12, 8], f_exp=8, ddof=0, axis=None,
chi2=4,
log=2*(4*np.log(4/8) + 12*np.log(12/8)),
mod_log=2*(8*np.log(8/4) + 8*np.log(8/12)),
cr=(4*((4/8)**(2/3) - 1) + 12*((12/8)**(2/3) - 1))/(5/9)),
# f_exp equal to f_obs.
PowerDivCase(f_obs=[3, 5, 7, 9], f_exp=[3, 5, 7, 9], ddof=0, axis=0,
chi2=0, log=0, mod_log=0, cr=0),
]
power_div_empty_cases = [
# Shape is (0,)--a data set with length 0. The computed
# test statistic should be 0.
PowerDivCase(f_obs=[],
f_exp=None, ddof=0, axis=0,
chi2=0, log=0, mod_log=0, cr=0),
# Shape is (0, 3). This is 3 data sets, but each data set has
# length 0, so the computed test statistic should be [0, 0, 0].
PowerDivCase(f_obs=np.array([[],[],[]]).T,
f_exp=None, ddof=0, axis=0,
chi2=[0, 0, 0],
log=[0, 0, 0],
mod_log=[0, 0, 0],
cr=[0, 0, 0]),
# Shape is (3, 0). This represents an empty collection of
# data sets in which each data set has length 3. The test
# statistic should be an empty array.
PowerDivCase(f_obs=np.array([[],[],[]]),
f_exp=None, ddof=0, axis=0,
chi2=[],
log=[],
mod_log=[],
cr=[]),
]
@array_api_compatible
class TestPowerDivergence:
def check_power_divergence(self, f_obs, f_exp, ddof, axis, lambda_,
expected_stat, xp):
dtype = xp.asarray(1.).dtype
f_obs = xp.asarray(f_obs, dtype=dtype)
f_exp = xp.asarray(f_exp, dtype=dtype) if f_exp is not None else f_exp
if axis is None:
num_obs = xp_size(f_obs)
else:
xp_test = array_namespace(f_obs) # torch needs broadcast_arrays
arrays = (xp_test.broadcast_arrays(f_obs, f_exp) if f_exp is not None
else (f_obs,))
num_obs = arrays[0].shape[axis]
with suppress_warnings() as sup:
sup.filter(RuntimeWarning, "Mean of empty slice")
stat, p = stats.power_divergence(
f_obs=f_obs, f_exp=f_exp, ddof=ddof,
axis=axis, lambda_=lambda_)
xp_assert_close(stat, xp.asarray(expected_stat, dtype=dtype))
if lambda_ == 1 or lambda_ == "pearson":
# Also test stats.chisquare.
stat, p = stats.chisquare(f_obs=f_obs, f_exp=f_exp, ddof=ddof,
axis=axis)
xp_assert_close(stat, xp.asarray(expected_stat, dtype=dtype))
ddof = np.asarray(ddof)
expected_p = stats.distributions.chi2.sf(expected_stat,
num_obs - 1 - ddof)
xp_assert_close(p, xp.asarray(expected_p, dtype=dtype))
@pytest.mark.parametrize('case', power_div_1d_cases)
@pytest.mark.parametrize('lambda_stat',
[(None, 'chi2'), ('pearson', 'chi2'), (1, 'chi2'),
('log-likelihood', 'log'), ('mod-log-likelihood', 'mod_log'),
('cressie-read', 'cr'), (2/3, 'cr')])
def test_basic(self, case, lambda_stat, xp):
lambda_, attr = lambda_stat
expected_stat = getattr(case, attr)
self.check_power_divergence(case.f_obs, case.f_exp, case.ddof, case.axis,
lambda_, expected_stat, xp)
def test_axis(self, xp):
case0 = power_div_1d_cases[0]
case1 = power_div_1d_cases[1]
f_obs = np.vstack((case0.f_obs, case1.f_obs))
f_exp = np.vstack((np.ones_like(case0.f_obs)*np.mean(case0.f_obs),
case1.f_exp))
# Check the four computational code paths in power_divergence
# using a 2D array with axis=1.
f_obs = xp.asarray(f_obs)
f_exp = xp.asarray(f_exp) if f_exp is not None else f_exp
self.check_power_divergence(
f_obs, f_exp, 0, 1,
"pearson", [case0.chi2, case1.chi2], xp=xp)
self.check_power_divergence(
f_obs, f_exp, 0, 1,
"log-likelihood", [case0.log, case1.log], xp=xp)
self.check_power_divergence(
f_obs, f_exp, 0, 1,
"mod-log-likelihood", [case0.mod_log, case1.mod_log], xp=xp)
self.check_power_divergence(
f_obs, f_exp, 0, 1,
"cressie-read", [case0.cr, case1.cr], xp=xp)
# Reshape case0.f_obs to shape (2,2), and use axis=None.
# The result should be the same.
f_obs_reshape = xp.reshape(xp.asarray(case0.f_obs), (2, 2))
self.check_power_divergence(
f_obs_reshape, None, 0, None,
"pearson", case0.chi2, xp=xp)
def test_ddof_broadcasting(self, xp):
# Test that ddof broadcasts correctly.
# ddof does not affect the test statistic. It is broadcast
# with the computed test statistic for the computation of
# the p value.
case0 = power_div_1d_cases[0]
case1 = power_div_1d_cases[1]
# Create 4x2 arrays of observed and expected frequencies.
f_obs = np.vstack((case0.f_obs, case1.f_obs)).T
f_exp = np.vstack((np.ones_like(case0.f_obs)*np.mean(case0.f_obs),
case1.f_exp)).T
expected_chi2 = [case0.chi2, case1.chi2]
dtype = xp.asarray(1.).dtype
f_obs = xp.asarray(f_obs, dtype=dtype)
f_exp = xp.asarray(f_exp, dtype=dtype)
expected_chi2 = xp.asarray(expected_chi2, dtype=dtype)
# ddof has shape (2, 1). This is broadcast with the computed
# statistic, so p will have shape (2,2).
ddof = xp.asarray([[0], [1]])
stat, p = stats.power_divergence(f_obs, f_exp, ddof=ddof)
xp_assert_close(stat, expected_chi2)
# Compute the p values separately, passing in scalars for ddof.
stat0, p0 = stats.power_divergence(f_obs, f_exp, ddof=ddof[0, 0])
stat1, p1 = stats.power_divergence(f_obs, f_exp, ddof=ddof[1, 0])
xp_test = array_namespace(f_obs) # needs `concat`, `newaxis`
expected_p = xp_test.concat((p0[xp_test.newaxis, :],
p1[xp_test.newaxis, :]),
axis=0)
xp_assert_close(p, expected_p)
@pytest.mark.parametrize('case', power_div_empty_cases)
@pytest.mark.parametrize('lambda_stat',
[('pearson', 'chi2'), ('log-likelihood', 'log'),
('mod-log-likelihood', 'mod_log'),
('cressie-read', 'cr'), (2/3, 'cr')])
def test_empty_cases(self, case, lambda_stat, xp):
lambda_, attr = lambda_stat
expected_stat = getattr(case, attr)
with warnings.catch_warnings():
self.check_power_divergence(
case.f_obs, case.f_exp, case.ddof, case.axis,
lambda_, expected_stat, xp)
def test_power_divergence_result_attributes(self, xp):
f_obs = power_div_1d_cases[0].f_obs
f_exp = power_div_1d_cases[0].f_exp
ddof = power_div_1d_cases[0].ddof
axis = power_div_1d_cases[0].axis
dtype = xp.asarray(1.).dtype
f_obs = xp.asarray(f_obs, dtype=dtype)
# f_exp is None
res = stats.power_divergence(f_obs=f_obs, f_exp=f_exp, ddof=ddof,
axis=axis, lambda_="pearson")
attributes = ('statistic', 'pvalue')
check_named_results(res, attributes, xp=xp)
def test_power_divergence_gh_12282(self, xp):
# The sums of observed and expected frequencies must match
f_obs = xp.asarray([[10., 20.], [30., 20.]])
f_exp = xp.asarray([[5., 15.], [35., 25.]])
message = 'For each axis slice...'
with pytest.raises(ValueError, match=message):
stats.power_divergence(f_obs=f_obs, f_exp=xp.asarray([30., 60.]))
with pytest.raises(ValueError, match=message):
stats.power_divergence(f_obs=f_obs, f_exp=f_exp, axis=1)
stat, pval = stats.power_divergence(f_obs=f_obs, f_exp=f_exp)
xp_assert_close(stat, xp.asarray([5.71428571, 2.66666667]))
xp_assert_close(pval, xp.asarray([0.01682741, 0.10247043]))
def test_power_divergence_against_cressie_read_data(self, xp):
# Test stats.power_divergence against tables 4 and 5 from
# Cressie and Read, "Multimonial Goodness-of-Fit Tests",
# J. R. Statist. Soc. B (1984), Vol 46, No. 3, pp. 440-464.
# This tests the calculation for several values of lambda.
# Table 4 data recalculated for greater precision according to:
# Shelby J. Haberman, Analysis of Qualitative Data: Volume 1
# Introductory Topics, Academic Press, New York, USA (1978).
obs = xp.asarray([15., 11., 14., 17., 5., 11., 10., 4., 8.,
10., 7., 9., 11., 3., 6., 1., 1., 4.])
beta = -0.083769 # Haberman (1978), p. 15
i = xp.arange(1., obs.shape[0] + 1.)
alpha = xp.log(xp.sum(obs) / xp.sum(xp.exp(beta*i)))
expected_counts = xp.exp(alpha + beta*i)
# `table4` holds just the second and third columns from Table 4.
xp_test = array_namespace(obs) # NumPy needs concat, torch needs newaxis
table4 = xp_test.concat((obs[xp_test.newaxis, :],
expected_counts[xp_test.newaxis, :])).T
table5 = xp.asarray([
# lambda, statistic
-10.0, 72.2e3,
-5.0, 28.9e1,
-3.0, 65.6,
-2.0, 40.6,
-1.5, 34.0,
-1.0, 29.5,
-0.5, 26.5,
0.0, 24.6,
0.5, 23.4,
0.67, 23.1,
1.0, 22.7,
1.5, 22.6,
2.0, 22.9,
3.0, 24.8,
5.0, 35.5,
10.0, 21.4e1,
])
table5 = xp.reshape(table5, (-1, 2))
for i in range(table5.shape[0]):
lambda_, expected_stat = table5[i, 0], table5[i, 1]
stat, p = stats.power_divergence(table4[:,0], table4[:,1],
lambda_=lambda_)
xp_assert_close(stat, expected_stat, rtol=5e-3)
@array_api_compatible
class TestChisquare:
def test_gh_chisquare_12282(self, xp):
# Currently `chisquare` is implemented via power_divergence
# in case that ever changes, perform a basic test like
# test_power_divergence_gh_12282
with assert_raises(ValueError, match='For each axis slice...'):
f_obs = xp.asarray([10., 20.])
f_exp = xp.asarray([30., 60.])
stats.chisquare(f_obs=f_obs, f_exp=f_exp)
@pytest.mark.parametrize("n, dtype", [(200, 'uint8'), (1000000, 'int32')])
def test_chiquare_data_types_attributes(self, n, dtype, xp):
# Regression test for gh-10159 and gh-18368
dtype = getattr(xp, dtype)
obs = xp.asarray([n, 0], dtype=dtype)
exp = xp.asarray([n // 2, n // 2], dtype=dtype)
res = stats.chisquare(obs, exp)
stat, p = res
xp_assert_close(stat, xp.asarray(n, dtype=xp.asarray(1.).dtype), rtol=1e-13)
# check that attributes are identical to unpacked outputs - see gh-18368
xp_assert_equal(res.statistic, stat)
xp_assert_equal(res.pvalue, p)
@skip_xp_invalid_arg
class TestChisquareMA:
@pytest.mark.filterwarnings('ignore::DeprecationWarning')
def test_chisquare_masked_arrays(self):
# Test masked arrays.
obs = np.array([[8, 8, 16, 32, -1], [-1, -1, 3, 4, 5]]).T
mask = np.array([[0, 0, 0, 0, 1], [1, 1, 0, 0, 0]]).T
mobs = np.ma.masked_array(obs, mask)
expected_chisq = np.array([24.0, 0.5])
expected_g = np.array([2*(2*8*np.log(0.5) + 32*np.log(2.0)),
2*(3*np.log(0.75) + 5*np.log(1.25))])
chi2 = stats.distributions.chi2
chisq, p = stats.chisquare(mobs)
mat.assert_array_equal(chisq, expected_chisq)
mat.assert_array_almost_equal(p, chi2.sf(expected_chisq,
mobs.count(axis=0) - 1))
g, p = stats.power_divergence(mobs, lambda_='log-likelihood')
mat.assert_array_almost_equal(g, expected_g, decimal=15)
mat.assert_array_almost_equal(p, chi2.sf(expected_g,
mobs.count(axis=0) - 1))
chisq, p = stats.chisquare(mobs.T, axis=1)
mat.assert_array_equal(chisq, expected_chisq)
mat.assert_array_almost_equal(p, chi2.sf(expected_chisq,
mobs.T.count(axis=1) - 1))
g, p = stats.power_divergence(mobs.T, axis=1, lambda_="log-likelihood")
mat.assert_array_almost_equal(g, expected_g, decimal=15)
mat.assert_array_almost_equal(p, chi2.sf(expected_g,
mobs.count(axis=0) - 1))
obs1 = np.ma.array([3, 5, 6, 99, 10], mask=[0, 0, 0, 1, 0])
exp1 = np.ma.array([2, 4, 8, 10, 99], mask=[0, 0, 0, 0, 1])
chi2, p = stats.chisquare(obs1, f_exp=exp1)
# Because of the mask at index 3 of obs1 and at index 4 of exp1,
# only the first three elements are included in the calculation
# of the statistic.
mat.assert_array_equal(chi2, 1/2 + 1/4 + 4/8)
# When axis=None, the two values should have type np.float64.
chisq, p = stats.chisquare(np.ma.array([1,2,3]), axis=None)
assert_(isinstance(chisq, np.float64))
assert_(isinstance(p, np.float64))
assert_equal(chisq, 1.0)
assert_almost_equal(p, stats.distributions.chi2.sf(1.0, 2))
# Empty arrays:
# A data set with length 0 returns a masked scalar.
with np.errstate(invalid='ignore'):
with suppress_warnings() as sup:
sup.filter(RuntimeWarning, "Mean of empty slice")
chisq, p = stats.chisquare(np.ma.array([]))
assert_(isinstance(chisq, np.ma.MaskedArray))
assert_equal(chisq.shape, ())
assert_(chisq.mask)
empty3 = np.ma.array([[],[],[]])
# empty3 is a collection of 0 data sets (whose lengths would be 3, if
# there were any), so the return value is an array with length 0.
chisq, p = stats.chisquare(empty3)
assert_(isinstance(chisq, np.ma.MaskedArray))
mat.assert_array_equal(chisq, [])
# empty3.T is an array containing 3 data sets, each with length 0,
# so an array of size (3,) is returned, with all values masked.
with np.errstate(invalid='ignore'):
with suppress_warnings() as sup:
sup.filter(RuntimeWarning, "Mean of empty slice")
chisq, p = stats.chisquare(empty3.T)
assert_(isinstance(chisq, np.ma.MaskedArray))
assert_equal(chisq.shape, (3,))
assert_(np.all(chisq.mask))
def test_deprecation_warning(self):
a = np.asarray([1., 2., 3.])
ma = np.ma.masked_array(a)
message = "`power_divergence` and `chisquare` support for masked..."
with pytest.warns(DeprecationWarning, match=message):
stats.chisquare(ma)
with pytest.warns(DeprecationWarning, match=message):
stats.chisquare(a, ma)
def test_friedmanchisquare():
# see ticket:113
# verified with matlab and R
# From Demsar "Statistical Comparisons of Classifiers over Multiple Data Sets"
# 2006, Xf=9.28 (no tie handling, tie corrected Xf >=9.28)
x1 = [array([0.763, 0.599, 0.954, 0.628, 0.882, 0.936, 0.661, 0.583,
0.775, 1.0, 0.94, 0.619, 0.972, 0.957]),
array([0.768, 0.591, 0.971, 0.661, 0.888, 0.931, 0.668, 0.583,
0.838, 1.0, 0.962, 0.666, 0.981, 0.978]),
array([0.771, 0.590, 0.968, 0.654, 0.886, 0.916, 0.609, 0.563,
0.866, 1.0, 0.965, 0.614, 0.9751, 0.946]),
array([0.798, 0.569, 0.967, 0.657, 0.898, 0.931, 0.685, 0.625,
0.875, 1.0, 0.962, 0.669, 0.975, 0.970])]
# From "Bioestadistica para las ciencias de la salud" Xf=18.95 p<0.001:
x2 = [array([4,3,5,3,5,3,2,5,4,4,4,3]),
array([2,2,1,2,3,1,2,3,2,1,1,3]),
array([2,4,3,3,4,3,3,4,4,1,2,1]),
array([3,5,4,3,4,4,3,3,3,4,4,4])]
# From Jerrorl H. Zar, "Biostatistical Analysis"(example 12.6),
# Xf=10.68, 0.005 < p < 0.01:
# Probability from this example is inexact
# using Chisquare approximation of Friedman Chisquare.
x3 = [array([7.0,9.9,8.5,5.1,10.3]),
array([5.3,5.7,4.7,3.5,7.7]),
array([4.9,7.6,5.5,2.8,8.4]),
array([8.8,8.9,8.1,3.3,9.1])]
assert_array_almost_equal(stats.friedmanchisquare(x1[0],x1[1],x1[2],x1[3]),
(10.2283464566929, 0.0167215803284414))
assert_array_almost_equal(stats.friedmanchisquare(x2[0],x2[1],x2[2],x2[3]),
(18.9428571428571, 0.000280938375189499))
assert_array_almost_equal(stats.friedmanchisquare(x3[0],x3[1],x3[2],x3[3]),
(10.68, 0.0135882729582176))
assert_raises(ValueError, stats.friedmanchisquare,x3[0],x3[1])
# test for namedtuple attribute results
attributes = ('statistic', 'pvalue')
res = stats.friedmanchisquare(*x1)
check_named_results(res, attributes)
# test using mstats
assert_array_almost_equal(mstats.friedmanchisquare(x1[0], x1[1],
x1[2], x1[3]),
(10.2283464566929, 0.0167215803284414))
# the following fails
# assert_array_almost_equal(mstats.friedmanchisquare(x2[0],x2[1],x2[2],x2[3]),
# (18.9428571428571, 0.000280938375189499))
assert_array_almost_equal(mstats.friedmanchisquare(x3[0], x3[1],
x3[2], x3[3]),
(10.68, 0.0135882729582176))
assert_raises(ValueError, mstats.friedmanchisquare,x3[0],x3[1])
class TestKSTest:
"""Tests kstest and ks_1samp agree with K-S various sizes, alternatives, modes."""
def _testOne(self, x, alternative, expected_statistic, expected_prob,
mode='auto', decimal=14):
result = stats.kstest(x, 'norm', alternative=alternative, mode=mode)
expected = np.array([expected_statistic, expected_prob])
assert_array_almost_equal(np.array(result), expected, decimal=decimal)
def _test_kstest_and_ks1samp(self, x, alternative, mode='auto', decimal=14):
result = stats.kstest(x, 'norm', alternative=alternative, mode=mode)
result_1samp = stats.ks_1samp(x, stats.norm.cdf,
alternative=alternative, mode=mode)
assert_array_almost_equal(np.array(result), result_1samp, decimal=decimal)
def test_namedtuple_attributes(self):
x = np.linspace(-1, 1, 9)
# test for namedtuple attribute results
attributes = ('statistic', 'pvalue')
res = stats.kstest(x, 'norm')
check_named_results(res, attributes)
def test_agree_with_ks_1samp(self):
x = np.linspace(-1, 1, 9)
self._test_kstest_and_ks1samp(x, 'two-sided')
x = np.linspace(-15, 15, 9)
self._test_kstest_and_ks1samp(x, 'two-sided')
x = [-1.23, 0.06, -0.60, 0.17, 0.66, -0.17, -0.08, 0.27, -0.98, -0.99]
self._test_kstest_and_ks1samp(x, 'two-sided')
self._test_kstest_and_ks1samp(x, 'greater', mode='exact')
self._test_kstest_and_ks1samp(x, 'less', mode='exact')
def test_pm_inf_gh20386(self):
# Check that gh-20386 is resolved - `kstest` does not
# return NaNs when both -inf and inf are in sample.
vals = [-np.inf, 0, 1, np.inf]
res = stats.kstest(vals, stats.cauchy.cdf)
ref = stats.kstest(vals, stats.cauchy.cdf, _no_deco=True)
assert np.all(np.isfinite(res))
assert_equal(res, ref)
assert not np.isnan(res.statistic)
assert not np.isnan(res.pvalue)
# missing: no test that uses *args
class TestKSOneSample:
"""
Tests kstest and ks_samp 1-samples with K-S various sizes, alternatives, modes.
"""
def _testOne(self, x, alternative, expected_statistic, expected_prob,
mode='auto', decimal=14):
result = stats.ks_1samp(x, stats.norm.cdf, alternative=alternative, mode=mode)
expected = np.array([expected_statistic, expected_prob])
assert_array_almost_equal(np.array(result), expected, decimal=decimal)
def test_namedtuple_attributes(self):
x = np.linspace(-1, 1, 9)
# test for namedtuple attribute results
attributes = ('statistic', 'pvalue')
res = stats.ks_1samp(x, stats.norm.cdf)
check_named_results(res, attributes)
def test_agree_with_r(self):
# comparing with some values from R
x = np.linspace(-1, 1, 9)
self._testOne(x, 'two-sided', 0.15865525393145705, 0.95164069201518386)
x = np.linspace(-15, 15, 9)
self._testOne(x, 'two-sided', 0.44435602715924361, 0.038850140086788665)
x = [-1.23, 0.06, -0.60, 0.17, 0.66, -0.17, -0.08, 0.27, -0.98, -0.99]
self._testOne(x, 'two-sided', 0.293580126801961, 0.293408463684361)
self._testOne(x, 'greater', 0.293580126801961, 0.146988835042376, mode='exact')
self._testOne(x, 'less', 0.109348552425692, 0.732768892470675, mode='exact')
def test_known_examples(self):
# the following tests rely on deterministically replicated rvs
x = stats.norm.rvs(loc=0.2, size=100, random_state=987654321)
self._testOne(x, 'two-sided', 0.12464329735846891, 0.089444888711820769,
mode='asymp')
self._testOne(x, 'less', 0.12464329735846891, 0.040989164077641749)
self._testOne(x, 'greater', 0.0072115233216310994, 0.98531158590396228)
def test_ks1samp_allpaths(self):
# Check NaN input, output.
assert_(np.isnan(kolmogn(np.nan, 1, True)))
with assert_raises(ValueError, match='n is not integral: 1.5'):
kolmogn(1.5, 1, True)
assert_(np.isnan(kolmogn(-1, 1, True)))
dataset = np.asarray([
# Check x out of range
(101, 1, True, 1.0),
(101, 1.1, True, 1.0),
(101, 0, True, 0.0),
(101, -0.1, True, 0.0),
(32, 1.0 / 64, True, 0.0), # Ruben-Gambino
(32, 1.0 / 64, False, 1.0), # Ruben-Gambino
# Miller
(32, 0.5, True, 0.9999999363163307),
# Miller 2 * special.smirnov(32, 0.5)
(32, 0.5, False, 6.368366937916623e-08),
# Check some other paths
(32, 1.0 / 8, True, 0.34624229979775223),
(32, 1.0 / 4, True, 0.9699508336558085),
(1600, 0.49, False, 0.0),
# 2 * special.smirnov(1600, 1/16.0)
(1600, 1 / 16.0, False, 7.0837876229702195e-06),
# _kolmogn_DMTW
(1600, 14 / 1600, False, 0.99962357317602),
# _kolmogn_PelzGood
(1600, 1 / 32, False, 0.08603386296651416),
])
FuncData(kolmogn, dataset, (0, 1, 2), 3).check(dtypes=[int, float, bool])
@pytest.mark.parametrize("ksfunc", [stats.kstest, stats.ks_1samp])
@pytest.mark.parametrize("alternative, x6val, ref_location, ref_sign",
[('greater', 6, 6, +1),
('less', 7, 7, -1),
('two-sided', 6, 6, +1),
('two-sided', 7, 7, -1)])
def test_location_sign(self, ksfunc, alternative,
x6val, ref_location, ref_sign):
# Test that location and sign corresponding with statistic are as
# expected. (Test is designed to be easy to predict.)
x = np.arange(10) + 0.5
x[6] = x6val
cdf = stats.uniform(scale=10).cdf
res = ksfunc(x, cdf, alternative=alternative)
assert_allclose(res.statistic, 0.1, rtol=1e-15)
assert res.statistic_location == ref_location
assert res.statistic_sign == ref_sign
# missing: no test that uses *args
class TestKSTwoSamples:
"""Tests 2-samples with K-S various sizes, alternatives, modes."""
def _testOne(self, x1, x2, alternative, expected_statistic, expected_prob,
mode='auto'):
result = stats.ks_2samp(x1, x2, alternative, mode=mode)
expected = np.array([expected_statistic, expected_prob])
assert_array_almost_equal(np.array(result), expected)
def testSmall(self):
self._testOne([0], [1], 'two-sided', 1.0/1, 1.0)
self._testOne([0], [1], 'greater', 1.0/1, 0.5)
self._testOne([0], [1], 'less', 0.0/1, 1.0)
self._testOne([1], [0], 'two-sided', 1.0/1, 1.0)
self._testOne([1], [0], 'greater', 0.0/1, 1.0)
self._testOne([1], [0], 'less', 1.0/1, 0.5)
def testTwoVsThree(self):
data1 = np.array([1.0, 2.0])
data1p = data1 + 0.01
data1m = data1 - 0.01
data2 = np.array([1.0, 2.0, 3.0])
self._testOne(data1p, data2, 'two-sided', 1.0 / 3, 1.0)
self._testOne(data1p, data2, 'greater', 1.0 / 3, 0.7)
self._testOne(data1p, data2, 'less', 1.0 / 3, 0.7)
self._testOne(data1m, data2, 'two-sided', 2.0 / 3, 0.6)
self._testOne(data1m, data2, 'greater', 2.0 / 3, 0.3)
self._testOne(data1m, data2, 'less', 0, 1.0)
def testTwoVsFour(self):
data1 = np.array([1.0, 2.0])
data1p = data1 + 0.01
data1m = data1 - 0.01
data2 = np.array([1.0, 2.0, 3.0, 4.0])
self._testOne(data1p, data2, 'two-sided', 2.0 / 4, 14.0/15)
self._testOne(data1p, data2, 'greater', 2.0 / 4, 8.0/15)
self._testOne(data1p, data2, 'less', 1.0 / 4, 12.0/15)
self._testOne(data1m, data2, 'two-sided', 3.0 / 4, 6.0/15)
self._testOne(data1m, data2, 'greater', 3.0 / 4, 3.0/15)
self._testOne(data1m, data2, 'less', 0, 1.0)
def test100_100(self):
x100 = np.linspace(1, 100, 100)
x100_2_p1 = x100 + 2 + 0.1
x100_2_m1 = x100 + 2 - 0.1
self._testOne(x100, x100_2_p1, 'two-sided', 3.0 / 100, 0.9999999999962055)
self._testOne(x100, x100_2_p1, 'greater', 3.0 / 100, 0.9143290114276248)
self._testOne(x100, x100_2_p1, 'less', 0, 1.0)
self._testOne(x100, x100_2_m1, 'two-sided', 2.0 / 100, 1.0)
self._testOne(x100, x100_2_m1, 'greater', 2.0 / 100, 0.960978450786184)
self._testOne(x100, x100_2_m1, 'less', 0, 1.0)
def test100_110(self):
x100 = np.linspace(1, 100, 100)
x110 = np.linspace(1, 100, 110)
x110_20_p1 = x110 + 20 + 0.1
x110_20_m1 = x110 + 20 - 0.1
# 100, 110
self._testOne(x100, x110_20_p1, 'two-sided', 232.0 / 1100, 0.015739183865607353)
self._testOne(x100, x110_20_p1, 'greater', 232.0 / 1100, 0.007869594319053203)
self._testOne(x100, x110_20_p1, 'less', 0, 1)
self._testOne(x100, x110_20_m1, 'two-sided', 229.0 / 1100, 0.017803803861026313)
self._testOne(x100, x110_20_m1, 'greater', 229.0 / 1100, 0.008901905958245056)
self._testOne(x100, x110_20_m1, 'less', 0.0, 1.0)
def testRepeatedValues(self):
x2233 = np.array([2] * 3 + [3] * 4 + [5] * 5 + [6] * 4, dtype=int)
x3344 = x2233 + 1
x2356 = np.array([2] * 3 + [3] * 4 + [5] * 10 + [6] * 4, dtype=int)
x3467 = np.array([3] * 10 + [4] * 2 + [6] * 10 + [7] * 4, dtype=int)
self._testOne(x2233, x3344, 'two-sided', 5.0/16, 0.4262934613454952)
self._testOne(x2233, x3344, 'greater', 5.0/16, 0.21465428276573786)
self._testOne(x2233, x3344, 'less', 0.0/16, 1.0)
self._testOne(x2356, x3467, 'two-sided', 190.0/21/26, 0.0919245790168125)
self._testOne(x2356, x3467, 'greater', 190.0/21/26, 0.0459633806858544)
self._testOne(x2356, x3467, 'less', 70.0/21/26, 0.6121593130022775)
def testEqualSizes(self):
data2 = np.array([1.0, 2.0, 3.0])
self._testOne(data2, data2+1, 'two-sided', 1.0/3, 1.0)
self._testOne(data2, data2+1, 'greater', 1.0/3, 0.75)
self._testOne(data2, data2+1, 'less', 0.0/3, 1.)
self._testOne(data2, data2+0.5, 'two-sided', 1.0/3, 1.0)
self._testOne(data2, data2+0.5, 'greater', 1.0/3, 0.75)
self._testOne(data2, data2+0.5, 'less', 0.0/3, 1.)
self._testOne(data2, data2-0.5, 'two-sided', 1.0/3, 1.0)
self._testOne(data2, data2-0.5, 'greater', 0.0/3, 1.0)
self._testOne(data2, data2-0.5, 'less', 1.0/3, 0.75)
@pytest.mark.slow
def testMiddlingBoth(self):
# 500, 600
n1, n2 = 500, 600
delta = 1.0/n1/n2/2/2
x = np.linspace(1, 200, n1) - delta
y = np.linspace(2, 200, n2)
self._testOne(x, y, 'two-sided', 2000.0 / n1 / n2, 1.0,
mode='auto')
self._testOne(x, y, 'two-sided', 2000.0 / n1 / n2, 1.0,
mode='asymp')
self._testOne(x, y, 'greater', 2000.0 / n1 / n2, 0.9697596024683929,
mode='asymp')
self._testOne(x, y, 'less', 500.0 / n1 / n2, 0.9968735843165021,
mode='asymp')
with suppress_warnings() as sup:
message = "ks_2samp: Exact calculation unsuccessful."
sup.filter(RuntimeWarning, message)
self._testOne(x, y, 'greater', 2000.0 / n1 / n2, 0.9697596024683929,
mode='exact')
self._testOne(x, y, 'less', 500.0 / n1 / n2, 0.9968735843165021,
mode='exact')
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
self._testOne(x, y, 'less', 500.0 / n1 / n2, 0.9968735843165021,
mode='exact')
_check_warnings(w, RuntimeWarning, 1)
@pytest.mark.slow
def testMediumBoth(self):
# 1000, 1100
n1, n2 = 1000, 1100
delta = 1.0/n1/n2/2/2
x = np.linspace(1, 200, n1) - delta
y = np.linspace(2, 200, n2)
self._testOne(x, y, 'two-sided', 6600.0 / n1 / n2, 1.0,
mode='asymp')
self._testOne(x, y, 'two-sided', 6600.0 / n1 / n2, 1.0,
mode='auto')
self._testOne(x, y, 'greater', 6600.0 / n1 / n2, 0.9573185808092622,
mode='asymp')
self._testOne(x, y, 'less', 1000.0 / n1 / n2, 0.9982410869433984,
mode='asymp')
with suppress_warnings() as sup:
message = "ks_2samp: Exact calculation unsuccessful."
sup.filter(RuntimeWarning, message)
self._testOne(x, y, 'greater', 6600.0 / n1 / n2, 0.9573185808092622,
mode='exact')
self._testOne(x, y, 'less', 1000.0 / n1 / n2, 0.9982410869433984,
mode='exact')
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
self._testOne(x, y, 'less', 1000.0 / n1 / n2, 0.9982410869433984,
mode='exact')
_check_warnings(w, RuntimeWarning, 1)
def testLarge(self):
# 10000, 110
n1, n2 = 10000, 110
lcm = n1*11.0
delta = 1.0/n1/n2/2/2
x = np.linspace(1, 200, n1) - delta
y = np.linspace(2, 100, n2)
self._testOne(x, y, 'two-sided', 55275.0 / lcm, 4.2188474935755949e-15)
self._testOne(x, y, 'greater', 561.0 / lcm, 0.99115454582047591)
self._testOne(x, y, 'less', 55275.0 / lcm, 3.1317328311518713e-26)
def test_gh11184(self):
# 3000, 3001, exact two-sided
np.random.seed(123456)
x = np.random.normal(size=3000)
y = np.random.normal(size=3001) * 1.5
self._testOne(x, y, 'two-sided', 0.11292880151060758, 2.7755575615628914e-15,
mode='asymp')
self._testOne(x, y, 'two-sided', 0.11292880151060758, 2.7755575615628914e-15,
mode='exact')
@pytest.mark.xslow
def test_gh11184_bigger(self):
# 10000, 10001, exact two-sided
np.random.seed(123456)
x = np.random.normal(size=10000)
y = np.random.normal(size=10001) * 1.5
self._testOne(x, y, 'two-sided', 0.10597913208679133, 3.3149311398483503e-49,
mode='asymp')
self._testOne(x, y, 'two-sided', 0.10597913208679133, 2.7755575615628914e-15,
mode='exact')
self._testOne(x, y, 'greater', 0.10597913208679133, 2.7947433906389253e-41,
mode='asymp')
self._testOne(x, y, 'less', 0.09658002199780022, 2.7947433906389253e-41,
mode='asymp')
@pytest.mark.xslow
def test_gh12999(self):
np.random.seed(123456)
for x in range(1000, 12000, 1000):
vals1 = np.random.normal(size=(x))
vals2 = np.random.normal(size=(x + 10), loc=0.5)
exact = stats.ks_2samp(vals1, vals2, mode='exact').pvalue
asymp = stats.ks_2samp(vals1, vals2, mode='asymp').pvalue
# these two p-values should be in line with each other
assert_array_less(exact, 3 * asymp)
assert_array_less(asymp, 3 * exact)
@pytest.mark.slow
def testLargeBoth(self):
# 10000, 11000
n1, n2 = 10000, 11000
lcm = n1*11.0
delta = 1.0/n1/n2/2/2
x = np.linspace(1, 200, n1) - delta
y = np.linspace(2, 200, n2)
self._testOne(x, y, 'two-sided', 563.0 / lcm, 0.9990660108966576,
mode='asymp')
self._testOne(x, y, 'two-sided', 563.0 / lcm, 0.9990456491488628,
mode='exact')
self._testOne(x, y, 'two-sided', 563.0 / lcm, 0.9990660108966576,
mode='auto')
self._testOne(x, y, 'greater', 563.0 / lcm, 0.7561851877420673)
self._testOne(x, y, 'less', 10.0 / lcm, 0.9998239693191724)
with suppress_warnings() as sup:
message = "ks_2samp: Exact calculation unsuccessful."
sup.filter(RuntimeWarning, message)
self._testOne(x, y, 'greater', 563.0 / lcm, 0.7561851877420673,
mode='exact')
self._testOne(x, y, 'less', 10.0 / lcm, 0.9998239693191724,
mode='exact')
def testNamedAttributes(self):
# test for namedtuple attribute results
attributes = ('statistic', 'pvalue')
res = stats.ks_2samp([1, 2], [3])
check_named_results(res, attributes)
@pytest.mark.slow
def test_some_code_paths(self):
# Check that some code paths are executed
from scipy.stats._stats_py import (
_count_paths_outside_method,
_compute_outer_prob_inside_method
)
_compute_outer_prob_inside_method(1, 1, 1, 1)
_count_paths_outside_method(1000, 1, 1, 1001)
with np.errstate(invalid='raise'):
assert_raises(FloatingPointError, _count_paths_outside_method,
1100, 1099, 1, 1)
assert_raises(FloatingPointError, _count_paths_outside_method,
2000, 1000, 1, 1)
@pytest.mark.parametrize('case', (([], [1]), ([1], []), ([], [])))
def test_argument_checking(self, case):
# Check that an empty array warns
with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
res = stats.ks_2samp(*case)
assert_equal(res.statistic, np.nan)
assert_equal(res.pvalue, np.nan)
@pytest.mark.xslow
def test_gh12218(self):
"""Ensure gh-12218 is fixed."""
# gh-1228 triggered a TypeError calculating sqrt(n1*n2*(n1+n2)).
# n1, n2 both large integers, the product exceeded 2^64
np.random.seed(12345678)
n1 = 2097152 # 2*^21
rvs1 = stats.uniform.rvs(size=n1, loc=0., scale=1)
rvs2 = rvs1 + 1 # Exact value of rvs2 doesn't matter.
stats.ks_2samp(rvs1, rvs2, alternative='greater', mode='asymp')
stats.ks_2samp(rvs1, rvs2, alternative='less', mode='asymp')
stats.ks_2samp(rvs1, rvs2, alternative='two-sided', mode='asymp')
def test_warnings_gh_14019(self):
# Check that RuntimeWarning is raised when method='auto' and exact
# p-value calculation fails. See gh-14019.
rng = np.random.RandomState(seed=23493549)
# random samples of the same size as in the issue
data1 = rng.random(size=881) + 0.5
data2 = rng.random(size=369)
message = "ks_2samp: Exact calculation unsuccessful"
with pytest.warns(RuntimeWarning, match=message):
res = stats.ks_2samp(data1, data2, alternative='less')
assert_allclose(res.pvalue, 0, atol=1e-14)
@pytest.mark.parametrize("ksfunc", [stats.kstest, stats.ks_2samp])
@pytest.mark.parametrize("alternative, x6val, ref_location, ref_sign",
[('greater', 5.9, 5.9, +1),
('less', 6.1, 6.0, -1),
('two-sided', 5.9, 5.9, +1),
('two-sided', 6.1, 6.0, -1)])
def test_location_sign(self, ksfunc, alternative,
x6val, ref_location, ref_sign):
# Test that location and sign corresponding with statistic are as
# expected. (Test is designed to be easy to predict.)
x = np.arange(10, dtype=np.float64)
y = x.copy()
x[6] = x6val
res = stats.ks_2samp(x, y, alternative=alternative)
assert res.statistic == 0.1
assert res.statistic_location == ref_location
assert res.statistic_sign == ref_sign
def test_ttest_rel():
# regression test
tr,pr = 0.81248591389165692, 0.41846234511362157
tpr = ([tr,-tr],[pr,pr])
rvs1 = np.linspace(1,100,100)
rvs2 = np.linspace(1.01,99.989,100)
rvs1_2D = np.array([np.linspace(1,100,100), np.linspace(1.01,99.989,100)])
rvs2_2D = np.array([np.linspace(1.01,99.989,100), np.linspace(1,100,100)])
t,p = stats.ttest_rel(rvs1, rvs2, axis=0)
assert_array_almost_equal([t,p],(tr,pr))
t,p = stats.ttest_rel(rvs1_2D.T, rvs2_2D.T, axis=0)
assert_array_almost_equal([t,p],tpr)
t,p = stats.ttest_rel(rvs1_2D, rvs2_2D, axis=1)
assert_array_almost_equal([t,p],tpr)
# test scalars
with suppress_warnings() as sup, \
np.errstate(invalid="ignore", divide="ignore"):
sup.filter(RuntimeWarning, "Degrees of freedom <= 0 for slice")
t, p = stats.ttest_rel(4., 3.)
assert_(np.isnan(t))
assert_(np.isnan(p))
# test for namedtuple attribute results
attributes = ('statistic', 'pvalue')
res = stats.ttest_rel(rvs1, rvs2, axis=0)
check_named_results(res, attributes)
# test on 3 dimensions
rvs1_3D = np.dstack([rvs1_2D,rvs1_2D,rvs1_2D])
rvs2_3D = np.dstack([rvs2_2D,rvs2_2D,rvs2_2D])
t,p = stats.ttest_rel(rvs1_3D, rvs2_3D, axis=1)
assert_array_almost_equal(np.abs(t), tr)
assert_array_almost_equal(np.abs(p), pr)
assert_equal(t.shape, (2, 3))
t, p = stats.ttest_rel(np.moveaxis(rvs1_3D, 2, 0),
np.moveaxis(rvs2_3D, 2, 0),
axis=2)
assert_array_almost_equal(np.abs(t), tr)
assert_array_almost_equal(np.abs(p), pr)
assert_equal(t.shape, (3, 2))
# test alternative parameter
assert_raises(ValueError, stats.ttest_rel, rvs1, rvs2, alternative="error")
t, p = stats.ttest_rel(rvs1, rvs2, axis=0, alternative="less")
assert_allclose(p, 1 - pr/2)
assert_allclose(t, tr)
t, p = stats.ttest_rel(rvs1, rvs2, axis=0, alternative="greater")
assert_allclose(p, pr/2)
assert_allclose(t, tr)
# check nan policy
rng = np.random.RandomState(12345678)
x = stats.norm.rvs(loc=5, scale=10, size=501, random_state=rng)
x[500] = np.nan
y = (stats.norm.rvs(loc=5, scale=10, size=501, random_state=rng) +
stats.norm.rvs(scale=0.2, size=501, random_state=rng))
y[500] = np.nan
with np.errstate(invalid="ignore"):
assert_array_equal(stats.ttest_rel(x, x), (np.nan, np.nan))
assert_array_almost_equal(stats.ttest_rel(x, y, nan_policy='omit'),
(0.25299925303978066, 0.8003729814201519))
assert_raises(ValueError, stats.ttest_rel, x, y, nan_policy='raise')
assert_raises(ValueError, stats.ttest_rel, x, y, nan_policy='foobar')
# test zero division problem
with pytest.warns(RuntimeWarning, match="Precision loss occurred"):
t, p = stats.ttest_rel([0, 0, 0], [1, 1, 1])
assert_equal((np.abs(t), p), (np.inf, 0))
with np.errstate(invalid="ignore"):
assert_equal(stats.ttest_rel([0, 0, 0], [0, 0, 0]), (np.nan, np.nan))
# check that nan in input array result in nan output
anan = np.array([[1, np.nan], [-1, 1]])
assert_equal(stats.ttest_rel(anan, np.zeros((2, 2))),
([0, np.nan], [1, np.nan]))
# test incorrect input shape raise an error
x = np.arange(24)
assert_raises(ValueError, stats.ttest_rel, x.reshape((8, 3)),
x.reshape((2, 3, 4)))
# Convert from two-sided p-values to one sided using T result data.
def convert(t, p, alt):
if (t < 0 and alt == "less") or (t > 0 and alt == "greater"):
return p / 2
return 1 - (p / 2)
converter = np.vectorize(convert)
rvs1_2D[:, 20:30] = np.nan
rvs2_2D[:, 15:25] = np.nan
with pytest.warns(SmallSampleWarning, match=too_small_nd_omit):
tr, pr = stats.ttest_rel(rvs1_2D, rvs2_2D, 0, nan_policy='omit')
with pytest.warns(SmallSampleWarning, match=too_small_nd_omit):
t, p = stats.ttest_rel(rvs1_2D, rvs2_2D, 0,
nan_policy='omit', alternative='less')
assert_allclose(t, tr, rtol=1e-14)
with np.errstate(invalid='ignore'):
assert_allclose(p, converter(tr, pr, 'less'), rtol=1e-14)
with pytest.warns(SmallSampleWarning, match=too_small_nd_omit):
t, p = stats.ttest_rel(rvs1_2D, rvs2_2D, 0,
nan_policy='omit', alternative='greater')
assert_allclose(t, tr, rtol=1e-14)
with np.errstate(invalid='ignore'):
assert_allclose(p, converter(tr, pr, 'greater'), rtol=1e-14)
def test_ttest_rel_nan_2nd_arg():
# regression test for gh-6134: nans in the second arg were not handled
x = [np.nan, 2.0, 3.0, 4.0]
y = [1.0, 2.0, 1.0, 2.0]
r1 = stats.ttest_rel(x, y, nan_policy='omit')
r2 = stats.ttest_rel(y, x, nan_policy='omit')
assert_allclose(r2.statistic, -r1.statistic, atol=1e-15)
assert_allclose(r2.pvalue, r1.pvalue, atol=1e-15)
# NB: arguments are paired when NaNs are dropped
r3 = stats.ttest_rel(y[1:], x[1:])
assert_allclose(r2, r3, atol=1e-15)
# .. and this is consistent with R. R code:
# x = c(NA, 2.0, 3.0, 4.0)
# y = c(1.0, 2.0, 1.0, 2.0)
# t.test(x, y, paired=TRUE)
assert_allclose(r2, (-2, 0.1835), atol=1e-4)
def test_ttest_rel_empty_1d_returns_nan():
# Two empty inputs should return a TtestResult containing nan
# for both values.
with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
result = stats.ttest_rel([], [])
assert isinstance(result, stats._stats_py.TtestResult)
assert_equal(result, (np.nan, np.nan))
@pytest.mark.parametrize('b, expected_shape',
[(np.empty((1, 5, 0)), (3, 5)),
(np.empty((1, 0, 0)), (3, 0))])
def test_ttest_rel_axis_size_zero(b, expected_shape):
# In this test, the length of the axis dimension is zero.
# The results should be arrays containing nan with shape
# given by the broadcast nonaxis dimensions.
a = np.empty((3, 1, 0))
with np.testing.suppress_warnings() as sup:
# first case should warn, second shouldn't?
sup.filter(SmallSampleWarning, too_small_nd_not_omit)
result = stats.ttest_rel(a, b, axis=-1)
assert isinstance(result, stats._stats_py.TtestResult)
expected_value = np.full(expected_shape, fill_value=np.nan)
assert_equal(result.statistic, expected_value)
assert_equal(result.pvalue, expected_value)
def test_ttest_rel_nonaxis_size_zero():
# In this test, the length of the axis dimension is nonzero,
# but one of the nonaxis dimensions has length 0. Check that
# we still get the correctly broadcast shape, which is (5, 0)
# in this case.
a = np.empty((1, 8, 0))
b = np.empty((5, 8, 1))
result = stats.ttest_rel(a, b, axis=1)
assert isinstance(result, stats._stats_py.TtestResult)
assert_equal(result.statistic.shape, (5, 0))
assert_equal(result.pvalue.shape, (5, 0))
@pytest.mark.parametrize("alternative", ['two-sided', 'less', 'greater'])
def test_ttest_rel_ci_1d(alternative):
# test confidence interval method against reference values
rng = np.random.default_rng(3749065329432213059)
n = 10
x = rng.normal(size=n, loc=1.5, scale=2)
y = rng.normal(size=n, loc=2, scale=2)
# Reference values generated with R t.test:
# options(digits=16)
# x = c(1.22825792, 1.63950485, 4.39025641, 0.68609437, 2.03813481,
# -1.20040109, 1.81997937, 1.86854636, 2.94694282, 3.94291373)
# y = c(3.49961496, 1.53192536, 5.53620083, 2.91687718, 0.04858043,
# 3.78505943, 3.3077496 , 2.30468892, 3.42168074, 0.56797592)
# t.test(x, y, paired=TRUE, conf.level=0.85, alternative='l')
ref = {'two-sided': [-1.912194489914035, 0.400169725914035],
'greater': [-1.563944820311475, np.inf],
'less': [-np.inf, 0.05192005631147523]}
res = stats.ttest_rel(x, y, alternative=alternative)
ci = res.confidence_interval(confidence_level=0.85)
assert_allclose(ci, ref[alternative])
assert_equal(res.df, n-1)
@pytest.mark.parametrize("test_fun, args",
[(stats.ttest_1samp, (np.arange(10), 0)),
(stats.ttest_rel, (np.arange(10), np.arange(10)))])
def test_ttest_ci_iv(test_fun, args):
# test `confidence_interval` method input validation
res = test_fun(*args)
message = '`confidence_level` must be a number between 0 and 1.'
with pytest.raises(ValueError, match=message):
res.confidence_interval(confidence_level=10)
def _desc_stats(x1, x2, axis=0):
def _stats(x, axis=0):
x = np.asarray(x)
mu = np.mean(x, axis=axis)
std = np.std(x, axis=axis, ddof=1)
nobs = x.shape[axis]
return mu, std, nobs
return _stats(x1, axis) + _stats(x2, axis)
def test_ttest_ind():
# regression test
tr = 1.0912746897927283
pr = 0.27647818616351882
tpr = ([tr,-tr],[pr,pr])
rvs2 = np.linspace(1,100,100)
rvs1 = np.linspace(5,105,100)
rvs1_2D = np.array([rvs1, rvs2])
rvs2_2D = np.array([rvs2, rvs1])
t,p = stats.ttest_ind(rvs1, rvs2, axis=0)
assert_array_almost_equal([t,p],(tr,pr))
# test from_stats API
assert_array_almost_equal(stats.ttest_ind_from_stats(*_desc_stats(rvs1,
rvs2)),
[t, p])
t,p = stats.ttest_ind(rvs1_2D.T, rvs2_2D.T, axis=0)
assert_array_almost_equal([t,p],tpr)
args = _desc_stats(rvs1_2D.T, rvs2_2D.T)
assert_array_almost_equal(stats.ttest_ind_from_stats(*args),
[t, p])
t,p = stats.ttest_ind(rvs1_2D, rvs2_2D, axis=1)
assert_array_almost_equal([t,p],tpr)
args = _desc_stats(rvs1_2D, rvs2_2D, axis=1)
assert_array_almost_equal(stats.ttest_ind_from_stats(*args),
[t, p])
# test scalars
with suppress_warnings() as sup, np.errstate(invalid="ignore"):
sup.filter(RuntimeWarning, "Degrees of freedom <= 0 for slice")
t, p = stats.ttest_ind(4., 3.)
assert_(np.isnan(t))
assert_(np.isnan(p))
# test on 3 dimensions
rvs1_3D = np.dstack([rvs1_2D,rvs1_2D,rvs1_2D])
rvs2_3D = np.dstack([rvs2_2D,rvs2_2D,rvs2_2D])
t,p = stats.ttest_ind(rvs1_3D, rvs2_3D, axis=1)
assert_almost_equal(np.abs(t), np.abs(tr))
assert_array_almost_equal(np.abs(p), pr)
assert_equal(t.shape, (2, 3))
t, p = stats.ttest_ind(np.moveaxis(rvs1_3D, 2, 0),
np.moveaxis(rvs2_3D, 2, 0),
axis=2)
assert_array_almost_equal(np.abs(t), np.abs(tr))
assert_array_almost_equal(np.abs(p), pr)
assert_equal(t.shape, (3, 2))
# test alternative parameter
assert_raises(ValueError, stats.ttest_ind, rvs1, rvs2, alternative="error")
assert_raises(ValueError, stats.ttest_ind_from_stats,
*_desc_stats(rvs1_2D.T, rvs2_2D.T), alternative="error")
t, p = stats.ttest_ind(rvs1, rvs2, alternative="less")
assert_allclose(p, 1 - (pr/2))
assert_allclose(t, tr)
t, p = stats.ttest_ind(rvs1, rvs2, alternative="greater")
assert_allclose(p, pr/2)
assert_allclose(t, tr)
# Below makes sure ttest_ind_from_stats p-val functions identically to
# ttest_ind
t, p = stats.ttest_ind(rvs1_2D.T, rvs2_2D.T, axis=0, alternative="less")
args = _desc_stats(rvs1_2D.T, rvs2_2D.T)
assert_allclose(
stats.ttest_ind_from_stats(*args, alternative="less"), [t, p])
t, p = stats.ttest_ind(rvs1_2D.T, rvs2_2D.T, axis=0, alternative="greater")
args = _desc_stats(rvs1_2D.T, rvs2_2D.T)
assert_allclose(
stats.ttest_ind_from_stats(*args, alternative="greater"), [t, p])
# check nan policy
rng = np.random.RandomState(12345678)
x = stats.norm.rvs(loc=5, scale=10, size=501, random_state=rng)
x[500] = np.nan
y = stats.norm.rvs(loc=5, scale=10, size=500, random_state=rng)
with np.errstate(invalid="ignore"):
assert_array_equal(stats.ttest_ind(x, y), (np.nan, np.nan))
assert_array_almost_equal(stats.ttest_ind(x, y, nan_policy='omit'),
(0.24779670949091914, 0.80434267337517906))
assert_raises(ValueError, stats.ttest_ind, x, y, nan_policy='raise')
assert_raises(ValueError, stats.ttest_ind, x, y, nan_policy='foobar')
# test zero division problem
with pytest.warns(RuntimeWarning, match="Precision loss occurred"):
t, p = stats.ttest_ind([0, 0, 0], [1, 1, 1])
assert_equal((np.abs(t), p), (np.inf, 0))
with np.errstate(invalid="ignore"):
assert_equal(stats.ttest_ind([0, 0, 0], [0, 0, 0]), (np.nan, np.nan))
# check that nan in input array result in nan output
anan = np.array([[1, np.nan], [-1, 1]])
assert_equal(stats.ttest_ind(anan, np.zeros((2, 2))),
([0, np.nan], [1, np.nan]))
rvs1_3D[:, :, 10:15] = np.nan
rvs2_3D[:, :, 6:12] = np.nan
# Convert from two-sided p-values to one sided using T result data.
def convert(t, p, alt):
if (t < 0 and alt == "less") or (t > 0 and alt == "greater"):
return p / 2
return 1 - (p / 2)
converter = np.vectorize(convert)
tr, pr = stats.ttest_ind(rvs1_3D, rvs2_3D, 0, nan_policy='omit')
t, p = stats.ttest_ind(rvs1_3D, rvs2_3D, 0, nan_policy='omit',
alternative='less')
assert_allclose(t, tr, rtol=1e-14)
assert_allclose(p, converter(tr, pr, 'less'), rtol=1e-14)
t, p = stats.ttest_ind(rvs1_3D, rvs2_3D, 0, nan_policy='omit',
alternative='greater')
assert_allclose(t, tr, rtol=1e-14)
assert_allclose(p, converter(tr, pr, 'greater'), rtol=1e-14)
class Test_ttest_ind_permutations:
N = 20
# data for most tests
np.random.seed(0)
a = np.vstack((np.arange(3*N//4), np.random.random(3*N//4)))
b = np.vstack((np.arange(N//4) + 100, np.random.random(N//4)))
# data for equal variance tests
a2 = np.arange(10)
b2 = np.arange(10) + 100
# data for exact test
a3 = [1, 2]
b3 = [3, 4]
# data for bigger test
np.random.seed(0)
rvs1 = stats.norm.rvs(loc=5, scale=10, # type: ignore
size=500).reshape(100, 5).T
rvs2 = stats.norm.rvs(loc=8, scale=20, size=100) # type: ignore
p_d = [1/1001, (676+1)/1001] # desired pvalues
p_d_gen = [1/1001, (672 + 1)/1001] # desired pvalues for Generator seed
p_d_big = [(993+1)/1001, (685+1)/1001, (840+1)/1001,
(955+1)/1001, (255+1)/1001]
params = [
(a, b, {"axis": 1}, p_d), # basic test
(a.T, b.T, {'axis': 0}, p_d), # along axis 0
(a[0, :], b[0, :], {'axis': None}, p_d[0]), # 1d data
(a[0, :].tolist(), b[0, :].tolist(), {'axis': None}, p_d[0]),
# different seeds
(a, b, {'random_state': 0, "axis": 1}, p_d),
(a, b, {'random_state': np.random.RandomState(0), "axis": 1}, p_d),
(a2, b2, {'equal_var': True}, 1/1001), # equal variances
(rvs1, rvs2, {'axis': -1, 'random_state': 0}, p_d_big), # bigger test
(a3, b3, {}, 1/3), # exact test
(a, b, {'random_state': np.random.default_rng(0), "axis": 1}, p_d_gen),
]
@pytest.mark.parametrize("a,b,update,p_d", params)
def test_ttest_ind_permutations(self, a, b, update, p_d):
options_a = {'axis': None, 'equal_var': False}
options_p = {'axis': None, 'equal_var': False,
'permutations': 1000, 'random_state': 0}
options_a.update(update)
options_p.update(update)
stat_a, _ = stats.ttest_ind(a, b, **options_a)
stat_p, pvalue = stats.ttest_ind(a, b, **options_p)
assert_array_almost_equal(stat_a, stat_p, 5)
assert_array_almost_equal(pvalue, p_d)
def test_ttest_ind_exact_alternative(self):
np.random.seed(0)
N = 3
a = np.random.rand(2, N, 2)
b = np.random.rand(2, N, 2)
options_p = {'axis': 1, 'permutations': 1000}
options_p.update(alternative="greater")
res_g_ab = stats.ttest_ind(a, b, **options_p)
res_g_ba = stats.ttest_ind(b, a, **options_p)
options_p.update(alternative="less")
res_l_ab = stats.ttest_ind(a, b, **options_p)
res_l_ba = stats.ttest_ind(b, a, **options_p)
options_p.update(alternative="two-sided")
res_2_ab = stats.ttest_ind(a, b, **options_p)
res_2_ba = stats.ttest_ind(b, a, **options_p)
# Alternative doesn't affect the statistic
assert_equal(res_g_ab.statistic, res_l_ab.statistic)
assert_equal(res_g_ab.statistic, res_2_ab.statistic)
# Reversing order of inputs negates statistic
assert_equal(res_g_ab.statistic, -res_g_ba.statistic)
assert_equal(res_l_ab.statistic, -res_l_ba.statistic)
assert_equal(res_2_ab.statistic, -res_2_ba.statistic)
# Reversing order of inputs does not affect p-value of 2-sided test
assert_equal(res_2_ab.pvalue, res_2_ba.pvalue)
# In exact test, distribution is perfectly symmetric, so these
# identities are exactly satisfied.
assert_equal(res_g_ab.pvalue, res_l_ba.pvalue)
assert_equal(res_l_ab.pvalue, res_g_ba.pvalue)
mask = res_g_ab.pvalue <= 0.5
assert_equal(res_g_ab.pvalue[mask] + res_l_ba.pvalue[mask],
res_2_ab.pvalue[mask])
assert_equal(res_l_ab.pvalue[~mask] + res_g_ba.pvalue[~mask],
res_2_ab.pvalue[~mask])
def test_ttest_ind_exact_selection(self):
# test the various ways of activating the exact test
np.random.seed(0)
N = 3
a = np.random.rand(N)
b = np.random.rand(N)
res0 = stats.ttest_ind(a, b)
res1 = stats.ttest_ind(a, b, permutations=1000)
res2 = stats.ttest_ind(a, b, permutations=0)
res3 = stats.ttest_ind(a, b, permutations=np.inf)
assert res1.pvalue != res0.pvalue
assert res2.pvalue == res0.pvalue
assert res3.pvalue == res1.pvalue
def test_ttest_ind_exact_distribution(self):
# the exact distribution of the test statistic should have
# binom(na + nb, na) elements, all unique. This was not always true
# in gh-4824; fixed by gh-13661.
np.random.seed(0)
a = np.random.rand(3)
b = np.random.rand(4)
data = np.concatenate((a, b))
na, nb = len(a), len(b)
permutations = 100000
t_stat, _, _ = _permutation_distribution_t(data, permutations, na,
True)
n_unique = len(set(t_stat))
assert n_unique == binom(na + nb, na)
assert len(t_stat) == n_unique
def test_ttest_ind_randperm_alternative(self):
np.random.seed(0)
N = 50
a = np.random.rand(2, 3, N)
b = np.random.rand(3, N)
options_p = {'axis': -1, 'permutations': 1000, "random_state": 0}
options_p.update(alternative="greater")
res_g_ab = stats.ttest_ind(a, b, **options_p)
res_g_ba = stats.ttest_ind(b, a, **options_p)
options_p.update(alternative="less")
res_l_ab = stats.ttest_ind(a, b, **options_p)
res_l_ba = stats.ttest_ind(b, a, **options_p)
# Alternative doesn't affect the statistic
assert_equal(res_g_ab.statistic, res_l_ab.statistic)
# Reversing order of inputs negates statistic
assert_equal(res_g_ab.statistic, -res_g_ba.statistic)
assert_equal(res_l_ab.statistic, -res_l_ba.statistic)
# For random permutations, the chance of ties between the observed
# test statistic and the population is small, so:
assert_equal(res_g_ab.pvalue + res_l_ab.pvalue,
1 + 1/(options_p['permutations'] + 1))
assert_equal(res_g_ba.pvalue + res_l_ba.pvalue,
1 + 1/(options_p['permutations'] + 1))
@pytest.mark.slow()
def test_ttest_ind_randperm_alternative2(self):
np.random.seed(0)
N = 50
a = np.random.rand(N, 4)
b = np.random.rand(N, 4)
options_p = {'permutations': 20000, "random_state": 0}
options_p.update(alternative="greater")
res_g_ab = stats.ttest_ind(a, b, **options_p)
options_p.update(alternative="less")
res_l_ab = stats.ttest_ind(a, b, **options_p)
options_p.update(alternative="two-sided")
res_2_ab = stats.ttest_ind(a, b, **options_p)
# For random permutations, the chance of ties between the observed
# test statistic and the population is small, so:
assert_equal(res_g_ab.pvalue + res_l_ab.pvalue,
1 + 1/(options_p['permutations'] + 1))
# For for large sample sizes, the distribution should be approximately
# symmetric, so these identities should be approximately satisfied
mask = res_g_ab.pvalue <= 0.5
assert_allclose(2 * res_g_ab.pvalue[mask],
res_2_ab.pvalue[mask], atol=2e-2)
assert_allclose(2 * (1-res_g_ab.pvalue[~mask]),
res_2_ab.pvalue[~mask], atol=2e-2)
assert_allclose(2 * res_l_ab.pvalue[~mask],
res_2_ab.pvalue[~mask], atol=2e-2)
assert_allclose(2 * (1-res_l_ab.pvalue[mask]),
res_2_ab.pvalue[mask], atol=2e-2)
def test_ttest_ind_permutation_nanpolicy(self):
np.random.seed(0)
N = 50
a = np.random.rand(N, 5)
b = np.random.rand(N, 5)
a[5, 1] = np.nan
b[8, 2] = np.nan
a[9, 3] = np.nan
b[9, 3] = np.nan
options_p = {'permutations': 1000, "random_state": 0}
# Raise
options_p.update(nan_policy="raise")
with assert_raises(ValueError, match="The input contains nan values"):
res = stats.ttest_ind(a, b, **options_p)
# Propagate
with suppress_warnings() as sup:
sup.record(RuntimeWarning, "invalid value*")
options_p.update(nan_policy="propagate")
res = stats.ttest_ind(a, b, **options_p)
mask = np.isnan(a).any(axis=0) | np.isnan(b).any(axis=0)
res2 = stats.ttest_ind(a[:, ~mask], b[:, ~mask], **options_p)
assert_equal(res.pvalue[mask], np.nan)
assert_equal(res.statistic[mask], np.nan)
assert_allclose(res.pvalue[~mask], res2.pvalue)
assert_allclose(res.statistic[~mask], res2.statistic)
# Propagate 1d
res = stats.ttest_ind(a.ravel(), b.ravel(), **options_p)
assert np.isnan(res.pvalue) # assert makes sure it's a scalar
assert np.isnan(res.statistic)
def test_ttest_ind_permutation_check_inputs(self):
with assert_raises(ValueError, match="Permutations must be"):
stats.ttest_ind(self.a2, self.b2, permutations=-3)
with assert_raises(ValueError, match="Permutations must be"):
stats.ttest_ind(self.a2, self.b2, permutations=1.5)
with assert_raises(ValueError, match="'hello' cannot be used"):
stats.ttest_ind(self.a, self.b, permutations=1,
random_state='hello', axis=1)
def test_ttest_ind_permutation_check_p_values(self):
# p-values should never be exactly zero
N = 10
a = np.random.rand(N, 20)
b = np.random.rand(N, 20)
p_values = stats.ttest_ind(a, b, permutations=1).pvalue
print(0.0 not in p_values)
assert 0.0 not in p_values
class Test_ttest_ind_common:
# for tests that are performed on variations of the t-test such as
# permutations and trimming
@pytest.mark.xslow()
@pytest.mark.parametrize("kwds", [{'permutations': 200, 'random_state': 0},
{'trim': .2}, {}],
ids=["permutations", "trim", "basic"])
@pytest.mark.parametrize('equal_var', [True, False],
ids=['equal_var', 'unequal_var'])
def test_ttest_many_dims(self, kwds, equal_var):
# Test that test works on many-dimensional arrays
np.random.seed(0)
a = np.random.rand(5, 4, 4, 7, 1, 6)
b = np.random.rand(4, 1, 8, 2, 6)
res = stats.ttest_ind(a, b, axis=-3, **kwds)
# compare fully-vectorized t-test against t-test on smaller slice
i, j, k = 2, 3, 1
a2 = a[i, :, j, :, 0, :]
b2 = b[:, 0, :, k, :]
res2 = stats.ttest_ind(a2, b2, axis=-2, **kwds)
assert_equal(res.statistic[i, :, j, k, :],
res2.statistic)
assert_equal(res.pvalue[i, :, j, k, :],
res2.pvalue)
# compare against t-test on one axis-slice at a time
# manually broadcast with tile; move axis to end to simplify
x = np.moveaxis(np.tile(a, (1, 1, 1, 1, 2, 1)), -3, -1)
y = np.moveaxis(np.tile(b, (5, 1, 4, 1, 1, 1)), -3, -1)
shape = x.shape[:-1]
statistics = np.zeros(shape)
pvalues = np.zeros(shape)
for indices in product(*(range(i) for i in shape)):
xi = x[indices] # use tuple to index single axis slice
yi = y[indices]
res3 = stats.ttest_ind(xi, yi, axis=-1, **kwds)
statistics[indices] = res3.statistic
pvalues[indices] = res3.pvalue
assert_allclose(statistics, res.statistic)
assert_allclose(pvalues, res.pvalue)
@pytest.mark.parametrize("kwds", [{'permutations': 200, 'random_state': 0},
{'trim': .2}, {}],
ids=["trim", "permutations", "basic"])
@pytest.mark.parametrize("axis", [-1, 0])
def test_nans_on_axis(self, kwds, axis):
# confirm that with `nan_policy='propagate'`, NaN results are returned
# on the correct location
a = np.random.randint(10, size=(5, 3, 10)).astype('float')
b = np.random.randint(10, size=(5, 3, 10)).astype('float')
# set some indices in `a` and `b` to be `np.nan`.
a[0][2][3] = np.nan
b[2][0][6] = np.nan
# arbitrarily use `np.sum` as a baseline for which indices should be
# NaNs
expected = np.isnan(np.sum(a + b, axis=axis))
# multidimensional inputs to `t.sf(np.abs(t), df)` with NaNs on some
# indices throws an warning. See issue gh-13844
with suppress_warnings() as sup, np.errstate(invalid="ignore"):
sup.filter(RuntimeWarning,
"invalid value encountered in less_equal")
sup.filter(RuntimeWarning, "Precision loss occurred")
res = stats.ttest_ind(a, b, axis=axis, **kwds)
p_nans = np.isnan(res.pvalue)
assert_array_equal(p_nans, expected)
statistic_nans = np.isnan(res.statistic)
assert_array_equal(statistic_nans, expected)
class Test_ttest_trim:
params = [
[[1, 2, 3], [1.1, 2.9, 4.2], 0.53619490753126731, -0.6864951273557258,
.2],
[[56, 128.6, 12, 123.8, 64.34, 78, 763.3], [1.1, 2.9, 4.2],
0.00998909252078421, 4.591598691181999, .2],
[[56, 128.6, 12, 123.8, 64.34, 78, 763.3], [1.1, 2.9, 4.2],
0.10512380092302633, 2.832256715395378, .32],
[[2.7, 2.7, 1.1, 3.0, 1.9, 3.0, 3.8, 3.8, 0.3, 1.9, 1.9],
[6.5, 5.4, 8.1, 3.5, 0.5, 3.8, 6.8, 4.9, 9.5, 6.2, 4.1],
0.002878909511344, -4.2461168970325, .2],
[[-0.84504783, 0.13366078, 3.53601757, -0.62908581, 0.54119466,
-1.16511574, -0.08836614, 1.18495416, 2.48028757, -1.58925028,
-1.6706357, 0.3090472, -2.12258305, 0.3697304, -1.0415207,
-0.57783497, -0.90997008, 1.09850192, 0.41270579, -1.4927376],
[1.2725522, 1.1657899, 2.7509041, 1.2389013, -0.9490494, -1.0752459,
1.1038576, 2.9912821, 3.5349111, 0.4171922, 1.0168959, -0.7625041,
-0.4300008, 3.0431921, 1.6035947, 0.5285634, -0.7649405, 1.5575896,
1.3670797, 1.1726023], 0.005293305834235, -3.0983317739483, .2]]
@pytest.mark.parametrize("a,b,pr,tr,trim", params)
def test_ttest_compare_r(self, a, b, pr, tr, trim):
'''
Using PairedData's yuen.t.test method. Something to note is that there
are at least 3 R packages that come with a trimmed t-test method, and
comparisons were made between them. It was found that PairedData's
method's results match this method, SAS, and one of the other R
methods. A notable discrepancy was the DescTools implementation of the
function, which only sometimes agreed with SAS, WRS2, PairedData and
this implementation. For this reason, most comparisons in R are made
against PairedData's method.
Rather than providing the input and output for all evaluations, here is
a representative example:
> library(PairedData)
> a <- c(1, 2, 3)
> b <- c(1.1, 2.9, 4.2)
> options(digits=16)
> yuen.t.test(a, b, tr=.2)
Two-sample Yuen test, trim=0.2
data: x and y
t = -0.68649512735573, df = 3.4104431643464, p-value = 0.5361949075313
alternative hypothesis: true difference in trimmed means is not equal
to 0
95 percent confidence interval:
-3.912777195645217 2.446110528978550
sample estimates:
trimmed mean of x trimmed mean of y
2.000000000000000 2.73333333333333
'''
statistic, pvalue = stats.ttest_ind(a, b, trim=trim, equal_var=False)
assert_allclose(statistic, tr, atol=1e-15)
assert_allclose(pvalue, pr, atol=1e-15)
def test_compare_SAS(self):
# Source of the data used in this test:
# https://support.sas.com/resources/papers/proceedings14/1660-2014.pdf
a = [12, 14, 18, 25, 32, 44, 12, 14, 18, 25, 32, 44]
b = [17, 22, 14, 12, 30, 29, 19, 17, 22, 14, 12, 30, 29, 19]
# In this paper, a trimming percentage of 5% is used. However,
# in their implementation, the number of values trimmed is rounded to
# the nearest whole number. However, consistent with
# `scipy.stats.trimmed_mean`, this test truncates to the lower
# whole number. In this example, the paper notes that 1 value is
# trimmed off of each side. 9% replicates this amount of trimming.
statistic, pvalue = stats.ttest_ind(a, b, trim=.09, equal_var=False)
assert_allclose(pvalue, 0.514522, atol=1e-6)
assert_allclose(statistic, 0.669169, atol=1e-6)
def test_equal_var(self):
'''
The PairedData library only supports unequal variances. To compare
samples with equal variances, the multicon library is used.
> library(multicon)
> a <- c(2.7, 2.7, 1.1, 3.0, 1.9, 3.0, 3.8, 3.8, 0.3, 1.9, 1.9)
> b <- c(6.5, 5.4, 8.1, 3.5, 0.5, 3.8, 6.8, 4.9, 9.5, 6.2, 4.1)
> dv = c(a,b)
> iv = c(rep('a', length(a)), rep('b', length(b)))
> yuenContrast(dv~ iv, EQVAR = TRUE)
$Ms
N M wgt
a 11 2.442857142857143 1
b 11 5.385714285714286 -1
$test
stat df crit p
results -4.246116897032513 12 2.178812829667228 0.00113508833897713
'''
a = [2.7, 2.7, 1.1, 3.0, 1.9, 3.0, 3.8, 3.8, 0.3, 1.9, 1.9]
b = [6.5, 5.4, 8.1, 3.5, 0.5, 3.8, 6.8, 4.9, 9.5, 6.2, 4.1]
# `equal_var=True` is default
statistic, pvalue = stats.ttest_ind(a, b, trim=.2)
assert_allclose(pvalue, 0.00113508833897713, atol=1e-10)
assert_allclose(statistic, -4.246116897032513, atol=1e-10)
@pytest.mark.parametrize('alt,pr,tr',
(('greater', 0.9985605452443, -4.2461168970325),
('less', 0.001439454755672, -4.2461168970325),),
)
def test_alternatives(self, alt, pr, tr):
'''
> library(PairedData)
> a <- c(2.7,2.7,1.1,3.0,1.9,3.0,3.8,3.8,0.3,1.9,1.9)
> b <- c(6.5,5.4,8.1,3.5,0.5,3.8,6.8,4.9,9.5,6.2,4.1)
> options(digits=16)
> yuen.t.test(a, b, alternative = 'greater')
'''
a = [2.7, 2.7, 1.1, 3.0, 1.9, 3.0, 3.8, 3.8, 0.3, 1.9, 1.9]
b = [6.5, 5.4, 8.1, 3.5, 0.5, 3.8, 6.8, 4.9, 9.5, 6.2, 4.1]
statistic, pvalue = stats.ttest_ind(a, b, trim=.2, equal_var=False,
alternative=alt)
assert_allclose(pvalue, pr, atol=1e-10)
assert_allclose(statistic, tr, atol=1e-10)
def test_errors_unsupported(self):
# confirm that attempting to trim with NaNs or permutations raises an
# error
match = "Permutations are currently not supported with trimming."
with assert_raises(ValueError, match=match):
stats.ttest_ind([1, 2], [2, 3], trim=.2, permutations=2)
@pytest.mark.parametrize("trim", [-.2, .5, 1])
def test_trim_bounds_error(self, trim):
match = "Trimming percentage should be 0 <= `trim` < .5."
with assert_raises(ValueError, match=match):
stats.ttest_ind([1, 2], [2, 1], trim=trim)
class Test_ttest_CI:
# indices in order [alternative={two-sided, less, greater},
# equal_var={False, True}, trim={0, 0.2}]
# reference values in order `statistic, df, pvalue, low, high`
# equal_var=False reference values computed with R PairedData yuen.t.test:
#
# library(PairedData)
# options(digits=16)
# a < - c(0.88236329, 0.97318744, 0.4549262, 0.97893335, 0.0606677,
# 0.44013366, 0.55806018, 0.40151434, 0.14453315, 0.25860601,
# 0.20202162)
# b < - c(0.93455277, 0.42680603, 0.49751939, 0.14152846, 0.711435,
# 0.77669667, 0.20507578, 0.78702772, 0.94691855, 0.32464958,
# 0.3873582, 0.35187468, 0.21731811)
# yuen.t.test(a, b, tr=0, conf.level = 0.9, alternative = 'l')
#
# equal_var=True reference values computed with R multicon yuenContrast:
#
# library(multicon)
# options(digits=16)
# a < - c(0.88236329, 0.97318744, 0.4549262, 0.97893335, 0.0606677,
# 0.44013366, 0.55806018, 0.40151434, 0.14453315, 0.25860601,
# 0.20202162)
# b < - c(0.93455277, 0.42680603, 0.49751939, 0.14152846, 0.711435,
# 0.77669667, 0.20507578, 0.78702772, 0.94691855, 0.32464958,
# 0.3873582, 0.35187468, 0.21731811)
# dv = c(a, b)
# iv = c(rep('a', length(a)), rep('b', length(b)))
# yuenContrast(dv~iv, EQVAR = FALSE, alternative = 'unequal', tr = 0.2)
r = np.empty(shape=(3, 2, 2, 5))
r[0, 0, 0] = [-0.2314607, 19.894435, 0.8193209, -0.247220294, 0.188729943]
r[1, 0, 0] = [-0.2314607, 19.894435, 0.40966045, -np.inf, 0.1382426469]
r[2, 0, 0] = [-0.2314607, 19.894435, 0.5903395, -0.1967329982, np.inf]
r[0, 0, 1] = [-0.2452886, 11.427896, 0.8105823, -0.34057446, 0.25847383]
r[1, 0, 1] = [-0.2452886, 11.427896, 0.40529115, -np.inf, 0.1865829074]
r[2, 0, 1] = [-0.2452886, 11.427896, 0.5947089, -0.268683541, np.inf]
# confidence interval not available for equal_var=True
r[0, 1, 0] = [-0.2345625322555006, 22, 0.8167175905643815, None, None]
r[1, 1, 0] = [-0.2345625322555006, 22, 0.4083587952821908, None, None]
r[2, 1, 0] = [-0.2345625322555006, 22, 0.5916412047178092, None, None]
r[0, 1, 1] = [-0.2505369406507428, 14, 0.8058115135702835, None, None]
r[1, 1, 1] = [-0.2505369406507428, 14, 0.4029057567851417, None, None]
r[2, 1, 1] = [-0.2505369406507428, 14, 0.5970942432148583, None, None]
@pytest.mark.parametrize('alternative', ['two-sided', 'less', 'greater'])
@pytest.mark.parametrize('equal_var', [False, True])
@pytest.mark.parametrize('trim', [0, 0.2])
def test_confidence_interval(self, alternative, equal_var, trim):
if equal_var and trim:
pytest.xfail('Discrepancy in `main`; needs further investigation.')
rng = np.random.default_rng(3810954496107292580)
x = rng.random(11)
y = rng.random(13)
res = stats.ttest_ind(x, y, alternative=alternative,
equal_var=equal_var, trim=trim)
alternatives = {'two-sided': 0, 'less': 1, 'greater': 2}
ref = self.r[alternatives[alternative], int(equal_var), int(np.ceil(trim))]
statistic, df, pvalue, low, high = ref
assert_allclose(res.statistic, statistic)
assert_allclose(res.df, df)
assert_allclose(res.pvalue, pvalue)
if not equal_var: # CI not available when `equal_var is True`
ci = res.confidence_interval(0.9)
assert_allclose(ci.low, low)
assert_allclose(ci.high, high)
def test__broadcast_concatenate():
# test that _broadcast_concatenate properly broadcasts arrays along all
# axes except `axis`, then concatenates along axis
np.random.seed(0)
a = np.random.rand(5, 4, 4, 3, 1, 6)
b = np.random.rand(4, 1, 8, 2, 6)
c = _broadcast_concatenate((a, b), axis=-3)
# broadcast manually as an independent check
a = np.tile(a, (1, 1, 1, 1, 2, 1))
b = np.tile(b[None, ...], (5, 1, 4, 1, 1, 1))
for index in product(*(range(i) for i in c.shape)):
i, j, k, l, m, n = index
if l < a.shape[-3]:
assert a[i, j, k, l, m, n] == c[i, j, k, l, m, n]
else:
assert b[i, j, k, l - a.shape[-3], m, n] == c[i, j, k, l, m, n]
def test_ttest_ind_with_uneq_var():
# check vs. R
a = (1, 2, 3)
b = (1.1, 2.9, 4.2)
pr = 0.53619490753126731
tr = -0.68649512735572582
t, p = stats.ttest_ind(a, b, equal_var=False)
assert_array_almost_equal([t,p], [tr, pr])
# test from desc stats API
assert_array_almost_equal(stats.ttest_ind_from_stats(*_desc_stats(a, b),
equal_var=False),
[t, p])
a = (1, 2, 3, 4)
pr = 0.84354139131608286
tr = -0.2108663315950719
t, p = stats.ttest_ind(a, b, equal_var=False)
assert_array_almost_equal([t,p], [tr, pr])
assert_array_almost_equal(stats.ttest_ind_from_stats(*_desc_stats(a, b),
equal_var=False),
[t, p])
# regression test
tr = 1.0912746897927283
tr_uneq_n = 0.66745638708050492
pr = 0.27647831993021388
pr_uneq_n = 0.50873585065616544
tpr = ([tr,-tr],[pr,pr])
rvs3 = np.linspace(1,100, 25)
rvs2 = np.linspace(1,100,100)
rvs1 = np.linspace(5,105,100)
rvs1_2D = np.array([rvs1, rvs2])
rvs2_2D = np.array([rvs2, rvs1])
t,p = stats.ttest_ind(rvs1, rvs2, axis=0, equal_var=False)
assert_array_almost_equal([t,p],(tr,pr))
assert_array_almost_equal(stats.ttest_ind_from_stats(*_desc_stats(rvs1,
rvs2),
equal_var=False),
(t, p))
t,p = stats.ttest_ind(rvs1, rvs3, axis=0, equal_var=False)
assert_array_almost_equal([t,p], (tr_uneq_n, pr_uneq_n))
assert_array_almost_equal(stats.ttest_ind_from_stats(*_desc_stats(rvs1,
rvs3),
equal_var=False),
(t, p))
t,p = stats.ttest_ind(rvs1_2D.T, rvs2_2D.T, axis=0, equal_var=False)
assert_array_almost_equal([t,p],tpr)
args = _desc_stats(rvs1_2D.T, rvs2_2D.T)
assert_array_almost_equal(stats.ttest_ind_from_stats(*args,
equal_var=False),
(t, p))
t,p = stats.ttest_ind(rvs1_2D, rvs2_2D, axis=1, equal_var=False)
assert_array_almost_equal([t,p],tpr)
args = _desc_stats(rvs1_2D, rvs2_2D, axis=1)
assert_array_almost_equal(stats.ttest_ind_from_stats(*args,
equal_var=False),
(t, p))
# test for namedtuple attribute results
attributes = ('statistic', 'pvalue')
res = stats.ttest_ind(rvs1, rvs2, axis=0, equal_var=False)
check_named_results(res, attributes)
# test on 3 dimensions
rvs1_3D = np.dstack([rvs1_2D,rvs1_2D,rvs1_2D])
rvs2_3D = np.dstack([rvs2_2D,rvs2_2D,rvs2_2D])
t,p = stats.ttest_ind(rvs1_3D, rvs2_3D, axis=1, equal_var=False)
assert_almost_equal(np.abs(t), np.abs(tr))
assert_array_almost_equal(np.abs(p), pr)
assert_equal(t.shape, (2, 3))
args = _desc_stats(rvs1_3D, rvs2_3D, axis=1)
t, p = stats.ttest_ind_from_stats(*args, equal_var=False)
assert_almost_equal(np.abs(t), np.abs(tr))
assert_array_almost_equal(np.abs(p), pr)
assert_equal(t.shape, (2, 3))
t, p = stats.ttest_ind(np.moveaxis(rvs1_3D, 2, 0),
np.moveaxis(rvs2_3D, 2, 0),
axis=2, equal_var=False)
assert_array_almost_equal(np.abs(t), np.abs(tr))
assert_array_almost_equal(np.abs(p), pr)
assert_equal(t.shape, (3, 2))
args = _desc_stats(np.moveaxis(rvs1_3D, 2, 0),
np.moveaxis(rvs2_3D, 2, 0), axis=2)
t, p = stats.ttest_ind_from_stats(*args, equal_var=False)
assert_array_almost_equal(np.abs(t), np.abs(tr))
assert_array_almost_equal(np.abs(p), pr)
assert_equal(t.shape, (3, 2))
# test zero division problem
with pytest.warns(RuntimeWarning, match="Precision loss occurred"):
t, p = stats.ttest_ind([0, 0, 0], [1, 1, 1], equal_var=False)
assert_equal((np.abs(t), p), (np.inf, 0))
with np.errstate(all='ignore'):
assert_equal(stats.ttest_ind([0, 0, 0], [0, 0, 0], equal_var=False),
(np.nan, np.nan))
# check that nan in input array result in nan output
anan = np.array([[1, np.nan], [-1, 1]])
assert_equal(stats.ttest_ind(anan, np.zeros((2, 2)), equal_var=False),
([0, np.nan], [1, np.nan]))
def test_ttest_ind_nan_2nd_arg():
# regression test for gh-6134: nans in the second arg were not handled
x = [np.nan, 2.0, 3.0, 4.0]
y = [1.0, 2.0, 1.0, 2.0]
r1 = stats.ttest_ind(x, y, nan_policy='omit')
r2 = stats.ttest_ind(y, x, nan_policy='omit')
assert_allclose(r2.statistic, -r1.statistic, atol=1e-15)
assert_allclose(r2.pvalue, r1.pvalue, atol=1e-15)
# NB: arguments are not paired when NaNs are dropped
r3 = stats.ttest_ind(y, x[1:])
assert_allclose(r2, r3, atol=1e-15)
# .. and this is consistent with R. R code:
# x = c(NA, 2.0, 3.0, 4.0)
# y = c(1.0, 2.0, 1.0, 2.0)
# t.test(x, y, var.equal=TRUE)
assert_allclose(r2, (-2.5354627641855498, 0.052181400457057901),
atol=1e-15)
def test_ttest_ind_empty_1d_returns_nan():
# Two empty inputs should return a TtestResult containing nan
# for both values.
with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
result = stats.ttest_ind([], [])
assert isinstance(result, stats._stats_py.TtestResult)
assert_equal(result, (np.nan, np.nan))
@pytest.mark.parametrize('b, expected_shape',
[(np.empty((1, 5, 0)), (3, 5)),
(np.empty((1, 0, 0)), (3, 0))])
def test_ttest_ind_axis_size_zero(b, expected_shape):
# In this test, the length of the axis dimension is zero.
# The results should be arrays containing nan with shape
# given by the broadcast nonaxis dimensions.
a = np.empty((3, 1, 0))
with np.testing.suppress_warnings() as sup:
# first case should warn, second shouldn't?
sup.filter(SmallSampleWarning, too_small_nd_not_omit)
result = stats.ttest_ind(a, b, axis=-1)
assert isinstance(result, stats._stats_py.TtestResult)
expected_value = np.full(expected_shape, fill_value=np.nan)
assert_equal(result.statistic, expected_value)
assert_equal(result.pvalue, expected_value)
def test_ttest_ind_nonaxis_size_zero():
# In this test, the length of the axis dimension is nonzero,
# but one of the nonaxis dimensions has length 0. Check that
# we still get the correctly broadcast shape, which is (5, 0)
# in this case.
a = np.empty((1, 8, 0))
b = np.empty((5, 8, 1))
result = stats.ttest_ind(a, b, axis=1)
assert isinstance(result, stats._stats_py.TtestResult)
assert_equal(result.statistic.shape, (5, 0))
assert_equal(result.pvalue.shape, (5, 0))
def test_ttest_ind_nonaxis_size_zero_different_lengths():
# In this test, the length of the axis dimension is nonzero,
# and that size is different in the two inputs,
# and one of the nonaxis dimensions has length 0. Check that
# we still get the correctly broadcast shape, which is (5, 0)
# in this case.
a = np.empty((1, 7, 0))
b = np.empty((5, 8, 1))
result = stats.ttest_ind(a, b, axis=1)
assert isinstance(result, stats._stats_py.TtestResult)
assert_equal(result.statistic.shape, (5, 0))
assert_equal(result.pvalue.shape, (5, 0))
def test_gh5686():
mean1, mean2 = np.array([1, 2]), np.array([3, 4])
std1, std2 = np.array([5, 3]), np.array([4, 5])
nobs1, nobs2 = np.array([130, 140]), np.array([100, 150])
# This will raise a TypeError unless gh-5686 is fixed.
stats.ttest_ind_from_stats(mean1, std1, nobs1, mean2, std2, nobs2)
def test_ttest_ind_from_stats_inputs_zero():
# Regression test for gh-6409.
result = stats.ttest_ind_from_stats(0, 0, 6, 0, 0, 6, equal_var=False)
assert_equal(result, [np.nan, np.nan])
def test_ttest_single_observation():
# test that p-values are uniformly distributed under the null hypothesis
rng = np.random.default_rng(246834602926842)
x = rng.normal(size=(10000, 2))
y = rng.normal(size=(10000, 1))
q = rng.uniform(size=100)
res = stats.ttest_ind(x, y, equal_var=True, axis=-1)
assert stats.ks_1samp(res.pvalue, stats.uniform().cdf).pvalue > 0.1
assert_allclose(np.percentile(res.pvalue, q*100), q, atol=1e-2)
res = stats.ttest_ind(y, x, equal_var=True, axis=-1)
assert stats.ks_1samp(res.pvalue, stats.uniform().cdf).pvalue > 0.1
assert_allclose(np.percentile(res.pvalue, q*100), q, atol=1e-2)
# reference values from R:
# options(digits=16)
# t.test(c(2, 3, 5), c(1.5), var.equal=TRUE)
res = stats.ttest_ind([2, 3, 5], [1.5], equal_var=True)
assert_allclose(res, (1.0394023007754, 0.407779907736), rtol=1e-10)
def _convert_pvalue_alternative(t, p, alt, xp):
# test alternative parameter
# Convert from two-sided p-values to one sided using T result data.
less = xp.asarray(alt == "less")
greater = xp.asarray(alt == "greater")
i = ((t < 0) & less) | ((t > 0) & greater)
return xp.where(i, p/2, 1 - p/2)
@pytest.mark.slow
@pytest.mark.skip_xp_backends(cpu_only=True,
reasons=['Uses NumPy for pvalue, CI'])
@pytest.mark.usefixtures("skip_xp_backends")
@array_api_compatible
def test_ttest_1samp_new(xp):
n1, n2, n3 = (10, 15, 20)
rvn1 = stats.norm.rvs(loc=5, scale=10, size=(n1, n2, n3))
rvn1 = xp.asarray(rvn1)
# check multidimensional array and correct axis handling
# deterministic rvn1 and rvn2 would be better as in test_ttest_rel
popmean = xp.ones((1, n2, n3))
t1, p1 = stats.ttest_1samp(rvn1, popmean, axis=0)
t2, p2 = stats.ttest_1samp(rvn1, 1., axis=0)
t3, p3 = stats.ttest_1samp(rvn1[:, 0, 0], 1.)
xp_assert_close(t1, t2, rtol=1e-14)
xp_assert_close(t1[0, 0], t3, rtol=1e-14)
assert_equal(t1.shape, (n2, n3))
popmean = xp.ones((n1, 1, n3))
t1, p1 = stats.ttest_1samp(rvn1, popmean, axis=1)
t2, p2 = stats.ttest_1samp(rvn1, 1., axis=1)
t3, p3 = stats.ttest_1samp(rvn1[0, :, 0], 1.)
xp_assert_close(t1, t2, rtol=1e-14)
xp_assert_close(t1[0, 0], t3, rtol=1e-14)
assert_equal(t1.shape, (n1, n3))
popmean = xp.ones((n1, n2, 1))
t1, p1 = stats.ttest_1samp(rvn1, popmean, axis=2)
t2, p2 = stats.ttest_1samp(rvn1, 1., axis=2)
t3, p3 = stats.ttest_1samp(rvn1[0, 0, :], 1.)
xp_assert_close(t1, t2, rtol=1e-14)
xp_assert_close(t1[0, 0], t3, rtol=1e-14)
assert_equal(t1.shape, (n1, n2))
# test zero division problem
t, p = stats.ttest_1samp(xp.asarray([0., 0., 0.]), 1.)
xp_assert_equal(xp.abs(t), xp.asarray(xp.inf))
xp_assert_equal(p, xp.asarray(0.))
tr, pr = stats.ttest_1samp(rvn1[:, :, :], 1.)
t, p = stats.ttest_1samp(rvn1[:, :, :], 1., alternative="greater")
pc = _convert_pvalue_alternative(tr, pr, "greater", xp)
xp_assert_close(p, pc)
xp_assert_close(t, tr)
t, p = stats.ttest_1samp(rvn1[:, :, :], 1., alternative="less")
pc = _convert_pvalue_alternative(tr, pr, "less", xp)
xp_assert_close(p, pc)
xp_assert_close(t, tr)
with np.errstate(all='ignore'):
res = stats.ttest_1samp(xp.asarray([0., 0., 0.]), 0.)
xp_assert_equal(res.statistic, xp.asarray(xp.nan))
xp_assert_equal(res.pvalue, xp.asarray(xp.nan))
# check that nan in input array result in nan output
anan = xp.asarray([[1., np.nan], [-1., 1.]])
res = stats.ttest_1samp(anan, 0.)
xp_assert_equal(res.statistic, xp.asarray([0., xp.nan]))
xp_assert_equal(res.pvalue, xp.asarray([1., xp.nan]))
@pytest.mark.skip_xp_backends(np_only=True,
reasons=["Only NumPy has nan_policy='omit' for now"])
@pytest.mark.usefixtures("skip_xp_backends")
@array_api_compatible
def test_ttest_1samp_new_omit(xp):
n1, n2, n3 = (5, 10, 15)
rvn1 = stats.norm.rvs(loc=5, scale=10, size=(n1, n2, n3))
rvn1 = xp.asarray(rvn1)
rvn1[0:2, 1:3, 4:8] = xp.nan
tr, pr = stats.ttest_1samp(rvn1[:, :, :], 1., nan_policy='omit')
t, p = stats.ttest_1samp(rvn1[:, :, :], 1., nan_policy='omit',
alternative="greater")
pc = _convert_pvalue_alternative(tr, pr, "greater", xp)
xp_assert_close(p, pc)
xp_assert_close(t, tr)
t, p = stats.ttest_1samp(rvn1[:, :, :], 1., nan_policy='omit',
alternative="less")
pc = _convert_pvalue_alternative(tr, pr, "less", xp)
xp_assert_close(p, pc)
xp_assert_close(t, tr)
@pytest.mark.skip_xp_backends(cpu_only=True,
reasons=['Uses NumPy for pvalue, CI'])
@pytest.mark.usefixtures("skip_xp_backends")
@array_api_compatible
def test_ttest_1samp_popmean_array(xp):
# when popmean.shape[axis] != 1, raise an error
# if the user wants to test multiple null hypotheses simultaneously,
# use standard broadcasting rules
rng = np.random.default_rng(2913300596553337193)
x = rng.random(size=(1, 15, 20))
x = xp.asarray(x)
message = r"`popmean.shape\[axis\]` must equal 1."
popmean = xp.asarray(rng.random(size=(5, 2, 20)))
with pytest.raises(ValueError, match=message):
stats.ttest_1samp(x, popmean=popmean, axis=-2)
popmean = xp.asarray(rng.random(size=(5, 1, 20)))
res = stats.ttest_1samp(x, popmean=popmean, axis=-2)
assert res.statistic.shape == (5, 20)
xp_test = array_namespace(x) # torch needs expand_dims
l, u = res.confidence_interval()
l = xp_test.expand_dims(l, axis=-2)
u = xp_test.expand_dims(u, axis=-2)
res = stats.ttest_1samp(x, popmean=l, axis=-2)
ref = xp.broadcast_to(xp.asarray(0.05, dtype=xp.float64), res.pvalue.shape)
xp_assert_close(res.pvalue, ref)
res = stats.ttest_1samp(x, popmean=u, axis=-2)
xp_assert_close(res.pvalue, ref)
class TestDescribe:
@array_api_compatible
def test_describe_scalar(self, xp):
with suppress_warnings() as sup, \
np.errstate(invalid="ignore", divide="ignore"):
sup.filter(RuntimeWarning, "Degrees of freedom <= 0 for slice")
n, mm, m, v, sk, kurt = stats.describe(xp.asarray(4.)[()])
assert n == 1
xp_assert_equal(mm[0], xp.asarray(4.0))
xp_assert_equal(mm[1], xp.asarray(4.0))
xp_assert_equal(m, xp.asarray(4.0))
xp_assert_equal(v ,xp.asarray(xp.nan))
xp_assert_equal(sk, xp.asarray(xp.nan))
xp_assert_equal(kurt, xp.asarray(xp.nan))
@array_api_compatible
def test_describe_numbers(self, xp):
xp_test = array_namespace(xp.asarray(1.)) # numpy needs `concat`
x = xp_test.concat((xp.ones((3, 4)), xp.full((2, 4), 2.)))
nc = 5
mmc = (xp.asarray([1., 1., 1., 1.]), xp.asarray([2., 2., 2., 2.]))
mc = xp.asarray([1.4, 1.4, 1.4, 1.4])
vc = xp.asarray([0.3, 0.3, 0.3, 0.3])
skc = xp.asarray([0.40824829046386357] * 4)
kurtc = xp.asarray([-1.833333333333333] * 4)
n, mm, m, v, sk, kurt = stats.describe(x)
assert n == nc
xp_assert_equal(mm[0], mmc[0])
xp_assert_equal(mm[1], mmc[1])
xp_assert_close(m, mc, rtol=4 * xp.finfo(m.dtype).eps)
xp_assert_close(v, vc, rtol=4 * xp.finfo(m.dtype).eps)
xp_assert_close(sk, skc)
xp_assert_close(kurt, kurtc)
n, mm, m, v, sk, kurt = stats.describe(x.T, axis=1)
assert n == nc
xp_assert_equal(mm[0], mmc[0])
xp_assert_equal(mm[1], mmc[1])
xp_assert_close(m, mc, rtol=4 * xp.finfo(m.dtype).eps)
xp_assert_close(v, vc, rtol=4 * xp.finfo(m.dtype).eps)
xp_assert_close(sk, skc)
xp_assert_close(kurt, kurtc)
def describe_nan_policy_omit_test(self):
x = np.arange(10.)
x[9] = np.nan
nc, mmc = (9, (0.0, 8.0))
mc = 4.0
vc = 7.5
skc = 0.0
kurtc = -1.2300000000000002
n, mm, m, v, sk, kurt = stats.describe(x, nan_policy='omit')
assert_equal(n, nc)
assert_equal(mm, mmc)
assert_equal(m, mc)
assert_equal(v, vc)
assert_array_almost_equal(sk, skc)
assert_array_almost_equal(kurt, kurtc, decimal=13)
@array_api_compatible
def test_describe_nan_policy_other(self, xp):
x = xp.arange(10.)
x = xp.where(x==9, xp.asarray(xp.nan), x)
message = 'The input contains nan values'
with pytest.raises(ValueError, match=message):
stats.describe(x, nan_policy='raise')
n, mm, m, v, sk, kurt = stats.describe(x, nan_policy='propagate')
ref = xp.asarray(xp.nan)[()]
assert n == 10
xp_assert_equal(mm[0], ref)
xp_assert_equal(mm[1], ref)
xp_assert_equal(m, ref)
xp_assert_equal(v, ref)
xp_assert_equal(sk, ref)
xp_assert_equal(kurt, ref)
if is_numpy(xp):
self.describe_nan_policy_omit_test()
else:
message = "`nan_policy='omit' is incompatible with non-NumPy arrays."
with pytest.raises(ValueError, match=message):
stats.describe(x, nan_policy='omit')
message = 'nan_policy must be one of...'
with pytest.raises(ValueError, match=message):
stats.describe(x, nan_policy='foobar')
def test_describe_result_attributes(self):
# some result attributes are tuples, which aren't meant to be compared
# with `xp_assert_close`
actual = stats.describe(np.arange(5.))
attributes = ('nobs', 'minmax', 'mean', 'variance', 'skewness', 'kurtosis')
check_named_results(actual, attributes)
@array_api_compatible
def test_describe_ddof(self, xp):
xp_test = array_namespace(xp.asarray(1.)) # numpy needs `concat`
x = xp_test.concat((xp.ones((3, 4)), xp.full((2, 4), 2.)))
nc = 5
mmc = (xp.asarray([1., 1., 1., 1.]), xp.asarray([2., 2., 2., 2.]))
mc = xp.asarray([1.4, 1.4, 1.4, 1.4])
vc = xp.asarray([0.24, 0.24, 0.24, 0.24])
skc = xp.asarray([0.40824829046386357] * 4)
kurtc = xp.asarray([-1.833333333333333] * 4)
n, mm, m, v, sk, kurt = stats.describe(x, ddof=0)
assert n == nc
xp_assert_equal(mm[0], mmc[0])
xp_assert_equal(mm[1], mmc[1])
xp_assert_close(m, mc)
xp_assert_close(v, vc)
xp_assert_close(sk, skc)
xp_assert_close(kurt, kurtc)
@array_api_compatible
def test_describe_axis_none(self, xp):
xp_test = array_namespace(xp.asarray(1.)) # numpy needs `concat`
x = xp_test.concat((xp.ones((3, 4)), xp.full((2, 4), 2.)))
# expected values
nc = 20
mmc = (xp.asarray(1.0), xp.asarray(2.0))
mc = xp.asarray(1.3999999999999999)
vc = xp.asarray(0.25263157894736848)
skc = xp.asarray(0.4082482904638634)
kurtc = xp.asarray(-1.8333333333333333)
# actual values
n, mm, m, v, sk, kurt = stats.describe(x, axis=None)
assert n == nc
xp_assert_equal(mm[0], mmc[0])
xp_assert_equal(mm[1], mmc[1])
xp_assert_close(m, mc)
xp_assert_close(v, vc)
xp_assert_close(sk, skc)
xp_assert_close(kurt, kurtc)
@array_api_compatible
def test_describe_empty(self, xp):
message = "The input must not be empty."
with pytest.raises(ValueError, match=message):
stats.describe(xp.asarray([]))
@array_api_compatible
class NormalityTests:
def test_too_small(self, xp):
# 1D sample has too few observations -> warning/error
test_fun = getattr(stats, self.test_name)
x = xp.asarray(4.)
if is_numpy(xp):
with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
res = test_fun(x)
NaN = xp.asarray(xp.nan)
xp_assert_equal(res.statistic, NaN)
xp_assert_equal(res.pvalue, NaN)
else:
message = "...requires at least..."
with pytest.raises(ValueError, match=message):
test_fun(x)
@pytest.mark.parametrize("alternative", ['two-sided', 'less', 'greater'])
def test_against_R(self, alternative, xp):
# testa against R `dagoTest` from package `fBasics`
# library(fBasics)
# options(digits=16)
# x = c(-2, -1, 0, 1, 2, 3)**2
# x = rep(x, times=4)
# test_result <- dagoTest(x)
# test_result@test$statistic
# test_result@test$p.value
test_name = self.test_name
test_fun = getattr(stats, test_name)
ref_statistic= xp.asarray(self.case_ref[0])
ref_pvalue = xp.asarray(self.case_ref[1])
kwargs = {}
if alternative in {'less', 'greater'}:
if test_name in {'skewtest', 'kurtosistest'}:
ref_pvalue = ref_pvalue/2 if alternative == "less" else 1-ref_pvalue/2
ref_pvalue = 1-ref_pvalue if test_name == 'skewtest' else ref_pvalue
kwargs['alternative'] = alternative
else:
pytest.skip('`alternative` not available for `normaltest`')
x = xp.asarray((-2, -1, 0, 1, 2, 3.)*4)**2
res = test_fun(x, **kwargs)
res_statistic, res_pvalue = res
xp_assert_close(res_statistic, ref_statistic)
xp_assert_close(res_pvalue, ref_pvalue)
check_named_results(res, ('statistic', 'pvalue'), xp=xp)
def test_nan(self, xp):
# nan in input -> nan output (default nan_policy='propagate')
test_fun = getattr(stats, self.test_name)
x = xp.arange(30.)
NaN = xp.asarray(xp.nan, dtype=x.dtype)
x = xp.where(x == 29, NaN, x)
with np.errstate(invalid="ignore"):
res = test_fun(x)
xp_assert_equal(res.statistic, NaN)
xp_assert_equal(res.pvalue, NaN)
class TestSkewTest(NormalityTests):
test_name = 'skewtest'
case_ref = (1.98078826090875881, 0.04761502382843208) # statistic, pvalue
def test_intuitive(self, xp):
# intuitive tests; see gh-13549. skewnorm with parameter 1 has skew > 0
a1 = stats.skewnorm.rvs(a=1, size=10000, random_state=123)
a1_xp = xp.asarray(a1)
pval = stats.skewtest(a1_xp, alternative='greater').pvalue
xp_assert_close(pval, xp.asarray(0.0, dtype=a1_xp.dtype), atol=9e-6)
def test_skewtest_too_few_observations(self, xp):
# Regression test for ticket #1492.
# skewtest requires at least 8 observations; 7 should raise a ValueError.
stats.skewtest(xp.arange(8.0))
x = xp.arange(7.0)
if is_numpy(xp):
with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
res = stats.skewtest(x)
NaN = xp.asarray(xp.nan)
xp_assert_equal(res.statistic, NaN)
xp_assert_equal(res.pvalue, NaN)
else:
message = "`skewtest` requires at least 8 observations"
with pytest.raises(ValueError, match=message):
stats.skewtest(x)
class TestKurtosisTest(NormalityTests):
test_name = 'kurtosistest'
case_ref = (-0.01403734404759738, 0.98880018772590561) # statistic, pvalue
def test_intuitive(self, xp):
# intuitive tests; see gh-13549. excess kurtosis of laplace is 3 > 0
a2 = stats.laplace.rvs(size=10000, random_state=123)
a2_xp = xp.asarray(a2)
pval = stats.kurtosistest(a2_xp, alternative='greater').pvalue
xp_assert_close(pval, xp.asarray(0.0, dtype=a2_xp.dtype), atol=1e-15)
def test_gh9033_regression(self, xp):
# regression test for issue gh-9033: x clearly non-normal but power of
# negative denom needs to be handled correctly to reject normality
counts = [128, 0, 58, 7, 0, 41, 16, 0, 0, 167]
x = np.hstack([np.full(c, i) for i, c in enumerate(counts)])
x = xp.asarray(x, dtype=xp.float64)
assert stats.kurtosistest(x)[1] < 0.01
def test_kurtosistest_too_few_observations(self, xp):
# kurtosistest requires at least 5 observations; 4 should raise a ValueError.
# At least 20 are needed to avoid warning
# Regression test for ticket #1425.
stats.kurtosistest(xp.arange(20.0))
message = "`kurtosistest` p-value may be inaccurate..."
with pytest.warns(UserWarning, match=message):
stats.kurtosistest(xp.arange(5.0))
with pytest.warns(UserWarning, match=message):
stats.kurtosistest(xp.arange(19.0))
x = xp.arange(4.0)
if is_numpy(xp):
with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
res = stats.skewtest(x)
NaN = xp.asarray(xp.nan)
xp_assert_equal(res.statistic, NaN)
xp_assert_equal(res.pvalue, NaN)
else:
message = "`kurtosistest` requires at least 5 observations"
with pytest.raises(ValueError, match=message):
stats.kurtosistest(x)
class TestNormalTest(NormalityTests):
test_name = 'normaltest'
case_ref = (3.92371918158185551, 0.14059672529747502) # statistic, pvalue
class TestRankSums:
np.random.seed(0)
x, y = np.random.rand(2, 10)
@pytest.mark.parametrize('alternative', ['less', 'greater', 'two-sided'])
def test_ranksums_result_attributes(self, alternative):
# ranksums pval = mannwhitneyu pval w/out continuity or tie correction
res1 = stats.ranksums(self.x, self.y,
alternative=alternative).pvalue
res2 = stats.mannwhitneyu(self.x, self.y, use_continuity=False,
alternative=alternative).pvalue
assert_allclose(res1, res2)
def test_ranksums_named_results(self):
res = stats.ranksums(self.x, self.y)
check_named_results(res, ('statistic', 'pvalue'))
def test_input_validation(self):
with assert_raises(ValueError, match="`alternative` must be 'less'"):
stats.ranksums(self.x, self.y, alternative='foobar')
@array_api_compatible
class TestJarqueBera:
def test_jarque_bera_against_R(self, xp):
# library(tseries)
# options(digits=16)
# x < - rnorm(5)
# jarque.bera.test(x)
x = [-0.160104223201523288, 1.131262000934478040, -0.001235254523709458,
-0.776440091309490987, -2.072959999533182884]
x = xp.asarray(x)
ref = xp.asarray([0.17651605223752, 0.9155246169805])
res = stats.jarque_bera(x)
xp_assert_close(res.statistic, ref[0])
xp_assert_close(res.pvalue, ref[1])
@skip_xp_backends(np_only=True)
@pytest.mark.usefixtures("skip_xp_backends")
def test_jarque_bera_array_like(self):
# array-like only relevant for NumPy
np.random.seed(987654321)
x = np.random.normal(0, 1, 100000)
jb_test1 = JB1, p1 = stats.jarque_bera(list(x))
jb_test2 = JB2, p2 = stats.jarque_bera(tuple(x))
jb_test3 = JB3, p3 = stats.jarque_bera(x.reshape(2, 50000))
assert JB1 == JB2 == JB3 == jb_test1.statistic == jb_test2.statistic == jb_test3.statistic # noqa: E501
assert p1 == p2 == p3 == jb_test1.pvalue == jb_test2.pvalue == jb_test3.pvalue
def test_jarque_bera_size(self, xp):
x = xp.asarray([])
if is_numpy(xp):
with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
res = stats.jarque_bera(x)
NaN = xp.asarray(xp.nan)
xp_assert_equal(res.statistic, NaN)
xp_assert_equal(res.pvalue, NaN)
else:
message = "At least one observation is required."
with pytest.raises(ValueError, match=message):
res = stats.jarque_bera(x)
def test_axis(self, xp):
rng = np.random.RandomState(seed=122398129)
x = xp.asarray(rng.random(size=(2, 45)))
res = stats.jarque_bera(x, axis=None)
ref = stats.jarque_bera(xp.reshape(x, (-1,)))
xp_assert_equal(res.statistic, ref.statistic)
xp_assert_equal(res.pvalue, ref.pvalue)
res = stats.jarque_bera(x, axis=1)
s0, p0 = stats.jarque_bera(x[0, :])
s1, p1 = stats.jarque_bera(x[1, :])
xp_assert_close(res.statistic, xp.asarray([s0, s1]))
xp_assert_close(res.pvalue, xp.asarray([p0, p1]))
resT = stats.jarque_bera(x.T, axis=0)
xp_assert_close(res.statistic, resT.statistic)
xp_assert_close(res.pvalue, resT.pvalue)
class TestMannWhitneyU:
X = [19.8958398126694, 19.5452691647182, 19.0577309166425, 21.716543054589,
20.3269502208702, 20.0009273294025, 19.3440043632957, 20.4216806548105,
19.0649894736528, 18.7808043120398, 19.3680942943298, 19.4848044069953,
20.7514611265663, 19.0894948874598, 19.4975522356628, 18.9971170734274,
20.3239606288208, 20.6921298083835, 19.0724259532507, 18.9825187935021,
19.5144462609601, 19.8256857844223, 20.5174677102032, 21.1122407995892,
17.9490854922535, 18.2847521114727, 20.1072217648826, 18.6439891962179,
20.4970638083542, 19.5567594734914]
Y = [19.2790668029091, 16.993808441865, 18.5416338448258, 17.2634018833575,
19.1577183624616, 18.5119655377495, 18.6068455037221, 18.8358343362655,
19.0366413269742, 18.1135025515417, 19.2201873866958, 17.8344909022841,
18.2894380745856, 18.6661374133922, 19.9688601693252, 16.0672254617636,
19.00596360572, 19.201561539032, 19.0487501090183, 19.0847908674356]
significant = 14
def test_mannwhitneyu_one_sided(self):
u1, p1 = stats.mannwhitneyu(self.X, self.Y, alternative='less')
u2, p2 = stats.mannwhitneyu(self.Y, self.X, alternative='greater')
u3, p3 = stats.mannwhitneyu(self.X, self.Y, alternative='greater')
u4, p4 = stats.mannwhitneyu(self.Y, self.X, alternative='less')
assert_equal(p1, p2)
assert_equal(p3, p4)
assert_(p1 != p3)
assert_equal(u1, 498)
assert_equal(u2, 102)
assert_equal(u3, 498)
assert_equal(u4, 102)
assert_approx_equal(p1, 0.999957683256589, significant=self.significant)
assert_approx_equal(p3, 4.5941632666275e-05, significant=self.significant)
def test_mannwhitneyu_two_sided(self):
u1, p1 = stats.mannwhitneyu(self.X, self.Y, alternative='two-sided')
u2, p2 = stats.mannwhitneyu(self.Y, self.X, alternative='two-sided')
assert_equal(p1, p2)
assert_equal(u1, 498)
assert_equal(u2, 102)
assert_approx_equal(p1, 9.188326533255e-05,
significant=self.significant)
def test_mannwhitneyu_no_correct_one_sided(self):
u1, p1 = stats.mannwhitneyu(self.X, self.Y, False,
alternative='less')
u2, p2 = stats.mannwhitneyu(self.Y, self.X, False,
alternative='greater')
u3, p3 = stats.mannwhitneyu(self.X, self.Y, False,
alternative='greater')
u4, p4 = stats.mannwhitneyu(self.Y, self.X, False,
alternative='less')
assert_equal(p1, p2)
assert_equal(p3, p4)
assert_(p1 != p3)
assert_equal(u1, 498)
assert_equal(u2, 102)
assert_equal(u3, 498)
assert_equal(u4, 102)
assert_approx_equal(p1, 0.999955905990004, significant=self.significant)
assert_approx_equal(p3, 4.40940099958089e-05, significant=self.significant)
def test_mannwhitneyu_no_correct_two_sided(self):
u1, p1 = stats.mannwhitneyu(self.X, self.Y, False,
alternative='two-sided')
u2, p2 = stats.mannwhitneyu(self.Y, self.X, False,
alternative='two-sided')
assert_equal(p1, p2)
assert_equal(u1, 498)
assert_equal(u2, 102)
assert_approx_equal(p1, 8.81880199916178e-05,
significant=self.significant)
def test_mannwhitneyu_ones(self):
# test for gh-1428
x = np.array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
1., 1., 1., 1., 1., 1., 1., 2., 1., 1., 1., 1., 1., 1.,
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
1., 1., 1., 1., 1., 1., 1., 2., 1., 1., 1., 1., 1., 1.,
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 2.,
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
1., 1., 2., 1., 1., 1., 1., 2., 1., 1., 2., 1., 1., 2.,
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 2., 1.,
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
1., 1., 1., 1., 1., 1., 1., 2., 1., 1., 1., 1., 1., 1.,
1., 1., 1., 2., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
1., 1., 1., 1., 1., 1., 1., 1., 3., 1., 1., 1., 1., 1.,
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
1., 1., 1., 1., 1., 1.])
y = np.array([1., 1., 1., 1., 1., 1., 1., 2., 1., 2., 1., 1., 1., 1.,
2., 1., 1., 1., 2., 1., 1., 1., 1., 1., 2., 1., 1., 3.,
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 2., 1., 2., 1.,
1., 1., 1., 1., 1., 2., 1., 1., 1., 1., 1., 1., 1., 1.,
1., 1., 1., 1., 1., 1., 1., 2., 1., 1., 1., 1., 1., 2.,
2., 1., 1., 2., 1., 1., 2., 1., 2., 1., 1., 1., 1., 2.,
2., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
1., 2., 1., 1., 1., 1., 1., 2., 2., 2., 1., 1., 1., 1.,
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
2., 1., 1., 2., 1., 1., 1., 1., 2., 1., 1., 1., 1., 1.,
1., 1., 1., 1., 1., 1., 1., 2., 1., 1., 1., 2., 1., 1.,
1., 1., 1., 1.])
# checked against R wilcox.test
assert_allclose(stats.mannwhitneyu(x, y, alternative='less'),
(16980.5, 2.8214327656317373e-005))
# p-value from R, e.g. wilcox.test(x, y, alternative="g")
assert_allclose(stats.mannwhitneyu(x, y, alternative='greater'),
(16980.5, 0.9999719954296))
assert_allclose(stats.mannwhitneyu(x, y, alternative='two-sided'),
(16980.5, 5.642865531266e-05))
def test_mannwhitneyu_result_attributes(self):
# test for namedtuple attribute results
attributes = ('statistic', 'pvalue')
res = stats.mannwhitneyu(self.X, self.Y, alternative="less")
check_named_results(res, attributes)
def test_pointbiserial():
# same as mstats test except for the nan
# Test data: https://web.archive.org/web/20060504220742/https://support.sas.com/ctx/samples/index.jsp?sid=490&tab=output
x = [1,0,1,1,1,1,0,1,0,0,0,1,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,1,0,
0,0,0,0,1]
y = [14.8,13.8,12.4,10.1,7.1,6.1,5.8,4.6,4.3,3.5,3.3,3.2,3.0,
2.8,2.8,2.5,2.4,2.3,2.1,1.7,1.7,1.5,1.3,1.3,1.2,1.2,1.1,
0.8,0.7,0.6,0.5,0.2,0.2,0.1]
assert_almost_equal(stats.pointbiserialr(x, y)[0], 0.36149, 5)
# test for namedtuple attribute results
attributes = ('correlation', 'pvalue')
res = stats.pointbiserialr(x, y)
check_named_results(res, attributes)
assert_equal(res.correlation, res.statistic)
def test_obrientransform():
# A couple tests calculated by hand.
x1 = np.array([0, 2, 4])
t1 = stats.obrientransform(x1)
expected = [7, -2, 7]
assert_allclose(t1[0], expected)
x2 = np.array([0, 3, 6, 9])
t2 = stats.obrientransform(x2)
expected = np.array([30, 0, 0, 30])
assert_allclose(t2[0], expected)
# Test two arguments.
a, b = stats.obrientransform(x1, x2)
assert_equal(a, t1[0])
assert_equal(b, t2[0])
# Test three arguments.
a, b, c = stats.obrientransform(x1, x2, x1)
assert_equal(a, t1[0])
assert_equal(b, t2[0])
assert_equal(c, t1[0])
# This is a regression test to check np.var replacement.
# The author of this test didn't separately verify the numbers.
x1 = np.arange(5)
result = np.array(
[[5.41666667, 1.04166667, -0.41666667, 1.04166667, 5.41666667],
[21.66666667, 4.16666667, -1.66666667, 4.16666667, 21.66666667]])
assert_array_almost_equal(stats.obrientransform(x1, 2*x1), result, decimal=8)
# Example from "O'Brien Test for Homogeneity of Variance"
# by Herve Abdi.
values = range(5, 11)
reps = np.array([5, 11, 9, 3, 2, 2])
data = np.repeat(values, reps)
transformed_values = np.array([3.1828, 0.5591, 0.0344,
1.6086, 5.2817, 11.0538])
expected = np.repeat(transformed_values, reps)
result = stats.obrientransform(data)
assert_array_almost_equal(result[0], expected, decimal=4)
def check_equal_gmean(array_like, desired, axis=None, dtype=None, rtol=1e-7,
weights=None):
# Note this doesn't test when axis is not specified
x = stats.gmean(array_like, axis=axis, dtype=dtype, weights=weights)
assert_allclose(x, desired, rtol=rtol)
assert_equal(x.dtype, dtype)
def check_equal_hmean(array_like, desired, axis=None, dtype=None, rtol=1e-7,
weights=None):
x = stats.hmean(array_like, axis=axis, dtype=dtype, weights=weights)
assert_allclose(x, desired, rtol=rtol)
assert_equal(x.dtype, dtype)
def check_equal_pmean(array_like, exp, desired, axis=None, dtype=None,
rtol=1e-7, weights=None):
x = stats.pmean(array_like, exp, axis=axis, dtype=dtype, weights=weights)
assert_allclose(x, desired, rtol=rtol)
assert_equal(x.dtype, dtype)
class TestHarMean:
def test_0(self):
a = [1, 0, 2]
desired = 0
check_equal_hmean(a, desired)
def test_1d_list(self):
# Test a 1d list
a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
desired = 34.1417152147
check_equal_hmean(a, desired)
a = [1, 2, 3, 4]
desired = 4. / (1. / 1 + 1. / 2 + 1. / 3 + 1. / 4)
check_equal_hmean(a, desired)
def test_1d_array(self):
# Test a 1d array
a = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
desired = 34.1417152147
check_equal_hmean(a, desired)
def test_1d_array_with_zero(self):
a = np.array([1, 0])
desired = 0.0
assert_equal(stats.hmean(a), desired)
def test_1d_array_with_negative_value(self):
a = np.array([1, 0, -1])
assert_raises(ValueError, stats.hmean, a)
# Note the next tests use axis=None as default, not axis=0
def test_2d_list(self):
# Test a 2d list
a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]]
desired = 38.6696271841
check_equal_hmean(a, desired)
def test_2d_array(self):
# Test a 2d array
a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]]
desired = 38.6696271841
check_equal_hmean(np.array(a), desired)
def test_2d_axis0(self):
# Test a 2d list with axis=0
a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]]
desired = np.array([22.88135593, 39.13043478, 52.90076336, 65.45454545])
check_equal_hmean(a, desired, axis=0)
def test_2d_axis0_with_zero(self):
a = [[10, 0, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]]
desired = np.array([22.88135593, 0.0, 52.90076336, 65.45454545])
assert_allclose(stats.hmean(a, axis=0), desired)
def test_2d_axis1(self):
# Test a 2d list with axis=1
a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]]
desired = np.array([19.2, 63.03939962, 103.80078637])
check_equal_hmean(a, desired, axis=1)
def test_2d_axis1_with_zero(self):
a = [[10, 0, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]]
desired = np.array([0.0, 63.03939962, 103.80078637])
assert_allclose(stats.hmean(a, axis=1), desired)
def test_weights_1d_list(self):
# Desired result from:
# https://www.hackmath.net/en/math-problem/35871
a = [2, 10, 6]
weights = [10, 5, 3]
desired = 3
check_equal_hmean(a, desired, weights=weights, rtol=1e-5)
def test_weights_2d_array_axis0(self):
# Desired result from:
# https://www.hackmath.net/en/math-problem/35871
a = np.array([[2, 5], [10, 5], [6, 5]])
weights = np.array([[10, 1], [5, 1], [3, 1]])
desired = np.array([3, 5])
check_equal_hmean(a, desired, axis=0, weights=weights, rtol=1e-5)
def test_weights_2d_array_axis1(self):
# Desired result from:
# https://www.hackmath.net/en/math-problem/35871
a = np.array([[2, 10, 6], [7, 7, 7]])
weights = np.array([[10, 5, 3], [1, 1, 1]])
desired = np.array([3, 7])
check_equal_hmean(a, desired, axis=1, weights=weights, rtol=1e-5)
def test_weights_masked_1d_array(self):
# Desired result from:
# https://www.hackmath.net/en/math-problem/35871
a = np.array([2, 10, 6, 42])
weights = np.ma.array([10, 5, 3, 42], mask=[0, 0, 0, 1])
desired = 3
check_equal_hmean(a, desired, weights=weights, rtol=1e-5)
class TestGeoMean:
def test_0(self):
a = [1, 0, 2]
desired = 0
check_equal_gmean(a, desired)
def test_1d_list(self):
# Test a 1d list
a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
desired = 45.2872868812
check_equal_gmean(a, desired)
a = [1, 2, 3, 4]
desired = power(1 * 2 * 3 * 4, 1. / 4.)
check_equal_gmean(a, desired, rtol=1e-14)
def test_1d_array(self):
# Test a 1d array
a = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
desired = 45.2872868812
check_equal_gmean(a, desired)
a = array([1, 2, 3, 4], float32)
desired = power(1 * 2 * 3 * 4, 1. / 4.)
check_equal_gmean(a, desired, dtype=float32)
# Note the next tests use axis=None as default, not axis=0
def test_2d_list(self):
# Test a 2d list
a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]]
desired = 52.8885199
check_equal_gmean(a, desired)
def test_2d_array(self):
# Test a 2d array
a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]]
desired = 52.8885199
check_equal_gmean(array(a), desired)
def test_2d_axis0(self):
# Test a 2d list with axis=0
a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]]
desired = np.array([35.56893304, 49.32424149, 61.3579244, 72.68482371])
check_equal_gmean(a, desired, axis=0)
a = array([[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]])
desired = array([1, 2, 3, 4])
check_equal_gmean(a, desired, axis=0, rtol=1e-14)
def test_2d_axis1(self):
# Test a 2d list with axis=1
a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]]
desired = np.array([22.13363839, 64.02171746, 104.40086817])
check_equal_gmean(a, desired, axis=1)
a = array([[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]])
v = power(1 * 2 * 3 * 4, 1. / 4.)
desired = array([v, v, v])
check_equal_gmean(a, desired, axis=1, rtol=1e-14)
def test_large_values(self):
a = array([1e100, 1e200, 1e300])
desired = 1e200
check_equal_gmean(a, desired, rtol=1e-13)
def test_1d_list0(self):
# Test a 1d list with zero element
a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 0]
desired = 0.0 # due to exp(-inf)=0
with np.errstate(all='ignore'):
check_equal_gmean(a, desired)
def test_1d_array0(self):
# Test a 1d array with zero element
a = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 0])
desired = 0.0 # due to exp(-inf)=0
with np.errstate(divide='ignore'):
check_equal_gmean(a, desired)
def test_1d_list_neg(self):
# Test a 1d list with negative element
a = [10, 20, 30, 40, 50, 60, 70, 80, 90, -1]
desired = np.nan # due to log(-1) = nan
with np.errstate(invalid='ignore'):
check_equal_gmean(a, desired)
def test_weights_1d_list(self):
# Desired result from:
# https://www.dummies.com/education/math/business-statistics/how-to-find-the-weighted-geometric-mean-of-a-data-set/
a = [1, 2, 3, 4, 5]
weights = [2, 5, 6, 4, 3]
desired = 2.77748
check_equal_gmean(a, desired, weights=weights, rtol=1e-5)
def test_weights_1d_array(self):
# Desired result from:
# https://www.dummies.com/education/math/business-statistics/how-to-find-the-weighted-geometric-mean-of-a-data-set/
a = np.array([1, 2, 3, 4, 5])
weights = np.array([2, 5, 6, 4, 3])
desired = 2.77748
check_equal_gmean(a, desired, weights=weights, rtol=1e-5)
def test_weights_masked_1d_array(self):
# Desired result from:
# https://www.dummies.com/education/math/business-statistics/how-to-find-the-weighted-geometric-mean-of-a-data-set/
a = np.array([1, 2, 3, 4, 5, 6])
weights = np.ma.array([2, 5, 6, 4, 3, 5], mask=[0, 0, 0, 0, 0, 1])
desired = 2.77748
check_equal_gmean(a, desired, weights=weights, rtol=1e-5)
class TestPowMean:
def pmean_reference(a, p):
return (np.sum(a**p) / a.size)**(1/p)
def wpmean_reference(a, p, weights):
return (np.sum(weights * a**p) / np.sum(weights))**(1/p)
def test_bad_exponent(self):
with pytest.raises(ValueError, match='Power mean only defined for'):
stats.pmean([1, 2, 3], [0])
with pytest.raises(ValueError, match='Power mean only defined for'):
stats.pmean([1, 2, 3], np.array([0]))
def test_1d_list(self):
a, p = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100], 3.5
desired = TestPowMean.pmean_reference(np.array(a), p)
check_equal_pmean(a, p, desired)
a, p = [1, 2, 3, 4], 2
desired = np.sqrt((1**2 + 2**2 + 3**2 + 4**2) / 4)
check_equal_pmean(a, p, desired)
def test_1d_array(self):
a, p = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]), -2.5
desired = TestPowMean.pmean_reference(a, p)
check_equal_pmean(a, p, desired)
def test_1d_array_with_zero(self):
a, p = np.array([1, 0]), -1
desired = 0.0
assert_equal(stats.pmean(a, p), desired)
def test_1d_array_with_negative_value(self):
a, p = np.array([1, 0, -1]), 1.23
with pytest.raises(ValueError, match='Power mean only defined if all'):
stats.pmean(a, p)
@pytest.mark.parametrize(
("a", "p"),
[([[10, 20], [50, 60], [90, 100]], -0.5),
(np.array([[10, 20], [50, 60], [90, 100]]), 0.5)]
)
def test_2d_axisnone(self, a, p):
desired = TestPowMean.pmean_reference(np.array(a), p)
check_equal_pmean(a, p, desired)
@pytest.mark.parametrize(
("a", "p"),
[([[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]], -0.5),
([[10, 0, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]], 0.5)]
)
def test_2d_list_axis0(self, a, p):
desired = [
TestPowMean.pmean_reference(
np.array([a[i][j] for i in range(len(a))]), p
)
for j in range(len(a[0]))
]
check_equal_pmean(a, p, desired, axis=0)
@pytest.mark.parametrize(
("a", "p"),
[([[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]], -0.5),
([[10, 0, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]], 0.5)]
)
def test_2d_list_axis1(self, a, p):
desired = [TestPowMean.pmean_reference(np.array(a_), p) for a_ in a]
check_equal_pmean(a, p, desired, axis=1)
def test_weights_1d_list(self):
a, p = [2, 10, 6], -1.23456789
weights = [10, 5, 3]
desired = TestPowMean.wpmean_reference(np.array(a), p, weights)
check_equal_pmean(a, p, desired, weights=weights, rtol=1e-5)
def test_weights_masked_1d_array(self):
a, p = np.array([2, 10, 6, 42]), 1
weights = np.ma.array([10, 5, 3, 42], mask=[0, 0, 0, 1])
desired = np.average(a, weights=weights)
check_equal_pmean(a, p, desired, weights=weights, rtol=1e-5)
@pytest.mark.parametrize(
("axis", "fun_name", "p"),
[(None, "wpmean_reference", 9.87654321),
(0, "gmean", 0),
(1, "hmean", -1)]
)
def test_weights_2d_array(self, axis, fun_name, p):
if fun_name == 'wpmean_reference':
def fun(a, axis, weights):
return TestPowMean.wpmean_reference(a, p, weights)
else:
fun = getattr(stats, fun_name)
a = np.array([[2, 5], [10, 5], [6, 5]])
weights = np.array([[10, 1], [5, 1], [3, 1]])
desired = fun(a, axis=axis, weights=weights)
check_equal_pmean(a, p, desired, axis=axis, weights=weights, rtol=1e-5)
class TestGeometricStandardDeviation:
# must add 1 as `gstd` is only defined for positive values
array_1d = np.arange(2 * 3 * 4) + 1
gstd_array_1d = 2.294407613602
array_3d = array_1d.reshape(2, 3, 4)
def test_1d_array(self):
gstd_actual = stats.gstd(self.array_1d)
assert_allclose(gstd_actual, self.gstd_array_1d)
def test_1d_numeric_array_like_input(self):
gstd_actual = stats.gstd(tuple(self.array_1d))
assert_allclose(gstd_actual, self.gstd_array_1d)
def test_raises_value_error_non_numeric_input(self):
# this is raised by NumPy, but it's quite interpretable
with pytest.raises(TypeError, match="ufunc 'log' not supported"):
stats.gstd('You cannot take the logarithm of a string.')
@pytest.mark.parametrize('bad_value', (0, -1, np.inf, np.nan))
def test_returns_nan_invalid_value(self, bad_value):
x = np.append(self.array_1d, [bad_value])
if np.isfinite(bad_value):
message = "The geometric standard deviation is only defined..."
with pytest.warns(RuntimeWarning, match=message):
res = stats.gstd(x)
else:
res = stats.gstd(x)
assert_equal(res, np.nan)
def test_propagates_nan_values(self):
a = array([[1, 1, 1, 16], [np.nan, 1, 2, 3]])
gstd_actual = stats.gstd(a, axis=1)
assert_allclose(gstd_actual, np.array([4, np.nan]))
def test_ddof_equal_to_number_of_observations(self):
with pytest.warns(RuntimeWarning, match='Degrees of freedom <= 0'):
assert_equal(stats.gstd(self.array_1d, ddof=self.array_1d.size), np.inf)
def test_3d_array(self):
gstd_actual = stats.gstd(self.array_3d, axis=None)
assert_allclose(gstd_actual, self.gstd_array_1d)
def test_3d_array_axis_type_tuple(self):
gstd_actual = stats.gstd(self.array_3d, axis=(1,2))
assert_allclose(gstd_actual, [2.12939215, 1.22120169])
def test_3d_array_axis_0(self):
gstd_actual = stats.gstd(self.array_3d, axis=0)
gstd_desired = np.array([
[6.1330555493918, 3.958900210120, 3.1206598248344, 2.6651441426902],
[2.3758135028411, 2.174581428192, 2.0260062829505, 1.9115518327308],
[1.8205343606803, 1.746342404566, 1.6846557065742, 1.6325269194382]
])
assert_allclose(gstd_actual, gstd_desired)
def test_3d_array_axis_1(self):
gstd_actual = stats.gstd(self.array_3d, axis=1)
gstd_desired = np.array([
[3.118993630946, 2.275985934063, 1.933995977619, 1.742896469724],
[1.271693593916, 1.254158641801, 1.238774141609, 1.225164057869]
])
assert_allclose(gstd_actual, gstd_desired)
def test_3d_array_axis_2(self):
gstd_actual = stats.gstd(self.array_3d, axis=2)
gstd_desired = np.array([
[1.8242475707664, 1.2243686572447, 1.1318311657788],
[1.0934830582351, 1.0724479791887, 1.0591498540749]
])
assert_allclose(gstd_actual, gstd_desired)
def test_masked_3d_array(self):
ma = np.ma.masked_where(self.array_3d > 16, self.array_3d)
message = "`gstd` support for masked array input was deprecated in..."
with pytest.warns(DeprecationWarning, match=message):
gstd_actual = stats.gstd(ma, axis=2)
gstd_desired = stats.gstd(self.array_3d, axis=2)
mask = [[0, 0, 0], [0, 1, 1]]
assert_allclose(gstd_actual, gstd_desired)
assert_equal(gstd_actual.mask, mask)
def test_binomtest():
# precision tests compared to R for ticket:986
pp = np.concatenate((np.linspace(0.1, 0.2, 5),
np.linspace(0.45, 0.65, 5),
np.linspace(0.85, 0.95, 5)))
n = 501
x = 450
results = [0.0, 0.0, 1.0159969301994141e-304,
2.9752418572150531e-275, 7.7668382922535275e-250,
2.3381250925167094e-099, 7.8284591587323951e-081,
9.9155947819961383e-065, 2.8729390725176308e-050,
1.7175066298388421e-037, 0.0021070691951093692,
0.12044570587262322, 0.88154763174802508, 0.027120993063129286,
2.6102587134694721e-006]
for p, res in zip(pp, results):
assert_approx_equal(stats.binomtest(x, n, p).pvalue, res,
significant=12, err_msg=f'fail forp={p}')
assert_approx_equal(stats.binomtest(50, 100, 0.1).pvalue,
5.8320387857343647e-024,
significant=12)
def test_binomtest2():
# test added for issue #2384
res2 = [
[1.0, 1.0],
[0.5, 1.0, 0.5],
[0.25, 1.00, 1.00, 0.25],
[0.125, 0.625, 1.000, 0.625, 0.125],
[0.0625, 0.3750, 1.0000, 1.0000, 0.3750, 0.0625],
[0.03125, 0.21875, 0.68750, 1.00000, 0.68750, 0.21875, 0.03125],
[0.015625, 0.125000, 0.453125, 1.000000, 1.000000, 0.453125, 0.125000,
0.015625],
[0.0078125, 0.0703125, 0.2890625, 0.7265625, 1.0000000, 0.7265625,
0.2890625, 0.0703125, 0.0078125],
[0.00390625, 0.03906250, 0.17968750, 0.50781250, 1.00000000,
1.00000000, 0.50781250, 0.17968750, 0.03906250, 0.00390625],
[0.001953125, 0.021484375, 0.109375000, 0.343750000, 0.753906250,
1.000000000, 0.753906250, 0.343750000, 0.109375000, 0.021484375,
0.001953125]
]
for k in range(1, 11):
res1 = [stats.binomtest(v, k, 0.5).pvalue for v in range(k + 1)]
assert_almost_equal(res1, res2[k-1], decimal=10)
def test_binomtest3():
# test added for issue #2384
# test when x == n*p and neighbors
res3 = [stats.binomtest(v, v*k, 1./k).pvalue
for v in range(1, 11) for k in range(2, 11)]
assert_equal(res3, np.ones(len(res3), int))
# > bt=c()
# > for(i in as.single(1:10)) {
# + for(k in as.single(2:10)) {
# + bt = c(bt, binom.test(i-1, k*i,(1/k))$p.value);
# + print(c(i+1, k*i,(1/k)))
# + }
# + }
binom_testm1 = np.array([
0.5, 0.5555555555555556, 0.578125, 0.5904000000000003,
0.5981224279835393, 0.603430543396034, 0.607304096221924,
0.610255656871054, 0.612579511000001, 0.625, 0.670781893004115,
0.68853759765625, 0.6980101120000006, 0.703906431368616,
0.70793209416498, 0.7108561134173507, 0.713076544331419,
0.714820192935702, 0.6875, 0.7268709038256367, 0.7418963909149174,
0.74986110468096, 0.7548015520398076, 0.7581671424768577,
0.760607984787832, 0.762459425024199, 0.7639120677676575, 0.7265625,
0.761553963657302, 0.774800934828818, 0.7818005980538996,
0.78613491480358, 0.789084353140195, 0.7912217659828884,
0.79284214559524, 0.794112956558801, 0.75390625, 0.7856929451142176,
0.7976688481430754, 0.8039848974727624, 0.807891868948366,
0.8105487660137676, 0.812473307174702, 0.8139318233591120,
0.815075399104785, 0.7744140625, 0.8037322594985427,
0.814742863657656, 0.8205425178645808, 0.8241275984172285,
0.8265645374416, 0.8283292196088257, 0.829666291102775,
0.8307144686362666, 0.7905273437499996, 0.8178712053954738,
0.828116983756619, 0.833508948940494, 0.8368403871552892,
0.839104213210105, 0.840743186196171, 0.84198481438049,
0.8429580531563676, 0.803619384765625, 0.829338573944648,
0.8389591907548646, 0.84401876783902, 0.84714369697889,
0.8492667010581667, 0.850803474598719, 0.851967542858308,
0.8528799045949524, 0.8145294189453126, 0.838881732845347,
0.847979024541911, 0.852760894015685, 0.8557134656773457,
0.8577190131799202, 0.85917058278431, 0.860270010472127,
0.861131648404582, 0.823802947998047, 0.846984756807511,
0.855635653643743, 0.860180994825685, 0.86298688573253,
0.864892525675245, 0.866271647085603, 0.867316125625004,
0.8681346531755114
])
# > bt=c()
# > for(i in as.single(1:10)) {
# + for(k in as.single(2:10)) {
# + bt = c(bt, binom.test(i+1, k*i,(1/k))$p.value);
# + print(c(i+1, k*i,(1/k)))
# + }
# + }
binom_testp1 = np.array([
0.5, 0.259259259259259, 0.26171875, 0.26272, 0.2632244513031551,
0.2635138663069203, 0.2636951804161073, 0.2638162407564354,
0.2639010709000002, 0.625, 0.4074074074074074, 0.42156982421875,
0.4295746560000003, 0.43473045988554, 0.4383309503172684,
0.4409884859402103, 0.4430309389962837, 0.444649849401104, 0.6875,
0.4927602499618962, 0.5096031427383425, 0.5189636628480,
0.5249280070771274, 0.5290623300865124, 0.5320974248125793,
0.5344204730474308, 0.536255847400756, 0.7265625, 0.5496019313526808,
0.5669248746708034, 0.576436455045805, 0.5824538812831795,
0.5866053321547824, 0.589642781414643, 0.5919618019300193,
0.593790427805202, 0.75390625, 0.590868349763505, 0.607983393277209,
0.617303847446822, 0.623172512167948, 0.627208862156123,
0.6301556891501057, 0.632401894928977, 0.6341708982290303,
0.7744140625, 0.622562037497196, 0.639236102912278, 0.648263335014579,
0.65392850011132, 0.657816519817211, 0.660650782947676,
0.662808780346311, 0.6645068560246006, 0.7905273437499996,
0.6478843304312477, 0.6640468318879372, 0.6727589686071775,
0.6782129857784873, 0.681950188903695, 0.684671508668418,
0.686741824999918, 0.688369886732168, 0.803619384765625,
0.668716055304315, 0.684360013879534, 0.6927642396829181,
0.6980155964704895, 0.701609591890657, 0.7042244320992127,
0.7062125081341817, 0.707775152962577, 0.8145294189453126,
0.686243374488305, 0.7013873696358975, 0.709501223328243,
0.714563595144314, 0.718024953392931, 0.7205416252126137,
0.722454130389843, 0.723956813292035, 0.823802947998047,
0.701255953767043, 0.715928221686075, 0.723772209289768,
0.7286603031173616, 0.7319999279787631, 0.7344267920995765,
0.736270323773157, 0.737718376096348
])
res4_p1 = [stats.binomtest(v+1, v*k, 1./k).pvalue
for v in range(1, 11) for k in range(2, 11)]
res4_m1 = [stats.binomtest(v-1, v*k, 1./k).pvalue
for v in range(1, 11) for k in range(2, 11)]
assert_almost_equal(res4_p1, binom_testp1, decimal=13)
assert_almost_equal(res4_m1, binom_testm1, decimal=13)
class TestTrim:
# test trim functions
def test_trim1(self):
a = np.arange(11)
assert_equal(np.sort(stats.trim1(a, 0.1)), np.arange(10))
assert_equal(np.sort(stats.trim1(a, 0.2)), np.arange(9))
assert_equal(np.sort(stats.trim1(a, 0.2, tail='left')),
np.arange(2, 11))
assert_equal(np.sort(stats.trim1(a, 3/11., tail='left')),
np.arange(3, 11))
assert_equal(stats.trim1(a, 1.0), [])
assert_equal(stats.trim1(a, 1.0, tail='left'), [])
# empty input
assert_equal(stats.trim1([], 0.1), [])
assert_equal(stats.trim1([], 3/11., tail='left'), [])
assert_equal(stats.trim1([], 4/6.), [])
# test axis
a = np.arange(24).reshape(6, 4)
ref = np.arange(4, 24).reshape(5, 4) # first row trimmed
axis = 0
trimmed = stats.trim1(a, 0.2, tail='left', axis=axis)
assert_equal(np.sort(trimmed, axis=axis), ref)
axis = 1
trimmed = stats.trim1(a.T, 0.2, tail='left', axis=axis)
assert_equal(np.sort(trimmed, axis=axis), ref.T)
def test_trimboth(self):
a = np.arange(11)
assert_equal(np.sort(stats.trimboth(a, 3/11.)), np.arange(3, 8))
assert_equal(np.sort(stats.trimboth(a, 0.2)),
np.array([2, 3, 4, 5, 6, 7, 8]))
assert_equal(np.sort(stats.trimboth(np.arange(24).reshape(6, 4), 0.2)),
np.arange(4, 20).reshape(4, 4))
assert_equal(np.sort(stats.trimboth(np.arange(24).reshape(4, 6).T,
2/6.)),
np.array([[2, 8, 14, 20], [3, 9, 15, 21]]))
assert_raises(ValueError, stats.trimboth,
np.arange(24).reshape(4, 6).T, 4/6.)
# empty input
assert_equal(stats.trimboth([], 0.1), [])
assert_equal(stats.trimboth([], 3/11.), [])
assert_equal(stats.trimboth([], 4/6.), [])
def test_trim_mean(self):
# don't use pre-sorted arrays
a = np.array([4, 8, 2, 0, 9, 5, 10, 1, 7, 3, 6])
idx = np.array([3, 5, 0, 1, 2, 4])
a2 = np.arange(24).reshape(6, 4)[idx, :]
a3 = np.arange(24).reshape(6, 4, order='F')[idx, :]
assert_equal(stats.trim_mean(a3, 2/6.),
np.array([2.5, 8.5, 14.5, 20.5]))
assert_equal(stats.trim_mean(a2, 2/6.),
np.array([10., 11., 12., 13.]))
idx4 = np.array([1, 0, 3, 2])
a4 = np.arange(24).reshape(4, 6)[idx4, :]
assert_equal(stats.trim_mean(a4, 2/6.),
np.array([9., 10., 11., 12., 13., 14.]))
# shuffled arange(24) as array_like
a = [7, 11, 12, 21, 16, 6, 22, 1, 5, 0, 18, 10, 17, 9, 19, 15, 23,
20, 2, 14, 4, 13, 8, 3]
assert_equal(stats.trim_mean(a, 2/6.), 11.5)
assert_equal(stats.trim_mean([5,4,3,1,2,0], 2/6.), 2.5)
# check axis argument
np.random.seed(1234)
a = np.random.randint(20, size=(5, 6, 4, 7))
for axis in [0, 1, 2, 3, -1]:
res1 = stats.trim_mean(a, 2/6., axis=axis)
res2 = stats.trim_mean(np.moveaxis(a, axis, 0), 2/6.)
assert_equal(res1, res2)
res1 = stats.trim_mean(a, 2/6., axis=None)
res2 = stats.trim_mean(a.ravel(), 2/6.)
assert_equal(res1, res2)
assert_raises(ValueError, stats.trim_mean, a, 0.6)
# empty input
assert_equal(stats.trim_mean([], 0.0), np.nan)
assert_equal(stats.trim_mean([], 0.6), np.nan)
class TestSigmaClip:
def test_sigmaclip1(self):
a = np.concatenate((np.linspace(9.5, 10.5, 31), np.linspace(0, 20, 5)))
fact = 4 # default
c, low, upp = stats.sigmaclip(a)
assert_(c.min() > low)
assert_(c.max() < upp)
assert_equal(low, c.mean() - fact*c.std())
assert_equal(upp, c.mean() + fact*c.std())
assert_equal(c.size, a.size)
def test_sigmaclip2(self):
a = np.concatenate((np.linspace(9.5, 10.5, 31), np.linspace(0, 20, 5)))
fact = 1.5
c, low, upp = stats.sigmaclip(a, fact, fact)
assert_(c.min() > low)
assert_(c.max() < upp)
assert_equal(low, c.mean() - fact*c.std())
assert_equal(upp, c.mean() + fact*c.std())
assert_equal(c.size, 4)
assert_equal(a.size, 36) # check original array unchanged
def test_sigmaclip3(self):
a = np.concatenate((np.linspace(9.5, 10.5, 11),
np.linspace(-100, -50, 3)))
fact = 1.8
c, low, upp = stats.sigmaclip(a, fact, fact)
assert_(c.min() > low)
assert_(c.max() < upp)
assert_equal(low, c.mean() - fact*c.std())
assert_equal(upp, c.mean() + fact*c.std())
assert_equal(c, np.linspace(9.5, 10.5, 11))
def test_sigmaclip_result_attributes(self):
a = np.concatenate((np.linspace(9.5, 10.5, 11),
np.linspace(-100, -50, 3)))
fact = 1.8
res = stats.sigmaclip(a, fact, fact)
attributes = ('clipped', 'lower', 'upper')
check_named_results(res, attributes)
def test_std_zero(self):
# regression test #8632
x = np.ones(10)
assert_equal(stats.sigmaclip(x)[0], x)
class TestAlexanderGovern:
def test_compare_dtypes(self):
args = [[13, 13, 13, 13, 13, 13, 13, 12, 12],
[14, 13, 12, 12, 12, 12, 12, 11, 11],
[14, 14, 13, 13, 13, 13, 13, 12, 12],
[15, 14, 13, 13, 13, 12, 12, 12, 11]]
args_int16 = np.array(args, dtype=np.int16)
args_int32 = np.array(args, dtype=np.int32)
args_uint8 = np.array(args, dtype=np.uint8)
args_float64 = np.array(args, dtype=np.float64)
res_int16 = stats.alexandergovern(*args_int16)
res_int32 = stats.alexandergovern(*args_int32)
res_unit8 = stats.alexandergovern(*args_uint8)
res_float64 = stats.alexandergovern(*args_float64)
assert (res_int16.pvalue == res_int32.pvalue ==
res_unit8.pvalue == res_float64.pvalue)
assert (res_int16.statistic == res_int32.statistic ==
res_unit8.statistic == res_float64.statistic)
@pytest.mark.parametrize('case',[([1, 2], []), ([1, 2], 2), ([1, 2], [2])])
def test_too_small_inputs(self, case):
# input array is of size zero or too small
with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
res = stats.alexandergovern(*case)
assert_equal(res.statistic, np.nan)
assert_equal(res.pvalue, np.nan)
def test_bad_inputs(self):
# inputs are not finite (infinity)
with assert_raises(ValueError, match="Input samples must be finite."):
stats.alexandergovern([1, 2], [np.inf, np.inf])
def test_compare_r(self):
'''
Data generated in R with
> set.seed(1)
> library("onewaytests")
> library("tibble")
> y <- c(rnorm(40, sd=10),
+ rnorm(30, sd=15),
+ rnorm(20, sd=20))
> x <- c(rep("one", times=40),
+ rep("two", times=30),
+ rep("eight", times=20))
> x <- factor(x)
> ag.test(y ~ x, tibble(y,x))
Alexander-Govern Test (alpha = 0.05)
-------------------------------------------------------------
data : y and x
statistic : 1.359941
parameter : 2
p.value : 0.5066321
Result : Difference is not statistically significant.
-------------------------------------------------------------
Example adapted from:
https://eval-serv2.metpsy.uni-jena.de/wiki-metheval-hp/index.php/R_FUN_Alexander-Govern
'''
one = [-6.264538107423324, 1.8364332422208225, -8.356286124100471,
15.952808021377916, 3.295077718153605, -8.204683841180152,
4.874290524284853, 7.383247051292173, 5.757813516534923,
-3.0538838715635603, 15.11781168450848, 3.898432364114311,
-6.2124058054180376, -22.146998871774997, 11.249309181431082,
-0.4493360901523085, -0.16190263098946087, 9.438362106852992,
8.212211950980885, 5.939013212175088, 9.189773716082183,
7.821363007310671, 0.745649833651906, -19.89351695863373,
6.198257478947102, -0.5612873952900078, -1.557955067053293,
-14.707523838992744, -4.781500551086204, 4.179415601997024,
13.58679551529044, -1.0278772734299553, 3.876716115593691,
-0.5380504058290512, -13.770595568286065, -4.149945632996798,
-3.942899537103493, -0.5931339671118566, 11.000253719838831,
7.631757484575442]
two = [-2.4678539438038034, -3.8004252020476135, 10.454450631071062,
8.34994798010486, -10.331335418242798, -10.612427354431794,
5.468729432052455, 11.527993867731237, -1.6851931822534207,
13.216615896813222, 5.971588205506021, -9.180395898761569,
5.116795371366372, -16.94044644121189, 21.495355525515556,
29.7059984775879, -5.508322146997636, -15.662019394747961,
8.545794411636193, -2.0258190582123654, 36.024266407571645,
-0.5886000409975387, 10.346090436761651, 0.4200323817099909,
-11.14909813323608, 2.8318844927151434, -27.074379433365568,
21.98332292344329, 2.2988000731784655, 32.58917505543229]
eight = [9.510190577993251, -14.198928618436291, 12.214527069781099,
-18.68195263288503, -25.07266800478204, 5.828924710349257,
-8.86583746436866, 0.02210703263248262, 1.4868264830332811,
-11.79041892376144, -11.37337465637004, -2.7035723024766414,
23.56173993146409, -30.47133600859524, 11.878923752568431,
6.659007424270365, 21.261996745527256, -6.083678472686013,
7.400376198325763, 5.341975815444621]
soln = stats.alexandergovern(one, two, eight)
assert_allclose(soln.statistic, 1.3599405447999450836)
assert_allclose(soln.pvalue, 0.50663205309676440091)
def test_compare_scholar(self):
'''
Data taken from 'The Modification and Evaluation of the
Alexander-Govern Test in Terms of Power' by Kingsley Ochuko, T.,
Abdullah, S., Binti Zain, Z., & Soaad Syed Yahaya, S. (2015).
'''
young = [482.43, 484.36, 488.84, 495.15, 495.24, 502.69, 504.62,
518.29, 519.1, 524.1, 524.12, 531.18, 548.42, 572.1, 584.68,
609.09, 609.53, 666.63, 676.4]
middle = [335.59, 338.43, 353.54, 404.27, 437.5, 469.01, 485.85,
487.3, 493.08, 494.31, 499.1, 886.41]
old = [519.01, 528.5, 530.23, 536.03, 538.56, 538.83, 557.24, 558.61,
558.95, 565.43, 586.39, 594.69, 629.22, 645.69, 691.84]
soln = stats.alexandergovern(young, middle, old)
assert_allclose(soln.statistic, 5.3237, atol=1e-3)
assert_allclose(soln.pvalue, 0.06982, atol=1e-4)
# verify with ag.test in r
'''
> library("onewaytests")
> library("tibble")
> young <- c(482.43, 484.36, 488.84, 495.15, 495.24, 502.69, 504.62,
+ 518.29, 519.1, 524.1, 524.12, 531.18, 548.42, 572.1,
+ 584.68, 609.09, 609.53, 666.63, 676.4)
> middle <- c(335.59, 338.43, 353.54, 404.27, 437.5, 469.01, 485.85,
+ 487.3, 493.08, 494.31, 499.1, 886.41)
> old <- c(519.01, 528.5, 530.23, 536.03, 538.56, 538.83, 557.24,
+ 558.61, 558.95, 565.43, 586.39, 594.69, 629.22,
+ 645.69, 691.84)
> young_fct <- c(rep("young", times=19))
> middle_fct <-c(rep("middle", times=12))
> old_fct <- c(rep("old", times=15))
> ag.test(a ~ b, tibble(a=c(young, middle, old), b=factor(c(young_fct,
+ middle_fct, old_fct))))
Alexander-Govern Test (alpha = 0.05)
-------------------------------------------------------------
data : a and b
statistic : 5.324629
parameter : 2
p.value : 0.06978651
Result : Difference is not statistically significant.
-------------------------------------------------------------
'''
assert_allclose(soln.statistic, 5.324629)
assert_allclose(soln.pvalue, 0.06978651)
def test_compare_scholar3(self):
'''
Data taken from 'Robustness And Comparative Power Of WelchAspin,
Alexander-Govern And Yuen Tests Under Non-Normality And Variance
Heteroscedasticity', by Ayed A. Almoied. 2017. Page 34-37.
https://digitalcommons.wayne.edu/cgi/viewcontent.cgi?article=2775&context=oa_dissertations
'''
x1 = [-1.77559, -1.4113, -0.69457, -0.54148, -0.18808, -0.07152,
0.04696, 0.051183, 0.148695, 0.168052, 0.422561, 0.458555,
0.616123, 0.709968, 0.839956, 0.857226, 0.929159, 0.981442,
0.999554, 1.642958]
x2 = [-1.47973, -1.2722, -0.91914, -0.80916, -0.75977, -0.72253,
-0.3601, -0.33273, -0.28859, -0.09637, -0.08969, -0.01824,
0.260131, 0.289278, 0.518254, 0.683003, 0.877618, 1.172475,
1.33964, 1.576766]
soln = stats.alexandergovern(x1, x2)
assert_allclose(soln.statistic, 0.713526, atol=1e-5)
assert_allclose(soln.pvalue, 0.398276, atol=1e-5)
'''
tested in ag.test in R:
> library("onewaytests")
> library("tibble")
> x1 <- c(-1.77559, -1.4113, -0.69457, -0.54148, -0.18808, -0.07152,
+ 0.04696, 0.051183, 0.148695, 0.168052, 0.422561, 0.458555,
+ 0.616123, 0.709968, 0.839956, 0.857226, 0.929159, 0.981442,
+ 0.999554, 1.642958)
> x2 <- c(-1.47973, -1.2722, -0.91914, -0.80916, -0.75977, -0.72253,
+ -0.3601, -0.33273, -0.28859, -0.09637, -0.08969, -0.01824,
+ 0.260131, 0.289278, 0.518254, 0.683003, 0.877618, 1.172475,
+ 1.33964, 1.576766)
> x1_fact <- c(rep("x1", times=20))
> x2_fact <- c(rep("x2", times=20))
> a <- c(x1, x2)
> b <- factor(c(x1_fact, x2_fact))
> ag.test(a ~ b, tibble(a, b))
Alexander-Govern Test (alpha = 0.05)
-------------------------------------------------------------
data : a and b
statistic : 0.7135182
parameter : 1
p.value : 0.3982783
Result : Difference is not statistically significant.
-------------------------------------------------------------
'''
assert_allclose(soln.statistic, 0.7135182)
assert_allclose(soln.pvalue, 0.3982783)
def test_nan_policy_propogate(self):
args = [[1, 2, 3, 4], [1, np.nan]]
# default nan_policy is 'propagate'
res = stats.alexandergovern(*args)
assert_equal(res.pvalue, np.nan)
assert_equal(res.statistic, np.nan)
def test_nan_policy_raise(self):
args = [[1, 2, 3, 4], [1, np.nan]]
with assert_raises(ValueError, match="The input contains nan values"):
stats.alexandergovern(*args, nan_policy='raise')
def test_nan_policy_omit(self):
args_nan = [[1, 2, 3, np.nan, 4], [1, np.nan, 19, 25]]
args_no_nan = [[1, 2, 3, 4], [1, 19, 25]]
res_nan = stats.alexandergovern(*args_nan, nan_policy='omit')
res_no_nan = stats.alexandergovern(*args_no_nan)
assert_equal(res_nan.pvalue, res_no_nan.pvalue)
assert_equal(res_nan.statistic, res_no_nan.statistic)
def test_constant_input(self):
# Zero variance input, consistent with `stats.pearsonr`
msg = "An input array is constant; the statistic is not defined."
with pytest.warns(stats.ConstantInputWarning, match=msg):
res = stats.alexandergovern([0.667, 0.667, 0.667],
[0.123, 0.456, 0.789])
assert_equal(res.statistic, np.nan)
assert_equal(res.pvalue, np.nan)
class TestFOneWay:
def test_trivial(self):
# A trivial test of stats.f_oneway, with F=0.
F, p = stats.f_oneway([0, 2], [0, 2])
assert_equal(F, 0.0)
assert_equal(p, 1.0)
def test_basic(self):
# Despite being a floating point calculation, this data should
# result in F being exactly 2.0.
F, p = stats.f_oneway([0, 2], [2, 4])
assert_equal(F, 2.0)
assert_allclose(p, 1 - np.sqrt(0.5), rtol=1e-14)
def test_known_exact(self):
# Another trivial dataset for which the exact F and p can be
# calculated.
F, p = stats.f_oneway([2], [2], [2, 3, 4])
# The use of assert_equal might be too optimistic, but the calculation
# in this case is trivial enough that it is likely to go through with
# no loss of precision.
assert_equal(F, 3/5)
assert_equal(p, 5/8)
def test_large_integer_array(self):
a = np.array([655, 788], dtype=np.uint16)
b = np.array([789, 772], dtype=np.uint16)
F, p = stats.f_oneway(a, b)
# The expected value was verified by computing it with mpmath with
# 40 digits of precision.
assert_allclose(F, 0.77450216931805540, rtol=1e-14)
def test_result_attributes(self):
a = np.array([655, 788], dtype=np.uint16)
b = np.array([789, 772], dtype=np.uint16)
res = stats.f_oneway(a, b)
attributes = ('statistic', 'pvalue')
check_named_results(res, attributes)
def test_nist(self):
# These are the nist ANOVA files. They can be found at:
# https://www.itl.nist.gov/div898/strd/anova/anova.html
filenames = ['SiRstv.dat', 'SmLs01.dat', 'SmLs02.dat', 'SmLs03.dat',
'AtmWtAg.dat', 'SmLs04.dat', 'SmLs05.dat', 'SmLs06.dat',
'SmLs07.dat', 'SmLs08.dat', 'SmLs09.dat']
for test_case in filenames:
rtol = 1e-7
fname = os.path.abspath(os.path.join(os.path.dirname(__file__),
'data/nist_anova', test_case))
with open(fname) as f:
content = f.read().split('\n')
certified = [line.split() for line in content[40:48]
if line.strip()]
dataf = np.loadtxt(fname, skiprows=60)
y, x = dataf.T
y = y.astype(int)
caty = np.unique(y)
f = float(certified[0][-1])
xlist = [x[y == i] for i in caty]
res = stats.f_oneway(*xlist)
# With the hard test cases we relax the tolerance a bit.
hard_tc = ('SmLs07.dat', 'SmLs08.dat', 'SmLs09.dat')
if test_case in hard_tc:
rtol = 1e-4
assert_allclose(res[0], f, rtol=rtol,
err_msg=f'Failing testcase: {test_case}')
@pytest.mark.parametrize("a, b, expected", [
(np.array([42, 42, 42]), np.array([7, 7, 7]), (np.inf, 0)),
(np.array([42, 42, 42]), np.array([42, 42, 42]), (np.nan, np.nan))
])
def test_constant_input(self, a, b, expected):
# For more details, look on https://github.com/scipy/scipy/issues/11669
msg = "Each of the input arrays is constant;"
with pytest.warns(stats.ConstantInputWarning, match=msg):
f, p = stats.f_oneway(a, b)
assert f, p == expected
@pytest.mark.parametrize('axis', [-2, -1, 0, 1])
def test_2d_inputs(self, axis):
a = np.array([[1, 4, 3, 3],
[2, 5, 3, 3],
[3, 6, 3, 3],
[2, 3, 3, 3],
[1, 4, 3, 3]])
b = np.array([[3, 1, 5, 3],
[4, 6, 5, 3],
[4, 3, 5, 3],
[1, 5, 5, 3],
[5, 5, 5, 3],
[2, 3, 5, 3],
[8, 2, 5, 3],
[2, 2, 5, 3]])
c = np.array([[4, 3, 4, 3],
[4, 2, 4, 3],
[5, 4, 4, 3],
[5, 4, 4, 3]])
if axis in [-1, 1]:
a = a.T
b = b.T
c = c.T
take_axis = 0
else:
take_axis = 1
warn_msg = "Each of the input arrays is constant;"
with pytest.warns(stats.ConstantInputWarning, match=warn_msg):
f, p = stats.f_oneway(a, b, c, axis=axis)
# Verify that the result computed with the 2d arrays matches
# the result of calling f_oneway individually on each slice.
for j in [0, 1]:
fj, pj = stats.f_oneway(np.take(a, j, take_axis),
np.take(b, j, take_axis),
np.take(c, j, take_axis))
assert_allclose(f[j], fj, rtol=1e-14)
assert_allclose(p[j], pj, rtol=1e-14)
for j in [2, 3]:
with pytest.warns(stats.ConstantInputWarning, match=warn_msg):
fj, pj = stats.f_oneway(np.take(a, j, take_axis),
np.take(b, j, take_axis),
np.take(c, j, take_axis))
assert_equal(f[j], fj)
assert_equal(p[j], pj)
def test_3d_inputs(self):
# Some 3-d arrays. (There is nothing special about the values.)
a = 1/np.arange(1.0, 4*5*7 + 1).reshape(4, 5, 7)
b = 2/np.arange(1.0, 4*8*7 + 1).reshape(4, 8, 7)
c = np.cos(1/np.arange(1.0, 4*4*7 + 1).reshape(4, 4, 7))
f, p = stats.f_oneway(a, b, c, axis=1)
assert f.shape == (4, 7)
assert p.shape == (4, 7)
for i in range(a.shape[0]):
for j in range(a.shape[2]):
fij, pij = stats.f_oneway(a[i, :, j], b[i, :, j], c[i, :, j])
assert_allclose(fij, f[i, j])
assert_allclose(pij, p[i, j])
def test_length0_1d_error(self):
# Require at least one value in each group.
with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
result = stats.f_oneway([1, 2, 3], [], [4, 5, 6, 7])
assert_equal(result, (np.nan, np.nan))
def test_length0_2d_error(self):
with pytest.warns(SmallSampleWarning, match=too_small_nd_not_omit):
ncols = 3
a = np.ones((4, ncols))
b = np.ones((0, ncols))
c = np.ones((5, ncols))
f, p = stats.f_oneway(a, b, c)
nans = np.full((ncols,), fill_value=np.nan)
assert_equal(f, nans)
assert_equal(p, nans)
def test_all_length_one(self):
with pytest.warns(SmallSampleWarning):
result = stats.f_oneway([10], [11], [12], [13])
assert_equal(result, (np.nan, np.nan))
@pytest.mark.parametrize('args', [(), ([1, 2, 3],)])
def test_too_few_inputs(self, args):
message = "At least two samples are required..."
with assert_raises(TypeError, match=message):
stats.f_oneway(*args)
def test_axis_error(self):
a = np.ones((3, 4))
b = np.ones((5, 4))
with assert_raises(AxisError):
stats.f_oneway(a, b, axis=2)
def test_bad_shapes(self):
a = np.ones((3, 4))
b = np.ones((5, 4))
with assert_raises(ValueError):
stats.f_oneway(a, b, axis=1)
class TestKruskal:
def test_simple(self):
x = [1]
y = [2]
h, p = stats.kruskal(x, y)
assert_equal(h, 1.0)
assert_approx_equal(p, stats.distributions.chi2.sf(h, 1))
h, p = stats.kruskal(np.array(x), np.array(y))
assert_equal(h, 1.0)
assert_approx_equal(p, stats.distributions.chi2.sf(h, 1))
def test_basic(self):
x = [1, 3, 5, 7, 9]
y = [2, 4, 6, 8, 10]
h, p = stats.kruskal(x, y)
assert_approx_equal(h, 3./11, significant=10)
assert_approx_equal(p, stats.distributions.chi2.sf(3./11, 1))
h, p = stats.kruskal(np.array(x), np.array(y))
assert_approx_equal(h, 3./11, significant=10)
assert_approx_equal(p, stats.distributions.chi2.sf(3./11, 1))
def test_simple_tie(self):
x = [1]
y = [1, 2]
h_uncorr = 1.5**2 + 2*2.25**2 - 12
corr = 0.75
expected = h_uncorr / corr # 0.5
h, p = stats.kruskal(x, y)
# Since the expression is simple and the exact answer is 0.5, it
# should be safe to use assert_equal().
assert_equal(h, expected)
def test_another_tie(self):
x = [1, 1, 1, 2]
y = [2, 2, 2, 2]
h_uncorr = (12. / 8. / 9.) * 4 * (3**2 + 6**2) - 3 * 9
corr = 1 - float(3**3 - 3 + 5**3 - 5) / (8**3 - 8)
expected = h_uncorr / corr
h, p = stats.kruskal(x, y)
assert_approx_equal(h, expected)
def test_three_groups(self):
# A test of stats.kruskal with three groups, with ties.
x = [1, 1, 1]
y = [2, 2, 2]
z = [2, 2]
h_uncorr = (12. / 8. / 9.) * (3*2**2 + 3*6**2 + 2*6**2) - 3 * 9 # 5.0
corr = 1 - float(3**3 - 3 + 5**3 - 5) / (8**3 - 8)
expected = h_uncorr / corr # 7.0
h, p = stats.kruskal(x, y, z)
assert_approx_equal(h, expected)
assert_approx_equal(p, stats.distributions.chi2.sf(h, 2))
def test_empty(self):
# A test of stats.kruskal with three groups, with ties.
x = [1, 1, 1]
y = [2, 2, 2]
z = []
with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
assert_equal(stats.kruskal(x, y, z), (np.nan, np.nan))
def test_kruskal_result_attributes(self):
x = [1, 3, 5, 7, 9]
y = [2, 4, 6, 8, 10]
res = stats.kruskal(x, y)
attributes = ('statistic', 'pvalue')
check_named_results(res, attributes)
def test_nan_policy(self):
x = np.arange(10.)
x[9] = np.nan
assert_equal(stats.kruskal(x, x), (np.nan, np.nan))
assert_almost_equal(stats.kruskal(x, x, nan_policy='omit'), (0.0, 1.0))
assert_raises(ValueError, stats.kruskal, x, x, nan_policy='raise')
assert_raises(ValueError, stats.kruskal, x, x, nan_policy='foobar')
def test_large_no_samples(self):
# Test to see if large samples are handled correctly.
n = 50000
x = np.random.randn(n)
y = np.random.randn(n) + 50
h, p = stats.kruskal(x, y)
expected = 0
assert_approx_equal(p, expected)
def test_no_args_gh20661(self):
message = r"Need at least two groups in stats.kruskal\(\)"
with pytest.raises(ValueError, match=message):
stats.kruskal()
class TestCombinePvalues:
def test_fisher(self):
# Example taken from https://en.wikipedia.org/wiki/Fisher%27s_exact_test#Example
xsq, p = stats.combine_pvalues([.01, .2, .3], method='fisher')
assert_approx_equal(p, 0.02156, significant=4)
def test_stouffer(self):
Z, p = stats.combine_pvalues([.01, .2, .3], method='stouffer')
assert_approx_equal(p, 0.01651, significant=4)
def test_stouffer2(self):
Z, p = stats.combine_pvalues([.5, .5, .5], method='stouffer')
assert_approx_equal(p, 0.5, significant=4)
def test_weighted_stouffer(self):
Z, p = stats.combine_pvalues([.01, .2, .3], method='stouffer',
weights=np.ones(3))
assert_approx_equal(p, 0.01651, significant=4)
def test_weighted_stouffer2(self):
Z, p = stats.combine_pvalues([.01, .2, .3], method='stouffer',
weights=np.array((1, 4, 9)))
assert_approx_equal(p, 0.1464, significant=4)
def test_pearson(self):
Z, p = stats.combine_pvalues([.01, .2, .3], method='pearson')
assert_approx_equal(p, 0.02213, significant=4)
def test_tippett(self):
Z, p = stats.combine_pvalues([.01, .2, .3], method='tippett')
assert_approx_equal(p, 0.0297, significant=4)
def test_mudholkar_george(self):
Z, p = stats.combine_pvalues([.1, .1, .1], method='mudholkar_george')
assert_approx_equal(p, 0.019462, significant=4)
def test_mudholkar_george_equal_fisher_pearson_average(self):
Z, p = stats.combine_pvalues([.01, .2, .3], method='mudholkar_george')
Z_f, p_f = stats.combine_pvalues([.01, .2, .3], method='fisher')
Z_p, p_p = stats.combine_pvalues([.01, .2, .3], method='pearson')
assert_approx_equal(0.5 * (Z_f+Z_p), Z, significant=4)
methods = ["fisher", "pearson", "tippett", "stouffer", "mudholkar_george"]
@pytest.mark.parametrize("variant", ["single", "all", "random"])
@pytest.mark.parametrize("method", methods)
def test_monotonicity(self, variant, method):
# Test that result increases monotonically with respect to input.
m, n = 10, 7
rng = np.random.default_rng(278448169958891062669391462690811630763)
# `pvaluess` is an m × n array of p values. Each row corresponds to
# a set of p values to be combined with p values increasing
# monotonically down one column (single), simultaneously down each
# column (all), or independently down each column (random).
if variant == "single":
pvaluess = np.full((m, n), rng.random(n))
pvaluess[:, 0] = np.linspace(0.1, 0.9, m)
elif variant == "all":
pvaluess = np.full((n, m), np.linspace(0.1, 0.9, m)).T
elif variant == "random":
pvaluess = np.sort(rng.uniform(0, 1, size=(m, n)), axis=0)
combined_pvalues = [
stats.combine_pvalues(pvalues, method=method)[1]
for pvalues in pvaluess
]
assert np.all(np.diff(combined_pvalues) >= 0)
@pytest.mark.parametrize("method", methods)
def test_result(self, method):
res = stats.combine_pvalues([.01, .2, .3], method=method)
assert_equal((res.statistic, res.pvalue), res)
class TestCdfDistanceValidation:
"""
Test that _cdf_distance() (via wasserstein_distance()) raises ValueErrors
for bad inputs.
"""
def test_distinct_value_and_weight_lengths(self):
# When the number of weights does not match the number of values,
# a ValueError should be raised.
assert_raises(ValueError, stats.wasserstein_distance,
[1], [2], [4], [3, 1])
assert_raises(ValueError, stats.wasserstein_distance, [1], [2], [1, 0])
def test_zero_weight(self):
# When a distribution is given zero weight, a ValueError should be
# raised.
assert_raises(ValueError, stats.wasserstein_distance,
[0, 1], [2], [0, 0])
assert_raises(ValueError, stats.wasserstein_distance,
[0, 1], [2], [3, 1], [0])
def test_negative_weights(self):
# A ValueError should be raised if there are any negative weights.
assert_raises(ValueError, stats.wasserstein_distance,
[0, 1], [2, 2], [1, 1], [3, -1])
def test_empty_distribution(self):
# A ValueError should be raised when trying to measure the distance
# between something and nothing.
assert_raises(ValueError, stats.wasserstein_distance, [], [2, 2])
assert_raises(ValueError, stats.wasserstein_distance, [1], [])
def test_inf_weight(self):
# An inf weight is not valid.
assert_raises(ValueError, stats.wasserstein_distance,
[1, 2, 1], [1, 1], [1, np.inf, 1], [1, 1])
class TestWassersteinDistanceND:
""" Tests for wasserstein_distance_nd() output values.
"""
def test_published_values(self):
# Compare against published values and manually computed results.
# The values and computed result are posted at James D. McCaffrey's blog,
# https://jamesmccaffrey.wordpress.com/2018/03/05/earth-mover-distance
# -wasserstein-metric-example-calculation/
u = [(1,1), (1,1), (1,1), (1,1), (1,1), (1,1), (1,1), (1,1), (1,1), (1,1),
(4,2), (6,1), (6,1)]
v = [(2,1), (2,1), (3,2), (3,2), (3,2), (5,1), (5,1), (5,1), (5,1), (5,1),
(5,1), (5,1), (7,1)]
res = stats.wasserstein_distance_nd(u, v)
# In original post, the author kept two decimal places for ease of calculation.
# This test uses the more precise value of distance to get the precise results.
# For comparison, please see the table and figure in the original blog post.
flow = np.array([2., 3., 5., 1., 1., 1.])
dist = np.array([1.00, 5**0.5, 4.00, 2**0.5, 1.00, 1.00])
ref = np.sum(flow * dist)/np.sum(flow)
assert_allclose(res, ref)
@pytest.mark.parametrize('n_value', (4, 15, 35))
@pytest.mark.parametrize('ndim', (3, 4, 7))
@pytest.mark.parametrize('max_repeats', (5, 10))
def test_same_distribution_nD(self, ndim, n_value, max_repeats):
# Any distribution moved to itself should have a Wasserstein distance
# of zero.
rng = np.random.default_rng(363836384995579937222333)
repeats = rng.integers(1, max_repeats, size=n_value, dtype=int)
u_values = rng.random(size=(n_value, ndim))
v_values = np.repeat(u_values, repeats, axis=0)
v_weights = rng.random(np.sum(repeats))
range_repeat = np.repeat(np.arange(len(repeats)), repeats)
u_weights = np.bincount(range_repeat, weights=v_weights)
index = rng.permutation(len(v_weights))
v_values, v_weights = v_values[index], v_weights[index]
res = stats.wasserstein_distance_nd(u_values, v_values, u_weights, v_weights)
assert_allclose(res, 0, atol=1e-15)
@pytest.mark.parametrize('nu', (8, 9, 38))
@pytest.mark.parametrize('nv', (8, 12, 17))
@pytest.mark.parametrize('ndim', (3, 5, 23))
def test_collapse_nD(self, nu, nv, ndim):
# test collapse for n dimensional values
# Collapsing a n-D distribution to a point distribution at zero
# is equivalent to taking the average of the norm of data.
rng = np.random.default_rng(38573488467338826109)
u_values = rng.random(size=(nu, ndim))
v_values = np.zeros((nv, ndim))
u_weights = rng.random(size=nu)
v_weights = rng.random(size=nv)
ref = np.average(np.linalg.norm(u_values, axis=1), weights=u_weights)
res = stats.wasserstein_distance_nd(u_values, v_values, u_weights, v_weights)
assert_allclose(res, ref)
@pytest.mark.parametrize('nu', (8, 16, 32))
@pytest.mark.parametrize('nv', (8, 16, 32))
@pytest.mark.parametrize('ndim', (1, 2, 6))
def test_zero_weight_nD(self, nu, nv, ndim):
# Values with zero weight have no impact on the Wasserstein distance.
rng = np.random.default_rng(38573488467338826109)
u_values = rng.random(size=(nu, ndim))
v_values = rng.random(size=(nv, ndim))
u_weights = rng.random(size=nu)
v_weights = rng.random(size=nv)
ref = stats.wasserstein_distance_nd(u_values, v_values, u_weights, v_weights)
add_row, nrows = rng.integers(0, nu, size=2)
add_value = rng.random(size=(nrows, ndim))
u_values = np.insert(u_values, add_row, add_value, axis=0)
u_weights = np.insert(u_weights, add_row, np.zeros(nrows), axis=0)
res = stats.wasserstein_distance_nd(u_values, v_values, u_weights, v_weights)
assert_allclose(res, ref)
def test_inf_values(self):
# Inf values can lead to an inf distance or trigger a RuntimeWarning
# (and return NaN) if the distance is undefined.
uv, vv, uw = [[1, 1], [2, 1]], [[np.inf, -np.inf]], [1, 1]
distance = stats.wasserstein_distance_nd(uv, vv, uw)
assert_equal(distance, np.inf)
with np.errstate(invalid='ignore'):
uv, vv = [[np.inf, np.inf]], [[np.inf, -np.inf]]
distance = stats.wasserstein_distance_nd(uv, vv)
assert_equal(distance, np.nan)
@pytest.mark.parametrize('nu', (10, 15, 20))
@pytest.mark.parametrize('nv', (10, 15, 20))
@pytest.mark.parametrize('ndim', (1, 3, 5))
def test_multi_dim_nD(self, nu, nv, ndim):
# Adding dimension on distributions do not affect the result
rng = np.random.default_rng(2736495738494849509)
u_values = rng.random(size=(nu, ndim))
v_values = rng.random(size=(nv, ndim))
u_weights = rng.random(size=nu)
v_weights = rng.random(size=nv)
ref = stats.wasserstein_distance_nd(u_values, v_values, u_weights, v_weights)
add_dim = rng.integers(0, ndim)
add_value = rng.random()
u_values = np.insert(u_values, add_dim, add_value, axis=1)
v_values = np.insert(v_values, add_dim, add_value, axis=1)
res = stats.wasserstein_distance_nd(u_values, v_values, u_weights, v_weights)
assert_allclose(res, ref)
@pytest.mark.parametrize('nu', (7, 13, 19))
@pytest.mark.parametrize('nv', (7, 13, 19))
@pytest.mark.parametrize('ndim', (2, 4, 7))
def test_orthogonal_nD(self, nu, nv, ndim):
# orthogonal transformations do not affect the result of the
# wasserstein_distance
rng = np.random.default_rng(34746837464536)
u_values = rng.random(size=(nu, ndim))
v_values = rng.random(size=(nv, ndim))
u_weights = rng.random(size=nu)
v_weights = rng.random(size=nv)
ref = stats.wasserstein_distance_nd(u_values, v_values, u_weights, v_weights)
dist = stats.ortho_group(ndim)
transform = dist.rvs(random_state=rng)
shift = rng.random(size=ndim)
res = stats.wasserstein_distance_nd(u_values @ transform + shift,
v_values @ transform + shift,
u_weights, v_weights)
assert_allclose(res, ref)
def test_error_code(self):
rng = np.random.default_rng(52473644737485644836320101)
with pytest.raises(ValueError, match='Invalid input values. The inputs'):
u_values = rng.random(size=(4, 10, 15))
v_values = rng.random(size=(6, 2, 7))
_ = stats.wasserstein_distance_nd(u_values, v_values)
with pytest.raises(ValueError, match='Invalid input values. Dimensions'):
u_values = rng.random(size=(15,))
v_values = rng.random(size=(3, 15))
_ = stats.wasserstein_distance_nd(u_values, v_values)
with pytest.raises(ValueError,
match='Invalid input values. If two-dimensional'):
u_values = rng.random(size=(2, 10))
v_values = rng.random(size=(2, 2))
_ = stats.wasserstein_distance_nd(u_values, v_values)
@pytest.mark.parametrize('u_size', [1, 10, 50])
@pytest.mark.parametrize('v_size', [1, 10, 50])
def test_optimization_vs_analytical(self, u_size, v_size):
rng = np.random.default_rng(45634745675)
# Test when u_weights = None, v_weights = None
u_values = rng.random(size=(u_size, 1))
v_values = rng.random(size=(v_size, 1))
u_values_flat = u_values.ravel()
v_values_flat = v_values.ravel()
# These three calculations are done using different backends
# but they must be equal
d1 = stats.wasserstein_distance(u_values_flat, v_values_flat)
d2 = stats.wasserstein_distance_nd(u_values, v_values)
d3 = stats.wasserstein_distance_nd(u_values_flat, v_values_flat)
assert_allclose(d2, d1)
assert_allclose(d3, d1)
# Test with u_weights and v_weights specified.
u_weights = rng.random(size=u_size)
v_weights = rng.random(size=v_size)
d1 = stats.wasserstein_distance(u_values_flat, v_values_flat,
u_weights, v_weights)
d2 = stats.wasserstein_distance_nd(u_values, v_values,
u_weights, v_weights)
d3 = stats.wasserstein_distance_nd(u_values_flat, v_values_flat,
u_weights, v_weights)
assert_allclose(d2, d1)
assert_allclose(d3, d1)
class TestWassersteinDistance:
""" Tests for wasserstein_distance() output values.
"""
def test_simple(self):
# For basic distributions, the value of the Wasserstein distance is
# straightforward.
assert_allclose(
stats.wasserstein_distance([0, 1], [0], [1, 1], [1]),
.5)
assert_allclose(stats.wasserstein_distance(
[0, 1], [0], [3, 1], [1]),
.25)
assert_allclose(stats.wasserstein_distance(
[0, 2], [0], [1, 1], [1]),
1)
assert_allclose(stats.wasserstein_distance(
[0, 1, 2], [1, 2, 3]),
1)
def test_same_distribution(self):
# Any distribution moved to itself should have a Wasserstein distance
# of zero.
assert_equal(stats.wasserstein_distance([1, 2, 3], [2, 1, 3]), 0)
assert_equal(
stats.wasserstein_distance([1, 1, 1, 4], [4, 1],
[1, 1, 1, 1], [1, 3]),
0)
def test_shift(self):
# If the whole distribution is shifted by x, then the Wasserstein
# distance should be the norm of x.
assert_allclose(stats.wasserstein_distance([0], [1]), 1)
assert_allclose(stats.wasserstein_distance([-5], [5]), 10)
assert_allclose(
stats.wasserstein_distance([1, 2, 3, 4, 5], [11, 12, 13, 14, 15]),
10)
assert_allclose(
stats.wasserstein_distance([4.5, 6.7, 2.1], [4.6, 7, 9.2],
[3, 1, 1], [1, 3, 1]),
2.5)
def test_combine_weights(self):
# Assigning a weight w to a value is equivalent to including that value
# w times in the value array with weight of 1.
assert_allclose(
stats.wasserstein_distance(
[0, 0, 1, 1, 1, 1, 5], [0, 3, 3, 3, 3, 4, 4],
[1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1]),
stats.wasserstein_distance([5, 0, 1], [0, 4, 3],
[1, 2, 4], [1, 2, 4]))
def test_collapse(self):
# Collapsing a distribution to a point distribution at zero is
# equivalent to taking the average of the absolute values of the
# values.
u = np.arange(-10, 30, 0.3)
v = np.zeros_like(u)
assert_allclose(
stats.wasserstein_distance(u, v),
np.mean(np.abs(u)))
u_weights = np.arange(len(u))
v_weights = u_weights[::-1]
assert_allclose(
stats.wasserstein_distance(u, v, u_weights, v_weights),
np.average(np.abs(u), weights=u_weights))
def test_zero_weight(self):
# Values with zero weight have no impact on the Wasserstein distance.
assert_allclose(
stats.wasserstein_distance([1, 2, 100000], [1, 1],
[1, 1, 0], [1, 1]),
stats.wasserstein_distance([1, 2], [1, 1], [1, 1], [1, 1]))
def test_inf_values(self):
# Inf values can lead to an inf distance or trigger a RuntimeWarning
# (and return NaN) if the distance is undefined.
assert_equal(
stats.wasserstein_distance([1, 2, np.inf], [1, 1]),
np.inf)
assert_equal(
stats.wasserstein_distance([1, 2, np.inf], [-np.inf, 1]),
np.inf)
assert_equal(
stats.wasserstein_distance([1, -np.inf, np.inf], [1, 1]),
np.inf)
with suppress_warnings() as sup:
sup.record(RuntimeWarning, "invalid value*")
assert_equal(
stats.wasserstein_distance([1, 2, np.inf], [np.inf, 1]),
np.nan)
class TestEnergyDistance:
""" Tests for energy_distance() output values.
"""
def test_simple(self):
# For basic distributions, the value of the energy distance is
# straightforward.
assert_almost_equal(
stats.energy_distance([0, 1], [0], [1, 1], [1]),
np.sqrt(2) * .5)
assert_almost_equal(stats.energy_distance(
[0, 1], [0], [3, 1], [1]),
np.sqrt(2) * .25)
assert_almost_equal(stats.energy_distance(
[0, 2], [0], [1, 1], [1]),
2 * .5)
assert_almost_equal(
stats.energy_distance([0, 1, 2], [1, 2, 3]),
np.sqrt(2) * (3*(1./3**2))**.5)
def test_same_distribution(self):
# Any distribution moved to itself should have a energy distance of
# zero.
assert_equal(stats.energy_distance([1, 2, 3], [2, 1, 3]), 0)
assert_equal(
stats.energy_distance([1, 1, 1, 4], [4, 1], [1, 1, 1, 1], [1, 3]),
0)
def test_shift(self):
# If a single-point distribution is shifted by x, then the energy
# distance should be sqrt(2) * sqrt(x).
assert_almost_equal(stats.energy_distance([0], [1]), np.sqrt(2))
assert_almost_equal(
stats.energy_distance([-5], [5]),
np.sqrt(2) * 10**.5)
def test_combine_weights(self):
# Assigning a weight w to a value is equivalent to including that value
# w times in the value array with weight of 1.
assert_almost_equal(
stats.energy_distance([0, 0, 1, 1, 1, 1, 5], [0, 3, 3, 3, 3, 4, 4],
[1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1]),
stats.energy_distance([5, 0, 1], [0, 4, 3], [1, 2, 4], [1, 2, 4]))
def test_zero_weight(self):
# Values with zero weight have no impact on the energy distance.
assert_almost_equal(
stats.energy_distance([1, 2, 100000], [1, 1], [1, 1, 0], [1, 1]),
stats.energy_distance([1, 2], [1, 1], [1, 1], [1, 1]))
def test_inf_values(self):
# Inf values can lead to an inf distance or trigger a RuntimeWarning
# (and return NaN) if the distance is undefined.
assert_equal(stats.energy_distance([1, 2, np.inf], [1, 1]), np.inf)
assert_equal(
stats.energy_distance([1, 2, np.inf], [-np.inf, 1]),
np.inf)
assert_equal(
stats.energy_distance([1, -np.inf, np.inf], [1, 1]),
np.inf)
with suppress_warnings() as sup:
sup.record(RuntimeWarning, "invalid value*")
assert_equal(
stats.energy_distance([1, 2, np.inf], [np.inf, 1]),
np.nan)
class TestBrunnerMunzel:
# Data from (Lumley, 1996)
X = [1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 1, 1]
Y = [3, 3, 4, 3, 1, 2, 3, 1, 1, 5, 4]
significant = 13
def test_brunnermunzel_one_sided(self):
# Results are compared with R's lawstat package.
u1, p1 = stats.brunnermunzel(self.X, self.Y, alternative='less')
u2, p2 = stats.brunnermunzel(self.Y, self.X, alternative='greater')
u3, p3 = stats.brunnermunzel(self.X, self.Y, alternative='greater')
u4, p4 = stats.brunnermunzel(self.Y, self.X, alternative='less')
assert_approx_equal(p1, p2, significant=self.significant)
assert_approx_equal(p3, p4, significant=self.significant)
assert_(p1 != p3)
assert_approx_equal(u1, 3.1374674823029505,
significant=self.significant)
assert_approx_equal(u2, -3.1374674823029505,
significant=self.significant)
assert_approx_equal(u3, 3.1374674823029505,
significant=self.significant)
assert_approx_equal(u4, -3.1374674823029505,
significant=self.significant)
assert_approx_equal(p1, 0.0028931043330757342,
significant=self.significant)
assert_approx_equal(p3, 0.99710689566692423,
significant=self.significant)
def test_brunnermunzel_two_sided(self):
# Results are compared with R's lawstat package.
u1, p1 = stats.brunnermunzel(self.X, self.Y, alternative='two-sided')
u2, p2 = stats.brunnermunzel(self.Y, self.X, alternative='two-sided')
assert_approx_equal(p1, p2, significant=self.significant)
assert_approx_equal(u1, 3.1374674823029505,
significant=self.significant)
assert_approx_equal(u2, -3.1374674823029505,
significant=self.significant)
assert_approx_equal(p1, 0.0057862086661515377,
significant=self.significant)
def test_brunnermunzel_default(self):
# The default value for alternative is two-sided
u1, p1 = stats.brunnermunzel(self.X, self.Y)
u2, p2 = stats.brunnermunzel(self.Y, self.X)
assert_approx_equal(p1, p2, significant=self.significant)
assert_approx_equal(u1, 3.1374674823029505,
significant=self.significant)
assert_approx_equal(u2, -3.1374674823029505,
significant=self.significant)
assert_approx_equal(p1, 0.0057862086661515377,
significant=self.significant)
def test_brunnermunzel_alternative_error(self):
alternative = "error"
distribution = "t"
nan_policy = "propagate"
assert_(alternative not in ["two-sided", "greater", "less"])
assert_raises(ValueError,
stats.brunnermunzel,
self.X,
self.Y,
alternative,
distribution,
nan_policy)
def test_brunnermunzel_distribution_norm(self):
u1, p1 = stats.brunnermunzel(self.X, self.Y, distribution="normal")
u2, p2 = stats.brunnermunzel(self.Y, self.X, distribution="normal")
assert_approx_equal(p1, p2, significant=self.significant)
assert_approx_equal(u1, 3.1374674823029505,
significant=self.significant)
assert_approx_equal(u2, -3.1374674823029505,
significant=self.significant)
assert_approx_equal(p1, 0.0017041417600383024,
significant=self.significant)
def test_brunnermunzel_distribution_error(self):
alternative = "two-sided"
distribution = "error"
nan_policy = "propagate"
assert_(alternative not in ["t", "normal"])
assert_raises(ValueError,
stats.brunnermunzel,
self.X,
self.Y,
alternative,
distribution,
nan_policy)
@pytest.mark.parametrize("kwarg_update", [{'y': []}, {'x': []},
{'x': [], 'y': []}])
def test_brunnermunzel_empty_imput(self, kwarg_update):
kwargs = {'x': self.X, 'y': self.Y}
kwargs.update(kwarg_update)
with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
statistic, pvalue = stats.brunnermunzel(**kwargs)
assert_equal(statistic, np.nan)
assert_equal(pvalue, np.nan)
def test_brunnermunzel_nan_input_propagate(self):
X = [1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 1, 1, np.nan]
Y = [3, 3, 4, 3, 1, 2, 3, 1, 1, 5, 4]
u1, p1 = stats.brunnermunzel(X, Y, nan_policy="propagate")
u2, p2 = stats.brunnermunzel(Y, X, nan_policy="propagate")
assert_equal(u1, np.nan)
assert_equal(p1, np.nan)
assert_equal(u2, np.nan)
assert_equal(p2, np.nan)
def test_brunnermunzel_nan_input_raise(self):
X = [1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 1, 1, np.nan]
Y = [3, 3, 4, 3, 1, 2, 3, 1, 1, 5, 4]
alternative = "two-sided"
distribution = "t"
nan_policy = "raise"
assert_raises(ValueError,
stats.brunnermunzel,
X,
Y,
alternative,
distribution,
nan_policy)
assert_raises(ValueError,
stats.brunnermunzel,
Y,
X,
alternative,
distribution,
nan_policy)
def test_brunnermunzel_nan_input_omit(self):
X = [1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 1, 1, np.nan]
Y = [3, 3, 4, 3, 1, 2, 3, 1, 1, 5, 4]
u1, p1 = stats.brunnermunzel(X, Y, nan_policy="omit")
u2, p2 = stats.brunnermunzel(Y, X, nan_policy="omit")
assert_approx_equal(p1, p2, significant=self.significant)
assert_approx_equal(u1, 3.1374674823029505,
significant=self.significant)
assert_approx_equal(u2, -3.1374674823029505,
significant=self.significant)
assert_approx_equal(p1, 0.0057862086661515377,
significant=self.significant)
def test_brunnermunzel_return_nan(self):
""" tests that a warning is emitted when p is nan
p-value with t-distributions can be nan (0/0) (see gh-15843)
"""
x = [1, 2, 3]
y = [5, 6, 7, 8, 9]
msg = "p-value cannot be estimated|divide by zero|invalid value encountered"
with pytest.warns(RuntimeWarning, match=msg):
stats.brunnermunzel(x, y, distribution="t")
def test_brunnermunzel_normal_dist(self):
""" tests that a p is 0 for datasets that cause p->nan
when t-distribution is used (see gh-15843)
"""
x = [1, 2, 3]
y = [5, 6, 7, 8, 9]
with pytest.warns(RuntimeWarning, match='divide by zero'):
_, p = stats.brunnermunzel(x, y, distribution="normal")
assert_equal(p, 0)
class TestRatioUniforms:
""" Tests for rvs_ratio_uniforms are in test_sampling.py,
as rvs_ratio_uniforms is deprecated and moved to stats.sampling """
def test_consistency(self):
f = stats.norm.pdf
v = np.sqrt(f(np.sqrt(2))) * np.sqrt(2)
umax = np.sqrt(f(0))
gen = stats.sampling.RatioUniforms(f, umax=umax, vmin=-v, vmax=v,
random_state=12345)
r1 = gen.rvs(10)
deprecation_msg = ("Please use `RatioUniforms` from the "
"`scipy.stats.sampling` namespace.")
with pytest.warns(DeprecationWarning, match=deprecation_msg):
r2 = stats.rvs_ratio_uniforms(f, umax, -v, v, size=10,
random_state=12345)
assert_equal(r1, r2)
class TestQuantileTest:
r""" Test the non-parametric quantile test,
including the computation of confidence intervals
"""
def test_quantile_test_iv(self):
x = [1, 2, 3]
message = "`x` must be a one-dimensional array of numbers."
with pytest.raises(ValueError, match=message):
stats.quantile_test([x])
message = "`q` must be a scalar."
with pytest.raises(ValueError, match=message):
stats.quantile_test(x, q=[1, 2])
message = "`p` must be a float strictly between 0 and 1."
with pytest.raises(ValueError, match=message):
stats.quantile_test(x, p=[0.5, 0.75])
with pytest.raises(ValueError, match=message):
stats.quantile_test(x, p=2)
with pytest.raises(ValueError, match=message):
stats.quantile_test(x, p=-0.5)
message = "`alternative` must be one of..."
with pytest.raises(ValueError, match=message):
stats.quantile_test(x, alternative='one-sided')
message = "`confidence_level` must be a number between 0 and 1."
with pytest.raises(ValueError, match=message):
stats.quantile_test(x).confidence_interval(1)
@pytest.mark.parametrize(
'p, alpha, lb, ub, alternative',
[[0.3, 0.95, 1.221402758160170, 1.476980793882643, 'two-sided'],
[0.5, 0.9, 1.506817785112854, 1.803988415397857, 'two-sided'],
[0.25, 0.95, -np.inf, 1.39096812846378, 'less'],
[0.8, 0.9, 2.117000016612675, np.inf, 'greater']]
)
def test_R_ci_quantile(self, p, alpha, lb, ub, alternative):
# Test against R library `confintr` function `ci_quantile`, e.g.
# library(confintr)
# options(digits=16)
# x <- exp(seq(0, 1, by = 0.01))
# ci_quantile(x, q = 0.3)$interval
# ci_quantile(x, q = 0.5, probs = c(0.05, 0.95))$interval
# ci_quantile(x, q = 0.25, probs = c(0, 0.95))$interval
# ci_quantile(x, q = 0.8, probs = c(0.1, 1))$interval
x = np.exp(np.arange(0, 1.01, 0.01))
res = stats.quantile_test(x, p=p, alternative=alternative)
assert_allclose(res.confidence_interval(alpha), [lb, ub], rtol=1e-15)
@pytest.mark.parametrize(
'q, p, alternative, ref',
[[1.2, 0.3, 'two-sided', 0.01515567517648],
[1.8, 0.5, 'two-sided', 0.1109183496606]]
)
def test_R_pvalue(self, q, p, alternative, ref):
# Test against R library `snpar` function `quant.test`, e.g.
# library(snpar)
# options(digits=16)
# x < - exp(seq(0, 1, by=0.01))
# quant.test(x, q=1.2, p=0.3, exact=TRUE, alternative='t')
x = np.exp(np.arange(0, 1.01, 0.01))
res = stats.quantile_test(x, q=q, p=p, alternative=alternative)
assert_allclose(res.pvalue, ref, rtol=1e-12)
@pytest.mark.parametrize('case', ['continuous', 'discrete'])
@pytest.mark.parametrize('alternative', ['less', 'greater'])
@pytest.mark.parametrize('alpha', [0.9, 0.95])
def test_pval_ci_match(self, case, alternative, alpha):
# Verify that the following statement holds:
# The 95% confidence interval corresponding with alternative='less'
# has -inf as its lower bound, and the upper bound `xu` is the greatest
# element from the sample `x` such that:
# `stats.quantile_test(x, q=xu, p=p, alternative='less').pvalue``
# will be greater than 5%.
# And the corresponding statement for the alternative='greater' case.
seed = int((7**len(case) + len(alternative))*alpha)
rng = np.random.default_rng(seed)
if case == 'continuous':
p, q = rng.random(size=2)
rvs = rng.random(size=100)
else:
rvs = rng.integers(1, 11, size=100)
p = rng.random()
q = rng.integers(1, 11)
res = stats.quantile_test(rvs, q=q, p=p, alternative=alternative)
ci = res.confidence_interval(confidence_level=alpha)
# select elements inside the confidence interval based on alternative
if alternative == 'less':
i_inside = rvs <= ci.high
else:
i_inside = rvs >= ci.low
for x in rvs[i_inside]:
res = stats.quantile_test(rvs, q=x, p=p, alternative=alternative)
assert res.pvalue > 1 - alpha
for x in rvs[~i_inside]:
res = stats.quantile_test(rvs, q=x, p=p, alternative=alternative)
assert res.pvalue < 1 - alpha
def test_match_conover_examples(self):
# Test against the examples in [1] (Conover Practical Nonparametric
# Statistics Third Edition) pg 139
# Example 1
# Data is [189, 233, 195, 160, 212, 176, 231, 185, 199, 213, 202, 193,
# 174, 166, 248]
# Two-sided test of whether the upper quartile (p=0.75) equals 193
# (q=193). Conover shows that 7 of the observations are less than or
# equal to 193, and "for the binomial random variable Y, P(Y<=7) =
# 0.0173", so the two-sided p-value is twice that, 0.0346.
x = [189, 233, 195, 160, 212, 176, 231, 185, 199, 213, 202, 193,
174, 166, 248]
pvalue_expected = 0.0346
res = stats.quantile_test(x, q=193, p=0.75, alternative='two-sided')
assert_allclose(res.pvalue, pvalue_expected, rtol=1e-5)
# Example 2
# Conover doesn't give explicit data, just that 8 out of 112
# observations are 60 or less. The test is whether the median time is
# equal to 60 against the alternative that the median is greater than
# 60. The p-value is calculated as P(Y<=8), where Y is again a binomial
# distributed random variable, now with p=0.5 and n=112. Conover uses a
# normal approximation, but we can easily calculate the CDF of the
# binomial distribution.
x = [59]*8 + [61]*(112-8)
pvalue_expected = stats.binom(p=0.5, n=112).pmf(k=8)
res = stats.quantile_test(x, q=60, p=0.5, alternative='greater')
assert_allclose(res.pvalue, pvalue_expected, atol=1e-10)
class TestPageTrendTest:
# expected statistic and p-values generated using R at
# https://rdrr.io/cran/cultevo/, e.g.
# library(cultevo)
# data = rbind(c(72, 47, 73, 35, 47, 96, 30, 59, 41, 36, 56, 49, 81, 43,
# 70, 47, 28, 28, 62, 20, 61, 20, 80, 24, 50),
# c(68, 52, 60, 34, 44, 20, 65, 88, 21, 81, 48, 31, 31, 67,
# 69, 94, 30, 24, 40, 87, 70, 43, 50, 96, 43),
# c(81, 13, 85, 35, 79, 12, 92, 86, 21, 64, 16, 64, 68, 17,
# 16, 89, 71, 43, 43, 36, 54, 13, 66, 51, 55))
# result = page.test(data, verbose=FALSE)
# Most test cases generated to achieve common critical p-values so that
# results could be checked (to limited precision) against tables in
# scipy.stats.page_trend_test reference [1]
np.random.seed(0)
data_3_25 = np.random.rand(3, 25)
data_10_26 = np.random.rand(10, 26)
ts = [
(12805, 0.3886487053947608, False, 'asymptotic', data_3_25),
(49140, 0.02888978556179862, False, 'asymptotic', data_10_26),
(12332, 0.7722477197436702, False, 'asymptotic',
[[72, 47, 73, 35, 47, 96, 30, 59, 41, 36, 56, 49, 81,
43, 70, 47, 28, 28, 62, 20, 61, 20, 80, 24, 50],
[68, 52, 60, 34, 44, 20, 65, 88, 21, 81, 48, 31, 31,
67, 69, 94, 30, 24, 40, 87, 70, 43, 50, 96, 43],
[81, 13, 85, 35, 79, 12, 92, 86, 21, 64, 16, 64, 68,
17, 16, 89, 71, 43, 43, 36, 54, 13, 66, 51, 55]]),
(266, 4.121656378600823e-05, False, 'exact',
[[1.5, 4., 8.3, 5, 19, 11],
[5, 4, 3.5, 10, 20, 21],
[8.4, 3.2, 10, 12, 14, 15]]),
(332, 0.9566400920502488, True, 'exact',
[[4, 3, 2, 1], [4, 3, 2, 1], [4, 3, 2, 1], [4, 3, 2, 1],
[4, 3, 2, 1], [4, 3, 2, 1], [4, 3, 2, 1], [4, 3, 2, 1],
[3, 4, 1, 2], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4],
[1, 2, 3, 4], [1, 2, 3, 4]]),
(241, 0.9622210164861476, True, 'exact',
[[3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1],
[3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1],
[3, 2, 1], [2, 1, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3],
[1, 2, 3], [1, 2, 3], [1, 2, 3]]),
(197, 0.9619432897162209, True, 'exact',
[[6, 5, 4, 3, 2, 1], [6, 5, 4, 3, 2, 1], [1, 3, 4, 5, 2, 6]]),
(423, 0.9590458306880073, True, 'exact',
[[5, 4, 3, 2, 1], [5, 4, 3, 2, 1], [5, 4, 3, 2, 1],
[5, 4, 3, 2, 1], [5, 4, 3, 2, 1], [5, 4, 3, 2, 1],
[4, 1, 3, 2, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5],
[1, 2, 3, 4, 5]]),
(217, 0.9693058575034678, True, 'exact',
[[3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1],
[3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1],
[2, 1, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3],
[1, 2, 3]]),
(395, 0.991530289351305, True, 'exact',
[[7, 6, 5, 4, 3, 2, 1], [7, 6, 5, 4, 3, 2, 1],
[6, 5, 7, 4, 3, 2, 1], [1, 2, 3, 4, 5, 6, 7]]),
(117, 0.9997817843373017, True, 'exact',
[[3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1],
[3, 2, 1], [3, 2, 1], [3, 2, 1], [2, 1, 3], [1, 2, 3]]),
]
@pytest.mark.parametrize("L, p, ranked, method, data", ts)
def test_accuracy(self, L, p, ranked, method, data):
np.random.seed(42)
res = stats.page_trend_test(data, ranked=ranked, method=method)
assert_equal(L, res.statistic)
assert_allclose(p, res.pvalue)
assert_equal(method, res.method)
ts2 = [
(542, 0.9481266260876332, True, 'exact',
[[10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
[1, 8, 4, 7, 6, 5, 9, 3, 2, 10]]),
(1322, 0.9993113928199309, True, 'exact',
[[10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [9, 2, 8, 7, 6, 5, 4, 3, 10, 1],
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]),
(2286, 0.9908688345484833, True, 'exact',
[[8, 7, 6, 5, 4, 3, 2, 1], [8, 7, 6, 5, 4, 3, 2, 1],
[8, 7, 6, 5, 4, 3, 2, 1], [8, 7, 6, 5, 4, 3, 2, 1],
[8, 7, 6, 5, 4, 3, 2, 1], [8, 7, 6, 5, 4, 3, 2, 1],
[8, 7, 6, 5, 4, 3, 2, 1], [8, 7, 6, 5, 4, 3, 2, 1],
[8, 7, 6, 5, 4, 3, 2, 1], [1, 3, 5, 6, 4, 7, 2, 8],
[1, 2, 3, 4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7, 8],
[1, 2, 3, 4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7, 8],
[1, 2, 3, 4, 5, 6, 7, 8]]),
]
# only the first of these appears slow because intermediate data are
# cached and used on the rest
@pytest.mark.parametrize("L, p, ranked, method, data", ts)
@pytest.mark.slow()
def test_accuracy2(self, L, p, ranked, method, data):
np.random.seed(42)
res = stats.page_trend_test(data, ranked=ranked, method=method)
assert_equal(L, res.statistic)
assert_allclose(p, res.pvalue)
assert_equal(method, res.method)
def test_options(self):
np.random.seed(42)
m, n = 10, 20
predicted_ranks = np.arange(1, n+1)
perm = np.random.permutation(np.arange(n))
data = np.random.rand(m, n)
ranks = stats.rankdata(data, axis=1)
res1 = stats.page_trend_test(ranks)
res2 = stats.page_trend_test(ranks, ranked=True)
res3 = stats.page_trend_test(data, ranked=False)
res4 = stats.page_trend_test(ranks, predicted_ranks=predicted_ranks)
res5 = stats.page_trend_test(ranks[:, perm],
predicted_ranks=predicted_ranks[perm])
assert_equal(res1.statistic, res2.statistic)
assert_equal(res1.statistic, res3.statistic)
assert_equal(res1.statistic, res4.statistic)
assert_equal(res1.statistic, res5.statistic)
def test_Ames_assay(self):
# test from _page_trend_test.py [2] page 151; data on page 144
np.random.seed(42)
data = [[101, 117, 111], [91, 90, 107], [103, 133, 121],
[136, 140, 144], [190, 161, 201], [146, 120, 116]]
data = np.array(data).T
predicted_ranks = np.arange(1, 7)
res = stats.page_trend_test(data, ranked=False,
predicted_ranks=predicted_ranks,
method="asymptotic")
assert_equal(res.statistic, 257)
assert_almost_equal(res.pvalue, 0.0035, decimal=4)
res = stats.page_trend_test(data, ranked=False,
predicted_ranks=predicted_ranks,
method="exact")
assert_equal(res.statistic, 257)
assert_almost_equal(res.pvalue, 0.0023, decimal=4)
def test_input_validation(self):
# test data not a 2d array
with assert_raises(ValueError, match="`data` must be a 2d array."):
stats.page_trend_test(None)
with assert_raises(ValueError, match="`data` must be a 2d array."):
stats.page_trend_test([])
with assert_raises(ValueError, match="`data` must be a 2d array."):
stats.page_trend_test([1, 2])
with assert_raises(ValueError, match="`data` must be a 2d array."):
stats.page_trend_test([[[1]]])
# test invalid dimensions
with assert_raises(ValueError, match="Page's L is only appropriate"):
stats.page_trend_test(np.random.rand(1, 3))
with assert_raises(ValueError, match="Page's L is only appropriate"):
stats.page_trend_test(np.random.rand(2, 2))
# predicted ranks must include each integer [1, 2, 3] exactly once
message = "`predicted_ranks` must include each integer"
with assert_raises(ValueError, match=message):
stats.page_trend_test(data=[[1, 2, 3], [1, 2, 3]],
predicted_ranks=[0, 1, 2])
with assert_raises(ValueError, match=message):
stats.page_trend_test(data=[[1, 2, 3], [1, 2, 3]],
predicted_ranks=[1.1, 2, 3])
with assert_raises(ValueError, match=message):
stats.page_trend_test(data=[[1, 2, 3], [1, 2, 3]],
predicted_ranks=[1, 2, 3, 3])
with assert_raises(ValueError, match=message):
stats.page_trend_test(data=[[1, 2, 3], [1, 2, 3]],
predicted_ranks="invalid")
# test improperly ranked data
with assert_raises(ValueError, match="`data` is not properly ranked"):
stats.page_trend_test([[0, 2, 3], [1, 2, 3]], True)
with assert_raises(ValueError, match="`data` is not properly ranked"):
stats.page_trend_test([[1, 2, 3], [1, 2, 4]], True)
# various
with assert_raises(ValueError, match="`data` contains NaNs"):
stats.page_trend_test([[1, 2, 3], [1, 2, np.nan]],
ranked=False)
with assert_raises(ValueError, match="`method` must be in"):
stats.page_trend_test(data=[[1, 2, 3], [1, 2, 3]],
method="ekki")
with assert_raises(TypeError, match="`ranked` must be boolean."):
stats.page_trend_test(data=[[1, 2, 3], [1, 2, 3]],
ranked="ekki")
rng = np.random.default_rng(902340982)
x = rng.random(10)
y = rng.random(10)
@pytest.mark.parametrize("fun, args",
[(stats.wilcoxon, (x,)),
(stats.ks_1samp, (x, stats.norm.cdf)), # type: ignore[attr-defined] # noqa: E501
(stats.ks_2samp, (x, y)),
(stats.kstest, (x, y)),
])
def test_rename_mode_method(fun, args):
res = fun(*args, method='exact')
res2 = fun(*args, mode='exact')
assert_equal(res, res2)
err = rf"{fun.__name__}() got multiple values for argument"
with pytest.raises(TypeError, match=re.escape(err)):
fun(*args, method='exact', mode='exact')
class TestExpectile:
def test_same_as_mean(self):
rng = np.random.default_rng(42)
x = rng.random(size=20)
assert_allclose(stats.expectile(x, alpha=0.5), np.mean(x))
def test_minimum(self):
rng = np.random.default_rng(42)
x = rng.random(size=20)
assert_allclose(stats.expectile(x, alpha=0), np.amin(x))
def test_maximum(self):
rng = np.random.default_rng(42)
x = rng.random(size=20)
assert_allclose(stats.expectile(x, alpha=1), np.amax(x))
def test_weights(self):
# expectile should minimize `fun` defined below; see
# F. Sobotka and T. Kneib, "Geoadditive expectile regression",
# Computational Statistics and Data Analysis 56 (2012) 755-767
# :doi:`10.1016/j.csda.2010.11.015`
rng = np.random.default_rng(1856392524598679138)
def fun(u, a, alpha, weights):
w = np.full_like(a, fill_value=alpha)
w[a <= u] = 1 - alpha
return np.sum(w * weights * (a - u)**2)
def expectile2(a, alpha, weights):
bracket = np.min(a), np.max(a)
return optimize.minimize_scalar(fun, bracket=bracket,
args=(a, alpha, weights)).x
n = 10
a = rng.random(n)
alpha = rng.random()
weights = rng.random(n)
res = stats.expectile(a, alpha, weights=weights)
ref = expectile2(a, alpha, weights)
assert_allclose(res, ref)
@pytest.mark.parametrize(
"alpha", [0.2, 0.5 - 1e-12, 0.5, 0.5 + 1e-12, 0.8]
)
@pytest.mark.parametrize("n", [20, 2000])
def test_expectile_properties(self, alpha, n):
"""
See Section 6 of
I. Steinwart, C. Pasin, R.C. Williamson & S. Zhang (2014).
"Elicitation and Identification of Properties". COLT.
http://proceedings.mlr.press/v35/steinwart14.html
and
Propositions 5, 6, 7 of
F. Bellini, B. Klar, and A. Müller and E. Rosazza Gianin (2013).
"Generalized Quantiles as Risk Measures"
http://doi.org/10.2139/ssrn.2225751
"""
rng = np.random.default_rng(42)
x = rng.normal(size=n)
# 0. definite / constancy
# Let T(X) denote the expectile of rv X ~ F.
# T(c) = c for constant c
for c in [-5, 0, 0.5]:
assert_allclose(
stats.expectile(np.full(shape=n, fill_value=c), alpha=alpha),
c
)
# 1. translation equivariance
# T(X + c) = T(X) + c
c = rng.exponential()
assert_allclose(
stats.expectile(x + c, alpha=alpha),
stats.expectile(x, alpha=alpha) + c,
)
assert_allclose(
stats.expectile(x - c, alpha=alpha),
stats.expectile(x, alpha=alpha) - c,
)
# 2. positively homogeneity
# T(cX) = c * T(X) for c > 0
assert_allclose(
stats.expectile(c * x, alpha=alpha),
c * stats.expectile(x, alpha=alpha),
)
# 3. subadditivity
# Note that subadditivity holds for alpha >= 0.5.
# T(X + Y) <= T(X) + T(Y)
# For alpha = 0.5, i.e. the mean, strict equality holds.
# For alpha < 0.5, one can use property 6. to show
# T(X + Y) >= T(X) + T(Y)
y = rng.logistic(size=n, loc=10) # different distribution than x
if alpha == 0.5:
def assert_op(a, b):
assert_allclose(a, b)
elif alpha > 0.5:
def assert_op(a, b):
assert a < b
else:
def assert_op(a, b):
assert a > b
assert_op(
stats.expectile(np.r_[x + y], alpha=alpha),
stats.expectile(x, alpha=alpha)
+ stats.expectile(y, alpha=alpha)
)
# 4. monotonicity
# This holds for first order stochastic dominance X:
# X >= Y whenever P(X <= x) < P(Y <= x)
# T(X) <= T(Y) whenever X <= Y
y = rng.normal(size=n, loc=5)
assert (
stats.expectile(x, alpha=alpha) <= stats.expectile(y, alpha=alpha)
)
# 5. convexity for alpha > 0.5, concavity for alpha < 0.5
# convexity is
# T((1 - c) X + c Y) <= (1 - c) T(X) + c T(Y) for 0 <= c <= 1
y = rng.logistic(size=n, loc=10)
for c in [0.1, 0.5, 0.8]:
assert_op(
stats.expectile((1-c)*x + c*y, alpha=alpha),
(1-c) * stats.expectile(x, alpha=alpha) +
c * stats.expectile(y, alpha=alpha)
)
# 6. negative argument
# T_{alpha}(-X) = -T_{1-alpha}(X)
assert_allclose(
stats.expectile(-x, alpha=alpha),
-stats.expectile(x, alpha=1-alpha),
)
@pytest.mark.parametrize("n", [20, 2000])
def test_monotonicity_in_alpha(self, n):
rng = np.random.default_rng(42)
x = rng.pareto(a=2, size=n)
e_list = []
alpha_seq = np.logspace(-15, np.log10(0.5), 100)
# sorted list of unique alpha values in interval (0, 1)
for alpha in np.r_[0, alpha_seq, 1 - alpha_seq[:-1:-1], 1]:
e_list.append(stats.expectile(x, alpha=alpha))
assert np.all(np.diff(e_list) > 0)
@array_api_compatible
def test_chk_asarray(xp):
rng = np.random.default_rng(2348923425434)
x0 = rng.random(size=(2, 3, 4))
x = xp.asarray(x0)
axis = 1
x_out, axis_out = _chk_asarray(x, axis=axis, xp=xp)
xp_assert_equal(x_out, xp.asarray(x0))
assert_equal(axis_out, axis)
axis = None
x_out, axis_out = _chk_asarray(x, axis=axis, xp=xp)
xp_assert_equal(x_out, xp.asarray(x0.ravel()))
assert_equal(axis_out, 0)
axis = 2
x_out, axis_out = _chk_asarray(x[0, 0, 0], axis=axis, xp=xp)
xp_assert_equal(x_out, xp.asarray(np.atleast_1d(x0[0, 0, 0])))
assert_equal(axis_out, axis)
@pytest.mark.skip_xp_backends('numpy',
reasons=['These parameters *are* compatible with NumPy'])
@pytest.mark.usefixtures("skip_xp_backends")
@array_api_compatible
def test_axis_nan_policy_keepdims_nanpolicy(xp):
# this test does not need to be repeated for every function
# using the _axis_nan_policy decorator. The test is here
# rather than in `test_axis_nanpolicy.py` because there is
# no reason to run those tests on an array API CI job.
x = xp.asarray([1, 2, 3, 4])
message = "Use of `nan_policy` and `keepdims`..."
with pytest.raises(NotImplementedError, match=message):
stats.skew(x, nan_policy='omit')
with pytest.raises(NotImplementedError, match=message):
stats.skew(x, keepdims=True)