Skip to content

Add PolynomialTransformer - #81

Merged
ablaom merged 8 commits into
devfrom
polynomial-transformer
Oct 5, 2026
Merged

ablaom merged 8 commits into
devfrom
polynomial-transformer

Conversation

@ablaom

@ablaom ablaom commented Sep 16, 2026 •

Copy link
Copy Markdown
Member

This PR addresses #45 . InteractionTransformer(; kwargs...) is now equivalent to* PolynomialTransformer(; interactions_only=true, kwargs...) but using InteractionTranformer triggers a depwarn.

I've renamed the field order to degree (seems more usual for a polynomial) but order is retained as a kwarg, aliased to degree.

Here's an example drawn from the new doc string:

using MLJ

X = (
    A = [1, 2, 3],
    B = [4, 5, 6],
    C = [7, 8, 9],
    D = ["cat", "dog", "rat"]
)

transformer = PolynomialTransformer(degree=2, features=[:A, :B])
mach = machine(transformer)
julia> transform(mach, X) |> pretty
┌───────┬───────┬───────┬─────────┬───────┬───────┬───────┐
│ A     │ B     │ C     │ D       │ A_A   │ A_B   │ B_B   │
│ Int64 │ Int64 │ Int64 │ String  │ Int64 │ Int64 │ Int64 │
│ Count │ Count │ Count │ Textual │ Count │ Count │ Count │
├───────┼───────┼───────┼─────────┼───────┼───────┼───────┤
│ 1     │ 4     │ 7     │ cat     │ 1     │ 4     │ 16    │
│ 2     │ 5     │ 8     │ dog     │ 4     │ 10    │ 25    │
│ 3     │ 6     │ 9     │ rat     │ 9     │ 18    │ 36    │
└───────┴───────┴───────┴─────────┴───────┴───────┴───────┘

transformer = PolynomialTransformer(degree=3, interactions_only=true)
mach = machine(transformer)
julia> transform(mach, X) |> pretty
┌───────┬───────┬───────┬─────────┬───────┬───────┬───────┬───────┐
│ A     │ B     │ C     │ D       │ A_B   │ A_C   │ B_C   │ A_B_C │
│ Int64 │ Int64 │ Int64 │ String  │ Int64 │ Int64 │ Int64 │ Int64 │
│ Count │ Count │ Count │ Textual │ Count │ Count │ Count │ Count │
├───────┼───────┼───────┼─────────┼───────┼───────┼───────┼───────┤
│ 1     │ 4     │ 7     │ cat     │ 4     │ 7     │ 28    │ 28    │
│ 2     │ 5     │ 8     │ dog     │ 10    │ 16    │ 40    │ 80    │
│ 3     │ 6     │ 9     │ rat     │ 18    │ 27    │ 54    │ 162   │
└───────┴───────┴───────┴─────────┴───────┴───────┴───────┴───────┘

An additional change: Somewhat confusingly, we had and an empty src/utils.jl but test/utils.jl defines only functions needed to run tests (dataset generators). I moved some code shared by both InteractionTransformer and PolynomialTransformer to src/utils.jl and moved the contents of test/utils.jl to a new file test/test_utils.jl so I could add actual tests of src/utils.jl in test/utils.jl.

*This is not quite true. InteractionTransformer always returns a NamedTuple of Vectors. PolynomialTransformer tries to return a table of the same type as the input table, using Tables.materalizer(input_table).

@ablaom

ablaom commented Sep 16, 2026 •

Copy link
Copy Markdown
Member Author

On another note, the logic for actually generating column combinations in InteractionTransformers uses Combinatorics.jl. I couldn't see similar functionality there to generate complete lists of monomials so had to add custom code for that. It's possible the new code could also be used to get rid of Combinatorics.jl altogether, but I haven't done that here.

@ablaom

ablaom commented Sep 24, 2026

Copy link
Copy Markdown
Member Author

@olivierlabayle Are you willing and able to provide a review here, in the next 3-4 weeks, say? This PR makes a replacement for your InteractionTransformer, as set out in the referenced issue.

@olivierlabayle

Copy link
Copy Markdown
Contributor

@ablaom Yes thank you, please assign me and I'll review!

@ablaom

ablaom commented Sep 28, 2026

Copy link
Copy Markdown
Member Author

Thank you very much.

@olivierlabayle olivierlabayle left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me, two little caveats:

  • You forgot to include the new test file in runtests.jl
  • The "combination with replacement" exists in Combinatorics.jl. Since the MLJTransforms already depends on it, it might be worth outsourcing the algorithm to them.

interactions_only=false,
)
model = PolynomialTransformer(degree, features, interactions_only)
message = MMI.clean!(model)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not throwing here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clean! mutates the model parameter so that it is valid, so no need to throw an exception. Or am I missing the point of your question?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose I am wondering whether it would be better to explicitly throw if a user's code does not make sense rather than assuming a fallback to order=2?

Comment thread test/runtests.jl
using Dates: DateTime, Date, Time, Day, Hour
_get(x) = CategoricalArrays.DataAPI.unwrap(x)

include("test_utils.jl")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably want to include the new include("transformers/other_transformers/polynomial_transformer.jl")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 4d58a8c

Comment thread test/transformers/other_transformers/polynomial_transformer.jl Outdated
# # CORE IMPLEMENTATION

function MMI.transform(model::PolynomialTransformer, _, X)
features = MLJTransforms.actualfeatures(model.features, X)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason to keep the MLJTransforms prefix here and line 142?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary but helpful for maintenance: The method actualfeaures, while not defined in the current file, is defined in MLJTransforms somewhere, and not from the namespace of some other package. At least, this is helpful to me.

premonomials(alphabet, degree, ::WithoutRepetitions) =
premonomials(alphabet, degree, Combinatorics.combinations)
premonomials(alphabet, degree, ::WithRepetitions) =
premonomials(alphabet, degree, orderedwords)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your implementation seems to produce the same results but this is also defined in Combinatorics: Combinatorics.with_replacement_combinations or am I mistaken? At least the tests also pass with this function.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah - I completely missed this, thank you. In eeab9bf I've swapped this in and dumped my orderedwords method.

@olivierlabayle olivierlabayle left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @ablaom , all good for me, I'll let you make the call on the clean! vs throw as it is rather a design choice.

@ablaom

ablaom commented Oct 5, 2026

Copy link
Copy Markdown
Member Author

@olivierlabayle Thanks for your prompt review. As indicated, I adopted all your suggestions.

Maintaining the quality of the MLJ ecosystem depends crucially on volunteer PR reviews such as this one. ❤️

@ablaom
ablaom merged commit 070bec0 into dev Oct 5, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants