tusk-ml 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.
tusk_ml-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.3
2
+ Name: tusk-ml
3
+ Version: 0.1.0
4
+ Summary: Deep feature synthesis for narwhals dataframes
5
+ Requires-Dist: narwhals>=2.24
6
+ Requires-Dist: scikit-learn>=1.4 ; extra == 'sklearn'
7
+ Requires-Python: >=3.10
8
+ Provides-Extra: sklearn
9
+ Description-Content-Type: text/markdown
10
+
11
+ # tusk
12
+
13
+ In nature, narwhals use their tusk to find mates.
14
+
15
+ In data science, you can use tusk to connect [narwhals](https://narwhals-dev.github.io/narwhals/) dataframes.
16
+
17
+ This package implements deep feature synthesis to automate feature engineering with the power of your favorite dataframe library.
18
+ Powered by [narwhals](https://narwhals-dev.github.io/narwhals/), inspired by [featuretools](https://featuretools.alteryx.com/).
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ uv add tusk-ml
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ```python
29
+ from datetime import datetime
30
+
31
+ import tusk
32
+ from tusk.primitives import Quantiles
33
+
34
+ db = tusk.Database("retail")
35
+ db.add_table("customers", customers_lf, primary_key="id", row_creation_time="signed_up_at")
36
+ db.add_table("sessions", sessions_lf, primary_key="id", row_creation_time="started_at")
37
+ db.add_table("transactions", tx_lf, primary_key="id", row_creation_time="occurred_at")
38
+
39
+ db.add_relationship(parent="customers", child="sessions", foreign_key="customer_id")
40
+ db.add_relationship(parent="sessions", child="transactions", foreign_key="session_id")
41
+
42
+ db.validate() # optional: confirm the keys really are keys, before you trust the numbers
43
+
44
+ feature_matrix, features = tusk.deep_feature_synthesis(
45
+ database=db,
46
+ target_table="customers",
47
+ agg_primitives=["mean", "count", Quantiles(qs=(0.25, 0.5, 0.75))],
48
+ trans_primitives=["month", "weekday"],
49
+ max_depth=2,
50
+ cutoff_time=datetime(2026, 1, 1),
51
+ )
52
+ ```
53
+
54
+ `feature_matrix` comes back as an uncomputed query plan — tusk never collects —
55
+ so on a backend with a lazy frame type you get one back and decide when to
56
+ compute:
57
+
58
+ ```python
59
+ matrix = feature_matrix.collect()
60
+ ```
61
+
62
+ `features` is a `FeatureList` — a sequence of inspectable definitions that
63
+ knows its target table and can re-apply itself to new data:
64
+
65
+ ```python
66
+ matrix = features.apply(db_new)
67
+ ```
68
+
69
+ ## Documentation
70
+
71
+ Full documentation lives in [`docs/`](docs/index.md):
72
+
73
+ - [Databases](docs/guide/databases.md) — tables, keys, relationships, and
74
+ [validating](docs/guide/databases.md#validation) them against the data.
75
+ - [Running DFS](docs/guide/deep-feature-synthesis.md) — depth, cutoff times, and the column naming scheme.
76
+ - [Primitives](docs/guide/primitives.md) — what ships with tusk and how it behaves.
77
+ - [Custom primitives](docs/guide/custom-primitives.md) — the extension point.
78
+ - [scikit-learn pipelines](docs/guide/sklearn.md) — DFS as a pipeline step, and
79
+ computing only the features you keep.
80
+ - [Differences from featuretools](docs/guide/featuretools.md) — if you are porting.
81
+ - [API reference](docs/api/index.md) — every public symbol.
82
+
83
+ Build the site locally with:
84
+
85
+ ```bash
86
+ uv run --group docs zensical serve
87
+ ```
@@ -0,0 +1,77 @@
1
+ # tusk
2
+
3
+ In nature, narwhals use their tusk to find mates.
4
+
5
+ In data science, you can use tusk to connect [narwhals](https://narwhals-dev.github.io/narwhals/) dataframes.
6
+
7
+ This package implements deep feature synthesis to automate feature engineering with the power of your favorite dataframe library.
8
+ Powered by [narwhals](https://narwhals-dev.github.io/narwhals/), inspired by [featuretools](https://featuretools.alteryx.com/).
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ uv add tusk-ml
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ ```python
19
+ from datetime import datetime
20
+
21
+ import tusk
22
+ from tusk.primitives import Quantiles
23
+
24
+ db = tusk.Database("retail")
25
+ db.add_table("customers", customers_lf, primary_key="id", row_creation_time="signed_up_at")
26
+ db.add_table("sessions", sessions_lf, primary_key="id", row_creation_time="started_at")
27
+ db.add_table("transactions", tx_lf, primary_key="id", row_creation_time="occurred_at")
28
+
29
+ db.add_relationship(parent="customers", child="sessions", foreign_key="customer_id")
30
+ db.add_relationship(parent="sessions", child="transactions", foreign_key="session_id")
31
+
32
+ db.validate() # optional: confirm the keys really are keys, before you trust the numbers
33
+
34
+ feature_matrix, features = tusk.deep_feature_synthesis(
35
+ database=db,
36
+ target_table="customers",
37
+ agg_primitives=["mean", "count", Quantiles(qs=(0.25, 0.5, 0.75))],
38
+ trans_primitives=["month", "weekday"],
39
+ max_depth=2,
40
+ cutoff_time=datetime(2026, 1, 1),
41
+ )
42
+ ```
43
+
44
+ `feature_matrix` comes back as an uncomputed query plan — tusk never collects —
45
+ so on a backend with a lazy frame type you get one back and decide when to
46
+ compute:
47
+
48
+ ```python
49
+ matrix = feature_matrix.collect()
50
+ ```
51
+
52
+ `features` is a `FeatureList` — a sequence of inspectable definitions that
53
+ knows its target table and can re-apply itself to new data:
54
+
55
+ ```python
56
+ matrix = features.apply(db_new)
57
+ ```
58
+
59
+ ## Documentation
60
+
61
+ Full documentation lives in [`docs/`](docs/index.md):
62
+
63
+ - [Databases](docs/guide/databases.md) — tables, keys, relationships, and
64
+ [validating](docs/guide/databases.md#validation) them against the data.
65
+ - [Running DFS](docs/guide/deep-feature-synthesis.md) — depth, cutoff times, and the column naming scheme.
66
+ - [Primitives](docs/guide/primitives.md) — what ships with tusk and how it behaves.
67
+ - [Custom primitives](docs/guide/custom-primitives.md) — the extension point.
68
+ - [scikit-learn pipelines](docs/guide/sklearn.md) — DFS as a pipeline step, and
69
+ computing only the features you keep.
70
+ - [Differences from featuretools](docs/guide/featuretools.md) — if you are porting.
71
+ - [API reference](docs/api/index.md) — every public symbol.
72
+
73
+ Build the site locally with:
74
+
75
+ ```bash
76
+ uv run --group docs zensical serve
77
+ ```
@@ -0,0 +1,99 @@
1
+ [project]
2
+ name = "tusk-ml"
3
+ version = "0.1.0"
4
+ description = "Deep feature synthesis for narwhals dataframes"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = ["narwhals>=2.24"]
8
+
9
+ [project.optional-dependencies]
10
+ sklearn = ["scikit-learn>=1.4"]
11
+
12
+ [build-system]
13
+ requires = ["uv_build>=0.12.7,<0.13"]
14
+ build-backend = "uv_build"
15
+
16
+ [tool.uv.build-backend]
17
+ module-name = "tusk"
18
+
19
+ [tool.pytest.ini_options]
20
+ testpaths = [
21
+ "tests",
22
+ "benchmarks",
23
+ ]
24
+ markers = [
25
+ "differential: cross-checks values against featuretools (opt-in)",
26
+ "benchmark: relbench performance runs (opt-in)",
27
+ ]
28
+ addopts = "-m 'not differential and not benchmark'"
29
+
30
+ [tool.ruff.lint]
31
+ select = [
32
+ "E",
33
+ "F",
34
+ "I",
35
+ "UP",
36
+ "B",
37
+ "D",
38
+ ]
39
+
40
+ [tool.ruff.lint.pydocstyle]
41
+ convention = "google"
42
+
43
+ [tool.ruff.lint.per-file-ignores]
44
+ "tests/*" = ["D"]
45
+
46
+ [tool.interrogate]
47
+ fail-under = 100
48
+ ignore-init-module = true
49
+ ignore-private = true
50
+ ignore-magic = true
51
+ ignore-nested-functions = true
52
+ exclude = [
53
+ "tests",
54
+ ".venv",
55
+ ".claude",
56
+ ]
57
+
58
+ [tool.pydoclint]
59
+ style = "google"
60
+ exclude = "tests|benchmarks"
61
+ arg-type-hints-in-docstring = false
62
+ check-return-types = false
63
+ allow-init-docstring = true
64
+
65
+ [[tool.ty.overrides]]
66
+ include = [
67
+ "src/tusk/primitives/**",
68
+ "tests/**",
69
+ ]
70
+
71
+ [tool.ty.overrides.rules]
72
+ invalid-method-override = "ignore"
73
+
74
+ [dependency-groups]
75
+ dev = [
76
+ "duckdb>=1.0",
77
+ "interrogate>=1.7.0",
78
+ "pre-commit-uv>=4.3.0",
79
+ "pydoclint>=0.9.1",
80
+ "pytest>=8.0",
81
+ "polars>=1.43",
82
+ "pyarrow>=17",
83
+ "ruff>=0.16.3",
84
+ "scikit-learn>=1.4",
85
+ "ty>=0.0.72",
86
+ ]
87
+ validation = [
88
+ "featuretools>=1.31",
89
+ "pandas>=2.0",
90
+ "setuptools<81",
91
+ ]
92
+ benchmark = [
93
+ "relbench>=2.1",
94
+ "duckdb>=1.0",
95
+ ]
96
+ docs = [
97
+ "mkdocstrings[python]>=1.0.6",
98
+ "zensical>=0.0.56",
99
+ ]
@@ -0,0 +1,93 @@
1
+ [project]
2
+ name = "tusk-ml"
3
+ version = "0.1.0"
4
+ description = "Deep feature synthesis for narwhals dataframes"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = ["narwhals>=2.24"]
8
+
9
+ [project.optional-dependencies]
10
+ sklearn = ["scikit-learn>=1.4"]
11
+
12
+ [build-system]
13
+ requires = ["uv_build>=0.12.7,<0.13"]
14
+ build-backend = "uv_build"
15
+
16
+ [tool.uv.build-backend]
17
+ # The distribution is named tusk-ml because tusk is taken on PyPI; the
18
+ # import name stays tusk.
19
+ module-name = "tusk"
20
+
21
+
22
+ [dependency-groups]
23
+ dev = [
24
+ "duckdb>=1.0",
25
+ "interrogate>=1.7.0",
26
+ "pre-commit-uv>=4.3.0",
27
+ "pydoclint>=0.9.1",
28
+ "pytest>=8.0",
29
+ "polars>=1.43",
30
+ "pyarrow>=17",
31
+ "ruff>=0.16.3",
32
+ "scikit-learn>=1.4",
33
+ "ty>=0.0.72",
34
+ ]
35
+ validation = [
36
+ "featuretools>=1.31",
37
+ "pandas>=2.0",
38
+ "setuptools<81",
39
+ ]
40
+ benchmark = [
41
+ "relbench>=2.1",
42
+ "duckdb>=1.0",
43
+ ]
44
+ docs = [
45
+ "mkdocstrings[python]>=1.0.6",
46
+ "zensical>=0.0.56",
47
+ ]
48
+
49
+ [tool.pytest.ini_options]
50
+ testpaths = ["tests", "benchmarks"]
51
+ markers = [
52
+ "differential: cross-checks values against featuretools (opt-in)",
53
+ "benchmark: relbench performance runs (opt-in)",
54
+ ]
55
+ addopts = "-m 'not differential and not benchmark'"
56
+
57
+ [tool.ruff.lint]
58
+ select = ["E", "F", "I", "UP", "B", "D"]
59
+
60
+ [tool.ruff.lint.pydocstyle]
61
+ convention = "google"
62
+
63
+ [tool.ruff.lint.per-file-ignores]
64
+ "tests/*" = ["D"]
65
+
66
+ [tool.interrogate]
67
+ fail-under = 100
68
+ ignore-init-module = true
69
+ ignore-private = true
70
+ ignore-magic = true
71
+ ignore-nested-functions = true
72
+ exclude = ["tests", ".venv", ".claude"]
73
+
74
+ [tool.pydoclint]
75
+ style = "google"
76
+ exclude = "tests|benchmarks"
77
+ arg-type-hints-in-docstring = false
78
+ check-return-types = false
79
+ # ruff (D107) and interrogate both require __init__ to carry its own
80
+ # docstring; without this flag pydoclint's DOC301 forbids exactly that
81
+ # whenever the class also has a docstring.
82
+ allow-init-docstring = true
83
+
84
+ [[tool.ty.overrides]]
85
+ include = ["src/tusk/primitives/**", "tests/**"]
86
+
87
+ [tool.ty.overrides.rules]
88
+ # Primitive.build is variadic in the ABC and fixed-arity in each concrete
89
+ # primitive. Arity is guaranteed by construction: synthesis supplies exactly
90
+ # len(input_dtypes) expressions to outputs(), which is build()'s only caller.
91
+ # Scoped rather than project-wide so override checking stays active everywhere
92
+ # else.
93
+ invalid-method-override = "ignore"
@@ -0,0 +1,21 @@
1
+ """Deep feature synthesis for narwhals dataframes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from tusk import exceptions
6
+ from tusk.api import apply_features, deep_feature_synthesis
7
+ from tusk.database import Database, Relationship, TableSchema
8
+ from tusk.feature_list import FeatureList
9
+
10
+ __version__ = "0.1.0"
11
+
12
+ __all__ = [
13
+ "__version__",
14
+ "Database",
15
+ "FeatureList",
16
+ "Relationship",
17
+ "TableSchema",
18
+ "apply_features",
19
+ "deep_feature_synthesis",
20
+ "exceptions",
21
+ ]
@@ -0,0 +1,125 @@
1
+ """The public entry points.
2
+
3
+ :func:`deep_feature_synthesis` builds feature definitions and computes them;
4
+ :func:`apply_features` computes existing ones.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Iterable, Sequence
10
+ from datetime import datetime
11
+ from typing import Any
12
+
13
+ from tusk.database import Database
14
+ from tusk.feature_list import FeatureList
15
+ from tusk.features import Feature
16
+ from tusk.primitives.base import Primitive
17
+ from tusk.synthesis import synthesize
18
+
19
+
20
+ def deep_feature_synthesis(
21
+ database: Database,
22
+ target_table: str,
23
+ agg_primitives: Iterable[str | Primitive] | None = None,
24
+ trans_primitives: Iterable[str | Primitive] | None = None,
25
+ groupby_trans_primitives: Iterable[str | Primitive] | None = None,
26
+ max_depth: int = 2,
27
+ cutoff_time: datetime | None = None,
28
+ features_only: bool = False,
29
+ ) -> Any:
30
+ """Run deep feature synthesis over a database.
31
+
32
+ Synthesis raises :class:`~tusk.exceptions.SchemaError` if the target table
33
+ is unknown or the walk generates no features at all, and
34
+ :class:`~tusk.exceptions.PrimitiveError` for an unknown primitive name or
35
+ an order-dependent primitive on a table with no ``row_creation_time``.
36
+ Compilation raises :class:`~tusk.exceptions.SchemaError` if the target
37
+ table has no ``primary_key``, and whatever :func:`apply_features`
38
+ documents for ``cutoff_time``.
39
+
40
+ Args:
41
+ database: The tables and relationships to synthesize over.
42
+ target_table: Table to build features for. The result has one
43
+ row per *visible* row of this table, keyed by its ``primary_key``.
44
+ With no ``cutoff_time`` that is every row; with one, the target is
45
+ filtered like any other table, so the matrix may have fewer rows.
46
+ agg_primitives: Aggregation primitives, as names or instances. None
47
+ selects the documented defaults.
48
+ trans_primitives: Transform primitives. None selects the defaults.
49
+ groupby_trans_primitives: Transforms applied within foreign-key groups.
50
+ None means none.
51
+ max_depth: Maximum number of stacked primitive applications.
52
+ cutoff_time: Only rows whose ``row_creation_time`` is at or before this
53
+ value are visible, on the target table as well as its relatives.
54
+ Its tz awareness must match the database's row creation times'.
55
+ A table with no ``row_creation_time`` is timeless and passes
56
+ through unfiltered, so a cutoff on a database that declares none is
57
+ silently a no-op. None disables filtering. Ignored entirely when
58
+ ``features_only`` is true, since nothing is computed: the cutoff
59
+ belongs to compilation, and feature definitions do not record it.
60
+ features_only: Return the feature definitions without computing them.
61
+
62
+ Returns:
63
+ feature_matrix: An uncomputed query plan in the caller's native frame
64
+ type. On a backend with a lazy frame type you get one back, so call
65
+ ``.collect()`` to compute it. Not returned when ``features_only``
66
+ is true.
67
+ features (FeatureList): The feature definitions, reusable with
68
+ :meth:`~tusk.FeatureList.apply` or :func:`apply_features`.
69
+
70
+ Warns:
71
+ CategoricalDtypeWarning: If a Categorical or Enum column is skipped
72
+ because a requested primitive requires a string input.
73
+ UnmatchedPrimitiveWarning: If a requested primitive matched no column
74
+ of its input dtypes anywhere in the walk.
75
+ """
76
+ features = synthesize(
77
+ database=database,
78
+ target_table=target_table,
79
+ agg_primitives=agg_primitives,
80
+ trans_primitives=trans_primitives,
81
+ groupby_trans_primitives=groupby_trans_primitives,
82
+ max_depth=max_depth,
83
+ )
84
+ if features_only:
85
+ return features
86
+ return features.apply(database, cutoff_time), features
87
+
88
+
89
+ def apply_features(
90
+ features: Sequence[Feature],
91
+ database: Database,
92
+ cutoff_time: datetime | None = None,
93
+ ) -> Any:
94
+ """Apply existing feature definitions to a database.
95
+
96
+ Use this to apply a feature set fitted on training data to new data. It
97
+ accepts any sequence of features; a :class:`~tusk.FeatureList` can compute
98
+ itself with :meth:`~tusk.FeatureList.apply` instead.
99
+
100
+ Raises :class:`~tusk.exceptions.SchemaError` if ``features`` is empty,
101
+ spans more than one table, or targets a table with no ``primary_key``, and
102
+ :class:`~tusk.exceptions.PrimitiveError` if an order-dependent primitive
103
+ lands on a table with no ``row_creation_time``.
104
+ :class:`~tusk.exceptions.ValidationError` is raised if ``cutoff_time``
105
+ differs from the database's row creation times in tz awareness, or if
106
+ those disagree among themselves, and ``TypeError`` if ``cutoff_time`` is
107
+ not a ``datetime``.
108
+
109
+ Args:
110
+ features: Feature definitions, all on the same target table.
111
+ database: The database to compute over.
112
+ cutoff_time: Only rows whose ``row_creation_time`` is at or before this
113
+ value are visible, on the target table as well as its relatives, so
114
+ the matrix may have fewer rows than the target. Its tz awareness
115
+ must match the database's row creation times'. A table with no
116
+ ``row_creation_time`` is timeless and passes through unfiltered,
117
+ so a cutoff on a database that declares none is silently a no-op.
118
+ None disables filtering.
119
+
120
+ Returns:
121
+ feature_matrix: An uncomputed query plan in the caller's native frame
122
+ type, with one row per visible target row. tusk never collects, so
123
+ on a backend with a lazy frame type you decide when to compute.
124
+ """
125
+ return FeatureList(features).apply(database, cutoff_time)