Skip to content

feat(workflow-operator): volcano plot significance cut-offs - #9041

Open
eugenegujing wants to merge 5 commits into
apache:mainfrom
eugenegujing:feat/volcano-plot-significance-thresholds
Open

eugenegujing wants to merge 5 commits into
apache:mainfrom
eugenegujing:feat/volcano-plot-significance-thresholds

Conversation

@eugenegujing

@eugenegujing eugenegujing commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

What changes were proposed in this PR?

The Volcano Plot draws a plain scatter of effect size vs -log10(p), colored by effect size, which is already the x axis. Nothing shows where the significance bar is or which points clear it. This PR adds three optional properties, all off by default, so saved workflows render as before.

Property Type Effect when set
Significance threshold number, 0 < x <= 1 dashed horizontal line at -log10(threshold)
Effect-size threshold number, > 0 dashed vertical lines at +threshold and -threshold
Color by significance boolean with both thresholds set, points are colored Up / Down / Not significant with a legend instead of the continuous RdBu scale
Before:  px.scatter(color=<effect column>, color_continuous_scale="RdBu")
After:   same by default
         + fig.add_hline(...)                        when a significance threshold is set
         + fig.add_vline(+eff) / fig.add_vline(-eff) when an effect-size threshold is set
         + color="Significance", 3-color map         when the toggle is on and both thresholds are set

Categories use strict comparisons on the selected p-value column: Up = p < sig and effect > eff, Down = p < sig and effect < -eff, everything else Not significant. The category is written to a column named Significance (also the legend title); an input column of that name would be shadowed inside the operator only. Colors are ColorBrewer RdBu end points #B2182B / #2166AC plus grey #9E9E9E.

Validation: the JSON schema bounds above stop bad values in the property panel. For hand-edited JSON the operator also ignores a threshold it cannot draw (NaN, infinite, <= 0, or a p cut-off > 1) and falls back to the continuous coloring if the toggle is on but a threshold is missing. A cut-off of exactly 1 is allowed and draws its line at y = 0. If the cut-off's -log10 is beyond the data range the line may fall outside the auto-ranged axis; extending the range is left for a follow-up.

Since #8504 merged, the operator also exports a standalone script through generateStandaloneCode(). This PR adds the cut-off lines, the Significance column and the three-color map there too, from the same helpers where possible: the color map and category order are one shared literal, the threshold lines come from the same builder, and only the column names differ (Python literals instead of decoded strings). A spec checks that both paths make the same line and coloring decisions for eight threshold shapes, so the exported figure cannot drift from the engine's.

Independent of the label-fix (#9037) and hover-column (#9038) PRs; all three touch neighbouring lines of VolcanoPlotOpDesc.scala, so whichever lands second needs a small rebase.

Figures below are the operator's generated Python from main and from this branch, run on the 430-gene example workflow from the issue, with thresholds 0.05 and 1.

Before After (lines only) After (lines + Color by significance)
before-main thresholds-after-lines-only thresholds-after-colored

Any related issues, documentation, discussions?

Closes #9039

PR #8504 (merged) added the generateStandaloneCode() export path; the mirroring described above is the follow-up it needed.

How was this PR tested?

New tests in VolcanoPlotOpDescSpec, written before the source change:

  • defaults: no lines, continuous coloring; each threshold alone emits only its own line(s); both with the toggle off keep the continuous coloring
  • toggle on with both thresholds: strict comparisons, np.select column, exact color map and category order, no color_continuous_scale
  • toggle on with one or no threshold: continuous fallback
  • zero / negative thresholds and a p cut-off above 1 emit no line of their own; Infinity, -Infinity, NaN from JSON never reach the Python
  • a cut-off of exactly 1 draws its line and still colors by category
  • thresholds are emitted as valid Python floats, checked for 1e-300, Double.MIN_VALUE and 1e21 in every site they appear
  • the generated JSON schema carries exclusiveMinimum / maximum on the two thresholds
  • JSON deserialization: numbers, numeric strings, null, blank, absent, and the boolean toggle
  • a workflow JSON saved before this change generates byte-for-byte the same code as a default-constructed operator
  • generateStandaloneCode: defaults draw no line and keep the continuous coloring; thresholds with the toggle off draw the dashed lines only; the toggle on colors Up / Down / Not significant with the column names as literals; a cross-path test checks that the exported script makes the same cut-off and coloring decisions as the engine for eight threshold shapes (unset, one side only, zero, p cut-off above 1, non-finite, exactly 1); a saved workflow renders as a default-constructed operator
sbt -Dsbt.server.forcestart=true "WorkflowOperator/testOnly *VolcanoPlotOpDescSpec"
# Tests: succeeded 30, failed 0, canceled 0, ignored 0, pending 1   (the pending one is pre-existing)

sbt -Dsbt.server.forcestart=true "WorkflowOperator/scalafmtAll" "WorkflowOperator/Test/scalafmtAll"
sbt -Dsbt.server.forcestart=true "WorkflowOperator/scalafixAll --check"
# success

Smoke run outside Texera (pytexera stubbed, pandas + plotly 5.24.1) on the 430-gene mock table, 420 rows after the operator drops zero and missing p-values:

Configuration Lines Traces
defaults none 1 continuous trace, 420 points
0.05 / 1, toggle off hline y = 1.301, vlines x = ±1 1 continuous trace
0.05 / 1, toggle on same Up 35, Down 35, Not significant 350
0.05 only, toggle on hline only continuous (fallback)
0.05 / -1, toggle on hline only continuous (fallback)
2 / 1, toggle on vlines only continuous (fallback)
"Infinity" / "NaN" from JSON none continuous, no such token in the code
1e-300 / 1e21, toggle on hline y = 300, vlines x = ±1e21 Not significant 420
1 / 1, toggle on hline y = 0, vlines x = ±1 Up 41, Down 38, Not significant 341

The exponent-form dumps pass python -m py_compile. The exported script for the default, lines-only and colored configurations parses with ast.parse.

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

Yes. Generated-by: Claude Code (claude-fable-5-1). Co-authored with Claude; the author reviewed the change before submission.

Add optional Significance threshold and Effect-size threshold properties that draw dashed cut-off lines at -log10(threshold) and at plus and minus the effect-size threshold.
Add a Color by significance toggle that, with both thresholds set, colors points Up, Down or Not significant instead of by effect size.
Bound both thresholds in the JSON schema and ignore a non-positive, non-finite or above-one value instead of drawing it.
@github-actions

github-actions Bot commented Oct 20, 2026 •

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, @Yicong-Huang
    You can notify them by mentioning @aglinxinyuan, @Yicong-Huang in a comment.

@codecov-commenter

codecov-commenter commented Oct 24, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.79%. Comparing base (d6c9050) to head (daecff7).

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #9041      +/-   ##
============================================
- Coverage     91.85%   91.79%   -0.06%     
- Complexity     5020     5045      +25     
============================================
  Files          1279     1273       -6     
  Lines         56192    55610     -582     
  Branches       7320     7156     -164     
============================================
- Hits          51615    51049     -566     
- Misses         2741     2750       +9     
+ Partials       1836     1811      -25     
Flag Coverage Δ *Carryforward flag
access-control-service 77.44% <ø> (ø)
agent-service 99.53% <ø> (ø) Carriedforward from 11ab0ce
amber 85.97% <100.00%> (+0.07%) ⬆️
computing-unit-managing-service 64.05% <ø> (ø)
config-service 87.61% <ø> (ø)
file-service 82.29% <ø> (ø)
frontend 96.46% <ø> (-0.08%) ⬇️ Carriedforward from 11ab0ce
notebook-migration-service 83.77% <ø> (ø)
pyamber 98.54% <ø> (+0.07%) ⬆️ Carriedforward from 11ab0ce
workflow-compiling-service 77.95% <ø> (ø)

*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

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ Benchmark changes need a look

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

Compared against main d6c9050 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 413 0.252 22,956/33,180/33,180 us 🔴 +12.5% / 🔴 +157.4%
🔴 bs=100 sw=10 sl=64 840 0.513 115,420/171,508/171,508 us 🔴 +25.5% / 🔴 +92.8%
⚪ bs=1000 sw=10 sl=64 986 0.602 1,009,955/1,055,749/1,055,749 us ⚪ within ±5% / 🔴 +23.8%
Baseline details

Latest main d6c9050 from same runner

config metric PR latest main 7d avg Δ latest Δ 7d
bs=10 sw=10 sl=64 throughput 413 tuples/sec 456 tuples/sec 953.99 tuples/sec -9.4% -56.7%
bs=10 sw=10 sl=64 MB/s 0.252 MB/s 0.278 MB/s 0.582 MB/s -9.4% -56.7%
bs=10 sw=10 sl=64 p50 22,956 us 20,412 us 10,584 us +12.5% +116.9%
bs=10 sw=10 sl=64 p95 33,180 us 32,778 us 12,888 us +1.2% +157.4%
bs=10 sw=10 sl=64 p99 33,180 us 32,778 us 16,266 us +1.2% +104.0%
bs=100 sw=10 sl=64 throughput 840 tuples/sec 895 tuples/sec 1,242 tuples/sec -6.1% -32.4%
bs=100 sw=10 sl=64 MB/s 0.513 MB/s 0.546 MB/s 0.758 MB/s -6.0% -32.3%
bs=100 sw=10 sl=64 p50 115,420 us 112,419 us 83,370 us +2.7% +38.4%
bs=100 sw=10 sl=64 p95 171,508 us 136,617 us 88,966 us +25.5% +92.8%
bs=100 sw=10 sl=64 p99 171,508 us 136,617 us 101,186 us +25.5% +69.5%
bs=1000 sw=10 sl=64 throughput 986 tuples/sec 988 tuples/sec 1,276 tuples/sec -0.2% -22.8%
bs=1000 sw=10 sl=64 MB/s 0.602 MB/s 0.603 MB/s 0.779 MB/s -0.2% -22.7%
bs=1000 sw=10 sl=64 p50 1,009,955 us 1,015,961 us 815,662 us -0.6% +23.8%
bs=1000 sw=10 sl=64 p95 1,055,749 us 1,046,858 us 864,800 us +0.8% +22.1%
bs=1000 sw=10 sl=64 p99 1,055,749 us 1,046,858 us 896,884 us +0.8% +17.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,483.70,200,128000,413,0.252,22955.54,33179.69,33179.69
1,100,10,64,20,2381.79,2000,1280000,840,0.513,115420.42,171508.39,171508.39
2,1000,10,64,20,20278.61,20000,12800000,986,0.602,1009955.23,1055749.07,1055749.07

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

LGTM. The schema bounds plus the runtime filter cover both the property panel and hand-edited JSON.

…ignificance-thresholds

# Conflicts:
#	common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/volcanoPlot/VolcanoPlotOpDesc.scala

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

LGTM!

val sig = significanceCutoff.get
val eff = effectCutoff.get
pyb"""significant = table[$pvalueColumn] < $sig
| table["Significance"] = np.select(

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.

Writing the categories into table["Significance"] overwrites an input column of that name before px reads the table. If that column is the selected effect column, the x axis turns into the Up / Down / Not significant labels (I checked with plotly 5.24.1). Passing the categories straight to px leaves the table alone: significance = np.select(...), then color=significance, labels={"color": "Significance"} and category_orders={"color": [...]} keep the same legend title, colors and order. The standalone twin below needs the same change.

Comment on lines +94 to +110
it should "draw a dashed horizontal line at -log10 of the significance threshold" in {
val op = configured
op.significanceThreshold = Some(0.05)
val code = op.generatePythonCode()
code should include(hline)
code should not include "add_vline"
assertContinuousColoring(code)
}

it should "draw dashed vertical lines at plus and minus the effect-size threshold" in {
val op = configured
op.effectSizeThreshold = Some(1.0)
val code = op.generatePythonCode()
code should include(vlines)
code should not include "add_hline"
assertContinuousColoring(code)
}

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.

A few of the new tests pin nothing another one doesn't:

  • These two single-threshold tests: the fallback test further down already checks that each threshold alone draws only its own lines with the continuous coloring, and the toggle-off test checks the lines don't depend on the toggle.
  • "hold Doubles" repeats "deserialize numeric strings", which already fails without contentAs.
  • The export path's toggle-off and saved-workflow tests: both paths draw the lines from the same thresholdLines, the parity test covers the toggle-off row, and the engine's saved-workflow test already shows a saved operator matches a default one (its assertions after the equality check can go too).

This branch has not been deployed

No deployments
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.

Volcano Plot: optional significance / effect-size thresholds with cut-off lines and up / down / not-significant coloring

4 participants