Skip to content

Commit 3c8952e

Browse files
ajchilik8s-ci-robot
authored andcommitted
Add TFDV, TFMA, and Table visualization support for Python based visualizations (#1898)
* Added table and tfdv visualization Also fixed issue surrounding ApiVisualizationType enum * Fixed table visualization * Removed byte limit * Fixed issue where headers would not properly be applied * Fixed issue where table would not be intractable * Updated table visualizaiton to reflect changes made to dependency injection * Fixed bug where checking if headers is provided to table visualizations could crash visualization * Added TFMA visualization * Updated new visualizations to match syntax of #1878 * Updated test snapshots to account for TFMA visualization * Small if statement synax changes * Add flake8 noqa comments to table.py and tfma.py
1 parent db6c9b4 commit 3c8952e

File tree

11 files changed

+322
-76
lines changed

11 files changed

+322
-76
lines changed

backend/api/go_client/visualization.pb.go

+43-32
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/api/swagger/visualization.swagger.json

+4-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/api/visualization.proto

+3
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ message Visualization {
7777
// API.
7878
enum Type {
7979
ROC_CURVE = 0;
80+
TFDV = 1;
81+
TFMA = 2;
82+
TABLE = 3;
8083
};
8184
Type type = 1;
8285

backend/src/apiserver/visualization/requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
bokeh==1.2.0
22
gcsfs==0.2.3
33
google-api-python-client==1.7.9
4+
itables==0.1.0
45
ipykernel==5.1.1
56
jupyter_client==5.2.4
67
nbconvert==5.5.0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Copyright 2019 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# gcsfs is required for pandas GCS integration.
16+
import gcsfs
17+
from itables import show
18+
# itables is requires as importing it changes the way pandas DataFrames are
19+
# rendered.
20+
import itables.interactive
21+
import itables.options as opts
22+
import pandas as pd
23+
from tensorflow.python.lib.io import file_io
24+
25+
# flake8: noqa TODO
26+
27+
# Remove maxByte limit to prevent issues where entire table cannot be rendered
28+
# due to size of data.
29+
opts.maxBytes = 0
30+
31+
dfs = []
32+
files = file_io.get_matching_files(source)
33+
34+
# Read data from file and write it to a DataFrame object.
35+
if variables.get("headers", False) == False:
36+
# If no headers are provided, use the first row as headers
37+
for f in files:
38+
dfs.append(pd.read_csv(f))
39+
else:
40+
# If headers are provided, do not set headers for DataFrames
41+
for f in files:
42+
dfs.append(pd.read_csv(f, header=None))
43+
44+
# Display DataFrame as output.
45+
df = pd.concat(dfs)
46+
if variables.get("headers", False) != False:
47+
df.columns = variables.get("headers")
48+
show(df)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Copyright 2019 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import tensorflow_model_analysis as tfma
16+
17+
# flake8: noqa TODO
18+
19+
if variables.get("slicing_column", False) == False {
20+
tfma.view.render_slicing_metrics(source)
21+
} else {
22+
tfma.view.render_slicing_metrics(source, slicing_column=variables.get("slicing_column"))
23+
}
24+

backend/src/apiserver/visualization/third_party_licenses.csv

+2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ ipykernel,https://github.com/ipython/ipykernel/blob/master/COPYING.md,BSD-3
5353
ipython,https://github.com/ipython/ipython/blob/master/LICENSE,BSD-3
5454
ipython-genutils,https://github.com/ipython/ipython_genutils/blob/master/COPYING.md,BSD-3
5555
ipywidgets,https://github.com/jupyter-widgets/ipywidgets/blob/master/LICENSE,BSD-3
56+
itables,https://github.com/mwouts/itables/blob/master/LICENSE,MIT
5657
jedi,https://github.com/davidhalter/jedi/blob/master/LICENSE.txt,MIT
5758
joblib,https://github.com/joblib/joblib/blob/master/LICENSE.txt,BSD-3
5859
jsonschema,https://github.com/Julian/jsonschema/blob/master/COPYING,MIT
@@ -83,6 +84,7 @@ pyarrow,https://github.com/apache/arrow/blob/master/LICENSE.txt,Apache 2.0
8384
pyasn1,https://github.com/etingof/pyasn1/blob/master/LICENSE.rst,BSD-2
8485
pyasn1-modules,https://github.com/etingof/pyasn1-modules/blob/master/LICENSE.txt,BSD-2
8586
pydot,https://github.com/pydot/pydot/blob/master/LICENSE,MIT
87+
pymongo,https://github.com/mongodb/mongo-python-driver/blob/master/LICENSE,Apache 2.0
8688
pyparsing,https://github.com/pyparsing/pyparsing/blob/master/LICENSE,MIT
8789
pyrsistent,https://github.com/tobgu/pyrsistent/blob/master/LICENCE.mit,MIT
8890
python-dateutil,https://github.com/dateutil/dateutil/blob/master/LICENSE,Apache 2.0

frontend/src/apis/visualization/api.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,10 @@ export interface ApiVisualization {
148148
* @enum {string}
149149
*/
150150
export enum ApiVisualizationType {
151-
CURVE = <any> 'ROC_CURVE'
151+
ROCCURVE = <any> 'ROC_CURVE',
152+
TFDV = <any> 'TFDV',
153+
TFMA = <any> 'TFMA',
154+
TABLE = <any> 'TABLE'
152155
}
153156

154157
/**

frontend/src/components/viewers/VisualizationCreator.test.tsx

+8-8
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ describe('VisualizationCreator', () => {
8484
const tree = shallow(<VisualizationCreator configs={[config]} />);
8585
tree.setState({
8686
// source by default is set to ''
87-
selectedType: ApiVisualizationType.CURVE,
87+
selectedType: ApiVisualizationType.ROCCURVE,
8888
});
8989
expect(tree.find('BusyButton').at(0).prop('disabled')).toBe(true);
9090
});
@@ -96,7 +96,7 @@ describe('VisualizationCreator', () => {
9696
};
9797
const tree = shallow(<VisualizationCreator configs={[config]} />);
9898
tree.setState({
99-
selectedType: ApiVisualizationType.CURVE,
99+
selectedType: ApiVisualizationType.ROCCURVE,
100100
source: 'gs://ml-pipeline/data.csv',
101101
});
102102
expect(tree.find('BusyButton').at(0).prop('disabled')).toBe(true);
@@ -110,7 +110,7 @@ describe('VisualizationCreator', () => {
110110
};
111111
const tree = shallow(<VisualizationCreator configs={[config]} />);
112112
tree.setState({
113-
selectedType: ApiVisualizationType.CURVE,
113+
selectedType: ApiVisualizationType.ROCCURVE,
114114
source: 'gs://ml-pipeline/data.csv',
115115
});
116116
expect(tree.find('BusyButton').at(0).prop('disabled')).toBe(true);
@@ -124,7 +124,7 @@ describe('VisualizationCreator', () => {
124124
};
125125
const tree = shallow(<VisualizationCreator configs={[config]} />);
126126
tree.setState({
127-
selectedType: ApiVisualizationType.CURVE,
127+
selectedType: ApiVisualizationType.ROCCURVE,
128128
source: 'gs://ml-pipeline/data.csv',
129129
});
130130
expect(tree.find('BusyButton').at(0).prop('disabled')).toBe(false);
@@ -140,7 +140,7 @@ describe('VisualizationCreator', () => {
140140
const tree = shallow(<VisualizationCreator configs={[config]} />);
141141
tree.setState({
142142
arguments: '{}',
143-
selectedType: ApiVisualizationType.CURVE,
143+
selectedType: ApiVisualizationType.ROCCURVE,
144144
source: 'gs://ml-pipeline/data.csv',
145145
});
146146
tree.find('BusyButton').at(0).simulate('click');
@@ -157,11 +157,11 @@ describe('VisualizationCreator', () => {
157157
const tree = shallow(<VisualizationCreator configs={[config]} />);
158158
tree.setState({
159159
arguments: '{}',
160-
selectedType: ApiVisualizationType.CURVE,
160+
selectedType: ApiVisualizationType.ROCCURVE,
161161
source: 'gs://ml-pipeline/data.csv',
162162
});
163163
tree.find('BusyButton').at(0).simulate('click');
164-
expect(onGenerate).toBeCalledWith('{}', 'gs://ml-pipeline/data.csv', ApiVisualizationType.CURVE);
164+
expect(onGenerate).toBeCalledWith('{}', 'gs://ml-pipeline/data.csv', ApiVisualizationType.ROCCURVE);
165165
});
166166

167167
it('renders the provided arguments correctly', () => {
@@ -192,7 +192,7 @@ describe('VisualizationCreator', () => {
192192
};
193193
const tree = shallow(<VisualizationCreator configs={[config]} />);
194194
tree.setState({
195-
selectedType: ApiVisualizationType.CURVE,
195+
selectedType: ApiVisualizationType.ROCCURVE,
196196
});
197197
expect(tree).toMatchSnapshot();
198198
});

frontend/src/components/viewers/VisualizationCreator.tsx

+25-18
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,27 @@ interface VisualizationCreatorState {
4545
}
4646

4747
class VisualizationCreator extends Viewer<VisualizationCreatorProps, VisualizationCreatorState> {
48+
/*
49+
Due to the swagger API definition generation, enum value that include
50+
an _ (underscore) remove all _ from the enum key. Additionally, due to the
51+
manner in which TypeScript is compiled to Javascript, enums are duplicated
52+
iff they included an _ in the proto file. This filters out those duplicate
53+
keys that are generated by the complication from TypeScript to JavaScript.
54+
55+
For example:
56+
export enum ApiVisualizationType {
57+
ROCCURVE = <any> 'ROC_CURVE'
58+
}
59+
60+
Object.keys(ApiVisualizationType) = ['CURVE', 'ROC_CURVE'];
61+
62+
Additional details can be found here:
63+
https://www.typescriptlang.org/play/#code/KYOwrgtgBAggDgSwGoIM5gIYBsEC8MAuCA9iACoCecwUA3gLABQUUASgPIDCnAqq0gFEoAXigAeDCAoA+AOQdOAfV78BsgDRMAvkyYBjUqmJZgAOizEA5gAp4yNJhz4ipStQCUAbiA
64+
*/
65+
private _types = Object.keys(ApiVisualizationType)
66+
.map((key: string) => key.replace('_', ''))
67+
.filter((key: string, i: number, arr: string[]) => arr.indexOf(key) === i);
68+
4869
constructor(props: VisualizationCreatorProps) {
4970
super(props);
5071
this.state = {
@@ -98,25 +119,11 @@ class VisualizationCreator extends Viewer<VisualizationCreatorProps, Visualizati
98119
}}
99120
disabled={isBusy}
100121
>
101-
{/*
102-
Due to the swagger API definition generation, getting the keys within
103-
ApiVisualizationType also provides the values for the keys. This
104-
filters out those values and only shows the actual keys as a
105-
visualization type.
106-
107-
For example:
108-
export enum ApiVisualizationType {
109-
CURVE = <any> 'ROC_CURVE'
110-
}
111-
112-
Object.keys(ApiVisualizationType) = ['CURVE', 'ROC_CURVE'];
113-
114-
This occurs due to the dictation of the any type.
115-
*/}
116-
{Object.keys(ApiVisualizationType)
117-
.filter((_, i: number) => i % 2 === 0)
122+
{this._types
118123
.map((key: string) => (
119-
<MenuItem key={key} value={ApiVisualizationType[key]}>{key}</MenuItem>
124+
<MenuItem key={key} value={ApiVisualizationType[key]}>
125+
{ApiVisualizationType[key]}
126+
</MenuItem>
120127
))}
121128
</Select>
122129
</FormControl>

0 commit comments

Comments
 (0)