| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- import numpy as np
- from numpy.testing import assert_allclose, assert_equal
- import pytest
- from scipy.optimize._pava_pybind import pava
- from scipy.optimize import isotonic_regression
- class TestIsotonicRegression:
- @pytest.mark.parametrize(
- ("y", "w", "msg"),
- [
- ([[0, 1]], None,
- "array has incorrect number of dimensions: 2; expected 1"),
- ([0, 1], [[1, 2]],
- "Input arrays y and w must have one dimension of equal length"),
- ([0, 1], [1],
- "Input arrays y and w must have one dimension of equal length"),
- (1, [1, 2],
- "Input arrays y and w must have one dimension of equal length"),
- ([1, 2], 1,
- "Input arrays y and w must have one dimension of equal length"),
- ([0, 1], [0, 1],
- "Weights w must be strictly positive"),
- ]
- )
- def test_raise_error(self, y, w, msg):
- with pytest.raises(ValueError, match=msg):
- isotonic_regression(y=y, weights=w)
- def test_simple_pava(self):
- # Test case of Busing 2020
- # https://doi.org/10.18637/jss.v102.c01
- y = np.array([8, 4, 8, 2, 2, 0, 8], dtype=np.float64)
- w = np.ones_like(y)
- r = np.full(shape=y.shape[0] + 1, fill_value=-1, dtype=np.intp)
- pava(y, w, r)
- assert_allclose(y, [4, 4, 4, 4, 4, 4, 8])
- # Only first 2 elements of w are changed.
- assert_allclose(w, [6, 1, 1, 1, 1, 1, 1])
- # Only first 3 elements of r are changed.
- assert_allclose(r, [0, 6, 7, -1, -1, -1, -1, -1])
- @pytest.mark.parametrize("y_dtype", [np.float64, np.float32, np.int64, np.int32])
- @pytest.mark.parametrize("w_dtype", [np.float64, np.float32, np.int64, np.int32])
- @pytest.mark.parametrize("w", [None, "ones"])
- def test_simple_isotonic_regression(self, w, w_dtype, y_dtype):
- # Test case of Busing 2020
- # https://doi.org/10.18637/jss.v102.c01
- y = np.array([8, 4, 8, 2, 2, 0, 8], dtype=y_dtype)
- if w is not None:
- w = np.ones_like(y, dtype=w_dtype)
- res = isotonic_regression(y, weights=w)
- assert res.x.dtype == np.float64
- assert res.weights.dtype == np.float64
- assert_allclose(res.x, [4, 4, 4, 4, 4, 4, 8])
- assert_allclose(res.weights, [6, 1])
- assert_allclose(res.blocks, [0, 6, 7])
- # Assert that y was not overwritten
- assert_equal(y, np.array([8, 4, 8, 2, 2, 0, 8], dtype=np.float64))
- @pytest.mark.parametrize("increasing", [True, False])
- def test_linspace(self, increasing):
- n = 10
- y = np.linspace(0, 1, n) if increasing else np.linspace(1, 0, n)
- res = isotonic_regression(y, increasing=increasing)
- assert_allclose(res.x, y)
- assert_allclose(res.blocks, np.arange(n + 1))
- def test_weights(self):
- w = np.array([1, 2, 5, 0.5, 0.5, 0.5, 1, 3])
- y = np.array([3, 2, 1, 10, 9, 8, 20, 10])
- res = isotonic_regression(y, weights=w)
- assert_allclose(res.x, [12/8, 12/8, 12/8, 9, 9, 9, 50/4, 50/4])
- assert_allclose(res.weights, [8, 1.5, 4])
- assert_allclose(res.blocks, [0, 3, 6, 8])
- # weights are like repeated observations, we repeat the 3rd element 5
- # times.
- w2 = np.array([1, 2, 1, 1, 1, 1, 1, 0.5, 0.5, 0.5, 1, 3])
- y2 = np.array([3, 2, 1, 1, 1, 1, 1, 10, 9, 8, 20, 10])
- res2 = isotonic_regression(y2, weights=w2)
- assert_allclose(np.diff(res2.x[0:7]), 0)
- assert_allclose(res2.x[4:], res.x)
- assert_allclose(res2.weights, res.weights)
- assert_allclose(res2.blocks[1:] - 4, res.blocks[1:])
- def test_against_R_monotone(self):
- y = [0, 6, 8, 3, 5, 2, 1, 7, 9, 4]
- res = isotonic_regression(y)
- # R code
- # library(monotone)
- # options(digits=8)
- # monotone(c(0, 6, 8, 3, 5, 2, 1, 7, 9, 4))
- x_R = [
- 0, 4.1666667, 4.1666667, 4.1666667, 4.1666667, 4.1666667,
- 4.1666667, 6.6666667, 6.6666667, 6.6666667,
- ]
- assert_allclose(res.x, x_R)
- assert_equal(res.blocks, [0, 1, 7, 10])
- n = 100
- y = np.linspace(0, 1, num=n, endpoint=False)
- y = 5 * y + np.sin(10 * y)
- res = isotonic_regression(y)
- # R code
- # library(monotone)
- # n <- 100
- # y <- 5 * ((1:n)-1)/n + sin(10 * ((1:n)-1)/n)
- # options(digits=8)
- # monotone(y)
- x_R = [
- 0.00000000, 0.14983342, 0.29866933, 0.44552021, 0.58941834, 0.72942554,
- 0.86464247, 0.99421769, 1.11735609, 1.23332691, 1.34147098, 1.44120736,
- 1.53203909, 1.57081100, 1.57081100, 1.57081100, 1.57081100, 1.57081100,
- 1.57081100, 1.57081100, 1.57081100, 1.57081100, 1.57081100, 1.57081100,
- 1.57081100, 1.57081100, 1.57081100, 1.57081100, 1.57081100, 1.57081100,
- 1.57081100, 1.57081100, 1.57081100, 1.57081100, 1.57081100, 1.57081100,
- 1.57081100, 1.57081100, 1.57081100, 1.57081100, 1.57081100, 1.57081100,
- 1.57081100, 1.57081100, 1.57081100, 1.57081100, 1.57081100, 1.57081100,
- 1.57081100, 1.57081100, 1.57081100, 1.62418532, 1.71654534, 1.81773256,
- 1.92723551, 2.04445967, 2.16873336, 2.29931446, 2.43539782, 2.57612334,
- 2.72058450, 2.86783750, 3.01691060, 3.16681390, 3.31654920, 3.46511999,
- 3.61154136, 3.75484992, 3.89411335, 4.02843976, 4.15698660, 4.27896904,
- 4.39366786, 4.50043662, 4.59870810, 4.68799998, 4.76791967, 4.83816823,
- 4.86564130, 4.86564130, 4.86564130, 4.86564130, 4.86564130, 4.86564130,
- 4.86564130, 4.86564130, 4.86564130, 4.86564130, 4.86564130, 4.86564130,
- 4.86564130, 4.86564130, 4.86564130, 4.86564130, 4.86564130, 4.86564130,
- 4.86564130, 4.86564130, 4.86564130, 4.86564130,
- ]
- assert_allclose(res.x, x_R)
- # Test increasing
- assert np.all(np.diff(res.x) >= 0)
- # Test balance property: sum(y) == sum(x)
- assert_allclose(np.sum(res.x), np.sum(y))
- # Reverse order
- res_inv = isotonic_regression(-y, increasing=False)
- assert_allclose(-res_inv.x, res.x)
- assert_equal(res_inv.blocks, res.blocks)
- def test_readonly(self):
- x = np.arange(3, dtype=float)
- w = np.ones(3, dtype=float)
- x.flags.writeable = False
- w.flags.writeable = False
- res = isotonic_regression(x, weights=w)
- assert np.all(np.isfinite(res.x))
- assert np.all(np.isfinite(res.weights))
- assert np.all(np.isfinite(res.blocks))
- def test_non_contiguous_arrays(self):
- x = np.arange(10, dtype=float)[::3]
- w = np.ones(10, dtype=float)[::3]
- assert not x.flags.c_contiguous
- assert not x.flags.f_contiguous
- assert not w.flags.c_contiguous
- assert not w.flags.f_contiguous
- res = isotonic_regression(x, weights=w)
- assert np.all(np.isfinite(res.x))
- assert np.all(np.isfinite(res.weights))
- assert np.all(np.isfinite(res.blocks))
|