Skip to content

Commit

Permalink
[ML] DFAnalytics Creation: update form to handle num_top_classes se…
Browse files Browse the repository at this point in the history
…tting for all classes (elastic#80751) (elastic#81026)

* set default numTopClasses and change to combobox to handle all classes option

* show warning message if all or no classes set for numTopClasses

* ensure value is a number if set

* ensure numTopClasses validated and add link to evalute docs

* fix translation

* update with suggestions
  • Loading branch information
alvarezmelissa87 authored Oct 19, 2020
1 parent 066fa9c commit 1a3eb3b
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { ANALYSIS_CONFIG_TYPE } from '../../../../common/analytics';
import { ANALYTICS_STEPS } from '../../page';

function getStringValue(value: number | undefined) {
return value !== undefined ? `${value}` : UNSET_CONFIG_ITEM;
return typeof value === 'number' ? `${value}` : UNSET_CONFIG_ITEM;
}

export interface ListItems {
Expand Down Expand Up @@ -135,7 +135,12 @@ export const AdvancedStepDetails: FC<{ setCurrentStep: any; state: State }> = ({
title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.numTopClasses', {
defaultMessage: 'Top classes',
}),
description: `${numTopClasses}`,
description:
numTopClasses === -1
? i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.allClasses', {
defaultMessage: 'All classes',
})
: getStringValue(numTopClasses),
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
*/

import React, { FC, Fragment, useMemo, useEffect, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiAccordion,
EuiComboBox,
EuiComboBoxOptionOption,
EuiFieldNumber,
EuiFieldText,
EuiFlexGrid,
EuiFlexItem,
EuiFormRow,
EuiLink,
EuiSelect,
EuiSpacer,
EuiTitle,
Expand All @@ -25,12 +29,93 @@ import {
NUM_TOP_FEATURE_IMPORTANCE_VALUES_MIN,
ANALYSIS_ADVANCED_FIELDS,
} from '../../../../common/analytics';
import { useMlKibana } from '../../../../../contexts/kibana';
import { DEFAULT_MODEL_MEMORY_LIMIT } from '../../../analytics_management/hooks/use_create_analytics_form/state';
import { ANALYTICS_STEPS } from '../../page';
import { fetchExplainData } from '../shared';
import { ContinueButton } from '../continue_button';
import { OutlierHyperParameters } from './outlier_hyper_parameters';

const defaultNumTopClassesOption: EuiComboBoxOptionOption = {
label: i18n.translate('xpack.ml.dataframe.analytics.create.allClassesLabel', {
defaultMessage: 'All classes',
}),
value: -1,
};
const numClassesTypeMessage = (
<FormattedMessage
id="xpack.ml.dataframe.analytics.create.numTopClassTypeWarning"
defaultMessage="Value must be an integer of -1 or greater, where -1 indicates all classes."
/>
);

function getZeroClassesMessage(elasaticUrl: string, version: string) {
return (
<FormattedMessage
id="xpack.ml.dataframe.analytics.create.zeroClassesMessage"
defaultMessage="To evaluate the {wikiLink}, select all classes or a value greater than the total number of categories."
values={{
wikiLink: (
<EuiLink
href={`${elasaticUrl}guide/en/machine-learning/${version}/ml-dfanalytics-evaluate.html#ml-dfanalytics-roc`}
target="_blank"
external
>
{i18n.translate('xpack.ml.dataframe.analytics.create.aucRocLabel', {
defaultMessage: 'AUC ROC',
})}
</EuiLink>
),
}}
/>
);
}

function getTopClassesHelpText(currentNumTopClasses?: number) {
if (currentNumTopClasses === -1) {
return (
<>
<FormattedMessage
id="xpack.ml.dataframe.analytics.create.numTopClassesHelpText"
defaultMessage="The number of categories for which the predicted probabilities are reported."
/>{' '}
<FormattedMessage
id="xpack.ml.dataframe.analytics.create.allClassesMessage"
defaultMessage="If you have a large number of classes there could be a significant effect on the size of your destination index."
/>
</>
);
}
return (
<FormattedMessage
id="xpack.ml.dataframe.analytics.create.numTopClassesHelpText"
defaultMessage="The number of categories for which the predicted probabilities are reported."
/>
);
}

function getSelectedNumTomClassesOption(currentNumTopClasses?: number) {
const option: EuiComboBoxOptionOption[] = [];
if (currentNumTopClasses === -1) {
option.push(defaultNumTopClassesOption);
} else if (currentNumTopClasses !== undefined) {
option.push({
label: `${currentNumTopClasses}`,
});
}
return option;
}

function isInvalidNumTopClasses(currentNumTopClasses?: number) {
// Only valid if undefined or a whole integer >= -1
return (
currentNumTopClasses !== undefined &&
(isNaN(currentNumTopClasses) ||
currentNumTopClasses < -1 ||
currentNumTopClasses - Math.floor(currentNumTopClasses) !== 0)
);
}

export function getNumberValue(value?: number) {
return value === undefined ? '' : +value;
}
Expand All @@ -47,6 +132,11 @@ export const AdvancedStepForm: FC<CreateAnalyticsStepProps> = ({
const [advancedParamErrors, setAdvancedParamErrors] = useState<AdvancedParamErrors>({});
const [fetchingAdvancedParamErrors, setFetchingAdvancedParamErrors] = useState<boolean>(false);

const {
services: { docLinks },
} = useMlKibana();
const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks;

const { setEstimatedModelMemoryLimit, setFormState } = actions;
const { form, isJobCreated } = state;
const {
Expand All @@ -71,6 +161,21 @@ export const AdvancedStepForm: FC<CreateAnalyticsStepProps> = ({
randomizeSeed,
} = form;

const [numTopClassesOptions, setNumTopClassesOptions] = useState<EuiComboBoxOptionOption[]>([
defaultNumTopClassesOption,
]);
const [numTopClassesSelectedOptions, setNumTopClassesSelectedOptions] = useState<
EuiComboBoxOptionOption[]
>(getSelectedNumTomClassesOption(numTopClasses));

const selectedNumTopClasses =
numTopClassesSelectedOptions[0] &&
((numTopClassesSelectedOptions[0].value ?? Number(numTopClassesSelectedOptions[0].label)) as
| number
| undefined);

const selectedNumTopClassesIsInvalid = isInvalidNumTopClasses(selectedNumTopClasses);

const mmlErrors = useMemo(() => getModelMemoryLimitErrors(modelMemoryLimitValidationResult), [
modelMemoryLimitValidationResult,
]);
Expand All @@ -84,6 +189,7 @@ export const AdvancedStepForm: FC<CreateAnalyticsStepProps> = ({
modelMemoryLimitValidationResult.required === true);

const isStepInvalid =
selectedNumTopClassesIsInvalid ||
mmlInvalid ||
Object.keys(advancedParamErrors).length > 0 ||
fetchingAdvancedParamErrors === true ||
Expand Down Expand Up @@ -309,31 +415,53 @@ export const AdvancedStepForm: FC<CreateAnalyticsStepProps> = ({
label={i18n.translate('xpack.ml.dataframe.analytics.create.numTopClassesLabel', {
defaultMessage: 'Top classes',
})}
helpText={i18n.translate(
'xpack.ml.dataframe.analytics.create.numTopClassesHelpText',
{
defaultMessage:
'The number of categories for which the predicted probabilities are reported.',
}
)}
helpText={getTopClassesHelpText(selectedNumTopClasses)}
isInvalid={selectedNumTopClasses === 0 || selectedNumTopClassesIsInvalid}
error={[
...(selectedNumTopClasses === 0
? [getZeroClassesMessage(ELASTIC_WEBSITE_URL, DOC_LINK_VERSION)]
: []),
...(selectedNumTopClassesIsInvalid ? [numClassesTypeMessage] : []),
]}
>
<EuiFieldNumber
<EuiComboBox
aria-label={i18n.translate(
'xpack.ml.dataframe.analytics.create.numTopClassesInputAriaLabel',
{
defaultMessage:
'The number of categories for which the predicted probabilities are reported',
}
)}
singleSelection={true}
options={numTopClassesOptions}
selectedOptions={numTopClassesSelectedOptions}
onCreateOption={(input: string, flattenedOptions = []) => {
const normalizedInput = input.trim().toLowerCase();

if (normalizedInput === '') {
return;
}

const newOption = {
label: input,
};

if (
flattenedOptions.findIndex(
(option) => option.label.trim().toLowerCase() === normalizedInput
) === -1
) {
setNumTopClassesOptions([...numTopClassesOptions, newOption]);
}

setNumTopClassesSelectedOptions([newOption]);
}}
onChange={(selectedOptions) => {
setNumTopClassesSelectedOptions(selectedOptions);
}}
isClearable={true}
isInvalid={selectedNumTopClasses !== undefined && selectedNumTopClasses < -1}
data-test-subj="mlAnalyticsCreateJobWizardnumTopClassesInput"
min={0}
onChange={(e) =>
setFormState({
numTopClasses: e.target.value === '' ? undefined : +e.target.value,
})
}
step={1}
value={getNumberValue(numTopClasses)}
/>
</EuiFormRow>
</EuiFlexItem>
Expand Down Expand Up @@ -440,6 +568,10 @@ export const AdvancedStepForm: FC<CreateAnalyticsStepProps> = ({
<ContinueButton
isDisabled={isStepInvalid}
onClick={() => {
setFormState({
numTopClasses:
selectedNumTopClassesIsInvalid === false ? selectedNumTopClasses : undefined,
});
setCurrentStep(ANALYTICS_STEPS.DETAILS);
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export const getInitialState = (): State => ({
nNeighbors: undefined,
numTopFeatureImportanceValues: DEFAULT_NUM_TOP_FEATURE_IMPORTANCE_VALUES,
numTopFeatureImportanceValuesValid: true,
numTopClasses: 2,
numTopClasses: -1,
outlierFraction: undefined,
predictionFieldName: undefined,
previousJobType: null,
Expand Down

0 comments on commit 1a3eb3b

Please sign in to comment.