Новые ML примеры и тесты - в основном в DataFrame

EncodeLabels - убрана проверка категориальности, добавлен тип Bool
В DataFrame добавлен тип DateTime
Добавлен тип DataValue позволяющий писать df.Filter(r -> r['Col'] > 5)
This commit is contained in:
Mikhalkovich Stanislav 2026-06-19 22:06:08 +03:00
parent d940541c65
commit 828484f15a
47 changed files with 3910 additions and 54 deletions

View file

@ -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 = "3830";
public const string Revision = "3833";
public const string MainVersion = Major + "." + Minor;
public const string FullVersion = Major + "." + Minor + "." + Build + "." + Revision;

View file

@ -1,4 +1,4 @@
%COREVERSION%=1
%REVISION%=3830
%MINOR%=11
%REVISION%=3833
%COREVERSION%=1
%MAJOR%=3

View file

@ -9,5 +9,5 @@ begin
Charlie,21,78
''');
df.Filter(row -> row.Int('age') >= 21).Print;
df.Filter(row -> row['age'] >= 21).Print;
end.

View file

@ -0,0 +1,31 @@
uses MLABC;
begin
var text := '''
id,created_at,name
1,2024-01-15,Alice
2,2024-01-16 12:30:00,Bob
''';
var schema := new Dictionary<string, ColumnType>;
schema['created_at'] := ColumnType.ctDateTime;
var df1 := CsvLoader.LoadFromLines(
text.ToLines,
schema := schema
);
Println('Явная схема:');
df1.Print;
Println(df1.DateTime('created_at')[0].Year);
var df2 := CsvLoader.LoadFromLines(
text.ToLines,
inferTypes := True
);
Println;
Println('Автоопределение:');
df2.Print(dateTimeFormat := 'dd.MM.yy');
Println(df2.GetColumnType('created_at'));
end.

View file

@ -0,0 +1,20 @@
uses MLABC;
begin
var text := '''
id,created_at,name
1,15.01.2024,Alice
2,16.01.2024 12:30:00,Bob
''';
var df := CsvLoader.LoadFromLines(
text.ToLines,
inferTypes := True
);
df.Print;
Println;
Println(df.GetColumnType('created_at'));
Println(df.DateTime('created_at')[0].Year);
Println(df.DateTime('created_at')[1].Hour);
end.

View file

@ -0,0 +1,23 @@
uses MLABC;
begin
var text := '''
id,created_at,name
1,16.01.2024 12:30:00,Bob
2,15.01.2024,Alice
3,17.01.2024 09:15:00,Charlie
''';
var df := CsvLoader.LoadFromLines(
text.ToLines,
inferTypes := True
);
var sorted := df.SortBy('created_at');
Println('Исходный DataFrame:');
df.Print;
Println;
Println('После SortBy(created_at):');
sorted.Print;
end.

View file

@ -0,0 +1,23 @@
uses MLABC;
begin
var text := '''
id,created_at,name
1,15.01.2024,Alice
2,16.01.2024 12:30:00,Bob
3,17.01.2024 09:15:00,Charlie
''';
var df := CsvLoader.LoadFromLines(
text.ToLines,
inferTypes := True
);
var filtered := df.Filter(cur -> cur.DateTime('created_at') >= DateTime.Create(2024, 1, 16));
Println('Исходный DataFrame:');
df.Print;
Println;
Println('Дата >= 16.01.2024:');
filtered.Print;
end.

View file

@ -0,0 +1,24 @@
uses MLABC;
begin
var text := '''
id,created_at,name
1,15.01.2024,Alice
2,16.01.2024 12:30:00,Bob
3,15.01.2024,Charlie
4,16.01.2024 12:30:00,Diana
''';
var df := CsvLoader.LoadFromLines(
text.ToLines,
inferTypes := True
);
var grouped := df.GroupBy('created_at').Count;
Println('Исходный DataFrame:');
df.Print;
Println;
Println('GroupBy(created_at).Count:');
grouped.Print;
end.

View file

@ -0,0 +1,29 @@
uses MLABC;
begin
var text := '''
id,created_at,name
1,15.01.2023,Alice
2,16.01.2024 12:30:00,Bob
3,20.02.2023,Charlie
4,05.03.2024 09:15:00,Diana
''';
var df := CsvLoader.LoadFromLines(
text.ToLines,
inferTypes := True
);
var df2 := df.WithColumnInt(
'year',
cur -> cur.DateTime('created_at').Year
);
var grouped := df2.GroupBy('year').Count;
Println('DataFrame с колонкой year:');
df2.Print;
Println;
Println('GroupBy(year).Count:');
grouped.Print;
end.

View file

@ -0,0 +1,24 @@
uses MLABC, DataFrameABC;
begin
var text := '''
id,created_at,name
1,15.01.2023 10:20:30,Alice
2,16.02.2024 12:30:00,Bob
3,05.03.2024 09:15:45,Charlie
''';
var df := CsvLoader.LoadFromLines(
text.ToLines,
inferTypes := True
);
var df2 := df.WithDatePart('created_at', 'year', dpYear);
var df3 := df2.WithDateParts('created_at', [
('month', dpMonth),
('day', dpDay),
('date_only', dpDate)
]);
df3.Print;
end.

View file

@ -0,0 +1,15 @@
uses MLABC;
begin
var df := DataFrame.FromCsvText('''
product,count,pack_price
milk,2,120
bread,1,55
milk,3,118
tea,1,340
''');
var total := df.Sum(r -> r['count'] * r['pack_price']);
Println('Общая стоимость упаковок:', total:0:2);
end.

View file

@ -0,0 +1,18 @@
uses MLABC;
begin
var df := DataFrame.FromCsvText('''
name,district,operation,amount
Alice,North,sale,120
Bob,North,refund,30
Clara,South,sale,210
Dmitry,North,sale,180
''');
var saleCount := df.Count(r ->
(r['district'] = 'North') and
(r['operation'] = 'sale')
);
Println('Продаж в районе North:', saleCount);
end.

View file

@ -0,0 +1,18 @@
uses MLABC;
begin
var df := DataFrame.FromCsvText('''
product,count,price
milk,2,120
bread,1,55
milk,3,118
tea,1,340
coffee,2,260
''');
Println('Средний чек по строке:', df.Mean(r -> r['count'] * r['price']):0:2);
Println('Медианный чек по строке:', df.Median(r -> r['count'] * r['price']):0:2);
Println('Минимальный чек по строке:', df.Min(r -> r['count'] * r['price']):0:2);
Println('Максимальный чек по строке:', df.Max(r -> r['count'] * r['price']):0:2);
Println('Стандартное отклонение:', df.Std(r -> r['count'] * r['price']):0:2);
end.

View file

@ -0,0 +1,23 @@
uses MLABC;
begin
var df := DataFrame.FromCsvText('''
Город
Москва
Казань
Москва
Омск
Казань
Москва
''');
var values := df.Unique('Город');
Println('Уникальные города:');
foreach var v in values do
Println(v.Str);
Println;
Println('Число различных городов: ', df.NUnique('Город'));
Println;
df.ValueCounts('Город').Print;
end.

View file

@ -0,0 +1,26 @@
uses MLABC;
begin
var df := DataFrame.FromCsvText('''
name,age,income
Alice,20,100
Bob,,120
Clara,25,
Dmitry,30,150
''');
Println('Пропусков в столбце age:', df.MissingCount('age'));
Println('Пропусков в столбце income:', df.MissingCount('income'));
Println;
Println('Пропуски по столбцам:');
df.MissingCounts.Print;
Println;
Println('Строки без пропусков во всех столбцах:');
df.DropMissing.Print;
Println;
Println('Строки без пропусков в age и income:');
df.DropMissing(['age', 'income']).Print;
end.

View file

@ -0,0 +1,18 @@
uses MLABC;
begin
var df := DataFrame.FromCsvText('''
name,age,score
Alice,10,1.5
Bob,25,4.8
Alice,10,1.5
Clara,30,8.2
''');
Println('Без полных дубликатов:');
df.DropDuplicates.Print;
Println;
Println('После Clip(score, 0, 5):');
df.Clip('score', 0, 5).Print;
end.

View file

@ -0,0 +1,23 @@
uses MLABC;
begin
var df := DataFrame.FromCsvText('''
id,created_at,name
1,15.01.2024 10:20:30,Alice
2,16.02.2024 12:30:00,Bob
3,17.03.2025 09:15:45,Charlie
''');
var enc1 := new DateTimeComponentsEncoder('created_at');
var df1 := enc1.FitTransform(df);
Println('Стандартные компоненты:');
df1.Print;
Println;
var enc2 := new DateTimeComponentsEncoder('created_at', [dpYear, dpMonth, dpDay, dpHour]);
var df2 := enc2.FitTransform(df);
Println('Пользовательский набор компонент:');
df2.Print;
end.

View file

@ -0,0 +1,23 @@
uses MLABC;
begin
var df := DataFrame.FromCsvText('''
id,created_at,name
1,15.01.2024 00:00:00,Alice
2,16.01.2024 12:00:00,Bob
3,18.01.2024 06:00:00,Charlie
''');
var enc1 := new DateTimeOrdinalEncoder('created_at');
var df1 := enc1.FitTransform(df);
Println('Смещение в днях от минимальной даты:');
df1.Print;
Println;
var enc2 := new DateTimeOrdinalEncoder('created_at', 'created_at_hours', dtuHours);
var df2 := enc2.FitTransform(df);
Println('Смещение в часах от минимальной даты:');
df2.Print;
end.

View file

@ -0,0 +1,17 @@
uses MLABC;
begin
var df := DataFrame.FromCsvText('''
id,created_at,name
1,15.01.2024 00:00:00,Alice
2,15.01.2024 06:00:00,Bob
3,15.01.2024 12:00:00,Charlie
4,15.01.2024 18:00:00,Diana
5,15.01.2024 23:56:00,Eva
''');
var enc := new DateTimeCyclicEncoder('created_at', dpTimeOfDay);
var df2 := enc.FitTransform(df);
df2.Print;
end.

View file

@ -1 +1 @@
3.11.1.3830
3.11.1.3833

View file

@ -1 +1 @@
!define VERSION '3.11.1.3830'
!define VERSION '3.11.1.3833'

View file

@ -4,7 +4,6 @@ uses TestHelpers in '..\TestHelpers.pas';
begin
var df := new DataFrame;
df.AddStrColumn('Target', Arr('cat', 'dog', 'cat', 'fox'));
df := df.SetCategorical(['Target']);
var classes: array of string;
var y := df.EncodeLabels('Target', classes);

View file

@ -4,7 +4,6 @@ uses TestHelpers in '..\TestHelpers.pas';
begin
var df := new DataFrame;
df.AddIntColumn('Target', Arr(10, 20, 10, 30));
df := df.SetCategorical(['Target']);
var classes: array of string;
var y := df.EncodeLabels('Target', classes);

View file

@ -0,0 +1,59 @@
uses MLABC;
uses TestHelpers in '..\TestHelpers.pas';
begin
var textIso := '''
id,created_at,name
1,2024-01-15,Alice
2,2024-01-16T12:30:00,Bob
''';
var textRu := '''
id,created_at,name
1,15.01.2024,Alice
2,16.01.2024 12:30:00,Bob
''';
var schema := new Dictionary<string, ColumnType>;
schema['created_at'] := ColumnType.ctDateTime;
var dfSchema := CsvLoader.LoadFromLines(
textIso.ToLines,
schema := schema
);
Check(dfSchema.GetColumnType('created_at') = ColumnType.ctDateTime, 'Schema DateTime type expected');
Check(dfSchema.DateTime('created_at')[0] = new System.DateTime(2024, 1, 15), 'Schema DateTime value mismatch');
var dfInfer := CsvLoader.LoadFromLines(
textIso.ToLines,
inferTypes := true
);
Check(dfInfer.GetColumnType('created_at') = ColumnType.ctDateTime, 'Inferred DateTime type expected');
Check(dfInfer.DateTime('created_at')[1] = new System.DateTime(2024, 1, 16, 12, 30, 0), 'Inferred DateTime value mismatch');
var dfRu := CsvLoader.LoadFromLines(
textRu.ToLines,
inferTypes := true
);
Check(dfRu.GetColumnType('created_at') = ColumnType.ctDateTime, 'Russian DateTime inference expected');
Check(dfRu.DateTime('created_at')[0] = new System.DateTime(2024, 1, 15), 'Russian DateTime date mismatch');
Check(dfRu.DateTime('created_at')[1] = new System.DateTime(2024, 1, 16, 12, 30, 0), 'Russian DateTime value mismatch');
var tmp := System.IO.Path.Combine(
System.IO.Path.GetTempPath,
'ml_datetime_' + System.Guid.NewGuid.ToString + '.csv'
);
dfInfer.ToCsv(tmp);
var dfRoundTrip := CsvLoader.Load(tmp);
Check(dfRoundTrip.GetColumnType('created_at') = ColumnType.ctDateTime, 'Round-trip DateTime type expected');
Check(dfRoundTrip.DateTime('created_at')[1] = dfInfer.DateTime('created_at')[1], 'Round-trip DateTime value mismatch');
CheckSchemaMatchesColumns(dfRoundTrip);
if System.IO.File.Exists(tmp) then
System.IO.File.Delete(tmp);
end.

View file

@ -0,0 +1,36 @@
uses MLABC;
uses TestHelpers in '..\TestHelpers.pas';
begin
var df := new DataFrame;
df.AddDateTimeColumn('created_at', Arr(
new System.DateTime(2024, 1, 15),
new System.DateTime(2024, 1, 16, 12, 30, 0)
));
var oldOut := System.Console.Out;
var sw := new System.IO.StringWriter;
System.Console.SetOut(sw);
try
df.Print;
finally
System.Console.SetOut(oldOut);
end;
var s := sw.ToString;
Check(s.Contains('2024-01-15'), 'Default Print must show date-only value');
Check(s.Contains('2024-01-16 12:30:00'), 'Default Print must show date and time');
Check(not s.Contains('2024-01-15 00:00:00'), 'Default Print must omit zero time');
sw := new System.IO.StringWriter;
System.Console.SetOut(sw);
try
df.Print(dateTimeFormat := 'yyyy/MM/dd');
finally
System.Console.SetOut(oldOut);
end;
s := sw.ToString;
Check(s.Contains('2024/01/15'), 'Custom Print format must be applied');
Check(s.Contains('2024/01/16'), 'Custom Print format must be applied to all DateTime values');
end.

View file

@ -0,0 +1,21 @@
uses MLABC;
uses TestHelpers in '..\TestHelpers.pas';
begin
var df := new DataFrame;
df.AddIntColumn('Id', Arr(1, 2, 3));
df.AddDateTimeColumn('CreatedAt', Arr(
new System.DateTime(2024, 1, 16, 12, 30, 0),
new System.DateTime(2024, 1, 15),
new System.DateTime(2024, 1, 17, 9, 15, 0)
));
var res := df.SortBy('CreatedAt');
Check(res.RowCount = 3, 'SortBy(DateTime) row count mismatch');
Check(res.Int('Id')[0] = 2, 'First sorted row mismatch');
Check(res.Int('Id')[1] = 1, 'Second sorted row mismatch');
Check(res.Int('Id')[2] = 3, 'Third sorted row mismatch');
Check(res.DateTime('CreatedAt')[0] = new System.DateTime(2024, 1, 15), 'Sorted DateTime value mismatch');
CheckSchemaMatchesColumns(res);
end.

View file

@ -0,0 +1,21 @@
uses MLABC;
uses TestHelpers in '..\TestHelpers.pas';
begin
var df := new DataFrame;
df.AddIntColumn('Id', Arr(1, 2, 3));
df.AddDateTimeColumn('CreatedAt', Arr(
new System.DateTime(2024, 1, 15),
new System.DateTime(2024, 1, 16, 12, 30, 0),
new System.DateTime(2024, 1, 17, 9, 15, 0)
));
df.AddStrColumn('Name', Arr('Alice', 'Bob', 'Charlie'));
var res := df.Filter(cur -> cur.DateTime('CreatedAt') >= new System.DateTime(2024, 1, 16));
Check(res.RowCount = 2, 'Filter(DateTime) row count mismatch');
Check(res.Int('Id')[0] = 2, 'First filtered row mismatch');
Check(res.Int('Id')[1] = 3, 'Second filtered row mismatch');
Check(res.DateTime('CreatedAt')[0] = new System.DateTime(2024, 1, 16, 12, 30, 0), 'First filtered DateTime mismatch');
CheckSchemaMatchesColumns(res);
end.

View file

@ -0,0 +1,25 @@
uses MLABC, DataFrameABCCore;
uses TestHelpers in '..\TestHelpers.pas';
begin
var df := new DataFrame;
df.AddIntColumn('Id', Arr(1, 2, 3, 4));
df.AddDateTimeColumn('CreatedAt', Arr(
new System.DateTime(2024, 1, 15),
new System.DateTime(2024, 1, 16, 12, 30, 0),
new System.DateTime(2024, 1, 15),
new System.DateTime(2024, 1, 16, 12, 30, 0)
));
var g := df.GroupBy('CreatedAt').Count;
Check(g.Schema.ColumnCount = 2, 'Unexpected GroupBy(DateTime) column count');
Check(g.Schema.NameAt(0) = 'CreatedAt', 'DateTime key column name mismatch');
Check(g.Schema.ColumnTypeAt(0) = ColumnType.ctDateTime, 'DateTime key column type must stay DateTime');
Check(g.GetColumn(0).Info.Name = 'CreatedAt', 'Physical DateTime key column name mismatch');
Check(g.GetColumn(0).Info.ColType = ColumnType.ctDateTime, 'Physical DateTime key column type must stay DateTime');
Check(g.RowCount = 2, 'GroupBy(DateTime) distinct key count mismatch');
Check(g.DateTime('CreatedAt')[0] = new System.DateTime(2024, 1, 15), 'First grouped DateTime key mismatch');
Check(g.GetIntColumn('count')[0] = 2, 'First grouped count mismatch');
Check(g.GetIntColumn('count')[1] = 2, 'Second grouped count mismatch');
end.

View file

@ -0,0 +1,22 @@
uses MLABC, DataFrameABC, DataFrameABCCore;
uses TestHelpers in '..\TestHelpers.pas';
begin
var df := new DataFrame;
df.AddIntColumn('Id', Arr(1, 2));
df.AddDateTimeColumn('CreatedAt', Arr(
new System.DateTime(2024, 1, 15, 10, 20, 30),
new System.DateTime(2025, 2, 16, 12, 30, 40)
));
var resYear := df.WithDatePart('CreatedAt', 'Year', dpYear);
Check(resYear.GetColumnType('Year') = ColumnType.ctInt, 'Year part type mismatch');
Check(resYear.Int('Year')[0] = 2024, 'First year mismatch');
Check(resYear.Int('Year')[1] = 2025, 'Second year mismatch');
var resDate := df.WithDatePart('CreatedAt', 'OnlyDate', dpDate);
Check(resDate.GetColumnType('OnlyDate') = ColumnType.ctDateTime, 'Date part type mismatch');
Check(resDate.DateTime('OnlyDate')[0] = new System.DateTime(2024, 1, 15), 'First date part mismatch');
Check(resDate.DateTime('OnlyDate')[1] = new System.DateTime(2025, 2, 16), 'Second date part mismatch');
CheckSchemaMatchesColumns(resDate);
end.

View file

@ -0,0 +1,26 @@
uses MLABC, DataFrameABC, DataFrameABCCore;
uses TestHelpers in '..\TestHelpers.pas';
begin
var df := new DataFrame;
df.AddIntColumn('Id', Arr(1, 2));
df.AddDateTimeColumn('CreatedAt', Arr(
new System.DateTime(2024, 1, 15, 10, 20, 30),
new System.DateTime(2025, 2, 16, 12, 30, 40)
));
var res := df.WithDateParts('CreatedAt', [
('Year', dpYear),
('Month', dpMonth),
('OnlyDate', dpDate)
]);
Check(res.ColumnCount = 5, 'WithDateParts column count mismatch');
Check(res.GetColumnType('Year') = ColumnType.ctInt, 'Year type mismatch');
Check(res.GetColumnType('Month') = ColumnType.ctInt, 'Month type mismatch');
Check(res.GetColumnType('OnlyDate') = ColumnType.ctDateTime, 'OnlyDate type mismatch');
Check(res.Int('Year')[0] = 2024, 'Year[0] mismatch');
Check(res.Int('Month')[1] = 2, 'Month[1] mismatch');
Check(res.DateTime('OnlyDate')[0] = new System.DateTime(2024, 1, 15), 'OnlyDate[0] mismatch');
CheckSchemaMatchesColumns(res);
end.

View file

@ -0,0 +1,23 @@
uses MLABC, DataFrameABC;
uses TestHelpers in '..\TestHelpers.pas';
begin
var df := CsvLoader.LoadFromLines('''
id,created_at,name
1,15.01.2024 10:20:30,Alice
2,16.02.2025 12:30:40,Bob
'''.ToLines, inferTypes := True);
var enc := new DateTimeComponentsEncoder('created_at');
var res := enc.FitTransform(df);
Check(not res.HasColumn('created_at'), 'Source DateTime column must be replaced');
Check(res.HasColumn('created_at_year'), 'Year column missing');
Check(res.HasColumn('created_at_month'), 'Month column missing');
Check(res.HasColumn('created_at_day'), 'Day column missing');
Check(res.GetColumnType('created_at_year') = ColumnType.ctInt, 'Year column type mismatch');
Check(res.Int('created_at_year')[0] = 2024, 'First year mismatch');
Check(res.Int('created_at_month')[1] = 2, 'Second month mismatch');
Check(res.Int('created_at_day')[1] = 16, 'Second day mismatch');
CheckSchemaMatchesColumns(res);
end.

View file

@ -0,0 +1,23 @@
uses MLABC, DataFrameABC;
uses TestHelpers in '..\TestHelpers.pas';
begin
var df := CsvLoader.LoadFromLines('''
id,created_at,name
1,15.01.2024 10:20:30,Alice
2,16.02.2025 12:30:40,Bob
'''.ToLines, inferTypes := True);
var enc := new DateTimeComponentsEncoder('created_at', [dpDate, dpHour, dpDayOfWeek]);
var res := enc.FitTransform(df);
Check(not res.HasColumn('created_at'), 'Source DateTime column must be replaced');
Check(res.HasColumn('created_at_date'), 'Date component column missing');
Check(res.HasColumn('created_at_hour'), 'Hour component column missing');
Check(res.HasColumn('created_at_dayofweek'), 'DayOfWeek component column missing');
Check(res.GetColumnType('created_at_date') = ColumnType.ctDateTime, 'Date component type mismatch');
Check(res.GetColumnType('created_at_hour') = ColumnType.ctInt, 'Hour component type mismatch');
Check(res.DateTime('created_at_date')[0] = new System.DateTime(2024, 1, 15), 'Date component value mismatch');
Check(res.Int('created_at_hour')[1] = 12, 'Hour component value mismatch');
CheckSchemaMatchesColumns(res);
end.

View file

@ -0,0 +1,29 @@
uses MLABC;
uses TestHelpers in '..\TestHelpers.pas';
begin
var df := CsvLoader.LoadFromLines('''
id,created_at,name
1,15.01.2024 00:00:00,Alice
2,16.01.2024 12:00:00,Bob
3,18.01.2024 06:00:00,Charlie
'''.ToLines, inferTypes := True);
var encDays := new DateTimeOrdinalEncoder('created_at');
var daysDf := encDays.FitTransform(df);
Check(not daysDf.HasColumn('created_at'), 'Source DateTime column must be replaced');
Check(daysDf.GetColumnType('created_at_days') = ColumnType.ctFloat, 'Ordinal DateTime column must become float');
Check(Abs(daysDf.Float('created_at_days')[0] - 0.0) < 1e-9, 'First day offset mismatch');
Check(Abs(daysDf.Float('created_at_days')[1] - 1.5) < 1e-9, 'Second day offset mismatch');
Check(Abs(daysDf.Float('created_at_days')[2] - 3.25) < 1e-9, 'Third day offset mismatch');
var encHours := new DateTimeOrdinalEncoder('created_at', 'created_at_hours', dtuHours);
var hoursDf := encHours.FitTransform(df);
Check(hoursDf.GetColumnType('created_at_hours') = ColumnType.ctFloat, 'Hour ordinal column type mismatch');
Check(Abs(hoursDf.Float('created_at_hours')[0] - 0.0) < 1e-9, 'First hour offset mismatch');
Check(Abs(hoursDf.Float('created_at_hours')[1] - 36.0) < 1e-9, 'Second hour offset mismatch');
Check(Abs(hoursDf.Float('created_at_hours')[2] - 78.0) < 1e-9, 'Third hour offset mismatch');
CheckSchemaMatchesColumns(hoursDf);
end.

View file

@ -0,0 +1,38 @@
uses MLABC;
uses TestHelpers in '..\TestHelpers.pas';
begin
var df := CsvLoader.LoadFromLines('''
id,created_at,name
1,15.01.2024 00:00:00,Alice
2,15.01.2024 06:00:00,Bob
3,15.01.2024 12:00:00,Charlie
4,15.01.2024 18:00:00,Diana
5,15.01.2024 23:59:59,Eva
'''.ToLines, inferTypes := True);
var enc := new DateTimeCyclicEncoder('created_at', dpTimeOfDay);
var res := enc.FitTransform(df);
Check(not res.HasColumn('created_at'), 'Source DateTime column must be replaced');
Check(res.HasColumn('created_at_cyc_timeofday_sin'), 'TimeOfDay sin column missing');
Check(res.HasColumn('created_at_cyc_timeofday_cos'), 'TimeOfDay cos column missing');
Check(res.GetColumnType('created_at_cyc_timeofday_sin') = ColumnType.ctFloat, 'TimeOfDay sin type mismatch');
Check(res.GetColumnType('created_at_cyc_timeofday_cos') = ColumnType.ctFloat, 'TimeOfDay cos type mismatch');
Check(Abs(res.Float('created_at_cyc_timeofday_sin')[0] - 0.0) < 1e-9, '00:00 sin mismatch');
Check(Abs(res.Float('created_at_cyc_timeofday_cos')[0] - 1.0) < 1e-9, '00:00 cos mismatch');
Check(Abs(res.Float('created_at_cyc_timeofday_sin')[1] - 1.0) < 1e-9, '06:00 sin mismatch');
Check(Abs(res.Float('created_at_cyc_timeofday_cos')[1] - 0.0) < 1e-9, '06:00 cos mismatch');
Check(Abs(res.Float('created_at_cyc_timeofday_sin')[2] - 0.0) < 1e-9, '12:00 sin mismatch');
Check(Abs(res.Float('created_at_cyc_timeofday_cos')[2] + 1.0) < 1e-9, '12:00 cos mismatch');
Check(Abs(res.Float('created_at_cyc_timeofday_sin')[3] + 1.0) < 1e-9, '18:00 sin mismatch');
Check(Abs(res.Float('created_at_cyc_timeofday_cos')[3] - 0.0) < 1e-9, '18:00 cos mismatch');
Check(Abs(res.Float('created_at_cyc_timeofday_sin')[4]) < 1e-3, '23:59:59 sin must be close to 0');
Check(Abs(res.Float('created_at_cyc_timeofday_cos')[4] - 1.0) < 1e-6, '23:59:59 cos must be close to 1');
CheckSchemaMatchesColumns(res);
end.

View file

@ -0,0 +1,32 @@
uses MLABC, DataFrameABC, DataFrameABCCore;
uses TestHelpers in '..\TestHelpers.pas';
begin
var df := DataFrame.FromCsvText('''
city
Moscow
Kazan
Moscow
Omsk
Kazan
Moscow
''');
var values := df.Unique('city');
Check(values.Length = 3, 'Unique length mismatch');
Check(values[0].Str = 'Moscow', 'Unique first value mismatch');
Check(values[1].Str = 'Kazan', 'Unique second value mismatch');
Check(values[2].Str = 'Omsk', 'Unique third value mismatch');
Check(df.NUnique('city') = 3, 'NUnique mismatch');
var vc := df.ValueCounts('city');
Check(vc.RowCount = 3, 'ValueCounts row count mismatch');
Check(vc.Str('city')[0] = 'Moscow', 'ValueCounts first key mismatch');
Check(vc.Int('Count')[0] = 3, 'ValueCounts first count mismatch');
Check(vc.Str('city')[1] = 'Kazan', 'ValueCounts second key mismatch');
Check(vc.Int('Count')[1] = 2, 'ValueCounts second count mismatch');
Check(vc.Str('city')[2] = 'Omsk', 'ValueCounts third key mismatch');
Check(vc.Int('Count')[2] = 1, 'ValueCounts third count mismatch');
CheckSchemaMatchesColumns(vc);
end.

View file

@ -0,0 +1,30 @@
uses MLABC, DataFrameABC, DataFrameABCCore;
uses TestHelpers in '..\TestHelpers.pas';
begin
var df := DataFrame.FromCsvText('''
name,age,income
Alice,20,100
Bob,,120
Clara,25,
Dmitry,30,150
''');
Check(df.MissingCount('age') = 1, 'MissingCount(age) mismatch');
Check(df.MissingCount('income') = 1, 'MissingCount(income) mismatch');
var mc := df.MissingCounts;
Check(mc.RowCount = 3, 'MissingCounts row count mismatch');
Check(mc.Str('Column')[0] = 'name', 'MissingCounts first column mismatch');
Check(mc.Int('MissingCount')[0] = 0, 'MissingCounts first count mismatch');
Check(mc.Str('Column')[1] = 'age', 'MissingCounts second column mismatch');
Check(mc.Int('MissingCount')[1] = 1, 'MissingCounts second count mismatch');
Check(mc.Str('Column')[2] = 'income', 'MissingCounts third column mismatch');
Check(mc.Int('MissingCount')[2] = 1, 'MissingCounts third count mismatch');
var dropped := df.DropMissing(['age', 'income']);
Check(dropped.RowCount = 2, 'DropMissing selected row count mismatch');
Check(dropped.Str('name')[0] = 'Alice', 'DropMissing first row mismatch');
Check(dropped.Str('name')[1] = 'Dmitry', 'DropMissing second row mismatch');
CheckSchemaMatchesColumns(dropped);
end.

View file

@ -0,0 +1,26 @@
uses MLABC, DataFrameABC, DataFrameABCCore;
uses TestHelpers in '..\TestHelpers.pas';
begin
var df := DataFrame.FromCsvText('''
name,age,score
Alice,10,1.5
Bob,25,4.8
Alice,10,1.5
Clara,30,8.2
''');
var dedup := df.DropDuplicates;
Check(dedup.RowCount = 3, 'DropDuplicates row count mismatch');
Check(dedup.Str('name')[0] = 'Alice', 'DropDuplicates first row mismatch');
Check(dedup.Str('name')[1] = 'Bob', 'DropDuplicates second row mismatch');
Check(dedup.Str('name')[2] = 'Clara', 'DropDuplicates third row mismatch');
CheckSchemaMatchesColumns(dedup);
var clipped := df.Clip('score', 0, 5);
Check(clipped.RowCount = 4, 'Clip row count mismatch');
Check(Abs(clipped.Float('score')[0] - 1.5) < 1e-9, 'Clip first value mismatch');
Check(Abs(clipped.Float('score')[1] - 4.8) < 1e-9, 'Clip second value mismatch');
Check(Abs(clipped.Float('score')[3] - 5.0) < 1e-9, 'Clip clipped value mismatch');
CheckSchemaMatchesColumns(clipped);
end.

View file

@ -0,0 +1,18 @@
uses MLABC, DataFrameABC, DataFrameABCCore;
uses TestHelpers in '..\TestHelpers.pas';
begin
var df := DataFrame.FromCsvText('''
name,age,income
Alice,20,100
Bob,,120
Clara,25,
Dmitry,30,150
''');
var dropped := df.DropMissing;
Check(dropped.RowCount = 2, 'DropMissing all row count mismatch');
Check(dropped.Str('name')[0] = 'Alice', 'DropMissing all first row mismatch');
Check(dropped.Str('name')[1] = 'Dmitry', 'DropMissing all second row mismatch');
CheckSchemaMatchesColumns(dropped);
end.

View file

@ -0,0 +1,36 @@
uses MLABC, DataFrameABC, DataFrameABCCore;
uses TestHelpers in '..\TestHelpers.pas';
begin
var df := DataFrame.FromCsvText('''
city
Moscow
Kazan
Moscow
Kazan
Omsk
''');
var vc := df.ValueCounts('city');
Check(vc.RowCount = 3, 'ValueCounts tie row count mismatch');
Check(vc.Str('city')[0] = 'Moscow', 'ValueCounts tie first key mismatch');
Check(vc.Int('Count')[0] = 2, 'ValueCounts tie first count mismatch');
Check(vc.Str('city')[1] = 'Kazan', 'ValueCounts tie second key mismatch');
Check(vc.Int('Count')[1] = 2, 'ValueCounts tie second count mismatch');
Check(vc.Str('city')[2] = 'Omsk', 'ValueCounts tie third key mismatch');
Check(vc.Int('Count')[2] = 1, 'ValueCounts tie third count mismatch');
var df2 := DataFrame.FromCsvText('''
Count
1
2
1
''');
var vc2 := df2.ValueCounts('Count');
Check(vc2.HasColumn('Count'), 'ValueCounts must keep source column name');
Check(vc2.HasColumn('Frequency'), 'ValueCounts must rename counter column on conflict');
Check(vc2.Int('Count')[0] = 1, 'ValueCounts conflict first key mismatch');
Check(vc2.Int('Frequency')[0] = 2, 'ValueCounts conflict first frequency mismatch');
CheckSchemaMatchesColumns(vc2);
end.

View file

@ -0,0 +1,26 @@
uses MLABC, DataFrameABC, DataFrameABCCore;
uses TestHelpers in '..\TestHelpers.pas';
begin
var df := DataFrame.FromCsvText('''
age
-5
10
30
''');
var clipped := df.Clip('age', 0, 20);
Check(clipped.RowCount = 3, 'Clip int row count mismatch');
Check(clipped.Int('age')[0] = 0, 'Clip int first value mismatch');
Check(clipped.Int('age')[1] = 10, 'Clip int second value mismatch');
Check(clipped.Int('age')[2] = 20, 'Clip int third value mismatch');
CheckSchemaMatchesColumns(clipped);
CheckRaises(procedure -> begin
var tmp := df.Clip('age', 0.5, 20);
end, 'Clip must reject non-integer bounds for int column');
CheckRaises(procedure -> begin
var tmp := df.Clip('age', 10, 0);
end, 'Clip must reject lower > upper');
end.

View file

@ -12,7 +12,14 @@ begin
new LogisticRegression
);
CheckRaises(procedure -> begin pipe.Fit(df); end,
'Classification pipeline must require categorical target');
pipe.Fit(df);
var pred := pipe.Predict(df);
var labels := pipe.PredictLabels(df);
var classes := pipe.GetClassLabels;
Check(pred.Length = df.RowCount, 'Pipeline prediction length mismatch');
Check(labels.Length = df.RowCount, 'Pipeline label prediction length mismatch');
Check(classes.Length = 2, 'Pipeline class count mismatch');
end.

View file

@ -134,9 +134,6 @@ begin
if not Self.HasColumn(target) then
ArgumentError(ER_COLUMN_NOT_FOUND, target);
if not Self.IsCategorical(target) then
ArgumentError(ER_ENCODELABELS_NOT_CATEGORICAL, target);
var col := Self[target];
for var i := 0 to Self.RowCount - 1 do
if not col.IsValid[i] then
@ -157,6 +154,13 @@ begin
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;
@ -166,7 +170,7 @@ end;
/// Каждому уникальному значению присваивается номер 0,1,2,...
/// Порядок кодирования соответствует порядку первого появления значений в столбце.
/// В параметр classes возвращается массив уникальных значений в порядке кодирования.
/// Работает только для категориальных столбцов типов string и integer.
/// Работает для столбцов типов string, integer и boolean.
/// Для целочисленных категориальных столбцов значения преобразуются в строки.
/// Используется при подготовке данных для задач классификации и визуализации.
function EncodeLabels(Self: DataFrame; target: string; var classes: array of string): array of integer; extensionmethod;
@ -180,9 +184,6 @@ begin
if not Self.HasColumn(target) then
ArgumentError(ER_COLUMN_NOT_FOUND, target);
if not Self.IsCategorical(target) then
ArgumentError(ER_ENCODELABELS_NOT_CATEGORICAL, target);
var col := Self[target];
for var i := 0 to Self.RowCount - 1 do
if not col.IsValid[i] then
@ -204,6 +205,11 @@ begin
// если 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;
@ -228,9 +234,6 @@ begin
if not Self.HasColumn(target) then
ArgumentError(ER_COLUMN_NOT_FOUND, target);
if not Self.IsCategorical(target) then
ArgumentError(ER_ENCODELABELS_NOT_CATEGORICAL, target);
var col := Self[target];
for var i := 0 to Self.RowCount - 1 do
if not col.IsValid[i] then
@ -247,6 +250,16 @@ begin
var data := Self.GetIntColumn(target).ToArray;
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
strData[i] := data[i].ToString;

File diff suppressed because it is too large Load diff

View file

@ -23,8 +23,9 @@ type
IntArray = array of integer;
RealArray = array of real;
BoolArray = array of boolean;
DateTimeArray = array of System.DateTime;
ColumnType = (ctInt, ctFloat, ctStr, ctBool);
ColumnType = (ctInt, ctFloat, ctStr, ctBool, ctDateTime);
/// Неизменяемое описание структуры столбцов DataFrame
DataFrameSchema = sealed class
@ -111,6 +112,102 @@ type
constructor Create(name: string; colType: ColumnType);
//IsCategorical - только в Schema!
end;
/// Типизированное значение ячейки DataFrame.
/// Используется для доступа через row['Column'].
///
/// Позволяет писать в фильтрах и вычислениях:
/// row['score'].IsValid
/// row['score'].Float
/// row['date'].DateTime
///
/// Если значение отсутствует, IsValid = False,
/// а попытка взять Int/Float/Str/Bool/DateTime
/// приводит к исключению.
DataValue = sealed class
private
fName: string;
fColType: ColumnType;
fIsValid: boolean;
fInt: integer;
fFloat: real;
fStr: string;
fBool: boolean;
fDateTime: System.DateTime;
class function IsNumericType(t: ColumnType): boolean;
class function Compare(a, b: DataValue): integer;
class function ArithmeticType(a, b: DataValue): ColumnType;
class function MakeNumeric(name: string; value: real; asInt: boolean := False): DataValue;
public
/// Имя столбца, из которого получено значение
property Name: string read fName;
/// Тип столбца, из которого получено значение
property ColType: ColumnType read fColType;
/// Показывает, что в ячейке есть корректное значение, а не NA
property IsValid: boolean read fIsValid;
constructor Create(name: string; value: integer);
constructor Create(name: string; value: real);
constructor Create(name: string; value: string);
constructor Create(name: string; value: boolean);
constructor Create(name: string; value: System.DateTime);
class function NA(name: string; colType: ColumnType): DataValue;
/// Возвращает значение как integer
function GetInt: integer;
/// Возвращает значение как real
function GetFloat: real;
/// Возвращает значение как string
function GetStr: string;
/// Возвращает значение как boolean
function GetBool: boolean;
/// Возвращает значение как DateTime
function GetDateTime: System.DateTime;
/// Значение ячейки как integer
property Int: integer read GetInt;
/// Значение ячейки как real
property Float: real read GetFloat;
/// Значение ячейки как string
property Str: string read GetStr;
/// Значение ячейки как boolean
property Bool: boolean read GetBool;
/// Значение ячейки как DateTime
property DateTime: System.DateTime read GetDateTime;
function ToInt: integer := GetInt;
function ToFloat: real := GetFloat;
function ToStr: string := GetStr;
function ToBool: boolean := GetBool;
function ToDateTime: System.DateTime := GetDateTime;
function ToString: string; override;
static function operator implicit(v: integer): DataValue := new DataValue('', v);
static function operator implicit(v: real): DataValue := new DataValue('', v);
static function operator implicit(v: string): DataValue := new DataValue('', v);
static function operator implicit(v: boolean): DataValue := new DataValue('', v);
static function operator implicit(v: System.DateTime): DataValue := new DataValue('', v);
{static function operator implicit(v: DataValue): integer;
static function operator implicit(v: DataValue): real;
static function operator implicit(v: DataValue): string;
static function operator implicit(v: DataValue): boolean;
static function operator implicit(v: DataValue): System.DateTime;}
static function operator =(a, b: DataValue): boolean;
static function operator <>(a, b: DataValue): boolean := not (a = b);
static function operator <(a, b: DataValue): boolean := Compare(a, b) < 0;
static function operator <=(a, b: DataValue): boolean := Compare(a, b) <= 0;
static function operator >(a, b: DataValue): boolean := Compare(a, b) > 0;
static function operator >=(a, b: DataValue): boolean := Compare(a, b) >= 0;
static function operator +(a, b: DataValue): DataValue;
static function operator -(a, b: DataValue): DataValue;
static function operator *(a, b: DataValue): DataValue;
static function operator /(a, b: DataValue): DataValue;
end;
DataFrameCursor = class;
@ -183,12 +280,26 @@ type
/// Возвращает количество строк в столбце
function RowCount: integer; override := Data.Length;
end;
/// Столбец значений DateTime
DateTimeColumn = class(Column)
Data: array of System.DateTime;
public
constructor Create; begin end;
constructor Create(name: string);
constructor Create(name: string; values: array of System.DateTime;
valid: array of boolean := nil);
function TryGetNumericValue(i: integer; var value: real): boolean; override;
/// Возвращает количество строк в столбце
function RowCount: integer; override := Data.Length;
end;
// Accessor типы для курсора
IntAccessor = function(pos: integer): integer;
FloatAccessor = function(pos: integer): real;
StrAccessor = function(pos: integer): string;
BoolAccessor = function(pos: integer): boolean;
DateTimeAccessor = function(pos: integer): System.DateTime;
ValidAccessor = function(pos: integer): boolean;
// Структуры для Join
@ -218,7 +329,10 @@ type
floatAcc: array of FloatAccessor;
strAcc: array of StrAccessor;
boolAcc: array of BoolAccessor;
dtAcc: array of DateTimeAccessor;
validAcc: array of ValidAccessor;
function GetValue(i: integer): DataValue;
function GetValue(name: string): DataValue;
public
/// Создает курсор для указанных столбцов
constructor Create(cols: array of Column; schema: DataFrameSchema);
@ -238,6 +352,8 @@ type
function Str(i: integer): string;
/// Возвращает булево значение из столбца по индексу
function Bool(i: integer): boolean;
/// Возвращает значение DateTime из столбца по индексу
function DateTime(i: integer): System.DateTime;
/// Возвращает целочисленное значение из столбца по имени
function Int(name: string): integer;
/// Возвращает вещественное значение из столбца по имени
@ -246,6 +362,17 @@ type
function Str(name: string): string;
/// Возвращает булево значение из столбца по имени
function Bool(name: string): boolean;
/// Возвращает значение DateTime из столбца по имени
function DateTime(name: string): System.DateTime;
/// Возвращает типизированное значение ячейки по индексу.
/// Пример: row.Value(0).IsValid
function Value(i: integer): DataValue := GetValue(i);
/// Возвращает типизированное значение ячейки по имени столбца.
/// Это основной способ проверки и чтения значения:
/// row['score'].IsValid
/// row['score'].Float
/// row['name'].Str
property Item[name: string]: DataValue read GetValue; default;
/// Проверяет валидность значения в столбце по индексу
function IsValid(i: integer): boolean;
/// Проверяет валидность значения в столбце по имени
@ -286,6 +413,8 @@ const
'Столбец не является Str!!Column is not Str';
ER_COLUMN_NOT_BOOL =
'Столбец не является Bool!!Column is not Bool';
ER_COLUMN_NOT_DATETIME =
'Столбец не является DateTime!!Column is not DateTime';
ER_VALUE_IS_NA =
'Значение в столбце "{0}" равно NA!!Value in column "{0}" is NA';
ER_DUPLICATE_COLUMN_NAME =
@ -318,7 +447,23 @@ const
'Индекс строки {0} вне диапазона [0..{1})!!' +
'Row index {0} out of range [0..{1})';
ER_INVALID_ISVALID_LENGTH =
'Длина IsValid должна совпадать с длиной Data!!IsValid length must match Data length';
'Длина IsValid должна совпадать с длиной Data!!IsValid length must match Data length';
ER_DATAVALUE_NOT_INT =
'Значение "{0}" не является Int!!Value "{0}" is not Int';
ER_DATAVALUE_NOT_FLOAT =
'Значение "{0}" не является Float!!Value "{0}" is not Float';
ER_DATAVALUE_NOT_STR =
'Значение "{0}" не является Str!!Value "{0}" is not Str';
ER_DATAVALUE_NOT_BOOL =
'Значение "{0}" не является Bool!!Value "{0}" is not Bool';
ER_DATAVALUE_NOT_DATETIME =
'Значение "{0}" не является DateTime!!Value "{0}" is not DateTime';
ER_DATAVALUE_COMPARE_TYPES_MISMATCH =
'Нельзя сравнивать значения типов {0} и {1}!!Cannot compare values of types {0} and {1}';
ER_DATAVALUE_ARITHMETIC_TYPES_MISMATCH =
'Нельзя выполнять арифметические операции для типов {0} и {1}!!Cannot apply arithmetic to types {0} and {1}';
ER_DATAVALUE_DIVISION_BY_ZERO =
'Деление на ноль!!Division by zero';
//-----------------------------
// Сервисные функции
@ -348,6 +493,25 @@ begin
Error(ER_COLUMN_NOT_BOOL);
end;
function NotDateTime(pos: integer): System.DateTime;
begin
Result := default(System.DateTime);
Error(ER_COLUMN_NOT_DATETIME);
end;
function ColumnTypeName(t: ColumnType): string;
begin
case t of
ctInt: Result := 'Int';
ctFloat: Result := 'Float';
ctStr: Result := 'Str';
ctBool: Result := 'Bool';
ctDateTime: Result := 'DateTime';
else
Result := t.ToString;
end;
end;
//-----------------------------
// DataFrameSchema
//-----------------------------
@ -409,6 +573,275 @@ begin
fColType := colType;
end;
class function DataValue.IsNumericType(t: ColumnType): boolean;
begin
Result := t in [ctInt, ctFloat];
end;
constructor DataValue.Create(name: string; value: integer);
begin
fName := name;
fColType := ctInt;
fIsValid := true;
fInt := value;
fFloat := value;
end;
constructor DataValue.Create(name: string; value: real);
begin
fName := name;
fColType := ctFloat;
fIsValid := true;
fFloat := value;
end;
constructor DataValue.Create(name: string; value: string);
begin
fName := name;
fColType := ctStr;
fIsValid := true;
fStr := value;
end;
constructor DataValue.Create(name: string; value: boolean);
begin
fName := name;
fColType := ctBool;
fIsValid := true;
fBool := value;
end;
constructor DataValue.Create(name: string; value: System.DateTime);
begin
fName := name;
fColType := ctDateTime;
fIsValid := true;
fDateTime := value;
end;
class function DataValue.NA(name: string; colType: ColumnType): DataValue;
begin
Result := new DataValue(name, 0);
Result.fColType := colType;
Result.fIsValid := false;
end;
function DataValue.GetInt: integer;
begin
if not fIsValid then
Error(ER_VALUE_IS_NA, fName);
if fColType <> ctInt then
Error(ER_DATAVALUE_NOT_INT, fName);
Result := fInt;
end;
function DataValue.GetFloat: real;
begin
if not fIsValid then
Error(ER_VALUE_IS_NA, fName);
if not IsNumericType(fColType) then
Error(ER_DATAVALUE_NOT_FLOAT, fName);
if fColType = ctInt then
Result := fInt
else
Result := fFloat;
end;
function DataValue.GetStr: string;
begin
if not fIsValid then
Error(ER_VALUE_IS_NA, fName);
if fColType <> ctStr then
Error(ER_DATAVALUE_NOT_STR, fName);
Result := fStr;
end;
function DataValue.GetBool: boolean;
begin
if not fIsValid then
Error(ER_VALUE_IS_NA, fName);
if fColType <> ctBool then
Error(ER_DATAVALUE_NOT_BOOL, fName);
Result := fBool;
end;
function DataValue.GetDateTime: System.DateTime;
begin
if not fIsValid then
Error(ER_VALUE_IS_NA, fName);
if fColType <> ctDateTime then
Error(ER_DATAVALUE_NOT_DATETIME, fName);
Result := fDateTime;
end;
function DataValue.ToString: string;
begin
if not fIsValid then
exit('NA');
case fColType of
ctInt: Result := fInt.ToString;
ctFloat: Result := fFloat.ToString;
ctStr: Result := fStr;
ctBool: Result := fBool.ToString;
ctDateTime: Result := fDateTime.ToString('yyyy-MM-dd HH:mm:ss');
else
Result := '';
end;
end;
class function DataValue.Compare(a, b: DataValue): integer;
begin
if System.Object.ReferenceEquals(a, nil) then
ArgumentNullError('a');
if System.Object.ReferenceEquals(b, nil) then
ArgumentNullError('b');
if not a.fIsValid then
Error(ER_VALUE_IS_NA, a.fName);
if not b.fIsValid then
Error(ER_VALUE_IS_NA, b.fName);
if IsNumericType(a.fColType) and IsNumericType(b.fColType) then
begin
var av := a.Float;
var bv := b.Float;
if av < bv then
Result := -1
else if av > bv then
Result := 1
else
Result := 0;
exit;
end;
if a.fColType <> b.fColType then
Error(ER_DATAVALUE_COMPARE_TYPES_MISMATCH, ColumnTypeName(a.fColType), ColumnTypeName(b.fColType));
case a.fColType of
ctStr:
if a.fStr < b.fStr then Result := -1 else if a.fStr > b.fStr then Result := 1 else Result := 0;
ctBool:
if integer(a.fBool) < integer(b.fBool) then Result := -1 else if integer(a.fBool) > integer(b.fBool) then Result := 1 else Result := 0;
ctDateTime:
if a.fDateTime < b.fDateTime then Result := -1 else if a.fDateTime > b.fDateTime then Result := 1 else Result := 0;
else
Result := 0;
end;
end;
class function DataValue.ArithmeticType(a, b: DataValue): ColumnType;
begin
if System.Object.ReferenceEquals(a, nil) then
ArgumentNullError('a');
if System.Object.ReferenceEquals(b, nil) then
ArgumentNullError('b');
if not a.fIsValid then
Error(ER_VALUE_IS_NA, a.fName);
if not b.fIsValid then
Error(ER_VALUE_IS_NA, b.fName);
if not (IsNumericType(a.fColType) and IsNumericType(b.fColType)) then
Error(ER_DATAVALUE_ARITHMETIC_TYPES_MISMATCH, ColumnTypeName(a.fColType), ColumnTypeName(b.fColType));
if (a.fColType = ctInt) and (b.fColType = ctInt) then
Result := ctInt
else
Result := ctFloat;
end;
class function DataValue.MakeNumeric(name: string; value: real; asInt: boolean): DataValue;
begin
if asInt then
Result := new DataValue(name, integer(value))
else
Result := new DataValue(name, value);
end;
{static function DataValue.operator implicit(v: DataValue): integer;
begin
if System.Object.ReferenceEquals(v, nil) then
ArgumentNullError('v');
Result := v.Int;
end;
static function DataValue.operator implicit(v: DataValue): real;
begin
if System.Object.ReferenceEquals(v, nil) then
ArgumentNullError('v');
Result := v.Float;
end;
static function DataValue.operator implicit(v: DataValue): string;
begin
if System.Object.ReferenceEquals(v, nil) then
ArgumentNullError('v');
Result := v.Str;
end;
static function DataValue.operator implicit(v: DataValue): boolean;
begin
if System.Object.ReferenceEquals(v, nil) then
ArgumentNullError('v');
Result := v.Bool;
end;
static function DataValue.operator implicit(v: DataValue): System.DateTime;
begin
if System.Object.ReferenceEquals(v, nil) then
ArgumentNullError('v');
Result := v.DateTime;
end;}
static function DataValue.operator =(a, b: DataValue): boolean;
begin
if System.Object.ReferenceEquals(a, nil) then
exit(System.Object.ReferenceEquals(b, nil));
if System.Object.ReferenceEquals(b, nil) then
exit(false);
Result := Compare(a, b) = 0;
end;
static function DataValue.operator +(a, b: DataValue): DataValue;
begin
if System.Object.ReferenceEquals(a, nil) then
ArgumentNullError('a');
if System.Object.ReferenceEquals(b, nil) then
ArgumentNullError('b');
if not a.fIsValid then
Error(ER_VALUE_IS_NA, a.fName);
if not b.fIsValid then
Error(ER_VALUE_IS_NA, b.fName);
if (a.fColType = ctStr) and (b.fColType = ctStr) then
begin
Result := new DataValue(a.Name, a.fStr + b.fStr);
exit;
end;
var t := ArithmeticType(a, b);
Result := MakeNumeric(a.Name, a.Float + b.Float, t = ctInt);
end;
static function DataValue.operator -(a, b: DataValue): DataValue;
begin
var t := ArithmeticType(a, b);
Result := MakeNumeric(a.Name, a.Float - b.Float, t = ctInt);
end;
static function DataValue.operator *(a, b: DataValue): DataValue;
begin
var t := ArithmeticType(a, b);
Result := MakeNumeric(a.Name, a.Float * b.Float, t = ctInt);
end;
static function DataValue.operator /(a, b: DataValue): DataValue;
begin
ArithmeticType(a, b);
if b.Float = 0 then
Error(ER_DATAVALUE_DIVISION_BY_ZERO);
Result := new DataValue(a.Name, a.Float / b.Float);
end;
procedure DataFrameSchema.Print;
begin
if fNames.Length = 0 then
@ -430,6 +863,7 @@ begin
ctFloat: t := 'float';
ctStr: t := 'string';
ctBool: t := 'bool';
ctDateTime: t := 'datetime';
end;
if fCategoricalFlags[i] then
@ -766,6 +1200,39 @@ begin
exit(True);
end;
constructor DateTimeColumn.Create(name: string; values: array of System.DateTime; valid: array of boolean);
begin
inherited Create;
Info := new ColumnInfo(name, ctDateTime);
var n := Length(values);
Self.Data := if n = 0 then [] else values;
if valid = nil then
IsValid := [True] * n
else
begin
if Length(valid) <> n then
Error(ER_INVALID_ISVALID_LENGTH);
IsValid := valid;
end;
end;
constructor DateTimeColumn.Create(name: string);
begin
inherited Create;
Info := new ColumnInfo(name, ctDateTime);
Data := new System.DateTime[0];
IsValid := new boolean[0];
end;
function DateTimeColumn.TryGetNumericValue(i: integer; var value: real): boolean;
begin
exit(False);
end;
//-----------------------------
@ -830,6 +1297,7 @@ begin
ctFloat: rowCnt := FloatColumn(cols[0]).Data.Length;
ctStr: rowCnt := StrColumn(cols[0]).Data.Length;
ctBool: rowCnt := BoolColumn(cols[0]).Data.Length;
ctDateTime: rowCnt := DateTimeColumn(cols[0]).Data.Length;
end;
var n := cols.Length;
@ -839,6 +1307,7 @@ begin
floatAcc := new FloatAccessor[n];
strAcc := new StrAccessor[n];
boolAcc := new BoolAccessor[n];
dtAcc := new DateTimeAccessor[n];
validAcc := new ValidAccessor[n];
for var i := 0 to n - 1 do
@ -849,6 +1318,7 @@ begin
floatAcc[i] := NotFloat;
strAcc[i] := NotStr;
boolAcc[i] := NotBool;
dtAcc[i] := NotDateTime;
case fSchema.ColumnTypeAt(i) of
ctInt:
@ -883,6 +1353,14 @@ begin
boolAcc[i] := pos -> c.Data[pos];
end;
ctDateTime:
begin
var c := DateTimeColumn(col);
validAcc[i] := pos -> c.IsValid[pos];
dtAcc[i] := pos -> c.Data[pos];
end;
else Error(ER_UNKNOWN_COLUMN_TYPE);
end;
@ -932,6 +1410,13 @@ begin
Error(ER_VALUE_IS_NA, fSchema.NameAt(i));
Result := boolAcc[i](pos);
end;
function DataFrameCursor.DateTime(i: integer): System.DateTime;
begin
if not IsValid(i) then
Error(ER_VALUE_IS_NA, fSchema.NameAt(i));
Result := dtAcc[i](pos);
end;
function DataFrameCursor.Int(name: string): integer;
begin
@ -952,6 +1437,34 @@ function DataFrameCursor.Bool(name: string): boolean;
begin
Result := Bool(fSchema.IndexOf(name));
end;
function DataFrameCursor.DateTime(name: string): System.DateTime;
begin
Result := DateTime(fSchema.IndexOf(name));
end;
function DataFrameCursor.GetValue(i: integer): DataValue;
begin
var name := fSchema.NameAt(i);
if not IsValid(i) then
exit(DataValue.NA(name, fSchema.ColumnTypeAt(i)));
case fSchema.ColumnTypeAt(i) of
ctInt: Result := new DataValue(name, Int(i));
ctFloat: Result := new DataValue(name, Float(i));
ctStr: Result := new DataValue(name, Str(i));
ctBool: Result := new DataValue(name, Bool(i));
ctDateTime: Result := new DataValue(name, DateTime(i));
else
Error(ER_UNKNOWN_COLUMN_TYPE);
end;
end;
function DataFrameCursor.GetValue(name: string): DataValue;
begin
Result := GetValue(fSchema.IndexOf(name));
end;
procedure DataFrameCursor.MoveTo(p: integer);
begin

View file

@ -106,8 +106,10 @@ type
DataFrame = DataFrameABC.DataFrame;
DataFrameCursor = DataFrameABCCore.DataFrameCursor;
DataValue = DataFrameABCCore.DataValue;
ColumnType = DataFrameABCCore.ColumnType;
Column = DataFrameABCCore.Column;
DatePartKind = DataFrameABC.DatePartKind;
Statistics = DataFrameABC.Statistics;
CsvLoader = DataFrameABC.CsvLoader;
@ -163,6 +165,10 @@ type
IPreprocessor = PreprocessorABC.IPreprocessor;
OrdinalEncoder = PreprocessorABC.OrdinalEncoder;
OneHotEncoder = PreprocessorABC.OneHotEncoder;
DateTimeComponentsEncoder = PreprocessorABC.DateTimeComponentsEncoder;
DateTimeUnit = PreprocessorABC.DateTimeUnit;
DateTimeOrdinalEncoder = PreprocessorABC.DateTimeOrdinalEncoder;
DateTimeCyclicEncoder = PreprocessorABC.DateTimeCyclicEncoder;
ImputeStrategy = PreprocessorABC.ImputeStrategy;
Imputer = PreprocessorABC.Imputer;
@ -191,6 +197,21 @@ const
jkLeft = JoinKind.jkLeft;
jkRight = JoinKind.jkRight;
jkFull = JoinKind.jkFull;
dpYear = DatePartKind.dpYear;
dpMonth = DatePartKind.dpMonth;
dpDay = DatePartKind.dpDay;
dpHour = DatePartKind.dpHour;
dpMinute = DatePartKind.dpMinute;
dpSecond = DatePartKind.dpSecond;
dpDayOfWeek = DatePartKind.dpDayOfWeek;
dpTimeOfDay = DatePartKind.dpTimeOfDay;
dpDate = DatePartKind.dpDate;
dtuDays = DateTimeUnit.dtuDays;
dtuHours = DateTimeUnit.dtuHours;
dtuMinutes = DateTimeUnit.dtuMinutes;
dtuSeconds = DateTimeUnit.dtuSeconds;
/// Кодирует строковые метки классов в целочисленные индексы.
/// Каждому уникальному значению присваивается номер 0,1,2,...

View file

@ -309,8 +309,8 @@ const
ER_ENCODELABELS_NOT_CATEGORICAL =
'Целевой столбец должен быть категориальным для задач классификации!!Target column must be categorical for classification tasks';
ER_CLASSIFICATION_TARGET_MUST_BE_CATEGORICAL_STR_OR_INT =
'Целевой столбец "{0}" должен быть категориальным строковым или целочисленным для задач классификации!!' +
'Target column "{0}" must be a categorical string or integer column for classification tasks';
'Целевой столбец "{0}" должен быть строковым, целочисленным или логическим для задач классификации!!' +
'Target column "{0}" must be a string, integer, or boolean column for classification tasks';
ER_REGRESSION_TARGET_MUST_BE_NUMERIC =
'Целевой столбец "{0}" должен быть числовым для задач регрессии!!Target column "{0}" must be numeric for regression tasks';
ER_PREPROCESSOR_ROWCOUNT_CHANGED =
@ -990,10 +990,8 @@ begin
case fTask of
tkClassification:
begin
if not df.IsCategorical(fTarget) then
ArgumentError(ER_ENCODELABELS_NOT_CATEGORICAL, fTarget);
var ct := df.GetColumnType(fTarget);
if not (ct in [ColumnType.ctStr, ColumnType.ctInt]) then
if not (ct in [ColumnType.ctStr, ColumnType.ctInt, ColumnType.ctBool]) then
ArgumentError(ER_CLASSIFICATION_TARGET_MUST_BE_CATEGORICAL_STR_OR_INT, fTarget);
end;

View file

@ -123,6 +123,92 @@ type
function Clone: IPreprocessor;
end;
DateTimeUnit = (dtuDays, dtuHours, dtuMinutes, dtuSeconds);
/// Разворачивает DateTime-столбец в набор компонентных признаков.
/// По умолчанию создаёт признаки year, month, day.
/// Исходный DateTime-столбец заменяется новыми столбцами.
DateTimeComponentsEncoder = class(IPreprocessor, IColumnBoundStep, IColumnExpander)
private
col: string;
parts: array of DatePartKind;
expandedColumns: array of string;
fitted: boolean;
function DefaultParts: array of DatePartKind;
class function PartSuffix(part: DatePartKind): string;
public
/// Создаёт encoder со стандартным набором компонент: year, month, day.
constructor Create(column: string);
/// Создаёт encoder с явным списком компонент.
constructor Create(column: string; dateParts: array of DatePartKind);
function Fit(df: DataFrame): IPreprocessor;
function Transform(df: DataFrame): DataFrame;
function FitTransform(df: DataFrame): DataFrame;
function ToString: string; override;
property ColumnName: string read col;
function GetExpandedColumns(sourceColumn: string): array of string;
function Clone: IPreprocessor;
end;
/// Кодирует DateTime-столбец в один вещественный столбец,
/// содержащий расстояние от минимального значения столбца в выбранных единицах.
/// На Fit запоминается fOrigin = Min(column), на Transform вычисляется
/// (dt - fOrigin).TotalDays / TotalHours / TotalMinutes / TotalSeconds.
/// Исходный столбец заменяется вещественным столбцом с новым именем.
DateTimeOrdinalEncoder = class(IPreprocessor, IColumnBoundStep)
private
col: string;
newCol: string;
unit_: DateTimeUnit;
fOrigin: System.DateTime;
fitted: boolean;
class function DefaultColumnName(column: string; unit_: DateTimeUnit): string;
public
constructor Create(column: string);
constructor Create(column, newColumn: string);
constructor Create(column, newColumn: string; unit_: DateTimeUnit);
function Fit(df: DataFrame): IPreprocessor;
function Transform(df: DataFrame): DataFrame;
function FitTransform(df: DataFrame): DataFrame;
function ToString: string; override;
property ColumnName: string read col;
function Clone: IPreprocessor;
end;
/// Кодирует циклическую компоненту DateTime через пару sin/cos.
/// Например, для dpHour создаёт created_at_cyc_hour_sin и created_at_cyc_hour_cos.
/// Исходный DateTime-столбец заменяется двумя вещественными столбцами.
DateTimeCyclicEncoder = class(IPreprocessor, IColumnBoundStep, IColumnExpander)
private
col: string;
part: DatePartKind;
sinCol: string;
cosCol: string;
fitted: boolean;
class function PartSuffix(part: DatePartKind): string;
class function IsSupportedPart(part: DatePartKind): boolean;
class function PartPeriod(part: DatePartKind): integer;
class function PartValue(dt: System.DateTime; part: DatePartKind): real;
public
constructor Create(column: string; part: DatePartKind);
function Fit(df: DataFrame): IPreprocessor;
function Transform(df: DataFrame): DataFrame;
function FitTransform(df: DataFrame): DataFrame;
function ToString: string; override;
property ColumnName: string read col;
function GetExpandedColumns(sourceColumn: string): array of string;
function Clone: IPreprocessor;
end;
ImputeStrategy = (isMean, isConstant, isMedian);
/// Заполняет пропущенные значения (NA) в столбцах DataFrame
@ -228,6 +314,45 @@ const
'Конфликт имён колонок: {0}!!Column name collision: {0}';
ER_ONEHOT_DUPLICATE_COLUMN =
'Дублирующаяся сгенерированная колонка: {0}!!Duplicate generated column: {0}';
ER_DTENC_NO_COLUMN =
'DateTimeComponentsEncoder: столбец не указан!!DateTimeComponentsEncoder: column not specified';
ER_DTENC_NOT_DATETIME =
'DateTimeComponentsEncoder: столбец "{0}" не является DateTime!!' +
'DateTimeComponentsEncoder: column "{0}" is not DateTime';
ER_DTENC_EMPTY_PARTS =
'DateTimeComponentsEncoder: список компонент пуст!!DateTimeComponentsEncoder: date parts list is empty';
ER_DTENC_DUPLICATE_PART =
'DateTimeComponentsEncoder: компонент даты повторяется: {0}!!DateTimeComponentsEncoder: duplicate date part: {0}';
ER_DTENC_NAME_EQUALS_SOURCE =
'DateTimeComponentsEncoder: сгенерированная колонка совпадает с исходной: {0}!!DateTimeComponentsEncoder: generated column equals source column: {0}';
ER_DTENC_COLUMN_COLLISION =
'DateTimeComponentsEncoder: конфликт имён колонок: {0}!!DateTimeComponentsEncoder: column name collision: {0}';
ER_DTENC_DUPLICATE_COLUMN =
'DateTimeComponentsEncoder: дублирующаяся сгенерированная колонка: {0}!!DateTimeComponentsEncoder: duplicate generated column: {0}';
ER_DTORD_NO_COLUMN =
'DateTimeOrdinalEncoder: столбец не указан!!DateTimeOrdinalEncoder: column not specified';
ER_DTORD_NO_NEW_COLUMN =
'DateTimeOrdinalEncoder: имя новой колонки не указано!!DateTimeOrdinalEncoder: new column name not specified';
ER_DTORD_NOT_DATETIME =
'DateTimeOrdinalEncoder: столбец "{0}" не является DateTime!!' +
'DateTimeOrdinalEncoder: column "{0}" is not DateTime';
ER_DTORD_NO_VALID_VALUES =
'DateTimeOrdinalEncoder: столбец "{0}" не содержит допустимых значений!!' +
'DateTimeOrdinalEncoder: column "{0}" has no valid values';
ER_DTORD_COLUMN_COLLISION =
'DateTimeOrdinalEncoder: конфликт имён колонок: {0}!!DateTimeOrdinalEncoder: column name collision: {0}';
ER_DTCYC_NO_COLUMN =
'DateTimeCyclicEncoder: столбец не указан!!DateTimeCyclicEncoder: column not specified';
ER_DTCYC_NOT_DATETIME =
'DateTimeCyclicEncoder: столбец "{0}" не является DateTime!!' +
'DateTimeCyclicEncoder: column "{0}" is not DateTime';
ER_DTCYC_UNSUPPORTED_PART =
'DateTimeCyclicEncoder: часть даты {0} не поддерживается для циклического кодирования!!' +
'DateTimeCyclicEncoder: date part {0} is not supported for cyclic encoding';
ER_DTCYC_COLUMN_COLLISION =
'DateTimeCyclicEncoder: конфликт имён колонок: {0}!!DateTimeCyclicEncoder: column name collision: {0}';
ER_DTCYC_DUPLICATE_COLUMN =
'DateTimeCyclicEncoder: дублирующаяся сгенерированная колонка: {0}!!DateTimeCyclicEncoder: duplicate generated column: {0}';
//-----------------------------
@ -532,6 +657,548 @@ begin
Result := new OneHotEncoder(col);
end;
//-----------------------------
// DateTimeComponentsEncoder
//-----------------------------
function DateTimeComponentsEncoder.DefaultParts: array of DatePartKind;
begin
Result := [DatePartKind.dpYear, DatePartKind.dpMonth, DatePartKind.dpDay];
end;
class function DateTimeComponentsEncoder.PartSuffix(part: DatePartKind): string;
begin
case part of
dpYear: Result := 'year';
dpMonth: Result := 'month';
dpDay: Result := 'day';
dpHour: Result := 'hour';
dpMinute: Result := 'minute';
dpSecond: Result := 'second';
dpDayOfWeek: Result := 'dayofweek';
dpDate: Result := 'date';
end;
end;
constructor DateTimeComponentsEncoder.Create(column: string);
begin
if column = '' then
ArgumentError(ER_DTENC_NO_COLUMN);
col := column;
parts := DefaultParts;
fitted := false;
end;
constructor DateTimeComponentsEncoder.Create(column: string; dateParts: array of DatePartKind);
begin
if column = '' then
ArgumentError(ER_DTENC_NO_COLUMN);
if (dateParts = nil) or (dateParts.Length = 0) then
ArgumentError(ER_DTENC_EMPTY_PARTS);
col := column;
parts := Copy(dateParts);
fitted := false;
end;
function DateTimeComponentsEncoder.Fit(df: DataFrame): IPreprocessor;
begin
if df = nil then
ArgumentNullError(ER_ARG_NULL, 'df');
var idx := df.Schema.IndexOf(col);
if idx < 0 then
Error(ER_COLUMN_NOT_FOUND, col);
if df.Schema.ColumnTypeAt(idx) <> ColumnType.ctDateTime then
Error(ER_DTENC_NOT_DATETIME, col);
var usedParts := new HashSet<DatePartKind>;
for var i := 0 to parts.Length - 1 do
if usedParts.Contains(parts[i]) then
Error(ER_DTENC_DUPLICATE_PART, parts[i])
else
usedParts.Add(parts[i]);
expandedColumns := new string[parts.Length];
var usedNames := new HashSet<string>;
for var i := 0 to parts.Length - 1 do
begin
var newName := col + '_' + PartSuffix(parts[i]);
expandedColumns[i] := newName;
if newName = col then
Error(ER_DTENC_NAME_EQUALS_SOURCE, newName);
if df.HasColumn(newName) then
Error(ER_DTENC_COLUMN_COLLISION, newName);
if usedNames.Contains(newName) then
Error(ER_DTENC_DUPLICATE_COLUMN, newName);
usedNames.Add(newName);
end;
fitted := true;
Result := Self;
end;
function DateTimeComponentsEncoder.Transform(df: DataFrame): DataFrame;
begin
if not fitted then
NotFittedError(ER_FIT_NOT_CALLED);
var srcIdx := df.Schema.IndexOf(col);
if srcIdx < 0 then
ArgumentError(ER_COLUMN_NOT_FOUND, col);
if df.Schema.ColumnTypeAt(srcIdx) <> ColumnType.ctDateTime then
Error(ER_DTENC_NOT_DATETIME, col);
var rowCount := df.RowCount;
var srcCol := DateTimeColumn(df.GetColumn(srcIdx));
var srcData := srcCol.Data;
var srcValid := srcCol.IsValid;
var res := new DataFrame;
var names := new List<string>;
var types := new List<ColumnType>;
var cats := new List<boolean>;
AppendAllColumnsExcept(res, df, srcIdx, names, types, cats);
for var j := 0 to parts.Length - 1 do
begin
var part := parts[j];
var newName := expandedColumns[j];
if part = dpDate then
begin
var data := new System.DateTime[rowCount];
var valid := new boolean[rowCount];
for var row := 0 to rowCount - 1 do
if (srcValid <> nil) and not srcValid[row] then
begin
data[row] := default(System.DateTime);
valid[row] := False;
end
else
begin
data[row] := srcData[row].Date;
valid[row] := True;
end;
res.AddDateTimeColumn(newName, data, valid);
names.Add(newName);
types.Add(ColumnType.ctDateTime);
cats.Add(False);
end
else
begin
var data := new integer[rowCount];
var valid := new boolean[rowCount];
for var row := 0 to rowCount - 1 do
if (srcValid <> nil) and not srcValid[row] then
begin
data[row] := 0;
valid[row] := False;
end
else
begin
case part of
dpYear: data[row] := srcData[row].Year;
dpMonth: data[row] := srcData[row].Month;
dpDay: data[row] := srcData[row].Day;
dpHour: data[row] := srcData[row].Hour;
dpMinute: data[row] := srcData[row].Minute;
dpSecond: data[row] := srcData[row].Second;
dpDayOfWeek: data[row] := integer(srcData[row].DayOfWeek);
end;
valid[row] := True;
end;
res.AddIntColumn(newName, data, valid);
names.Add(newName);
types.Add(ColumnType.ctInt);
cats.Add(False);
end;
end;
res.SetSchema(new DataFrameSchema(
names.ToArray,
types.ToArray,
cats.ToArray
));
Result := res;
end;
function DateTimeComponentsEncoder.FitTransform(df: DataFrame): DataFrame;
begin
Fit(df);
Result := Transform(df);
end;
function DateTimeComponentsEncoder.ToString: string;
begin
Result := 'DateTimeComponentsEncoder(column=' + col + ')';
end;
function DateTimeComponentsEncoder.GetExpandedColumns(sourceColumn: string): array of string;
begin
if not fitted then
NotFittedError(ER_FIT_NOT_CALLED);
if sourceColumn <> col then
exit(nil);
Result := Copy(expandedColumns);
end;
function DateTimeComponentsEncoder.Clone: IPreprocessor;
begin
Result := new DateTimeComponentsEncoder(col, parts);
end;
//-----------------------------
// DateTimeOrdinalEncoder
//-----------------------------
class function DateTimeOrdinalEncoder.DefaultColumnName(column: string; unit_: DateTimeUnit): string;
begin
case unit_ of
dtuDays: Result := column + '_days';
dtuHours: Result := column + '_hours';
dtuMinutes: Result := column + '_minutes';
dtuSeconds: Result := column + '_seconds';
end;
end;
constructor DateTimeOrdinalEncoder.Create(column: string);
begin
Create(column, DefaultColumnName(column, DateTimeUnit.dtuDays), DateTimeUnit.dtuDays);
end;
constructor DateTimeOrdinalEncoder.Create(column, newColumn: string);
begin
Create(column, newColumn, DateTimeUnit.dtuDays);
end;
constructor DateTimeOrdinalEncoder.Create(column, newColumn: string; unit_: DateTimeUnit);
begin
if column = '' then
ArgumentError(ER_DTORD_NO_COLUMN);
if newColumn = '' then
ArgumentError(ER_DTORD_NO_NEW_COLUMN);
col := column;
newCol := newColumn;
self.unit_ := unit_;
fitted := false;
end;
function DateTimeOrdinalEncoder.Fit(df: DataFrame): IPreprocessor;
begin
if df = nil then
ArgumentNullError(ER_ARG_NULL, 'df');
var idx := df.Schema.IndexOf(col);
if idx < 0 then
Error(ER_COLUMN_NOT_FOUND, col);
if df.Schema.ColumnTypeAt(idx) <> ColumnType.ctDateTime then
Error(ER_DTORD_NOT_DATETIME, col);
if (newCol <> col) and df.HasColumn(newCol) then
Error(ER_DTORD_COLUMN_COLLISION, newCol);
var cur := df.GetCursor;
var hasValue := false;
var minValue := default(System.DateTime);
while cur.MoveNext do
if cur.IsValid(idx) then
begin
var dt := cur.DateTime(idx);
if not hasValue or (dt < minValue) then
begin
minValue := dt;
hasValue := true;
end;
end;
if not hasValue then
Error(ER_DTORD_NO_VALID_VALUES, col);
fOrigin := minValue;
fitted := true;
Result := Self;
end;
function DateTimeOrdinalEncoder.Transform(df: DataFrame): DataFrame;
begin
if not fitted then
NotFittedError(ER_FIT_NOT_CALLED);
var idx := df.Schema.IndexOf(col);
if idx < 0 then
ArgumentError(ER_COLUMN_NOT_FOUND, col);
if df.Schema.ColumnTypeAt(idx) <> ColumnType.ctDateTime then
Error(ER_DTORD_NOT_DATETIME, col);
var srcCol := DateTimeColumn(df.GetColumn(idx));
var rowCount := df.RowCount;
var data := new real[rowCount];
var valid := new boolean[rowCount];
for var row := 0 to rowCount - 1 do
if (srcCol.IsValid <> nil) and not srcCol.IsValid[row] then
begin
data[row] := 0.0;
valid[row] := False;
end
else
begin
var delta := srcCol.Data[row] - fOrigin;
case unit_ of
dtuDays: data[row] := delta.TotalDays;
dtuHours: data[row] := delta.TotalHours;
dtuMinutes: data[row] := delta.TotalMinutes;
dtuSeconds: data[row] := delta.TotalSeconds;
end;
valid[row] := True;
end;
var res := new DataFrame;
var names := new List<string>;
var types := new List<ColumnType>;
var cats := new List<boolean>;
for var i := 0 to df.ColumnCount - 1 do
if i <> idx then
begin
res.AddColumnAlias(df.GetColumn(i));
names.Add(df.Schema.NameAt(i));
types.Add(df.Schema.ColumnTypeAt(i));
cats.Add(df.Schema.IsCategoricalAt(i));
end
else
begin
res.AddFloatColumn(newCol, data, valid);
names.Add(newCol);
types.Add(ColumnType.ctFloat);
cats.Add(False);
end;
res.SetSchema(new DataFrameSchema(
names.ToArray,
types.ToArray,
cats.ToArray
));
Result := res;
end;
function DateTimeOrdinalEncoder.FitTransform(df: DataFrame): DataFrame;
begin
Fit(df);
Result := Transform(df);
end;
function DateTimeOrdinalEncoder.ToString: string;
begin
Result := 'DateTimeOrdinalEncoder(column=' + col + ', newColumn=' + newCol + ', unit=' + unit_.ToString + ')';
end;
function DateTimeOrdinalEncoder.Clone: IPreprocessor;
begin
Result := new DateTimeOrdinalEncoder(col, newCol, unit_);
end;
//-----------------------------
// DateTimeCyclicEncoder
//-----------------------------
class function DateTimeCyclicEncoder.PartSuffix(part: DatePartKind): string;
begin
case part of
dpMonth: Result := 'month';
dpDayOfWeek: Result := 'dayofweek';
dpHour: Result := 'hour';
dpMinute: Result := 'minute';
dpSecond: Result := 'second';
dpTimeOfDay: Result := 'timeofday';
end;
end;
class function DateTimeCyclicEncoder.IsSupportedPart(part: DatePartKind): boolean;
begin
Result := part in [dpMonth, dpDayOfWeek, dpHour, dpMinute, dpSecond, dpTimeOfDay];
end;
class function DateTimeCyclicEncoder.PartPeriod(part: DatePartKind): integer;
begin
case part of
dpMonth: Result := 12;
dpDayOfWeek: Result := 7;
dpHour: Result := 24;
dpMinute: Result := 60;
dpSecond: Result := 60;
dpTimeOfDay: Result := 86400;
else
Error(ER_DTCYC_UNSUPPORTED_PART, part);
end;
end;
class function DateTimeCyclicEncoder.PartValue(dt: System.DateTime; part: DatePartKind): real;
begin
case part of
dpMonth: Result := dt.Month - 1;
dpDayOfWeek: Result := integer(dt.DayOfWeek);
dpHour: Result := dt.Hour;
dpMinute: Result := dt.Minute;
dpSecond: Result := dt.Second;
dpTimeOfDay:
Result := dt.Hour * 3600.0 + dt.Minute * 60.0 + dt.Second + dt.Millisecond / 1000.0;
else
Error(ER_DTCYC_UNSUPPORTED_PART, part);
end;
end;
constructor DateTimeCyclicEncoder.Create(column: string; part: DatePartKind);
begin
if column = '' then
ArgumentError(ER_DTCYC_NO_COLUMN);
if not IsSupportedPart(part) then
Error(ER_DTCYC_UNSUPPORTED_PART, part);
col := column;
self.part := part;
var suffix := PartSuffix(part);
sinCol := col + '_cyc_' + suffix + '_sin';
cosCol := col + '_cyc_' + suffix + '_cos';
fitted := false;
end;
function DateTimeCyclicEncoder.Fit(df: DataFrame): IPreprocessor;
begin
if df = nil then
ArgumentNullError(ER_ARG_NULL, 'df');
var idx := df.Schema.IndexOf(col);
if idx < 0 then
Error(ER_COLUMN_NOT_FOUND, col);
if df.Schema.ColumnTypeAt(idx) <> ColumnType.ctDateTime then
Error(ER_DTCYC_NOT_DATETIME, col);
if df.HasColumn(sinCol) then
Error(ER_DTCYC_COLUMN_COLLISION, sinCol);
if df.HasColumn(cosCol) then
Error(ER_DTCYC_COLUMN_COLLISION, cosCol);
if sinCol = cosCol then
Error(ER_DTCYC_DUPLICATE_COLUMN, sinCol);
fitted := true;
Result := Self;
end;
function DateTimeCyclicEncoder.Transform(df: DataFrame): DataFrame;
begin
if not fitted then
NotFittedError(ER_FIT_NOT_CALLED);
var srcIdx := df.Schema.IndexOf(col);
if srcIdx < 0 then
ArgumentError(ER_COLUMN_NOT_FOUND, col);
if df.Schema.ColumnTypeAt(srcIdx) <> ColumnType.ctDateTime then
Error(ER_DTCYC_NOT_DATETIME, col);
var rowCount := df.RowCount;
var srcCol := DateTimeColumn(df.GetColumn(srcIdx));
var srcData := srcCol.Data;
var srcValid := srcCol.IsValid;
var period := PartPeriod(part);
var sinData := new real[rowCount];
var cosData := new real[rowCount];
var sinValid := new boolean[rowCount];
var cosValid := new boolean[rowCount];
for var row := 0 to rowCount - 1 do
if (srcValid <> nil) and not srcValid[row] then
begin
sinData[row] := 0.0;
cosData[row] := 0.0;
sinValid[row] := False;
cosValid[row] := False;
end
else
begin
var value := PartValue(srcData[row], part);
var angle := 2 * PI * value / period;
sinData[row] := Sin(angle);
cosData[row] := Cos(angle);
sinValid[row] := True;
cosValid[row] := True;
end;
var res := new DataFrame;
var names := new List<string>;
var types := new List<ColumnType>;
var cats := new List<boolean>;
AppendAllColumnsExcept(res, df, srcIdx, names, types, cats);
res.AddFloatColumn(sinCol, sinData, sinValid);
names.Add(sinCol);
types.Add(ColumnType.ctFloat);
cats.Add(False);
res.AddFloatColumn(cosCol, cosData, cosValid);
names.Add(cosCol);
types.Add(ColumnType.ctFloat);
cats.Add(False);
res.SetSchema(new DataFrameSchema(
names.ToArray,
types.ToArray,
cats.ToArray
));
Result := res;
end;
function DateTimeCyclicEncoder.FitTransform(df: DataFrame): DataFrame;
begin
Fit(df);
Result := Transform(df);
end;
function DateTimeCyclicEncoder.ToString: string;
begin
Result := 'DateTimeCyclicEncoder(column=' + col + ', part=' + part.ToString + ')';
end;
function DateTimeCyclicEncoder.GetExpandedColumns(sourceColumn: string): array of string;
begin
if not fitted then
NotFittedError(ER_FIT_NOT_CALLED);
if sourceColumn <> col then
exit(nil);
Result := [sinCol, cosCol];
end;
function DateTimeCyclicEncoder.Clone: IPreprocessor;
begin
Result := new DateTimeCyclicEncoder(col, part);
end;
//-----------------------------
// Imputer
//-----------------------------