diff --git a/Configuration/GlobalAssemblyInfo.cs b/Configuration/GlobalAssemblyInfo.cs
index e7a245651..37a69999c 100644
--- a/Configuration/GlobalAssemblyInfo.cs
+++ b/Configuration/GlobalAssemblyInfo.cs
@@ -15,7 +15,7 @@ internal static class RevisionClass
public const string Major = "3";
public const string Minor = "11";
public const string Build = "1";
- public const string Revision = "3773";
+ public const string Revision = "3776";
public const string MainVersion = Major + "." + Minor;
public const string FullVersion = Major + "." + Minor + "." + Build + "." + Revision;
diff --git a/Configuration/Version.defs b/Configuration/Version.defs
index b217f7047..561461942 100644
--- a/Configuration/Version.defs
+++ b/Configuration/Version.defs
@@ -1,4 +1,4 @@
-%MINOR%=11
-%REVISION%=3773
%COREVERSION%=1
+%REVISION%=3776
+%MINOR%=11
%MAJOR%=3
diff --git a/CoreUtils/Utils.csproj b/CoreUtils/Utils.csproj
index 23625065b..886cfb913 100644
--- a/CoreUtils/Utils.csproj
+++ b/CoreUtils/Utils.csproj
@@ -7,5 +7,7 @@
false
false
+
+ $(OutputPath)
\ No newline at end of file
diff --git a/InstallerSamples/MachineLearning/08_Datasets/06_LoadIris.pas b/InstallerSamples/MachineLearning/08_Datasets/06_LoadIris.pas
new file mode 100644
index 000000000..8f22f4a49
--- /dev/null
+++ b/InstallerSamples/MachineLearning/08_Datasets/06_LoadIris.pas
@@ -0,0 +1,16 @@
+uses MLABC;
+
+begin
+ var ds := Datasets.Iris;
+
+ Println('Dataset: ', ds.Name);
+ Println('Task: ', ds.Task);
+ Println;
+
+ Println('Rows:', ds.Data.RowCount);
+ Println('Features:', ds.Features.Length);
+ Println;
+
+ Println('Head:');
+ ds.Data.Print;
+end.
\ No newline at end of file
diff --git a/Release/pabcversion.txt b/Release/pabcversion.txt
index 5f7509154..bff0c8543 100644
--- a/Release/pabcversion.txt
+++ b/Release/pabcversion.txt
@@ -1 +1 @@
-3.11.1.3773
+3.11.1.3776
diff --git a/ReleaseGenerators/Files/Datasets/iris.csv b/ReleaseGenerators/Files/Datasets/iris.csv
new file mode 100644
index 000000000..80511dba8
--- /dev/null
+++ b/ReleaseGenerators/Files/Datasets/iris.csv
@@ -0,0 +1,151 @@
+sepal_length,sepal_width,petal_length,petal_width,species
+5.1,3.5,1.4,0.2,setosa
+4.9,3.0,1.4,0.2,setosa
+4.7,3.2,1.3,0.2,setosa
+4.6,3.1,1.5,0.2,setosa
+5.0,3.6,1.4,0.2,setosa
+5.4,3.9,1.7,0.4,setosa
+4.6,3.4,1.4,0.3,setosa
+5.0,3.4,1.5,0.2,setosa
+4.4,2.9,1.4,0.2,setosa
+4.9,3.1,1.5,0.1,setosa
+5.4,3.7,1.5,0.2,setosa
+4.8,3.4,1.6,0.2,setosa
+4.8,3.0,1.4,0.1,setosa
+4.3,3.0,1.1,0.1,setosa
+5.8,4.0,1.2,0.2,setosa
+5.7,4.4,1.5,0.4,setosa
+5.4,3.9,1.3,0.4,setosa
+5.1,3.5,1.4,0.3,setosa
+5.7,3.8,1.7,0.3,setosa
+5.1,3.8,1.5,0.3,setosa
+5.4,3.4,1.7,0.2,setosa
+5.1,3.7,1.5,0.4,setosa
+4.6,3.6,1.0,0.2,setosa
+5.1,3.3,1.7,0.5,setosa
+4.8,3.4,1.9,0.2,setosa
+5.0,3.0,1.6,0.2,setosa
+5.0,3.4,1.6,0.4,setosa
+5.2,3.5,1.5,0.2,setosa
+5.2,3.4,1.4,0.2,setosa
+4.7,3.2,1.6,0.2,setosa
+4.8,3.1,1.6,0.2,setosa
+5.4,3.4,1.5,0.4,setosa
+5.2,4.1,1.5,0.1,setosa
+5.5,4.2,1.4,0.2,setosa
+4.9,3.1,1.5,0.2,setosa
+5.0,3.2,1.2,0.2,setosa
+5.5,3.5,1.3,0.2,setosa
+4.9,3.6,1.4,0.1,setosa
+4.4,3.0,1.3,0.2,setosa
+5.1,3.4,1.5,0.2,setosa
+5.0,3.5,1.3,0.3,setosa
+4.5,2.3,1.3,0.3,setosa
+4.4,3.2,1.3,0.2,setosa
+5.0,3.5,1.6,0.6,setosa
+5.1,3.8,1.9,0.4,setosa
+4.8,3.0,1.4,0.3,setosa
+5.1,3.8,1.6,0.2,setosa
+4.6,3.2,1.4,0.2,setosa
+5.3,3.7,1.5,0.2,setosa
+5.0,3.3,1.4,0.2,setosa
+7.0,3.2,4.7,1.4,versicolor
+6.4,3.2,4.5,1.5,versicolor
+6.9,3.1,4.9,1.5,versicolor
+5.5,2.3,4.0,1.3,versicolor
+6.5,2.8,4.6,1.5,versicolor
+5.7,2.8,4.5,1.3,versicolor
+6.3,3.3,4.7,1.6,versicolor
+4.9,2.4,3.3,1.0,versicolor
+6.6,2.9,4.6,1.3,versicolor
+5.2,2.7,3.9,1.4,versicolor
+5.0,2.0,3.5,1.0,versicolor
+5.9,3.0,4.2,1.5,versicolor
+6.0,2.2,4.0,1.0,versicolor
+6.1,2.9,4.7,1.4,versicolor
+5.6,2.9,3.6,1.3,versicolor
+6.7,3.1,4.4,1.4,versicolor
+5.6,3.0,4.5,1.5,versicolor
+5.8,2.7,4.1,1.0,versicolor
+6.2,2.2,4.5,1.5,versicolor
+5.6,2.5,3.9,1.1,versicolor
+5.9,3.2,4.8,1.8,versicolor
+6.1,2.8,4.0,1.3,versicolor
+6.3,2.5,4.9,1.5,versicolor
+6.1,2.8,4.7,1.2,versicolor
+6.4,2.9,4.3,1.3,versicolor
+6.6,3.0,4.4,1.4,versicolor
+6.8,2.8,4.8,1.4,versicolor
+6.7,3.0,5.0,1.7,versicolor
+6.0,2.9,4.5,1.5,versicolor
+5.7,2.6,3.5,1.0,versicolor
+5.5,2.4,3.8,1.1,versicolor
+5.5,2.4,3.7,1.0,versicolor
+5.8,2.7,3.9,1.2,versicolor
+6.0,2.7,5.1,1.6,versicolor
+5.4,3.0,4.5,1.5,versicolor
+6.0,3.4,4.5,1.6,versicolor
+6.7,3.1,4.7,1.5,versicolor
+6.3,2.3,4.4,1.3,versicolor
+5.6,3.0,4.1,1.3,versicolor
+5.5,2.5,4.0,1.3,versicolor
+5.5,2.6,4.4,1.2,versicolor
+6.1,3.0,4.6,1.4,versicolor
+5.8,2.6,4.0,1.2,versicolor
+5.0,2.3,3.3,1.0,versicolor
+5.6,2.7,4.2,1.3,versicolor
+5.7,3.0,4.2,1.2,versicolor
+5.7,2.9,4.2,1.3,versicolor
+6.2,2.9,4.3,1.3,versicolor
+5.1,2.5,3.0,1.1,versicolor
+5.7,2.8,4.1,1.3,versicolor
+6.3,3.3,6.0,2.5,virginica
+5.8,2.7,5.1,1.9,virginica
+7.1,3.0,5.9,2.1,virginica
+6.3,2.9,5.6,1.8,virginica
+6.5,3.0,5.8,2.2,virginica
+7.6,3.0,6.6,2.1,virginica
+4.9,2.5,4.5,1.7,virginica
+7.3,2.9,6.3,1.8,virginica
+6.7,2.5,5.8,1.8,virginica
+7.2,3.6,6.1,2.5,virginica
+6.5,3.2,5.1,2.0,virginica
+6.4,2.7,5.3,1.9,virginica
+6.8,3.0,5.5,2.1,virginica
+5.7,2.5,5.0,2.0,virginica
+5.8,2.8,5.1,2.4,virginica
+6.4,3.2,5.3,2.3,virginica
+6.5,3.0,5.5,1.8,virginica
+7.7,3.8,6.7,2.2,virginica
+7.7,2.6,6.9,2.3,virginica
+6.0,2.2,5.0,1.5,virginica
+6.9,3.2,5.7,2.3,virginica
+5.6,2.8,4.9,2.0,virginica
+7.7,2.8,6.7,2.0,virginica
+6.3,2.7,4.9,1.8,virginica
+6.7,3.3,5.7,2.1,virginica
+7.2,3.2,6.0,1.8,virginica
+6.2,2.8,4.8,1.8,virginica
+6.1,3.0,4.9,1.8,virginica
+6.4,2.8,5.6,2.1,virginica
+7.2,3.0,5.8,1.6,virginica
+7.4,2.8,6.1,1.9,virginica
+7.9,3.8,6.4,2.0,virginica
+6.4,2.8,5.6,2.2,virginica
+6.3,2.8,5.1,1.5,virginica
+6.1,2.6,5.6,1.4,virginica
+7.7,3.0,6.1,2.3,virginica
+6.3,3.4,5.6,2.4,virginica
+6.4,3.1,5.5,1.8,virginica
+6.0,3.0,4.8,1.8,virginica
+6.9,3.1,5.4,2.1,virginica
+6.7,3.1,5.6,2.4,virginica
+6.9,3.1,5.1,2.3,virginica
+5.8,2.7,5.1,1.9,virginica
+6.8,3.2,5.9,2.3,virginica
+6.7,3.3,5.7,2.5,virginica
+6.7,3.0,5.2,2.3,virginica
+6.3,2.5,5.0,1.9,virginica
+6.5,3.0,5.2,2.0,virginica
+6.2,3.4,5.4,2.3,virginica
+5.9,3.0,5.1,1.8,virginica
\ No newline at end of file
diff --git a/ReleaseGenerators/Files/Datasets/iris.meta b/ReleaseGenerators/Files/Datasets/iris.meta
new file mode 100644
index 000000000..50170f442
--- /dev/null
+++ b/ReleaseGenerators/Files/Datasets/iris.meta
@@ -0,0 +1,34 @@
+name = Iris
+
+task = classification
+
+features = sepal_length,sepal_width,petal_length,petal_width
+target = species
+
+description.en = The classic Fisher iris dataset. One of the most famous datasets in machine learning and statistics.
+description.ru = Классический датасет ирисов Фишера. Один из самых известных датасетов в машинном обучении.
+
+source = UCI Machine Learning Repository
+url = https://github.com/mwaskom/seaborn-data/blob/master/iris.csv
+
+feature.sepal_length = float
+feature.sepal_width = float
+feature.petal_length = float
+feature.petal_width = float
+feature.species = categorical
+
+feature.sepal_length.ru = длина чашелистика
+feature.sepal_width.ru = ширина чашелистика
+feature.petal_length.ru = длина лепестка
+feature.petal_width.ru = ширина лепестка
+feature.species.ru = вид ириса
+
+feature.sepal_length.en = sepal length
+feature.sepal_width.en = sepal width
+feature.petal_length.en = petal length
+feature.petal_width.en = petal width
+feature.species.en = iris species
+
+value.species.setosa.ru = ирис щетинистый
+value.species.versicolor.ru = ирис разноцветный
+value.species.virginica.ru = ирис виргинский
\ No newline at end of file
diff --git a/ReleaseGenerators/PascalABCNET_version.nsh b/ReleaseGenerators/PascalABCNET_version.nsh
index 29d030df0..1fc727dbc 100644
--- a/ReleaseGenerators/PascalABCNET_version.nsh
+++ b/ReleaseGenerators/PascalABCNET_version.nsh
@@ -1 +1 @@
-!define VERSION '3.11.1.3773'
+!define VERSION '3.11.1.3776'
diff --git a/ReleaseGenerators/RebuildStandartModules.pas b/ReleaseGenerators/RebuildStandartModules.pas
index b17339c17..04a6666d0 100644
--- a/ReleaseGenerators/RebuildStandartModules.pas
+++ b/ReleaseGenerators/RebuildStandartModules.pas
@@ -22,7 +22,7 @@ uses
WPF,
DataFrameABC, DataFrameABCCore, LinearAlgebraML, PreprocessorABC,
MetricsABC, MLABC, MLCoreABC, MLModelsABC, ValidationML, MLExceptions, InspectionML, MLPipelineABC,
- PlotML, MLDatasets
+ PlotML, MLDatasets, DataAdapters
;
begin
diff --git a/ReleaseGenerators/RebuildStandartModulesMono.pas b/ReleaseGenerators/RebuildStandartModulesMono.pas
index af04ea0a4..a75e259fb 100644
--- a/ReleaseGenerators/RebuildStandartModulesMono.pas
+++ b/ReleaseGenerators/RebuildStandartModulesMono.pas
@@ -12,7 +12,7 @@
School, SF, TurtleABC,
DataFrameABC, DataFrameABCCore, LinearAlgebraML, PreprocessorABC,
MetricsABC, MLABC, MLCoreABC, MLModelsABC, ValidationML, MLExceptions, InspectionML, MLPipelineABC,
- PlotML, MLDatasets
+ PlotML, MLDatasets, DataAdapters
;
begin
diff --git a/ReleaseGenerators/sect_Core.nsh b/ReleaseGenerators/sect_Core.nsh
index fff471ac4..6e897bf72 100644
--- a/ReleaseGenerators/sect_Core.nsh
+++ b/ReleaseGenerators/sect_Core.nsh
@@ -193,6 +193,8 @@
File ..\bin\Lib\MLPipelineABC.pcu
File ..\bin\Lib\PlotML.pcu
File ..\bin\Lib\MLDatasets.pcu
+ File ..\bin\Lib\DataAdapters.pcu
+
File ..\bin\Lib\PABCRtl.dll
File ..\bin\Lib\HelixToolkit.Wpf.dll
@@ -300,6 +302,7 @@
${AddFile} "MLPipelineABC.pcu"
${AddFile} "PlotML.pcu"
${AddFile} "MLDatasets.pcu"
+ ${AddFile} "DataAdapters.pcu"
${AddFile} "turtle.png"
@@ -444,6 +447,7 @@
File ..\bin\Lib\MLPipelineABC.pas
File ..\bin\Lib\PlotML.pas
File ..\bin\Lib\MLDatasets.pas
+ File ..\bin\Lib\DataAdapters.pas
File ..\bin\Lib\__RedirectIOMode.vb
@@ -539,6 +543,8 @@
${AddFile} "MLPipelineABC.pas"
${AddFile} "PlotML.pas"
${AddFile} "MLDatasets.pas"
+ ${AddFile} "DataAdapters.pas"
+
${AddFile} "__RedirectIOMode.vb"
${AddFile} "VBSystem.vb"
diff --git a/ReleaseGenerators/sect_Files.nsh b/ReleaseGenerators/sect_Files.nsh
index d60126d3b..31e726169 100644
--- a/ReleaseGenerators/sect_Files.nsh
+++ b/ReleaseGenerators/sect_Files.nsh
@@ -6,6 +6,10 @@
File "Files\Databases\*.txt"
File "Files\Words\*.txt"
+ SetOutPath "$INSTDIR\Files\Datasets"
+ File "Files\Datasets\*.csv"
+ File "Files\Datasets\*.meta"
+
SetOutPath "$INSTDIR\Files\Images"
File "Files\Images\*.png"
SectionEnd
diff --git a/bin/Lib/DataAdapters.pas b/bin/Lib/DataAdapters.pas
new file mode 100644
index 000000000..525885a94
--- /dev/null
+++ b/bin/Lib/DataAdapters.pas
@@ -0,0 +1,110 @@
+unit DataAdapters;
+
+interface
+
+uses DataFrameABC;
+uses LinearAlgebraML;
+
+/// Преобразует вектор меток классов в массив целых чисел.
+/// Используется при визуализации и других задачах,
+/// где метки должны быть представлены как 0,1,2,...
+/// Значения округляются функцией Round, чтобы устранить
+/// возможные небольшие численные ошибки
+function LabelsToInts(y: Vector): array of integer;
+
+function EncodeLabels(labels: array of string): array of integer;
+
+implementation
+
+uses MLExceptions;
+
+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';
+ ER_TO_MATRIX_NON_NUMERIC =
+ 'Столбец "{0}" содержит нечисловые или NA значения!!Column "{0}" contains non-numeric or NA values';
+
+function LabelsToInts(y: Vector): array of integer;
+begin
+ if y = nil then
+ ArgumentNullError(ER_ARG_NULL, 'y');
+
+ Result := ArrGen(y.Length, i -> Round(y[i]));
+end;
+
+function EncodeLabels(labels: array of string): array of integer;
+begin
+ if labels = nil then
+ ArgumentNullError(ER_ARG_NULL, 'labels');
+
+ var map := new Dictionary;
+ var res := new integer[labels.Length];
+
+ var next := 0;
+
+ for var i := 0 to labels.Length - 1 do
+ begin
+ var s := labels[i];
+
+ if not map.ContainsKey(s) then
+ begin
+ map[s] := next;
+ next += 1;
+ end;
+
+ res[i] := map[s];
+ end;
+
+ Result := res;
+end;
+
+function ToMatrix(Self: DataFrame; colNames: array of string): Matrix; extensionmethod;
+begin
+ var df := Self;
+ var n := df.RowCount;
+ var p := colNames.Length;
+
+ if p = 0 then
+ ArgumentError(ER_TO_MATRIX_NO_COLUMNS);
+
+ Result := new Matrix(n, p);
+
+ for var j := 0 to p - 1 do
+ begin
+ var col := df[colNames[j]];
+
+ for var i := 0 to n - 1 do
+ begin
+ var value: real;
+
+ if not col.TryGetNumericValue(i, value) then
+ ArgumentError(ER_TO_MATRIX_NON_NUMERIC, colNames[j]);
+
+ Result[i,j] := value;
+ end;
+ end;
+end;
+
+function ToVector(Self: DataFrame; colName: string): Vector; extensionmethod;
+begin
+ var df := Self;
+ var n := df.RowCount;
+ Result := new Vector(n);
+
+ var col := df[colName];
+
+ for var i := 0 to n - 1 do
+ begin
+ var value: real;
+
+ if not col.TryGetNumericValue(i, value) then
+ ArgumentError(ER_TO_VECTOR_NON_NUMERIC, colName);
+
+ Result[i] := value;
+ end;
+end;
+
+end.
\ No newline at end of file
diff --git a/bin/Lib/DataFrameABC.pas b/bin/Lib/DataFrameABC.pas
index 999e7ee5c..7f40d5d16 100644
--- a/bin/Lib/DataFrameABC.pas
+++ b/bin/Lib/DataFrameABC.pas
@@ -114,9 +114,24 @@ type
/// Возвращает индекс столбца по имени
function ColumnIndex(name: string): integer;
+ function HasColumn(name: string): boolean;
+
/// Создает курсор для итерации по строкам
function GetCursor: DataFrameCursor;
+ /// Разбивает таблицу на обучающую и тестовую выборки.
+ ///
+ /// Строки случайным образом перемешиваются и делятся на две части:
+ /// train и test. Доля тестовой выборки задаётся параметром testRatio.
+ ///
+ /// testRatio = 0.2 означает, что 20% строк попадут в тестовую выборку.
+ ///
+ /// Если seed >= 0, разбиение будет детерминированным.
+ /// Если seed = -1, используется случайная инициализация генератора.
+ ///
+ /// Возвращает кортеж (trainDataFrame, testDataFrame).
+ function TrainTestSplit(testRatio: real := 0.2; seed: integer := -1): (DataFrame, DataFrame);
+
/// Добавляет столбец целых чисел
procedure AddIntColumn(name: string; data: array of integer; valid: array of boolean := nil; isCategorical: boolean := false);
/// Добавляет столбец вещественных чисел
@@ -130,6 +145,10 @@ type
function GetIntColumn(name: string): array of integer;
/// Возвращает данные вещественного столбца по имени
function GetFloatColumn(name: string): array of real;
+ /// Возвращает данные строкового столбца по имени
+ function GetStrColumn(name: string): array of string;
+ /// Возвращает данные логического столбца по имени
+ function GetBoolColumn(name: string): array of boolean;
/// Вычисляет сумму значений столбца по индексу
function Sum(colIndex: integer): real;
@@ -428,7 +447,9 @@ const
ER_CSV_INVALID_BOOL =
'Некорректное логическое значение "{0}" в столбце "{1}"!!' +
'Invalid bool "{0}" in column "{1}"';
-
+ ER_TEST_RATIO_INVALID =
+ 'Некорректное значение testRatio = {0}. Ожидается число в диапазоне (0, 1).' +
+ 'Invalid testRatio = {0}. Expected value in range (0, 1).';
type
/// Класс для группировки данных
@@ -1478,6 +1499,11 @@ begin
Error(ER_COLUMN_NOT_FOUND, name);
end;
+function DataFrame.HasColumn(name: string): boolean;
+begin
+ Result := fSchema.HasColumn(name);
+end;
+
function DataFrame.GetCursor: DataFrameCursor :=
new DataFrameCursor(columns.ToArray,fSchema);
@@ -1495,6 +1521,67 @@ begin
Result := c.Data;
end;
+function DataFrame.GetStrColumn(name: string): array of string;
+begin
+ var i := ColumnIndex(name);
+ var c := StrColumn(columns[i]);
+ Result := c.Data;
+end;
+
+function DataFrame.GetBoolColumn(name: string): array of boolean;
+begin
+ var i := ColumnIndex(name);
+ var c := BoolColumn(columns[i]);
+ Result := c.Data;
+end;
+
+function DataFrame.TrainTestSplit(testRatio: real; seed: integer): (DataFrame, DataFrame);
+begin
+ if Self = nil then
+ ArgumentNullError(ER_ARG_NULL, 'DataFrame');
+
+ if (testRatio <= 0.0) or (testRatio >= 1.0) then
+ ArgumentError(ER_TEST_RATIO_INVALID, testRatio);
+
+ var n := RowCount;
+
+ if n < 2 then
+ ArgumentError(ER_EMPTY_DATA, 'TrainTestSplit');
+
+ var actualSeed := if seed >= 0 then seed else System.Environment.TickCount and integer.MaxValue;
+ var rnd := new System.Random(actualSeed);
+
+ var idx := Arr(0..n-1);
+ idx.Shuffle(rnd);
+
+ var rawSize := Round(n * testRatio);
+ var testSize := rawSize.Clamp(1, n - 1);
+
+ // --- маркер тестовых строк
+ var isTest := new boolean[n];
+
+ for var i := 0 to testSize - 1 do
+ isTest[idx[i]] := true;
+
+ var trainDf := new DataFrame;
+ var testDf := new DataFrame;
+
+ var cur := GetCursor;
+ var row := 0;
+
+ while cur.MoveNext do
+ begin
+ if isTest[row] then
+ testDf.AppendRowFromCursor(self, cur)
+ else
+ trainDf.AppendRowFromCursor(self, cur);
+
+ row += 1;
+ end;
+
+ Result := (trainDf, testDf);
+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
@@ -1531,7 +1618,7 @@ begin
else
begin
if valid.Length <> data.Length then
- DimensionError(ER_COLUMN_VALID_LENGTH_MISMATCH);
+ DimensionError(ER_COLUMN_VALID_LENGTH_MISMATCH);
c.IsValid := valid;
end;
@@ -2835,7 +2922,7 @@ begin
end;
-procedure DataFrame.PrintPreview(maxRows: integer; headRows: integer; decimals: integer);
+{procedure DataFrame.PrintPreview(maxRows: integer; headRows: integer; decimals: integer);
begin
var ColumnSeparator := ' ';
var colCount := columns.Count;
@@ -3025,6 +3112,179 @@ begin
PABCSystem.Print(FormatValue(j) + ColumnSeparator);
PABCSystem.Println;
end;
+end;}
+
+procedure DataFrame.PrintPreview(maxRows: integer; headRows: integer; decimals: integer);
+begin
+ var colCount := columns.Count;
+ if colCount = 0 then exit;
+
+ var rowCount := RowCount;
+ if rowCount = 0 then exit;
+
+ if maxRows < 1 then exit;
+ if decimals < 0 then decimals := 0;
+
+ if rowCount <= maxRows then
+ headRows := rowCount
+ else
+ begin
+ if headRows = -1 then
+ headRows := (maxRows + 1) div 2;
+
+ if headRows < 0 then headRows := 0;
+ if headRows > maxRows then headRows := maxRows;
+ end;
+
+ var tailRows := maxRows - headRows;
+
+ if tailRows < 0 then
+ tailRows := 0;
+
+ if tailRows > rowCount - headRows then
+ tailRows := rowCount - headRows;
+
+ var widths := new integer[colCount];
+
+ // --- начальная ширина = длина заголовка ---
+ for var j := 0 to colCount - 1 do
+ begin
+ widths[j] := columns[j].Info.Name.Length;
+ if widths[j] < 2 then
+ widths[j] := 2;
+ end;
+
+ var cursor := GetCursor;
+
+ // --- scan head ---
+ for var i := 0 to headRows - 1 do
+ begin
+ cursor.MoveTo(i);
+
+ for var j := 0 to colCount - 1 do
+ begin
+ var s: string;
+
+ if not cursor.IsValid(j) then
+ s := 'NA'
+ else
+ case columns[j].Info.ColType of
+ ctInt: s := cursor.Int(j).ToString;
+ ctFloat: s := cursor.Float(j).ToString('F' + decimals);
+ ctStr: s := cursor.Str(j);
+ ctBool: s := cursor.Bool(j).ToString;
+ end;
+
+ if s.Length > widths[j] then
+ widths[j] := s.Length;
+ end;
+ end;
+
+ // --- scan tail ---
+ if rowCount > headRows then
+ for var i := rowCount - tailRows to rowCount - 1 do
+ if i >= headRows then
+ begin
+ cursor.MoveTo(i);
+
+ for var j := 0 to colCount - 1 do
+ begin
+ var s: string;
+
+ if not cursor.IsValid(j) then
+ s := 'NA'
+ else
+ case columns[j].Info.ColType of
+ ctInt: s := cursor.Int(j).ToString;
+ ctFloat: s := cursor.Float(j).ToString('F' + decimals);
+ ctStr: s := cursor.Str(j);
+ ctBool: s := cursor.Bool(j).ToString;
+ end;
+
+ if s.Length > widths[j] then
+ widths[j] := s.Length;
+ end;
+ end;
+
+ // --- header ---
+ for var j := 0 to colCount - 1 do
+ begin
+ PABCSystem.Print(columns[j].Info.Name.PadLeft(widths[j]));
+ if j < colCount - 1 then
+ Write(' ');
+ end;
+
+ PABCSystem.Println;
+
+ // --- head rows ---
+ for var i := 0 to headRows - 1 do
+ begin
+ cursor.MoveTo(i);
+
+ for var j := 0 to colCount - 1 do
+ begin
+ var s: string;
+
+ if not cursor.IsValid(j) then
+ s := 'NA'
+ else
+ case columns[j].Info.ColType of
+ ctInt: s := cursor.Int(j).ToString;
+ ctFloat: s := cursor.Float(j).ToString('F' + decimals);
+ ctStr: s := cursor.Str(j);
+ ctBool: s := cursor.Bool(j).ToString;
+ end;
+
+ PABCSystem.Print(s.PadLeft(widths[j]));
+
+ if j < colCount - 1 then
+ Write(' ');
+ end;
+
+ PABCSystem.Println;
+ end;
+
+ // --- ellipsis ---
+ if headRows + tailRows < rowCount then
+ begin
+ for var j := 0 to colCount - 1 do
+ begin
+ PABCSystem.Print($'…'.PadLeft(widths[j]));
+ if j < colCount - 1 then
+ Write(' ');
+ end;
+
+ PABCSystem.Println;
+ end;
+
+ // --- tail rows ---
+ for var i := rowCount - tailRows to rowCount - 1 do
+ if i >= headRows then
+ begin
+ cursor.MoveTo(i);
+
+ for var j := 0 to colCount - 1 do
+ begin
+ var s: string;
+
+ if not cursor.IsValid(j) then
+ s := 'NA'
+ else
+ case columns[j].Info.ColType of
+ ctInt: s := cursor.Int(j).ToString;
+ ctFloat: s := cursor.Float(j).ToString('F' + decimals);
+ ctStr: s := cursor.Str(j);
+ ctBool: s := cursor.Bool(j).ToString;
+ end;
+
+ PABCSystem.Print(s.PadLeft(widths[j]));
+
+ if j < colCount - 1 then
+ Write(' ');
+ end;
+
+ PABCSystem.Println;
+ end;
end;
procedure DataFrame.PrintlnPreview(maxRows: integer; headRows: integer; decimals: integer);
@@ -3035,7 +3295,7 @@ end;
procedure DataFrame.Print(decimals: integer);
begin
- PrintPreview(20, 10, decimals);
+ PrintPreview(10, 5, decimals);
end;
procedure DataFrame.Println(decimals: integer);
@@ -3934,7 +4194,6 @@ end;
// CSVLoader
//-----------------------------
-
{procedure ScanFields(line: string; delimiter: char;
starts, lens: array of integer; var actualCount: integer);
begin
diff --git a/bin/Lib/MLABC.pas b/bin/Lib/MLABC.pas
index f8fddbf6f..8cfd25551 100644
--- a/bin/Lib/MLABC.pas
+++ b/bin/Lib/MLABC.pas
@@ -15,6 +15,7 @@ uses MLExceptions;
uses InspectionML;
uses MLPipelineABC;
uses MLDatasets;
+uses DataAdapters;
type
Vector = LinearAlgebraML.Vector;
@@ -79,21 +80,17 @@ type
Datasets = MLDatasets.Datasets;
-/// Преобразует вектор меток классов в массив целых чисел.
-/// Используется при визуализации и других задачах,
-/// где метки должны быть представлены как 0,1,2,...
-/// Значения округляются функцией Round, чтобы устранить
-/// возможные небольшие численные ошибки
-function LabelsToInts(y: Vector): array of integer;
+ function LabelsToInts(y: Vector): array of integer;
+ function EncodeLabels(labels: array of string): array of integer;
+
implementation
-
+
function LabelsToInts(y: Vector): array of integer;
begin
- if y = nil then
- ArgumentNullError(ER_ARG_NULL, 'y');
-
- Result := ArrGen(y.Length, i -> Round(y[i]));
+ Result := DataAdapters.LabelsToInts(y);
end;
-
+
+function EncodeLabels(labels: array of string): array of integer := DataAdapters.EncodeLabels(labels);
+
end.
\ No newline at end of file
diff --git a/bin/Lib/MLDatasets.pas b/bin/Lib/MLDatasets.pas
index 301f5546f..e32767a9c 100644
--- a/bin/Lib/MLDatasets.pas
+++ b/bin/Lib/MLDatasets.pas
@@ -5,6 +5,56 @@ interface
uses DataFrameABC, LinearAlgebraML;
type
+ TaskType = (Classification, Regression, Clustering);
+
+ /// ML-датасет: таблица данных вместе с метаданными задачи.
+ ///
+ /// Dataset содержит:
+ /// Data — таблицу данных (DataFrame)
+ /// Features — список признаков
+ /// Target — целевую переменную (если есть)
+ /// Task — тип задачи (classification/regression/clustering)
+ ///
+ /// Предоставляет удобные методы для получения матриц признаков
+ /// и целевых значений для обучения моделей
+ Dataset = class
+ public
+ Name: string;
+ Data: DataFrame;
+ Features: array of string;
+ Target: string;
+ Task: TaskType;
+
+ /// Возвращает матрицу признаков X.
+ /// Используются столбцы, указанные в Features.
+ function ToX: Matrix;
+
+ /// Возвращает вектор целевой переменной y.
+ /// Доступно только для задач с учителем.
+ function ToY: Vector;
+
+ /// Возвращает пару (X, y) для обучения модели.
+ function ToXY: (Matrix, Vector);
+
+ /// Возвращает (X, y), где y преобразован в целочисленные метки классов.
+ /// Используется для задач классификации.
+ function ToXYInt: (Matrix, array of integer);
+
+ /// Возвращает true, если датасет относится к задаче с учителем
+ /// (classification или regression).
+ function IsSupervised: boolean;
+
+ /// Разбивает датасет на обучающую и тестовую части.
+ /// testRatio — доля тестовой выборки.
+ function TrainTestSplit(testRatio: real := 0.2; seed: integer := -1): (Dataset, Dataset);
+
+ /// Возвращает первые n строк таблицы данных.
+ function Head(n: integer := 5): DataFrame;
+
+ /// Возвращает краткое описание датасета (метаданные).
+ function Describe: DataFrame;
+ end;
+
/// Набор генераторов и загрузчиков датасетов для задач машинного обучения.
/// Содержит синтетические генераторы (MakeBlobs, MakeMoons, MakeRegression)
/// и реальные учебные датасеты (например, RussianHousing, StudentExam).
@@ -78,7 +128,7 @@ type
/// • noise — стандартное отклонение гауссовского шума, добавляемого к координатам.
/// • factor — отношение радиуса внутреннего круга к внешнему (0 < factor < 1).
/// • shuffle — перемешивать ли порядок объектов.
- /// • seed — значение генератора случайных чисел (-1 означает использовать текущее время).
+ /// • seed — значение генератора случайных чисел (-1 означает использовать текущее время)
static function MakeCircles(
n: integer := 300;
noise: real := 0.05;
@@ -96,7 +146,7 @@ type
/// • turns — число оборотов спирали.
/// • radius — максимальный радиус спирали.
/// • shuffle — перемешивать ли порядок объектов.
- /// • seed — значение генератора случайных чисел (-1 означает использовать текущее время).
+ /// • seed — значение генератора случайных чисел (-1 означает использовать текущее время)
static function MakeSpiral(
n: integer := 300;
classes: integer := 2;
@@ -106,31 +156,67 @@ type
shuffle: boolean := true;
seed: integer := -1): (Matrix, Vector);
+ /// Загружает датасет по имени.
+ ///
+ /// Датасет ищется в каталоге:
+ /// Files\Datasets
+ ///
+ /// Для датасета должны существовать файлы:
+ /// name.meta — метаданные датасета
+ /// name.csv — данные таблицы
+ ///
+ /// Метаданные определяют:
+ /// тип задачи (classification / regression / clustering)
+ /// целевую переменную
+ /// список признаков (опционально)
+ ///
+ /// Если признаки не указаны в .meta, используются
+ /// все столбцы таблицы, кроме целевой переменной.
+ ///
+ /// Пример:
+ /// var ds := Datasets.Load('Iris');
+ static function Load(name: string): Dataset;
+
// --- DataFrame датасеты (реалистичные таблицы, считываемые из csv)
+ /// Датасет Iris (классификация).
+ ///
+ /// 150 объектов, 4 признака:
+ /// sepal_length, sepal_width, petal_length, petal_width.
+ /// Целевая переменная: species.
+ ///
+ /// Пример:
+ /// var ds := Datasets.Iris;
+ /// var (X,y) := ds.ToXYInt;
+ static function Iris: Dataset;
+
/// Датасет цен на квартиры (задача регрессии)
- static function RussianHousing: DataFrame;
+ static function RussianHousing: Dataset;
/// Датасет результатов экзамена студентов (классификация)
- static function StudentExam: DataFrame;
+ static function StudentExam: Dataset;
/// Датасет банковских клиентов (классификация одобрения кредита)
- static function BankClients: DataFrame;
+ static function BankClients: Dataset;
/// Датасет поездок такси (регрессия стоимости поездки)
- static function TaxiTrips: DataFrame;
+ static function TaxiTrips: Dataset;
/// Датасет транспортной активности пассажиров (кластеризация)
- static function MoscowTransport: DataFrame;
+ static function MoscowTransport: Dataset;
/// Датасет интернет-покупок пользователей (классификация покупки)
- static function OnlineShopping: DataFrame;
-
+ static function OnlineShopping: Dataset;
+
+ static function LoadMeta(path: string): Dictionary;
+ static function ParseFeatures(meta: Dictionary): array of string;
end;
+
implementation
uses MLExceptions;
+uses DataAdapters;
const
ER_PARAM_GT_ZERO =
@@ -141,7 +227,25 @@ const
'Параметр {0} должен быть >= 0!!Parameter {0} must be >= 0';
ER_PARAM_BETWEEN_01 =
'Параметр {0} должен быть в диапазоне (0,1)!!Parameter {0} must be in range (0,1)';
-
+ ER_DATASET_NO_TARGET =
+ 'У датасета нет целевой переменной (задача кластеризации).' +
+ 'Dataset has no target variable (clustering task).';
+ ER_DATASET_TARGET_NOT_FOUND =
+ 'Целевая переменная "{0}" не найдена в таблице.' +
+ 'Target column "{0}" not found in DataFrame';
+ ER_DATASET_META_NOT_FOUND =
+ 'Файл метаданных датасета "{0}" не найден!!Dataset meta file "{0}" not found';
+ ER_DATASET_CSV_NOT_FOUND =
+ 'Файл данных датасета "{0}" не найден!!Dataset csv file "{0}" not found';
+ ER_DATASET_TASK_UNKNOWN =
+ 'Неизвестный тип задачи датасета "{0}"!!Unknown dataset task "{0}"';
+ ER_DATASET_TASK_MISSING =
+ 'В метаданных датасета "{0}" отсутствует поле task!!Dataset meta missing field "task" for "{0}"';
+ ER_DATASET_FEATURE_NOT_FOUND =
+ 'Признак "{0}" не найден в датасете!!Feature column "{0}" not found in dataset';
+ ER_DATASET_FEATURE_EQUALS_TARGET =
+ 'Признак "{0}" совпадает с целевой переменной!!Feature "{0}" equals target column';
+
function Normal(rnd: System.Random): real;
begin
var u1 := rnd.NextDouble;
@@ -149,15 +253,116 @@ begin
Result := Sqrt(-2 * Ln(u1)) * Cos(2 * Pi * u2);
end;
-/// Генерирует синтетический датасет из нескольких гауссовых кластеров.
-/// Возвращает матрицу признаков X и вектор меток кластеров y.
-///
-/// Параметры:
-/// • n — количество объектов (точек)
-/// • centers — число кластеров
-/// • clusterStd — стандартное отклонение точек внутри кластера
-/// • nFeatures — число признаков (размерность пространства)
-/// • seed — значение генератора случайных чисел (seed < 0 → случайный)
+//-----------------------------
+// Dataset
+//-----------------------------
+
+function Dataset.ToX(): Matrix;
+begin
+ Result := Data.ToMatrix(Features);
+end;
+
+function Dataset.ToY(): Vector;
+begin
+ if Task = Clustering then
+ ArgumentError(ER_DATASET_NO_TARGET);
+
+ Result := Data.ToVector(Target);
+end;
+
+function Dataset.ToXY(): (Matrix, Vector);
+begin
+ Result := (ToX, ToY);
+end;
+
+function Dataset.ToXYInt: (Matrix, array of integer);
+begin
+ if Task <> Classification then
+ ArgumentError(ER_DATASET_NO_TARGET);
+
+ var X := ToX;
+
+ var labels := Data.GetStrColumn(Target);
+ var y := EncodeLabels(labels);
+
+ Result := (X, y);
+end;
+
+function Dataset.IsSupervised: boolean;
+begin
+ Result := Task <> Clustering;
+end;
+
+function Dataset.TrainTestSplit(testRatio: real; seed: integer): (Dataset, Dataset);
+begin
+ if Self = nil then
+ ArgumentNullError(ER_ARG_NULL, 'Dataset');
+
+ var (trainDf, testDf) := Data.TrainTestSplit(testRatio, seed);
+
+ var trainDs := new Dataset;
+ trainDs.Name := Name;
+ trainDs.Data := trainDf;
+ trainDs.Features := Features;
+ trainDs.Target := Target;
+ trainDs.Task := Task;
+
+ var testDs := new Dataset;
+ testDs.Name := Name;
+ testDs.Data := testDf;
+ testDs.Features := Features;
+ testDs.Target := Target;
+ testDs.Task := Task;
+
+ Result := (trainDs, testDs);
+end;
+
+function Dataset.Head(n: integer): DataFrame;
+begin
+ if Data = nil then
+ ArgumentNullError(ER_ARG_NULL, 'Data');
+
+ Result := Data.Head(n);
+end;
+
+function Dataset.Describe: DataFrame;
+begin
+ if Data = nil then
+ ArgumentNullError(ER_ARG_NULL, 'Data');
+
+ var props := Arr(
+ 'Name',
+ 'Rows',
+ 'Columns',
+ 'Task',
+ 'Target',
+ 'FeatureCount',
+ 'Features'
+ );
+
+ var values := Arr(
+ Name,
+ Data.RowCount.ToString,
+ Data.ColumnCount.ToString,
+ Task.ToString,
+ Target,
+ Features.Length.ToString,
+ Features.JoinToString(', ')
+ );
+
+ var valid := ArrFill(props.Length, true);
+
+ var df := new DataFrame;
+ df.AddStrColumn('Property', props, valid, true);
+ df.AddStrColumn('Value', values, valid, false);
+
+ Result := df;
+end;
+
+//-----------------------------
+// Datasets
+//-----------------------------
+
static function Datasets.MakeBlobs(
n: integer; centers: integer;
clusterStd: real; nFeatures: integer;
@@ -454,35 +659,144 @@ begin
Result := (X, y);
end;
-static function Datasets.RussianHousing: DataFrame;
+function ParseTask(s: string): TaskType;
+begin
+ case s.ToLower of
+ 'classification': Result := Classification;
+ 'regression': Result := Regression;
+ 'clustering': Result := Clustering;
+ else
+ ArgumentError(ER_DATASET_TASK_UNKNOWN, s);
+ end;
+end;
+
+static function Datasets.Load(name: string): Dataset;
+begin
+ if name = nil then
+ ArgumentNullError(ER_ARG_NULL, 'name');
+
+ var baseDir := PascalABCDirectory + 'Files\Datasets\';
+
+ var metaPath := baseDir + name + '.meta';
+ var csvPath := baseDir + name + '.csv';
+
+ if not FileExists(metaPath) then
+ ArgumentError(ER_DATASET_META_NOT_FOUND, name);
+
+ if not FileExists(csvPath) then
+ ArgumentError(ER_DATASET_CSV_NOT_FOUND, name);
+
+ var meta := LoadMeta(metaPath);
+
+ if not meta.ContainsKey('task') then
+ ArgumentError(ER_DATASET_TASK_MISSING, name);
+
+ var df := DataFrame.FromCsv(csvPath);
+
+ var ds := new Dataset;
+
+ ds.Name := name;
+ ds.Data := df;
+
+ ds.Task := ParseTask(meta['task']);
+ ds.Target := meta['target'];
+ ds.Features := ParseFeatures(meta);
+
+ // --- проверка target
+ if (ds.Target <> nil) and not df.HasColumn(ds.Target) then
+ ArgumentError(ER_DATASET_TARGET_NOT_FOUND, ds.Target);
+
+ // --- проверка features
+ foreach var f in ds.Features do
+ begin
+ if not df.HasColumn(f) then
+ ArgumentError(ER_DATASET_FEATURE_NOT_FOUND, f);
+
+ if (ds.Target <> nil) and (f = ds.Target) then
+ ArgumentError(ER_DATASET_FEATURE_EQUALS_TARGET, f);
+ end;
+ Result := ds;
+end;
+
+static function Datasets.Iris: Dataset;
+begin
+ Result := Load('Iris');
+end;
+
+static function Datasets.RussianHousing: Dataset;
begin
Result := nil;
end;
-static function Datasets.StudentExam: DataFrame;
+static function Datasets.StudentExam: Dataset;
begin
Result := nil;
end;
-static function Datasets.BankClients: DataFrame;
+static function Datasets.BankClients: Dataset;
begin
Result := nil;
end;
-static function Datasets.TaxiTrips: DataFrame;
+static function Datasets.TaxiTrips: Dataset;
begin
Result := nil;
end;
-static function Datasets.MoscowTransport: DataFrame;
+static function Datasets.MoscowTransport: Dataset;
begin
Result := nil;
end;
-static function Datasets.OnlineShopping: DataFrame;
+static function Datasets.OnlineShopping: Dataset;
begin
Result := nil;
end;
+static function Datasets.LoadMeta(path: string): Dictionary;
+begin
+ if path = nil then
+ ArgumentNullError(ER_ARG_NULL, 'path');
+
+ if not FileExists(path) then
+ ArgumentError(ER_DATASET_META_NOT_FOUND, path);
+
+ var dict := new Dictionary;
+
+ foreach var line in ReadLines(path) do
+ begin
+ var s := line.Trim;
+
+ if s = '' then
+ continue;
+
+ if s.StartsWith('#') then
+ continue;
+
+ var p := s.IndexOf('=');
+
+ if p < 0 then
+ continue;
+
+ var key := s.Substring(0,p).Trim;
+ var val := s.Substring(p+1).Trim;
+
+ dict[key] := val;
+ end;
+
+ Result := dict;
+end;
+
+static function Datasets.ParseFeatures(meta: Dictionary): array of string;
+begin
+ if not meta.ContainsKey('features') then
+ exit(nil);
+
+ Result :=
+ meta['features']
+ .Split(',')
+ .Select(s -> s.Trim)
+ .ToArray;
+end;
end.
\ No newline at end of file
diff --git a/bin/Lib/MLPipelineABC.pas b/bin/Lib/MLPipelineABC.pas
index 32f3103c4..d3050e1eb 100644
--- a/bin/Lib/MLPipelineABC.pas
+++ b/bin/Lib/MLPipelineABC.pas
@@ -161,6 +161,7 @@ type
implementation
uses MLExceptions;
+uses DataAdapters;
const
ER_PIPELINE_MODIFY_AFTER_FIT =
@@ -202,52 +203,6 @@ const
ER_DATAPIPE_DUPLICATE_FEATURE =
'Повторяющийся признак: {0}!!Duplicate feature: {0}';
-function ToMatrix(Self: DataFrame; colNames: array of string): Matrix; extensionmethod;
-begin
- var df := Self;
- var n := df.RowCount;
- var p := colNames.Length;
-
- if p = 0 then
- ArgumentError(ER_TO_MATRIX_NO_COLUMNS);
-
- Result := new Matrix(n, p);
-
- for var j := 0 to p - 1 do
- begin
- var col := df[colNames[j]];
-
- for var i := 0 to n - 1 do
- begin
- var value: real;
-
- if not col.TryGetNumericValue(i, value) then
- ArgumentError(ER_TO_MATRIX_NON_NUMERIC, colNames[j]);
-
- Result[i,j] := value;
- end;
- end;
-end;
-
-function ToVector(Self: DataFrame; colName: string): Vector; extensionmethod;
-begin
- var df := Self;
- var n := df.RowCount;
- Result := new Vector(n);
-
- var col := df[colName];
-
- for var i := 0 to n - 1 do
- begin
- var value: real;
-
- if not col.TryGetNumericValue(i, value) then
- ArgumentError(ER_TO_VECTOR_NON_NUMERIC, colName);
-
- Result[i] := value;
- end;
-end;
-
//-----------------------------
// DataPipeline
//-----------------------------