diff --git a/bin/Lib/DataFrameABC.pas b/bin/Lib/DataFrameABC.pas index f39bb5497..3fdf15971 100644 --- a/bin/Lib/DataFrameABC.pas +++ b/bin/Lib/DataFrameABC.pas @@ -347,6 +347,89 @@ type implementation +uses MLExceptions; + +const + ER_COLS_NULL = + 'cols не может быть nil!!cols cannot be nil'; + ER_SCHEMA_NULL = + 'schema не может быть nil!!schema cannot be nil'; + ER_COLS_SCHEMA_MISMATCH = + 'Количество столбцов не совпадает со схемой!!Columns count and schema mismatch'; + ER_JOIN_KEY_TYPE_MISMATCH = + 'Типы ключей соединения не совпадают!!Join key types mismatch'; + ER_JOIN_KEY_NOT_FOUND = + 'Ключ соединения "{0}" не найден!!Join key "{0}" not found'; + ER_JOIN_KIND_NOT_IMPLEMENTED = + 'Тип соединения не реализован!!Join kind not implemented'; + ER_JOIN_KEYS_LENGTH_MISMATCH = + 'leftKeys и rightKeys должны иметь одинаковую длину!!leftKeys and rightKeys must have the same length'; + ER_COLUMN_NOT_FOUND = + 'Столбец "{0}" не найден!!Column not found: {0}'; + ER_ADD_COLUMN_ROW_MISMATCH = + 'Несоответствие числа строк при добавлении столбца!!Row count mismatch when adding column'; + ER_COLUMN_VALID_LENGTH_MISMATCH = + 'Длины data и valid не совпадают при добавлении столбца!!Data and valid length mismatch when adding column'; + ER_COLUMN_INDEX_OUT_OF_RANGE = + 'Индекс столбца {0} вне диапазона [0..{1})!!Column index {0} out of range [0..{1})'; + ER_NO_VALID_VALUES_COLUMN = + 'Нет допустимых значений в столбце {0}!!No valid values in column {0}'; + ER_SORTBY_LENGTH_MISMATCH = + 'SortBy: длины colIndices и descending не совпадают!!SortBy: colIndices and descending length mismatch'; + ER_UNKNOWN_COLUMN_TYPE = + 'Неизвестный тип столбца!!Unknown column type'; + ER_COLUMN_ALREADY_EXISTS = + 'Столбец "{0}" уже существует!!Column "{0}" already exists'; + ER_SCHEMA_INCONSISTENT = + 'Несогласованная схема: столбец "{0}" имеет RowCount={1}, ожидалось {2}!!' + + 'Schema inconsistent: column "{0}" has RowCount={1}, expected {2}'; + ER_SCHEMA_COLUMNCOUNT_MISMATCH = + 'Несогласованная схема: ColumnCount={0}, columns.Count={1}!!' + + 'Schema inconsistent: ColumnCount={0}, columns.Count={1}'; + ER_SCHEMA_COLUMN_MISSING = + 'Несогласованная схема: столбец "{0}" отсутствует в схеме!!' + + 'Schema inconsistent: column "{0}" missing in schema'; + ER_SCHEMA_COLUMN_INDEX_INCONSISTENT = + 'Несогласованная схема: GetColumnIndex("{0}")={1}, ожидалось {2}!!' + + 'Schema inconsistent: GetColumnIndex("{0}")={1}, expected {2}'; + ER_COLUMN_NOT_NUMERIC = + 'Столбец не является числовым!!Column is not numeric'; + ER_GROUPBY_UNSUPPORTED_KEY_TYPE = + 'Неподдерживаемый тип ключа GroupBy: {0}!!Unsupported GroupBy key type: {0}'; + ER_ZERO_VARIANCE = + 'Нулевая дисперсия при вычислении корреляции!!Zero variance in correlation'; + ER_NO_VALID_PAIRS = + 'Нет допустимых пар для вычисления корреляции!!No valid pairs for correlation'; + ER_NO_NUMERIC_COLUMNS = + 'Нет числовых столбцов для матрицы корреляции!!No numeric columns for correlation matrix'; + ER_ZERO_STD_STANDARDIZE = + 'Нулевое стандартное отклонение при вызове Standardize!!Zero standard deviation in Standardize'; + ER_INVALID_VALUE_IN_COLUMN = + 'Недопустимое значение в столбце "{0}"!!' + + 'Invalid value in column "{0}"'; + ER_ZERO_STD_COLUMN = + 'Нулевое стандартное отклонение в столбце "{0}" при StandardizeAll!!' + + 'Zero standard deviation in column "{0}" in StandardizeAll'; + ER_ZERO_RANGE = + 'Нулевой диапазон при нормализации!!Zero range in normalization'; + ER_ZERO_RANGE_COLUMN = + 'Нулевой диапазон в столбце "{0}"!!Zero range in column "{0}"'; + ER_QUANTILE_P_INVALID = + 'Параметр p должен быть в диапазоне [0,1]!!Quantile p must be in [0,1]'; + ER_NO_VALID_VALUES_QUANTILE = + 'Нет допустимых значений для вычисления квантиля!!No valid values for quantile'; + ER_CSV_COLUMN_COUNT_MISMATCH = + 'Ошибка формата CSV: ожидалось {0} столбцов, получено {1}!!' + + 'CSV format error: expected {0} columns, got {1}'; + ER_EMPTY_CSV = + 'CSV-файл пуст!!Empty CSV'; + ER_CSV_UNCLOSED_QUOTE = + 'Ошибка формата CSV: незакрытая кавычка!!CSV format error: unclosed quote'; + ER_CSV_INVALID_BOOL = + 'Некорректное логическое значение "{0}" в столбце "{1}"!!' + + 'Invalid bool "{0}" in column "{1}"'; + + type /// Класс для группировки данных GroupByContext = class(IGroupByContext) @@ -392,11 +475,11 @@ end; constructor DataFrame.Create(cols: List; schema: DataFrameSchema); begin if cols = nil then - raise new System.ArgumentException('cols is nil'); + ArgumentNullError(ER_COLS_NULL); if schema = nil then - raise new System.ArgumentException('schema is nil'); + ArgumentNullError(ER_SCHEMA_NULL); if cols.Count <> schema.ColumnCount then - raise new System.ArgumentException('Columns count and schema mismatch'); + ArgumentError(ER_COLS_SCHEMA_MISMATCH); self.columns := cols; self.fSchema := schema; @@ -573,7 +656,7 @@ begin var rt := other.fSchema.ColumnTypeAt(rightKey); if lt <> rt then - raise new Exception('Join key types mismatch'); + Error(ER_JOIN_KEY_TYPE_MISMATCH); // 3. типоспецифичный алгоритм (КАК РАНЬШЕ) case lt of @@ -804,9 +887,8 @@ begin // 2. проверка типов ключей — через Schema for var i := 0 to n - 1 do - if fSchema.ColumnTypeAt(leftKeyIdx[i]) <> - other.fSchema.ColumnTypeAt(rightKeyIdx[i]) then - raise new Exception('Join key types mismatch'); + if fSchema.ColumnTypeAt(leftKeyIdx[i]) <> other.fSchema.ColumnTypeAt(rightKeyIdx[i]) then + Error(ER_JOIN_KEY_TYPE_MISMATCH); // 3. layout'ы (как раньше) var leftLayout := BuildJoinKeyLayout(leftKeyIdx); @@ -881,7 +963,7 @@ begin var ri := other.fSchema.IndexOf(key); if (li < 0) or (ri < 0) then - raise new Exception($'Join key "{key}" not found'); + Error(ER_JOIN_KEY_NOT_FOUND, key); var leftKeyIdx := [li]; var rightKeyIdx := [ri]; @@ -1032,28 +1114,6 @@ begin Result := res; end; -{function DataFrame.JoinInnerSingleKey(other: DataFrame; leftKey, rightKey: integer; - resultSchema: DataFrameSchema): DataFrame; -begin - // типы ключей — ТОЛЬКО из Schema - var lt := fSchema.ColumnTypeAt(leftKey); - var rt := other.fSchema.ColumnTypeAt(rightKey); - - if lt <> rt then - raise new Exception('Join key types mismatch'); - - case lt of - ctInt: - Result := JoinInnerSingleKeyInt(other, leftKey, rightKey, resultSchema); - ctFloat: - Result := JoinInnerSingleKeyFloat(other, leftKey, rightKey, resultSchema); - ctStr: - Result := JoinInnerSingleKeyStr(other, leftKey, rightKey, resultSchema); - ctBool: - Result := JoinInnerSingleKeyBool(other, leftKey, rightKey, resultSchema); - end; -end;} - function DataFrame.JoinInnerSingleKey(other: DataFrame; key: string): DataFrame; begin var leftKey := fSchema.IndexOf(key); @@ -1063,7 +1123,7 @@ begin var rt := other.fSchema.ColumnTypeAt(rightKey); if lt <> rt then - raise new Exception('Join key types mismatch'); + Error(ER_JOIN_KEY_TYPE_MISMATCH); case lt of ctInt: Result := JoinInnerSingleKeyInt(other, leftKey, rightKey); @@ -1264,9 +1324,8 @@ begin // 2. проверка типов ключей — через Schema for var i := 0 to n - 1 do - if fSchema.ColumnTypeAt(leftKeyIdx[i]) <> - other.fSchema.ColumnTypeAt(rightKeyIdx[i]) then - raise new Exception('Join key types mismatch'); + if fSchema.ColumnTypeAt(leftKeyIdx[i]) <> other.fSchema.ColumnTypeAt(rightKeyIdx[i]) then + Error(ER_JOIN_KEY_TYPE_MISMATCH); // 3. строим layout'ы var leftLayout := BuildJoinKeyLayout(leftKeyIdx); @@ -1344,7 +1403,7 @@ begin else exit(FullJoinMultiKey(other, keys)); - raise new Exception('Join kind not implemented'); + Error(ER_JOIN_KIND_NOT_IMPLEMENTED); end; function DataFrame.Join(other: DataFrame; key: string; kind: JoinKind): DataFrame; @@ -1355,7 +1414,7 @@ end; function DataFrame.Join(other: DataFrame; leftKeys, rightKeys: array of string; kind: JoinKind): DataFrame; begin if leftKeys.Length <> rightKeys.Length then - raise new Exception('leftKeys and rightKeys must have the same length'); + ArgumentError(ER_JOIN_KEYS_LENGTH_MISMATCH); // временно переименовываем столбцы справа var tmp := other; @@ -1416,7 +1475,7 @@ begin for var i := 0 to columns.Count - 1 do if columns[i].Info.Name = name then exit(i); - raise new Exception('Column not found: ' + name); + Error(ER_COLUMN_NOT_FOUND, name); end; function DataFrame.GetCursor: DataFrameCursor := @@ -1439,7 +1498,7 @@ end; procedure DataFrame.AddIntColumn(name: string; data: array of integer; valid: array of boolean; isCategorical: boolean); begin if (columns.Count > 0) and (data.Length <> RowCount) then - raise new Exception('Row count mismatch'); + DimensionError(ER_ADD_COLUMN_ROW_MISMATCH); var c := new IntColumn; c.Info := new ColumnInfo(name, ctInt, isCategorical); @@ -1450,7 +1509,7 @@ begin else begin if valid.Length <> data.Length then - raise new Exception('AddIntColumn: data and valid length mismatch'); + DimensionError(ER_COLUMN_VALID_LENGTH_MISMATCH);; c.IsValid := valid; end; @@ -1461,7 +1520,7 @@ end; procedure DataFrame.AddFloatColumn(name: string; data: array of real; valid: array of boolean); begin if (columns.Count > 0) and (data.Length <> RowCount) then - raise new Exception('Row count mismatch'); + DimensionError(ER_ADD_COLUMN_ROW_MISMATCH); var c := new FloatColumn; c.Info := new ColumnInfo(name, ctFloat, False); @@ -1472,7 +1531,7 @@ begin else begin if valid.Length <> data.Length then - raise new Exception('AddFloatColumn: data and valid length mismatch'); + DimensionError(ER_COLUMN_VALID_LENGTH_MISMATCH); c.IsValid := valid; end; @@ -1483,7 +1542,7 @@ end; procedure DataFrame.AddStrColumn(name: string; data: array of string; valid: array of boolean; isCategorical: boolean); begin if (columns.Count > 0) and (data.Length <> RowCount) then - raise new Exception('Row count mismatch'); + DimensionError(ER_ADD_COLUMN_ROW_MISMATCH); var c := new StrColumn; c.Info := new ColumnInfo(name, ctStr, isCategorical); @@ -1494,7 +1553,7 @@ begin else begin if valid.Length <> data.Length then - raise new Exception('AddStrColumn: data and valid length mismatch'); + DimensionError(ER_COLUMN_VALID_LENGTH_MISMATCH); c.IsValid := valid; end; @@ -1505,7 +1564,7 @@ end; procedure DataFrame.AddBoolColumn(name: string; data: array of boolean; valid: array of boolean); begin if (columns.Count > 0) and (data.Length <> RowCount) then - raise new Exception('Row count mismatch'); + DimensionError(ER_ADD_COLUMN_ROW_MISMATCH); var c := new BoolColumn; c.Info := new ColumnInfo(name, ctBool, False); @@ -1516,7 +1575,7 @@ begin else begin if valid.Length <> data.Length then - raise new Exception('AddBoolColumn: data and valid length mismatch'); + DimensionError(ER_COLUMN_VALID_LENGTH_MISMATCH); c.IsValid := valid; end; @@ -1528,7 +1587,7 @@ end; procedure DataFrame.CheckColumnIndex(colIndex: integer); begin if (colIndex < 0) or (colIndex >= ColumnCount) then - raise new Exception('Column index out of range'); + ArgumentOutOfRangeError(ER_COLUMN_INDEX_OUT_OF_RANGE, colIndex, ColumnCount); end; function DataFrame.Sum(colIndex: integer): real; @@ -1620,7 +1679,7 @@ begin end; if not has then - raise new Exception('Min: no valid values'); + Error(ER_NO_VALID_VALUES_COLUMN, colIndex); Result := m; end; @@ -1650,7 +1709,7 @@ begin end; if not has then - raise new Exception('Max: no valid values'); + Error(ER_NO_VALID_VALUES_COLUMN, colIndex); Result := m; end; @@ -1684,7 +1743,7 @@ begin end; if not has then - raise new Exception('MinMax: no valid values'); + Error(ER_NO_VALID_VALUES_COLUMN, colIndex); Result := (mn, mx); end; @@ -1974,7 +2033,7 @@ type function DataFrame.SortBy(colIndices: array of integer; descending: array of boolean): DataFrame; begin if colIndices.Length <> descending.Length then - raise new Exception('SortBy: length mismatch'); + ArgumentError(ER_SORTBY_LENGTH_MISMATCH); foreach var c in colIndices do CheckColumnIndex(c); @@ -2172,7 +2231,7 @@ begin res.AddBoolColumn(src.Info.Name, data, valid); end else - raise new Exception('Unknown column type'); + Error(ER_UNKNOWN_COLUMN_TYPE); end; Result := res; @@ -2422,7 +2481,7 @@ end; function DataFrame.WithColumnInt(name: string; f: DataFrameCursor -> integer): DataFrame; begin if fSchema.HasColumn(name) then - raise new Exception($'Column "{name}" already exists'); + ArgumentError(ER_COLUMN_ALREADY_EXISTS, name); var res := new DataFrame; @@ -2499,7 +2558,7 @@ end; function DataFrame.WithColumnFloat(name: string; f: DataFrameCursor -> real): DataFrame; begin if fSchema.HasColumn(name) then - raise new Exception($'Column "{name}" already exists'); + ArgumentError(ER_COLUMN_ALREADY_EXISTS, name); var res := new DataFrame; @@ -2550,7 +2609,7 @@ end; function DataFrame.WithColumnStr(name: string; f: DataFrameCursor -> string): DataFrame; begin if fSchema.HasColumn(name) then - raise new Exception($'Column "{name}" already exists'); + ArgumentError(ER_COLUMN_ALREADY_EXISTS, name); var res := new DataFrame; @@ -2601,7 +2660,7 @@ end; function DataFrame.WithColumnBool(name: string; f: DataFrameCursor -> boolean): DataFrame; begin if fSchema.HasColumn(name) then - raise new Exception($'Column "{name}" already exists'); + ArgumentError(ER_COLUMN_ALREADY_EXISTS, name); var res := new DataFrame; @@ -2737,7 +2796,7 @@ function DataFrame.AddDerivedIntColumn( ): DataFrame; begin if Schema.HasColumn(name) then - raise new Exception($'Column "{name}" already exists'); + ArgumentError(ER_COLUMN_ALREADY_EXISTS, name); var rowCount := RowCount; var data := new integer[rowCount]; @@ -3028,15 +3087,11 @@ begin var rc := columns[0].RowCount; for var i := 1 to columns.Count - 1 do if columns[i].RowCount <> rc then - raise new Exception( - $'Schema inconsistent: column "{columns[i].Info.Name}" has RowCount={columns[i].RowCount}, expected {rc}' - ); + Error(ER_SCHEMA_INCONSISTENT, columns[i].Info.Name, columns[i].RowCount, rc); // --- 2. fschema.ColumnCount = columns.Count --- if fschema.ColumnCount <> columns.Count then - raise new Exception( - $'Schema inconsistent: ColumnCount={fschema.ColumnCount}, columns.Count={columns.Count}' - ); + Error(ER_SCHEMA_COLUMNCOUNT_MISMATCH, fSchema.ColumnCount, columns.Count); // --- 3. имена уникальны и корректно индексированы --- for var i := 0 to columns.Count - 1 do @@ -3044,15 +3099,11 @@ begin var name := columns[i].Info.Name; if not fSchema.HasColumn(name) then - raise new Exception( - $'Schema inconsistent: column "{name}" missing in schema' - ); + Error(ER_SCHEMA_COLUMN_MISSING, name); var idx := GetColumnIndex(name); if idx <> i then - raise new Exception( - $'Schema inconsistent: GetColumnIndex("{name}")={idx}, expected {i}' - ); + Error(ER_SCHEMA_COLUMN_INDEX_INCONSISTENT, name, idx, i); end; {$ENDIF} @@ -3113,7 +3164,7 @@ begin isInt := false; end else - raise new Exception('Column is not numeric'); + Error(ER_COLUMN_NOT_NUMERIC); end; constructor GroupByContext.Create(df: DataFrame; keyCols: array of integer); @@ -3137,7 +3188,7 @@ begin case df.columns[keyColumn].Info.ColType of ctInt: key := cursor.Int(keyColumn); ctStr: key := cursor.Str(keyColumn); - else raise new Exception('Unsupported GroupBy key type'); + else Error(ER_GROUPBY_UNSUPPORTED_KEY_TYPE, df.columns[keyColumn].Info.ColType); end; if not groups1.ContainsKey(key) then @@ -3171,7 +3222,7 @@ begin case df.columns[c].Info.ColType of ctInt: key[i] := cursor.Int(c); ctStr: key[i] := cursor.Str(c); - else raise new Exception('Unsupported GroupBy key type'); + else Error(ER_GROUPBY_UNSUPPORTED_KEY_TYPE, df.columns[c].Info.ColType); end; end; @@ -3618,7 +3669,7 @@ begin var sy := df.Std(iy); if (sx = 0) or (sy = 0) then - raise new Exception('Zero variance in correlation'); + Error(ER_ZERO_VARIANCE); var cur := df.GetCursor; var sum := 0.0; @@ -3632,7 +3683,7 @@ begin end; if cnt = 0 then - raise new Exception('No valid pairs for correlation'); + Error(ER_NO_VALID_PAIRS); Result := sum / (cnt * sx * sy); end; @@ -3648,7 +3699,7 @@ begin var n := names.Count; if n = 0 then - raise new Exception('No numeric columns for correlation matrix'); + Error(ER_NO_NUMERIC_COLUMNS); var res := new DataFrame; @@ -3679,12 +3730,12 @@ begin var std := df.Std(idx); if std = 0 then - raise new Exception('Zero standard deviation'); + Error(ER_ZERO_STD_STANDARDIZE); Result := df.ReplaceColumnFloat(colName, cur -> begin if not cur.IsValid(idx) then - raise new Exception; + Error(ER_INVALID_VALUE_IN_COLUMN, colName); Result := (cur.Float(idx) - mean) / std; end); end; @@ -3707,7 +3758,7 @@ begin means[i] := df.Mean(i); stds[i] := df.Std(i); if stds[i] = 0 then - raise new Exception($'Zero std in column {df.fSchema.Names[i]}'); + Error(ER_ZERO_STD_COLUMN, df.fSchema.Names[i]); isNumeric[i] := true; end; end; @@ -3761,12 +3812,12 @@ begin var (mn, mx) := df.MinMax(idx); if mn = mx then - raise new Exception('Zero range in normalization'); + Error(ER_ZERO_RANGE); Result := df.ReplaceColumnFloat(colName, cur -> begin if not cur.IsValid(idx) then - raise new Exception; + Error(ER_INVALID_VALUE_IN_COLUMN, colName); Result := (cur.Float(idx) - mn) / (mx - mn); end); end; @@ -3788,7 +3839,7 @@ begin begin var (mn, mx) := df.MinMax(i); if mn = mx then - raise new Exception($'Zero range in column {df.fSchema.Names[i]}'); + Error(ER_ZERO_RANGE_COLUMN, df.fSchema.Names[i]); mins[i] := mn; maxs[i] := mx; isNumeric[i] := true; @@ -3841,7 +3892,7 @@ end; static function Statistics.Quantile(df: DataFrame; colName: string; p: real): real; begin if (p < 0) or (p > 1) then - raise new Exception('Quantile p must be in [0,1]'); + ArgumentError(ER_QUANTILE_P_INVALID); var idx := df.ColumnIndex(colName); var values := new List; @@ -3852,7 +3903,7 @@ begin values.Add(cur.Float(idx)); if values.Count = 0 then - raise new Exception('No valid values for quantile'); + Error(ER_NO_VALID_VALUES_QUANTILE); values.Sort; @@ -4320,7 +4371,7 @@ begin if parts.Length <> colCount then if strict then - raise new Exception($'CSV format error: expected {colCount} columns, got {parts.Length}'); + Error(ER_CSV_COLUMN_COUNT_MISMATCH, colCount, parts.Length); for var j := 0 to colCount - 1 do begin @@ -4347,7 +4398,7 @@ begin end; if headers = nil then - raise new Exception('Empty CSV'); + Error(ER_EMPTY_CSV); // ---------- PASS 2: allocate ---------- var df := new DataFrame; @@ -4394,7 +4445,7 @@ begin if unclosedQuote then begin if strict then - raise new Exception('CSV format error: unclosed quote'); + Error(ER_CSV_UNCLOSED_QUOTE); for var j := 0 to colCount - 1 do valid[j][row] := false; @@ -4404,7 +4455,7 @@ begin end; if (actualCount <> colCount) and strict then - raise new Exception($'CSV format error: expected {colCount} columns, got {actualCount}'); + Error(ER_CSV_COLUMN_COUNT_MISMATCH, colCount, actualCount); for var j := 0 to colCount - 1 do begin @@ -4439,8 +4490,7 @@ begin else begin if strict then - raise new Exception( - $'Invalid bool "{line.Substring(starts[j]-1, lens[j])}" in column {headers[j]}'); + Error(ER_CSV_INVALID_BOOL, line.Substring(starts[j]-1, lens[j]), headers[j]); valid[j][row] := false; end; end diff --git a/bin/Lib/DataFrameABCCore.pas b/bin/Lib/DataFrameABCCore.pas index 6d365812d..78a96a78b 100644 --- a/bin/Lib/DataFrameABCCore.pas +++ b/bin/Lib/DataFrameABCCore.pas @@ -230,6 +230,36 @@ type implementation +uses MLExceptions; + +const + ER_COLUMN_NOT_INT = + 'Столбец не является Int!!Column is not Int'; + ER_COLUMN_NOT_FLOAT = + 'Столбец не является Float!!Column is not Float'; + ER_COLUMN_NOT_STR = + 'Столбец не является Str!!Column is not Str'; + ER_COLUMN_NOT_BOOL = + 'Столбец не является Bool!!Column is not Bool'; + ER_DUPLICATE_COLUMN_NAME = + 'Повторяющееся имя столбца "{0}"!!Duplicate column name "{0}"'; + ER_NAMES_NULL = + 'names не может быть nil!!names is nil'; + ER_TYPES_NULL = + 'types не может быть nil!!types is nil'; + ER_NAMES_TYPES_LENGTH_MISMATCH = + 'Длины names и types не совпадают!!names and types length mismatch'; + ER_ISCATEGORICAL_LENGTH_MISMATCH = + 'Длина isCategorical не совпадает с names!!isCategorical length mismatch'; + ER_COLUMN_NOT_EXISTS = + 'Столбец "{0}" не существует!!Column "{0}" does not exist'; + ER_COLUMN_ALREADY_EXISTS = + 'Столбец "{0}" уже существует!!Column "{0}" already exists'; + ER_INDEX_OUT_OF_RANGE = + 'Индекс {0} вне диапазона [0..{1})!!Index {0} out of range [0..{1})'; + ER_INDICES_NULL = + 'indices не может быть nil!!indices is nil'; + //----------------------------- // Сервисные функции //----------------------------- @@ -237,25 +267,25 @@ implementation function NotInt(pos: integer): integer; begin Result := 0; - raise new Exception('Column is not Int'); + Error(ER_COLUMN_NOT_INT); end; function NotFloat(pos: integer): real; begin Result := 0; - raise new Exception('Column is not Float'); + Error(ER_COLUMN_NOT_FLOAT); end; function NotStr(pos: integer): string; begin Result := nil; - raise new Exception('Column is not Str'); + Error(ER_COLUMN_NOT_STR); end; function NotBool(pos: integer): boolean; begin Result := False; - raise new Exception('Column is not Bool'); + Error(ER_COLUMN_NOT_BOOL); end; //----------------------------- @@ -267,7 +297,7 @@ begin for var i := 0 to names.Length - 1 do begin if Result.ContainsKey(names[i]) then - raise new System.ArgumentException($'Duplicate column name "{names[i]}"'); + ArgumentError(ER_DUPLICATE_COLUMN_NAME, names[i]); Result.Add(names[i], i); end; end; @@ -275,12 +305,14 @@ end; constructor DataFrameSchema.Create(names: array of string; types: array of ColumnType; isCategorical: array of boolean); begin - if names = nil then raise new System.ArgumentException('names is nil'); - if types = nil then raise new System.ArgumentException('types is nil'); + if names = nil then + ArgumentNullError(ER_NAMES_NULL); + if types = nil then + ArgumentNullError(ER_TYPES_NULL); if names.Length <> types.Length then - raise new System.ArgumentException('names and types length mismatch'); + ArgumentError(ER_NAMES_TYPES_LENGTH_MISMATCH); if (isCategorical <> nil) and (isCategorical.Length <> names.Length) then - raise new System.ArgumentException('isCategorical length mismatch'); + ArgumentError(ER_ISCATEGORICAL_LENGTH_MISMATCH); fNames := Copy(names); fTypes := Copy(types); @@ -293,7 +325,7 @@ end; function DataFrameSchema.IndexOf(name: string): integer; begin if not fIndexByName.ContainsKey(name) then - raise new System.ArgumentException($'Column "{name}" does not exist'); + ArgumentError(ER_COLUMN_NOT_EXISTS, name); Result := fIndexByName[name]; end; @@ -303,21 +335,21 @@ function DataFrameSchema.HasColumn(name: string): boolean := function DataFrameSchema.NameAt(i: integer): string; begin if (i < 0) or (i >= ColumnCount) then - raise new System.ArgumentOutOfRangeException('i'); + ArgumentOutOfRangeError(ER_INDEX_OUT_OF_RANGE, i, ColumnCount); Result := fNames[i]; end; function DataFrameSchema.ColumnTypeAt(i: integer): ColumnType; begin if (i < 0) or (i >= ColumnCount) then - raise new System.ArgumentOutOfRangeException('i'); + ArgumentOutOfRangeError(ER_INDEX_OUT_OF_RANGE, i, ColumnCount); Result := fTypes[i]; end; function DataFrameSchema.IsCategoricalAt(i: integer): boolean; begin if (i < 0) or (i >= ColumnCount) then - raise new System.ArgumentOutOfRangeException('i'); + ArgumentOutOfRangeError(ER_INDEX_OUT_OF_RANGE, i, ColumnCount); if fIsCategorical = nil then Result := false else @@ -326,7 +358,8 @@ end; function DataFrameSchema.Select(indices: array of integer): DataFrameSchema; begin - if indices = nil then raise new System.ArgumentException('indices is nil'); + if indices = nil then + ArgumentNullError(ER_INDICES_NULL); var n := indices.Length; var names := new string[n]; @@ -348,13 +381,14 @@ end; function DataFrameSchema.Drop(indices: array of integer): DataFrameSchema; begin - if indices = nil then raise new System.ArgumentException('indices is nil'); + if indices = nil then + ArgumentNullError(ER_INDICES_NULL); var drop := new boolean[ColumnCount]; foreach var i in indices do begin if (i < 0) or (i >= ColumnCount) then - raise new System.ArgumentOutOfRangeException('indices'); + ArgumentOutOfRangeError(ER_INDEX_OUT_OF_RANGE, i, ColumnCount); drop[i] := true; end; @@ -369,9 +403,9 @@ end; function DataFrameSchema.Rename(oldName, newName: string): DataFrameSchema; begin if not HasColumn(oldName) then - raise new System.ArgumentException($'Column "{oldName}" does not exist'); + ArgumentError(ER_COLUMN_NOT_EXISTS, oldName); if HasColumn(newName) then - raise new System.ArgumentException($'Column "{newName}" already exists'); + ArgumentError(ER_COLUMN_ALREADY_EXISTS, newName); var names := Copy(fNames); names[IndexOf(oldName)] := newName; @@ -393,8 +427,10 @@ end; class function DataFrameSchema.Merge(left, right: DataFrameSchema; leftKeys, rightKeys: array of integer; rightPrefix: string): DataFrameSchema; begin - if left = nil then raise new System.ArgumentException('left is nil'); - if right = nil then raise new System.ArgumentException('right is nil'); + if left = nil then + raise new System.ArgumentException('left is nil'); + if right = nil then + raise new System.ArgumentException('right is nil'); if leftKeys.Length <> rightKeys.Length then raise new System.ArgumentException('join keys length mismatch'); diff --git a/bin/Lib/MLABC.pas b/bin/Lib/MLABC.pas index ca85b2180..579f5fa7f 100644 --- a/bin/Lib/MLABC.pas +++ b/bin/Lib/MLABC.pas @@ -50,6 +50,13 @@ type implementation +const + ER_TO_MATRIX_NO_COLUMNS = + 'ToMatrix: не указаны столбцы!!ToMatrix: no columns specified'; + ER_TO_VECTOR_NON_NUMERIC = + 'ToVector: столбец "{0}" содержит нечисловые или NA значения!!' + + 'ToVector: column "{0}" contains non-numeric or NA values'; + function ToMatrix(Self: DataFrame; colNames: array of string): Matrix; extensionmethod; begin var df := Self; @@ -57,7 +64,7 @@ begin var p := colNames.Length; if p = 0 then - raise new Exception('ToMatrix: no columns specified'); + ArgumentError(ER_TO_MATRIX_NO_COLUMNS); Result := new Matrix(n, p); @@ -92,9 +99,7 @@ begin var value: real; if not col.TryGetNumericValue(i, value) then - raise new Exception( - 'ToVector: column "' + colName + - '" contains non-numeric or NA values'); + ArgumentError(ER_TO_VECTOR_NON_NUMERIC, colName); Result[i] := value; end; diff --git a/bin/Lib/MLExceptions.pas b/bin/Lib/MLExceptions.pas index 0277fb397..5ad37d2d3 100644 --- a/bin/Lib/MLExceptions.pas +++ b/bin/Lib/MLExceptions.pas @@ -2,13 +2,24 @@ interface +const + ER_DIM_MISMATCH = + 'Несоответствие размерностей: {0} и {1}!!Dimension mismatch: {0} and {1}'; + ER_PARAM_VALUES_EMPTY = + 'Список paramValues пуст!!paramValues is empty'; + ER_EMPTY_DATASET = + 'Набор данных пуст!!Dataset is empty'; + ER_LAMBDA_NEGATIVE = + 'Параметр lambda должен быть >= 0!!Lambda parameter must be >= 0'; + ER_FIT_NOT_CALLED = + 'Метод Fit() не вызван!!Fit() must be called first'; + ER_MODEL_NULL = + 'Модель не может быть nil!!Model cannot be nil'; + type /// Базовое исключение ML-библиотеки MLException = class(Exception); - /// Ошибка аргументов ML-методов - MLArgumentException = class(System.ArgumentException); - /// Попытка использования необученной модели/пайплайна MLNotFittedException = class(MLException); @@ -19,6 +30,8 @@ procedure Error(msg: string; params args: array of object); procedure ArgumentError(msg: string; params args: array of object); procedure NotFittedError(msg: string; params args: array of object); procedure DimensionError(msg: string; params args: array of object); +procedure ArgumentNullError(msg: string; params args: array of object); +procedure ArgumentOutOfRangeError(msg: string; params args: array of object); implementation @@ -39,7 +52,7 @@ end; procedure ArgumentError(msg: string; params args: array of object); begin - raise new MLArgumentException(FormatSafe(msg, args)); + raise new System.ArgumentException(FormatSafe(msg, args)); end; procedure NotFittedError(msg: string; params args: array of object); @@ -52,4 +65,14 @@ begin raise new MLDimensionException(FormatSafe(msg, args)); end; +procedure ArgumentNullError(msg: string; params args: array of object); +begin + raise new System.ArgumentNullException(FormatSafe(msg, args)); +end; + +procedure ArgumentOutOfRangeError(msg: string; params args: array of object); +begin + raise new System.ArgumentOutOfRangeException(FormatSafe(msg, args)); +end; + end. diff --git a/bin/Lib/MLModelsABC.pas b/bin/Lib/MLModelsABC.pas index bad6be643..72d6064a1 100644 --- a/bin/Lib/MLModelsABC.pas +++ b/bin/Lib/MLModelsABC.pas @@ -593,8 +593,34 @@ type implementation -uses System; +uses MLExceptions; +const + ER_PIPELINE_NO_STEPS = + 'Pipeline должен содержать хотя бы один шаг!!Pipeline requires at least one step'; + ER_PIPELINE_LAST_NOT_MODEL = + 'Последний шаг Pipeline должен быть моделью (IModel)!!Last step must be a model (IModel)'; + ER_PIPELINE_INVALID_STEP_ORDER = + 'Все шаги, кроме последнего, должны быть трансформерами!!All steps except the last must be transformers'; + ER_TRANSFORMER_NULL = + 'Трансформер не может быть nil!!Transformer cannot be nil'; + ER_PROBA_NOT_SUPPORTED = + 'Модель не поддерживает предсказание вероятностей!!Model does not support probability prediction'; + ER_RANGE_INVALID = + 'rangeMax должен быть больше rangeMin!!rangeMax must be greater than rangeMin'; + ER_K_MUST_BE_POSITIVE = + 'Параметр k должен быть > 0!!Parameter k must be > 0'; + ER_K_EXCEEDS_FEATURES = + 'k превышает число признаков!!k exceeds feature count'; + ER_THRESHOLD_NEGATIVE = + 'Порог threshold должен быть >= 0!!threshold must be >= 0'; + ER_CHI_SQUARE_NEGATIVE = + 'ChiSquare требует неотрицательные признаки!!ChiSquare requires non-negative features'; + ER_UNKNOWN_FEATURE_SCORE = + 'Неизвестный тип FeatureScore!!Unknown FeatureScore type'; + ER_SELECTKBEST_FIT_INVALID = + 'Для SelectKBest необходимо вызывать Fit(X, y)!!SelectKBest requires Fit(X, y)'; + //----------------------------- // LinearRegression //----------------------------- @@ -609,9 +635,9 @@ begin var n := X.RowCount; if n = 0 then - raise new Exception('Empty dataset'); + ArgumentError(ER_EMPTY_DATASET); if y.Length <> n then - raise new Exception('Dimension mismatch in Fit'); + DimensionError(ER_DIM_MISMATCH, y.Length, n); // 1. Means var meanX := X.ColumnMeans; @@ -639,10 +665,10 @@ end; function LinearRegression.Predict(X: Matrix): Vector; begin if not ffitted then - raise new Exception('Model not fitted'); + NotFittedError(ER_FIT_NOT_CALLED); if X.ColCount <> fcoef.Length then - raise new Exception('Dimension mismatch in Predict'); + DimensionError(ER_DIM_MISMATCH, X.ColCount, fCoef.Length); Result := X * fcoef + intercept; end; @@ -696,7 +722,7 @@ begin var p := X.ColCount; if y.Length <> n then - raise new Exception('Dimension mismatch in Fit'); + DimensionError(ER_DIM_MISMATCH, y.Length, n); fCoef := new Vector(p); fIntercept := 0.0; @@ -726,7 +752,7 @@ end; function LogisticRegression.PredictProba(X: Matrix): Vector; begin if not fFitted then - raise new Exception('Model not fitted'); + NotFittedError(ER_FIT_NOT_CALLED); Result := Activations.Sigmoid(X * fCoef + fIntercept); end; @@ -767,7 +793,7 @@ end; constructor RidgeRegression.Create(lambda: real); begin if lambda < 0 then - raise new ArgumentException('lambda must be >= 0'); + ArgumentError(ER_LAMBDA_NEGATIVE, lambda); fLambda := lambda; fFitted := false; end; @@ -775,7 +801,7 @@ end; function RidgeRegression.Fit(X: Matrix; y: Vector): IModel; begin if X.RowCount <> y.Length then - raise new ArgumentException('X and y size mismatch'); + DimensionError(ER_DIM_MISMATCH, X.RowCount, y.Length); var n := X.RowCount; var p := X.ColCount; @@ -808,7 +834,7 @@ end; function RidgeRegression.Predict(X: Matrix): Vector; begin if not fFitted then - raise new InvalidOperationException('Model is not fitted'); + NotFittedError(ER_FIT_NOT_CALLED); Result := X * fCoef; for var i := 0 to Result.Length - 1 do @@ -833,7 +859,7 @@ end; constructor ElasticNet.Create(lambda1, lambda2: real; maxIter: integer; tol: real); begin if (lambda1 < 0) or (lambda2 < 0) then - raise new ArgumentException('lambda must be >= 0'); + ArgumentError(ER_LAMBDA_NEGATIVE); fLambda1 := lambda1; fLambda2 := lambda2; @@ -855,7 +881,7 @@ end; function ElasticNet.Fit(X: Matrix; y: Vector): IModel; begin if X.RowCount <> y.Length then - raise new ArgumentException('X and y size mismatch'); + DimensionError(ER_DIM_MISMATCH, X.RowCount, y.Length); // Loss(β) = ||y - (Xβ + b)||² + λ1||β||1 + λ2||β||2² @@ -921,7 +947,7 @@ end; function ElasticNet.Predict(X: Matrix): Vector; begin if not fFitted then - raise new InvalidOperationException('Model is not fitted'); + NotFittedError(ER_FIT_NOT_CALLED); Result := X * fCoef; for var i := 0 to Result.Length - 1 do @@ -1036,7 +1062,7 @@ end; function MulticlassLogisticRegression.PredictProbaMatrix(X: Matrix): Matrix; begin if not fFitted then - raise new Exception('Model not fitted'); + NotFittedError(ER_FIT_NOT_CALLED); var m := X.RowCount; @@ -1106,20 +1132,20 @@ constructor Pipeline.Create(model: IModel); begin Create; if model = nil then - raise new ArgumentException('Model cannot be nil'); + ArgumentError(ER_MODEL_NULL); fModel := model; end; class function Pipeline.Build(params steps: array of IPipeStep): Pipeline; begin if (steps = nil) or (Length(steps) = 0) then - raise new ArgumentException('Pipeline requires at least one step'); + ArgumentError(ER_PIPELINE_NO_STEPS); // последний шаг должен быть моделью var last := steps[High(steps)]; if not (last is IModel) then - raise new ArgumentException('Last step must be a model (IModel)'); + ArgumentError(ER_PIPELINE_LAST_NOT_MODEL); var pipe := new Pipeline(last as IModel); @@ -1129,7 +1155,7 @@ begin var step := steps[i]; if not (step is ITransformer) then - raise new ArgumentException('All steps except the last must be transformers'); + ArgumentError(ER_PIPELINE_INVALID_STEP_ORDER); pipe.Add(step as ITransformer); end; @@ -1140,7 +1166,7 @@ end; function Pipeline.Add(t: ITransformer): Pipeline; begin if t = nil then - raise new ArgumentException('Transformer cannot be nil'); + ArgumentError(ER_TRANSFORMER_NULL); fTransformers.Add(t); Result := Self; @@ -1149,7 +1175,7 @@ end; function Pipeline.SetModel(m: IModel): Pipeline; begin if m = nil then - raise new ArgumentException('Model cannot be nil'); + ArgumentError(ER_TRANSFORMER_NULL); fModel := m; Result := Self; @@ -1158,7 +1184,7 @@ end; function Pipeline.Fit(X: Matrix; y: Vector): IModel; begin if fModel = nil then - raise new Exception('Model is not set'); + ArgumentError(ER_MODEL_NULL); var Xt := X; @@ -1180,7 +1206,7 @@ end; function Pipeline.Transform(X: Matrix): Matrix; begin if not fFitted then - raise new Exception('Pipeline not fitted'); + NotFittedError(ER_FIT_NOT_CALLED); var Xt := X; @@ -1193,7 +1219,7 @@ end; function Pipeline.Predict(X: Matrix): Vector; begin if not fFitted then - raise new Exception('Pipeline not fitted'); + NotFittedError(ER_FIT_NOT_CALLED); var Xt := Transform(X); Result := fModel.Predict(Xt); @@ -1202,10 +1228,10 @@ end; function Pipeline.PredictProba(X: Matrix): Vector; begin if not fFitted then - raise new Exception('Pipeline not fitted'); + NotFittedError(ER_FIT_NOT_CALLED); if not (fModel is IProbabilisticClassifier) then - raise new Exception('Model does not support probability prediction'); + Error(ER_PROBA_NOT_SUPPORTED); var Xt := Transform(X); @@ -1234,7 +1260,7 @@ end; function Pipeline.Clone: IModel; begin if fModel = nil then - raise new InvalidOperationException('Pipeline has no model'); + ArgumentError(ER_MODEL_NULL); var p := new Pipeline; @@ -1262,7 +1288,7 @@ end; function StandardScaler.Transform(X: Matrix): Matrix; begin if not fFitted then - raise new Exception('StandardScaler not fitted'); + Error(ER_PROBA_NOT_SUPPORTED); var n := X.RowCount; var p := X.ColCount; @@ -1301,7 +1327,7 @@ end; constructor MinMaxScaler.Create(rangeMin: real; rangeMax: real); begin if rangeMax <= rangeMin then - raise new Exception('rangeMax must be greater than rangeMin'); + ArgumentError(ER_RANGE_INVALID); fRangeMin := rangeMin; fRangeMax := rangeMax; @@ -1320,7 +1346,7 @@ end; function MinMaxScaler.Transform(X: Matrix): Matrix; begin if not fFitted then - raise new Exception('MinMaxScaler is not fitted'); + NotFittedError(ER_FIT_NOT_CALLED); var n := X.RowCount; var p := X.ColCount; @@ -1362,7 +1388,7 @@ end; constructor PCATransformer.Create(k: integer); begin if k <= 0 then - raise new ArgumentException('k must be > 0'); + ArgumentError(ER_K_MUST_BE_POSITIVE); fK := k; fFitted := false; @@ -1371,7 +1397,7 @@ end; function PCATransformer.Fit(X: Matrix): ITransformer; begin if fK > X.ColCount then - raise new ArgumentException('k exceeds feature count'); + ArgumentError(ER_K_EXCEEDS_FEATURES); fMean := X.ColumnMeans; @@ -1392,7 +1418,7 @@ end; function PCATransformer.Transform(X: Matrix): Matrix; begin if not fFitted then - raise new InvalidOperationException('PCA not fitted'); + NotFittedError(ER_FIT_NOT_CALLED); var Xc := X.Clone; @@ -1420,7 +1446,7 @@ end; constructor VarianceThreshold.Create(threshold: real); begin if threshold < 0 then - raise new ArgumentException('threshold must be >= 0'); + ArgumentError(ER_THRESHOLD_NEGATIVE); fThreshold := threshold; fFitted := false; @@ -1445,7 +1471,7 @@ end; function VarianceThreshold.Transform(X: Matrix): Matrix; begin if not fFitted then - raise new InvalidOperationException('VarianceThreshold not fitted'); + NotFittedError(ER_FIT_NOT_CALLED); var n := X.RowCount; var k := fSelected.Length; @@ -1476,7 +1502,7 @@ end; constructor SelectKBest.Create(k: integer; score: FeatureScore); begin if k <= 0 then - raise new ArgumentException('k must be > 0'); + ArgumentError(ER_K_MUST_BE_POSITIVE); fK := k; fScoreType := score; @@ -1487,7 +1513,7 @@ end; constructor SelectKBest.Create(k: integer; scoreFunc: (Vector, Vector) -> real); begin if k <= 0 then - raise new ArgumentException('k must be > 0'); + ArgumentError(ER_K_MUST_BE_POSITIVE); fK := k; fScoreFunc := scoreFunc; @@ -1589,7 +1615,7 @@ begin begin var v := feature[i]; if v < 0 then - raise new ArgumentException('ChiSquare requires non-negative features'); + ArgumentError(ER_CHI_SQUARE_NEGATIVE); total += v; @@ -1630,7 +1656,7 @@ begin FeatureScore.AnovaF: Result := ComputeAnovaF(feature, y); FeatureScore.ChiSquare: Result := ComputeChiSquare(feature, y); else - raise new InvalidOperationException('Unknown FeatureScore type'); + Error(ER_UNKNOWN_FEATURE_SCORE); end; end; @@ -1671,13 +1697,13 @@ end; function SelectKBest.Fit(X: Matrix): ITransformer; begin Result := nil; - raise new InvalidOperationException('Target variable is required'); + ArgumentError(ER_SELECTKBEST_FIT_INVALID); end; function SelectKBest.Transform(X: Matrix): Matrix; begin if not fFitted then - raise new InvalidOperationException('SelectKBest not fitted'); + NotFittedError(ER_FIT_NOT_CALLED); var n := X.RowCount; var k := fSelected.Length; @@ -1731,7 +1757,7 @@ end; function Normalizer.Transform(X: Matrix): Matrix; begin if not fFitted then - raise new InvalidOperationException('Normalizer not fitted'); + NotFittedError(ER_FIT_NOT_CALLED); var n := X.RowCount; var p := X.ColCount; diff --git a/bin/Lib/MetricsABC.pas b/bin/Lib/MetricsABC.pas index e32885714..6d9e14a15 100644 --- a/bin/Lib/MetricsABC.pas +++ b/bin/Lib/MetricsABC.pas @@ -138,6 +138,8 @@ type implementation +uses MLExceptions; + //----------------------------- // Metrics //----------------------------- @@ -145,7 +147,7 @@ implementation static function Metrics.MSE(yTrue, yPred: Vector): real; begin if yTrue.Length <> yPred.Length then - raise new Exception('Dimension mismatch in MSE'); + DimensionError(ER_DIM_MISMATCH, yTrue.Length, yPred.Length); var n := yTrue.Length; var s := 0.0; @@ -162,7 +164,7 @@ end; static function Metrics.MAE(yTrue, yPred: Vector): real; begin if yTrue.Length <> yPred.Length then - raise new Exception('Dimension mismatch in MAE'); + DimensionError(ER_DIM_MISMATCH, yTrue.Length, yPred.Length); var n := yTrue.Length; var s := 0.0; @@ -176,7 +178,7 @@ end; static function Metrics.R2(yTrue, yPred: Vector): real; begin if yTrue.Length <> yPred.Length then - raise new Exception('Dimension mismatch in R2'); + DimensionError(ER_DIM_MISMATCH, yTrue.Length, yPred.Length); var n := yTrue.Length; var meanY := yTrue.Mean; @@ -202,7 +204,7 @@ end; static function Metrics.LogLoss(yTrue, yProb: Vector): real; begin if yTrue.Length <> yProb.Length then - raise new Exception('Dimension mismatch in LogLoss'); + DimensionError(ER_DIM_MISMATCH, yTrue.Length, yProb.Length); var n := yTrue.Length; var eps := 1e-15; @@ -224,7 +226,7 @@ end; static function Metrics.ROC(yTrue, yProb: Vector): (Vector, Vector); begin if yTrue.Length <> yProb.Length then - raise new Exception('Dimension mismatch in ROC'); + DimensionError(ER_DIM_MISMATCH, yTrue.Length, yProb.Length); var n := yTrue.Length; @@ -319,7 +321,7 @@ end; constructor ConfusionMatrix.Create(yTrue, yPred: Vector); begin if yTrue.Length <> yPred.Length then - raise new Exception('Dimension mismatch in ConfusionMatrix'); + DimensionError(ER_DIM_MISMATCH, yTrue.Length, yPred.Length); for var i := 0 to yTrue.Length - 1 do begin diff --git a/bin/Lib/PreprocessorABC.pas b/bin/Lib/PreprocessorABC.pas index 3a5b68ec2..0d26d6765 100644 --- a/bin/Lib/PreprocessorABC.pas +++ b/bin/Lib/PreprocessorABC.pas @@ -173,6 +173,14 @@ type implementation +uses MLExceptions; + +const + ER_SCALER_NO_COLUMNS = + 'StandardScaler: столбцы не указаны!!StandardScaler: columns not specified'; + ER_SCALER_COLUMN_NOT_NUMERIC = + 'StandardScaler: столбец "{0}" не является числовым!!StandardScaler: column "{0}" is not numeric'; + //----------------------------- // StandardScaler //----------------------------- @@ -180,7 +188,7 @@ implementation constructor DataStandardScaler.Create(params columns: array of string); begin if (columns = nil) or (columns.Length = 0) then - raise new ArgumentException('StandardScaler: columns not specified'); + ArgumentError(ER_SCALER_NO_COLUMNS); cols := columns; fitted := false; @@ -199,7 +207,7 @@ begin var ct := df.Schema.ColumnTypeAt(idx); if not (ct in [ColumnType.ctInt, ColumnType.ctFloat]) then - raise new Exception($'StandardScaler: column "{colName}" is not numeric'); + Error(ER_SCALER_COLUMN_NOT_NUMERIC, colName); var sum := 0.0; var sum2 := 0.0; @@ -710,7 +718,7 @@ end; function DataPipeline.Transform(df: DataFrame): DataFrame; begin if not fitted then - raise new Exception('Pipeline is not fitted'); + NotFittedError(ER_FIT_NOT_CALLED);NotFittedError(ER_FIT_NOT_CALLED); var current := df; foreach var step in steps do diff --git a/bin/Lib/ValidationML.pas b/bin/Lib/ValidationML.pas index fb7934164..5e5b10f83 100644 --- a/bin/Lib/ValidationML.pas +++ b/bin/Lib/ValidationML.pas @@ -76,6 +76,18 @@ const ER_DIM_MISMATCH_TRAIN_TEST = 'Несоответствие размерностей в TrainTestSplit: X.RowCount={0}, y.Length={1}!!' + 'Dimension mismatch in TrainTestSplit: X.RowCount={0}, y.Length={1}'; + ER_TEST_RATIO_INVALID = + 'Параметр testRatio должен быть в интервале (0,1), получено {0}!!' + + 'Parameter testRatio must be in (0,1), got {0}'; + ER_K_INVALID = + 'Некорректное значение k в KFold: k={0}, n={1}!!' + + 'Invalid k in KFold: k={0}, n={1}'; + ER_K_INVALID_STRATIFIED = + 'Некорректное значение k в StratifiedKFold: k={0}, n={1}!!' + + 'Invalid k in StratifiedKFold: k={0}, n={1}'; + ER_STRATIFIED_LABELS_INVALID = + 'StratifiedKFold поддерживает только метки 0/1!!' + + 'StratifiedKFold supports only 0/1 labels'; //----------------------------- // Validation @@ -88,7 +100,7 @@ begin DimensionError(ER_DIM_MISMATCH_TRAIN_TEST, X.RowCount, y.Length); if (testRatio <= 0) or (testRatio >= 1) then - raise new Exception('testRatio must be in (0,1)'); + ArgumentError(ER_TEST_RATIO_INVALID, testRatio); var n := X.RowCount; var p := X.ColCount; @@ -137,7 +149,7 @@ static function Validation.KFold(n, k: integer; seed: integer): sequence of (array of integer, array of integer); begin if (k < 2) or (k > n) then - raise new Exception('Invalid k in KFold'); + ArgumentError(ER_K_INVALID, k, n); var rnd := new System.Random(seed); var idx := (0..n-1).OrderBy(i -> rnd.Next).ToArray; @@ -163,7 +175,7 @@ static function Validation.StratifiedKFold(y: Vector; k: integer; begin var n := y.Length; if (k < 2) or (k > n) then - raise new Exception('Invalid k in StratifiedKFold'); + ArgumentError(ER_K_INVALID_STRATIFIED, k, n); var rnd := new System.Random(seed); @@ -178,7 +190,7 @@ begin .ToArray; if idx0.Length + idx1.Length <> n then - raise new Exception('StratifiedKFold supports only 0/1 labels'); + ArgumentError(ER_STRATIFIED_LABELS_INVALID); var base0 := idx0.Length div k; var extra0 := idx0.Length mod k; @@ -213,7 +225,7 @@ static function Validation.CrossValidate(model: IModel; X: Matrix; y: Vector; k: integer; metric: (Vector,Vector) -> real; seed: integer): real; begin if X.RowCount <> y.Length then - raise new Exception('Dimension mismatch in CrossValidate'); + DimensionError(ER_DIM_MISMATCH, X.RowCount, y.Length); var total := 0.0; var folds := 0; @@ -260,7 +272,7 @@ static function Validation.StratifiedCrossValidate( ): real; begin if X.RowCount <> y.Length then - raise new Exception('Dimension mismatch in StratifiedCrossValidate'); + DimensionError(ER_DIM_MISMATCH, X.RowCount, y.Length); var total := 0.0; var folds := 0; @@ -314,7 +326,7 @@ class function GridSearch.Search( ): (real, real, T); where T: IModel; begin if paramValues.Length = 0 then - raise new System.ArgumentException('paramValues is empty'); + ArgumentError(ER_PARAM_VALUES_EMPTY); var bestParam := paramValues[0]; var bestScore := -1e308;