Skip to content

Commit

Permalink
Limit individual feature importance selection to up to 5 (#1305)
Browse files Browse the repository at this point in the history
* update feature importance string

* limit selection to up to 5

* add group count

* remove message bar, show info icon instead

* update e2e locator

* fix E2E failure on feature importance

* add ariaLabel for expand collapse button

* add renderOnNewLayer props
  • Loading branch information
tongyu-microsoft authored and gaugup committed Apr 21, 2022
1 parent c5ee69d commit 6aabd52
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 7 deletions.
3 changes: 2 additions & 1 deletion libs/core-ui/src/lib/components/LabelWithCallout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { labelWithCalloutStyles } from "./LabelWithCallout.styles";
export interface ILabelWithCalloutProps {
label: string;
calloutTitle: string | undefined;
renderOnNewLayer?: boolean;
type?: "label" | "button";
}
interface ILabelWithCalloutState {
Expand Down Expand Up @@ -62,7 +63,7 @@ export class LabelWithCallout extends React.Component<
)}
{this.state.showCallout && (
<FabricCallout
doNotLayer
doNotLayer={!this.props.renderOnNewLayer}
target={`#${id}`}
setInitialFocus
onDismiss={this.toggleCallout}
Expand Down
6 changes: 4 additions & 2 deletions libs/localization/src/lib/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1300,8 +1300,10 @@
"CorrectPredictions": "Correct predictions",
"GlobalExplanation": "Aggregate feature importance",
"IncorrectPredictions": "Incorrect predictions",
"IndividualFeature": "Select a datapoint by clicking on a datapoint (or multiple datapoints) in the table to view their local feature importance values (local explanation) and individual conditional expectation (ICE) plot below. For datasets with more than 5000 datapoints, the view is a random subsample to enable easy exploration.",
"LocalExplanation": "Individual feature importance"
"IndividualFeature": "Select a datapoint by clicking on a datapoint (up to 5 datapoints) in the table to view their local feature importance values (local explanation) and individual conditional expectation (ICE) plot below. For datasets with more than 5000 datapoints, the view is a random subsample to enable easy exploration.",
"LocalExplanation": "Individual feature importance",
"SelectionCounter": "{0}/{1} datapoints selected",
"SelectionLimit": "Up to 5 datapoints can be selected at this time."
},
"MainMenu": {
"DashboardSettings": "Dashboard configuration",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import {
IStyle,
mergeStyleSets,
IProcessedStyleSet,
getTheme
} from "office-ui-fabric-react";

export interface IFeatureImportanceStyles {
chevronButton: IStyle;
header: IStyle;
headerCount: IStyle;
headerTitle: IStyle;
selectionCounter: IStyle;
}

export const individualFeatureImportanceViewStyles: () => IProcessedStyleSet<IFeatureImportanceStyles> =
() => {
const theme = getTheme();
return mergeStyleSets({
chevronButton: {
marginLeft: 48,
paddingTop: 6,
width: 36
},
header: {
margin: `8px 0`,
padding: 8,
// Overlay the sizer bars
position: "relative",
zIndex: 100
},
headerCount: [
"headerCount",
theme.fonts.medium,
{
paddingTop: 4
}
],
headerTitle: [
theme.fonts.medium,
{
paddingTop: 4
}
],
selectionCounter: {
paddingTop: 12
}
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
FabricStyles,
constructRows,
constructCols,
ModelTypes
ModelTypes,
LabelWithCallout
} from "@responsible-ai/core-ui";
import { IGlobalSeries, LocalImportancePlots } from "@responsible-ai/interpret";
import { localization } from "@responsible-ai/localization";
Expand All @@ -34,10 +35,14 @@ import {
TooltipHost,
IColumn,
IGroup,
Text
Text,
IDetailsGroupDividerProps,
Icon
} from "office-ui-fabric-react";
import React from "react";

import { individualFeatureImportanceViewStyles } from "./IndividualFeatureImportanceView.styles";

export interface IIndividualFeatureImportanceProps {
features: string[];
jointDataset: JointDataset;
Expand All @@ -59,6 +64,8 @@ export interface IIndividualFeatureImportanceTableState {
export interface IIndividualFeatureImportanceState
extends IIndividualFeatureImportanceTableState {
featureImportances: IGlobalSeries[];
indexToUnselect?: number;
selectedIndices: number[];
sortArray: number[];
sortingSeriesIndex?: number;
}
Expand All @@ -70,9 +77,22 @@ export class IndividualFeatureImportanceView extends React.Component<
public static contextType = ModelAssessmentContext;
public context: React.ContextType<typeof ModelAssessmentContext> =
defaultModelAssessmentContext;
private readonly maxSelectable = 5;

private selection: Selection = new Selection({
onSelectionChanged: (): void => {
const c = this.selection.getSelectedCount();
const indices = this.selection.getSelectedIndices();
if (c === this.maxSelectable) {
this.setState({ selectedIndices: indices });
}
if (c > this.maxSelectable) {
for (const index of indices) {
if (!this.state.selectedIndices.includes(index)) {
this.setState({ indexToUnselect: index });
}
}
}
this.updateViewedFeatureImportances();
}
});
Expand All @@ -84,6 +104,8 @@ export class IndividualFeatureImportanceView extends React.Component<

this.state = {
featureImportances: [],
indexToUnselect: undefined,
selectedIndices: [],
sortArray: [],
...tableState
};
Expand All @@ -95,6 +117,10 @@ export class IndividualFeatureImportanceView extends React.Component<
if (this.props.selectedCohort !== prevProps.selectedCohort) {
this.setState(this.updateItems());
}
if (this.state.indexToUnselect) {
this.selection.toggleIndexSelected(this.state.indexToUnselect);
this.setState({ indexToUnselect: undefined });
}
}

public render(): React.ReactNode {
Expand Down Expand Up @@ -132,6 +158,7 @@ export class IndividualFeatureImportanceView extends React.Component<
text: meta.abbridgedLabel
};
});
const classNames = individualFeatureImportanceViewStyles();

return (
<Stack tokens={{ padding: "l1" }}>
Expand All @@ -140,6 +167,22 @@ export class IndividualFeatureImportanceView extends React.Component<
{localization.ModelAssessment.FeatureImportances.IndividualFeature}
</Text>
</Stack.Item>
<Stack.Item className={classNames.selectionCounter}>
<LabelWithCallout
label={localization.formatString(
localization.ModelAssessment.FeatureImportances.SelectionCounter,
this.selection.count,
this.maxSelectable
)}
calloutTitle={undefined}
renderOnNewLayer
type="label"
>
<Text block>
{localization.ModelAssessment.FeatureImportances.SelectionLimit}
</Text>
</LabelWithCallout>
</Stack.Item>
<Stack.Item className="tabularDataView">
<div style={{ height: "500px", position: "relative" }}>
<Fabric>
Expand All @@ -155,10 +198,12 @@ export class IndividualFeatureImportanceView extends React.Component<
onRenderDetailsHeader={this.onRenderDetailsHeader}
selectionPreservedOnEmptyClick
ariaLabelForSelectionColumn="Toggle selection"
ariaLabelForSelectAllCheckbox="Toggle selection for all items"
checkButtonAriaLabel="Row checkbox"
// checkButtonGroupAriaLabel="Group checkbox"
groupProps={{ showEmptyGroups: true }}
groupProps={{
onRenderHeader: this._onRenderGroupHeader,
showEmptyGroups: true
}}
selectionMode={SelectionMode.multiple}
selection={this.selection}
/>
Expand Down Expand Up @@ -329,4 +374,32 @@ export class IndividualFeatureImportanceView extends React.Component<
</div>
);
};

private _onRenderGroupHeader = (props?: IDetailsGroupDividerProps) => {
const classNames = individualFeatureImportanceViewStyles();
const iconName = props?.group?.isCollapsed
? "ChevronRightMed"
: "ChevronDownMed";
return (
<Stack className={classNames.header} horizontal>
<Icon
ariaLabel="expand collapse group"
className={classNames.chevronButton}
iconName={iconName}
onClick={this._onToggleCollapse(props)}
/>
<span className={classNames.headerTitle}>{props?.group!.name}</span>
&nbsp;
<span className={classNames.headerCount}>
{`(${props?.group!.count})`}
</span>
</Stack>
);
};

private _onToggleCollapse = (props?: IDetailsGroupDividerProps) => {
return () => {
props!.onToggleCollapse!(props!.group!);
};
};
}

0 comments on commit 6aabd52

Please sign in to comment.