Skip to content

Divergence stat tests crash or silently return wrong results on edge case inputs #1900

Description

@KunalG67

Running drift checks on a feature column that had no data in a
particular monitoring window (upstream pipeline failed). Instead of
a meaningful error I got a ZeroDivisionError buried several frames
deep inside get_binned_data, which took a while to trace back.

After digging into it there are actually a few related issues across
the divergence-based stat tests.

Empty input series

psi_stat_test, kl_div_stat_test, jensenshannon_stat_test and
hellinger_stat_test all behave differently when given an empty series:

  • psi_stat_test and kl_div_stat_test raise ZeroDivisionError
  • jensenshannon_stat_test raises ZeroDivisionError or silently
    returns (0.0, False) depending on which series is empty
  • hellinger_stat_test silently returns (nan, False) or (1.0, True)
    depending on feature type, no exception at all

Quick repro:

import pandas as pd
from evidently.legacy.core import ColumnType
from evidently.legacy.calculations.stattests import psi_stat_test

psi_stat_test(pd.Series([], dtype=float), pd.Series([1.0, 2.0, 3.0]), ColumnType.Numerical, 0.1)

get_one_column_drift already guards against this at the higher level
but the individual functions are all in all with no documented
precondition on input size.

Both series empty crashes differently

When both series are empty, get_binned_data hits a different failure
path - the feel_zeroes block calls min() on an empty array:

ValueError: min() arg is an empty sequence

So the same input produces two different exceptions depending on
which series is empty vs both being empty.

Out of range values silently bucketed into boundary bin

When current data is completely outside the reference data range,
numpy histogram silently dumps all values into the last bin instead
of indicating that the distributions don't overlap at all:

import pandas as pd
from evidently.legacy.core import ColumnType
from evidently.legacy.calculations.stattests import psi_stat_test

ref = pd.Series(list(range(1, 50)))
cur = pd.Series([1000.0] * 49)
print(psi_stat_test(ref, cur, ColumnType.Numerical, 0.1))

Drift is detected but the score is computed on wrong bin counts
since all current values land in the last bin. No warning is raised.

All four tests are publicly exported with no documented precondition
on input size or range, so users have no way to know these inputs
are unsupported.

Thanks !!!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions