ML - исправление ошибок и неточностей (внешний аудит)
This commit is contained in:
parent
b9aa6ae65d
commit
9cf33b094c
|
|
@ -115,9 +115,10 @@ type
|
|||
|
||||
function GetColumn(i: integer): Column;
|
||||
|
||||
/// Добавляет в DataFrame столбец-представление (view),
|
||||
/// использующий те же данные, что и исходный столбец
|
||||
procedure AddColumnView(src: Column);
|
||||
/// Добавляет в DataFrame столбец-синоним,
|
||||
/// разделяющий те же массивы данных и валидности с исходным столбцом.
|
||||
/// Изменение данных повлияет на все DataFrame, использующие этот столбец.
|
||||
procedure AddColumnAlias(src: Column);
|
||||
|
||||
function ExtendSchema(name: string; colType: ColumnType; isCategorical: boolean): DataFrameSchema;
|
||||
|
||||
|
|
@ -164,12 +165,16 @@ type
|
|||
procedure AddBoolColumn(name: string; data: array of boolean; valid: array of boolean := nil);
|
||||
|
||||
/// Возвращает данные целочисленного столбца по имени
|
||||
/// Не изменяйте их, иначе DataFrame будет повреждён
|
||||
function GetIntColumn(name: string): array of integer;
|
||||
/// Возвращает данные вещественного столбца по имени
|
||||
/// Не изменяйте их, иначе DataFrame будет повреждён
|
||||
function GetFloatColumn(name: string): array of real;
|
||||
/// Возвращает данные строкового столбца по имени
|
||||
/// Не изменяйте их, иначе DataFrame будет повреждён
|
||||
function GetStrColumn(name: string): array of string;
|
||||
/// Возвращает данные логического столбца по имени
|
||||
/// Не изменяйте их, иначе DataFrame будет повреждён
|
||||
function GetBoolColumn(name: string): array of boolean;
|
||||
|
||||
/// Вычисляет сумму значений столбца по индексу
|
||||
|
|
@ -224,13 +229,24 @@ type
|
|||
/// Возвращает статистику по всем числовым столбцам
|
||||
function DescribeAll: Dictionary<string, DescribeStats>;
|
||||
|
||||
/// Группирует данные по столбцу по индексу
|
||||
/// Группирует данные по столбцу по индексу.
|
||||
/// Поддерживаемые типы ключей: integer, string, boolean.
|
||||
/// Вещественные столбцы (float) не поддерживаются из-за численной нестабильности.
|
||||
function GroupBy(colIndex: integer): IGroupByContext;
|
||||
/// Группирует данные по столбцу по имени
|
||||
|
||||
/// Группирует данные по столбцу по имени.
|
||||
/// Поддерживаемые типы ключей: integer, string, boolean.
|
||||
/// Вещественные столбцы (float) не поддерживаются из-за численной нестабильности.
|
||||
function GroupBy(colName: string): IGroupByContext;
|
||||
/// Группирует данные по нескольким столбцам по индексам
|
||||
|
||||
/// Группирует данные по нескольким столбцам по индексам.
|
||||
/// Все столбцы-ключи должны иметь тип integer, string или boolean.
|
||||
/// Использование float-столбцов в качестве ключей не поддерживается.
|
||||
function GroupBy(colIndices: array of integer): IGroupByContext;
|
||||
/// Группирует данные по нескольким столбцам по именам
|
||||
|
||||
/// Группирует данные по нескольким столбцам по именам.
|
||||
/// Все столбцы-ключи должны иметь тип integer, string или boolean.
|
||||
/// Использование float-столбцов в качестве ключей не поддерживается.
|
||||
function GroupBy(colNames: array of string): IGroupByContext;
|
||||
|
||||
/// Возвращает первые n строк
|
||||
|
|
@ -585,9 +601,11 @@ const
|
|||
'Индекс строки вне диапазона!!Row index is out of range';
|
||||
ER_UNSUPPORTED_COLUMN_TYPE =
|
||||
'Неподдерживаемый тип столбца!!Unsupported column type';
|
||||
ER_JOIN_FLOAT_KEY_NOT_SUPPORTED =
|
||||
'Соединение по вещественным ключам не поддерживается из-за ошибок точности!!Join on float keys is not supported due to precision issues';
|
||||
|
||||
type
|
||||
GroupKey = class
|
||||
GroupKey = class(IComparable<GroupKey>)
|
||||
private
|
||||
fValues: array of object;
|
||||
public
|
||||
|
|
@ -596,6 +614,8 @@ type
|
|||
function GetHashCode: integer; override;
|
||||
|
||||
property Values: array of object read fValues;
|
||||
|
||||
function CompareTo(other: GroupKey): integer;
|
||||
end;
|
||||
|
||||
/// Класс для группировки данных
|
||||
|
|
@ -1155,7 +1175,10 @@ end;
|
|||
|
||||
function DataFrame.LeftJoinSingleKeyFloat(other: DataFrame; leftKey, rightKey: integer): DataFrame;
|
||||
begin
|
||||
var index := new Dictionary<real, List<integer>>;
|
||||
Error(ER_JOIN_FLOAT_KEY_NOT_SUPPORTED);
|
||||
Result := nil;
|
||||
|
||||
{var index := new Dictionary<real, List<integer>>;
|
||||
|
||||
var rcur := other.GetCursor;
|
||||
while rcur.MoveNext do
|
||||
|
|
@ -1203,7 +1226,7 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
Result := BuildLeftJoinResult(Self, other, leftIdx, rightIdx, leftKey, rightKey);
|
||||
Result := BuildLeftJoinResult(Self, other, leftIdx, rightIdx, leftKey, rightKey);}
|
||||
end;
|
||||
|
||||
function DataFrame.LeftJoinSingleKeyStr(other: DataFrame; leftKey, rightKey: integer): DataFrame;
|
||||
|
|
@ -1798,7 +1821,9 @@ end;
|
|||
|
||||
function DataFrame.JoinInnerSingleKeyFloat(other: DataFrame; leftKey, rightKey: integer): DataFrame;
|
||||
begin
|
||||
var index := new Dictionary<real, List<integer>>;
|
||||
Error(ER_JOIN_FLOAT_KEY_NOT_SUPPORTED);
|
||||
Result := nil;
|
||||
{var index := new Dictionary<real, List<integer>>;
|
||||
|
||||
var rcur := other.GetCursor;
|
||||
while rcur.MoveNext do
|
||||
|
|
@ -1834,7 +1859,7 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
Result := BuildJoinResult(Self, other, leftIdx, rightIdx, leftKey, rightKey);
|
||||
Result := BuildJoinResult(Self, other, leftIdx, rightIdx, leftKey, rightKey);}
|
||||
end;
|
||||
|
||||
function DataFrame.JoinInnerSingleKeyStr(other: DataFrame; leftKey, rightKey: integer): DataFrame;
|
||||
|
|
@ -2057,7 +2082,7 @@ begin
|
|||
|
||||
Result := Self.Join(tmp, leftKeys, kind);
|
||||
|
||||
AssertSchemaConsistent;
|
||||
Result.AssertSchemaConsistent;
|
||||
end;
|
||||
|
||||
procedure DataFrame.RebuildSchema;
|
||||
|
|
@ -2536,7 +2561,9 @@ begin
|
|||
acc += d * d;
|
||||
end;
|
||||
|
||||
var variance := acc / cnt;
|
||||
var variance :=
|
||||
if cnt > 1 then acc / (cnt - 1)
|
||||
else 0.0;
|
||||
|
||||
Result := (mean, variance);
|
||||
end;
|
||||
|
|
@ -2601,7 +2628,12 @@ begin
|
|||
|
||||
Result.Count := cnt;
|
||||
Result.Mean := mean;
|
||||
Result.Std := Sqrt(acc / cnt);
|
||||
|
||||
if cnt > 1 then
|
||||
Result.Std := Sqrt(acc / (cnt - 1))
|
||||
else
|
||||
Result.Std := 0.0;
|
||||
|
||||
Result.Min := mn;
|
||||
Result.Max := mx;
|
||||
end;
|
||||
|
|
@ -2778,7 +2810,7 @@ begin
|
|||
|
||||
var cmp: integer;
|
||||
case columns[colIndices[i]].Info.ColType of
|
||||
ctInt: cmp := a.IntVals[i] - b.IntVals[i];
|
||||
ctInt: cmp := a.IntVals[i].CompareTo(b.IntVals[i]);
|
||||
ctFloat: cmp := a.FloatVals[i].CompareTo(b.FloatVals[i]);
|
||||
ctStr: cmp := a.StrVals[i].CompareTo(b.StrVals[i]);
|
||||
ctBool: cmp := a.BoolVals[i].CompareTo(b.BoolVals[i]);
|
||||
|
|
@ -2982,7 +3014,7 @@ begin
|
|||
));
|
||||
|
||||
Result := res;
|
||||
AssertSchemaConsistent;
|
||||
Result.AssertSchemaConsistent;
|
||||
end;
|
||||
|
||||
function DataFrame.Select(colIndices: array of integer): DataFrame;
|
||||
|
|
@ -3090,7 +3122,7 @@ begin
|
|||
|
||||
Result := res;
|
||||
|
||||
AssertSchemaConsistent;
|
||||
Result.AssertSchemaConsistent;
|
||||
end;
|
||||
|
||||
function DataFrame.Drop(colIndices: array of integer): DataFrame;
|
||||
|
|
@ -3267,7 +3299,7 @@ begin
|
|||
res.SetSchema(ExtendSchema(name, ctInt, false));
|
||||
|
||||
Result := res;
|
||||
AssertSchemaConsistent;
|
||||
Result.AssertSchemaConsistent;
|
||||
end;
|
||||
|
||||
function DataFrame.WithColumnFloat(name: string; f: DataFrameCursor -> real): DataFrame;
|
||||
|
|
@ -3308,7 +3340,7 @@ begin
|
|||
res.SetSchema(ExtendSchema(name, ctFloat, false));
|
||||
|
||||
Result := res;
|
||||
AssertSchemaConsistent;
|
||||
Result.AssertSchemaConsistent;
|
||||
end;
|
||||
|
||||
function DataFrame.WithColumnStr(name: string; f: DataFrameCursor -> string): DataFrame;
|
||||
|
|
@ -3349,7 +3381,7 @@ begin
|
|||
res.SetSchema(ExtendSchema(name, ctStr, false));
|
||||
|
||||
Result := res;
|
||||
AssertSchemaConsistent;
|
||||
Result.AssertSchemaConsistent;
|
||||
end;
|
||||
|
||||
function DataFrame.WithColumnBool(name: string; f: DataFrameCursor -> boolean): DataFrame;
|
||||
|
|
@ -3390,7 +3422,7 @@ begin
|
|||
res.SetSchema(ExtendSchema(name, ctBool, false));
|
||||
|
||||
Result := res;
|
||||
AssertSchemaConsistent;
|
||||
Result.AssertSchemaConsistent;
|
||||
end;
|
||||
|
||||
function DataFrame.ExtendSchema(
|
||||
|
|
@ -3482,10 +3514,22 @@ begin
|
|||
|
||||
for var i := 0 to columns.Count - 1 do
|
||||
if i <> colIndex then
|
||||
res.AddColumnView(columns[i])
|
||||
res.AddColumnAlias(columns[i])
|
||||
else
|
||||
res.AddFloatColumn(colName, data, valid);
|
||||
|
||||
// --- ИСПРАВЛЕНИЕ СХЕМЫ ---
|
||||
var newTypes := Copy(fSchema.Types);
|
||||
newTypes[colIndex] := ColumnType.ctFloat;
|
||||
|
||||
var newSchema := new DataFrameSchema(
|
||||
fSchema.ColumnNames,
|
||||
newTypes,
|
||||
fSchema.CategoricalFlags
|
||||
);
|
||||
|
||||
res.SetSchema(newSchema);
|
||||
|
||||
Result := res;
|
||||
Result.AssertSchemaConsistent;
|
||||
end;
|
||||
|
|
@ -3523,10 +3567,22 @@ begin
|
|||
var res := new DataFrame;
|
||||
for var i := 0 to columns.Count - 1 do
|
||||
if i <> colIndex then
|
||||
res.AddColumnView(columns[i])
|
||||
res.AddColumnAlias(columns[i])
|
||||
else
|
||||
res.AddIntColumn(colName, data, valid);
|
||||
|
||||
// --- ИСПРАВЛЕНИЕ СХЕМЫ ---
|
||||
var newTypes := Copy(fSchema.Types);
|
||||
newTypes[colIndex] := ColumnType.ctInt;
|
||||
|
||||
var newSchema := new DataFrameSchema(
|
||||
fSchema.ColumnNames,
|
||||
newTypes,
|
||||
fSchema.CategoricalFlags
|
||||
);
|
||||
|
||||
res.SetSchema(newSchema);
|
||||
|
||||
Result := res;
|
||||
Result.AssertSchemaConsistent;
|
||||
end;
|
||||
|
|
@ -3567,7 +3623,7 @@ begin
|
|||
|
||||
var res := new DataFrame;
|
||||
for var i := 0 to columns.Count - 1 do
|
||||
res.AddColumnView(columns[i]);
|
||||
res.AddColumnAlias(columns[i]);
|
||||
|
||||
res.AddIntColumn(name, data, valid);
|
||||
|
||||
|
|
@ -3622,12 +3678,9 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
// КЛЮЧЕВОЕ: schema НЕ меняется
|
||||
res.SetSchema(fSchema);
|
||||
|
||||
Result := res;
|
||||
|
||||
AssertSchemaConsistent;
|
||||
Result.AssertSchemaConsistent;
|
||||
end;
|
||||
|
||||
function DataFrame.TransformFloatColumn(name: string; f: real -> real): DataFrame;
|
||||
|
|
@ -3677,12 +3730,9 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
// schema просто копируется
|
||||
res.SetSchema(fSchema);
|
||||
|
||||
Result := res;
|
||||
|
||||
AssertSchemaConsistent;
|
||||
Result.AssertSchemaConsistent;
|
||||
end;
|
||||
|
||||
function DataFrame.TransformStrColumn(name: string; f: string -> string): DataFrame;
|
||||
|
|
@ -3732,12 +3782,9 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
// schema НЕ меняется
|
||||
res.SetSchema(fSchema);
|
||||
|
||||
Result := res;
|
||||
|
||||
AssertSchemaConsistent;
|
||||
Result.AssertSchemaConsistent;
|
||||
end;
|
||||
|
||||
function DataFrame.TransformBoolColumn(name: string; f: boolean -> boolean): DataFrame;
|
||||
|
|
@ -3787,12 +3834,9 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
// schema просто копируется
|
||||
res.SetSchema(fSchema);
|
||||
|
||||
Result := res;
|
||||
|
||||
AssertSchemaConsistent;
|
||||
Result.AssertSchemaConsistent;
|
||||
end;
|
||||
|
||||
function DataFrame.TakeRows(indices: array of integer): DataFrame;
|
||||
|
|
@ -4256,7 +4300,7 @@ end;
|
|||
|
||||
/// Добавляет в DataFrame столбец-представление (view),
|
||||
/// использующий те же данные, что и исходный столбец
|
||||
procedure DataFrame.AddColumnView(src: Column);
|
||||
procedure DataFrame.AddColumnAlias(src: Column);
|
||||
begin
|
||||
case src.Info.ColType of
|
||||
ctInt:
|
||||
|
|
@ -4309,7 +4353,7 @@ begin
|
|||
res.AddIntColumn(name, data, fc.IsValid);
|
||||
end
|
||||
else
|
||||
res.AddColumnView(col);
|
||||
res.AddColumnAlias(col);
|
||||
end;
|
||||
|
||||
// пересобираем schema (меняются ТИПЫ)
|
||||
|
|
@ -4423,6 +4467,31 @@ begin
|
|||
Result := h;
|
||||
end;
|
||||
|
||||
function GroupKey.CompareTo(other: GroupKey): integer;
|
||||
begin
|
||||
for var i := 0 to Values.Length - 1 do
|
||||
begin
|
||||
var a := Values[i];
|
||||
var b := other.Values[i];
|
||||
|
||||
var cmp := 0;
|
||||
|
||||
if a is integer then
|
||||
cmp := integer(a).CompareTo(integer(b))
|
||||
else if a is string then
|
||||
cmp := string(a).CompareTo(string(b))
|
||||
else if a is boolean then
|
||||
cmp := boolean(a).CompareTo(boolean(b))
|
||||
else
|
||||
raise new Exception('Unsupported GroupKey type');
|
||||
|
||||
if cmp <> 0 then
|
||||
exit(cmp);
|
||||
end;
|
||||
|
||||
Result := 0;
|
||||
end;
|
||||
|
||||
//-----------------------------
|
||||
// GroupView
|
||||
//-----------------------------
|
||||
|
|
@ -4621,6 +4690,7 @@ begin
|
|||
case df.columns[keyColumn].Info.ColType of
|
||||
ctInt: key := cursor.Int(keyColumn);
|
||||
ctStr: key := cursor.Str(keyColumn);
|
||||
ctBool: key := cursor.Bool(keyColumn);
|
||||
else Error(ER_GROUPBY_UNSUPPORTED_KEY_TYPE, df.columns[keyColumn].Info.ColType);
|
||||
end;
|
||||
|
||||
|
|
@ -4658,6 +4728,7 @@ begin
|
|||
case df.columns[c].Info.ColType of
|
||||
ctInt: values[i] := cursor.Int(c);
|
||||
ctStr: values[i] := cursor.Str(c);
|
||||
ctBool: values[i] := cursor.Bool(c);
|
||||
else Error(ER_GROUPBY_UNSUPPORTED_KEY_TYPE, df.columns[c].Info.ColType);
|
||||
end;
|
||||
end;
|
||||
|
|
@ -4689,26 +4760,48 @@ begin
|
|||
|
||||
if singleKey then
|
||||
begin
|
||||
var keys := groups1.Select(kvp -> kvp.Key).ToArray;
|
||||
var col := source.columns[keyColumn];
|
||||
var keyName := col.Info.Name;
|
||||
|
||||
var keys: array of object;
|
||||
|
||||
case col.Info.ColType of
|
||||
ctInt:
|
||||
keys := groups1.Keys.OrderBy(k -> integer(k)).Select(k -> object(k)).ToArray;
|
||||
ctStr:
|
||||
keys := groups1.Keys.OrderBy(k -> string(k)).Select(k -> object(k)).ToArray;
|
||||
ctBool:
|
||||
keys := groups1.Keys.OrderBy(k -> boolean(k)).Select(k -> object(k)).ToArray;
|
||||
else
|
||||
Error(ER_GROUPBY_UNSUPPORTED_KEY_TYPE);
|
||||
end;
|
||||
|
||||
var counts := new integer[keys.Length];
|
||||
|
||||
for var i := 0 to keys.Length - 1 do
|
||||
counts[i] := groups1[keys[i]].Count;
|
||||
|
||||
var col := source.columns[keyColumn];
|
||||
var keyName := col.Info.Name;
|
||||
|
||||
if col.Info.ColType = ctInt then
|
||||
// добавление ключевого столбца
|
||||
case col.Info.ColType of
|
||||
ctInt:
|
||||
begin
|
||||
res.AddIntColumn(keyName, keys.Select(k -> integer(k)).ToArray, nil);
|
||||
types.Add(ctInt);
|
||||
end
|
||||
else
|
||||
end;
|
||||
|
||||
ctStr:
|
||||
begin
|
||||
res.AddStrColumn(keyName, keys.Select(k -> string(k)).ToArray, nil);
|
||||
types.Add(ctStr);
|
||||
end;
|
||||
|
||||
ctBool:
|
||||
begin
|
||||
res.AddBoolColumn(keyName, keys.Select(k -> boolean(k)).ToArray, nil);
|
||||
types.Add(ctBool);
|
||||
end;
|
||||
end;
|
||||
|
||||
names.Add(keyName);
|
||||
cats.Add(true); // ключ — categorical
|
||||
|
||||
|
|
@ -4719,7 +4812,7 @@ begin
|
|||
end
|
||||
else
|
||||
begin
|
||||
var keys := groupsN.Select(kvp -> kvp.Key).ToArray;
|
||||
var keys := groupsN.Keys.OrderBy(k -> k).ToArray;
|
||||
var counts := new integer[keys.Length];
|
||||
|
||||
for var i := 0 to keys.Length - 1 do
|
||||
|
|
@ -4731,17 +4824,29 @@ begin
|
|||
var col := source.columns[ci];
|
||||
var colName := col.Info.Name;
|
||||
|
||||
if col.Info.ColType = ctInt then
|
||||
case col.Info.ColType of
|
||||
ctInt:
|
||||
begin
|
||||
res.AddIntColumn(colName, keys.Select(key -> integer(key.Values[k])).ToArray, nil);
|
||||
types.Add(ctInt);
|
||||
end
|
||||
else
|
||||
end;
|
||||
|
||||
ctStr:
|
||||
begin
|
||||
res.AddStrColumn(colName, keys.Select(key -> string(key.Values[k])).ToArray, nil);
|
||||
types.Add(ctStr);
|
||||
end;
|
||||
|
||||
ctBool:
|
||||
begin
|
||||
res.AddBoolColumn(colName, keys.Select(key -> boolean(key.Values[k])).ToArray, nil);
|
||||
types.Add(ctBool);
|
||||
end;
|
||||
|
||||
else
|
||||
Error(ER_GROUPBY_UNSUPPORTED_KEY_TYPE);
|
||||
end;
|
||||
|
||||
names.Add(colName);
|
||||
cats.Add(true); // ключи — categorical
|
||||
end;
|
||||
|
|
@ -4821,7 +4926,15 @@ begin
|
|||
|
||||
if singleKey then
|
||||
begin
|
||||
var keys := groups1.Select(kvp -> kvp.Key).ToArray;
|
||||
var col := source.columns[keyColumn];
|
||||
var keys: array of object;
|
||||
|
||||
case col.Info.ColType of
|
||||
ctInt: keys := groups1.Keys.OrderBy(k -> integer(k)).Select(k -> object(k)).ToArray;
|
||||
ctStr: keys := groups1.Keys.OrderBy(k -> string(k)).Select(k -> object(k)).ToArray;
|
||||
ctBool: keys := groups1.Keys.OrderBy(k -> boolean(k)).Select(k -> object(k)).ToArray;
|
||||
else Error(ER_GROUPBY_UNSUPPORTED_KEY_TYPE);
|
||||
end;
|
||||
|
||||
for var i := 0 to keys.Length - 1 do
|
||||
begin
|
||||
|
|
@ -4834,7 +4947,7 @@ begin
|
|||
end
|
||||
else
|
||||
begin
|
||||
var keys := groupsN.Select(kvp -> kvp.Key).ToArray;
|
||||
var keys := groupsN.Keys.OrderBy(k -> k).ToArray;
|
||||
|
||||
for var i := 0 to keys.Length - 1 do
|
||||
begin
|
||||
|
|
@ -4961,9 +5074,34 @@ begin
|
|||
var keysN: array of GroupKey := nil;
|
||||
|
||||
if singleKey then
|
||||
keys1 := groups1.Keys.ToArray
|
||||
begin
|
||||
var col := source.columns[keyColumn];
|
||||
|
||||
case col.Info.ColType of
|
||||
ctInt:
|
||||
keys1 := groups1.Keys
|
||||
.OrderBy(k -> integer(k))
|
||||
.Select(k -> object(k))
|
||||
.ToArray;
|
||||
|
||||
ctStr:
|
||||
keys1 := groups1.Keys
|
||||
.OrderBy(k -> string(k))
|
||||
.Select(k -> object(k))
|
||||
.ToArray;
|
||||
|
||||
ctBool:
|
||||
keys1 := groups1.Keys
|
||||
.OrderBy(k -> boolean(k))
|
||||
.Select(k -> object(k))
|
||||
.ToArray;
|
||||
|
||||
else
|
||||
keysN := groupsN.Keys.ToArray;
|
||||
Error(ER_GROUPBY_UNSUPPORTED_KEY_TYPE);
|
||||
end;
|
||||
end
|
||||
else
|
||||
keysN := groupsN.Keys.OrderBy(k -> k).ToArray;
|
||||
|
||||
// ----------------------------
|
||||
// 4. Аллокации только под нужные агрегаты
|
||||
|
|
@ -5200,6 +5338,7 @@ begin
|
|||
cats.ToArray
|
||||
));
|
||||
|
||||
res.AssertSchemaConsistent;
|
||||
Result := res;
|
||||
end;
|
||||
|
||||
|
|
@ -5261,6 +5400,7 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
res.AssertSchemaConsistent;
|
||||
Result := res;
|
||||
end;
|
||||
|
||||
|
|
@ -5273,29 +5413,47 @@ begin
|
|||
var ix := df.ColumnIndex(colX);
|
||||
var iy := df.ColumnIndex(colY);
|
||||
|
||||
var mx := df.Mean(ix);
|
||||
var my := df.Mean(iy);
|
||||
var sx := df.Std(ix);
|
||||
var sy := df.Std(iy);
|
||||
|
||||
if (sx = 0) or (sy = 0) then
|
||||
Error(ER_ZERO_VARIANCE);
|
||||
|
||||
var cur := df.GetCursor;
|
||||
var sum := 0.0;
|
||||
var sumX := 0.0;
|
||||
var sumY := 0.0;
|
||||
var cnt := 0;
|
||||
|
||||
// pass 1: mean по пересечению
|
||||
while cur.MoveNext do
|
||||
if cur.IsValid(ix) and cur.IsValid(iy) then
|
||||
begin
|
||||
sum += (cur.Float(ix) - mx) * (cur.Float(iy) - my);
|
||||
sumX += cur.Float(ix);
|
||||
sumY += cur.Float(iy);
|
||||
cnt += 1;
|
||||
end;
|
||||
|
||||
if cnt = 0 then
|
||||
Error(ER_NO_VALID_PAIRS);
|
||||
|
||||
Result := sum / (cnt * sx * sy);
|
||||
var mx := sumX / cnt;
|
||||
var my := sumY / cnt;
|
||||
|
||||
// pass 2: covariance и variance
|
||||
cur := df.GetCursor;
|
||||
var acc := 0.0;
|
||||
var accX := 0.0;
|
||||
var accY := 0.0;
|
||||
|
||||
while cur.MoveNext do
|
||||
if cur.IsValid(ix) and cur.IsValid(iy) then
|
||||
begin
|
||||
var dx := cur.Float(ix) - mx;
|
||||
var dy := cur.Float(iy) - my;
|
||||
|
||||
acc += dx * dy;
|
||||
accX += dx * dx;
|
||||
accY += dy * dy;
|
||||
end;
|
||||
|
||||
if (accX = 0) or (accY = 0) then
|
||||
Error(ER_ZERO_VARIANCE);
|
||||
|
||||
Result := acc / Sqrt(accX * accY);
|
||||
end;
|
||||
|
||||
static function Statistics.CorrelationMatrix(df: DataFrame): DataFrame;
|
||||
|
|
@ -5333,7 +5491,12 @@ begin
|
|||
if i = j then
|
||||
data[i] := 1.0
|
||||
else
|
||||
try
|
||||
data[i] := Correlation(df, names[i], names[j]);
|
||||
except
|
||||
on e: Exception do
|
||||
data[i] := real.NaN;
|
||||
end;
|
||||
|
||||
res.AddFloatColumn(names[j], data, nil);
|
||||
|
||||
|
|
@ -5355,8 +5518,9 @@ end;
|
|||
static function Statistics.Standardize(df: DataFrame; colName: string): DataFrame;
|
||||
begin
|
||||
var idx := df.ColumnIndex(colName);
|
||||
var mean := df.Mean(idx);
|
||||
var std := df.Std(idx);
|
||||
|
||||
var (mean, variance) := df.MeanVariance(idx);
|
||||
var std := Sqrt(variance);
|
||||
|
||||
if std = 0 then
|
||||
Error(ER_ZERO_STD_STANDARDIZE);
|
||||
|
|
@ -5369,6 +5533,8 @@ end;
|
|||
static function Statistics.StandardizeAll(df: DataFrame): DataFrame;
|
||||
begin
|
||||
var res := new DataFrame;
|
||||
var oldSchema := df.Schema;
|
||||
|
||||
var cur := df.GetCursor;
|
||||
|
||||
// 1. заранее считаем mean/std для всех числовых столбцов
|
||||
|
|
@ -5381,8 +5547,9 @@ begin
|
|||
var t := df.GetColumnType(i);
|
||||
if t in [ColumnType.ctInt, ColumnType.ctFloat] then
|
||||
begin
|
||||
means[i] := df.Mean(i);
|
||||
stds[i] := df.Std(i);
|
||||
var (mean, variance) := df.MeanVariance(i);
|
||||
means[i] := mean;
|
||||
stds[i] := Sqrt(variance);
|
||||
if stds[i] = 0 then
|
||||
Error(ER_ZERO_STD_COLUMN, df.fSchema.ColumnNames[i]);
|
||||
isNumeric[i] := true;
|
||||
|
|
@ -5395,7 +5562,7 @@ begin
|
|||
if isNumeric[i] then
|
||||
res.AddFloatColumn(df.fSchema.ColumnNames[i], new real[df.RowCount], nil)
|
||||
else
|
||||
res.AddColumnView(df.columns[i]); // private helper
|
||||
res.AddColumnAlias(df.GetColumn(i));
|
||||
end;
|
||||
|
||||
// 3. заполняем данные
|
||||
|
|
@ -5409,26 +5576,23 @@ begin
|
|||
var col := FloatColumn(res.columns[i]);
|
||||
|
||||
if cur.IsValid(i) then
|
||||
begin
|
||||
col.Data[row] := (cur.Float(i) - means[i]) / stds[i];
|
||||
if col.IsValid <> nil then
|
||||
col.IsValid[row] := true;
|
||||
end
|
||||
col.Data[row] := (cur.Float(i) - means[i]) / stds[i]
|
||||
else
|
||||
begin
|
||||
// первый NA → создаём IsValid
|
||||
if col.IsValid = nil then
|
||||
begin
|
||||
col.IsValid := new boolean[df.RowCount];
|
||||
for var r := 0 to row - 1 do
|
||||
col.IsValid[r] := true;
|
||||
end;
|
||||
col.IsValid[row] := false;
|
||||
end;
|
||||
|
||||
end;
|
||||
row += 1;
|
||||
end;
|
||||
|
||||
var names := oldSchema.ColumnNames;
|
||||
var cats := oldSchema.CategoricalFlags;
|
||||
|
||||
var types := new ColumnType[names.Length];
|
||||
for var i := 0 to names.Length - 1 do
|
||||
types[i] := ctFloat;
|
||||
|
||||
res.SetSchema(new DataFrameSchema(names, types, cats));
|
||||
|
||||
Result := res;
|
||||
end;
|
||||
|
||||
|
|
@ -5443,7 +5607,8 @@ begin
|
|||
Result := df.ReplaceColumnFloat(colName, cur ->
|
||||
begin
|
||||
if not cur.IsValid(idx) then
|
||||
Error(ER_INVALID_VALUE_IN_COLUMN, colName);
|
||||
Result := real.NaN
|
||||
else
|
||||
Result := (cur.Float(idx) - mn) / (mx - mn);
|
||||
end);
|
||||
end;
|
||||
|
|
@ -5451,6 +5616,8 @@ end;
|
|||
static function Statistics.NormalizeAll(df: DataFrame): DataFrame;
|
||||
begin
|
||||
var res := new DataFrame;
|
||||
var oldSchema := df.Schema;
|
||||
|
||||
var cur := df.GetCursor;
|
||||
|
||||
// 1. заранее считаем min/max для всех числовых столбцов
|
||||
|
|
@ -5478,7 +5645,7 @@ begin
|
|||
if isNumeric[i] then
|
||||
res.AddFloatColumn(df.fSchema.ColumnNames[i], new real[df.RowCount], nil)
|
||||
else
|
||||
res.AddColumnView(df.columns[i]); // private helper
|
||||
res.AddColumnAlias(df.GetColumn(i));
|
||||
end;
|
||||
|
||||
// 3. заполняем данные
|
||||
|
|
@ -5512,6 +5679,15 @@ begin
|
|||
row += 1;
|
||||
end;
|
||||
|
||||
var names := oldSchema.ColumnNames;
|
||||
var cats := oldSchema.CategoricalFlags;
|
||||
|
||||
var types := new ColumnType[names.Length];
|
||||
for var i := 0 to names.Length - 1 do
|
||||
types[i] := ctFloat;
|
||||
|
||||
res.SetSchema(new DataFrameSchema(names, types, cats));
|
||||
|
||||
Result := res;
|
||||
end;
|
||||
|
||||
|
|
@ -5940,13 +6116,49 @@ begin
|
|||
var autoCat: array of boolean := nil;
|
||||
|
||||
var first := true;
|
||||
|
||||
// временные буферы для определения числа колонок
|
||||
var tmpStarts := new integer[64];
|
||||
var tmpLens := new integer[64];
|
||||
var actualCount: integer;
|
||||
var unclosedQuote: boolean;
|
||||
|
||||
ScanFieldsQuoted(linesArray[0], delimiter, tmpStarts, tmpLens, actualCount, unclosedQuote);
|
||||
|
||||
if unclosedQuote then
|
||||
if strict then
|
||||
Error(ER_CSV_UNCLOSED_QUOTE);
|
||||
|
||||
// число колонок
|
||||
var maxColumns := 256;
|
||||
|
||||
var starts := new integer[maxColumns];
|
||||
var lens := new integer[maxColumns];
|
||||
|
||||
foreach var line in linesArray index inferRead do
|
||||
begin
|
||||
if inferRead >= inferLimit then break;
|
||||
|
||||
if first then
|
||||
begin
|
||||
var parts := line.Split(delimiter);
|
||||
ScanFieldsQuoted(line, delimiter, starts, lens, actualCount, unclosedQuote);
|
||||
|
||||
if unclosedQuote then
|
||||
if strict then
|
||||
Error(ER_CSV_UNCLOSED_QUOTE);
|
||||
|
||||
var parts := new string[actualCount];
|
||||
|
||||
for var j := 0 to actualCount - 1 do
|
||||
begin
|
||||
var s := line.Substring(starts[j] - 1, lens[j]);
|
||||
|
||||
// снять кавычки, если есть
|
||||
if (s.Length >= 2) and (s[1] = '"') and (s[s.Length] = '"') then
|
||||
s := s.Substring(1, s.Length - 2);
|
||||
|
||||
parts[j] := s;
|
||||
end;
|
||||
|
||||
if hasHeader then
|
||||
begin
|
||||
|
|
@ -6046,7 +6258,24 @@ begin
|
|||
if hasHeader then continue;
|
||||
end;
|
||||
|
||||
var parts := line.Split(delimiter);
|
||||
ScanFieldsQuoted(line, delimiter, starts, lens, actualCount, unclosedQuote);
|
||||
|
||||
if unclosedQuote then
|
||||
if strict then
|
||||
Error(ER_CSV_UNCLOSED_QUOTE);
|
||||
|
||||
var parts := new string[actualCount];
|
||||
|
||||
for var j := 0 to actualCount - 1 do
|
||||
begin
|
||||
var s := line.Substring(starts[j]-1, lens[j]);
|
||||
|
||||
// снять кавычки, если есть
|
||||
if (s.Length >= 2) and (s[1] = '"') and (s[s.Length] = '"') then
|
||||
s := s.Substring(1, s.Length - 2);
|
||||
|
||||
parts[j] := s;
|
||||
end;
|
||||
|
||||
if parts.Length <> originalColCount then
|
||||
if strict then
|
||||
|
|
@ -6164,9 +6393,6 @@ begin
|
|||
strData[j] := new string[rowCount];
|
||||
end;
|
||||
|
||||
var starts := new integer[originalColCount];
|
||||
var lens := new integer[originalColCount];
|
||||
|
||||
var row := 0;
|
||||
first := true;
|
||||
foreach var line in linesArray do
|
||||
|
|
@ -6177,9 +6403,6 @@ begin
|
|||
if hasHeader then continue;
|
||||
end;
|
||||
|
||||
var actualCount: integer;
|
||||
var unclosedQuote: boolean;
|
||||
|
||||
ScanFieldsQuoted(line, delimiter, starts, lens, actualCount, unclosedQuote);
|
||||
|
||||
if unclosedQuote then
|
||||
|
|
@ -6423,6 +6646,20 @@ begin
|
|||
);
|
||||
end;
|
||||
|
||||
function EscapeCsv(s: string; delimiter: char): string;
|
||||
begin
|
||||
if (s.Contains(delimiter)) or
|
||||
(s.Contains('"')) or
|
||||
(s.Contains(#10)) or
|
||||
(s.Contains(#13)) then
|
||||
begin
|
||||
s := s.Replace('"', '""');
|
||||
Result := '"' + s + '"';
|
||||
end
|
||||
else
|
||||
Result := s;
|
||||
end;
|
||||
|
||||
static procedure CsvSaver.Save(df: DataFrame; filename: string;
|
||||
delimiter: char; header: boolean);
|
||||
begin
|
||||
|
|
@ -6441,7 +6678,7 @@ begin
|
|||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
if i > 0 then w.Write(delimiter);
|
||||
w.Write(schema.NameAt(i));
|
||||
w.Write(EscapeCsv(schema.NameAt(i), delimiter));
|
||||
end;
|
||||
w.WriteLine;
|
||||
end;
|
||||
|
|
@ -6460,7 +6697,7 @@ begin
|
|||
case schema.ColumnTypeAt(i) of
|
||||
ctInt: w.Write(cur.Int(i));
|
||||
ctFloat: w.Write(cur.Float(i));
|
||||
ctStr: w.Write(cur.Str(i));
|
||||
ctStr: w.Write(EscapeCsv(cur.Str(i), delimiter));
|
||||
ctBool: w.Write(cur.Bool(i));
|
||||
end;
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -557,7 +557,6 @@ begin
|
|||
IsValid := valid;
|
||||
end;
|
||||
|
||||
|
||||
constructor IntColumn.Create(name: string);
|
||||
begin
|
||||
inherited Create;
|
||||
|
|
@ -567,34 +566,9 @@ begin
|
|||
IsValid := nil;
|
||||
end;
|
||||
|
||||
{procedure IntColumn.AppendInvalid;
|
||||
begin
|
||||
Data := Data + [0];
|
||||
|
||||
if IsValid = nil then
|
||||
begin
|
||||
IsValid := new boolean[Length(Data) - 1];
|
||||
for var i := 0 to IsValid.Length - 1 do
|
||||
IsValid[i] := true;
|
||||
end;
|
||||
|
||||
IsValid := IsValid + [false];
|
||||
end;}
|
||||
|
||||
{procedure IntColumn.AppendFromCursor(cur: DataFrameCursor; colIndex: integer);
|
||||
begin
|
||||
if cur.IsValid(colIndex) then
|
||||
begin
|
||||
Data := Data + [cur.Int(colIndex)];
|
||||
if IsValid <> nil then
|
||||
IsValid := IsValid + [true];
|
||||
end
|
||||
else AppendInvalid;
|
||||
end;}
|
||||
|
||||
function IntColumn.TryGetNumericValue(i: integer; var value: real): boolean;
|
||||
begin
|
||||
if not IsValid[i] then
|
||||
if (IsValid <> nil) and not IsValid[i] then
|
||||
exit(false);
|
||||
|
||||
value := Data[i];
|
||||
|
|
@ -620,34 +594,9 @@ begin
|
|||
end;
|
||||
|
||||
|
||||
{procedure FloatColumn.AppendFromCursor(cur: DataFrameCursor; colIndex: integer);
|
||||
begin
|
||||
if cur.IsValid(colIndex) then
|
||||
begin
|
||||
Data := Data + [cur.Float(colIndex)];
|
||||
if IsValid <> nil then
|
||||
IsValid := IsValid + [true];
|
||||
end
|
||||
else AppendInvalid;
|
||||
end;}
|
||||
|
||||
{procedure FloatColumn.AppendInvalid;
|
||||
begin
|
||||
Data := Data + [0.0];
|
||||
|
||||
if IsValid = nil then
|
||||
begin
|
||||
IsValid := new boolean[Length(Data) - 1];
|
||||
for var i := 0 to IsValid.Length - 1 do
|
||||
IsValid[i] := true;
|
||||
end;
|
||||
|
||||
IsValid := IsValid + [false];
|
||||
end;}
|
||||
|
||||
function FloatColumn.TryGetNumericValue(i: integer; var value: real): boolean;
|
||||
begin
|
||||
if not IsValid[i] then
|
||||
if (IsValid <> nil) and not IsValid[i] then
|
||||
exit(false);
|
||||
|
||||
value := Data[i];
|
||||
|
|
@ -673,31 +622,6 @@ begin
|
|||
IsValid := nil;
|
||||
end;
|
||||
|
||||
{procedure StrColumn.AppendFromCursor(cur: DataFrameCursor; colIndex: integer);
|
||||
begin
|
||||
if cur.IsValid(colIndex) then
|
||||
begin
|
||||
Data := Data + [cur.Str(colIndex)];
|
||||
if IsValid <> nil then
|
||||
IsValid := IsValid + [true];
|
||||
end
|
||||
else AppendInvalid;
|
||||
end;}
|
||||
|
||||
{procedure StrColumn.AppendInvalid;
|
||||
begin
|
||||
Data := Data + [''];
|
||||
|
||||
if IsValid = nil then
|
||||
begin
|
||||
IsValid := new boolean[Length(Data) - 1];
|
||||
for var i := 0 to IsValid.Length - 1 do
|
||||
IsValid[i] := true;
|
||||
end;
|
||||
|
||||
IsValid := IsValid + [false];
|
||||
end;}
|
||||
|
||||
function StrColumn.TryGetNumericValue(i: integer; var value: real): boolean;
|
||||
begin
|
||||
exit(false);
|
||||
|
|
@ -705,7 +629,7 @@ end;
|
|||
|
||||
function BoolColumn.TryGetNumericValue(i: integer; var value: real): boolean;
|
||||
begin
|
||||
if not IsValid[i] then
|
||||
if (IsValid <> nil) and not IsValid[i] then
|
||||
exit(false);
|
||||
|
||||
if Data[i] then
|
||||
|
|
|
|||
|
|
@ -325,6 +325,9 @@ const
|
|||
'Для QR-разложения требуется m >= n!!QR decomposition requires m >= n';
|
||||
ER_SINGULAR_MATRIX =
|
||||
'Матрица вырождена или плохо обусловлена!!Matrix is singular or ill-conditioned';
|
||||
ER_EMPTY_MATRIX =
|
||||
'Матрица пуста!!Matrix is empty';
|
||||
|
||||
|
||||
type
|
||||
MLNotSPDException = class(MLException);
|
||||
|
|
@ -470,7 +473,7 @@ end;
|
|||
function Vector.Average: real;
|
||||
begin
|
||||
if Length = 0 then
|
||||
ArgumentError('Vector is empty');
|
||||
ArgumentError(ER_VECTOR_EMPTY);
|
||||
|
||||
Result := Sum / Length;
|
||||
end;
|
||||
|
|
@ -646,6 +649,10 @@ function Matrix.ColumnMins: Vector;
|
|||
begin
|
||||
var n := RowCount;
|
||||
var p := ColCount;
|
||||
|
||||
if n = 0 then
|
||||
exit(new Vector(p));
|
||||
|
||||
Result := new Vector(p);
|
||||
|
||||
for var j := 0 to p - 1 do
|
||||
|
|
@ -662,6 +669,10 @@ function Matrix.ColumnMaxs: Vector;
|
|||
begin
|
||||
var n := RowCount;
|
||||
var p := ColCount;
|
||||
|
||||
if n = 0 then
|
||||
exit(new Vector(p));
|
||||
|
||||
Result := new Vector(p);
|
||||
|
||||
for var j := 0 to p - 1 do
|
||||
|
|
@ -678,6 +689,10 @@ function Matrix.RowMins: Vector;
|
|||
begin
|
||||
var n := RowCount;
|
||||
var p := ColCount;
|
||||
|
||||
if n = 0 then
|
||||
exit(new Vector(p));
|
||||
|
||||
Result := new Vector(n);
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
|
|
@ -694,6 +709,10 @@ function Matrix.RowMaxs: Vector;
|
|||
begin
|
||||
var n := RowCount;
|
||||
var p := ColCount;
|
||||
|
||||
if n = 0 then
|
||||
exit(new Vector(p));
|
||||
|
||||
Result := new Vector(n);
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
|
|
@ -782,10 +801,15 @@ end;
|
|||
|
||||
function Matrix.ColumnArgMin(j: integer): integer;
|
||||
begin
|
||||
var n := RowCount;
|
||||
|
||||
if n = 0 then
|
||||
Error(ER_EMPTY_MATRIX);
|
||||
|
||||
var minVal := fdata[0,j];
|
||||
var arg := 0;
|
||||
|
||||
for var i := 1 to RowCount - 1 do
|
||||
for var i := 1 to n - 1 do
|
||||
if fdata[i,j] < minVal then
|
||||
begin
|
||||
minVal := fdata[i,j];
|
||||
|
|
@ -802,10 +826,15 @@ end;
|
|||
|
||||
function Matrix.ColumnArgMax(j: integer): integer;
|
||||
begin
|
||||
var n := RowCount;
|
||||
|
||||
if n = 0 then
|
||||
Error(ER_EMPTY_MATRIX);
|
||||
|
||||
var maxVal := fdata[0,j];
|
||||
var arg := 0;
|
||||
|
||||
for var i := 1 to RowCount - 1 do
|
||||
for var i := 1 to n - 1 do
|
||||
if fdata[i,j] > maxVal then
|
||||
begin
|
||||
maxVal := fdata[i,j];
|
||||
|
|
@ -1388,7 +1417,7 @@ begin
|
|||
maxRow := i;
|
||||
end;
|
||||
|
||||
if maxVal = 0.0 then
|
||||
if maxVal < 1e-12 then
|
||||
Error(ER_MATRIX_SINGULAR);
|
||||
|
||||
if maxRow <> k then
|
||||
|
|
@ -1604,6 +1633,9 @@ begin
|
|||
for var i := k to m - 1 do
|
||||
vnorm2 += R[i,k] * R[i,k];
|
||||
|
||||
if vnorm2 < 1e-12 then
|
||||
continue;
|
||||
|
||||
var beta := 2.0 / vnorm2;
|
||||
|
||||
// ---- применить отражение к R
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@ type
|
|||
/// Датасет российских городов (задача кластеризации)
|
||||
static function RussianCities: Dataset;
|
||||
|
||||
/// Датасет результатов экзамена студентов (классификация)
|
||||
{/// Датасет результатов экзамена студентов (классификация)
|
||||
static function StudentExam: Dataset;
|
||||
|
||||
/// Датасет банковских клиентов (классификация одобрения кредита)
|
||||
|
|
@ -272,7 +272,7 @@ type
|
|||
static function MoscowTransport: Dataset;
|
||||
|
||||
/// Датасет интернет-покупок пользователей (классификация покупки)
|
||||
static function OnlineShopping: Dataset;
|
||||
static function OnlineShopping: Dataset;}
|
||||
|
||||
static function LoadMeta(path: string): Dictionary<string,string>;
|
||||
static function ParseFeatures(meta: Dictionary<string,string>): array of string;
|
||||
|
|
@ -330,6 +330,11 @@ const
|
|||
'Неподдерживаемый тип ключа для группировки!!Unsupported key type for grouping';
|
||||
ER_STRATIFIED_ONLY_FOR_CLASSIFICATION =
|
||||
'Стратифицированное разбиение доступно только для задач классификации!!Stratified split is only for classification tasks';
|
||||
ER_CLASS_BALANCE_TOO_SMALL =
|
||||
'Слишком малое значение classBalance: {0}. Минимально допустимое значение — 1e-3!!classBalance is too small: {0}. Minimum allowed value is 1e-3';
|
||||
ER_UNSUPPORTED_TARGET_TYPE =
|
||||
'Неподдерживаемый тип целевого столбца: {0}!!Unsupported target column type: {0}';
|
||||
|
||||
|
||||
C_DATASET = 'Датасет: {0}!!Dataset: {0}';
|
||||
C_DESCRIPTION = 'Описание:!!Description:';
|
||||
|
|
@ -593,7 +598,14 @@ begin
|
|||
if Task <> TaskType.Classification then
|
||||
ArgumentError(ER_CLASSES_ONLY_CLASSIFICATION);
|
||||
|
||||
Result := Data.GetStrColumn(Target).Distinct.ToArray;
|
||||
var idx := Data.ColumnIndex(Target);
|
||||
var t := Data.GetColumnType(idx);
|
||||
|
||||
case t of
|
||||
ctStr: Result := Data.GetStrColumn(Target).Distinct.ToArray;
|
||||
ctInt: Result := Data.GetIntColumn(Target).Distinct.Select(x -> x.ToString).ToArray;
|
||||
else Error(ER_UNSUPPORTED_TARGET_TYPE, t);
|
||||
end;
|
||||
end;
|
||||
|
||||
function Dataset.ClassCounts: Dictionary<string,integer>;
|
||||
|
|
@ -678,6 +690,9 @@ begin
|
|||
if (classBalance <= 0) or (classBalance > 1) then
|
||||
ArgumentOutOfRangeError(ER_PARAM_RANGE_01, 'classBalance');
|
||||
|
||||
if classBalance < 1e-3 then
|
||||
ArgumentOutOfRangeError(ER_CLASS_BALANCE_TOO_SMALL, classBalance);
|
||||
|
||||
if noisePoints < 0 then
|
||||
ArgumentOutOfRangeError(ER_PARAM_GE_ZERO, 'noisePoints');
|
||||
|
||||
|
|
@ -1301,7 +1316,7 @@ begin
|
|||
Result := ds;
|
||||
end;
|
||||
|
||||
static function Datasets.StudentExam: Dataset;
|
||||
{static function Datasets.StudentExam: Dataset;
|
||||
begin
|
||||
NotImplementedError(ER_NOT_IMPLEMENTED, 'Datasets.StudentExam');
|
||||
Result := nil;
|
||||
|
|
@ -1329,7 +1344,7 @@ static function Datasets.OnlineShopping: Dataset;
|
|||
begin
|
||||
NotImplementedError(ER_NOT_IMPLEMENTED, 'Datasets.OnlineShopping');
|
||||
Result := nil;
|
||||
end;
|
||||
end;}
|
||||
|
||||
static function Datasets.LoadMeta(path: string): Dictionary<string,string>;
|
||||
begin
|
||||
|
|
|
|||
|
|
@ -486,11 +486,13 @@ type
|
|||
// DecisionTreeBase
|
||||
//============================
|
||||
/// Базовый абстрактный класс дерева решений.
|
||||
/// Используется только DecisionTreeRegressor
|
||||
/// Classifier использует DecisionTreeCore
|
||||
/// Реализует общую логику построения структуры дерева:
|
||||
/// рекурсивное разбиение, контроль глубины,
|
||||
/// минимального числа объектов и расчет важности признаков.
|
||||
/// Конкретная логика вычисления значения листа и критерия разбиения задается в наследниках
|
||||
DecisionTreeBase = abstract class(ITreeModel)
|
||||
/// Конкретная логика вычисления значения листа и критерия разбиения задается в наследнике
|
||||
DecisionTreeRegressorBase = abstract class(ITreeModel)
|
||||
protected
|
||||
fRoot: DecisionTreeNode;
|
||||
fMaxDepth: integer;
|
||||
|
|
@ -516,7 +518,7 @@ type
|
|||
|
||||
function LeafValue(y: Vector; indices: array of integer): real; virtual; abstract;
|
||||
function LeafNode(value: real): DecisionTreeNode;
|
||||
procedure CopyBaseState(dest: DecisionTreeBase);
|
||||
procedure CopyBaseState(dest: DecisionTreeRegressorBase);
|
||||
function GetFeatureSubset(nFeatures: integer): array of integer; virtual;
|
||||
|
||||
procedure SetRowIndices(rows: array of integer);
|
||||
|
|
@ -563,20 +565,23 @@ type
|
|||
/// Дерево решений для задачи классификации.
|
||||
/// Использует критерий нечистоты (обычно Gini) для выбора оптимальных разбиений.
|
||||
/// В листьях хранится наиболее частый класс
|
||||
DecisionTreeClassifier = class(DecisionTreeBase, IClassifier)
|
||||
DecisionTreeClassifier = class(IClassifier)
|
||||
private
|
||||
fMaxDepth: integer;
|
||||
fMinSamplesSplit: integer;
|
||||
fMinSamplesLeaf: integer;
|
||||
fFitted: boolean;
|
||||
fCriterion: ISplitCriterion;
|
||||
fFeatureImportances: Vector;
|
||||
fRandomSeed: integer;
|
||||
fMaxFeatures: integer := 0;
|
||||
fUserProvidedSeed: boolean;
|
||||
fRng: System.Random;
|
||||
|
||||
fCore: DecisionTreeCore;
|
||||
fIndexToClass: array of integer;
|
||||
fClassLabels: array of string;
|
||||
|
||||
function PredictOne(X: Matrix; rowIndex: integer): integer;
|
||||
function MajorityClass(y: Vector; indices: array of integer): integer;
|
||||
|
||||
protected
|
||||
function LeafValue(y: Vector; indices: array of integer): real; override;
|
||||
|
||||
function FindBestSplit(X: Matrix; y: Vector; indices: array of integer): SplitResult; override;
|
||||
|
||||
public
|
||||
/// Создает классификационное дерево:
|
||||
/// • maxDepth — максимальная глубина дерева (-1 означает без ограничения).
|
||||
|
|
@ -589,11 +594,11 @@ type
|
|||
/// X — матрица признаков.
|
||||
/// y — вектор целевых меток (целые значения).
|
||||
/// Строит структуру дерева путем минимизации нечистоты в узлах.
|
||||
function Fit(X: Matrix; y: Vector): ISupervisedModel; override;
|
||||
function Fit(X: Matrix; y: Vector): ISupervisedModel;
|
||||
|
||||
/// Выполняет предсказание меток классов для X.
|
||||
/// Для каждого объекта возвращается класс, соответствующий листу дерева.
|
||||
function Predict(X: Matrix): Vector; override;
|
||||
function Predict(X: Matrix): Vector;
|
||||
|
||||
/// Возвращает предсказанные метки классов для объектов из X.
|
||||
/// Каждый элемент результата — индекс класса (целое число).
|
||||
|
|
@ -603,7 +608,7 @@ type
|
|||
|
||||
/// Создает глубокую копию дерева классификации.
|
||||
/// Копируется структура узлов, параметры и обученное состояние.
|
||||
function Clone: IModel; override;
|
||||
function Clone: IModel;
|
||||
|
||||
/// Возвращает строковое представление модели.
|
||||
function ToString: string; override;
|
||||
|
|
@ -617,6 +622,10 @@ type
|
|||
procedure SetClassLabels(classes: array of string);
|
||||
|
||||
function GetClassLabels: array of string;
|
||||
|
||||
/// Возвращает true, если дерево обучено.
|
||||
/// Если false — Predict вызовет ошибку.
|
||||
property IsFitted: boolean read fFitted;
|
||||
end;
|
||||
|
||||
//============================
|
||||
|
|
@ -627,7 +636,7 @@ type
|
|||
/// Использует критерий дисперсии для выбора разбиений.
|
||||
/// В листьях хранится среднее значение целевой переменной.
|
||||
/// Поддерживает L2-регуляризацию значения листа (leafL2)
|
||||
DecisionTreeRegressor = class(DecisionTreeBase, IRegressor)
|
||||
DecisionTreeRegressor = class(DecisionTreeRegressorBase, IRegressor)
|
||||
private
|
||||
fLeafL2: real;
|
||||
|
||||
|
|
@ -2633,7 +2642,7 @@ end;
|
|||
constructor LassoRegression.Create(alpha: real; maxIter: integer; tol: real);
|
||||
begin
|
||||
// Lasso = ElasticNet с L2 = 0
|
||||
new ElasticNet(alpha, 0.0, maxIter, tol);
|
||||
fModel := new ElasticNet(alpha, 0.0, maxIter, tol);
|
||||
end;
|
||||
|
||||
function LassoRegression.Fit(X: Matrix; y: Vector): ISupervisedModel;
|
||||
|
|
@ -2810,7 +2819,7 @@ begin
|
|||
// --- convergence check
|
||||
if fCheckConvergence then
|
||||
begin
|
||||
if Abs(prevLoss - loss) < fTol then
|
||||
if Abs(prevLoss - loss) < Max(fTol, fMinImprovement) then
|
||||
break;
|
||||
|
||||
prevLoss := loss;
|
||||
|
|
@ -2934,7 +2943,7 @@ begin
|
|||
Result := new integer[v.Length];
|
||||
|
||||
for var i := 0 to v.Length - 1 do
|
||||
Result[i] := integer(v[i]);
|
||||
Result[i] := Round(v[i]);
|
||||
end;
|
||||
|
||||
function LogisticRegression.ToString: string;
|
||||
|
|
@ -3490,7 +3499,7 @@ end;
|
|||
|
||||
// DecisionTreeBase
|
||||
|
||||
constructor DecisionTreeBase.Create(
|
||||
constructor DecisionTreeRegressorBase.Create(
|
||||
maxDepth: integer;
|
||||
minSamplesSplit: integer;
|
||||
minSamplesLeaf: integer;
|
||||
|
|
@ -3536,7 +3545,7 @@ begin
|
|||
fRng := new System.Random(fRandomSeed);
|
||||
end;
|
||||
|
||||
procedure DecisionTreeBase.CopyBaseState(dest: DecisionTreeBase);
|
||||
procedure DecisionTreeRegressorBase.CopyBaseState(dest: DecisionTreeRegressorBase);
|
||||
begin
|
||||
dest.fMaxDepth := fMaxDepth;
|
||||
dest.fMinSamplesSplit := fMinSamplesSplit;
|
||||
|
|
@ -3566,7 +3575,7 @@ begin
|
|||
dest.fRowIndices := Copy(fRowIndices);
|
||||
end;
|
||||
|
||||
function DecisionTreeBase.GetFeatureSubset(nFeatures: integer): array of integer;
|
||||
function DecisionTreeRegressorBase.GetFeatureSubset(nFeatures: integer): array of integer;
|
||||
begin
|
||||
if (fMaxFeatures = 0) or (fMaxFeatures >= nFeatures) then
|
||||
begin
|
||||
|
|
@ -3592,7 +3601,7 @@ begin
|
|||
Result := subset;
|
||||
end;
|
||||
|
||||
procedure DecisionTreeBase.SetRowIndices(rows: array of integer);
|
||||
procedure DecisionTreeRegressorBase.SetRowIndices(rows: array of integer);
|
||||
begin
|
||||
if Length(rows) = 0 then
|
||||
ArgumentError('Row subset cannot be empty!!Row subset cannot be empty');
|
||||
|
|
@ -3600,7 +3609,7 @@ begin
|
|||
fRowIndices := Copy(rows);
|
||||
end;
|
||||
|
||||
function DecisionTreeBase.FeatureImportances: Vector;
|
||||
function DecisionTreeRegressorBase.FeatureImportances: Vector;
|
||||
begin
|
||||
if not fFitted then
|
||||
NotFittedError(ER_FIT_NOT_CALLED);
|
||||
|
|
@ -3611,7 +3620,7 @@ begin
|
|||
Result := fFeatureImportances.Clone;
|
||||
end;
|
||||
|
||||
function DecisionTreeBase.LeafNode(value: real): DecisionTreeNode;
|
||||
function DecisionTreeRegressorBase.LeafNode(value: real): DecisionTreeNode;
|
||||
begin
|
||||
var n := new DecisionTreeNode;
|
||||
n.IsLeaf := true;
|
||||
|
|
@ -3619,7 +3628,7 @@ begin
|
|||
Result := n;
|
||||
end;
|
||||
|
||||
function DecisionTreeBase.BuildTree(X: Matrix; y: Vector;
|
||||
function DecisionTreeRegressorBase.BuildTree(X: Matrix; y: Vector;
|
||||
indices: array of integer; depth: integer): DecisionTreeNode;
|
||||
begin
|
||||
if (fMaxDepth >= 0) and (depth >= fMaxDepth) then
|
||||
|
|
@ -3695,7 +3704,7 @@ end;
|
|||
|
||||
const EPS = 1e-12;
|
||||
|
||||
function DecisionTreeBase.IsPure(y: Vector; indices: array of integer): boolean;
|
||||
function DecisionTreeRegressorBase.IsPure(y: Vector; indices: array of integer): boolean;
|
||||
begin
|
||||
Result := fCriterion.Impurity(y, indices) < EPS;
|
||||
end;
|
||||
|
|
@ -3823,180 +3832,37 @@ end;
|
|||
// DecisionTreeClassifier
|
||||
//==============================
|
||||
|
||||
function DecisionTreeClassifier.FindBestSplit(X: Matrix; y: Vector; indices: array of integer): SplitResult;
|
||||
constructor DecisionTreeClassifier.Create(maxDepth: integer; minSamplesSplit: integer; minSamplesLeaf: integer; criterion: ISplitCriterion; seed: integer);
|
||||
begin
|
||||
var n := indices.Length;
|
||||
if n <= 1 then
|
||||
exit(SplitResult.Invalid);
|
||||
if maxDepth = 0 then
|
||||
ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, maxDepth);
|
||||
|
||||
var p := X.ColCount;
|
||||
if minSamplesSplit < 2 then
|
||||
ArgumentOutOfRangeError(ER_MIN_SAMPLES_SPLIT_INVALID, minSamplesSplit);
|
||||
|
||||
var bestFeature := -1;
|
||||
var bestThreshold := 0.0;
|
||||
var bestScore := real.PositiveInfinity;
|
||||
if minSamplesLeaf < 1 then
|
||||
ArgumentOutOfRangeError(ER_MIN_SAMPLES_LEAF_INVALID, minSamplesLeaf);
|
||||
|
||||
var feat := new integer[p];
|
||||
for var i := 0 to p - 1 do
|
||||
feat[i] := i;
|
||||
if minSamplesLeaf >= minSamplesSplit then
|
||||
ArgumentOutOfRangeError(ER_MIN_LEAF_GE_SPLIT, minSamplesLeaf, minSamplesSplit);
|
||||
|
||||
var m := fMaxFeatures;
|
||||
if (m <= 0) or (m > p) then
|
||||
m := p;
|
||||
fMaxDepth := maxDepth;
|
||||
fMinSamplesSplit := minSamplesSplit;
|
||||
fMinSamplesLeaf := minSamplesLeaf;
|
||||
fCriterion := if criterion = nil then new GiniCriterion else criterion;
|
||||
|
||||
// partial Fisher–Yates
|
||||
for var i := 0 to m - 1 do
|
||||
if seed < 0 then
|
||||
begin
|
||||
var j := fRng.Next(i, p);
|
||||
var tmp := feat[i];
|
||||
feat[i] := feat[j];
|
||||
feat[j] := tmp;
|
||||
end;
|
||||
|
||||
for var fi := 0 to m - 1 do
|
||||
begin
|
||||
var feature := feat[fi];
|
||||
|
||||
var values := new real[n];
|
||||
var labels := new integer[n];
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
var row := indices[i];
|
||||
values[i] := X[row, feature];
|
||||
|
||||
var cls := Round(y[row]);
|
||||
|
||||
if (cls < 0) or (cls >= ClassCount) then
|
||||
ArgumentError(ER_LABEL_INDEX_INVALID);
|
||||
|
||||
labels[i] := cls;
|
||||
end;
|
||||
|
||||
System.Array.Sort(values, labels);
|
||||
|
||||
var rightCounts := new integer[ClassCount];
|
||||
for var i := 0 to n - 1 do
|
||||
rightCounts[labels[i]] += 1;
|
||||
|
||||
var leftCounts := new integer[ClassCount];
|
||||
|
||||
var leftSize := 0;
|
||||
var rightSize := n;
|
||||
|
||||
for var i := 0 to n - 2 do
|
||||
begin
|
||||
var cls := labels[i];
|
||||
|
||||
leftCounts[cls] += 1;
|
||||
rightCounts[cls] -= 1;
|
||||
|
||||
leftSize += 1;
|
||||
rightSize -= 1;
|
||||
|
||||
if values[i] = values[i + 1] then
|
||||
continue;
|
||||
|
||||
if (leftSize < fMinSamplesLeaf) or (rightSize < fMinSamplesLeaf) then
|
||||
continue;
|
||||
|
||||
// ----- GINI LEFT -----
|
||||
var giniLeft := 1.0;
|
||||
|
||||
for var c := 0 to ClassCount - 1 do
|
||||
begin
|
||||
if leftCounts[c] > 0 then
|
||||
begin
|
||||
var q := leftCounts[c] / real(leftSize);
|
||||
giniLeft -= q * q;
|
||||
end;
|
||||
end;
|
||||
|
||||
if giniLeft < 0 then
|
||||
giniLeft := 0.0;
|
||||
|
||||
// ----- GINI RIGHT -----
|
||||
var giniRight := 1.0;
|
||||
|
||||
for var c := 0 to ClassCount - 1 do
|
||||
begin
|
||||
if rightCounts[c] > 0 then
|
||||
begin
|
||||
var q := rightCounts[c] / real(rightSize);
|
||||
giniRight -= q * q;
|
||||
end;
|
||||
end;
|
||||
|
||||
if giniRight < 0 then
|
||||
giniRight := 0.0;
|
||||
|
||||
var weighted :=
|
||||
(real(leftSize) / n) * giniLeft +
|
||||
(real(rightSize) / n) * giniRight;
|
||||
|
||||
if double.IsNaN(weighted) then
|
||||
continue;
|
||||
|
||||
if weighted < bestScore then
|
||||
begin
|
||||
bestScore := weighted;
|
||||
bestFeature := feature;
|
||||
bestThreshold := (values[i] + values[i + 1]) * 0.5;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
if bestFeature = -1 then
|
||||
exit(SplitResult.Invalid);
|
||||
|
||||
Result := SplitResult.Create(bestFeature, bestThreshold);
|
||||
end;
|
||||
|
||||
function DecisionTreeClassifier.PredictOne(X: Matrix; rowIndex: integer): integer;
|
||||
begin
|
||||
var node := fRoot;
|
||||
|
||||
while not node.IsLeaf do
|
||||
begin
|
||||
if X[rowIndex, node.FeatureIndex] <= node.Threshold then
|
||||
node := node.Left
|
||||
fUserProvidedSeed := false;
|
||||
fRandomSeed := System.Environment.TickCount and integer.MaxValue;
|
||||
end
|
||||
else
|
||||
node := node.Right;
|
||||
end;
|
||||
|
||||
Result := integer(node.LeafValue); // внутренний индекс
|
||||
end;
|
||||
|
||||
function DecisionTreeClassifier.MajorityClass(y: Vector; indices: array of integer): integer;
|
||||
begin
|
||||
var counts := new integer[ClassCount];
|
||||
|
||||
// Подсчёт частот
|
||||
foreach var i in indices do
|
||||
begin
|
||||
var c := Round(y[i]);
|
||||
counts[c] += 1;
|
||||
fUserProvidedSeed := true;
|
||||
fRandomSeed := seed;
|
||||
end;
|
||||
|
||||
// Поиск максимума
|
||||
var bestClass := 0;
|
||||
var bestCount := -1;
|
||||
|
||||
for var c := 0 to ClassCount - 1 do
|
||||
if counts[c] > bestCount then
|
||||
begin
|
||||
bestCount := counts[c];
|
||||
bestClass := c;
|
||||
end;
|
||||
|
||||
Result := bestClass;
|
||||
end;
|
||||
|
||||
constructor DecisionTreeClassifier.Create(maxDepth: integer; minSamplesSplit: integer; minSamplesLeaf: integer;
|
||||
criterion: ISplitCriterion; seed: integer);
|
||||
begin
|
||||
inherited Create(maxDepth, minSamplesSplit, minSamplesLeaf,
|
||||
(if criterion = nil then new GiniCriterion else criterion),
|
||||
seed);
|
||||
fRng := new System.Random(fRandomSeed);
|
||||
end;
|
||||
|
||||
function DecisionTreeClassifier.Fit(X: Matrix; y: Vector): ISupervisedModel;
|
||||
|
|
@ -4060,7 +3926,8 @@ begin
|
|||
fMinSamplesSplit,
|
||||
fMinSamplesLeaf,
|
||||
fCriterion,
|
||||
0 // maxFeatures = 0 -> использовать все признаки
|
||||
fMaxFeatures,
|
||||
fRandomSeed
|
||||
);
|
||||
|
||||
fCore.Fit(X, yEncoded);
|
||||
|
|
@ -4115,7 +3982,24 @@ begin
|
|||
fRandomSeed
|
||||
);
|
||||
|
||||
CopyBaseState(m);
|
||||
m.fMaxDepth := fMaxDepth;
|
||||
m.fMinSamplesSplit := fMinSamplesSplit;
|
||||
m.fMinSamplesLeaf := fMinSamplesLeaf;
|
||||
m.fFitted := fFitted;
|
||||
m.fRandomSeed := fRandomSeed;
|
||||
m.fMaxFeatures := fMaxFeatures;
|
||||
m.fUserProvidedSeed := fUserProvidedSeed;
|
||||
|
||||
if fUserProvidedSeed then
|
||||
m.fRng := new System.Random(fRandomSeed)
|
||||
else
|
||||
m.fRng := new System.Random;
|
||||
|
||||
if fCriterion <> nil then
|
||||
m.fCriterion := fCriterion;
|
||||
|
||||
if fFeatureImportances <> nil then
|
||||
m.fFeatureImportances := fFeatureImportances.Clone;
|
||||
|
||||
// --- classes (единственный источник истины)
|
||||
if fIndexToClass <> nil then
|
||||
|
|
@ -4134,20 +4018,16 @@ begin
|
|||
fMinSamplesSplit,
|
||||
fMinSamplesLeaf,
|
||||
fCriterion,
|
||||
fMaxFeatures
|
||||
fMaxFeatures,
|
||||
fRandomSeed
|
||||
);
|
||||
|
||||
m.fCore.fRoot := fCore.fRoot.Clone; // ключевой момент
|
||||
m.fCore.fRoot := fCore.fRoot.Clone;
|
||||
end;
|
||||
|
||||
Result := m;
|
||||
end;
|
||||
|
||||
function DecisionTreeClassifier.LeafValue(y: Vector; indices: array of integer): real;
|
||||
begin
|
||||
Result := MajorityClass(y, indices);
|
||||
end;
|
||||
|
||||
function DecisionTreeClassifier.ToString: string;
|
||||
begin
|
||||
Result :=
|
||||
|
|
@ -4907,7 +4787,7 @@ begin
|
|||
Result := new integer[v.Length];
|
||||
|
||||
for var i := 0 to v.Length - 1 do
|
||||
Result[i] := integer(v[i]);
|
||||
Result[i] := Round(v[i]);
|
||||
end;
|
||||
|
||||
function RandomForestClassifier.PredictProba(X: Matrix): Matrix;
|
||||
|
|
@ -5517,19 +5397,8 @@ begin
|
|||
var deltaTrain := tree.Predict(XTrain);
|
||||
|
||||
// --- update TRAIN ---
|
||||
if useSubsample then
|
||||
begin
|
||||
for var i := 0 to rows.Length - 1 do
|
||||
begin
|
||||
var idx := rows[i];
|
||||
yPredTrain[idx] += fLearningRate * deltaTrain[idx];
|
||||
end;
|
||||
end
|
||||
else
|
||||
begin
|
||||
for var i := 0 to nTrain - 1 do
|
||||
yPredTrain[i] += fLearningRate * deltaTrain[i];
|
||||
end;
|
||||
|
||||
// --- update OOB ---
|
||||
if useOOB then
|
||||
|
|
@ -6128,10 +5997,7 @@ begin
|
|||
begin
|
||||
for var i := 0 to nTrain - 1 do
|
||||
if not inBag[i] then
|
||||
begin
|
||||
logitsOOB[i, cls] += fLearningRate * deltaTrain[i];
|
||||
oobCount[i] += 1;
|
||||
end;
|
||||
end;
|
||||
|
||||
if useValidation then
|
||||
|
|
@ -6142,6 +6008,13 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
if useOOB then
|
||||
begin
|
||||
for var i := 0 to nTrain - 1 do
|
||||
if not inBag[i] then
|
||||
oobCount[i] += 1;
|
||||
end;
|
||||
|
||||
fEstimators.Add(trees);
|
||||
|
||||
SoftmaxMatrix(logitsTrain, probsTrain);
|
||||
|
|
@ -6801,7 +6674,7 @@ begin
|
|||
Result := new integer[v.Length];
|
||||
|
||||
for var i := 0 to v.Length - 1 do
|
||||
Result[i] := integer(v[i]);
|
||||
Result[i] := Round(v[i]);
|
||||
end;
|
||||
|
||||
procedure GradientBoostingClassifier.SetClassLabels(classes: array of string);
|
||||
|
|
@ -7146,7 +7019,7 @@ begin
|
|||
Result := new integer[v.Length];
|
||||
|
||||
for var i := 0 to v.Length - 1 do
|
||||
Result[i] := integer(v[i]);
|
||||
Result[i] := Round(v[i]);
|
||||
end;
|
||||
|
||||
function KNNClassifier.PredictProba(X: Matrix): Matrix;
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ type
|
|||
/// • target — целевая переменная.
|
||||
///
|
||||
/// Если модель не добавлена, DataPipeline работает как чистый DF-конвейер (Fit/Transform/FitTransform)
|
||||
DataPipeline = class(PipelineBase)
|
||||
DataPipeline = class(PipelineBase, IModel)
|
||||
private
|
||||
fModel: ISupervisedModel;
|
||||
fTask: TaskKind;
|
||||
|
|
@ -112,6 +112,10 @@ type
|
|||
function GetClassLabels: array of string;
|
||||
|
||||
function ToString: string; override;
|
||||
|
||||
function Name: string := Self.GetType.Name;
|
||||
|
||||
function Clone: IModel;
|
||||
end;
|
||||
|
||||
/// UDataPipeline — конвейер подготовки данных и обучения модели без учителя на DataFrame.
|
||||
|
|
@ -131,7 +135,7 @@ type
|
|||
///
|
||||
/// Если модель не добавлена, UDataPipeline работает как чистый DF-конвейер
|
||||
/// (Fit/Transform/FitTransform)
|
||||
UDataPipeline = class(PipelineBase)
|
||||
UDataPipeline = class(PipelineBase, IModel)
|
||||
private
|
||||
//fDataSteps: List<IPreprocessor>;
|
||||
//fMatrixSteps: List<ITransformer>;
|
||||
|
|
@ -198,6 +202,10 @@ type
|
|||
function PredictLabels(df: DataFrame): array of integer;
|
||||
|
||||
function ToString: string; override;
|
||||
|
||||
function Clone: IModel;
|
||||
|
||||
function Name: string := Self.GetType.Name;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -304,6 +312,8 @@ const
|
|||
ER_MODEL_NOT_CLASSIFIER =
|
||||
'Модель не является классификатором!!' +
|
||||
'Model is not a classifier';
|
||||
ER_MODEL_CLONE_TYPE =
|
||||
'Clone модели вернул неподдерживаемый тип!!Model Clone returned unsupported type';
|
||||
|
||||
//-----------------------------
|
||||
// DataPipeline
|
||||
|
|
@ -588,6 +598,10 @@ begin
|
|||
if feats.Count = 0 then
|
||||
ArgumentError(ER_PIPELINE_NO_FEATURES);
|
||||
|
||||
// fFinalFeatures — признаки DataFrame (ДО matrix-transform)
|
||||
// После FitTransformMatrix пространство признаков меняется (PCA и т.п.),
|
||||
// но fFinalFeatures используется только для ToMatrix в Predict.
|
||||
|
||||
fFinalFeatures := feats.ToArray;
|
||||
|
||||
var X := current.ToMatrix(fFinalFeatures);
|
||||
|
|
@ -611,6 +625,8 @@ begin
|
|||
|
||||
// --- 3) Matrix transformers
|
||||
X := FitTransformMatrix(X, y);
|
||||
// После этого X уже не соответствует fFinalFeatures по размерности,
|
||||
// но это нормально: fFinalFeatures используется только до ToMatrix.
|
||||
|
||||
// --- 4) модель
|
||||
fModel := fModel.Fit(X, y);
|
||||
|
|
@ -672,15 +688,7 @@ begin
|
|||
|
||||
var current := Transform(df);
|
||||
|
||||
if fFinalFeatures = nil then
|
||||
Error(ER_PIPELINE_FINALFEATURES);
|
||||
|
||||
for var i := 0 to High(fFinalFeatures) do
|
||||
if not current.HasColumn(fFinalFeatures[i]) then
|
||||
ArgumentError(ER_PIPELINE_FEATURE_NOT_FOUND, fFinalFeatures[i]);
|
||||
|
||||
var X := current.ToMatrix(fFinalFeatures);
|
||||
|
||||
X := TransformMatrix(X);
|
||||
|
||||
if not (fModel is IPredictiveModel) then
|
||||
|
|
@ -704,15 +712,7 @@ begin
|
|||
|
||||
var current := Transform(df);
|
||||
|
||||
if fFinalFeatures = nil then
|
||||
Error(ER_PIPELINE_FINALFEATURES);
|
||||
|
||||
for var i := 0 to High(fFinalFeatures) do
|
||||
if not current.HasColumn(fFinalFeatures[i]) then
|
||||
ArgumentError(ER_PIPELINE_FEATURE_NOT_FOUND, fFinalFeatures[i]);
|
||||
|
||||
var X := current.ToMatrix(fFinalFeatures);
|
||||
|
||||
X := TransformMatrix(X);
|
||||
|
||||
Result := (fModel as IProbabilisticClassifier).PredictProba(X);
|
||||
|
|
@ -807,6 +807,36 @@ begin
|
|||
Result := sb;
|
||||
end;
|
||||
|
||||
function DataPipeline.Clone: IModel;
|
||||
begin
|
||||
var p := new DataPipeline;
|
||||
|
||||
// --- конфигурация
|
||||
p.fFeatures := Copy(fFeatures);
|
||||
p.fTarget := fTarget;
|
||||
p.fTask := fTask;
|
||||
|
||||
// --- шаги (глубокая копия)
|
||||
p.fDataSteps := fDataSteps.Select(s -> s.Clone).ToList;
|
||||
p.fMatrixSteps := fMatrixSteps.Select(s -> s.Clone).ToList;
|
||||
|
||||
// --- модель (глубокая копия конфигурации)
|
||||
if fModel <> nil then
|
||||
begin
|
||||
var m := fModel.Clone;
|
||||
if not (m is ISupervisedModel) then
|
||||
Error(ER_MODEL_CLONE_TYPE);
|
||||
|
||||
p.fModel := m as ISupervisedModel;
|
||||
end;
|
||||
|
||||
// --- состояние НЕ копируем
|
||||
p.fFinalFeatures := nil;
|
||||
p.fFitted := false;
|
||||
|
||||
Result := p;
|
||||
end;
|
||||
|
||||
//-----------------------------
|
||||
// UDataPipeline
|
||||
//-----------------------------
|
||||
|
|
@ -1195,4 +1225,26 @@ begin
|
|||
Result := sb;
|
||||
end;
|
||||
|
||||
function UDataPipeline.Clone: IModel;
|
||||
begin
|
||||
var p := new UDataPipeline;
|
||||
|
||||
// --- конфигурация
|
||||
p.fFeatures := Copy(fFeatures);
|
||||
|
||||
// --- шаги (глубокая копия)
|
||||
p.fDataSteps := fDataSteps.Select(s -> s.Clone).ToList;
|
||||
p.fMatrixSteps := fMatrixSteps.Select(s -> s.Clone).ToList;
|
||||
|
||||
// --- модель
|
||||
if fModel <> nil then
|
||||
p.fModel := fModel.Clone as IUnsupervisedModel;
|
||||
|
||||
// --- состояние НЕ копируем
|
||||
p.fFinalFeatures := nil;
|
||||
p.fFitted := false;
|
||||
|
||||
Result := p;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
|
@ -994,7 +994,7 @@ begin
|
|||
var correct := 0;
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
if yTrue[i] = yPred[i] then
|
||||
if Round(yTrue[i]) = Round(yPred[i]) then
|
||||
correct += 1;
|
||||
|
||||
Result := correct / n;
|
||||
|
|
@ -1667,6 +1667,9 @@ end;
|
|||
/// 1 — идеальное совпадение кластеризации и истинных меток
|
||||
/// 0 — случайное разбиение
|
||||
/// <0 — хуже случайного
|
||||
/// Сложность: O(n²). При больших n может быть медленным.
|
||||
/// Для больших выборок рекомендуется использовать альтернативную реализацию
|
||||
/// через таблицу сопряжённости (contingency table) с асимптотикой O(n · k)
|
||||
static function Metrics.AdjustedRandIndex(yTrue, yPred: Vector): real;
|
||||
begin
|
||||
if yTrue.Length <> yPred.Length then
|
||||
|
|
|
|||
|
|
@ -785,6 +785,7 @@ begin
|
|||
var container := rootChart.Content as GridWPF;
|
||||
if container <> nil then
|
||||
container.Children.Clear;
|
||||
rootPaletteIndex := 0;
|
||||
end);
|
||||
end;
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ type
|
|||
function Transform(df: DataFrame): DataFrame;
|
||||
/// Выполняет Fit и Transform последовательно
|
||||
function FitTransform(df: DataFrame): DataFrame;
|
||||
|
||||
function Clone: IPreprocessor;
|
||||
end;
|
||||
|
||||
/// Кодирует строковый категориальный столбец в целочисленные индексы (0,1,2,...).
|
||||
|
|
@ -62,6 +64,8 @@ type
|
|||
function ToString: string; override;
|
||||
|
||||
property ColumnName: string read col;
|
||||
|
||||
function Clone: IPreprocessor;
|
||||
end;
|
||||
|
||||
/// Кодирует строковый категориальный столбец в набор бинарных (one-hot) столбцов
|
||||
|
|
@ -89,6 +93,8 @@ type
|
|||
function ToString: string; override;
|
||||
|
||||
property ColumnName: string read col;
|
||||
|
||||
function Clone: IPreprocessor;
|
||||
end;
|
||||
|
||||
ImputeStrategy = (isMean, isConstant, isMedian);
|
||||
|
|
@ -123,6 +129,8 @@ type
|
|||
function ToString: string; override;
|
||||
|
||||
property Columns: array of string read cols;
|
||||
|
||||
function Clone: IPreprocessor;
|
||||
end;
|
||||
|
||||
|
||||
|
|
@ -194,6 +202,9 @@ const
|
|||
'Массив констант не задан или имеет неверный размер!!Constants array is null or has invalid length';
|
||||
ER_IMPUTER_STRATEGY_NOT_SUPPORTED =
|
||||
'Стратегия импутации {0} не поддерживается!!Imputation strategy {0} is not supported';
|
||||
ER_UNSUPPORTED_IMPUTE_STRATEGY =
|
||||
'Неподдерживаемая стратегия заполнения: {0}!!Unsupported impute strategy: {0}';
|
||||
|
||||
|
||||
//-----------------------------
|
||||
// LabelEncoder
|
||||
|
|
@ -287,7 +298,7 @@ begin
|
|||
|
||||
foreach var src in df.GetColumns do
|
||||
if src.Info.Name <> col then
|
||||
res.AddColumnView(src)
|
||||
res.AddColumnAlias(src)
|
||||
else
|
||||
res.AddIntColumn(col, data, valid);
|
||||
|
||||
|
|
@ -305,6 +316,10 @@ begin
|
|||
Result := 'LabelEncoder(' + col + ')';
|
||||
end;
|
||||
|
||||
function LabelEncoder.Clone: IPreprocessor;
|
||||
begin
|
||||
Result := new LabelEncoder(col);
|
||||
end;
|
||||
//-----------------------------
|
||||
// OneHotEncoder
|
||||
//-----------------------------
|
||||
|
|
@ -414,6 +429,11 @@ begin
|
|||
Result := 'OneHotEncoder(column=' + col + ')';
|
||||
end;
|
||||
|
||||
function OneHotEncoder.Clone: IPreprocessor;
|
||||
begin
|
||||
Result := new OneHotEncoder(col);
|
||||
end;
|
||||
|
||||
//-----------------------------
|
||||
// Imputer
|
||||
//-----------------------------
|
||||
|
|
@ -546,8 +566,10 @@ begin
|
|||
for var i := 0 to cols.Length - 1 do
|
||||
begin
|
||||
var name := cols[i];
|
||||
var idx := df.Schema.IndexOf(name);
|
||||
var ct := df.Schema.ColumnTypeAt(idx);
|
||||
|
||||
// --- ВАЖНО: используем актуальную схему
|
||||
var idx := res.Schema.IndexOf(name);
|
||||
var ct := res.Schema.ColumnTypeAt(idx);
|
||||
|
||||
if not (ct in [ColumnType.ctInt, ColumnType.ctFloat]) then
|
||||
Error(ER_IMPUTER_COLUMN_NOT_NUMERIC, name);
|
||||
|
|
@ -571,12 +593,19 @@ begin
|
|||
if ct = ColumnType.ctInt then
|
||||
begin
|
||||
var k: integer;
|
||||
try
|
||||
k := integer(v);
|
||||
except
|
||||
on e: Exception do
|
||||
|
||||
if v is integer then
|
||||
k := integer(v)
|
||||
else if v is real then
|
||||
begin
|
||||
var r := real(v);
|
||||
var ir := Round(r);
|
||||
if Abs(r - ir) > 1e-9 then
|
||||
Error(ER_IMPUTER_CONSTANT_TYPE_MISMATCH, name);
|
||||
k := ir;
|
||||
end
|
||||
else
|
||||
Error(ER_IMPUTER_CONSTANT_TYPE_MISMATCH, name);
|
||||
end;
|
||||
|
||||
res := res.ReplaceColumnInt(
|
||||
name,
|
||||
|
|
@ -651,4 +680,24 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
function Imputer.Clone: IPreprocessor;
|
||||
begin
|
||||
case strategy of
|
||||
ImputeStrategy.isMean:
|
||||
Result := new Imputer(cols);
|
||||
|
||||
ImputeStrategy.isMedian:
|
||||
Result := new Imputer(ImputeStrategy.isMedian, cols);
|
||||
|
||||
ImputeStrategy.isConstant:
|
||||
begin
|
||||
var val := if (constants <> nil) and (constants.Length > 0) then constants[0] else nil;
|
||||
Result := new Imputer(val, cols);
|
||||
end;
|
||||
|
||||
else
|
||||
Error(ER_UNSUPPORTED_IMPUTE_STRATEGY, strategy);
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
|
@ -109,6 +109,8 @@ const
|
|||
ER_DATASET_TOO_SMALL =
|
||||
'Для {0} требуется как минимум 2 объекта!!' +
|
||||
'At least 2 samples are required for {0}';
|
||||
ER_STRATIFIED_CLASS_TOO_SMALL =
|
||||
'Класс {0} содержит {1} объектов, что меньше числа фолдов ({2})!!Class {0} has {1} samples, which is less than the number of folds ({2})';
|
||||
|
||||
//-----------------------------
|
||||
// Validation
|
||||
|
|
@ -319,7 +321,7 @@ begin
|
|||
|
||||
if cnt < k then
|
||||
ArgumentError(
|
||||
'StratifiedKFold: класс %d содержит %d объектов, что меньше числа фолдов (%d)',
|
||||
ER_STRATIFIED_CLASS_TOO_SMALL,
|
||||
cls, cnt, k
|
||||
);
|
||||
end;
|
||||
|
|
|
|||
Loading…
Reference in a new issue