From 9d7d888b8fac1f104f37ff49c9bceba9c8f63165 Mon Sep 17 00:00:00 2001 From: Joshua Wendland <80349780+joshuawe@users.noreply.github.com> Date: Fri, 10 Nov 2023 17:03:30 +0100 Subject: [PATCH 1/8] upload tests --- README.md | 2 +- tests/test_binary_classifier.py | 61 +++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 tests/test_binary_classifier.py diff --git a/README.md b/README.md index 6fae346..056911f 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Install the package via pip. pip install plotsandgraphs ``` -Alternativelynstall the package from git. +Alternatively install the package from git. ```bash git clone https://github.com/joshuawe/plots_and_graphs cd plots_and_graphs diff --git a/tests/test_binary_classifier.py b/tests/test_binary_classifier.py new file mode 100644 index 0000000..9a3be35 --- /dev/null +++ b/tests/test_binary_classifier.py @@ -0,0 +1,61 @@ +import pytest +from pathlib import Path +import numpy as np +import pytest +import plotsandgraphs.binary_classifier as binary + +TEST_RESULTS_PATH = Path(r"tests\test_results") + + +@pytest.fixture(scope="module") +def random_data_binary_classifier(): + # create some data + n_samples = 1000 + y_true = np.random.choice([0,1], n_samples, p=[0.4, 0.6]) # the true class labels 0 or 1, with class imbalance 40:60 + + y_prob = np.zeros(y_true.shape) # a model's probability of class 1 predictions + y_prob[y_true==1] = np.random.beta(1, 0.6, y_prob[y_true==1].shape) + y_prob[y_true==0] = np.random.beta(0.5, 1, y_prob[y_true==0].shape) + return y_true, y_prob + +# Test histogram plot +def test_hist_plot(random_data_binary_classifier): + y_true, y_prob = random_data_binary_classifier + print(TEST_RESULTS_PATH) + binary.plot_y_prob_histogram(y_prob, save_fig_path=TEST_RESULTS_PATH/'histogram.png') + binary.plot_y_prob_histogram(y_prob, y_true, save_fig_path=TEST_RESULTS_PATH/'histogram_2_classes.png') + return + +# test roc curve +def test_roc_curve(random_data_binary_classifier): + y_true, y_prob = random_data_binary_classifier + binary.plot_roc_curve(y_true, y_prob, save_fig_path=TEST_RESULTS_PATH/'roc_curve.png') + return + +# test precision recall curve +def test_pr_curve(random_data_binary_classifier): + y_true, y_prob = random_data_binary_classifier + binary.plot_pr_curve(y_true, y_prob, save_fig_path=TEST_RESULTS_PATH/'pr_curve.png') + return + +# test confusion matrix +def test_confusion_matrix(random_data_binary_classifier): + y_true, y_prob = random_data_binary_classifier + binary.plot_confusion_matrix(y_true, y_prob, save_fig_path=TEST_RESULTS_PATH/'confusion_matrix.png') + return + +# test classification report +def test_classification_report(random_data_binary_classifier): + y_true, y_prob = random_data_binary_classifier + binary.plot_classification_report(y_true, y_prob, save_fig_path=TEST_RESULTS_PATH/'classification_report.png') + return + +# test calibration curve +def test_calibration_curve(random_data_binary_classifier): + y_true, y_prob = random_data_binary_classifier + binary.plot_calibration_curve(y_prob, y_true, save_fig_path=TEST_RESULTS_PATH/'calibration_curve.png') + return + + + + From fe31ef772ea2542d03b2b4242b63c589bbd53464 Mon Sep 17 00:00:00 2001 From: Joshua Wendland <80349780+joshuawe@users.noreply.github.com> Date: Fri, 10 Nov 2023 17:03:53 +0100 Subject: [PATCH 2/8] cosmetic adjustments --- plotsandgraphs/binary_classifier.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plotsandgraphs/binary_classifier.py b/plotsandgraphs/binary_classifier.py index 3f81c01..6d05c8e 100644 --- a/plotsandgraphs/binary_classifier.py +++ b/plotsandgraphs/binary_classifier.py @@ -66,7 +66,7 @@ def plot_confusion_matrix( values_format="", colorbar=False, ax=ax, - text_kw={"visible": False}, + # text_kw={"visible": False}, ) cmd.texts_ = [] cmd.text_ = [] @@ -115,7 +115,7 @@ def plot_confusion_matrix( def plot_classification_report( - y_test: np.ndarray, + y_true: np.ndarray, y_pred: np.ndarray, title="Classification Report", figsize=(8, 4), @@ -158,7 +158,7 @@ def plot_classification_report( cmap = "YlOrRd" - clf_report = classification_report(y_test, y_pred, output_dict=True, **kwargs) + clf_report = classification_report(y_true, y_pred, output_dict=True, **kwargs) keys_to_plot = [ key for key in clf_report.keys() From 2ad9c64d0e957ee37ea9569c508416456fbadf86 Mon Sep 17 00:00:00 2001 From: Joshua Wendland <80349780+joshuawe@users.noreply.github.com> Date: Fri, 10 Nov 2023 21:31:06 +0100 Subject: [PATCH 3/8] cosmetic adjustments (black) --- plotsandgraphs/binary_classifier.py | 58 +++++++++++++++-------------- tests/__init__.py | 7 ++++ tests/test_binary_classifier.py | 49 ++++++++++++++++-------- tests/test_test.py | 3 +- 4 files changed, 72 insertions(+), 45 deletions(-) diff --git a/plotsandgraphs/binary_classifier.py b/plotsandgraphs/binary_classifier.py index 6d05c8e..795cda0 100644 --- a/plotsandgraphs/binary_classifier.py +++ b/plotsandgraphs/binary_classifier.py @@ -43,12 +43,10 @@ def plot_accuracy(y_true, y_pred, name="", save_fig_path=None) -> Figure: path = Path(save_fig_path) path.parent.mkdir(parents=True, exist_ok=True) fig.savefig(save_fig_path, bbox_inches="tight") - return fig, accuracy + return fig -def plot_confusion_matrix( - y_true: np.ndarray, y_pred: np.ndarray, save_fig_path=None -) -> Figure: +def plot_confusion_matrix(y_true: np.ndarray, y_pred: np.ndarray, save_fig_path=None) -> Figure: import matplotlib.colors as colors # Compute the confusion matrix @@ -57,9 +55,7 @@ def plot_confusion_matrix( cm = cm.astype("float") / cm.sum(axis=1)[:, np.newaxis] # Create the ConfusionMatrixDisplay instance and plot it - cmd = ConfusionMatrixDisplay( - cm, display_labels=["class 0\nnegative", "class 1\npositive"] - ) + cmd = ConfusionMatrixDisplay(cm, display_labels=["class 0\nnegative", "class 1\npositive"]) fig, ax = plt.subplots(figsize=(4, 4)) cmd.plot( cmap="YlOrRd", @@ -159,11 +155,7 @@ def plot_classification_report( cmap = "YlOrRd" clf_report = classification_report(y_true, y_pred, output_dict=True, **kwargs) - keys_to_plot = [ - key - for key in clf_report.keys() - if key not in ("accuracy", "macro avg", "weighted avg") - ] + keys_to_plot = [key for key in clf_report.keys() if key not in ("accuracy", "macro avg", "weighted avg")] df = pd.DataFrame(clf_report, columns=keys_to_plot).T # the following line ensures that dataframe are sorted from the majority classes to the minority classes df.sort_values(by=["support"], inplace=True) @@ -174,8 +166,8 @@ def plot_classification_report( mask[:, cols - 1] = True bounds = np.linspace(0, 1, 11) - cmap = plt.cm.get_cmap("YlOrRd", len(bounds) + 1) - norm = colors.BoundaryNorm(bounds, cmap.N) # type: ignore[attr-defined] + cmap = plt.cm.get_cmap("YlOrRd", len(bounds) + 1) # type: ignore[assignment] + norm = colors.BoundaryNorm(bounds, cmap.N) # type: ignore[attr-defined] ax = sns.heatmap( df, @@ -332,9 +324,7 @@ def plot_roc_curve( auc_upper = np.quantile(bootstrap_aucs, CI_upper) auc_lower = np.quantile(bootstrap_aucs, CI_lower) label = f"{confidence_interval:.0%} CI: [{auc_lower:.2f}, {auc_upper:.2f}]" - plt.fill_between( - base_fpr, tprs_lower, tprs_upper, alpha=0.3, label=label, zorder=2 - ) + plt.fill_between(base_fpr, tprs_lower, tprs_upper, alpha=0.3, label=label, zorder=2) if highlight_roc_area is True: print( @@ -366,9 +356,7 @@ def plot_roc_curve( return fig -def plot_calibration_curve( - y_prob: np.ndarray, y_true: np.ndarray, n_bins=10, save_fig_path=None -) -> Figure: +def plot_calibration_curve(y_prob: np.ndarray, y_true: np.ndarray, n_bins=10, save_fig_path=None) -> Figure: """ Creates calibration plot for a binary classifier and calculates the ECE. @@ -390,9 +378,7 @@ def plot_calibration_curve( ece : float The expected calibration error. """ - prob_true, prob_pred = calibration_curve( - y_true, y_prob, n_bins=n_bins, strategy="uniform" - ) + prob_true, prob_pred = calibration_curve(y_true, y_prob, n_bins=n_bins, strategy="uniform") # Find the number of samples in each bin bin_counts = np.histogram(y_prob, bins=n_bins, range=(0, 1))[0] @@ -465,7 +451,7 @@ def plot_calibration_curve( return fig -def plot_y_prob_histogram(y_prob: np.ndarray, y_true: Optional[np.ndarray]=None, save_fig_path=None) -> Figure: +def plot_y_prob_histogram(y_prob: np.ndarray, y_true: Optional[np.ndarray] = None, save_fig_path=None) -> Figure: """ Provides a histogram for the predicted probabilities of a binary classifier. If ```y_true``` is provided, it divides the ```y_prob``` values into the two classes and plots them jointly into the same plot with different colors. @@ -485,16 +471,32 @@ def plot_y_prob_histogram(y_prob: np.ndarray, y_true: Optional[np.ndarray]=None, """ fig = plt.figure(figsize=(5, 5)) ax = fig.add_subplot(111) - + if y_true is None: ax.hist(y_prob, bins=10, alpha=0.9, edgecolor="midnightblue", linewidth=2, rwidth=1) # same histogram as above, but with border lines # ax.hist(y_prob, bins=10, alpha=0.5, edgecolor='black', linewidth=1.2) else: alpha = 0.6 - ax.hist(y_prob[y_true==0], bins=10, alpha=alpha, edgecolor="midnightblue", linewidth=2, rwidth=1, label="$\\hat{y} = 0$") - ax.hist(y_prob[y_true==1], bins=10, alpha=alpha, edgecolor="darkred", linewidth=2, rwidth=1, label="$\\hat{y} = 1$") - + ax.hist( + y_prob[y_true == 0], + bins=10, + alpha=alpha, + edgecolor="midnightblue", + linewidth=2, + rwidth=1, + label="$\\hat{y} = 0$", + ) + ax.hist( + y_prob[y_true == 1], + bins=10, + alpha=alpha, + edgecolor="darkred", + linewidth=2, + rwidth=1, + label="$\\hat{y} = 1$", + ) + plt.legend() ax.set(xlabel="Predicted probability [-]", ylabel="Count [-]", xlim=(-0.01, 1.0)) ax.set_title("Histogram of predicted probabilities") diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..72848bb 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,7 @@ +import os + +TEST_RESULTS_PATH = os.path.join(os.path.dirname(__file__), "test_results") + +# print cwd in console + +# print os.path.dirname(__file__) diff --git a/tests/test_binary_classifier.py b/tests/test_binary_classifier.py index 9a3be35..f9847ce 100644 --- a/tests/test_binary_classifier.py +++ b/tests/test_binary_classifier.py @@ -1,4 +1,3 @@ -import pytest from pathlib import Path import numpy as np import pytest @@ -11,51 +10,69 @@ def random_data_binary_classifier(): # create some data n_samples = 1000 - y_true = np.random.choice([0,1], n_samples, p=[0.4, 0.6]) # the true class labels 0 or 1, with class imbalance 40:60 + y_true = np.random.choice( + [0, 1], n_samples, p=[0.4, 0.6] + ) # the true class labels 0 or 1, with class imbalance 40:60 - y_prob = np.zeros(y_true.shape) # a model's probability of class 1 predictions - y_prob[y_true==1] = np.random.beta(1, 0.6, y_prob[y_true==1].shape) - y_prob[y_true==0] = np.random.beta(0.5, 1, y_prob[y_true==0].shape) + y_prob = np.zeros(y_true.shape) # a model's probability of class 1 predictions + y_prob[y_true == 1] = np.random.beta(1, 0.6, y_prob[y_true == 1].shape) + y_prob[y_true == 0] = np.random.beta(0.5, 1, y_prob[y_true == 0].shape) return y_true, y_prob + # Test histogram plot def test_hist_plot(random_data_binary_classifier): y_true, y_prob = random_data_binary_classifier print(TEST_RESULTS_PATH) - binary.plot_y_prob_histogram(y_prob, save_fig_path=TEST_RESULTS_PATH/'histogram.png') - binary.plot_y_prob_histogram(y_prob, y_true, save_fig_path=TEST_RESULTS_PATH/'histogram_2_classes.png') + binary.plot_y_prob_histogram( + y_prob, save_fig_path=TEST_RESULTS_PATH / "histogram.png" + ) + binary.plot_y_prob_histogram( + y_prob, y_true, save_fig_path=TEST_RESULTS_PATH / "histogram_2_classes.png" + ) return + # test roc curve def test_roc_curve(random_data_binary_classifier): y_true, y_prob = random_data_binary_classifier - binary.plot_roc_curve(y_true, y_prob, save_fig_path=TEST_RESULTS_PATH/'roc_curve.png') + binary.plot_roc_curve( + y_true, y_prob, save_fig_path=TEST_RESULTS_PATH / "roc_curve.png" + ) return + # test precision recall curve def test_pr_curve(random_data_binary_classifier): y_true, y_prob = random_data_binary_classifier - binary.plot_pr_curve(y_true, y_prob, save_fig_path=TEST_RESULTS_PATH/'pr_curve.png') + binary.plot_pr_curve( + y_true, y_prob, save_fig_path=TEST_RESULTS_PATH / "pr_curve.png" + ) return + # test confusion matrix def test_confusion_matrix(random_data_binary_classifier): y_true, y_prob = random_data_binary_classifier - binary.plot_confusion_matrix(y_true, y_prob, save_fig_path=TEST_RESULTS_PATH/'confusion_matrix.png') + binary.plot_confusion_matrix( + y_true, y_prob, save_fig_path=TEST_RESULTS_PATH / "confusion_matrix.png" + ) return + # test classification report def test_classification_report(random_data_binary_classifier): y_true, y_prob = random_data_binary_classifier - binary.plot_classification_report(y_true, y_prob, save_fig_path=TEST_RESULTS_PATH/'classification_report.png') + binary.plot_classification_report( + y_true, y_prob, save_fig_path=TEST_RESULTS_PATH / "classification_report.png" + ) return + # test calibration curve def test_calibration_curve(random_data_binary_classifier): y_true, y_prob = random_data_binary_classifier - binary.plot_calibration_curve(y_prob, y_true, save_fig_path=TEST_RESULTS_PATH/'calibration_curve.png') + binary.plot_calibration_curve( + y_prob, y_true, save_fig_path=TEST_RESULTS_PATH / "calibration_curve.png" + ) return - - - - diff --git a/tests/test_test.py b/tests/test_test.py index b2820fc..c55aff2 100644 --- a/tests/test_test.py +++ b/tests/test_test.py @@ -1,4 +1,5 @@ # This is just a test for a test + def test_test(): - assert True \ No newline at end of file + assert True From f76572a52d2ca554320041e880d3775022bd31e8 Mon Sep 17 00:00:00 2001 From: Joshua Wendland <80349780+joshuawe@users.noreply.github.com> Date: Fri, 10 Nov 2023 21:37:18 +0100 Subject: [PATCH 4/8] pylint optimization --- plotsandgraphs/binary_classifier.py | 14 ++++++-------- plotsandgraphs/compare_distributions.py | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/plotsandgraphs/binary_classifier.py b/plotsandgraphs/binary_classifier.py index 795cda0..4eb06ef 100644 --- a/plotsandgraphs/binary_classifier.py +++ b/plotsandgraphs/binary_classifier.py @@ -1,7 +1,8 @@ +from pathlib import Path +from typing import Optional import matplotlib.pyplot as plt from matplotlib.colors import to_rgba from matplotlib.figure import Figure -import seaborn as sns import numpy as np import pandas as pd from sklearn.metrics import ( @@ -15,9 +16,7 @@ ) from sklearn.calibration import calibration_curve from sklearn.utils import resample -from pathlib import Path from tqdm import tqdm -from typing import Optional def plot_accuracy(y_true, y_pred, name="", save_fig_path=None) -> Figure: @@ -39,7 +38,7 @@ def plot_accuracy(y_true, y_pred, name="", save_fig_path=None) -> Figure: plt.title(title) plt.tight_layout() - if save_fig_path != None: + if save_fig_path is not None: path = Path(save_fig_path) path.parent.mkdir(parents=True, exist_ok=True) fig.savefig(save_fig_path, bbox_inches="tight") @@ -102,7 +101,7 @@ def plot_confusion_matrix(y_true: np.ndarray, y_pred: np.ndarray, save_fig_path= cbar.outline.set_visible(False) plt.tight_layout() - if save_fig_path != None: + if save_fig_path is not None: path = Path(save_fig_path) path.parent.mkdir(parents=True, exist_ok=True) fig.savefig(save_fig_path, bbox_inches="tight") @@ -148,7 +147,6 @@ def plot_classification_report( import matplotlib as mpl import matplotlib.colors as colors import seaborn as sns - import pathlib fig, ax = plt.subplots(figsize=figsize) @@ -239,7 +237,7 @@ def plot_classification_report( plt.yticks(rotation=360) plt.tight_layout() - if save_fig_path != None: + if save_fig_path is not None: path = Path(save_fig_path) path.parent.mkdir(parents=True, exist_ok=True) fig.savefig(save_fig_path, bbox_inches="tight") @@ -507,7 +505,7 @@ def plot_y_prob_histogram(y_prob: np.ndarray, y_true: Optional[np.ndarray] = Non plt.tight_layout() # save plot - if save_fig_path != None: + if save_fig_path is not None: path = Path(save_fig_path) path.parent.mkdir(parents=True, exist_ok=True) fig.savefig(save_fig_path, bbox_inches="tight") diff --git a/plotsandgraphs/compare_distributions.py b/plotsandgraphs/compare_distributions.py index e9cc9fe..4689245 100644 --- a/plotsandgraphs/compare_distributions.py +++ b/plotsandgraphs/compare_distributions.py @@ -1,8 +1,8 @@ +from typing import List, Tuple, Optional import numpy as np import matplotlib.pyplot as plt import matplotlib as mpl import pandas as pd -from typing import List, Tuple, Optional def plot_raincloud( From 3f08ea43668604f8c13f70d41d289829dbaae019 Mon Sep 17 00:00:00 2001 From: Joshua Wendland <80349780+joshuawe@users.noreply.github.com> Date: Fri, 10 Nov 2023 21:39:46 +0100 Subject: [PATCH 5/8] update pylint --- .github/workflows/pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 867fd32..a384e87 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -21,5 +21,5 @@ jobs: pip install -r requirements.txt - name: Analysing the code with pylint run: | - pylint --fail-under=4 $(git ls-files '*.py') + pylint --fail-under=8 $(git ls-files '*.py') # pylint $(git ls-files '*.py') From 1d63c6974a2e1f4c19c469c2e671391bbefbcd2a Mon Sep 17 00:00:00 2001 From: Joshua Wendland <80349780+joshuawe@users.noreply.github.com> Date: Fri, 10 Nov 2023 21:51:21 +0100 Subject: [PATCH 6/8] add tests accuracy + roc bootstrapping --- tests/test_binary_classifier.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/test_binary_classifier.py b/tests/test_binary_classifier.py index f9847ce..aac12f6 100644 --- a/tests/test_binary_classifier.py +++ b/tests/test_binary_classifier.py @@ -33,7 +33,7 @@ def test_hist_plot(random_data_binary_classifier): return -# test roc curve +# test roc curve without bootstrapping def test_roc_curve(random_data_binary_classifier): y_true, y_prob = random_data_binary_classifier binary.plot_roc_curve( @@ -41,6 +41,14 @@ def test_roc_curve(random_data_binary_classifier): ) return +# test roc curve with bootstrapping +def test_roc_curve_bootstrap(random_data_binary_classifier): + y_true, y_prob = random_data_binary_classifier + binary.plot_roc_curve( + y_true, y_prob, n_bootstrap=10000, save_fig_path=TEST_RESULTS_PATH / "roc_curve_bootstrap.png" + ) + return + # test precision recall curve def test_pr_curve(random_data_binary_classifier): @@ -76,3 +84,12 @@ def test_calibration_curve(random_data_binary_classifier): y_prob, y_true, save_fig_path=TEST_RESULTS_PATH / "calibration_curve.png" ) return + + +# test accuracy +def test_accuracy(random_data_binary_classifier): + y_true, y_prob = random_data_binary_classifier + binary.plot_accuracy( + y_prob, y_true, save_fig_path=TEST_RESULTS_PATH / "accuracy.png" + ) + return \ No newline at end of file From 67a99ac42af20bafcb587f45202bb9f6fe77a169 Mon Sep 17 00:00:00 2001 From: Joshua Wendland <80349780+joshuawe@users.noreply.github.com> Date: Fri, 10 Nov 2023 22:01:05 +0100 Subject: [PATCH 7/8] fix tests and added plot --- .github/workflows/main.yml | 5 ----- README.md | 4 ++-- images/pr_curve.png | Bin 0 -> 20898 bytes images/y_prob_histogram.png | Bin 33059 -> 21628 bytes tests/test_binary_classifier.py | 4 ++-- 5 files changed, 4 insertions(+), 9 deletions(-) create mode 100644 images/pr_curve.png diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1dfcdd8..e340fb3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,11 +55,6 @@ jobs: - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - name: Debugging - run: | - ls -la - cat Makefile - make virtualenv - name: Install project run: | make virtualenv diff --git a/README.md b/README.md index 056911f..639414c 100644 --- a/README.md +++ b/README.md @@ -55,9 +55,9 @@ These could include visualizing the results for a binary classifier, for which p |:--------------------------------------------------:|:----------------------------------------------------------:|:-------------------------------------------------:| | Calibration Curve | Classification Report | Confusion Matrix | -| Your Image | Your Image | Your Image | +| Your Image | Your Image | Your Image | |:--------------------------------------------------:|:----------------------------------------------------------:|:-------------------------------------------------:| -| ROC Curve (AUROC) | ROC Curve (AUROC) with bootstrapping | y_prob histogram | +| ROC Curve (AUROC) with bootstrapping | Precision-Recall Curve | y_prob histogram | | Your Image | | | diff --git a/images/pr_curve.png b/images/pr_curve.png new file mode 100644 index 0000000000000000000000000000000000000000..6904ad563c6f0a2962eda01f6da781b9357fbfaa GIT binary patch literal 20898 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 tB~aT#2eiinZ3PEZB!PC{xWt~$(69BX-s9gX6 literal 0 HcmV?d00001 diff --git a/images/y_prob_histogram.png b/images/y_prob_histogram.png index e094db66a11695c8f9c0f87b00dfdc6a374d2b4a..c4a664ce177f6226bacd4ca6fb64515353ab90e0 100644 GIT binary patch literal 21628 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 k)KMq@JFLa?7uL?*CCIaHlDxbJ`tum2i)t6L Date: Fri, 10 Nov 2023 22:20:52 +0100 Subject: [PATCH 8/8] improve pylint score --- .github/workflows/pylint.yml | 2 +- pyproject.toml | 3 + src/binary_classifier.py | 7 +- src/compare_distributions.py | 17 +++-- tests/test_binary_classifier.py | 118 ++++++++++++++++++++++---------- tests/test_test.py | 5 -- 6 files changed, 98 insertions(+), 54 deletions(-) delete mode 100644 tests/test_test.py diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index a384e87..cd625cd 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -21,5 +21,5 @@ jobs: pip install -r requirements.txt - name: Analysing the code with pylint run: | - pylint --fail-under=8 $(git ls-files '*.py') + pylint --fail-under=6 $(git ls-files '*.py') # pylint $(git ls-files '*.py') diff --git a/pyproject.toml b/pyproject.toml index 7717d35..576db67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,5 +4,8 @@ max-line-length = 120 [tool.pylint."BASIC"] variable-rgx = "[a-z_][a-z0-9_]{0,30}$|[a-z0-9_]+([A-Z][a-z0-9_]+)*$" # Allow snake case and camel case for variable names +[tool.pylint."MESSAGES CONTROL"] +disable = "W0621" # Allow redefining names in outer scope + [flake8] max-line-length = 120 diff --git a/src/binary_classifier.py b/src/binary_classifier.py index de5d636..fdd44ad 100644 --- a/src/binary_classifier.py +++ b/src/binary_classifier.py @@ -1,15 +1,14 @@ +from typing import Optional +from pathlib import Path import matplotlib.pyplot as plt from matplotlib.colors import to_rgba from matplotlib.figure import Figure -import seaborn as sns import numpy as np import pandas as pd from sklearn.metrics import confusion_matrix, classification_report, ConfusionMatrixDisplay, roc_curve, auc, accuracy_score, precision_recall_curve from sklearn.calibration import calibration_curve from sklearn.utils import resample -from pathlib import Path from tqdm import tqdm -from typing import Optional def plot_accuracy(y_true, y_pred, name='', save_fig_path=None) -> Figure: @@ -381,7 +380,7 @@ def plot_y_prob_histogram(y_prob: np.ndarray, save_fig_path=None) -> Figure: plt.tight_layout() # save plot - if (save_fig_path != None): + if (save_fig_path is not None): path = Path(save_fig_path) path.parent.mkdir(parents=True, exist_ok=True) fig.savefig(save_fig_path, bbox_inches='tight') diff --git a/src/compare_distributions.py b/src/compare_distributions.py index 943c3e0..5a9aeb4 100644 --- a/src/compare_distributions.py +++ b/src/compare_distributions.py @@ -7,14 +7,14 @@ def plot_raincloud(df: pd.DataFrame, x_col: str, - y_col: str, - colors: List[str] = None, - order: List[str] = None, - title: str = None, - x_label: str = None, - x_range: Tuple[float, float] = None, - show_violin = True, - show_scatter = True, + y_col: str, + colors: List[str] = None, + order: List[str] = None, + title: str = None, + x_label: str = None, + x_range: Tuple[float, float] = None, + show_violin = True, + show_scatter = True, show_boxplot = True): """ @@ -49,7 +49,6 @@ def plot_raincloud(df: pd.DataFrame, colors = [mpl.colors.to_hex(cmap(i)) for i in np.linspace(0, 1, len(order))] else: assert len(colors) == len(order), 'colors and order must be the same length' - colors = colors # Boxplot if show_boxplot: diff --git a/tests/test_binary_classifier.py b/tests/test_binary_classifier.py index 2b29ac3..bc878d6 100644 --- a/tests/test_binary_classifier.py +++ b/tests/test_binary_classifier.py @@ -1,4 +1,5 @@ from pathlib import Path +from typing import Tuple import numpy as np import pytest import plotsandgraphs.binary_classifier as binary @@ -7,7 +8,15 @@ @pytest.fixture(scope="module") -def random_data_binary_classifier(): +def random_data_binary_classifier() -> Tuple[np.ndarray, np.ndarray]: + """ + Create random data for binary classifier tests. + + Returns + ------- + Tuple[np.ndarray, np.ndarray] + The simulated data. + """ # create some data n_samples = 1000 y_true = np.random.choice( @@ -22,74 +31,113 @@ def random_data_binary_classifier(): # Test histogram plot def test_hist_plot(random_data_binary_classifier): + """ + Test histogram plot. + + Parameters + ---------- + random_data_binary_classifier : Tuple[np.ndarray, np.ndarray] + The simulated data. + """ y_true, y_prob = random_data_binary_classifier print(TEST_RESULTS_PATH) - binary.plot_y_prob_histogram( - y_prob, save_fig_path=TEST_RESULTS_PATH / "histogram.png" - ) - binary.plot_y_prob_histogram( - y_prob, y_true, save_fig_path=TEST_RESULTS_PATH / "histogram_2_classes.png" - ) - return + binary.plot_y_prob_histogram(y_prob, save_fig_path=TEST_RESULTS_PATH / "histogram.png") + binary.plot_y_prob_histogram(y_prob, y_true, save_fig_path=TEST_RESULTS_PATH / "histogram_2_classes.png") # test roc curve without bootstrapping def test_roc_curve(random_data_binary_classifier): + """ + Test roc curve. + + Parameters + ---------- + random_data_binary_classifier : Tuple[np.ndarray, np.ndarray] + The simulated data. + """ y_true, y_prob = random_data_binary_classifier - binary.plot_roc_curve( - y_true, y_prob, save_fig_path=TEST_RESULTS_PATH / "roc_curve.png" - ) - return + binary.plot_roc_curve(y_true, y_prob, save_fig_path=TEST_RESULTS_PATH / "roc_curve.png") + # test roc curve with bootstrapping def test_roc_curve_bootstrap(random_data_binary_classifier): + """ + Test roc curve with bootstrapping. + + Parameters + ---------- + random_data_binary_classifier : Tuple[np.ndarray, np.ndarray] + The simulated data. + """ y_true, y_prob = random_data_binary_classifier binary.plot_roc_curve( y_true, y_prob, n_bootstraps=10000, save_fig_path=TEST_RESULTS_PATH / "roc_curve_bootstrap.png" ) - return # test precision recall curve def test_pr_curve(random_data_binary_classifier): + """ + Test precision recall curve. + + Parameters + ---------- + random_data_binary_classifier : Tuple[np.ndarray, np.ndarray] + The simulated data. + """ y_true, y_prob = random_data_binary_classifier - binary.plot_pr_curve( - y_true, y_prob, save_fig_path=TEST_RESULTS_PATH / "pr_curve.png" - ) - return + binary.plot_pr_curve(y_true, y_prob, save_fig_path=TEST_RESULTS_PATH / "pr_curve.png") # test confusion matrix def test_confusion_matrix(random_data_binary_classifier): + """ + Test confusion matrix. + + Parameters + ---------- + random_data_binary_classifier : Tuple[np.ndarray, np.ndarray] + The simulated data. + """ y_true, y_prob = random_data_binary_classifier - binary.plot_confusion_matrix( - y_true, y_prob, save_fig_path=TEST_RESULTS_PATH / "confusion_matrix.png" - ) - return + binary.plot_confusion_matrix(y_true, y_prob, save_fig_path=TEST_RESULTS_PATH / "confusion_matrix.png") # test classification report def test_classification_report(random_data_binary_classifier): + """ + Test classification report. + + Parameters + ---------- + random_data_binary_classifier : Tuple[np.ndarray, np.ndarray] + The simulated data. + """ y_true, y_prob = random_data_binary_classifier - binary.plot_classification_report( - y_true, y_prob, save_fig_path=TEST_RESULTS_PATH / "classification_report.png" - ) - return - + binary.plot_classification_report(y_true, y_prob, save_fig_path=TEST_RESULTS_PATH / "classification_report.png") # test calibration curve def test_calibration_curve(random_data_binary_classifier): + """ + Test calibration curve. + + Parameters + ---------- + random_data_binary_classifier : Tuple[np.ndarray, np.ndarray] + The simulated data. + """ y_true, y_prob = random_data_binary_classifier - binary.plot_calibration_curve( - y_prob, y_true, save_fig_path=TEST_RESULTS_PATH / "calibration_curve.png" - ) - return - + binary.plot_calibration_curve(y_prob, y_true, save_fig_path=TEST_RESULTS_PATH / "calibration_curve.png") # test accuracy def test_accuracy(random_data_binary_classifier): + """ + Test accuracy. + + Parameters + ---------- + random_data_binary_classifier : Tuple[np.ndarray, np.ndarray] + The simulated data. + """ y_true, y_prob = random_data_binary_classifier - binary.plot_accuracy( - y_true, y_prob, save_fig_path=TEST_RESULTS_PATH / "accuracy.png" - ) - return \ No newline at end of file + binary.plot_accuracy(y_true, y_prob, save_fig_path=TEST_RESULTS_PATH / "accuracy.png") diff --git a/tests/test_test.py b/tests/test_test.py deleted file mode 100644 index c55aff2..0000000 --- a/tests/test_test.py +++ /dev/null @@ -1,5 +0,0 @@ -# This is just a test for a test - - -def test_test(): - assert True