Fix peak detection for Mann-Whitney U test by inverting P-value magnitude In anomaly_peaks.go, invert the magnitude calculation for MannWhitneyU so that smaller P-values (which are more significant) result in larger magnitudes, correctly identifying them as peaks. Update caller in anomaly_bounds_refiner.go and add tests in anomaly_peaks_test.go. Change-Id: I210175f89d306e6829ea052e0b4635ff548521b6 Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/1227656 Commit-Queue: Viktar Zinkou <viktarzinkou@google.com> Reviewed-by: Ruslan Schalkenbajew <sruslan@google.com>
diff --git a/perf/go/regression/refiner/anomaly_bounds_refiner.go b/perf/go/regression/refiner/anomaly_bounds_refiner.go index cc05db7..db80699 100644 --- a/perf/go/regression/refiner/anomaly_bounds_refiner.go +++ b/perf/go/regression/refiner/anomaly_bounds_refiner.go
@@ -115,7 +115,7 @@ // and delegates to refineGroup to determine the true boundaries of the anomaly. It returns a single // ConfirmedRegression that encompasses the refined anomalous range. func (r *AnomalyBoundsRefiner) mergeArea(cfg *alerts.Alert, area []*regression.RegressionDetectionResponse) *regression.ConfirmedRegression { - peakIdx := findPeaks(area) + peakIdx := findPeaks(area, cfg.Step) return r.refineGroup(area, peakIdx, cfg) }
diff --git a/perf/go/regression/refiner/anomaly_peaks.go b/perf/go/regression/refiner/anomaly_peaks.go index fe4aeca..0d0c3f1 100644 --- a/perf/go/regression/refiner/anomaly_peaks.go +++ b/perf/go/regression/refiner/anomaly_peaks.go
@@ -4,6 +4,7 @@ "math" "go.skia.org/infra/perf/go/regression" + "go.skia.org/infra/perf/go/types" ) // PeakIndexes holds the indices of a peak in a regression group. @@ -32,7 +33,7 @@ // is strictly greater than its immediate neighbors (or a flat region of equal magnitudes > neighbors). // 2. Global Max: The point with the highest StepFit regression magnitude in the group. // If there's a flat region of max values, the center index of that flat region is returned. -func findPeaks(group []*regression.RegressionDetectionResponse) PeakIndexes { +func findPeaks(group []*regression.RegressionDetectionResponse, algo types.StepDetection) PeakIndexes { n := len(group) if n == 0 { return PeakIndexes{} @@ -43,7 +44,13 @@ return -1.0 // Implicitly smaller than any valid magnitude (>=0) } // Validated in validateInput to have Summary/Clusters/StepFit - return math.Abs(float64(group[i].Summary.Clusters[0].StepFit.Regression)) + mag := math.Abs(float64(group[i].Summary.Clusters[0].StepFit.Regression)) + if algo == types.MannWhitneyU { + // For Mann-Whitney, smaller P-value is better. + // Invert it so that smaller P-values give larger magnitudes. + return 1.0 - mag + } + return mag } localPeaks := findLocalPeaks(n, getMag)
diff --git a/perf/go/regression/refiner/anomaly_peaks_test.go b/perf/go/regression/refiner/anomaly_peaks_test.go index 5f6cec7..2ab0ebd 100644 --- a/perf/go/regression/refiner/anomaly_peaks_test.go +++ b/perf/go/regression/refiner/anomaly_peaks_test.go
@@ -8,6 +8,7 @@ "go.skia.org/infra/perf/go/dataframe" "go.skia.org/infra/perf/go/regression" "go.skia.org/infra/perf/go/stepfit" + "go.skia.org/infra/perf/go/types" "go.skia.org/infra/perf/go/ui/frame" ) @@ -110,7 +111,62 @@ for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expected, findPeaks(tc.input)) + assert.Equal(t, tc.expected, findPeaks(tc.input, types.AbsoluteStep)) + }) + } +} + +func TestFindPeaks_MannWhitney_ReturnsStructuredPeaks(t *testing.T) { + // Helper to create a response with a specific regression value (P-value) + r := func(val float64) *regression.RegressionDetectionResponse { + return ®ression.RegressionDetectionResponse{ + Frame: &frame.FrameResponse{DataFrame: &dataframe.DataFrame{}}, + Summary: &clustering2.ClusterSummaries{ + Clusters: []*clustering2.ClusterSummary{ + { + StepFit: &stepfit.StepFit{ + Regression: float32(val), + Status: stepfit.LOW, + }, + StepPoint: &dataframe.ColumnHeader{Offset: 100}, + Keys: []string{"k"}, + }, + }, + }, + } + } + + tests := []struct { + name string + input []*regression.RegressionDetectionResponse + expected PeakIndexes + }{ + { + name: "Single Peak (Smallest P-value is peak)", + input: []*regression.RegressionDetectionResponse{r(0.05), r(0.01), r(0.05)}, + expected: PeakIndexes{ + LeftIndex: 1, RightIndex: 1, MaxIndex: 1, + }, + }, + { + name: "Flat Peak", + input: []*regression.RegressionDetectionResponse{r(0.05), r(0.01), r(0.01), r(0.05)}, + expected: PeakIndexes{ + LeftIndex: 1, RightIndex: 2, MaxIndex: 2, + }, + }, + { + name: "Multiple Peaks - Global Max Middle", + input: []*regression.RegressionDetectionResponse{r(0.05), r(0.03), r(0.05), r(0.001), r(0.05), r(0.03), r(0.05)}, + expected: PeakIndexes{ + LeftIndex: 1, RightIndex: 5, MaxIndex: 3, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expected, findPeaks(tc.input, types.MannWhitneyU)) }) } }