diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts index aa637f71db1cc..618ea5184007d 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts @@ -121,16 +121,24 @@ export interface DfAnalyticsExplainResponse { } export interface Eval { - meanSquaredError: number | string; + mse: number | string; + msle: number | string; + huber: number | string; rSquared: number | string; error: null | string; } export interface RegressionEvaluateResponse { regression: { + huber: { + value: number; + }; mse: { value: number; }; + msle: { + value: number; + }; r_squared: { value: number; }; @@ -414,19 +422,37 @@ export const useRefreshAnalyticsList = ( const DEFAULT_SIG_FIGS = 3; -export function getValuesFromResponse(response: RegressionEvaluateResponse) { - let meanSquaredError = response?.regression?.mse?.value; +interface RegressionEvaluateExtractedResponse { + mse: number | string; + msle: number | string; + huber: number | string; + r_squared: number | string; +} - if (meanSquaredError) { - meanSquaredError = Number(meanSquaredError.toPrecision(DEFAULT_SIG_FIGS)); - } +export const EMPTY_STAT = '--'; - let rSquared = response?.regression?.r_squared?.value; - if (rSquared) { - rSquared = Number(rSquared.toPrecision(DEFAULT_SIG_FIGS)); +export function getValuesFromResponse(response: RegressionEvaluateResponse) { + const results: RegressionEvaluateExtractedResponse = { + mse: EMPTY_STAT, + msle: EMPTY_STAT, + huber: EMPTY_STAT, + r_squared: EMPTY_STAT, + }; + + if (response?.regression) { + for (const statType in response.regression) { + if (response.regression.hasOwnProperty(statType)) { + let currentStatValue = + response.regression[statType as keyof RegressionEvaluateResponse['regression']]?.value; + if (currentStatValue) { + currentStatValue = Number(currentStatValue.toPrecision(DEFAULT_SIG_FIGS)); + } + results[statType as keyof RegressionEvaluateExtractedResponse] = currentStatValue; + } + } } - return { meanSquaredError, rSquared }; + return results; } interface ResultsSearchBoolQuery { bool: Dictionary; @@ -490,13 +516,22 @@ export function getEvalQueryBody({ return query; } +export enum REGRESSION_STATS { + MSE = 'mse', + MSLE = 'msle', + R_SQUARED = 'rSquared', + HUBER = 'huber', +} + interface EvaluateMetrics { classification: { multiclass_confusion_matrix: object; }; regression: { r_squared: object; - mean_squared_error: object; + mse: object; + msle: object; + huber: object; }; } @@ -541,7 +576,9 @@ export const loadEvalData = async ({ }, regression: { r_squared: {}, - mean_squared_error: {}, + mse: {}, + msle: {}, + huber: {}, }, }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx index b83dd2e4329e0..9dae54b6537b3 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx @@ -71,12 +71,8 @@ export const ConfigurationStepForm: FC = ({ EuiComboBoxOptionOption[] >([]); const [includesTableItems, setIncludesTableItems] = useState([]); - const [maxDistinctValuesError, setMaxDistinctValuesError] = useState( - undefined - ); - const [unsupportedFieldsError, setUnsupportedFieldsError] = useState( - undefined - ); + const [maxDistinctValuesError, setMaxDistinctValuesError] = useState(); + const [unsupportedFieldsError, setUnsupportedFieldsError] = useState(); const { setEstimatedModelMemoryLimit, setFormState } = actions; const { estimatedModelMemoryLimit, form, isJobCreated, requestMessages } = state; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_details.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_details.tsx index a4d86b48006e8..8a41eb4b8a865 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_details.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_details.tsx @@ -26,7 +26,7 @@ export const DetailsStepDetails: FC<{ setCurrentStep: any; state: State }> = ({ state, }) => { const { form, isJobCreated } = state; - const { description, jobId, destinationIndex } = form; + const { description, jobId, destinationIndex, resultsField } = form; const detailsFirstCol: ListItems[] = [ { @@ -37,6 +37,19 @@ export const DetailsStepDetails: FC<{ setCurrentStep: any; state: State }> = ({ }, ]; + if ( + resultsField !== undefined && + typeof resultsField === 'string' && + resultsField.trim() !== '' + ) { + detailsFirstCol.push({ + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.resultsField', { + defaultMessage: 'Results field', + }), + description: resultsField, + }); + } + const detailsSecondCol: ListItems[] = [ { title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.jobDescription', { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_form.tsx index d846ae95c2c7e..168d5e31f57c3 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_form.tsx @@ -47,6 +47,7 @@ export const DetailsStepForm: FC = ({ jobIdExists, jobIdInvalidMaxLength, jobIdValid, + resultsField, } = form; const forceInput = useRef(null); @@ -195,6 +196,22 @@ export const DetailsStepForm: FC = ({ data-test-subj="mlAnalyticsCreateJobFlyoutDestinationIndexInput" /> + + setFormState({ resultsField: e.target.value })} + data-test-subj="mlAnalyticsCreateJobWizardResultsFieldInput" + /> + = ({ jobConfig, jobStatus, searchQuery }) => { const { @@ -82,18 +94,19 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) genErrorEval.eval && isRegressionEvaluateResponse(genErrorEval.eval) ) { - const { meanSquaredError, rSquared } = getValuesFromResponse(genErrorEval.eval); + const { mse, msle, huber, r_squared } = getValuesFromResponse(genErrorEval.eval); setGeneralizationEval({ - meanSquaredError, - rSquared, + mse, + msle, + huber, + rSquared: r_squared, error: null, }); setIsLoadingGeneralization(false); } else { setIsLoadingGeneralization(false); setGeneralizationEval({ - meanSquaredError: '--', - rSquared: '--', + ...EMPTY_STATS, error: genErrorEval.error, }); } @@ -118,18 +131,19 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) trainingErrorEval.eval && isRegressionEvaluateResponse(trainingErrorEval.eval) ) { - const { meanSquaredError, rSquared } = getValuesFromResponse(trainingErrorEval.eval); + const { mse, msle, huber, r_squared } = getValuesFromResponse(trainingErrorEval.eval); setTrainingEval({ - meanSquaredError, - rSquared, + mse, + msle, + huber, + rSquared: r_squared, error: null, }); setIsLoadingTraining(false); } else { setIsLoadingTraining(false); setTrainingEval({ - meanSquaredError: '--', - rSquared: '--', + ...EMPTY_STATS, error: trainingErrorEval.error, }); } @@ -274,22 +288,48 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) - + + {/* First row stats */} - + + + + + + + + + {/* Second row stats */} - + + + + + + + + @@ -331,22 +371,48 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) - + + {/* First row stats */} - + + + + + + + + + {/* Second row stats */} - + + + + + + + + diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_stat.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_stat.tsx index 1b4461b2bb075..114ec75efb2e7 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_stat.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_stat.tsx @@ -6,58 +6,99 @@ import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiStat, EuiIconTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiStat, EuiIconTip, EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; +import { REGRESSION_STATS } from '../../../../common/analytics'; interface Props { isLoading: boolean; title: number | string; - isMSE: boolean; + statType: REGRESSION_STATS; dataTestSubj: string; } -const meanSquaredErrorText = i18n.translate( - 'xpack.ml.dataframe.analytics.regressionExploration.meanSquaredErrorText', - { - defaultMessage: 'Mean squared error', - } -); -const rSquaredText = i18n.translate( - 'xpack.ml.dataframe.analytics.regressionExploration.rSquaredText', - { - defaultMessage: 'R squared', - } -); -const meanSquaredErrorTooltipContent = i18n.translate( - 'xpack.ml.dataframe.analytics.regressionExploration.meanSquaredErrorTooltipContent', - { - defaultMessage: - 'Measures how well the regression analysis model is performing. Mean squared sum of the difference between true and predicted values.', - } -); -const rSquaredTooltipContent = i18n.translate( - 'xpack.ml.dataframe.analytics.regressionExploration.rSquaredTooltipContent', - { - defaultMessage: - 'Represents the goodness of fit. Measures how well the observed outcomes are replicated by the model.', - } -); +const statDescriptions = { + [REGRESSION_STATS.MSE]: i18n.translate( + 'xpack.ml.dataframe.analytics.regressionExploration.meanSquaredErrorText', + { + defaultMessage: 'Mean squared error', + } + ), + [REGRESSION_STATS.MSLE]: i18n.translate( + 'xpack.ml.dataframe.analytics.regressionExploration.msleText', + { + defaultMessage: 'Mean squared logarithmic error', + } + ), + [REGRESSION_STATS.R_SQUARED]: i18n.translate( + 'xpack.ml.dataframe.analytics.regressionExploration.rSquaredText', + { + defaultMessage: 'R squared', + } + ), + [REGRESSION_STATS.HUBER]: ( + + {i18n.translate('xpack.ml.dataframe.analytics.regressionExploration.huberLinkText', { + defaultMessage: 'Pseudo Huber loss function', + })} + + ), + }} + /> + ), +}; + +const tooltipContent = { + [REGRESSION_STATS.MSE]: i18n.translate( + 'xpack.ml.dataframe.analytics.regressionExploration.meanSquaredErrorTooltipContent', + { + defaultMessage: + 'Measures how well the regression analysis model is performing. Mean squared sum of the difference between true and predicted values.', + } + ), + [REGRESSION_STATS.MSLE]: i18n.translate( + 'xpack.ml.dataframe.analytics.regressionExploration.msleTooltipContent', + { + defaultMessage: + 'Average squared difference between the logarithm of the predicted values and the logarithm of the actual (ground truth) value', + } + ), + [REGRESSION_STATS.R_SQUARED]: i18n.translate( + 'xpack.ml.dataframe.analytics.regressionExploration.rSquaredTooltipContent', + { + defaultMessage: + 'Represents the goodness of fit. Measures how well the observed outcomes are replicated by the model.', + } + ), +}; -export const EvaluateStat: FC = ({ isLoading, isMSE, title, dataTestSubj }) => ( +export const EvaluateStat: FC = ({ isLoading, statType, title, dataTestSubj }) => ( - + {statType !== REGRESSION_STATS.HUBER && ( + + )} ); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.test.ts index 006cccf3b4610..9db32e298691e 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.test.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.test.ts @@ -131,7 +131,7 @@ describe('Analytics job clone action', () => { }, analyzed_fields: { includes: [], - excludes: [], + excludes: ['excluded_field'], }, model_memory_limit: '350mb', allow_lazy_start: false, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.tsx index f8b6fdfbe2119..280ec544c1e5e 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.tsx @@ -247,6 +247,7 @@ const getAnalyticsJobMeta = (config: CloneDataFrameAnalyticsConfig): AnalyticsJo }, results_field: { optional: true, + formKey: 'resultsField', defaultValue: DEFAULT_RESULTS_FIELD, }, }, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx index 4d029ff1d9546..5276fedff0fde 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx @@ -29,6 +29,7 @@ import { getDataFrameAnalyticsProgressPhase, isCompletedAnalyticsJob } from './c import { isRegressionAnalysis, ANALYSIS_CONFIG_TYPE, + REGRESSION_STATS, isRegressionEvaluateResponse, } from '../../../../common/analytics'; import { ExpandedRowMessagesPane } from './expanded_row_messages_pane'; @@ -44,7 +45,7 @@ function getItemDescription(value: any) { interface LoadedStatProps { isLoading: boolean; evalData: Eval; - resultProperty: 'meanSquaredError' | 'rSquared'; + resultProperty: REGRESSION_STATS; } const LoadedStat: FC = ({ isLoading, evalData, resultProperty }) => { @@ -61,7 +62,7 @@ interface Props { item: DataFrameAnalyticsListRow; } -const defaultEval: Eval = { meanSquaredError: '', rSquared: '', error: null }; +const defaultEval: Eval = { mse: '', msle: '', huber: '', rSquared: '', error: null }; export const ExpandedRow: FC = ({ item }) => { const [trainingEval, setTrainingEval] = useState(defaultEval); @@ -94,17 +95,21 @@ export const ExpandedRow: FC = ({ item }) => { genErrorEval.eval && isRegressionEvaluateResponse(genErrorEval.eval) ) { - const { meanSquaredError, rSquared } = getValuesFromResponse(genErrorEval.eval); + const { mse, msle, huber, r_squared } = getValuesFromResponse(genErrorEval.eval); setGeneralizationEval({ - meanSquaredError, - rSquared, + mse, + msle, + huber, + rSquared: r_squared, error: null, }); setIsLoadingGeneralization(false); } else { setIsLoadingGeneralization(false); setGeneralizationEval({ - meanSquaredError: '', + mse: '', + msle: '', + huber: '', rSquared: '', error: genErrorEval.error, }); @@ -124,17 +129,21 @@ export const ExpandedRow: FC = ({ item }) => { trainingErrorEval.eval && isRegressionEvaluateResponse(trainingErrorEval.eval) ) { - const { meanSquaredError, rSquared } = getValuesFromResponse(trainingErrorEval.eval); + const { mse, msle, huber, r_squared } = getValuesFromResponse(trainingErrorEval.eval); setTrainingEval({ - meanSquaredError, - rSquared, + mse, + msle, + huber, + rSquared: r_squared, error: null, }); setIsLoadingTraining(false); } else { setIsLoadingTraining(false); setTrainingEval({ - meanSquaredError: '', + mse: '', + msle: '', + huber: '', rSquared: '', error: genErrorEval.error, }); @@ -221,7 +230,17 @@ export const ExpandedRow: FC = ({ item }) => { + ), + }, + { + title: 'generalization mean squared logarithmic error', + description: ( + ), }, @@ -231,7 +250,17 @@ export const ExpandedRow: FC = ({ item }) => { + ), + }, + { + title: 'generalization pseudo huber loss function', + description: ( + ), }, @@ -241,7 +270,17 @@ export const ExpandedRow: FC = ({ item }) => { + ), + }, + { + title: 'training mean squared logarithmic error', + description: ( + ), }, @@ -251,7 +290,17 @@ export const ExpandedRow: FC = ({ item }) => { + ), + }, + { + title: 'training pseudo huber loss function', + description: ( + ), } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts index 81d35679443b8..b344e44c97d59 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts @@ -144,6 +144,11 @@ export const validateAdvancedEditor = (state: State): State => { const destinationIndexNameValid = isValidIndexName(destinationIndexName); const destinationIndexPatternTitleExists = state.indexPatternsMap[destinationIndexName] !== undefined; + + const resultsFieldEmptyString = + typeof jobConfig?.dest?.results_field === 'string' && + jobConfig?.dest?.results_field.trim() === ''; + const mml = jobConfig.model_memory_limit; const modelMemoryLimitEmpty = mml === '' || mml === undefined; if (!modelMemoryLimitEmpty && mml !== undefined) { @@ -292,6 +297,18 @@ export const validateAdvancedEditor = (state: State): State => { }); } + if (resultsFieldEmptyString) { + state.advancedEditorMessages.push({ + error: i18n.translate( + 'xpack.ml.dataframe.analytics.create.advancedEditorMessage.resultsFieldEmptyString', + { + defaultMessage: 'The results field must not be an empty string.', + } + ), + message: '', + }); + } + if (dependentVariableEmpty) { state.advancedEditorMessages.push({ error: i18n.translate( @@ -336,6 +353,7 @@ export const validateAdvancedEditor = (state: State): State => { sourceIndexNameValid && !destinationIndexNameEmpty && destinationIndexNameValid && + !resultsFieldEmptyString && !dependentVariableEmpty && !modelMemoryLimitEmpty && numTopFeatureImportanceValuesValid && diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts index cedbe9094cb20..0d425c8ead4a2 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts @@ -82,6 +82,7 @@ export interface State { previousJobType: null | AnalyticsJobType; requiredFieldsError: string | undefined; randomizeSeed: undefined | number; + resultsField: undefined | string; sourceIndex: EsIndexName; sourceIndexNameEmpty: boolean; sourceIndexNameValid: boolean; @@ -147,6 +148,7 @@ export const getInitialState = (): State => ({ previousJobType: null, requiredFieldsError: undefined, randomizeSeed: undefined, + resultsField: undefined, sourceIndex: '', sourceIndexNameEmpty: true, sourceIndexNameValid: false, @@ -198,6 +200,13 @@ export const getJobConfigFromFormState = ( model_memory_limit: formState.modelMemoryLimit, }; + const resultsFieldEmpty = + typeof formState?.resultsField === 'string' && formState?.resultsField.trim() === ''; + + if (jobConfig.dest && !resultsFieldEmpty) { + jobConfig.dest.results_field = formState.resultsField; + } + if ( formState.jobType === ANALYSIS_CONFIG_TYPE.REGRESSION || formState.jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION @@ -277,6 +286,7 @@ export function getCloneFormStateFromJobConfig( const resultState: Partial = { jobType, description: analyticsJobConfig.description ?? '', + resultsField: analyticsJobConfig.dest.results_field, sourceIndex: Array.isArray(analyticsJobConfig.source.index) ? analyticsJobConfig.source.index.join(',') : analyticsJobConfig.source.index,