Skip to content

feat(workflow-operator): vectorize every named text column, not just one - #7668

Open
kz930 wants to merge 1 commit into
apache:mainfrom
kz930:feat/sklearn-multi-text-columns
Open

feat(workflow-operator): vectorize every named text column, not just one#7668
kz930 wants to merge 1 commit into
apache:mainfrom
kz930:feat/sklearn-multi-text-columns

Conversation

@kz930

@kz930 kz930 commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this PR?

Text Attribute named one column, so a dataset whose text is spread over several could not be used as it stands. A title beside a body, or a description beside a comment, is ordinary, and the only way through was a Concat upstream, which merges the columns into one string and gives up which column each word came from.

Naming several columns was not an alternative either. CountVectorizer takes a flat sequence of documents, so a two-column frame reaches it as two documents rather than as the rows, and it learns a vocabulary of the column names:

>>> CountVectorizer().fit(df[["title", "body"]]).vocabulary_
{'body': 0, 'title': 1}

The field now takes a list, and the generated pipeline opens with a ColumnTransformer that gives each named column its own CountVectorizer and concatenates the results, so a word keeps the column it came from through the feature's prefix:

make_pipeline(ColumnTransformer([("text0", CountVectorizer(), <col>),
                                 ("text1", CountVectorizer(), <col>)]),
              TfidfTransformer(), estimator()).fit(X, Y)

The steps are named by position rather than after the column, which keeps a column whose name carries a double underscore away from the separator get_feature_names_out puts between step and feature. The transformer names the columns it reads, so the frame no longer has to be narrowed before the pipeline and that line goes.

Tfidf Transformer stays one switch over the whole matrix, leaving the shape of the pipeline as it was. Turning Count Vectorizer on still discards the numeric columns, as before: carrying them along needs a way to say which columns are features, which these two families do not have, and that is a separate change.

The operator's output is unchanged, one row of model_name and model. A workflow written before this holds a bare string in the field, which ACCEPT_SINGLE_VALUE_AS_ARRAY reads as a list of one, so nothing has to be migrated.

The field is declared on SklearnModelOpDesc, so this reaches all fifty-one operators of the Sklearn and Sklearn Training groups.

Any related issues, documentation, discussions?

Closes #7667

This touches the same line of the two codegen templates as #7645, which rewrites it while this deletes it. Whichever merges second needs that line resolved by hand: the else branch #7645 introduces is kept, and the if branch this removes stays removed.

How was this PR tested?

SklearnClassifierOpDescCodegenSpec and SklearnTrainingOpDescCodegenSpec pinned the generated pipeline and were updated to the new one, each gaining a case for several columns getting a vectorizer apiece. The fifty-one per-operator specs that pinned the field's default were updated to the empty list. WorkflowOperator/test passes: 2287 tests.

The emitted line was also run as it stands, with the base64 decoding stubbed, against a frame of two text columns and a numeric one: it fits, and the feature names carry the text0__ and text1__ prefixes.

Was this PR authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Claude Opus 5)

Text Attribute named one column, so a dataset whose text is spread over several
could not be used as it stands. A title beside a body, or a description beside a
comment, is ordinary, and the only way through was a Concat upstream, which
merges the columns into one string and gives up which column each word came from.

Naming several columns was not an alternative. CountVectorizer takes a flat
sequence of documents, so a two-column frame reaches it as two documents rather
than as the rows, and it learns a vocabulary of the column names.

The field takes a list, and the generated pipeline opens with a ColumnTransformer
that gives each named column its own CountVectorizer and concatenates the
results, so a word keeps the column it came from through the feature's prefix.
The steps are named by position rather than after the column, keeping a column
whose name carries a double underscore away from the separator
get_feature_names_out puts between step and feature.

The transformer names the columns it reads, so the frame no longer has to be
narrowed before the pipeline, and that line goes.

Tfidf Transformer stays one switch over the whole matrix, which leaves the shape
of the pipeline as it was. Turning Count Vectorizer on still discards the numeric
columns, as before: carrying them along needs a way to say which columns are
features, which these two families do not have.

A workflow written before this holds a bare string in the field, which reads as a
list of one, so nothing has to be migrated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Automated Reviewer Suggestions

Based on the git blame history of the changed files, we recommend the following reviewers:

  • Contributors with relevant context: @aglinxinyuan, @Ma77Ball, @eugenegujing
    You can notify them by mentioning @aglinxinyuan, @Ma77Ball, @eugenegujing in a comment.

@codecov-commenter

codecov-commenter commented Aug 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.77%. Comparing base (12169c2) to head (cd509f8).
⚠️ Report is 15 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #7668      +/-   ##
============================================
+ Coverage     89.65%   89.77%   +0.11%     
- Complexity     4397     4414      +17     
============================================
  Files          1177     1177              
  Lines         46996    47018      +22     
  Branches       5268     5270       +2     
============================================
+ Hits          42136    42212      +76     
+ Misses         3094     3035      -59     
- Partials       1766     1771       +5     
Flag Coverage Δ *Carryforward flag
access-control-service 70.00% <ø> (ø)
agent-service 98.62% <ø> (ø) Carriedforward from 12169c2
amber 86.68% <100.00%> (+0.32%) ⬆️
computing-unit-managing-service 72.46% <ø> (ø)
config-service 77.31% <ø> (ø)
file-service 68.90% <ø> (ø)
frontend 90.67% <ø> (ø) Carriedforward from 12169c2
notebook-migration-service 78.89% <ø> (ø)
pyamber 97.57% <ø> (ø) Carriedforward from 12169c2
workflow-compiling-service 57.89% <ø> (ø)

*This pull request uses carry forward flags. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

Copy link
Copy Markdown
Contributor

✅ No material benchmark regressions detected

🟢 4 better · 🔴 0 worse · ⚪ 11 noise (<±5%) · 0 without baseline

Compared against main 3cb3479 benchmarked on this same runner, so the delta is largely free of cross-runner hardware noise. The "7d avg" column still reflects the gh-pages dashboard. Treat <±5% as noise unless repeated.

Dashboard · Run

config throughput MB/s latency max Δ latest / 7d
🟢 bs=10 sw=10 sl=64 436 0.266 21,966/27,069/27,069 us 🟢 -24.4% / 🔴 +72.8%
🟢 bs=100 sw=10 sl=64 945 0.577 105,328/127,901/127,901 us 🟢 -8.0% / 🔴 +20.1%
bs=1000 sw=10 sl=64 1,102 0.673 903,853/993,171/993,171 us ⚪ within ±5% / 🟢 -7.4%
Baseline details

Latest main 3cb3479 from same runner

config metric PR latest main 7d avg Δ latest Δ 7d
bs=10 sw=10 sl=64 throughput 436 tuples/sec 426 tuples/sec 779.28 tuples/sec +2.3% -44.1%
bs=10 sw=10 sl=64 MB/s 0.266 MB/s 0.26 MB/s 0.476 MB/s +2.3% -44.1%
bs=10 sw=10 sl=64 p50 21,966 us 21,309 us 12,712 us +3.1% +72.8%
bs=10 sw=10 sl=64 p95 27,069 us 35,788 us 16,345 us -24.4% +65.6%
bs=10 sw=10 sl=64 p99 27,069 us 35,788 us 19,050 us -24.4% +42.1%
bs=100 sw=10 sl=64 throughput 945 tuples/sec 942 tuples/sec 1,017 tuples/sec +0.3% -7.1%
bs=100 sw=10 sl=64 MB/s 0.577 MB/s 0.575 MB/s 0.621 MB/s +0.3% -7.1%
bs=100 sw=10 sl=64 p50 105,328 us 103,642 us 100,048 us +1.6% +5.3%
bs=100 sw=10 sl=64 p95 127,901 us 139,038 us 106,477 us -8.0% +20.1%
bs=100 sw=10 sl=64 p99 127,901 us 139,038 us 114,739 us -8.0% +11.5%
bs=1000 sw=10 sl=64 throughput 1,102 tuples/sec 1,111 tuples/sec 1,048 tuples/sec -0.8% +5.2%
bs=1000 sw=10 sl=64 MB/s 0.673 MB/s 0.678 MB/s 0.639 MB/s -0.7% +5.3%
bs=1000 sw=10 sl=64 p50 903,853 us 906,557 us 976,350 us -0.3% -7.4%
bs=1000 sw=10 sl=64 p95 993,171 us 951,570 us 1,022,084 us +4.4% -2.8%
bs=1000 sw=10 sl=64 p99 993,171 us 951,570 us 1,053,520 us +4.4% -5.7%
Raw CSV
config_idx,batch_size,schema_width,string_len,num_batches,total_ms,total_tuples,total_bytes,tuples_per_sec,mb_per_sec,lat_p50_us,lat_p95_us,lat_p99_us
0,10,10,64,20,458.68,200,128000,436,0.266,21966.28,27068.89,27068.89
1,100,10,64,20,2116.06,2000,1280000,945,0.577,105327.67,127900.51,127900.51
2,1000,10,64,20,18140.91,20000,12800000,1102,0.673,903853.08,993171.22,993171.22

@kz930

kz930 commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

@aglinxinyuan @carloea2 May you take a look at it?

@aglinxinyuan aglinxinyuan 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.

Can you test with a real workflow?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sklearn operators vectorize one text column, so a dataset whose text spans several cannot be used

3 participants