splitiq 0.5.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.
splitiq-0.5.2/PKG-INFO ADDED
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.4
2
+ Name: splitiq
3
+ Version: 0.5.2
4
+ Summary: Optimal train/test splitting via support points, backed by SPlit.jl
5
+ Keywords: data splitting,support points,energy distance,train test split,julia
6
+ Author: Jongsu Liam Kim
7
+ Author-email: Jongsu Liam Kim <jongsukim8@gmail.com>
8
+ License-Expression: Apache-2.0
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Operating System :: POSIX
16
+ Classifier: Topic :: Scientific/Engineering
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Dist: juliacall>=0.9.35,<0.10
19
+ Requires-Dist: juliapkg>=0.1.26,<0.2
20
+ Requires-Dist: numpy>=1.26
21
+ Requires-Dist: pandas>=2.0 ; extra == 'pandas'
22
+ Requires-Python: >=3.12, <4.0
23
+ Project-URL: Homepage, https://github.com/appleparan/SPlit.jl
24
+ Project-URL: Documentation, https://appleparan.github.io/SPlit.jl
25
+ Project-URL: Issues, https://github.com/appleparan/SPlit.jl/issues
26
+ Provides-Extra: pandas
27
+ Description-Content-Type: text/markdown
28
+
29
+ # splitiq
30
+
31
+ `splitiq` is a Python wrapper around [SPlit.jl](https://github.com/appleparan/SPlit.jl), a
32
+ Julia package for optimal train/test splitting via support points. All computation runs in
33
+ Julia through [juliacall](https://github.com/JuliaPy/PythonCall.jl); `splitiq` only converts
34
+ data in and out and translates errors, so its results and guarantees are those of SPlit.jl.
35
+
36
+ ## Installation
37
+
38
+ Install with [uv](https://docs.astral.sh/uv/):
39
+
40
+ ```bash
41
+ uv add splitiq
42
+ ```
43
+
44
+ Add the `pandas` extra for DataFrame input:
45
+
46
+ ```bash
47
+ uv add "splitiq[pandas]"
48
+ ```
49
+
50
+ If you are not using uv, `pip` works as well:
51
+
52
+ ```bash
53
+ pip install splitiq # or: pip install "splitiq[pandas]"
54
+ ```
55
+
56
+ Python 3.12 or later is required.
57
+
58
+ ## Quick start
59
+
60
+ ```python
61
+ import numpy as np
62
+ import splitiq
63
+
64
+ X = np.random.default_rng(1).standard_normal((1_000, 3))
65
+ result = splitiq.datasplit(X, ratio=0.2, seed=2)
66
+
67
+ train, test = result.apply(X) # or X[result.train_indices], X[result.test_indices]
68
+ splitiq.splitquality(X, result) # energy distance between train and test; lower is better
69
+ splitiq.optimal_split_ratio(X[:, :2], X[:, 2])
70
+ ```
71
+
72
+ `datasplit` also accepts a pandas DataFrame. `category` columns keep their category order;
73
+ plain string/object columns are encoded using their sorted unique values as levels:
74
+
75
+ ```python
76
+ import pandas as pd
77
+
78
+ df = pd.DataFrame({'x': X[:, 0], 'g': pd.Categorical(['a', 'b', 'c'] * (len(X) // 3))})
79
+ result = splitiq.datasplit(df, ratio=0.2, seed=2)
80
+ train, test = result.apply(df) # df.iloc[result.train_indices], df.iloc[result.test_indices]
81
+ ```
82
+
83
+ ## API
84
+
85
+ | Function | Description |
86
+ | --- | --- |
87
+ | `datasplit(data, ratio=0.2, *, method, kernel, bandwidth, kappa, max_iterations, tolerance, n_threads, seed)` | Split `data` into train/test sets whose distributions match closely; returns a `SplitResult`. |
88
+ | `SplitResult` | `train_indices`, `test_indices` (0-based numpy arrays), `converged`, `iterations`, `method`, `kernel`, `bandwidth`, `ratio`; `.apply(data)` returns `(train, test)`; supports `train_idx, test_idx = result`. |
89
+ | `splitquality(data, result, *, kernel, bandwidth, estimator, exact_threshold, seed, n_threads)` | Discrepancy between the train and test rows of `data`; lower is better. |
90
+ | `energydistance(x, y, *, estimator, seed, n_threads)` | Energy distance between two samples. |
91
+ | `mmd(x, y, kernel='gaussian', *, bandwidth, estimator, seed, n_threads)` | Squared maximum mean discrepancy between two samples. |
92
+ | `Exact()`, `Subsample(m, repeats=8)`, `RandomSlices(k)`, `RandomFeatures(D)` | Discrepancy estimators for `energydistance`/`mmd`/`splitquality`. |
93
+ | `optimal_split_ratio(x, y, *, method='simple', degree=2)` | Optimal test-set fraction `gamma = 1 / (sqrt(p) + 1)`. |
94
+
95
+ `method='support_points'` runs the Mak & Joseph (2018) / Joseph & Vakayil (2021) optimizer;
96
+ `method='herding'` runs greedy kernel herding. Indices are 0-based. A Julia `ArgumentError`
97
+ surfaces as a Python `ValueError`; other Julia errors propagate as `juliacall.JuliaError`.
98
+ See the docstrings under `src/splitiq/` for the full argument reference, or build the API
99
+ reference locally with `make docs`.
100
+
101
+ ## First call and threads
102
+
103
+ Julia does not start when you `import splitiq`. It starts on the first call to `datasplit`,
104
+ `splitquality`, or any other function that needs it. On that first call, `juliapkg` installs a
105
+ compatible Julia (>= 1.10, via `juliaup`) if none is on the `PATH`, instantiates SPlit.jl from
106
+ git, and precompiles it. This one-time step takes a few minutes. Later starts (a new process
107
+ picking up the already-installed Julia and the precompiled package) take about two seconds.
108
+
109
+ Julia runs single-threaded inside Python unless `PYTHON_JULIACALL_THREADS` (e.g. `auto`, or a
110
+ number) is set in the environment before the first call. The `n_threads` keyword argument only
111
+ limits parallelism within the threads Julia was started with; it cannot raise that count.
112
+
113
+ ## Versioning and releases
114
+
115
+ The `splitiq` version tracks the SPlit.jl version (currently 0.5.2); `src/splitiq/juliapkg.json`
116
+ pins SPlit.jl at the git tag `v<version>`. Pushing a `vX.Y.Z` release tag builds the versioned
117
+ Julia documentation and, through the `PythonPublish` workflow, publishes `splitiq` to PyPI, so
118
+ both releases come from one tag. There is no separate changelog or release script for the
119
+ Python package.
120
+
121
+ ## Development
122
+
123
+ From the `splitiq/` directory:
124
+
125
+ ```bash
126
+ uv sync --group dev --group docs # install dependencies
127
+ make julia-dev # build .julia_dev/, developing SPlit.jl from this checkout
128
+ make test # run pytest against .julia_dev/
129
+ make format # ruff format
130
+ make lint # ruff check --fix
131
+ make typecheck # ty check
132
+ make docs # properdocs build --strict
133
+ make build # uv build
134
+ ```
135
+
136
+ `make julia-dev` runs `scripts/setup_julia_dev.sh`, which develops SPlit.jl from the repository
137
+ checkout instead of the git-pinned revision in `juliapkg.json`, and pins `PythonCall` to the
138
+ version `juliacall` itself requires. `make test` runs against that project by setting
139
+ `PYTHON_JULIACALL_PROJECT`/`PYTHON_JULIACALL_EXE`.
140
+
141
+ Pre-commit hooks are configured at the repository root and run from there
142
+ (`uvx pre-commit run -a`), not from `splitiq/`.
143
+
144
+ ## References
145
+
146
+ - Mak, S. & Joseph, V. R. (2018). Support points. *Annals of Statistics*, 46(6A).
147
+ - Joseph, V. R. & Vakayil, A. (2021). SPlit: An optimal method for data splitting.
148
+ *Technometrics*, 63(4).
149
+ - Joseph, V. R. (2022). Optimal ratio for data splitting. *Statistical Analysis and Data
150
+ Mining*, 15(4).
151
+ - Chen, Y., Welling, M. & Smola, A. (2010). Super-samples from kernel herding. *UAI*.
152
+
153
+ This project template is generated by [copier-modern-ml](https://github.com/appleparan/copier-modern-ml).
@@ -0,0 +1,125 @@
1
+ # splitiq
2
+
3
+ `splitiq` is a Python wrapper around [SPlit.jl](https://github.com/appleparan/SPlit.jl), a
4
+ Julia package for optimal train/test splitting via support points. All computation runs in
5
+ Julia through [juliacall](https://github.com/JuliaPy/PythonCall.jl); `splitiq` only converts
6
+ data in and out and translates errors, so its results and guarantees are those of SPlit.jl.
7
+
8
+ ## Installation
9
+
10
+ Install with [uv](https://docs.astral.sh/uv/):
11
+
12
+ ```bash
13
+ uv add splitiq
14
+ ```
15
+
16
+ Add the `pandas` extra for DataFrame input:
17
+
18
+ ```bash
19
+ uv add "splitiq[pandas]"
20
+ ```
21
+
22
+ If you are not using uv, `pip` works as well:
23
+
24
+ ```bash
25
+ pip install splitiq # or: pip install "splitiq[pandas]"
26
+ ```
27
+
28
+ Python 3.12 or later is required.
29
+
30
+ ## Quick start
31
+
32
+ ```python
33
+ import numpy as np
34
+ import splitiq
35
+
36
+ X = np.random.default_rng(1).standard_normal((1_000, 3))
37
+ result = splitiq.datasplit(X, ratio=0.2, seed=2)
38
+
39
+ train, test = result.apply(X) # or X[result.train_indices], X[result.test_indices]
40
+ splitiq.splitquality(X, result) # energy distance between train and test; lower is better
41
+ splitiq.optimal_split_ratio(X[:, :2], X[:, 2])
42
+ ```
43
+
44
+ `datasplit` also accepts a pandas DataFrame. `category` columns keep their category order;
45
+ plain string/object columns are encoded using their sorted unique values as levels:
46
+
47
+ ```python
48
+ import pandas as pd
49
+
50
+ df = pd.DataFrame({'x': X[:, 0], 'g': pd.Categorical(['a', 'b', 'c'] * (len(X) // 3))})
51
+ result = splitiq.datasplit(df, ratio=0.2, seed=2)
52
+ train, test = result.apply(df) # df.iloc[result.train_indices], df.iloc[result.test_indices]
53
+ ```
54
+
55
+ ## API
56
+
57
+ | Function | Description |
58
+ | --- | --- |
59
+ | `datasplit(data, ratio=0.2, *, method, kernel, bandwidth, kappa, max_iterations, tolerance, n_threads, seed)` | Split `data` into train/test sets whose distributions match closely; returns a `SplitResult`. |
60
+ | `SplitResult` | `train_indices`, `test_indices` (0-based numpy arrays), `converged`, `iterations`, `method`, `kernel`, `bandwidth`, `ratio`; `.apply(data)` returns `(train, test)`; supports `train_idx, test_idx = result`. |
61
+ | `splitquality(data, result, *, kernel, bandwidth, estimator, exact_threshold, seed, n_threads)` | Discrepancy between the train and test rows of `data`; lower is better. |
62
+ | `energydistance(x, y, *, estimator, seed, n_threads)` | Energy distance between two samples. |
63
+ | `mmd(x, y, kernel='gaussian', *, bandwidth, estimator, seed, n_threads)` | Squared maximum mean discrepancy between two samples. |
64
+ | `Exact()`, `Subsample(m, repeats=8)`, `RandomSlices(k)`, `RandomFeatures(D)` | Discrepancy estimators for `energydistance`/`mmd`/`splitquality`. |
65
+ | `optimal_split_ratio(x, y, *, method='simple', degree=2)` | Optimal test-set fraction `gamma = 1 / (sqrt(p) + 1)`. |
66
+
67
+ `method='support_points'` runs the Mak & Joseph (2018) / Joseph & Vakayil (2021) optimizer;
68
+ `method='herding'` runs greedy kernel herding. Indices are 0-based. A Julia `ArgumentError`
69
+ surfaces as a Python `ValueError`; other Julia errors propagate as `juliacall.JuliaError`.
70
+ See the docstrings under `src/splitiq/` for the full argument reference, or build the API
71
+ reference locally with `make docs`.
72
+
73
+ ## First call and threads
74
+
75
+ Julia does not start when you `import splitiq`. It starts on the first call to `datasplit`,
76
+ `splitquality`, or any other function that needs it. On that first call, `juliapkg` installs a
77
+ compatible Julia (>= 1.10, via `juliaup`) if none is on the `PATH`, instantiates SPlit.jl from
78
+ git, and precompiles it. This one-time step takes a few minutes. Later starts (a new process
79
+ picking up the already-installed Julia and the precompiled package) take about two seconds.
80
+
81
+ Julia runs single-threaded inside Python unless `PYTHON_JULIACALL_THREADS` (e.g. `auto`, or a
82
+ number) is set in the environment before the first call. The `n_threads` keyword argument only
83
+ limits parallelism within the threads Julia was started with; it cannot raise that count.
84
+
85
+ ## Versioning and releases
86
+
87
+ The `splitiq` version tracks the SPlit.jl version (currently 0.5.2); `src/splitiq/juliapkg.json`
88
+ pins SPlit.jl at the git tag `v<version>`. Pushing a `vX.Y.Z` release tag builds the versioned
89
+ Julia documentation and, through the `PythonPublish` workflow, publishes `splitiq` to PyPI, so
90
+ both releases come from one tag. There is no separate changelog or release script for the
91
+ Python package.
92
+
93
+ ## Development
94
+
95
+ From the `splitiq/` directory:
96
+
97
+ ```bash
98
+ uv sync --group dev --group docs # install dependencies
99
+ make julia-dev # build .julia_dev/, developing SPlit.jl from this checkout
100
+ make test # run pytest against .julia_dev/
101
+ make format # ruff format
102
+ make lint # ruff check --fix
103
+ make typecheck # ty check
104
+ make docs # properdocs build --strict
105
+ make build # uv build
106
+ ```
107
+
108
+ `make julia-dev` runs `scripts/setup_julia_dev.sh`, which develops SPlit.jl from the repository
109
+ checkout instead of the git-pinned revision in `juliapkg.json`, and pins `PythonCall` to the
110
+ version `juliacall` itself requires. `make test` runs against that project by setting
111
+ `PYTHON_JULIACALL_PROJECT`/`PYTHON_JULIACALL_EXE`.
112
+
113
+ Pre-commit hooks are configured at the repository root and run from there
114
+ (`uvx pre-commit run -a`), not from `splitiq/`.
115
+
116
+ ## References
117
+
118
+ - Mak, S. & Joseph, V. R. (2018). Support points. *Annals of Statistics*, 46(6A).
119
+ - Joseph, V. R. & Vakayil, A. (2021). SPlit: An optimal method for data splitting.
120
+ *Technometrics*, 63(4).
121
+ - Joseph, V. R. (2022). Optimal ratio for data splitting. *Statistical Analysis and Data
122
+ Mining*, 15(4).
123
+ - Chen, Y., Welling, M. & Smola, A. (2010). Super-samples from kernel herding. *UAI*.
124
+
125
+ This project template is generated by [copier-modern-ml](https://github.com/appleparan/copier-modern-ml).
@@ -0,0 +1,261 @@
1
+ [project]
2
+ name = "splitiq"
3
+ version = "0.5.2"
4
+ description = "Optimal train/test splitting via support points, backed by SPlit.jl"
5
+ readme = "README.md"
6
+ license = "Apache-2.0"
7
+ dependencies = [
8
+ "juliacall>=0.9.35,<0.10",
9
+ "juliapkg>=0.1.26,<0.2",
10
+ "numpy>=1.26",
11
+ ]
12
+ requires-python = ">=3.12, <4.0"
13
+ keywords = [
14
+ "data splitting",
15
+ "support points",
16
+ "energy distance",
17
+ "train test split",
18
+ "julia",
19
+ ]
20
+ classifiers = [
21
+ "Development Status :: 3 - Alpha",
22
+ "Intended Audience :: Developers",
23
+ "Intended Audience :: Science/Research",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
27
+ "Operating System :: POSIX",
28
+ "Topic :: Scientific/Engineering",
29
+ "Topic :: Software Development :: Libraries :: Python Modules",
30
+ ]
31
+
32
+ [[project.authors]]
33
+ name = "Jongsu Liam Kim"
34
+ email = "jongsukim8@gmail.com"
35
+
36
+ [project.urls]
37
+ Homepage = "https://github.com/appleparan/SPlit.jl"
38
+ Documentation = "https://appleparan.github.io/SPlit.jl"
39
+ Issues = "https://github.com/appleparan/SPlit.jl/issues"
40
+
41
+ [project.optional-dependencies]
42
+ pandas = ["pandas>=2.0"]
43
+
44
+ [dependency-groups]
45
+ dev = [
46
+ { include-group = "docs" },
47
+ "coverage==7.15.4",
48
+ "pandas>=2.0",
49
+ "coverage-badge==1.1.2",
50
+ "git-cliff==2.13.1",
51
+ "ruff==0.16.4",
52
+ "pytest==9.1.1",
53
+ "pytest-cov==7.1.0",
54
+ "pytest-html==4.2.0",
55
+ "pytest-mock==3.15.1",
56
+ "pre-commit==4.6.2",
57
+ "ty>=0.0.74",
58
+ ]
59
+ docs = [
60
+ "properdocs==1.6.7",
61
+ "mkdocs-materialx==10.2.0",
62
+ "mkdocstrings[python]==1.0.6",
63
+ "mkdocs-gen-files==0.6.1",
64
+ "mkdocs-section-index==0.3.12",
65
+ "mkdocs-literate-nav==0.6.3",
66
+ ]
67
+
68
+ [build-system]
69
+ requires = ["uv_build>=0.12.4,<0.13"]
70
+ build-backend = "uv_build"
71
+
72
+ [tool.uv]
73
+ package = true
74
+ managed = true
75
+ exclude-newer = "P7D"
76
+
77
+ [tool.uv.pip]
78
+ index-url = "https://pypi.org/simple"
79
+
80
+ [tool.ruff]
81
+ include = [
82
+ "*.py",
83
+ "*.pyi",
84
+ "**/pyproject.toml",
85
+ "*.ipynb",
86
+ ]
87
+ exclude = [
88
+ "__pycache__",
89
+ "data/*",
90
+ "notebooks/*",
91
+ "logs/*",
92
+ "**/__pycache__",
93
+ ".bzr",
94
+ ".direnv",
95
+ ".eggs",
96
+ ".git",
97
+ ".git-rewrite",
98
+ ".hg",
99
+ ".nox",
100
+ ".pants.d",
101
+ ".pytype",
102
+ ".ruff_cache",
103
+ ".svn",
104
+ ".tox",
105
+ ".venv",
106
+ "__pypackages__",
107
+ "_build",
108
+ "buck-out",
109
+ "build",
110
+ "dist",
111
+ "node_modules",
112
+ "venv",
113
+ "docs",
114
+ "data",
115
+ "tests/data",
116
+ ]
117
+ line-length = 100
118
+ target-version = "py313"
119
+
120
+ [tool.ruff.lint]
121
+ ignore = [
122
+ "ANN002",
123
+ "ANN003",
124
+ "ANN204",
125
+ "COM812",
126
+ "D100",
127
+ "D101",
128
+ "E741",
129
+ "F811",
130
+ "FBT001",
131
+ "FBT002",
132
+ "N806",
133
+ ]
134
+ select = [
135
+ "A",
136
+ "AIR",
137
+ "ANN",
138
+ "ARG",
139
+ "B",
140
+ "BLE",
141
+ "B9",
142
+ "COM",
143
+ "C4",
144
+ "D",
145
+ "DTZ",
146
+ "E",
147
+ "EM",
148
+ "F",
149
+ "FBT",
150
+ "G",
151
+ "I",
152
+ "ICN",
153
+ "LOG",
154
+ "N",
155
+ "NPY",
156
+ "PD",
157
+ "PERF",
158
+ "PIE",
159
+ "PLE",
160
+ "PT",
161
+ "PTH",
162
+ "RUF",
163
+ "S",
164
+ "SIM",
165
+ "TC",
166
+ "TID",
167
+ "T100",
168
+ "YTT",
169
+ "UP",
170
+ "W",
171
+ ]
172
+ fixable = ["ALL"]
173
+ unfixable = []
174
+ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
175
+
176
+ [tool.ruff.lint.mccabe]
177
+ max-complexity = 18
178
+
179
+ [tool.ruff.lint.per-file-ignores]
180
+ "**/configs/**.py" = [
181
+ "F401",
182
+ "E402",
183
+ ]
184
+ "**/__init__.py" = [
185
+ "F401",
186
+ "F403",
187
+ "E402",
188
+ ]
189
+ "**/tests/config/**.py" = [
190
+ "F401",
191
+ "E402",
192
+ ]
193
+ "**/tests/**.py" = [
194
+ "ANN",
195
+ "D100",
196
+ "D103",
197
+ "D104",
198
+ "S101",
199
+ ]
200
+
201
+ [tool.ruff.lint.pydocstyle]
202
+ convention = "google"
203
+
204
+ [tool.ruff.lint.isort]
205
+ known-first-party = ["splitiq"]
206
+ known-third-party = [
207
+ "numpy",
208
+ "pandas",
209
+ "juliacall",
210
+ "juliapkg",
211
+ ]
212
+
213
+ [tool.ruff.format]
214
+ quote-style = "single"
215
+ indent-style = "space"
216
+ skip-magic-trailing-comma = false
217
+ line-ending = "auto"
218
+ docstring-code-format = false
219
+ docstring-code-line-length = "dynamic"
220
+
221
+ [tool.pytest.ini_options]
222
+ minversion = "8.0"
223
+ testpaths = ["tests"]
224
+ pythonpath = ["src"]
225
+ python_files = ["test_*.py"]
226
+ norecursedirs = [
227
+ "hooks",
228
+ "*.egg",
229
+ ".eggs",
230
+ "dist",
231
+ "build",
232
+ "docs",
233
+ ".tox",
234
+ ".git",
235
+ "__pycache__",
236
+ ]
237
+ log_cli = "true"
238
+ markers = ["slow"]
239
+ doctest_optionflags = [
240
+ "NUMBER",
241
+ "NORMALIZE_WHITESPACE",
242
+ "IGNORE_EXCEPTION_DETAIL",
243
+ ]
244
+ addopts = [
245
+ "--strict-markers",
246
+ "--tb=short",
247
+ "--doctest-modules",
248
+ "--doctest-continue-on-failure",
249
+ ]
250
+ filterwarnings = [
251
+ "ignore::DeprecationWarning",
252
+ "ignore::UserWarning",
253
+ ]
254
+
255
+ [tool.coverage.run]
256
+ source = ["src"]
257
+ branch = true
258
+
259
+ [tool.coverage.report]
260
+ fail_under = 50
261
+ show_missing = true