Repository: scikit-learn/scikit-learn
File: sklearn/linear_model/tests/test_logistic.py (Python)

Code before:
            assert clf_multi.scores_.shape == (n_folds, n_l1_ratios, n_cs)

            # Norm of coefficients should increase with increasing C.
            for fold in range(clf_multi.coefs_paths_.shape[0]):
                # with use_legacy_attributes=False, coefs_paths_ has shape
                # (n_folds, n_l1_ratios, n_Cs, n_classes, n_features + 1)
                # Note that we have to exclude the intercept, hence the ':-1'
                # on the last dimension
                coefs = clf_multi.coefs_paths_[fold, 0, :, :, :-1]
                norms = np.sum(coefs * coefs, axis=(-2, -1))  # L2 norm for each C
                assert np.all(np.diff(norms) >= 0)

    # Test CV folds with missing class labels:
    # It is well known that the iris target variable has 3 classes and is ordered such
    # that a simple CV split with 3 folds separates the classes.
    cv = KFold(n_splits=3)
    # Check this assumption.
    classes = np.unique(y)
    assert len(classes) == 3
    for train, test in cv.split(X, y):
        assert len(np.unique(y[train])) == 2
        assert len(np.unique(y[test])) == 1
        assert set(y[train]) & set(y[test]) == set()
    clf = LogisticRegressionCV(cv=cv, use_legacy_attributes=False).fit(X, y)
    # We use Brier score as metric. Accuracy, e.g. via clf.scores_, is a bad metric as
    # it uses the stupid fixed threshold of 50%. Instead we choose a proper scoring
    # rule, i.e. Brier score. Log loss could be problematic with values at the border,
    # i.e. probabilities of 0 or 1.
    for train, test in cv.split(X, y):
        bs = brier_score_loss(
            y[test], clf.predict_proba(X[test]), labels=classes, scale_by_half=True
        )
        assert np.all(bs > 0.3)


def test_logistic_regression_solvers(global_random_seed):
    """Test solvers converge to the same result."""
    X, y = make_classification(
        n_samples=200, n_features=10, n_informative=5, random_state=global_random_seed
    )

    params = dict(C=0.1, fit_intercept=False, random_state=global_random_seed)

    classifiers = {
        solver: LogisticRegression(solver=solver, **params).fit(X, y)
        for solver in SOLVERS
    }

    for solver_1, solver_2 in itertools.combinations(classifiers, r=2):
        assert_allclose(
            classifiers[solver_1].coef_,

Review comment (suggestion):
Nit:

```suggestion
    # it uses a useless fixed threshold of 50%. Instead we choose a proper scoring
```

Code after:
            assert clf_multi.scores_.shape == (n_folds, n_l1_ratios, n_cs)

            # Norm of coefficients should increase with increasing C.
            for fold in range(clf_multi.coefs_paths_.shape[0]):
                # with use_legacy_attributes=False, coefs_paths_ has shape
                # (n_folds, n_l1_ratios, n_Cs, n_classes, n_features + 1)
                # Note that we have to exclude the intercept, hence the ':-1'
                # on the last dimension
                coefs = clf_multi.coefs_paths_[fold, 0, :, :, :-1]
                norms = np.sum(coefs * coefs, axis=(-2, -1))  # L2 norm for each C
                assert np.all(np.diff(norms) >= 0)

    # Test CV folds with missing class labels:
    # The iris target variable has 3 classes and is ordered such that a simple
    # CV split with 3 folds separates the classes.
    cv = KFold(n_splits=3)
    # Check this assumption.
    classes = np.unique(y)
    assert len(classes) == 3
    for train, test in cv.split(X, y):
        assert len(np.unique(y[train])) == 2
        assert len(np.unique(y[test])) == 1
        assert set(y[train]) & set(y[test]) == set()

    clf = LogisticRegressionCV(cv=cv, use_legacy_attributes=False).fit(X, y)
    # We expect accuracy to be exactly 0 because train and test sets have
    # non-overlapping labels
    assert np.all(clf.scores_ == 0.0)

    # We use a proper scoring rule, i.e. the Brier score, to evaluate our classifier.
    # Because of a bug in LogisticRegressionCV, we need to create our own scoring
    # function to pass explicitly the labels.
    scoring = make_scorer(
        brier_score_loss,
        greater_is_better=False,
        response_method="predict_proba",
        scale_by_half=True,
        labels=classes,
    )
    # We set small Cs, that is strong penalty as the best C is likely the smallest one.
    clf = LogisticRegressionCV(
        cv=cv, scoring=scoring, Cs=np.logspace(-6, 3, 10), use_legacy_attributes=False
    ).fit(X, y)
    assert clf.C_ == 1e-6  # smallest value of provided Cs
    brier_scores = -clf.scores_
    # We expect the scores to be bad because train and test sets have
    # non-overlapping labels
    assert np.all(brier_scores > 0.7)
    # But the best score should be better than the worst value of 1.
    assert np.min(brier_scores) < 0.8