Changeset 230295 in webkit


Ignore:
Timestamp:
Apr 4, 2018 9:49:21 PM (6 years ago)
Author:
Dewei Zhu
Message:

Added UI to show potential regressions in chart with t-testing against segmentations.
https://bugs.webkit.org/show_bug.cgi?id=184131

Reviewed by Ryosuke Niwa.

Added UI in the chart-pane so that user can use new option in trendline which not only
shows the segmentation, but also t-test against potential changes indicated by segmentation.

Fix a bug in AnalysisTaskPage that chart is not updated when change type of task changes.

  • public/shared/statistics.js: Added a function to t-test certain range based on segmentation results.

(Statistics.supportedOneSideTTestProbabilities):
(Statistics.findRangesForChangeDetectionsWithWelchsTTest): The argument segmentations, every 2 items in the list defines
segmentation, that is why the index incremental is 2 in this funcion.

  • public/v3/components/chart-pane-base.js: Will select the range if user clicks on a suggested annotation.

(ChartPaneBase.prototype.configure):
(ChartPaneBase.prototype._didClickAnnotation):

  • public/v3/components/chart-styles.js:

(ChartStyles.annotationFillStyleForTask): Added 'annotationFillStyleForTask' to determine the fillStyle for annotation based on change type of a analysis task.

  • public/v3/components/interactive-time-series-chart.js:

(InteractiveTimeSeriesChart.prototype._findAnnotation): Also need to search among suggested annotaions.

  • public/v3/components/time-series-chart.js: Introduced 'suggested annotaion' which does not have an existing task and is suggested by t-test based on segmentation.

(TimeSeriesChart):
(TimeSeriesChart.prototype.setSuggestedAnnotations):
(TimeSeriesChart.prototype.allAnnotations): Returns both annotations with and without analysis task.
(TimeSeriesChart.prototype._layoutAnnotationBars): Should take all annotations in the calculation.

  • public/v3/models/measurement-set.js:

(MeasurementSet.prototype.metricId): Returns metric id.

  • public/v3/models/metric.js:

(Metric.prototype.summarizeForValues): Added helper function to summarize a given value

  • public/v3/models/test-group.js:

(TestGroup.prototype.compareTestResults): Adapted to use 'Metric.summarizeForValues'.

  • public/v3/pages/chart-pane.js: Added 'Segmentation with t-test analysis' to 'ChartTrendLineTypes'.

(ChartPane.prototype._renderTrendLinePopover):
(ChartPane.prototype.async._updateTrendLine): make it an async function.

  • unit-tests/statistics-tests.js: Added unit tests for 'findRangesForChangeDetectionsWithWelchsTTest'.
Location:
trunk/Websites/perf.webkit.org
Files:
13 edited

Legend:

Unmodified
Added
Removed
  • trunk/Websites/perf.webkit.org/ChangeLog

    r230151 r230295  
     12018-03-29  Dewei Zhu  <dewei_zhu@apple.com>
     2
     3        Added UI to show potential regressions in chart with t-testing against segmentations.
     4        https://bugs.webkit.org/show_bug.cgi?id=184131
     5
     6        Reviewed by Ryosuke Niwa.
     7
     8        Added UI in the chart-pane so that user can use new option in trendline which not only
     9        shows the segmentation, but also t-test against potential changes indicated by segmentation.
     10
     11        Fix a bug in AnalysisTaskPage that chart is not updated when change type of task changes.
     12
     13        * browser-tests/interactive-time-series-chart-tests.js: Fix a unit tests.
     14        * browser-tests/time-series-chart-tests.js: Fix a unit tests.
     15        * public/shared/statistics.js: Added a function to t-test certain range based on segmentation results.
     16        (Statistics.supportedOneSideTTestProbabilities):
     17        (Statistics.findRangesForChangeDetectionsWithWelchsTTest): The argument `segmentations`, every 2 items in the list defines
     18        segmentation, that is why the index incremental is 2 in this funcion.
     19        * public/v3/components/chart-pane-base.js: Will select the range if user clicks on a suggested annotation.
     20        (ChartPaneBase.prototype.configure):
     21        (ChartPaneBase.prototype._didClickAnnotation):
     22        * public/v3/components/chart-styles.js:
     23        (ChartStyles.annotationFillStyleForTask): Added 'annotationFillStyleForTask' to determine the fillStyle for annotation based on change type of a analysis task.
     24        * public/v3/components/interactive-time-series-chart.js:
     25        (InteractiveTimeSeriesChart.prototype._findAnnotation): Also need to search among suggested annotaions.
     26        * public/v3/components/time-series-chart.js: Introduced 'suggested annotaion' which does not have an existing task and is suggested by t-test based on segmentation.
     27        (TimeSeriesChart):
     28        (TimeSeriesChart.prototype.setSuggestedAnnotations):
     29        (TimeSeriesChart.prototype.allAnnotations): Returns both annotations with and without analysis task.
     30        (TimeSeriesChart.prototype._layoutAnnotationBars): Should take all annotations in the calculation.
     31        * public/v3/models/measurement-set.js:
     32        (MeasurementSet.prototype.metricId): Returns metric id.
     33        * public/v3/models/metric.js:
     34        (Metric.prototype.summarizeForValues): Added helper function to summarize a given value
     35        * public/v3/models/test-group.js:
     36        (TestGroup.prototype.compareTestResults): Adapted to use 'Metric.summarizeForValues'.
     37        * public/v3/pages/chart-pane.js: Added 'Segmentation with t-test analysis' to 'ChartTrendLineTypes'.
     38        (ChartPane.prototype._renderTrendLinePopover):
     39        (ChartPane.prototype.async._updateTrendLine): make it an async function.
     40        * unit-tests/statistics-tests.js: Added unit tests for 'findRangesForChangeDetectionsWithWelchsTTest'.
     41
    1422018-04-02  Aakash Jain  <aakash_jain@apple.com>
    243
  • trunk/Websites/perf.webkit.org/browser-tests/interactive-time-series-chart-tests.js

    r213119 r230295  
    495495        return ChartTest.importChartScripts(context).then(() => {
    496496            const chart = ChartTest.createInteractiveChartWithSampleCluster(context, null,
    497                 {annotations: { textStyle: '#000', textBackground: '#fff', minWidth: 3, barHeight: 10, barSpacing: 1}});
     497                {annotations: { textStyle: '#000', textBackground: '#fff', minWidth: 3, barHeight: 10, barSpacing: 1, fillStyle: '#ccc'}});
    498498
    499499            chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
     
    506506                endTime: ChartTest.sampleCluster.endTime - diff / 4,
    507507                label: 'hello, world',
    508                 fillStyle: 'rgb(0, 0, 255)',
    509             }]
     508            }];
    510509            chart.setAnnotations(annotations);
    511510
  • trunk/Websites/perf.webkit.org/browser-tests/time-series-chart-tests.js

    r213122 r230295  
    801801            const context = new BrowsingContext();
    802802            return ChartTest.importChartScripts(context).then(() => {
    803                 const options = {annotations: { textStyle: '#000', textBackground: '#fff', minWidth: 3, barHeight: 7, barSpacing: 2}};
     803                const options = {annotations: { textStyle: '#000', textBackground: '#fff', minWidth: 3, barHeight: 7, barSpacing: 2, fillStyle: '#ccc'}};
    804804                const chartWithoutAnnotations = ChartTest.createChartWithSampleCluster(context, null, options);
    805805                const chartWithAnnotations = ChartTest.createChartWithSampleCluster(context, null, options);
     
    819819                        endTime: ChartTest.sampleCluster.startTime + diff / 2,
    820820                        label: 'hello, world',
    821                         fillStyle: 'rgb(0, 0, 255)',
    822821                    }]);
    823822
  • trunk/Websites/perf.webkit.org/public/shared/statistics.js

    r227182 r230295  
    3838            supportedProbabilities.push(oneSidedToTwoSidedProbability(probability).toFixed(2));
    3939        return supportedProbabilities
     40    }
     41
     42    this.supportedOneSideTTestProbabilities = function () {
     43        return Object.keys(tDistributionByOneSidedProbability);
    4044    }
    4145
     
    106110        };
    107111    }
     112
     113    this.findRangesForChangeDetectionsWithWelchsTTest = function (values, segmentations, oneSidedPossibility) {
     114        if (!values.length)
     115            return [];
     116
     117        const selectedRanges = [];
     118        const twoSidedFromOneSidedPossibility = 2 * oneSidedPossibility - 1;
     119
     120        for (let i = 1; i + 2 < segmentations.length; i += 2) {
     121            let found = false;
     122            const previousMean = segmentations[i].value;
     123            const currentMean = segmentations[i + 1].value;
     124            console.assert(currentMean != previousMean);
     125            const currentChangePoint = segmentations[i].seriesIndex;
     126            const start = segmentations[i - 1].seriesIndex;
     127            const end = segmentations[i + 2].seriesIndex;
     128
     129            for (let leftEdge = currentChangePoint - 2, rightEdge = currentChangePoint + 2; leftEdge >= start && rightEdge <= end; leftEdge--, rightEdge++) {
     130                const result = this.computeWelchsT(values, leftEdge, currentChangePoint - leftEdge, values, currentChangePoint, rightEdge - currentChangePoint, twoSidedFromOneSidedPossibility);
     131                if (result.significantlyDifferent) {
     132                    selectedRanges.push({
     133                        startIndex: leftEdge,
     134                        endIndex: rightEdge - 1,
     135                        segmentationStartValue: previousMean,
     136                        segmentationEndValue: currentMean
     137                    });
     138                    found = true;
     139                    break;
     140                }
     141            }
     142            if (!found && Statistics.debuggingTestingRangeNomination)
     143                console.log('Failed to find a testing range at', currentChangePoint, 'changing from', previousMean, 'to', currentMean);
     144        }
     145
     146        return selectedRanges;
     147    };
    108148
    109149    function sampleMeanAndVarianceForValues(values, startIndex, length) {
  • trunk/Websites/perf.webkit.org/public/v3/components/chart-pane-base.js

    r219381 r230295  
    2020        this._commitLogViewer = null;
    2121        this._tasksForAnnotations = null;
    22         this._renderedAnnotations = false;
     22        this._detectedAnnotations = null;
     23        this._renderAnnotationsLazily = new LazilyEvaluatedFunction(this._renderAnnotations.bind(this));
    2324    }
    2425
     
    5253        this._mainChart.listenToAction('selectionChange', this._mainSelectionDidChange.bind(this));
    5354        this._mainChart.listenToAction('zoom', this._mainSelectionDidZoom.bind(this));
    54         this._mainChart.listenToAction('annotationClick', this._openAnalysisTask.bind(this));
     55        this._mainChart.listenToAction('annotationClick', this._didClickAnnotation.bind(this));
    5556        this.renderReplace(this.content().querySelector('.chart-pane-main'), this._mainChart);
    5657
     
    9899        AnalysisTask.fetchByPlatformAndMetric(this._platformId, this._metricId, noCache).then(function (tasks) {
    99100            self._tasksForAnnotations = tasks;
    100             self._renderedAnnotations = false;
    101101            self.enqueueToRender();
    102102        });
     
    106106    didUpdateAnnotations()
    107107    {
    108         this._renderedAnnotations = false;
     108        this._tasksForAnnotations = [...this._tasksForAnnotations];
    109109        this.enqueueToRender();
    110110    }
     
    170170        this._commitLogViewer.view(this._openRepository, range.from, range.to);
    171171        this.enqueueToRender();
     172    }
     173
     174    _didClickAnnotation(annotation)
     175    {
     176        if (annotation.task)
     177            this._openAnalysisTask(annotation);
     178        else {
     179            const newSelection = [annotation.startTime, annotation.endTime];
     180            this._mainChart.setSelection(newSelection);
     181            this._overviewChart.setSelection(newSelection, this);
     182            this.enqueueToRender();
     183        }
    172184    }
    173185
     
    247259            this._overviewChart.enqueueToRender();
    248260
    249         if (this._mainChart)
     261        if (this._mainChart) {
    250262            this._mainChart.enqueueToRender();
     263            this._renderAnnotationsLazily.evaluate(this._tasksForAnnotations, this._detectedAnnotations);
     264        }
    251265
    252266        if (this._errorMessage) {
     
    255269        }
    256270
    257         this._renderAnnotations();
    258271
    259272        if (this._mainChartStatus)
     
    269282    }
    270283
    271     _renderAnnotations()
    272     {
    273         if (!this._tasksForAnnotations || this._renderedAnnotations)
    274             return;
    275         this._renderedAnnotations = true;
    276 
    277         var annotations = this._tasksForAnnotations.map(function (task) {
    278             var fillStyle = '#fc6';
    279             switch (task.changeType()) {
    280             case 'inconclusive':
    281                 fillStyle = '#fcc';
    282                 break;
    283             case 'progression':
    284                 fillStyle = '#39f';
    285                 break;
    286             case 'regression':
    287                 fillStyle = '#c60';
    288                 break;
    289             case 'unchanged':
    290                 fillStyle = '#ccc';
    291                 break;
    292             }
    293 
     284    _renderAnnotations(taskForAnnotations, detectedAnnotations)
     285    {
     286        let annotations = (taskForAnnotations || []).map((task) => {
    294287            return {
    295                 task: task,
     288                task,
     289                fillStyle: ChartStyles.annotationFillStyleForTask(task),
    296290                startTime: task.startTime(),
    297291                endTime: task.endTime(),
    298                 label: task.label(),
    299                 fillStyle: fillStyle,
     292                label: task.label()
    300293            };
    301294        });
     295
     296        annotations = annotations.concat(detectedAnnotations || []);
     297
    302298        this._mainChart.setAnnotations(annotations);
    303299    }
  • trunk/Websites/perf.webkit.org/public/v3/components/chart-styles.js

    r213300 r230295  
    153153            minWidth: 3,
    154154            barHeight: 7,
    155             barSpacing: 2,
     155            barSpacing: 2
    156156        };
    157157        return options;
    158158    }
     159
     160    static annotationFillStyleForTask(task) {
     161        if (!task)
     162            return '#888';
     163
     164        switch (task.changeType()) {
     165            case 'inconclusive':
     166                return '#fcc';
     167            case 'progression':
     168                return '#39f';
     169            case 'regression':
     170                return '#c60';
     171            case 'unchanged':
     172                return '#ccc';
     173        }
     174        return '#fc6';
     175
     176    }
    159177}
  • trunk/Websites/perf.webkit.org/public/v3/components/interactive-time-series-chart.js

    r213300 r230295  
    342342            return null;
    343343
    344         for (var item of this._annotations) {
     344        for (const item of this._annotations) {
    345345            if (item.x <= cursorLocation.x && cursorLocation.x <= item.x + item.width
    346346                && item.y <= cursorLocation.y && cursorLocation.y <= item.y + item.height)
  • trunk/Websites/perf.webkit.org/public/v3/models/measurement-set.js

    r212520 r230295  
    2323
    2424    platformId() { return this._platformId; }
     25    metricId() { return this._metricId; }
    2526
    2627    static findSet(platformId, metricId, lastModified)
     
    226227            var addSegment = function (startingPoint, endingPoint) {
    227228                var value = Statistics.mean(timeSeries.valuesBetweenRange(startingPoint.seriesIndex, endingPoint.seriesIndex));
    228                 segmentationSeries.push({value: value, time: startingPoint.time, interval: function () { return null; }});
    229                 segmentationSeries.push({value: value, time: endingPoint.time, interval: function () { return null; }});
     229                segmentationSeries.push({value: value, time: startingPoint.time, seriesIndex: startingPoint.seriesIndex, interval: function () { return null; }});
     230                segmentationSeries.push({value: value, time: endingPoint.time, seriesIndex: endingPoint.seriesIndex, interval: function () { return null; }});
    230231            };
    231232
  • trunk/Websites/perf.webkit.org/public/v3/models/metric.js

    r215633 r230295  
    8686    }
    8787
     88    labelForDifference(beforeValue, afterValue, progressionTypeName, regressionTypeName)
     89    {
     90        const diff = afterValue - beforeValue;
     91        const changeType = diff < 0 == this.isSmallerBetter() ? progressionTypeName : regressionTypeName;
     92        const relativeChange = diff / beforeValue * 100;
     93        const changeLabel = Math.abs(relativeChange).toFixed(2) + '% ' + changeType;
     94        return {changeType, relativeChange, changeLabel};
     95    }
     96
    8897    makeFormatter(sigFig, alwaysShowSign) { return Metric.makeFormatter(this.unit(), sigFig, alwaysShowSign); }
    8998
  • trunk/Websites/perf.webkit.org/public/v3/models/test-group.js

    r222219 r230295  
    145145
    146146        if (beforeValues.length && afterValues.length) {
    147             var diff = afterMean - beforeMean;
    148             var smallerIsBetter = metric.isSmallerBetter();
    149             var changeType = diff < 0 == smallerIsBetter ? 'better' : 'worse';
    150             var changeLabel = Math.abs(diff / beforeMean * 100).toFixed(2) + '% ' + changeType;
     147            const summary = metric.labelForDifference(beforeMean, afterMean, 'better', 'worse');
     148            result.changeType = summary.changeType;
     149            result.changeLabel = summary.changeLabel;
    151150            var isSignificant = Statistics.testWelchsT(beforeValues, afterValues);
    152151            var significanceLabel = isSignificant ? 'significant' : 'insignificant';
    153152
    154             result.changeType = changeType;
    155             result.label = changeLabel;
    156153            if (hasCompleted)
    157154                result.status = isSignificant ? result.changeType : 'unchanged';
  • trunk/Websites/perf.webkit.org/public/v3/pages/analysis-task-page.js

    r219321 r230295  
    752752
    753753        const updateRendering = () => {
    754             this._chartPane.didUpdateAnnotations();
     754            this.part('chart-pane').didUpdateAnnotations();
    755755            this.enqueueToRender();
    756756        };
  • trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane.js

    r215205 r230295  
    4040    },
    4141    {
     42        id: 6,
     43        label: 'Segmentation with Welch\'s t-test change detection',
     44        execute: async function (source, parameters) {
     45            const segmentation =  await source.measurementSet.fetchSegmentation('segmentTimeSeriesByMaximizingSchwarzCriterion', parameters,
     46                source.type, source.includeOutliers, source.extendToFuture);
     47            if (!segmentation)
     48                return segmentation;
     49
     50            const metric = Metric.findById(source.measurementSet.metricId());
     51            const timeSeries = source.measurementSet.fetchedTimeSeries(source.type, source.includeOutliers, source.extendToFuture);
     52            segmentation.analysisAnnotations = Statistics.findRangesForChangeDetectionsWithWelchsTTest(timeSeries.values(),
     53                segmentation, parameters[parameters.length - 1]).map((range) => {
     54                const startPoint = timeSeries.findPointByIndex(range.startIndex);
     55                const endPoint = timeSeries.findPointByIndex(range.endIndex);
     56                const summary = metric.labelForDifference(range.segmentationStartValue, range.segmentationEndValue, 'progression', 'regression');
     57                return {
     58                    task: null,
     59                    fillStyle: ChartStyles.annotationFillStyleForTask(null),
     60                    startTime: startPoint.time,
     61                    endTime: endPoint.time,
     62                    label: `Potential ${summary.changeLabel}`,
     63                };
     64            });
     65            return segmentation;
     66        },
     67        parameterList: [
     68            {label: "Segment count weight", value: 2.5, min: 0.01, max: 10, step: 0.01},
     69            {label: "Grid size", value: 500, min: 100, max: 10000, step: 10},
     70            {label: "t-test significance", value: 0.99, options: Statistics.supportedOneSideTTestProbabilities()},
     71        ]
     72    },
     73    {
    4274        id: 1,
    4375        label: 'Simple Moving Average',
     
    418450                element('h3', 'Parameters'),
    419451                element('ul', this._trendLineType.parameterList.map(function (parameter, index) {
     452                    if (parameter.options) {
     453                        const select = element('select', parameter.options.map((option) =>
     454                            element('option', {value: option, selected: option == parameter.value}, option)));
     455                        select.onchange = self._trendLineParameterDidChange.bind(self);
     456                        select.parameterIndex = index;
     457                        return element('li', element('label', [parameter.label + ': ', select]));
     458                    }
     459
    420460                    var attributes = {type: 'number'};
    421461                    for (var name in parameter)
    422462                        attributes[name] = parameter[name];
     463
    423464                    attributes.value = configuredParameters[index];
    424                     var input = element('input', attributes);
     465                    const input = element('input', attributes);
    425466                    input.parameterIndex = index;
    426467                    input.oninput = self._trendLineParameterDidChange.bind(self);
     
    476517    }
    477518
    478     _updateTrendLine()
     519    async _updateTrendLine()
    479520    {
    480521        if (!this._mainChart.sourceList())
     
    485526        var currentTrendLineParameters = this._trendLineParameters || this._defaultParametersForTrendLine(currentTrendLineType);
    486527        var currentTrendLineVersion = this._trendLineVersion;
    487         var self = this;
    488528        var sourceList = this._mainChart.sourceList();
    489529
     
    493533        } else {
    494534            // Wait for all trendlines to be ready. Otherwise we might see FOC when the domain is expanded.
    495             Promise.all(sourceList.map(function (source, sourceIndex) {
    496                 return currentTrendLineType.execute.call(null, source, currentTrendLineParameters).then(function (trendlineSeries) {
    497                     if (self._trendLineVersion == currentTrendLineVersion)
    498                         self._mainChart.setTrendLine(sourceIndex, trendlineSeries);
    499                 });
    500             })).then(function () {
    501                 self.enqueueToRender();
    502             });
     535            await Promise.all(sourceList.map(async (source, sourceIndex) => {
     536                const trendlineSeries = await currentTrendLineType.execute.call(null, source, currentTrendLineParameters);
     537                if (this._trendLineVersion == currentTrendLineVersion)
     538                    this._mainChart.setTrendLine(sourceIndex, trendlineSeries);
     539
     540                if (trendlineSeries && trendlineSeries.analysisAnnotations)
     541                    this._detectedAnnotations = trendlineSeries.analysisAnnotations;
     542                else
     543                    this._detectedAnnotations = null;
     544            }));
     545            this.enqueueToRender();
    503546        }
    504547    }
  • trunk/Websites/perf.webkit.org/unit-tests/statistics-tests.js

    r227182 r230295  
    389389        });
    390390    });
     391
     392    describe('findRangesForChangeDetectionsWithWelchsTTest', () => {
     393        it('should return an empty array if the value is empty list', () => {
     394            assert.deepEqual(Statistics.findRangesForChangeDetectionsWithWelchsTTest([], [], 0.975), []);
     395        });
     396
     397        it('should return an empty array if segmentation is empty list', () => {
     398            assert.deepEqual(Statistics.findRangesForChangeDetectionsWithWelchsTTest([1,2,3], [], 0.975), []);
     399        });
     400
     401        it('should return the range if computeWelchsT shows a significant change', () => {
     402            const values = [
     403                747.30337423744,
     404                731.47392585276,
     405                743.66763513161,
     406                738.02055323487,
     407                738.25426340842,
     408                742.38680046471,
     409                733.13921703284,
     410                739.22069966147,
     411                735.69295749633,
     412                743.01705472504,
     413                745.45778145306,
     414                731.04841157169,
     415                729.4372674973,
     416                735.4497416527,
     417                739.0230668644,
     418                730.91782989909,
     419                722.18725411279,
     420                731.96223451728,
     421                730.04119216192,
     422                730.78087646284,
     423                729.63155210365,
     424                730.17585200878,
     425                733.93766054706,
     426                740.74920717197,
     427                752.14718023647,
     428                764.49990164847,
     429                766.36100828473,
     430                756.2291883252,
     431                750.14522451097,
     432                749.57595092266,
     433                748.03624881866,
     434                769.41522176386,
     435                744.04660430456,
     436                751.17927808265,
     437                753.29996854062,
     438                757.01813756936,
     439                746.62413820741,
     440                742.64420062736,
     441                758.12726352772,
     442                778.2278439089,
     443                775.11818554541,
     444                775.11818554541];
     445            const segmentation = [{
     446                    seriesIndex: 0,
     447                    time: 1505176030671,
     448                    value: 736.5366704896555,
     449                    x: 370.4571789404566,
     450                    y: 185.52613334520248,
     451                },
     452                {
     453                    seriesIndex: 18,
     454                    time: 1515074391534,
     455                    value: 736.5366704896555,
     456                    x: 919.4183852714947,
     457                    y: 185.52613334520248
     458                },
     459                {
     460                    seriesIndex: 18,
     461                    time: 1515074391534,
     462                    value: 750.3483428383142,
     463                    x: 919.4183852714947,
     464                    y: 177.9710953409673,
     465                },
     466                {
     467                    seriesIndex: 41,
     468                    time: 1553851695869,
     469                    value: 750.3483428383142,
     470                    x: 3070.000290764446,
     471                    y: 177.9710953409673,
     472                }];
     473            assert.deepEqual(Statistics.findRangesForChangeDetectionsWithWelchsTTest(values, segmentation, 0.975), [
     474                {
     475                  "endIndex": 29,
     476                  "segmentationEndValue": 750.3483428383142,
     477                  "segmentationStartValue": 736.5366704896555,
     478                  "startIndex": 6
     479                }
     480            ]);
     481        })
     482    });
    391483});
Note: See TracChangeset for help on using the changeset viewer.