lighttar 0.1.0__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.
- lighttar-0.1.0/.gitignore +8 -0
- lighttar-0.1.0/LICENSE +21 -0
- lighttar-0.1.0/PKG-INFO +114 -0
- lighttar-0.1.0/README.md +61 -0
- lighttar-0.1.0/pyproject.toml +67 -0
- lighttar-0.1.0/src/lighttar/__init__.py +19 -0
- lighttar-0.1.0/src/lighttar/_arms.py +93 -0
- lighttar-0.1.0/src/lighttar/_columns.py +82 -0
- lighttar-0.1.0/src/lighttar/_gate.py +356 -0
- lighttar-0.1.0/src/lighttar/_version.py +3 -0
- lighttar-0.1.0/src/lighttar/core.py +404 -0
- lighttar-0.1.0/src/lighttar/encoder.py +293 -0
- lighttar-0.1.0/src/lighttar/image.py +191 -0
- lighttar-0.1.0/src/lighttar/py.typed +0 -0
- lighttar-0.1.0/src/lighttar/table.py +416 -0
- lighttar-0.1.0/tests/_reference.py +73 -0
- lighttar-0.1.0/tests/conftest.py +39 -0
- lighttar-0.1.0/tests/test_api.py +105 -0
- lighttar-0.1.0/tests/test_core.py +219 -0
- lighttar-0.1.0/tests/test_encoder.py +140 -0
- lighttar-0.1.0/tests/test_gate.py +451 -0
- lighttar-0.1.0/tests/test_gate_parallel.py +130 -0
- lighttar-0.1.0/tests/test_image.py +111 -0
- lighttar-0.1.0/tests/test_precomputed.py +103 -0
- lighttar-0.1.0/tests/test_table.py +193 -0
lighttar-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eilam Shapira
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
lighttar-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: lighttar
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: LightTAR: target-aware tabular features from frozen encoder layers, in closed form. A scikit-learn step for tabular models.
|
|
5
|
+
Project-URL: Homepage, https://github.com/eilamshapira/LightTAR
|
|
6
|
+
Project-URL: Repository, https://github.com/eilamshapira/LightTAR
|
|
7
|
+
Project-URL: Issues, https://github.com/eilamshapira/LightTAR/issues
|
|
8
|
+
Author-email: Eilam Shapira <eilamshapira@campus.technion.ac.il>
|
|
9
|
+
Maintainer-email: Eilam Shapira <eilamshapira@campus.technion.ac.il>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: feature extraction,in-context learning,partial least squares,scikit-learn,tabular,tabular foundation models,text embeddings
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: numpy>=1.22
|
|
26
|
+
Requires-Dist: pandas>=1.5
|
|
27
|
+
Requires-Dist: scikit-learn>=1.2
|
|
28
|
+
Provides-Extra: all
|
|
29
|
+
Requires-Dist: lightgbm>=4.0; extra == 'all'
|
|
30
|
+
Requires-Dist: pillow>=9; extra == 'all'
|
|
31
|
+
Requires-Dist: torch>=2.0; extra == 'all'
|
|
32
|
+
Requires-Dist: transformers>=4.30; extra == 'all'
|
|
33
|
+
Requires-Dist: transformers>=4.56; extra == 'all'
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
36
|
+
Requires-Dist: lightgbm>=4.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: pillow>=9; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
39
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
40
|
+
Requires-Dist: torch>=2.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: transformers>=4.30; extra == 'dev'
|
|
42
|
+
Requires-Dist: transformers>=4.56; extra == 'dev'
|
|
43
|
+
Provides-Extra: gate
|
|
44
|
+
Requires-Dist: lightgbm>=4.0; extra == 'gate'
|
|
45
|
+
Provides-Extra: image
|
|
46
|
+
Requires-Dist: pillow>=9; extra == 'image'
|
|
47
|
+
Requires-Dist: torch>=2.0; extra == 'image'
|
|
48
|
+
Requires-Dist: transformers>=4.56; extra == 'image'
|
|
49
|
+
Provides-Extra: text
|
|
50
|
+
Requires-Dist: torch>=2.0; extra == 'text'
|
|
51
|
+
Requires-Dist: transformers>=4.30; extra == 'text'
|
|
52
|
+
Description-Content-Type: text/markdown
|
|
53
|
+
|
|
54
|
+
# LightTAR
|
|
55
|
+
|
|
56
|
+
LightTAR replaces text or image columns in a pandas DataFrame with numeric features for a tabular model. By default, each selected column contributes up to 30 PCA features from the last encoder layer and 5 supervised features learned from the last and middle layers. The encoder stays frozen.
|
|
57
|
+
|
|
58
|
+
## Install
|
|
59
|
+
|
|
60
|
+
Requires Python 3.10 or later. For the text example below, install `lighttar[text]`.
|
|
61
|
+
|
|
62
|
+
| Use case | Command |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| Precomputed embeddings | `pip install lighttar` |
|
|
65
|
+
| Text columns | `pip install "lighttar[text]"` |
|
|
66
|
+
| Image columns | `pip install "lighttar[image]"` |
|
|
67
|
+
| Text, images, and the LightGBM gate | `pip install "lighttar[all]"` |
|
|
68
|
+
|
|
69
|
+
To use the LightGBM gate with precomputed embeddings, run `pip install "lighttar[gate]"`. You can also combine extras, for example `pip install "lighttar[text,gate]"`.
|
|
70
|
+
|
|
71
|
+
## Text
|
|
72
|
+
|
|
73
|
+
The first run downloads the text encoder (`intfloat/e5-small-v2`). Pass the text column names explicitly, or use `LightTAR()` to detect them. Other columns pass through unchanged.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import pandas as pd
|
|
77
|
+
from sklearn.linear_model import LogisticRegression
|
|
78
|
+
from sklearn.pipeline import make_pipeline
|
|
79
|
+
|
|
80
|
+
from lighttar import LightTAR
|
|
81
|
+
|
|
82
|
+
train = pd.DataFrame({"review": ["Very helpful", "Hard to use", "Works well", "Broke immediately"]})
|
|
83
|
+
test = pd.DataFrame({"review": ["Easy to use", "Stopped working"]})
|
|
84
|
+
|
|
85
|
+
model = make_pipeline(LightTAR(columns=["review"]), LogisticRegression())
|
|
86
|
+
model.fit(train, [1, 0, 1, 0])
|
|
87
|
+
predictions = model.predict(test)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Images
|
|
91
|
+
|
|
92
|
+
For a column of image filenames, use this transformer in the same pipeline. The default DINOv3 model requires access on Hugging Face.
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from lighttar import ImageLayerEncoder, LightTAR
|
|
96
|
+
|
|
97
|
+
features = LightTAR(columns=["photo"], encoder=ImageLayerEncoder(image_folder="images"))
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Precomputed embeddings
|
|
101
|
+
|
|
102
|
+
Pass one array per split with shape `(rows, layers, embedding_dim)`, with the last encoder layer first. A single layer can also be passed as `(rows, embedding_dim)`.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from lighttar import LightTARProjection
|
|
106
|
+
|
|
107
|
+
projection = LightTARProjection()
|
|
108
|
+
Z_train = projection.fit_transform(H_train, y_train)
|
|
109
|
+
Z_test = projection.transform(H_test)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Optional gate
|
|
113
|
+
|
|
114
|
+
Use `GatedLightTAR` in place of `LightTAR` to choose between LightTAR and PCA using cross-validation. After fitting, `gate_decision_` records the choice.
|
lighttar-0.1.0/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# LightTAR
|
|
2
|
+
|
|
3
|
+
LightTAR replaces text or image columns in a pandas DataFrame with numeric features for a tabular model. By default, each selected column contributes up to 30 PCA features from the last encoder layer and 5 supervised features learned from the last and middle layers. The encoder stays frozen.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Requires Python 3.10 or later. For the text example below, install `lighttar[text]`.
|
|
8
|
+
|
|
9
|
+
| Use case | Command |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| Precomputed embeddings | `pip install lighttar` |
|
|
12
|
+
| Text columns | `pip install "lighttar[text]"` |
|
|
13
|
+
| Image columns | `pip install "lighttar[image]"` |
|
|
14
|
+
| Text, images, and the LightGBM gate | `pip install "lighttar[all]"` |
|
|
15
|
+
|
|
16
|
+
To use the LightGBM gate with precomputed embeddings, run `pip install "lighttar[gate]"`. You can also combine extras, for example `pip install "lighttar[text,gate]"`.
|
|
17
|
+
|
|
18
|
+
## Text
|
|
19
|
+
|
|
20
|
+
The first run downloads the text encoder (`intfloat/e5-small-v2`). Pass the text column names explicitly, or use `LightTAR()` to detect them. Other columns pass through unchanged.
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import pandas as pd
|
|
24
|
+
from sklearn.linear_model import LogisticRegression
|
|
25
|
+
from sklearn.pipeline import make_pipeline
|
|
26
|
+
|
|
27
|
+
from lighttar import LightTAR
|
|
28
|
+
|
|
29
|
+
train = pd.DataFrame({"review": ["Very helpful", "Hard to use", "Works well", "Broke immediately"]})
|
|
30
|
+
test = pd.DataFrame({"review": ["Easy to use", "Stopped working"]})
|
|
31
|
+
|
|
32
|
+
model = make_pipeline(LightTAR(columns=["review"]), LogisticRegression())
|
|
33
|
+
model.fit(train, [1, 0, 1, 0])
|
|
34
|
+
predictions = model.predict(test)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Images
|
|
38
|
+
|
|
39
|
+
For a column of image filenames, use this transformer in the same pipeline. The default DINOv3 model requires access on Hugging Face.
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from lighttar import ImageLayerEncoder, LightTAR
|
|
43
|
+
|
|
44
|
+
features = LightTAR(columns=["photo"], encoder=ImageLayerEncoder(image_folder="images"))
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Precomputed embeddings
|
|
48
|
+
|
|
49
|
+
Pass one array per split with shape `(rows, layers, embedding_dim)`, with the last encoder layer first. A single layer can also be passed as `(rows, embedding_dim)`.
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from lighttar import LightTARProjection
|
|
53
|
+
|
|
54
|
+
projection = LightTARProjection()
|
|
55
|
+
Z_train = projection.fit_transform(H_train, y_train)
|
|
56
|
+
Z_test = projection.transform(H_test)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Optional gate
|
|
60
|
+
|
|
61
|
+
Use `GatedLightTAR` in place of `LightTAR` to choose between LightTAR and PCA using cross-validation. After fitting, `gate_decision_` records the choice.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "lighttar"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "LightTAR: target-aware tabular features from frozen encoder layers, in closed form. A scikit-learn step for tabular models."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Eilam Shapira", email = "eilamshapira@campus.technion.ac.il" }]
|
|
14
|
+
maintainers = [{ name = "Eilam Shapira", email = "eilamshapira@campus.technion.ac.il" }]
|
|
15
|
+
keywords = [
|
|
16
|
+
"tabular", "text embeddings", "partial least squares", "feature extraction",
|
|
17
|
+
"in-context learning", "tabular foundation models", "scikit-learn",
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 3 - Alpha",
|
|
21
|
+
"Intended Audience :: Science/Research",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
30
|
+
"Typing :: Typed",
|
|
31
|
+
]
|
|
32
|
+
dependencies = ["numpy>=1.22", "pandas>=1.5", "scikit-learn>=1.2"]
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
text = ["torch>=2.0", "transformers>=4.30"]
|
|
36
|
+
image = ["torch>=2.0", "transformers>=4.56", "Pillow>=9"]
|
|
37
|
+
gate = ["lightgbm>=4.0"]
|
|
38
|
+
all = ["lighttar[text,image,gate]"]
|
|
39
|
+
dev = ["lighttar[all]", "pytest>=7", "ruff>=0.6", "build>=1.2"]
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://github.com/eilamshapira/LightTAR"
|
|
43
|
+
Repository = "https://github.com/eilamshapira/LightTAR"
|
|
44
|
+
Issues = "https://github.com/eilamshapira/LightTAR/issues"
|
|
45
|
+
|
|
46
|
+
[tool.hatch.version]
|
|
47
|
+
path = "src/lighttar/_version.py"
|
|
48
|
+
|
|
49
|
+
[tool.hatch.build.targets.wheel]
|
|
50
|
+
packages = ["src/lighttar"]
|
|
51
|
+
|
|
52
|
+
[tool.hatch.build.targets.sdist]
|
|
53
|
+
include = ["/src/lighttar", "/tests", "/README.md", "/LICENSE", "/pyproject.toml"]
|
|
54
|
+
exclude = ["**/__pycache__"]
|
|
55
|
+
|
|
56
|
+
[tool.pytest.ini_options]
|
|
57
|
+
testpaths = ["tests"]
|
|
58
|
+
addopts = "-ra"
|
|
59
|
+
|
|
60
|
+
[tool.ruff]
|
|
61
|
+
line-length = 120
|
|
62
|
+
target-version = "py310"
|
|
63
|
+
src = ["src", "tests"]
|
|
64
|
+
|
|
65
|
+
[tool.ruff.lint]
|
|
66
|
+
select = ["E", "F", "W", "B", "I", "UP"]
|
|
67
|
+
ignore = []
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""LightTAR: target-aware tabular features from frozen encoder layers, in closed form."""
|
|
2
|
+
|
|
3
|
+
from ._version import __version__
|
|
4
|
+
from .core import LightTARProjection
|
|
5
|
+
from .encoder import LayerEncoder, PrecomputedEncoder
|
|
6
|
+
from .image import ImageLayerEncoder, detect_image_columns
|
|
7
|
+
from .table import GatedLightTAR, LightTAR, detect_text_columns
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"LightTAR",
|
|
11
|
+
"GatedLightTAR",
|
|
12
|
+
"LightTARProjection",
|
|
13
|
+
"LayerEncoder",
|
|
14
|
+
"PrecomputedEncoder",
|
|
15
|
+
"ImageLayerEncoder",
|
|
16
|
+
"detect_text_columns",
|
|
17
|
+
"detect_image_columns",
|
|
18
|
+
"__version__",
|
|
19
|
+
]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""The two arms of ``GatedLightTAR``: the frozen pipeline (raw last layer -> PCA-30) and LightTAR from
|
|
2
|
+
moment sums shared across the inner folds."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from sklearn.base import BaseEstimator, TransformerMixin
|
|
11
|
+
|
|
12
|
+
from .core import _moment_sums, _split_layers, raw_target
|
|
13
|
+
|
|
14
|
+
BASELINE = "frozen"
|
|
15
|
+
CANDIDATE = "lighttar"
|
|
16
|
+
FROZEN_DIM = 30 # the frozen baseline's PCA dimension
|
|
17
|
+
FROZEN_SEED = 42 # the frozen baseline's PCA seed (the SEED of MulTaBench's frozen e5 + PCA-30 pipeline)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _first_layer32(H) -> np.ndarray:
|
|
21
|
+
"""The first layer of ``(n, d)``, ``(n, L, d)`` or a list of layers, as a float32 numpy array."""
|
|
22
|
+
return np.asarray(_split_layers(H, as_numpy=True)[0][0], dtype=np.float32)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FrozenPCA(BaseEstimator, TransformerMixin):
|
|
26
|
+
"""The standard frozen pipeline on one column: scikit-learn PCA-30 of the raw first layer (the encoder's
|
|
27
|
+
last layer by default), fitted and applied on float32 states exactly as the upstream pipeline does
|
|
28
|
+
(a float64 fit rotates near-degenerate trailing components)."""
|
|
29
|
+
|
|
30
|
+
def fit(self, H, y=None) -> FrozenPCA:
|
|
31
|
+
from sklearn.decomposition import PCA
|
|
32
|
+
|
|
33
|
+
X = _first_layer32(H)
|
|
34
|
+
self.n_components_ = max(1, min(FROZEN_DIM, X.shape[1], len(X) - 1))
|
|
35
|
+
self.pca_ = PCA(n_components=self.n_components_, random_state=FROZEN_SEED).fit(X)
|
|
36
|
+
return self
|
|
37
|
+
|
|
38
|
+
def transform(self, H) -> np.ndarray:
|
|
39
|
+
return self.pca_.transform(_first_layer32(H)).astype(np.float32, copy=False)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class ColumnStates:
|
|
44
|
+
"""One column's float64 layers, its float32 first layer, and the moment sums of all its rows (shared by
|
|
45
|
+
the inner folds)."""
|
|
46
|
+
|
|
47
|
+
mats: list
|
|
48
|
+
first32: np.ndarray
|
|
49
|
+
Y: np.ndarray
|
|
50
|
+
classes: np.ndarray | None
|
|
51
|
+
total: np.ndarray
|
|
52
|
+
|
|
53
|
+
def rows(self, idx) -> list:
|
|
54
|
+
return [m[idx] for m in self.mats]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _frozen_arm(cs: ColumnStates, tr, va, head) -> tuple[np.ndarray, np.ndarray]:
|
|
58
|
+
fz = FrozenPCA().fit(cs.first32[tr])
|
|
59
|
+
return fz.transform(cs.first32[tr]), fz.transform(cs.first32[va])
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _lighttar_arm(cs: ColumnStates, tr, va, head) -> tuple[np.ndarray, np.ndarray]:
|
|
63
|
+
"""The projection of the inner-train rows from shared sums: all rows minus the inner-val part."""
|
|
64
|
+
A = cs.total - _moment_sums(cs.rows(va), cs.Y[va])
|
|
65
|
+
proj = head()._fit_from_sums(A, [m.shape[1] for m in cs.mats], cs.classes)
|
|
66
|
+
return proj.transform(cs.rows(tr)), proj.transform(cs.rows(va))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
ARMS: dict[str, Callable] = {BASELINE: _frozen_arm, CANDIDATE: _lighttar_arm}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def prepare_column(H, y, classification: bool) -> ColumnStates:
|
|
73
|
+
"""The per-column work shared by all inner folds: float64 layers, float32 first layer, total sums."""
|
|
74
|
+
raw = _split_layers(H, as_numpy=True)[0]
|
|
75
|
+
mats = [np.asarray(m, dtype=np.float64) for m in raw]
|
|
76
|
+
Y, classes = raw_target(y, classification)
|
|
77
|
+
if not classification:
|
|
78
|
+
Y = Y - Y.mean() # the sums are shift-invariant in y; centering keeps them well conditioned
|
|
79
|
+
return ColumnStates(mats, np.asarray(raw[0], dtype=np.float32), Y, classes, _moment_sums(mats, Y))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def fold_arms(cs: ColumnStates, tr, va, head: Callable) -> dict:
|
|
83
|
+
"""``{arm: (Z_inner_train, Z_inner_val)}`` of one column on one inner fold; ``head()`` makes an unfitted
|
|
84
|
+
``LightTARProjection``. The inner-train rows must be the complement of the inner-val rows."""
|
|
85
|
+
return {arm: fn(cs, tr, va, head) for arm, fn in ARMS.items()}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def column_arms(H, y, splits, classification: bool, head: Callable) -> list[dict]:
|
|
89
|
+
"""``fold_arms`` of one column for every inner fold, sequentially."""
|
|
90
|
+
if not splits:
|
|
91
|
+
return []
|
|
92
|
+
cs = prepare_column(H, y, classification)
|
|
93
|
+
return [fold_arms(cs, tr, va, head) for tr, va in splits]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Text-column detection and the cell texts handed to an encoder."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from numbers import Number
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
10
|
+
MIN_TEXT_UNIQUE = 100
|
|
11
|
+
MIN_TEXT_UNIQUE_RATIO = 0.8
|
|
12
|
+
MAX_NUMERIC_FOR_CATEGORICAL = 50
|
|
13
|
+
MISSING_TEXT = "Unknown Value"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _valid(values) -> list:
|
|
17
|
+
out = []
|
|
18
|
+
for v in values:
|
|
19
|
+
if isinstance(v, str):
|
|
20
|
+
out.append(v)
|
|
21
|
+
elif v is None or _isna(v) or (isinstance(v, Number) and not np.isfinite(v)):
|
|
22
|
+
continue
|
|
23
|
+
else:
|
|
24
|
+
out.append(v)
|
|
25
|
+
return out
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _isna(v) -> bool:
|
|
29
|
+
try:
|
|
30
|
+
return bool(pd.isna(v))
|
|
31
|
+
except (TypeError, ValueError):
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _is_numeric_value(v) -> bool:
|
|
36
|
+
if v is None:
|
|
37
|
+
return False
|
|
38
|
+
if isinstance(v, str):
|
|
39
|
+
return v.isdigit()
|
|
40
|
+
if isinstance(v, (int, float)):
|
|
41
|
+
return True
|
|
42
|
+
try:
|
|
43
|
+
float(v)
|
|
44
|
+
return True
|
|
45
|
+
except (TypeError, ValueError):
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def is_numerical(s) -> bool:
|
|
50
|
+
"""Numerical column: it has valid values and either a numeric (or bool) dtype or more than 50 distinct
|
|
51
|
+
valid values of which at most one is non-numeric."""
|
|
52
|
+
from pandas.api.types import is_numeric_dtype
|
|
53
|
+
|
|
54
|
+
if is_numeric_dtype(s.dtype):
|
|
55
|
+
return bool(np.isfinite(s.to_numpy(dtype=float, na_value=np.nan)).any())
|
|
56
|
+
vals = _valid(s.tolist())
|
|
57
|
+
if not vals:
|
|
58
|
+
return False
|
|
59
|
+
uniq = set(vals)
|
|
60
|
+
return len(uniq) > MAX_NUMERIC_FOR_CATEGORICAL and sum(not _is_numeric_value(v) for v in uniq) <= 1
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def is_text(s) -> bool:
|
|
64
|
+
"""A non-numerical column is text if its values as strings (missing values as "Unknown Value") have at
|
|
65
|
+
least 100 distinct values or a distinct ratio of at least 0.8; otherwise it is categorical."""
|
|
66
|
+
vals = column_texts(s)
|
|
67
|
+
n = len(set(vals))
|
|
68
|
+
return bool(vals) and (n >= MIN_TEXT_UNIQUE or n / len(vals) >= MIN_TEXT_UNIQUE_RATIO)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def detect_text_columns(df) -> list:
|
|
72
|
+
"""The text columns of a DataFrame: neither datetime nor ``is_numerical``, and ``is_text``."""
|
|
73
|
+
from pandas.api.types import is_datetime64_any_dtype
|
|
74
|
+
|
|
75
|
+
return [
|
|
76
|
+
c for c in df.columns if not is_datetime64_any_dtype(df[c].dtype) and not is_numerical(df[c]) and is_text(df[c])
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def column_texts(s) -> list:
|
|
81
|
+
"""Values as strings, with missing values as "Unknown Value"."""
|
|
82
|
+
return s.astype(object).fillna(MISSING_TEXT).astype(str).tolist()
|