pyFOCI 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. pyfoci-0.2.2/.github/workflows/release.yml +135 -0
  2. pyfoci-0.2.2/CHANGELOG.md +28 -0
  3. {pyfoci-0.2.0 → pyfoci-0.2.2}/PKG-INFO +1 -1
  4. {pyfoci-0.2.0 → pyfoci-0.2.2}/dev-readme.txt +1 -0
  5. pyfoci-0.2.2/doc/api.rst +19 -0
  6. {pyfoci-0.2.0 → pyfoci-0.2.2}/pyFOCI/_foci.py +31 -4
  7. {pyfoci-0.2.0 → pyfoci-0.2.2}/pyFOCI/_version.py +3 -3
  8. {pyfoci-0.2.0 → pyfoci-0.2.2}/pyFOCI/tests/test_foci.py +16 -7
  9. {pyfoci-0.2.0 → pyfoci-0.2.2}/pyFOCI.egg-info/PKG-INFO +1 -1
  10. {pyfoci-0.2.0 → pyfoci-0.2.2}/pyFOCI.egg-info/SOURCES.txt +1 -0
  11. pyfoci-0.2.0/.github/workflows/release.yml +0 -54
  12. pyfoci-0.2.0/doc/api.rst +0 -30
  13. {pyfoci-0.2.0 → pyfoci-0.2.2}/.codecov.yml +0 -0
  14. {pyfoci-0.2.0 → pyfoci-0.2.2}/.coveragerc +0 -0
  15. {pyfoci-0.2.0 → pyfoci-0.2.2}/.github/dependabot.yml +0 -0
  16. {pyfoci-0.2.0 → pyfoci-0.2.2}/.github/workflows/deploy-gh-pages.yml +0 -0
  17. {pyfoci-0.2.0 → pyfoci-0.2.2}/.github/workflows/lint.yml +0 -0
  18. {pyfoci-0.2.0 → pyfoci-0.2.2}/.github/workflows/python-app.yml +0 -0
  19. {pyfoci-0.2.0 → pyfoci-0.2.2}/.gitignore +0 -0
  20. {pyfoci-0.2.0 → pyfoci-0.2.2}/.pre-commit-config.yaml +0 -0
  21. {pyfoci-0.2.0 → pyfoci-0.2.2}/CITATION.cff +0 -0
  22. {pyfoci-0.2.0 → pyfoci-0.2.2}/LICENSE +0 -0
  23. {pyfoci-0.2.0 → pyfoci-0.2.2}/README.md +0 -0
  24. {pyfoci-0.2.0 → pyfoci-0.2.2}/doc/Makefile +0 -0
  25. {pyfoci-0.2.0 → pyfoci-0.2.2}/doc/_static/css/project-template.css +0 -0
  26. {pyfoci-0.2.0 → pyfoci-0.2.2}/doc/_static/img/index_api.svg +0 -0
  27. {pyfoci-0.2.0 → pyfoci-0.2.2}/doc/_static/img/index_examples.svg +0 -0
  28. {pyfoci-0.2.0 → pyfoci-0.2.2}/doc/_static/img/index_getting_started.svg +0 -0
  29. {pyfoci-0.2.0 → pyfoci-0.2.2}/doc/_static/img/index_user_guide.svg +0 -0
  30. {pyfoci-0.2.0 → pyfoci-0.2.2}/doc/_static/img/logo.png +0 -0
  31. {pyfoci-0.2.0 → pyfoci-0.2.2}/doc/_static/js/copybutton.js +0 -0
  32. {pyfoci-0.2.0 → pyfoci-0.2.2}/doc/_templates/class.rst +0 -0
  33. {pyfoci-0.2.0 → pyfoci-0.2.2}/doc/_templates/function.rst +0 -0
  34. {pyfoci-0.2.0 → pyfoci-0.2.2}/doc/_templates/numpydoc_docstring.py +0 -0
  35. {pyfoci-0.2.0 → pyfoci-0.2.2}/doc/_templates/sidebar-search-bs.html +0 -0
  36. {pyfoci-0.2.0 → pyfoci-0.2.2}/doc/conf.py +0 -0
  37. {pyfoci-0.2.0 → pyfoci-0.2.2}/doc/index.rst +0 -0
  38. {pyfoci-0.2.0 → pyfoci-0.2.2}/doc/make.bat +0 -0
  39. {pyfoci-0.2.0 → pyfoci-0.2.2}/doc/quick_start.rst +0 -0
  40. {pyfoci-0.2.0 → pyfoci-0.2.2}/doc/user_guide.rst +0 -0
  41. {pyfoci-0.2.0 → pyfoci-0.2.2}/examples/README.txt +0 -0
  42. {pyfoci-0.2.0 → pyfoci-0.2.2}/examples/plot_FOCISelector.py +0 -0
  43. {pyfoci-0.2.0 → pyfoci-0.2.2}/pixi.lock +0 -0
  44. {pyfoci-0.2.0 → pyfoci-0.2.2}/pyFOCI/__init__.py +0 -0
  45. {pyfoci-0.2.0 → pyfoci-0.2.2}/pyFOCI/tests/__init__.py +0 -0
  46. {pyfoci-0.2.0 → pyfoci-0.2.2}/pyFOCI/tests/test_common.py +0 -0
  47. {pyfoci-0.2.0 → pyfoci-0.2.2}/pyFOCI/utils/__init__.py +0 -0
  48. {pyfoci-0.2.0 → pyfoci-0.2.2}/pyFOCI/utils/discovery.py +0 -0
  49. {pyfoci-0.2.0 → pyfoci-0.2.2}/pyFOCI/utils/tests/__init__.py +0 -0
  50. {pyfoci-0.2.0 → pyfoci-0.2.2}/pyFOCI/utils/tests/test_discovery.py +0 -0
  51. {pyfoci-0.2.0 → pyfoci-0.2.2}/pyFOCI.egg-info/dependency_links.txt +0 -0
  52. {pyfoci-0.2.0 → pyfoci-0.2.2}/pyFOCI.egg-info/requires.txt +0 -0
  53. {pyfoci-0.2.0 → pyfoci-0.2.2}/pyFOCI.egg-info/top_level.txt +0 -0
  54. {pyfoci-0.2.0 → pyfoci-0.2.2}/pyproject.toml +0 -0
  55. {pyfoci-0.2.0 → pyfoci-0.2.2}/setup.cfg +0 -0
@@ -0,0 +1,135 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ build:
10
+ name: Build distributions
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - name: Checkout
15
+ uses: actions/checkout@v6
16
+
17
+ - name: Install Pixi
18
+ uses: prefix-dev/setup-pixi@v0.9.6
19
+ with:
20
+ pixi-version: v0.68.1
21
+
22
+ - name: Build sdist and wheel
23
+ run: pixi run build
24
+
25
+ - name: Check distributions
26
+ run: pixi run check-dist
27
+
28
+ - name: Upload build artifacts
29
+ uses: actions/upload-artifact@v7
30
+ with:
31
+ name: dist
32
+ path: dist/*
33
+
34
+ publish-testpypi:
35
+ name: Publish to TestPyPI
36
+ needs: build
37
+ runs-on: ubuntu-latest
38
+ environment:
39
+ name: testpypi
40
+ url: https://test.pypi.org/project/pyFOCI
41
+ permissions:
42
+ id-token: write # mandatory for trusted publishing
43
+
44
+ if: startsWith(github.ref, 'refs/tags/')
45
+ steps:
46
+ - name: Download artifacts
47
+ uses: actions/download-artifact@v8
48
+ with:
49
+ name: dist
50
+ path: dist
51
+
52
+ - name: Publish to TestPyPI
53
+ uses: pypa/gh-action-pypi-publish@release/v1
54
+ with:
55
+ repository-url: https://test.pypi.org/legacy/
56
+
57
+ publish-pypi:
58
+ name: Publish to PyPI
59
+ needs: [build, publish-testpypi]
60
+ runs-on: ubuntu-latest
61
+ environment:
62
+ name: pypi
63
+ url: https://pypi.org/p/pyFOCI
64
+ permissions:
65
+ id-token: write # mandatory for trusted publishing
66
+
67
+ if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
68
+ steps:
69
+ - name: Download artifacts
70
+ uses: actions/download-artifact@v8
71
+ with:
72
+ name: dist
73
+ path: dist
74
+
75
+ - name: Publish to PyPI
76
+ uses: pypa/gh-action-pypi-publish@release/v1
77
+
78
+ github-release:
79
+ name: Create GitHub Release
80
+ needs: publish-pypi
81
+ runs-on: ubuntu-latest
82
+ permissions:
83
+ contents: write # needed to create the release and upload assets
84
+
85
+ if: startsWith(github.ref, 'refs/tags/')
86
+ steps:
87
+ - name: Checkout
88
+ uses: actions/checkout@v6 # needed so we can read CHANGELOG.md
89
+
90
+ - name: Download artifacts
91
+ uses: actions/download-artifact@v8
92
+ with:
93
+ name: dist
94
+ path: dist
95
+
96
+ - name: Extract CHANGELOG section for this version
97
+ id: changelog
98
+ run: |
99
+ VERSION="${GITHUB_REF_NAME#v}"
100
+ # Extract from "## [<version>]" until the next "## [" heading (or EOF).
101
+ # Tolerates trailing "- date" or no date, and pre-release suffixes.
102
+ awk -v ver="$VERSION" '
103
+ BEGIN { found = 0 }
104
+ /^## \[/ {
105
+ line = $0
106
+ sub(/^## \[/, "", line)
107
+ sub(/\].*$/, "", line)
108
+ if (line == ver) { found = 1 }
109
+ else if (found) { exit }
110
+ }
111
+ found { print }
112
+ ' CHANGELOG.md > release_body.md
113
+
114
+ if [ -s release_body.md ]; then
115
+ echo "CHANGELOG section found for $VERSION"
116
+ echo "body_path=release_body.md" >> "$GITHUB_OUTPUT"
117
+ else
118
+ echo "No CHANGELOG section for $VERSION — will use auto-generated notes"
119
+ rm -f release_body.md
120
+ echo "use_auto_notes=true" >> "$GITHUB_OUTPUT"
121
+ fi
122
+
123
+ - name: Create release (from CHANGELOG)
124
+ if: steps.changelog.outputs.body_path != ''
125
+ uses: softprops/action-gh-release@v2
126
+ with:
127
+ body_path: release_body.md
128
+ files: dist/*
129
+
130
+ - name: Create release (auto-generated notes fallback)
131
+ if: steps.changelog.outputs.use_auto_notes == 'true'
132
+ uses: softprops/action-gh-release@v2
133
+ with:
134
+ generate_release_notes: true
135
+ files: dist/*
@@ -0,0 +1,28 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.2.2] - 2026-06-17
9
+
10
+ ### Added
11
+ - There is now a changelog, which is also used for the Github releases.
12
+
13
+ ## [0.2.1] - 2026-06-17
14
+
15
+ ### Changed
16
+ - Input features are now N(0,1)-normalized by default.
17
+ This can be switched off with the new parameter `standardize` by setting it to `Ǹone`.
18
+
19
+ ## [0.2.0] - 2026-06-16
20
+
21
+ ### Added
22
+ - FOCI feature selection with Fuchs Tn formula and radius_neighbors tie breaking.
23
+
24
+ ## [0.1.2] - 2026-05-22
25
+
26
+ ### Added
27
+ - Dummy release to test PyPI publishing.
28
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyFOCI
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: A template for scikit-learn compatible packages.
5
5
  Author-email: Robert Pollak <robert.pollak@jku.at>
6
6
  License-Expression: BSD-3-Clause
@@ -6,6 +6,7 @@ Testing and merging:
6
6
  pixi run -e lint lint
7
7
 
8
8
  pixi run -e test test
9
+ # Also examine the resulting coverage.xml.
9
10
 
10
11
  pixi run -e doc build-doc
11
12
 
@@ -0,0 +1,19 @@
1
+ .. _api:
2
+
3
+ #############
4
+ API Reference
5
+ #############
6
+
7
+ This is the full API documentation of the pyFOCI package.
8
+
9
+ .. currentmodule:: pyFOCI
10
+
11
+ Transformer
12
+ ===========
13
+
14
+ .. autosummary::
15
+ :toctree: generated/
16
+ :template: class.rst
17
+
18
+ FOCISelector
19
+
@@ -12,7 +12,12 @@ import sklearn.neighbors
12
12
  from sklearn.base import BaseEstimator, _fit_context
13
13
  from sklearn.feature_selection import SelectorMixin
14
14
  from sklearn.utils import check_random_state
15
- from sklearn.utils._param_validation import Integral, Interval, InvalidParameterError
15
+ from sklearn.utils._param_validation import (
16
+ Integral,
17
+ Interval,
18
+ InvalidParameterError,
19
+ StrOptions,
20
+ )
16
21
  from sklearn.utils.multiclass import type_of_target
17
22
  from sklearn.utils.validation import validate_data
18
23
 
@@ -103,7 +108,7 @@ def _Tn(X_sub, y_rank, random_state):
103
108
  class FOCISelector(SelectorMixin, BaseEstimator):
104
109
  """
105
110
  Feature selector using hierarchical forward selection based on the
106
- Azadkia–Chatterjee T_n coefficient (see reference below).
111
+ nonlinear Azadkia–Chatterjee T_n coefficient and its Fuchs form (see references).
107
112
 
108
113
  At each step, among remaining features, we choose the feature that maximizes
109
114
  the cumulative T_n on the growing set S_k = S_{k-1} ∪ {j}.
@@ -136,6 +141,11 @@ class FOCISelector(SelectorMixin, BaseEstimator):
136
141
  - min_delta == 0 corresponds to stop=TRUE
137
142
  - min_delta is None corresponds to stop=FALSE
138
143
 
144
+ standardize : {"normalize", None}, default="normalize"
145
+ If "normalize", each column of X is standardized to zero mean and unit
146
+ variance before computing nearest neighbors. If None, X is used as-is.
147
+ Columns with zero variance are left unchanged.
148
+
139
149
  random_state : int, RandomState instance or None, default=None
140
150
  Controls the random tie-breaking among nearest neighbors. Pass an int
141
151
  for reproducible results across multiple calls. If None, the global
@@ -158,19 +168,27 @@ class FOCISelector(SelectorMixin, BaseEstimator):
158
168
  References
159
169
  ----------
160
170
  Mona Azadkia and Sourav Chatterjee. A simple measure of conditional dependence.
161
- The Annals of Statistics, 49(6):3070–3102, 2021. doi:10.1214/21-AOS2073
171
+ The Annals of Statistics, 49(6):3070–3102, 2021. https://doi.org/10.1214/21-AOS2073
172
+
162
173
  R FOCI package (reference implementation): https://cran.r-project.org/package=FOCI
174
+
175
+ Sebastian Fuchs. Quantifying directed dependence via dimension reduction.
176
+ Journal of Multivariate Analysis 201 (2024): 105266. https://doi.org/10.1016/j.jmva.2023.105266
163
177
  """
164
178
 
165
179
  _parameter_constraints = {
166
180
  "max_features": [None, Interval(Integral, 1, None, closed="left")],
167
181
  "min_delta": [None, Interval(Real, None, None, closed="neither")],
182
+ "standardize": [None, StrOptions({"normalize"})],
168
183
  "random_state": ["random_state"],
169
184
  }
170
185
 
171
- def __init__(self, max_features=None, min_delta=0, random_state=None):
186
+ def __init__(
187
+ self, max_features=None, min_delta=0, standardize="normalize", random_state=None
188
+ ):
172
189
  self.max_features = max_features
173
190
  self.min_delta = min_delta
191
+ self.standardize = standardize
174
192
  self.random_state = random_state
175
193
 
176
194
  @_fit_context(prefer_skip_nested_validation=True)
@@ -198,6 +216,15 @@ class FOCISelector(SelectorMixin, BaseEstimator):
198
216
  self, X, y, accept_sparse=False, y_numeric=True
199
217
  ) # asserts finite values
200
218
 
219
+ # Standardization (if requested)
220
+ if self.standardize == "normalize":
221
+ X_mean = np.mean(X, axis=0, dtype=float)
222
+ X_std = np.std(X, axis=0, dtype=float, ddof=0)
223
+ # Avoid division by zero: leave zero-variance columns unchanged
224
+ safe_std = X_std.copy()
225
+ safe_std[safe_std == 0] = 1.0
226
+ X = (X - X_mean) / safe_std
227
+
201
228
  n_samples, n_features = X.shape
202
229
  if n_samples < 2:
203
230
  raise InvalidParameterError(
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.2.0'
22
- __version_tuple__ = version_tuple = (0, 2, 0)
21
+ __version__ = version = '0.2.2'
22
+ __version_tuple__ = version_tuple = (0, 2, 2)
23
23
 
24
- __commit_id__ = commit_id = 'g2d8096d98'
24
+ __commit_id__ = commit_id = 'g62b06f720'
@@ -93,13 +93,6 @@ def test_min_delta_none_ignores_early_stopping_and_selects_up_to_max():
93
93
  assert len(selector.Tn_path_) == 3
94
94
 
95
95
 
96
- def test_fit_raises_when_y_is_none():
97
- X = np.arange(10.0).reshape(-1, 1)
98
- sel = FOCISelector()
99
- with pytest.raises(ValueError, match="y must be provided"):
100
- sel.fit(X, y=None)
101
-
102
-
103
96
  def test_min_delta_enforces_gap():
104
97
  """
105
98
  With a positive min_delta, consecutive cumulative Tn values must improve
@@ -118,6 +111,22 @@ def test_min_delta_enforces_gap():
118
111
  assert np.all(diffs > min_delta)
119
112
 
120
113
 
114
+ def test_standardize_none():
115
+ """
116
+ standardize=None is also accepted.
117
+ """
118
+ X_df, y = make_demo_data(n=100, p=10, seed=0)
119
+
120
+ FOCISelector(random_state=0, standardize=None).fit(X_df, y)
121
+
122
+
123
+ def test_fit_raises_when_y_is_none():
124
+ X = np.arange(10.0).reshape(-1, 1)
125
+ sel = FOCISelector()
126
+ with pytest.raises(ValueError, match="y must be provided"):
127
+ sel.fit(X, y=None)
128
+
129
+
121
130
  @pytest.mark.parametrize("max_features", [0, -1])
122
131
  def test_fit_raises_when_max_features_invalid(max_features):
123
132
  n, p = 12, 5
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyFOCI
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: A template for scikit-learn compatible packages.
5
5
  Author-email: Robert Pollak <robert.pollak@jku.at>
6
6
  License-Expression: BSD-3-Clause
@@ -2,6 +2,7 @@
2
2
  .coveragerc
3
3
  .gitignore
4
4
  .pre-commit-config.yaml
5
+ CHANGELOG.md
5
6
  CITATION.cff
6
7
  LICENSE
7
8
  README.md
@@ -1,54 +0,0 @@
1
- name: Release
2
-
3
- on:
4
- push:
5
- tags:
6
- - "v*"
7
-
8
- jobs:
9
- build:
10
- name: Build distributions
11
- runs-on: ubuntu-latest
12
-
13
- steps:
14
- - name: Checkout
15
- uses: actions/checkout@v6
16
-
17
- - name: Install Pixi
18
- uses: prefix-dev/setup-pixi@v0.9.6
19
- with:
20
- pixi-version: v0.68.1
21
-
22
- - name: Build sdist and wheel
23
- run: pixi run build
24
-
25
- - name: Check distributions
26
- run: pixi run check-dist
27
-
28
- - name: Upload build artifacts
29
- uses: actions/upload-artifact@v7
30
- with:
31
- name: dist
32
- path: dist/*
33
-
34
- publish-pypi:
35
- name: Publish to PyPI
36
- needs: build
37
- runs-on: ubuntu-latest
38
- environment:
39
- name: pypi
40
- url: https://pypi.org/p/pyFOCI
41
- permissions:
42
- id-token: write # IMPORTANT: mandatory for trusted publishing
43
-
44
- if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
45
- steps:
46
- - name: Download artifacts
47
- uses: actions/download-artifact@v8
48
- with:
49
- name: dist
50
- path: dist
51
-
52
- - name: Publish to PyPI
53
- uses: pypa/gh-action-pypi-publish@release/v1
54
-
pyfoci-0.2.0/doc/api.rst DELETED
@@ -1,30 +0,0 @@
1
- .. _api:
2
-
3
- #############
4
- API Reference
5
- #############
6
-
7
- This is an example on how to document the API of your own project.
8
-
9
- .. currentmodule:: pyFOCI
10
-
11
- Transformer
12
- ===========
13
-
14
- .. autosummary::
15
- :toctree: generated/
16
- :template: class.rst
17
-
18
- FOCISelector
19
-
20
-
21
- Utilities
22
- =========
23
-
24
- .. autosummary::
25
- :toctree: generated/
26
- :template: functions.rst
27
-
28
- utils.discovery.all_estimators
29
- utils.discovery.all_displays
30
- utils.discovery.all_functions
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes