Agent Wranglers

MLO-34Required

Measure every metric for each group of people

For a model that scores or ranks people, compute the headline metric, error rates and calibration per group. List the groups, such as age band, sex or country. Block the release when the gap between groups passes the limit you wrote down.

How to do it

A good total can hide a model that fails one group, and unequal treatment can break discrimination law. List the groups from the model's purpose and the protected grounds in the law of each market. Pick the measure from the decision. Equal recall suits a model that grants something, and equal false positive rates suit one that flags people. Write the limit, such as 5 percentage points, in the release gate. Repeat the check on live outcomes in Measure live quality once the outcomes arrive. A high-risk system must examine its data for such bias under AI Act Article 10(2). Where you need sensitive traits to measure this, follow Hold sensitive traits for bias checks apart from training.

Examples

Python example

import pandas as pd

GAP_LIMIT = 0.05

def recall_by_group(df: pd.DataFrame, group: str) -> pd.Series:
    positives = df[df["label"] == 1]
    return positives.groupby(group)["predicted"].mean()

def group_gap_failures(df: pd.DataFrame, groups: list[str]) -> list[str]:
    failures = []
    for group in groups:
        recall = recall_by_group(df, group)
        if recall.max() - recall.min() > GAP_LIMIT:
            failures.append(f"{group}: recall gap {recall.max() - recall.min():.3f}")
    return failures

TypeScript example

const GAP_LIMIT = 0.05;
type Row = { label: boolean; predicted: boolean; groups: Record<string, string> };

export function recallGap(rows: Row[], group: string): number {
  const byGroup = new Map<string, { hit: number; total: number }>();
  for (const row of rows.filter(r => r.label)) {
    const key = row.groups[group];
    const entry = byGroup.get(key) ?? { hit: 0, total: 0 };
    entry.total += 1; entry.hit += row.predicted ? 1 : 0;
    byGroup.set(key, entry);
  }
  const recalls = [...byGroup.values()].map(e => e.hit / e.total);
  return Math.max(...recalls) - Math.min(...recalls);
}

export const groupGapFailures = (rows: Row[], groups: string[]) => groups.filter(g => recallGap(rows, g) > GAP_LIMIT);

How to check

Every evaluation report of a model that scores people has a table per listed group with its count, metric, error rates and interval. The gate fails when a gap passes the limit or a group has no row.

Sources

For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/machine-learning/people-affected-by-the-model/per-group-metrics">Agent Wranglers machine learning playbook</a>. In code you write, add a comment next to it with the same link.