Repository: scikit-learn/scikit-learn
File: sklearn/tree/tests/test_tree.py (Python)

Code before:
    # fmt: off
    # no black reformatting for this specific array
    expected_samples = [
        0, 40, 30, 20, 10, 29, 39, 19, 49,  9, 45, 15, 35,  5, 25, 11, 31,
        41,  1, 21, 22, 12,  2, 42, 32, 23, 13, 43,  3, 33,  6, 36, 46, 16,
        26,  4, 14, 24, 34, 44, 27, 47,  7, 37, 17,  8, 38, 48, 28, 18
    ]
    # fmt: on
    assert_array_equal(samples, expected_samples)


def test_splitting_with_missing_values():
    # Non regression test for https://github.com/scikit-learn/scikit-learn/issues/32178
    X = (
        np.vstack([[0, 0, 0, 0, 1, 2, 3, 4], [1, 2, 1, 2, 1, 2, 1, 2]])
        .swapaxes(0, 1)
        .astype(float)
    )
    y = [0, 0, 0, 0, 1, 1, 1, 1]
    X[X == 0] = np.nan

    # The important thing here is that we want to have two trees, where each one tries
    # one of the two features first. The resulting tree should be the same in both
    # cases. The way to control which feature is tried first is `random_state`.
    tree1 = DecisionTreeRegressor(max_depth=1, random_state=1).fit(X, y)
    tree2 = DecisionTreeRegressor(max_depth=1, random_state=2).fit(X, y)

    assert_array_equal(tree1.tree_.impurity, np.array([0.25, 0.0, 0.0]))
    assert_array_equal(tree1.tree_.impurity, tree2.tree_.impurity)


Review comment (bug):
It would be good to know if this test fails on `main` for others as well. I kinda expect that this is not a good enough test :-/

My next best idea is trying ten or twenty different seeds and asserting that they are all the same. Roughly half the trees should fail on `main`, so with ten attempts we should observe at least one with fairly high probability. But still, not great to have to rely on probability (this is where flaky tests come from)

Code after:
    # only keeping one child as a leaf results in an improper tree
    leave_in_subtree = np.zeros(tree.tree_.node_count, dtype=np.uint8)
    leave_in_subtree[1] = 1
    with pytest.raises(
        ValueError, match="Node has reached a leaf in the original tree"
    ):
        _build_pruned_tree_py(pruned_tree, tree.tree_, leave_in_subtree)


def test_sort_log2_build():
    """Non-regression test for gh-30554.

    Using log2 and log in sort correctly sorts feature_values, but the tie breaking is
    different which can results in placing samples in a different order.
    """
    rng = np.random.default_rng(75)
    some = rng.normal(loc=0.0, scale=10.0, size=10).astype(np.float32)
    feature_values = np.concatenate([some] * 5)
    samples = np.arange(50, dtype=np.intp)
    _py_sort(feature_values, samples, 50)
    # fmt: off
    # no black reformatting for this specific array
    expected_samples = [
        0, 40, 30, 20, 10, 29, 39, 19, 49,  9, 45, 15, 35,  5, 25, 11, 31,
        41,  1, 21, 22, 12,  2, 42, 32, 23, 13, 43,  3, 33,  6, 36, 46, 16,
        26,  4, 14, 24, 34, 44, 27, 47,  7, 37, 17,  8, 38, 48, 28, 18
    ]
    # fmt: on
    assert_array_equal(samples, expected_samples)


def test_splitting_with_missing_values():
    # Non regression test for https://github.com/scikit-learn/scikit-learn/issues/32178
    X = (
        np.vstack([[0, 0, 0, 0, 1, 2, 3, 4], [1, 2, 1, 2, 1, 2, 1, 2]])
        .swapaxes(0, 1)
        .astype(float)
    )
    y = [0, 0, 0, 0, 1, 1, 1, 1]
    X[X == 0] = np.nan

    # The important thing here is that we try several trees, where each one tries
    # one of the two features first. The resulting tree should be the same in all
    # cases. The way to control which feature is tried first is `random_state`.
    # Twenty trees is a good guess for how many we need to try to make sure we get
    # both orders of features at least once.
    for i in range(20):
        tree = DecisionTreeRegressor(max_depth=1, random_state=i).fit(X, y)
        assert_array_equal(tree.tree_.impurity, np.array([0.25, 0.0, 0.0]))