Algorithm-Classifier-IsolationForest
view release on metacpan or search on metacpan
t/21-fit-from-csv.t view on Meta::CPAN
for ( 1 .. 16 ) { push @rows, [ 15 + rand(), 15 + rand() ]; push @truth, 1 }
my $csv = write_csv( \@rows );
my %args = (
n_trees => 100,
sample_size => 256,
seed => 42,
contamination => 16 / scalar(@rows),
use_c => $USE_C,
);
my $stream = $CLASS->new(%args);
$stream->fit_from_csv($csv);
my $mem = $CLASS->new(%args);
$mem->fit( \@rows );
my $sp = $stream->predict( \@rows );
my $mp = $mem->predict( \@rows );
is_deeply( $sp, $mp, 'fit_from_csv and fit flag identical anomalies' );
is_deeply( $sp, \@truth, 'and both flag exactly the true outliers' );
}; ## end "[$be_name] same verdicts as fit() on the same data" => sub
subtest "[$be_name] deterministic given seed" => sub {
my ($rows) = make_dataset();
my $csv = write_csv($rows);
my %args = ( n_trees => 40, sample_size => 128, seed => 7, contamination => 0.05, use_c => $USE_C );
my $a = $CLASS->new(%args);
$a->fit_from_csv($csv);
my $b = $CLASS->new(%args);
$b->fit_from_csv($csv);
is( $a->to_json, $b->to_json, 'identical model across two runs' );
}; ## end "[$be_name] deterministic given seed" => sub
subtest "[$be_name] contamination threshold is exact" => sub {
my ($rows) = make_dataset();
my $csv = write_csv($rows);
# Against the very same forest, the streaming learner must land on the
# identical cut the batch learner computes over all rows.
for my $cont ( 0.01, 0.05, 0.1, 0.25, 0.5 ) {
my $m = $CLASS->new(
n_trees => 60,
sample_size => 128,
seed => 5,
contamination => $cont,
use_c => $USE_C,
);
$m->fit_from_csv($csv);
my $streamed = $m->decision_threshold;
ok( defined $streamed, "contamination=$cont learned a threshold" );
delete @$m{qw(_c_nodes _c_coef_idx _c_coef_val)};
$m->_learn_contamination_threshold($rows);
my $batch = $m->decision_threshold;
ok( abs( $streamed - $batch ) < 1e-12, "contamination=$cont matches batch learner" );
} ## end for my $cont ( 0.01, 0.05, 0.1, 0.25, 0.5 )
}; ## end "[$be_name] contamination threshold is exact" => sub
subtest "[$be_name] tied scores across the boundary" => sub {
# Identical rows produce identical scores, forcing a tie block straddling
# the contamination rank -- the streaming learner's rare second pass.
my @rows = ( ( [ 1, 1 ] ) x 40, map { [ 5 + $_ / 10, 5 + $_ / 10 ] } 1 .. 10 );
my $csv = write_csv( \@rows );
my $m = $CLASS->new(
n_trees => 40,
sample_size => 64,
seed => 9,
contamination => 0.2,
use_c => $USE_C,
);
$m->fit_from_csv($csv);
my $streamed = $m->decision_threshold;
delete @$m{qw(_c_nodes _c_coef_idx _c_coef_val)};
$m->_learn_contamination_threshold( \@rows );
ok( abs( $streamed - $m->decision_threshold ) < 1e-12, 'tie-block threshold is exact' );
}; ## end "[$be_name] tied scores across the boundary" => sub
subtest "[$be_name] missing-value strategies" => sub {
my @rows = ( [ 1.0, 2.0 ], [ 1.1, undef ], [ undef, 2.1 ], [ 1.2, 2.2 ], [ 1.15, 2.15 ] );
my $csv = write_csv( \@rows );
# sample_size >= row count => every row (including the gaps) trains, so
# 'die' rejects the missing cell it finds in a sampled row.
my $die = $CLASS->new( n_trees => 8, sample_size => 8, seed => 1, missing => 'die', use_c => $USE_C );
like( exception { $die->fit_from_csv($csv) }, qr/missing value/, "die strategy rejects gaps" );
for my $mode (qw(zero nan impute)) {
my $m = $CLASS->new( n_trees => 8, sample_size => 4, seed => 1, missing => $mode, use_c => $USE_C );
is( exception { $m->fit_from_csv($csv) }, undef, "$mode strategy fits" );
is( scalar @{ $m->{trees} }, 8, "$mode built its trees" );
if ( $mode eq 'impute' ) {
is( scalar @{ $m->{missing_fill} }, 2, 'impute learned a fill vector' );
}
my $s = $m->score_samples( [ [ 1.05, undef ], [ undef, 2.05 ] ] );
is( scalar @$s, 2, "$mode scores rows with gaps" );
} ## end for my $mode (qw(zero nan impute))
}; ## end "[$be_name] missing-value strategies" => sub
subtest "[$be_name] header option and feature_names" => sub {
my @rows = map { [ $_, $_ + 1 ] } 1 .. 30;
my $csv = write_csv( \@rows, 'alpha,beta' );
my $m = $CLASS->new( n_trees => 10, sample_size => 8, seed => 1, use_c => $USE_C );
$m->fit_from_csv( $csv, header => 1 );
is( $m->{n_features}, 2, 'header line skipped, width from data' );
my $named = $CLASS->new(
n_trees => 10,
sample_size => 8,
seed => 1,
feature_names => [qw(alpha beta)],
use_c => $USE_C,
);
is( exception { $named->fit_from_csv( $csv, header => 1 ) }, undef, 'matching feature_names fits' );
my $bad = $CLASS->new(
n_trees => 10,
sample_size => 8,
seed => 1,
feature_names => [qw(alpha beta gamma)],
use_c => $USE_C,
( run in 1.324 second using v1.01-cache-2.11-cpan-d01c6094234 )