Skip to content

Commit

Permalink
Add TFDV, TFMA, and Table visualization support for Python based visu…
Browse files Browse the repository at this point in the history
…alizations (#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
  • Loading branch information
ajchili authored and k8s-ci-robot committed Aug 22, 2019
1 parent db6c9b4 commit 3c8952e
Show file tree
Hide file tree
Showing 11 changed files with 322 additions and 76 deletions.
75 changes: 43 additions & 32 deletions backend/api/go_client/visualization.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion backend/api/swagger/visualization.swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions backend/api/visualization.proto
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ message Visualization {
// API.
enum Type {
ROC_CURVE = 0;
TFDV = 1;
TFMA = 2;
TABLE = 3;
};
Type type = 1;

Expand Down
1 change: 1 addition & 0 deletions backend/src/apiserver/visualization/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
bokeh==1.2.0
gcsfs==0.2.3
google-api-python-client==1.7.9
itables==0.1.0
ipykernel==5.1.1
jupyter_client==5.2.4
nbconvert==5.5.0
Expand Down
48 changes: 48 additions & 0 deletions backend/src/apiserver/visualization/table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# gcsfs is required for pandas GCS integration.
import gcsfs
from itables import show
# itables is requires as importing it changes the way pandas DataFrames are
# rendered.
import itables.interactive
import itables.options as opts
import pandas as pd
from tensorflow.python.lib.io import file_io

# flake8: noqa TODO

# Remove maxByte limit to prevent issues where entire table cannot be rendered
# due to size of data.
opts.maxBytes = 0

dfs = []
files = file_io.get_matching_files(source)

# Read data from file and write it to a DataFrame object.
if variables.get("headers", False) == False:
# If no headers are provided, use the first row as headers
for f in files:
dfs.append(pd.read_csv(f))
else:
# If headers are provided, do not set headers for DataFrames
for f in files:
dfs.append(pd.read_csv(f, header=None))

# Display DataFrame as output.
df = pd.concat(dfs)
if variables.get("headers", False) != False:
df.columns = variables.get("headers")
show(df)
24 changes: 24 additions & 0 deletions backend/src/apiserver/visualization/tfma.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import tensorflow_model_analysis as tfma

# flake8: noqa TODO

if variables.get("slicing_column", False) == False {
tfma.view.render_slicing_metrics(source)
} else {
tfma.view.render_slicing_metrics(source, slicing_column=variables.get("slicing_column"))
}

2 changes: 2 additions & 0 deletions backend/src/apiserver/visualization/third_party_licenses.csv
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ ipykernel,https://github.com/ipython/ipykernel/blob/master/COPYING.md,BSD-3
ipython,https://github.com/ipython/ipython/blob/master/LICENSE,BSD-3
ipython-genutils,https://github.com/ipython/ipython_genutils/blob/master/COPYING.md,BSD-3
ipywidgets,https://github.com/jupyter-widgets/ipywidgets/blob/master/LICENSE,BSD-3
itables,https://github.com/mwouts/itables/blob/master/LICENSE,MIT
jedi,https://github.com/davidhalter/jedi/blob/master/LICENSE.txt,MIT
joblib,https://github.com/joblib/joblib/blob/master/LICENSE.txt,BSD-3
jsonschema,https://github.com/Julian/jsonschema/blob/master/COPYING,MIT
Expand Down Expand Up @@ -83,6 +84,7 @@ pyarrow,https://github.com/apache/arrow/blob/master/LICENSE.txt,Apache 2.0
pyasn1,https://github.com/etingof/pyasn1/blob/master/LICENSE.rst,BSD-2
pyasn1-modules,https://github.com/etingof/pyasn1-modules/blob/master/LICENSE.txt,BSD-2
pydot,https://github.com/pydot/pydot/blob/master/LICENSE,MIT
pymongo,https://github.com/mongodb/mongo-python-driver/blob/master/LICENSE,Apache 2.0
pyparsing,https://github.com/pyparsing/pyparsing/blob/master/LICENSE,MIT
pyrsistent,https://github.com/tobgu/pyrsistent/blob/master/LICENCE.mit,MIT
python-dateutil,https://github.com/dateutil/dateutil/blob/master/LICENSE,Apache 2.0
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/apis/visualization/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,10 @@ export interface ApiVisualization {
* @enum {string}
*/
export enum ApiVisualizationType {
CURVE = <any> 'ROC_CURVE'
ROCCURVE = <any> 'ROC_CURVE',
TFDV = <any> 'TFDV',
TFMA = <any> 'TFMA',
TABLE = <any> 'TABLE'
}

/**
Expand Down
16 changes: 8 additions & 8 deletions frontend/src/components/viewers/VisualizationCreator.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ describe('VisualizationCreator', () => {
const tree = shallow(<VisualizationCreator configs={[config]} />);
tree.setState({
// source by default is set to ''
selectedType: ApiVisualizationType.CURVE,
selectedType: ApiVisualizationType.ROCCURVE,
});
expect(tree.find('BusyButton').at(0).prop('disabled')).toBe(true);
});
Expand All @@ -96,7 +96,7 @@ describe('VisualizationCreator', () => {
};
const tree = shallow(<VisualizationCreator configs={[config]} />);
tree.setState({
selectedType: ApiVisualizationType.CURVE,
selectedType: ApiVisualizationType.ROCCURVE,
source: 'gs://ml-pipeline/data.csv',
});
expect(tree.find('BusyButton').at(0).prop('disabled')).toBe(true);
Expand All @@ -110,7 +110,7 @@ describe('VisualizationCreator', () => {
};
const tree = shallow(<VisualizationCreator configs={[config]} />);
tree.setState({
selectedType: ApiVisualizationType.CURVE,
selectedType: ApiVisualizationType.ROCCURVE,
source: 'gs://ml-pipeline/data.csv',
});
expect(tree.find('BusyButton').at(0).prop('disabled')).toBe(true);
Expand All @@ -124,7 +124,7 @@ describe('VisualizationCreator', () => {
};
const tree = shallow(<VisualizationCreator configs={[config]} />);
tree.setState({
selectedType: ApiVisualizationType.CURVE,
selectedType: ApiVisualizationType.ROCCURVE,
source: 'gs://ml-pipeline/data.csv',
});
expect(tree.find('BusyButton').at(0).prop('disabled')).toBe(false);
Expand All @@ -140,7 +140,7 @@ describe('VisualizationCreator', () => {
const tree = shallow(<VisualizationCreator configs={[config]} />);
tree.setState({
arguments: '{}',
selectedType: ApiVisualizationType.CURVE,
selectedType: ApiVisualizationType.ROCCURVE,
source: 'gs://ml-pipeline/data.csv',
});
tree.find('BusyButton').at(0).simulate('click');
Expand All @@ -157,11 +157,11 @@ describe('VisualizationCreator', () => {
const tree = shallow(<VisualizationCreator configs={[config]} />);
tree.setState({
arguments: '{}',
selectedType: ApiVisualizationType.CURVE,
selectedType: ApiVisualizationType.ROCCURVE,
source: 'gs://ml-pipeline/data.csv',
});
tree.find('BusyButton').at(0).simulate('click');
expect(onGenerate).toBeCalledWith('{}', 'gs://ml-pipeline/data.csv', ApiVisualizationType.CURVE);
expect(onGenerate).toBeCalledWith('{}', 'gs://ml-pipeline/data.csv', ApiVisualizationType.ROCCURVE);
});

it('renders the provided arguments correctly', () => {
Expand Down Expand Up @@ -192,7 +192,7 @@ describe('VisualizationCreator', () => {
};
const tree = shallow(<VisualizationCreator configs={[config]} />);
tree.setState({
selectedType: ApiVisualizationType.CURVE,
selectedType: ApiVisualizationType.ROCCURVE,
});
expect(tree).toMatchSnapshot();
});
Expand Down
43 changes: 25 additions & 18 deletions frontend/src/components/viewers/VisualizationCreator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,27 @@ interface VisualizationCreatorState {
}

class VisualizationCreator extends Viewer<VisualizationCreatorProps, VisualizationCreatorState> {
/*
Due to the swagger API definition generation, enum value that include
an _ (underscore) remove all _ from the enum key. Additionally, due to the
manner in which TypeScript is compiled to Javascript, enums are duplicated
iff they included an _ in the proto file. This filters out those duplicate
keys that are generated by the complication from TypeScript to JavaScript.
For example:
export enum ApiVisualizationType {
ROCCURVE = <any> 'ROC_CURVE'
}
Object.keys(ApiVisualizationType) = ['CURVE', 'ROC_CURVE'];
Additional details can be found here:
https://www.typescriptlang.org/play/#code/KYOwrgtgBAggDgSwGoIM5gIYBsEC8MAuCA9iACoCecwUA3gLABQUUASgPIDCnAqq0gFEoAXigAeDCAoA+AOQdOAfV78BsgDRMAvkyYBjUqmJZgAOizEA5gAp4yNJhz4ipStQCUAbiA
*/
private _types = Object.keys(ApiVisualizationType)
.map((key: string) => key.replace('_', ''))
.filter((key: string, i: number, arr: string[]) => arr.indexOf(key) === i);

constructor(props: VisualizationCreatorProps) {
super(props);
this.state = {
Expand Down Expand Up @@ -98,25 +119,11 @@ class VisualizationCreator extends Viewer<VisualizationCreatorProps, Visualizati
}}
disabled={isBusy}
>
{/*
Due to the swagger API definition generation, getting the keys within
ApiVisualizationType also provides the values for the keys. This
filters out those values and only shows the actual keys as a
visualization type.
For example:
export enum ApiVisualizationType {
CURVE = <any> 'ROC_CURVE'
}
Object.keys(ApiVisualizationType) = ['CURVE', 'ROC_CURVE'];
This occurs due to the dictation of the any type.
*/}
{Object.keys(ApiVisualizationType)
.filter((_, i: number) => i % 2 === 0)
{this._types
.map((key: string) => (
<MenuItem key={key} value={ApiVisualizationType[key]}>{key}</MenuItem>
<MenuItem key={key} value={ApiVisualizationType[key]}>
{ApiVisualizationType[key]}
</MenuItem>
))}
</Select>
</FormControl>
Expand Down
Loading

0 comments on commit 3c8952e

Please sign in to comment.