Repository: scikit-learn/scikit-learn
File: sklearn/metrics/cluster/tests/test_common.py (Python)

Code before:


@pytest.mark.parametrize("name", chain(SUPERVISED_METRICS, UNSUPERVISED_METRICS))
def test_returned_value_consistency(name):
    """Ensure that the returned values of all metrics are consistent.

    It can only be a float. It should not be a numpy float64 or float32.
    """

    rng = np.random.RandomState(0)
    X = rng.randint(10, size=(20, 10))
    labels_true = rng.randint(0, 3, size=(20,))
    labels_pred = rng.randint(0, 3, size=(20,))

    if name in SUPERVISED_METRICS:
        metric = SUPERVISED_METRICS[name]
        score = metric(labels_true, labels_pred)
    else:
        metric = UNSUPERVISED_METRICS[name]
        score = metric(X, labels_pred)

    assert isinstance(score, float)
    assert not isinstance(score, (np.float64, np.float32))


def check_array_api_metric(
    metric, array_namespace, device, dtype_name, a_np, b_np, **metric_kwargs
):
    xp = _array_api_for_tests(array_namespace, device)

    a_xp = xp.asarray(a_np, device=device)
    b_xp = xp.asarray(b_np, device=device)

    metric_np = metric(a_np, b_np, **metric_kwargs)

    # When array API dispatch is disabled, and np.asarray works (for example PyTorch
    # with CPU device), calling the metric function with such numpy compatible inputs
    # should work (albeit by implicitly converting to numpy arrays instead of
    # dispatching to the array library).
    try:
        np.asarray(a_xp)
        np.asarray(b_xp)
        numpy_as_array_works = True
    except (TypeError, RuntimeError, ValueError):
        # PyTorch with CUDA device and CuPy raise TypeError consistently.
        # array-api-strict chose to raise RuntimeError instead. NumPy raises
        # a ValueError if the `__array__` dunder does not return an array.
        # Exception type may need to be updated in the future for other libraries.
        numpy_as_array_works = False

    def _check_metric_matches(metric_a, metric_b, convert_a=False):

Review comment (suggestion):
Would importing `check_array_api_metric` from `test_common.py` work? I see it's got redundant 'sample_weight' and multioutput stuff but the logic seems to be there to ignore this if not relevant.

Code after:
@pytest.mark.parametrize("name", chain(SUPERVISED_METRICS, UNSUPERVISED_METRICS))
def test_returned_value_consistency(name):
    """Ensure that the returned values of all metrics are consistent.

    It can only be a float. It should not be a numpy float64 or float32.
    """

    rng = np.random.RandomState(0)
    X = rng.randint(10, size=(20, 10))
    labels_true = rng.randint(0, 3, size=(20,))
    labels_pred = rng.randint(0, 3, size=(20,))

    if name in SUPERVISED_METRICS:
        metric = SUPERVISED_METRICS[name]
        score = metric(labels_true, labels_pred)
    else:
        metric = UNSUPERVISED_METRICS[name]
        score = metric(X, labels_pred)

    assert isinstance(score, float)
    assert not isinstance(score, (np.float64, np.float32))


def check_array_api_unsupervised_metric(metric, array_namespace, device, dtype_name):
    y_pred = np.array([1, 0, 1, 0, 1, 1, 0])
    X = np.random.randint(10, size=(7, 10))

    check_array_api_metric(
        metric,
        array_namespace,
        device,
        dtype_name,
        a_np=X,
        b_np=y_pred,
    )


array_api_metric_checkers = {
    calinski_harabasz_score: [
        check_array_api_unsupervised_metric,
    ]
}


def yield_metric_checker_combinations(metric_checkers=array_api_metric_checkers):
    for metric, checkers in metric_checkers.items():
        for checker in checkers:
            yield metric, checker


@pytest.mark.parametrize(