diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..ce9fdba --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-complexity=10 +max-line-length=127 diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index f0142a1..0acad12 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -35,20 +35,13 @@ jobs: python -m pip install ".[all]" # ^ install local package with all extras - - name: Lint errors with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 error_parity --count --select=E9,F63,F7,F82 --show-source --statistics - flake8 tests --count --select=E9,F63,F7,F82 --show-source --statistics - - - name: Lint warnings with flake8 + - name: Lint with flake8 continue-on-error: true run: | - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 error_parity --count --max-complexity=10 --max-line-length=127 --statistics - flake8 tests --count --max-complexity=10 --max-line-length=127 --statistics + flake8 error_parity --count --statistics + flake8 tests --count --statistics - name: Test with pytest run: | - coverage run -m pytest tests && coverage report -m # pytest tests + coverage run -m pytest tests && coverage report -m --fail-under=75 diff --git a/tests/test_constraints.py b/tests/test_constraints.py index c8a2c63..85c399d 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -223,42 +223,42 @@ def predictor(idx): ) -# def test_unprocessing( -# y_true: np.ndarray, -# y_pred_scores: np.ndarray, -# sensitive_attribute: np.ndarray, -# random_seed: int, -# ): -# """Tests that unprocessing strictly increases accuracy. -# """ -# # Predictor function -# # # > predicts the generated scores from the sample indices -# def predictor(idx): -# return y_pred_scores[idx] - -# # Hence, for this example, the features are the sample indices -# num_samples = len(y_true) -# X_features = np.arange(num_samples) - -# clf = RelaxedThresholdOptimizer( -# predictor=predictor, -# tolerance=1, -# false_pos_cost=1, -# false_neg_cost=1, -# seed=random_seed, -# ) - -# # Fit postprocessing to data -# clf.fit(X=X_features, y=y_true, group=sensitive_attribute) - -# # Optimal binarized predictions -# y_pred_binary = clf(X_features, group=sensitive_attribute) - -# # Original accuracy (using group-blind thresholds) -# original_acc = accuracy_score(y_true, (y_pred_scores >= 0.5).astype(int)) - -# # Unprocessed accuracy (using group-dependent thresholds) -# unprocessed_acc = accuracy_score(y_true, y_pred_binary) - -# # Assert that unprocessing always improves (or maintains) accuracy -# assert unprocessed_acc >= original_acc +def test_unprocessing( + y_true: np.ndarray, + y_pred_scores: np.ndarray, + sensitive_attribute: np.ndarray, + random_seed: int, +): + """Tests that unprocessing strictly increases accuracy. + """ + # Predictor function + # # > predicts the generated scores from the sample indices + def predictor(idx): + return y_pred_scores[idx] + + # Hence, for this example, the features are the sample indices + num_samples = len(y_true) + X_features = np.arange(num_samples) + + clf = RelaxedThresholdOptimizer( + predictor=predictor, + tolerance=1, + false_pos_cost=1, + false_neg_cost=1, + seed=random_seed, + ) + + # Fit postprocessing to data + clf.fit(X=X_features, y=y_true, group=sensitive_attribute) + + # Optimal binarized predictions + y_pred_binary = clf(X_features, group=sensitive_attribute) + + # Original accuracy (using group-blind thresholds) + original_acc = accuracy_score(y_true, (y_pred_scores >= 0.5).astype(int)) + + # Unprocessed accuracy (using group-dependent thresholds) + unprocessed_acc = accuracy_score(y_true, y_pred_binary) + + # Assert that unprocessing always improves (or maintains) accuracy + assert unprocessed_acc >= original_acc