pascalabcnet/bin/Lib/DataAdapters.pas

387 lines
14 KiB
ObjectPascal
Raw Normal View History

unit DataAdapters;
interface
type
/// Результат кодирования целевого столбца:
/// целочисленные метки и соответствующие имена классов.
EncodedTarget = record
Labels: array of integer;
ClassNames: array of string;
/// Возвращает код класса для строки с индексом rowIndex.
function ClassIndex(rowIndex: integer): integer;
/// Возвращает имя класса для строки с индексом rowIndex.
function ClassName(rowIndex: integer): string;
end;
implementation
uses MLUtilsABC;
uses DataFrameABC;
uses MLExceptions;
uses LinearAlgebraML;
const
ER_TO_MATRIX_NO_COLUMNS =
'ToMatrix: не указаны столбцы!!ToMatrix: no columns specified';
ER_TO_MATRIX_NON_NUMERIC =
'Столбец "{0}" содержит нечисловые или NA значения!!Column "{0}" contains non-numeric or NA values';
ER_ENCODELABELS_COLUMN_NOT_STRING =
'Столбец {0} должен быть строковым для EncodeLabels!!Column {0} must be string for EncodeLabels';
ER_ENCODELABELS_NOT_CATEGORICAL =
'Столбец "{0}" должен быть категориальным для EncodeLabels!!Column "{0}" must be categorical for EncodeLabels';
ER_ENCODELABELS_UNSUPPORTED_TYPE =
'Неподдерживаемый тип столбца "{0}" для EncodeLabels!!Unsupported column type "{0}" for EncodeLabels';
ER_TARGET_HAS_NA =
'Целевой столбец "{0}" содержит NA!!Target column "{0}" contains NA values';
ER_ENCODED_TARGET_ROW_INDEX_OUT_OF_RANGE =
'Индекс строки {0} вне диапазона [0, {1})!!Row index {0} is out of range [0, {1})';
ER_ENCODED_TARGET_CLASS_INDEX_OUT_OF_RANGE =
'Индекс класса {0} вне диапазона [0, {1})!!Class index {0} is out of range [0, {1})';
function EncodedTarget.ClassIndex(rowIndex: integer): integer;
begin
if (rowIndex < 0) or (rowIndex >= Labels.Length) then
ArgumentOutOfRangeError(ER_ENCODED_TARGET_ROW_INDEX_OUT_OF_RANGE, rowIndex, Labels.Length);
Result := Labels[rowIndex];
end;
function EncodedTarget.ClassName(rowIndex: integer): string;
begin
var cls := ClassIndex(rowIndex);
if (cls < 0) or (cls >= ClassNames.Length) then
ArgumentOutOfRangeError(ER_ENCODED_TARGET_CLASS_INDEX_OUT_OF_RANGE, cls, ClassNames.Length);
Result := ClassNames[cls];
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]];
case col.Info.ColType of
ColumnType.ctInt:
begin
var c := IntColumn(col);
for var i := 0 to n - 1 do
begin
if not c.IsValid[i] then
ArgumentError(ER_TO_MATRIX_NON_NUMERIC, colNames[j]);
Result[i,j] := c.Data[i];
end;
end;
ColumnType.ctFloat:
begin
var c := FloatColumn(col);
for var i := 0 to n - 1 do
begin
if not c.IsValid[i] then
ArgumentError(ER_TO_MATRIX_NON_NUMERIC, colNames[j]);
Result[i,j] := c.Data[i];
end;
end;
ColumnType.ctBool:
begin
var c := BoolColumn(col);
for var i := 0 to n - 1 do
begin
if not c.IsValid[i] then
ArgumentError(ER_TO_MATRIX_NON_NUMERIC, colNames[j]);
Result[i,j] := if c.Data[i] then 1.0 else 0.0;
end;
end;
else
ArgumentError(ER_TO_MATRIX_NON_NUMERIC, colNames[j]);
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];
case col.Info.ColType of
ColumnType.ctInt:
begin
var c := IntColumn(col);
for var i := 0 to n - 1 do
begin
if not c.IsValid[i] then
ArgumentError(ER_TO_VECTOR_NON_NUMERIC, colName);
Result[i] := c.Data[i];
end;
end;
ColumnType.ctFloat:
begin
var c := FloatColumn(col);
for var i := 0 to n - 1 do
begin
if not c.IsValid[i] then
ArgumentError(ER_TO_VECTOR_NON_NUMERIC, colName);
Result[i] := c.Data[i];
end;
end;
ColumnType.ctBool:
begin
var c := BoolColumn(col);
for var i := 0 to n - 1 do
begin
if not c.IsValid[i] then
ArgumentError(ER_TO_VECTOR_NON_NUMERIC, colName);
Result[i] := if c.Data[i] then 1.0 else 0.0;
end;
end;
else
ArgumentError(ER_TO_VECTOR_NON_NUMERIC, colName);
end;
end;
/// Кодирует строковые метки классов в целочисленные индексы (0..K-1).
/// Порядок кодирования соответствует порядку первого появления меток.
/// Используйте только при ручной подготовке данных (без DataPipeline).
/// Вызывать только ДО разбиения на train/test, чтобы избежать рассинхронизации меток.
function EncodeLabels(Self: DataFrame; target: string): array of integer; extensionmethod;
begin
if Self = nil then
ArgumentNullError(ER_ARG_NULL, 'Self');
if target = nil then
ArgumentNullError(ER_ARG_NULL, 'target');
if not Self.HasColumn(target) then
ArgumentError(ER_COLUMN_NOT_FOUND, target);
var col := Self[target];
for var i := 0 to Self.RowCount - 1 do
if not col.IsValid[i] then
ArgumentError(ER_TARGET_HAS_NA, target);
if Self.GetColumnType(target) = ColumnType.ctStr then
begin
var labels := Self.GetStrColumn(target);
Result := EncodeLabels(labels);
exit;
end;
if Self.GetColumnType(target) = ColumnType.ctInt then
begin
var labels := Self.GetIntColumn(target).ToArray;
var classes: array of integer;
Result := EncodeLabelsInt(labels,classes);
exit;
end;
if Self.GetColumnType(target) = ColumnType.ctBool then
begin
var labels := Self.GetBoolColumn(target).ToArray.Select(x -> x.ToString).ToArray;
Result := EncodeLabels(labels);
exit;
end;
ArgumentError(ER_ENCODELABELS_UNSUPPORTED_TYPE, target);
end;
/// Кодирует значения категориального столбца DataFrame в целочисленные индексы.
/// Каждому уникальному значению присваивается номер 0,1,2,...
/// Порядок кодирования соответствует порядку первого появления значений в столбце.
/// В параметр classes возвращается массив уникальных значений в порядке кодирования.
/// Работает для столбцов типов string, integer и boolean.
/// Для целочисленных категориальных столбцов значения преобразуются в строки.
/// Используется при подготовке данных для задач классификации и визуализации.
function EncodeLabels(Self: DataFrame; target: string; var classes: array of string): array of integer; extensionmethod;
begin
if Self = nil then
ArgumentNullError(ER_ARG_NULL, 'Self');
if target = nil then
ArgumentNullError(ER_ARG_NULL, 'target');
if not Self.HasColumn(target) then
ArgumentError(ER_COLUMN_NOT_FOUND, target);
var col := Self[target];
for var i := 0 to Self.RowCount - 1 do
if not col.IsValid[i] then
ArgumentError(ER_TARGET_HAS_NA, target);
case Self.GetColumnType(target) of
ColumnType.ctStr:
begin
var labels := Self.GetStrColumn(target).ToArray;
Result := EncodeLabels(labels, classes);
end;
ColumnType.ctInt:
begin
var labels := Self.GetIntColumn(target).ToArray;
var intClasses: array of integer;
Result := EncodeLabelsInt(labels, intClasses);
// если API требует string classes:
classes := intClasses.Select(x -> x.ToString).ToArray;
end;
ColumnType.ctBool:
begin
var labels := Self.GetBoolColumn(target).ToArray.Select(x -> x.ToString).ToArray;
Result := EncodeLabels(labels, classes);
end;
else
ArgumentError(ER_ENCODELABELS_UNSUPPORTED_TYPE, target);
end;
end;
/// Кодирует целевой столбец DataFrame и возвращает
/// целочисленные метки вместе с массивом имён классов.
/// Порядок кодирования соответствует порядку первого появления значений.
function EncodeTarget(Self: DataFrame; target: string): EncodedTarget; extensionmethod;
begin
var classes: array of string;
Result.Labels := Self.EncodeLabels(target, classes);
Result.ClassNames := classes;
end;
/// Преобразует строковые метки целевого столбца в целочисленные индексы (0,1,2,...)
/// с использованием заданного массива classes (mapping индекс → метка).
/// classes должен быть получен ранее с помощью EncodeLabels.
/// При обнаружении неизвестного значения выбрасывается исключение.
/// Используется для применения кодирования к тестовым данным (Transform).
function TransformLabels(Self: DataFrame; target: string; classes: array of string): array of integer; extensionmethod;
begin
if Self = nil then
ArgumentNullError(ER_ARG_NULL, 'Self');
if target = nil then
ArgumentNullError(ER_ARG_NULL, 'target');
if classes = nil then
ArgumentNullError(ER_ARG_NULL, 'classes');
if not Self.HasColumn(target) then
ArgumentError(ER_COLUMN_NOT_FOUND, target);
var col := Self[target];
for var i := 0 to Self.RowCount - 1 do
if not col.IsValid[i] then
ArgumentError(ER_TARGET_HAS_NA, target);
case Self.GetColumnType(target) of
ColumnType.ctStr:
begin
var data := Self.GetStrColumn(target).ToArray;
2026-04-19 21:07:00 +03:00
Result := TransformLabels(data, classes);
end;
ColumnType.ctInt:
begin
var data := Self.GetIntColumn(target).ToArray;
2026-04-19 21:07:00 +03:00
var strData := new string[data.Length];
for var i := 0 to data.Length - 1 do
strData[i] := data[i].ToString;
Result := TransformLabels(strData, classes);
end;
ColumnType.ctBool:
begin
var data := Self.GetBoolColumn(target).ToArray;
var strData := new string[data.Length];
for var i := 0 to data.Length - 1 do
2026-04-19 21:07:00 +03:00
strData[i] := data[i].ToString;
Result := TransformLabels(strData, classes);
end;
else
ArgumentError(ER_ENCODELABELS_UNSUPPORTED_TYPE, target);
end;
end;
/// Преобразует значения целочисленного категориального столбца
/// в плотные индексы (0,1,2,...) с использованием заданного массива classes.
/// classes должен быть получен ранее с помощью EncodeLabelsInt.
/// При обнаружении неизвестного значения выбрасывается исключение.
function TransformLabelsInt(Self: DataFrame; target: string; classes: array of integer): array of integer; extensionmethod;
begin
if Self = nil then
ArgumentNullError(ER_ARG_NULL, 'Self');
if target = nil then
ArgumentNullError(ER_ARG_NULL, 'target');
if classes = nil then
ArgumentNullError(ER_ARG_NULL, 'classes');
if not Self.HasColumn(target) then
ArgumentError(ER_COLUMN_NOT_FOUND, target);
if not Self.IsCategorical(target) then
ArgumentError(ER_ENCODELABELS_NOT_CATEGORICAL, target);
if Self.GetColumnType(target) <> ColumnType.ctInt then
ArgumentError(ER_ENCODELABELS_UNSUPPORTED_TYPE, target);
var col := Self[target];
for var i := 0 to Self.RowCount - 1 do
if not col.IsValid[i] then
ArgumentError(ER_TARGET_HAS_NA, target);
var data := Self.GetIntColumn(target).ToArray;
2026-04-19 21:07:00 +03:00
Result := TransformLabelsInt(data, classes);
end;
/// Кодирует значения целочисленного категориального столбца DataFrame
/// в плотные целочисленные индексы 0,1,2,...
/// Каждому уникальному значению присваивается номер в порядке первого появления.
/// В параметр classes возвращается массив уникальных значений в порядке кодирования.
/// Работает только для категориальных столбцов типа integer.
/// Используется при подготовке данных для задач классификации и анализа.
function EncodeLabelsInt(Self: DataFrame; target: string; var classes: array of integer): array of integer; extensionmethod;
begin
if Self = nil then
ArgumentNullError(ER_ARG_NULL, 'Self');
if target = nil then
ArgumentNullError(ER_ARG_NULL, 'target');
if not Self.HasColumn(target) then
ArgumentError(ER_COLUMN_NOT_FOUND, target);
if not Self.IsCategorical(target) then
ArgumentError(ER_ENCODELABELS_NOT_CATEGORICAL, target);
if Self.GetColumnType(target) <> ColumnType.ctInt then
ArgumentError(ER_ENCODELABELS_UNSUPPORTED_TYPE, target);
var col := Self[target];
for var i := 0 to Self.RowCount - 1 do
if not col.IsValid[i] then
ArgumentError(ER_TARGET_HAS_NA, target);
var labels := Self.GetIntColumn(target).ToArray;
Result := EncodeLabelsInt(labels, classes);
end;
2026-04-27 09:34:44 +03:00
end.