From a2c99b6fe7f23061dfa0075e1f8c2422d6c48c35 Mon Sep 17 00:00:00 2001 From: Mario Graff Date: Fri, 28 Feb 2025 22:30:22 +0000 Subject: [PATCH 1/5] Readme --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 4ac5f92..82da3ba 100644 --- a/README.rst +++ b/README.rst @@ -70,6 +70,8 @@ Let us incorporate another predictions, now with Naive Bayes classifier, and His >>> nb = GaussianNB().fit(X_train, y_train) >>> score(nb.predict(X_val), name='Naive Bayes') +>>> hist = HistGradientBoostingClassifier().fit(X_train, y_train) +>>> score(hist.predict(X_val), name='Hist. Grad. Boost. Tree') Statistic with its standard error (se) statistic (se) From 7ba4cf7f102b02987d3479c432ba347d3bd1372c Mon Sep 17 00:00:00 2001 From: Mario Graff <11542693+mgraffg@users.noreply.github.com> Date: Fri, 7 Mar 2025 11:27:29 +0000 Subject: [PATCH 2/5] lazzy evaluation --- CompStats/__init__.py | 2 +- CompStats/interface.py | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/CompStats/__init__.py b/CompStats/__init__.py index 304ae65..cb88852 100644 --- a/CompStats/__init__.py +++ b/CompStats/__init__.py @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -__version__ = '0.1.11' +__version__ = '0.1.12' from CompStats.bootstrap import StatisticSamples from CompStats.measurements import CI, SE, difference_p_value from CompStats.performance import performance, difference, all_differences, plot_performance, plot_difference diff --git a/CompStats/interface.py b/CompStats/interface.py index a4262fb..47d6faa 100644 --- a/CompStats/interface.py +++ b/CompStats/interface.py @@ -186,6 +186,7 @@ def __call__(self, y_pred, name=None): k = 1 name = f'alg-{k}' self.best = None + self.statistic = None self.predictions[name] = np.asanyarray(y_pred) samples = self._statistic_samples calls = samples.calls @@ -296,14 +297,23 @@ def statistic(self): >>> perf.statistic {'alg-1': 1.0, 'forest': 0.9500891265597148} """ - + if hasattr(self, '_statistic') and self._statistic is not None: + return self._statistic + BiB = True if self.score_func is not None else False data = sorted([(k, self.statistic_func(self.y_true, v)) for k, v in self.predictions.items()], - key=lambda x: self.sorting_func(x[1]), - reverse=self.statistic_samples.BiB) + key=lambda x: self.sorting_func(x[1]), + reverse=BiB) if len(data) == 1: - return data[0][1] - return dict(data) + self._statistic = data[0][1] + else: + self._statistic = dict(data) + return self._statistic + + @statistic.setter + def statistic(self, value): + """statistic setter""" + self._statistic = value @property def se(self): From 532650e5a44cc9424cc750e8218e34e0fffa8148 Mon Sep 17 00:00:00 2001 From: Mario Graff Date: Fri, 7 Mar 2025 23:53:54 +0000 Subject: [PATCH 3/5] name in the first call --- CompStats/interface.py | 12 +++++++++--- CompStats/tests/test_interface.py | 7 +++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CompStats/interface.py b/CompStats/interface.py index 47d6faa..4b0e580 100644 --- a/CompStats/interface.py +++ b/CompStats/interface.py @@ -90,6 +90,7 @@ class Perf(object): """ def __init__(self, y_true, *y_pred, + name:str=None, score_func=balanced_accuracy_score, error_func=None, num_samples: int=500, @@ -100,8 +101,13 @@ def __init__(self, y_true, *y_pred, self.score_func = score_func self.error_func = error_func algs = {} - for k, v in enumerate(y_pred): - algs[f'alg-{k+1}'] = np.asanyarray(v) + if name is not None: + if isinstance(name, str): + name = [name] + else: + name = [f'alg-{k+1}' for k, _ in enumerate(y_pred)] + for key, v in zip(name, y_pred): + algs[key] = np.asanyarray(v) algs.update(**kwargs) self.predictions = algs self.y_true = y_true @@ -519,7 +525,7 @@ def y_true(self, value): algs[c] = value[c].to_numpy() self.predictions.update(algs) return - self._y_true = value + self._y_true = np.asanyarray(value) @property def score_func(self): diff --git a/CompStats/tests/test_interface.py b/CompStats/tests/test_interface.py index f2d02c4..1ff7626 100644 --- a/CompStats/tests/test_interface.py +++ b/CompStats/tests/test_interface.py @@ -23,6 +23,13 @@ from CompStats.tests.test_performance import DATA +def test_Perf_name(): + """Test Perf name keyword""" + from CompStats.metrics import f1_score + score = f1_score([1, 0, 1], [1, 0, 0], name='algo') + assert 'algo' in score.predictions + + def test_Perf_plot_col_wrap(): """Test plot when 2 classes""" from CompStats.metrics import f1_score From d536c76fee21e75d778bd391aca869ae17d72b11 Mon Sep 17 00:00:00 2001 From: Mario Graff Date: Fri, 14 Mar 2025 16:41:48 +0000 Subject: [PATCH 4/5] quarto --- .devcontainer/devcontainer.json | 4 ++ .github/workflows/publish.yml | 42 +++++++++++++ .gitignore | 1 + .vscode/extensions.json | 3 +- environment.yml | 2 + pyproject.toml | 6 +- quarto/CompStats.qmd | 105 ++++++++++++++++++++++++++++++++ quarto/images/ingeotec.png | Bin 0 -> 34842 bytes 8 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/publish.yml create mode 100644 quarto/CompStats.qmd create mode 100644 quarto/images/ingeotec.png diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d497b4a..9882b66 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,5 +1,9 @@ {"image": "mcr.microsoft.com/devcontainers/base:ubuntu", "features": { + "ghcr.io/rocker-org/devcontainer-features/quarto-cli": + {"installChromium": true, "installTinyTex": true}, + "ghcr.io/rocker-org/devcontainer-features/apt-packages:1": + {"packages": "ca-certificates,fonts-liberation,libasound2,libatk-bridge2.0-0,libatk1.0-0,libc6,libcairo2,libcups2,libdbus-1-3,libexpat1,libfontconfig1,libgbm1,libgcc1,libglib2.0-0,libgtk-3-0,libnspr4,libnss3,libpango-1.0-0,libpangocairo-1.0-0,libstdc++6,libx11-6,libx11-xcb1,libxcb1,libxcomposite1,libxcursor1,libxdamage1,libxext6,libxfixes3,libxi6,libxrandr2,libxrender1,libxss1,libxtst6,lsb-release,wget,xdg-utils"}, "ghcr.io/rocker-org/devcontainer-features/miniforge:2": {} }, "postCreateCommand": "conda env create --file environment.yml" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..d321c1b --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,42 @@ +on: + workflow_dispatch: + +name: Quarto Publish + +jobs: + build-deploy: + runs-on: ubuntu-latest + defaults: + run: + shell: bash -el {0} + permissions: + contents: write + steps: + - name: Check out repository + uses: actions/checkout@v3 + - name: Set up Quarto + uses: quarto-dev/quarto-actions/setup@v2 + with: + tinytex: true + # version: "pre-release" + - name: Set up Python + uses: conda-incubator/setup-miniconda@v3 + with: + activate-environment: test + auto-update-conda: true + python-version: "3.10" + channels: conda-forge + allow-softlinks: true + channel-priority: flexible + show-channel-urls: true + - name: Install dependencies + run: | + conda install --yes -c numpy scipy scikit-learn statsmodels pandas seaborn jupyter tabulate + - name: Render and Publish + run: | + git config --global user.email "mgraffg@ieee.org" + git config --global user.name "mgraffg" + cd quarto + quarto publish gh-pages CompStats.qmd --no-browser + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4ccf5c8..5c09734 100644 --- a/.gitignore +++ b/.gitignore @@ -160,3 +160,4 @@ cython_debug/ #.idea/ .vscode/settings.json +quarto/CompStats_files/ \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 9260ee9..043cbb2 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -7,7 +7,8 @@ "ms-toolsai.jupyter", "ms-python.vscode-pylance", "ms-python.python", - "ms-python.pylint" + "ms-python.pylint", + "quarto.quarto" ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. "unwantedRecommendations": [ diff --git a/environment.yml b/environment.yml index 5039519..3e1ea8c 100644 --- a/environment.yml +++ b/environment.yml @@ -9,6 +9,8 @@ dependencies: - pytest - tqdm - sphinx + - yaml + - jupyter - pip: - pandas - seaborn \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 8108be5..6ffe615 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,8 @@ dependencies = [ 'numpy', 'scikit-learn>=1.3.0', 'pandas', - 'seaborn>=0.13.0' + 'seaborn>=0.13.0', + 'statsmodels' ] dynamic = ['version'] @@ -26,6 +27,9 @@ classifiers = [ [tool.setuptools.dynamic] version = {attr = 'CompStats.__version__'} +[tool.setuptools] +packages = ['CompStats', 'CompStats.tests'] + [project.urls] Homepage = "https://compstats.readthedocs.io" Repository = "https://github.com/INGEOTEC/CompStats" diff --git a/quarto/CompStats.qmd b/quarto/CompStats.qmd new file mode 100644 index 0000000..0ac6dba --- /dev/null +++ b/quarto/CompStats.qmd @@ -0,0 +1,105 @@ +--- +title: "CompStats" +format: + dashboard: + logo: images/ingeotec.png + orientation: columns + nav-buttons: [github] + theme: cosmo +execute: + freeze: auto +--- + +# Introduction + +## Column + +::: {.card title='Introduction'} +Collaborative competitions have gained popularity in the scientific and technological fields. These competitions involve defining tasks, selecting evaluation scores, and devising result verification methods. In the standard scenario, participants receive a training set and are expected to provide a solution for a held-out dataset kept by organizers. An essential challenge for organizers arises when comparing algorithms' performance, assessing multiple participants, and ranking them. Statistical tools are often used for this purpose; however, traditional statistical methods often fail to capture decisive differences between systems' performance. CompStats implements an evaluation methodology for statistically analyzing competition results and competition. CompStats offers several advantages, including off-the-shell comparisons with correction mechanisms and the inclusion of confidence intervals. +::: + +::: {.card title='Installing using conda'} + +`CompStats` can be install using the conda package manager with the following instruction. + +```{sh} +conda install --channel conda-forge CompStats +``` +::: + +::: {.card title='Installing using pip'} +A more general approach to installing `CompStats` is through the use of the command pip, as illustrated in the following instruction. + +```{sh} +pip install CompStats +``` +::: + +# Quick Start Guide + +## Column + +To illustrate the use of `CompStats`, the following snippets show an example. The instructions load the necessary libraries, including the one to obtain the problem (e.g., digits), four different classifiers, and the last line is the score used to measure the performance and compare the algorithm. + +```{python} +#| echo: true + +from sklearn.svm import LinearSVC +from sklearn.naive_bayes import GaussianNB +from sklearn.ensemble import RandomForestClassifier, HistGradientBoostingClassifier +from sklearn.datasets import load_digits +from sklearn.model_selection import train_test_split +from sklearn.base import clone +from CompStats.metrics import f1_score +``` + +The first step is to load the digits problem and split the dataset into training and validation sets. The second step is to estimate the parameters of a linear Support Vector Machine and predict the validation set's classes. The predictions are stored in the variable `hy`. + +```{python} +#| echo: true + +X, y = load_digits(return_X_y=True) +_ = train_test_split(X, y, test_size=0.3) +X_train, X_val, y_train, y_val = _ +m = LinearSVC().fit(X_train, y_train) +hy = m.predict(X_val) +``` + +## Column + +Once the predictions are available, it is time to measure the algorithm's performance, as seen in the following code. It is essential to note that the API used in `sklearn.metrics` is followed; the difference is that the function returns an instance with different methods that can be used to estimate different performance statistics and compare algorithms. + +```{python} +#| echo: true + +score = f1_score(y_val, hy, average='macro') +score +``` + +The previous code shows the macro-f1 score and its standard error. The actual performance value is stored in the attributes `statistic` function, and `se` + +```{python} +#| echo: true + +score.statistic, score.se +``` + +Continuing with the example, let us assume that one wants to test another classifier on the same problem, in this case, a random forest, as can be seen in the following two lines. The second line predicts the validation set and sets it to the analysis. + +```{python} +#| echo: true + +ens = RandomForestClassifier().fit(X_train, y_train) +score(ens.predict(X_val), name='Random Forest') +``` + +Let us incorporate another predictions, now with Naive Bayes classifier, and Histogram Gradient Boosting as seen below. + +```{python} +#| echo: true + +nb = GaussianNB().fit(X_train, y_train) +score(nb.predict(X_val), name='Naive Bayes') +hist = HistGradientBoostingClassifier().fit(X_train, y_train) +score(hist.predict(X_val), name='Hist. Grad. Boost. Tree') +``` \ No newline at end of file diff --git a/quarto/images/ingeotec.png b/quarto/images/ingeotec.png new file mode 100644 index 0000000000000000000000000000000000000000..188d23d87d3a6770076477bfa95c9e18c6d5f7ef GIT binary patch literal 34842 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 Date: Fri, 14 Mar 2025 21:07:30 +0000 Subject: [PATCH 5/5] quarto --- quarto/CompStats.qmd | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/quarto/CompStats.qmd b/quarto/CompStats.qmd index 0ac6dba..c9f37c7 100644 --- a/quarto/CompStats.qmd +++ b/quarto/CompStats.qmd @@ -35,7 +35,7 @@ pip install CompStats ``` ::: -# Quick Start Guide +# scikit-learn Users ## Column @@ -46,7 +46,8 @@ To illustrate the use of `CompStats`, the following snippets show an example. Th from sklearn.svm import LinearSVC from sklearn.naive_bayes import GaussianNB -from sklearn.ensemble import RandomForestClassifier, HistGradientBoostingClassifier +from sklearn.ensemble import RandomForestClassifier +from sklearn.ensemble import HistGradientBoostingClassifier from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split from sklearn.base import clone @@ -65,10 +66,10 @@ m = LinearSVC().fit(X_train, y_train) hy = m.predict(X_val) ``` -## Column - Once the predictions are available, it is time to measure the algorithm's performance, as seen in the following code. It is essential to note that the API used in `sklearn.metrics` is followed; the difference is that the function returns an instance with different methods that can be used to estimate different performance statistics and compare algorithms. +## Column + ```{python} #| echo: true @@ -76,14 +77,6 @@ score = f1_score(y_val, hy, average='macro') score ``` -The previous code shows the macro-f1 score and its standard error. The actual performance value is stored in the attributes `statistic` function, and `se` - -```{python} -#| echo: true - -score.statistic, score.se -``` - Continuing with the example, let us assume that one wants to test another classifier on the same problem, in this case, a random forest, as can be seen in the following two lines. The second line predicts the validation set and sets it to the analysis. ```{python} @@ -99,7 +92,8 @@ Let us incorporate another predictions, now with Naive Bayes classifier, and His #| echo: true nb = GaussianNB().fit(X_train, y_train) -score(nb.predict(X_val), name='Naive Bayes') +_ = score(nb.predict(X_val), name='Naive Bayes') hist = HistGradientBoostingClassifier().fit(X_train, y_train) -score(hist.predict(X_val), name='Hist. Grad. Boost. Tree') +_ = score(hist.predict(X_val), name='Hist. Grad. Boost. Tree') +score.plot() ``` \ No newline at end of file