Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 77 additions & 40 deletions diffly/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,51 +31,41 @@ def _make_numeric_metric(fn: MetricFn) -> Metric:
return Metric(fn=fn, selector=cs.numeric())


def mean(left: pl.Expr, right: pl.Expr) -> pl.Expr:
"""Mean of ``right - left``."""
return (right - left).mean()


def median(left: pl.Expr, right: pl.Expr) -> pl.Expr:
"""Median of ``right - left``."""
return (right - left).median()


def min(left: pl.Expr, right: pl.Expr) -> pl.Expr:
"""Minimum of ``right - left``."""
return (right - left).min()


def max(left: pl.Expr, right: pl.Expr) -> pl.Expr:
"""Maximum of ``right - left``."""
return (right - left).max()


def std(left: pl.Expr, right: pl.Expr) -> pl.Expr:
"""Standard deviation of ``right - left``."""
return (right - left).std()


def mean_absolute_deviation(left: pl.Expr, right: pl.Expr) -> pl.Expr:
"""Mean of ``|right - left|``."""
return (right - left).abs().mean()
# ------------------------------------ FORMATTING ------------------------------------ #


def _signed(value: pl.Expr, body: pl.Expr) -> pl.Expr:
"""Prefix ``body`` with a sign based on ``value``: ``+`` if positive, ``-`` (already
part of ``body``) if negative, ``±`` if zero."""
return (
pl.when(value > 0)
.then(pl.format("+{}", body))
.when(value < 0)
.then(body)
.otherwise(pl.format("±{}", body))
)


def mean_relative_deviation(left: pl.Expr, right: pl.Expr) -> pl.Expr:
"""Mean of ``|(right - left) / left|``. Yields ``inf`` or ``null`` where
``left`` is zero."""
return ((right - left) / left).abs().mean()
def _number_string(
value: pl.Expr,
*,
signed: bool = False,
round: Callable[[pl.Expr], pl.Expr] = lambda value: value.round_sig_figs(4),
) -> pl.Expr:
"""Format a numeric value with the given rounding, optionally with a sign."""
rounded = round(value)
body = rounded.cast(pl.String)
return _signed(rounded, body) if signed else body


def _percentage_string(
fraction: pl.Expr, *, signed: bool = False, percent_sign: bool = True
) -> pl.Expr:
"""Format a fraction as a percentage string, optionally with an explicit sign."""
pct = (fraction * 100).round(2)
body = pl.format("{}%", pct) if percent_sign else pl.format("{}", pct)
if signed:
return pl.when(pct >= 0).then(pl.format("+{}", body)).otherwise(body)
return body
body = _number_string(
fraction * 100, signed=signed, round=lambda value: value.round(2)
)
return pl.format("{}%", body) if percent_sign else body


def _render_change(
Expand All @@ -97,6 +87,53 @@ def _render_change(
)


def _render_number_change(old: pl.Expr, new: pl.Expr) -> pl.Expr:
"""Render a numeric change as ``<old> -> <new> (<delta>)``."""
return _render_change(
old, new, lambda value, signed: _number_string(value, signed=signed)
)


# -------------------------------------- METRICS ------------------------------------- #


def mean(left: pl.Expr, right: pl.Expr) -> pl.Expr:
"""Change in mean, rendered as ``<mean(left)> -> <mean(right)> (<delta>)``."""
return _render_number_change(left.mean(), right.mean())


def median(left: pl.Expr, right: pl.Expr) -> pl.Expr:
"""Change in median, rendered as ``<median(left)> -> <median(right)> (<delta>)``."""
return _render_number_change(left.median(), right.median())


def min(left: pl.Expr, right: pl.Expr) -> pl.Expr:
"""Change in minimum, rendered as ``<min(left)> -> <min(right)> (<delta>)``."""
return _render_number_change(left.min(), right.min())


def max(left: pl.Expr, right: pl.Expr) -> pl.Expr:
"""Change in maximum, rendered as ``<max(left)> -> <max(right)> (<delta>)``."""
return _render_number_change(left.max(), right.max())


def std(left: pl.Expr, right: pl.Expr) -> pl.Expr:
"""Change in standard deviation, rendered as ``<std(left)> -> <std(right)>
(<delta>)``."""
return _render_number_change(left.std(), right.std())


def mean_absolute_deviation(left: pl.Expr, right: pl.Expr) -> pl.Expr:
"""Mean of ``|right - left|``."""
return (right - left).abs().mean()


def mean_relative_deviation(left: pl.Expr, right: pl.Expr) -> pl.Expr:
"""Mean of ``|(right - left) / left|``. Yields ``inf`` or ``null`` where
``left`` is zero."""
return ((right - left) / left).abs().mean()


def null_fraction_change(left: pl.Expr, right: pl.Expr) -> pl.Expr:
"""Change in the fraction of null entries, rendered as ``<old> -> <new> (<delta>)``.

Expand All @@ -115,13 +152,13 @@ def null_fraction_change(left: pl.Expr, right: pl.Expr) -> pl.Expr:


def quantile(q: float) -> MetricFn:
"""Factory returning a metric that computes the ``q``-quantile of
``right - left``."""
"""Factory returning a metric for the change in the ``q``-quantile, rendered as
``<quantile(left)> -> <quantile(right)> (<delta>)``."""
if not 0 <= q <= 1:
raise ValueError(f"q must be in [0, 1], got {q}")

def _quantile(left: pl.Expr, right: pl.Expr) -> pl.Expr:
return (right - left).quantile(q)
return _render_number_change(left.quantile(q), right.quantile(q))

return _quantile

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@

Columns
▔▔▔▔▔▔▔
┏━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Column ┃ Match Rate ┃ mean_delta ┃ p95_delta ┃ max_abs_delta ┃
┡━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ price │ 60.00% │ 0.6 │ 2 │ 2 │
│ qty │ 80.00% │ 0.2 │ 1 │ 1 │
│ status │ 80.00% │ │ │
└────────┴────────────┴───────────────────────┴───────────────┘
┏━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Column ┃ Match Rate ┃ mean_delta ┃ p95_delta ┃ max_abs_delta ┃
┡━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ price │ 60.00% │ 30.0 -> 30.6 (+0.6)50.0 -> 50.0 (±0.0) │ 2 │
│ qty │ 80.00% │ 3.0 -> 3.2 (+0.2)5.0 -> 5.0 (±0.0) │ 1 │
│ status │ 80.00% │ │ │
└────────┴────────────┴─────────────────────┴─────────────────────┴───────────────┘
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@

Columns
▔▔▔▔▔▔▔
┏━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Column ┃ Match Rate ┃ mean_delta ┃ p95_delta ┃ max_abs_delta ┃
┡━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ price │ 60.00% │ 0.6 │ 2 │ 2 │
│ qty │ 80.00% │ 0.2 │ 1 │ 1 │
│ status │ 80.00% │ │ │
└────────┴────────────┴───────────────────────┴───────────────┘
┏━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Column ┃ Match Rate ┃ mean_delta ┃ p95_delta ┃ max_abs_delta ┃
┡━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ price │ 60.00% │ 30.0 -> 30.6 (+0.6)50.0 -> 50.0 (±0.0) │ 2 │
│ qty │ 80.00% │ 3.0 -> 3.2 (+0.2)5.0 -> 5.0 (±0.0) │ 1 │
│ status │ 80.00% │ │ │
└────────┴────────────┴─────────────────────┴─────────────────────┴───────────────┘
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@

Columns
▔▔▔▔▔▔▔
┏━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Column ┃ Match Rate ┃ mean_delta ┃ p95_delta ┃ max_abs_delta ┃
┡━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ price │ 60.00% │ 0.6 │ 2 │ 2 │
│ qty │ 80.00% │ 0.2 │ 1 │ 1 │
│ status │ 80.00% │ │ │
└────────┴────────────┴───────────────────────┴───────────────┘
┏━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Column ┃ Match Rate ┃ mean_delta ┃ p95_delta ┃ max_abs_delta ┃
┡━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ price │ 60.00% │ 30.0 -> 30.6 (+0.6)50.0 -> 50.0 (±0.0) │ 2 │
│ qty │ 80.00% │ 3.0 -> 3.2 (+0.2)5.0 -> 5.0 (±0.0) │ 1 │
│ status │ 80.00% │ │ │
└────────┴────────────┴─────────────────────┴─────────────────────┴───────────────┘
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@

Columns
▔▔▔▔▔▔▔
┏━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Column ┃ Match Rate ┃ mean_delta ┃ p95_delta ┃ max_abs_delta ┃
┡━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ price │ 60.00% │ 0.6 │ 2 │ 2 │
│ qty │ 80.00% │ 0.2 │ 1 │ 1 │
│ status │ 80.00% │ │ │
└────────┴────────────┴───────────────────────┴───────────────┘
┏━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Column ┃ Match Rate ┃ mean_delta ┃ p95_delta ┃ max_abs_delta ┃
┡━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ price │ 60.00% │ 30.0 -> 30.6 (+0.6)50.0 -> 50.0 (±0.0) │ 2 │
│ qty │ 80.00% │ 3.0 -> 3.2 (+0.2)5.0 -> 5.0 (±0.0) │ 1 │
│ status │ 80.00% │ │ │
└────────┴────────────┴─────────────────────┴─────────────────────┴───────────────┘
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@

Columns
▔▔▔▔▔▔▔
┏━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓
┃ Column ┃ Match Rate ┃ mean_delta ┃ p95_delta ┃ max_abs_delta ┃ Top Changes ┃
┡━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩
│ price │ 60.00% │ 0.6 │ 2 │ 2 │ 40.0 -> 42.0 (1x) │
│ │ │ │ │ │ 20.0 -> 21.0 (1x) │
├────────┼────────────┼────────────┼───────────┼───────────────┼───────────────────┤
│ qty │ 80.00% │ 0.2 │ 1 │ 1 │ 4 -> 5 (1x) │
├────────┼────────────┼────────────┼───────────┼───────────────┼───────────────────┤
│ status │ 80.00% │ │ │ │ "c" -> "x" (1x) │
└────────┴────────────┴────────────┴───────────┴───────────────┴───────────────────┘
┏━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Column ┃ Match Rate ┃ mean_delta ┃ p95_delta ┃ max_abs_delta ┃ Top Changes ┃
┡━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ price │ 60.00% │ 30.0 -> 30.6 │ 50.0 -> 50.0 │ 2 │ 40.0 -> 42.0 │
│ │ │ (+0.6) │ (±0.0) │ │ (1x) │
│ │ │ │ │ │ 20.0 -> 21.0 │
│ │ │ │ │ │ (1x) │
├────────┼────────────┼───────────────┼──────────────┼───────────────┼──────────────┤
│ qty │ 80.00% │ 3.0 -> 3.2 │ 5.0 -> 5.0 │ 1 │ 4 -> 5 (1x) │
│ │ │ (+0.2) │ (±0.0) │ │ │
├────────┼────────────┼───────────────┼──────────────┼───────────────┼──────────────┤
│ status │ 80.00% │ │ │ │ "c" -> "x" │
│ │ │ │ │ │ (1x) │
└────────┴────────────┴───────────────┴──────────────┴───────────────┴──────────────┘
Loading