Skip to content

Commit 86be9ae

Browse files
author
tf-model-analysis-team
committed
Introduce tfma-residual-plot component.
PiperOrigin-RevId: 257896389
1 parent 3c90ff3 commit 86be9ae

File tree

6 files changed

+332
-0
lines changed

6 files changed

+332
-0
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!--
2+
Copyright 2018 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-->
16+
<html>
17+
<body>
18+
<tfma-residual-plot id="plot"></tfma-residual-plot>
19+
</body>
20+
</html>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* Copyright 2018 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
(function() {
17+
const count = 50;
18+
const step = 0.02;
19+
20+
const makeOneBucket = (threshold, weight, prediction) => {
21+
const bucket = {
22+
'lowerThresholdInclusive': threshold - step,
23+
'upperThresholdExclusive': threshold,
24+
};
25+
bucket['numWeightedExamples'] = weight;
26+
bucket['totalWeightedLabel'] = Math.ceil(weight * Math.random());
27+
bucket['totalWeightedRefinedPrediction'] = prediction * weight;
28+
return bucket;
29+
};
30+
31+
const input = [];
32+
let threshold = 0;
33+
let weight;
34+
let prediction;
35+
36+
for (let i = 0; i < count; i++) {
37+
weight = Math.ceil(Math.random() * 300);
38+
prediction = threshold - step * Math.random();
39+
input.push(makeOneBucket(threshold, weight, prediction));
40+
threshold += step;
41+
}
42+
43+
let plot = document.getElementById('plot');
44+
plot.data = input;
45+
})();
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Copyright 2018 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
const template = /** @type {!HTMLTemplateElement} */(document.createElement('template'));
17+
template.innerHTML = `
18+
<tfma-google-chart-wrapper type="combo" data="[[plotData_]]" options="[[options_]]">
19+
</tfma-google-chart-wrapper>
20+
21+
`;
22+
export {template};
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/**
2+
* Copyright 2018 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
17+
import {template} from './tfma-residual-plot-template.html.js';
18+
19+
import '../tfma-google-chart-wrapper/tfma-google-chart-wrapper.js';
20+
21+
/**
22+
* tfma-residual-plot plot renders the residual plot.
23+
*
24+
* @polymer
25+
*/
26+
export class ResidualPlot extends PolymerElement {
27+
constructor() {
28+
super();
29+
}
30+
31+
static get is() {
32+
return 'tfma-residual-plot';
33+
}
34+
35+
/** @return {!HTMLTemplateElement} */
36+
static get template() {
37+
return template;
38+
}
39+
40+
/** @return {!PolymerElementProperties} */
41+
static get properties() {
42+
return {
43+
/** @type {!Array<!Object>} */
44+
data: {type: Array},
45+
46+
/**
47+
* Chart rendering options.
48+
* @type {!Object}
49+
* @private
50+
*/
51+
options_: {
52+
type: Object,
53+
value: {
54+
'legend': {'textStyle': {'fontSize': 9}, 'position': 'top'},
55+
'hAxis': {'title': 'Label'},
56+
'vAxes': {0: {'title': 'Residual'}, 1: {'title': 'Sample Count'}},
57+
'series': {
58+
0: {'visibleInLegend': true, 'targetAxisIndex': 0, 'type': 'line'},
59+
1: {
60+
'visibleInLegend': false,
61+
'targetAxisIndex': 0,
62+
'type': 'line',
63+
'lineDashStyle': [3, 2],
64+
},
65+
2: {
66+
'visibleInLegend': true,
67+
'targetAxisIndex': 1,
68+
'type': 'scatter',
69+
'pointShape': 'diamond',
70+
},
71+
},
72+
'explorer':
73+
{'actions': ['dragToPan', 'scrollToZoom', 'rightClickToReset']},
74+
},
75+
},
76+
77+
/**
78+
* The data to be plotted in the line chart.
79+
* @private {!Array<!Array<string|number>>}
80+
*/
81+
plotData_: {type: Array, computed: 'computePlotData_(data)'},
82+
};
83+
}
84+
85+
/**
86+
* @param {!Array<!Object>} data
87+
* @return {!Array<!Array<string|number>>|undefined} A 2d array representing
88+
* the data that will be visualized in the ROC curve.
89+
* @private
90+
*/
91+
computePlotData_(data) {
92+
if (!data.length) {
93+
return undefined;
94+
}
95+
96+
const plotData = [
97+
[
98+
'Label',
99+
'Residual',
100+
{'type': 'string', 'role': 'tooltip'},
101+
'',
102+
{'type': 'string', 'role': 'tooltip'},
103+
'Count',
104+
{'type': 'string', 'role': 'tooltip'},
105+
],
106+
];
107+
108+
for (let i = 0; i < data.length; i++) {
109+
const entry = data[i];
110+
const count = entry['numWeightedExamples'] || 0;
111+
const upperBound = entry['upperThresholdExclusive'] || 0;
112+
const lowerBound = entry['lowerThresholdInclusive'] || 0;
113+
const label = (upperBound + lowerBound) / 2;
114+
const prediction =
115+
count ? entry['totalWeightedRefinedPrediction'] / count : 0;
116+
const residual = count ? label - prediction : 0;
117+
const predictionRange = '[' +
118+
lowerBound.toFixed(tfma.FLOATING_POINT_PRECISION) + ', ' +
119+
upperBound.toFixed(tfma.FLOATING_POINT_PRECISION) + ')';
120+
121+
plotData.push([
122+
label,
123+
residual,
124+
'Residual is ' + residual.toFixed(tfma.FLOATING_POINT_PRECISION) +
125+
' for label in ' + predictionRange,
126+
0,
127+
'Prediciton range is ' + predictionRange,
128+
count,
129+
'There are ' + count + ' example(s) for label in ' + predictionRange,
130+
]);
131+
}
132+
133+
return plotData;
134+
}
135+
}
136+
137+
customElements.define('tfma-residual-plot', ResidualPlot);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<!doctype html>
2+
<!--
3+
Copyright 2018 Google LLC
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
https://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
<html>
18+
<head>
19+
<meta charset="utf-8">
20+
<script src="imports.js"></script>
21+
<script src="webcomponentsjs/webcomponents-lite.js"></script>
22+
</head>
23+
<body>
24+
<test-fixture id="residual-plot-fixture">
25+
<template>
26+
<tfma-residual-plot></tfma-residual-plot>
27+
</template>
28+
</test-fixture>
29+
</body>
30+
</html>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* Copyright 2018 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
suite('tests', () => {
17+
test('ExtractsData', () => {
18+
const element = fixture('residual-plot-fixture');
19+
element.data = [
20+
{
21+
'numWeightedExamples': 5,
22+
'upperThresholdExclusive': 0.25,
23+
'lowerThresholdInclusive': 0,
24+
'totalWeightedRefinedPrediction': 0.4,
25+
},
26+
{
27+
'numWeightedExamples': 10,
28+
'upperThresholdExclusive': 0.5,
29+
'lowerThresholdInclusive': 0.25,
30+
'totalWeightedRefinedPrediction': 4,
31+
},
32+
{
33+
'numWeightedExamples': 20,
34+
'upperThresholdExclusive': 0.75,
35+
'lowerThresholdInclusive': 0.5,
36+
'totalWeightedRefinedPrediction': 10,
37+
},
38+
{
39+
'numWeightedExamples': 1,
40+
'upperThresholdExclusive': 1,
41+
'lowerThresholdInclusive': 0.75,
42+
'totalWeightedRefinedPrediction': 0.875,
43+
},
44+
];
45+
const chartData =
46+
element.shadowRoot.querySelector('tfma-google-chart-wrapper').data;
47+
assert.deepEqual(chartData[0], [
48+
'Label',
49+
'Residual',
50+
{'type': 'string', 'role': 'tooltip'},
51+
'',
52+
{'type': 'string', 'role': 'tooltip'},
53+
'Count',
54+
{'type': 'string', 'role': 'tooltip'},
55+
]);
56+
assert.deepEqual(chartData[1], [
57+
0.125, 0.045, 'Residual is 0.04500 for label in [0.00000, 0.25000)', 0,
58+
'Prediciton range is [0.00000, 0.25000)', 5,
59+
'There are 5 example(s) for label in [0.00000, 0.25000)'
60+
]);
61+
assert.deepEqual(chartData[2], [
62+
0.375, -0.025000000000000022,
63+
'Residual is -0.02500 for label in [0.25000, 0.50000)', 0,
64+
'Prediciton range is [0.25000, 0.50000)', 10,
65+
'There are 10 example(s) for label in [0.25000, 0.50000)'
66+
]);
67+
assert.deepEqual(chartData[3], [
68+
0.625, 0.125, 'Residual is 0.12500 for label in [0.50000, 0.75000)', 0,
69+
'Prediciton range is [0.50000, 0.75000)', 20,
70+
'There are 20 example(s) for label in [0.50000, 0.75000)'
71+
]);
72+
assert.deepEqual(chartData[4], [
73+
0.875, 0, 'Residual is 0.00000 for label in [0.75000, 1.00000)', 0,
74+
'Prediciton range is [0.75000, 1.00000)', 1,
75+
'There are 1 example(s) for label in [0.75000, 1.00000)'
76+
]);
77+
});
78+
});

0 commit comments

Comments
 (0)