Algorithm-Classifier-IsolationForest

 view release on metacpan or  search on metacpan

t/80-sklearn-comparison.t  view on Meta::CPAN

}

# -----------------------------------------------------------------------
# Per-dataset test battery
# -----------------------------------------------------------------------
sub run_dataset_tests {
	my ( $ds, $sk_all, $use_c ) = @_;

	my $label = $ds->{label};
	my $n_in  = $ds->{n_in};
	my $n_out = $ds->{n_out};

	my $f = $CLASS->new(
		n_trees     => 100,
		sample_size => 256,
		seed        => 42,
		use_c       => $use_c
	);
	$f->fit( $ds->{data} );

	my $perl_in_scores  = $f->score_samples( $ds->{inliers} );
	my $perl_out_scores = $f->score_samples( $ds->{outliers} );
	my $perl_all_scores = $f->score_samples( $ds->{data} );

	my @sk_in  = @{$sk_all}[ 0 .. $n_in - 1 ];
	my @sk_out = @{$sk_all}[ $n_in .. $n_in + $n_out - 1 ];

	subtest 'Perl: outliers score clearly higher than inliers' => sub {
		cmp_ok(
			mean(@$perl_out_scores), '>',
			mean(@$perl_in_scores) + 0.2,
			'mean outlier Perl score exceeds mean inlier score by at least 0.2'
		);
		cmp_ok( min(@$perl_out_scores), '>', max(@$perl_in_scores),
			'every outlier has a strictly higher Perl score than every inlier' );
	};

	subtest 'sklearn: outliers score clearly lower (more anomalous) than inliers' => sub {
		cmp_ok( mean(@sk_out), '<', mean(@sk_in),
			'mean outlier sklearn score is lower (more anomalous) than mean inlier score' );
		cmp_ok( max(@sk_out), '<', min(@sk_in),
			'every outlier has a strictly lower sklearn score than every inlier' );
	};

	subtest "both models rank all $n_out outliers in the top-$n_out anomalies" => sub {
		# Perl: sort by descending score; highest scores are the most anomalous.
		my @perl_rank = sort { $perl_all_scores->[$b] <=> $perl_all_scores->[$a] } 0 .. $#$perl_all_scores;
		my %perl_top  = map  { $_ => 1 } @perl_rank[ 0 .. $n_out - 1 ];

		# sklearn: sort by ascending score; lowest scores are the most anomalous.
		my @sk_rank = sort { $sk_all->[$a] <=> $sk_all->[$b] } 0 .. $#$sk_all;
		my %sk_top  = map  { $_ => 1 } @sk_rank[ 0 .. $n_out - 1 ];

		my $perl_caught = grep { $perl_top{$_} } $n_in .. $n_in + $n_out - 1;
		my $sk_caught   = grep { $sk_top{$_} } $n_in .. $n_in + $n_out - 1;

		is( $perl_caught, $n_out, "Perl top-$n_out contains all $n_out outlier points" );
		is( $sk_caught,   $n_out, "sklearn top-$n_out contains all $n_out outlier points" );
	}; ## end "both models rank all $n_out outliers in the top-$n_out anomalies" => sub

	subtest 'Perl predict at 0.5 threshold flags all outliers and almost no inliers' => sub {
		my $in_labels  = $f->predict( $ds->{inliers} );
		my $out_labels = $f->predict( $ds->{outliers} );

		is( sum(@$out_labels), $n_out, "Perl predict() flags all $n_out outliers at the 0.5 threshold" );
		cmp_ok( sum(@$in_labels), '<', 0.05 * $n_in, 'fewer than 5% of inliers are flagged by Perl predict()' );
	};

	subtest 'Spearman rank correlation between Perl and sklearn scores >= 0.85' => sub {
		# Negate sklearn scores so both vectors point in the same direction
		# (higher value = more anomalous) before ranking.
		my @neg_sk = map { -$_ } @$sk_all;
		my $rho    = spearman_rho( $perl_all_scores, \@neg_sk );
		cmp_ok( $rho, '>=', 0.85, sprintf( 'Spearman rho(Perl, -sklearn) = %.4f (must be >= 0.85)', $rho ) );
	};
} ## end sub run_dataset_tests

# -----------------------------------------------------------------------
# Run the battery for each dataset
# -----------------------------------------------------------------------
for my $be (@BACKENDS) {
	my ( $be_name, $USE_C ) = @$be;
	for my $ds (@datasets) {
		my $sk_all = $py->{ $ds->{label} };
		unless ( ref $sk_all eq 'ARRAY' && @$sk_all == @{ $ds->{data} } ) {
			fail("sklearn output missing or wrong length for dataset '$ds->{label}'");
			next;
		}
		subtest "[$be_name] $ds->{label} ($ds->{n_feat} features)" => sub {
			run_dataset_tests( $ds, $sk_all, $USE_C );
		};
	} ## end for my $ds (@datasets)
} ## end for my $be (@BACKENDS)

done_testing;



( run in 0.936 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )