# Encode categories by the target

Shape / columns

A variant of [Encode categories as numbers](https://meridian.online/operations/encode-categories-as-numbers).

Replace each category with a statistic of the label for that category, such as its mean or weight of evidence.

Page: <https://meridian.online/operations/encode-categories-as-numbers/encode-categories-by-the-target>

## Status

Runs as a SQL step.

arcform carries it as a `sql:` step today.

## The DuckDB step

replace each category with the mean of the label and its weight of evidence

```sql
CREATE TEMP TABLE labelled AS SELECT * FROM (VALUES
  (1, 'A', 1), (2, 'A', 1), (3, 'A', 0), (4, 'A', 1),
  (5, 'B', 0), (6, 'B', 0), (7, 'B', 1),
  (8, 'C', 0), (9, 'C', 1),
  (10, 'D', 1), (11, 'E', 0)
) t(id, category, label);
-- weight of evidence is undefined for a category with no events or no non-events (D and E), so it is NULL there and the
-- mean label, which is defined, still encodes them
CREATE TEMP TABLE result AS
WITH per_category AS (
  SELECT category, avg(label) AS mean_label, sum(label) AS events, count(*) - sum(label) AS non_events FROM labelled GROUP BY category),
totals AS (SELECT sum(events) AS events, sum(non_events) AS non_events FROM per_category)
SELECT l.id, l.category, l.label, round(c.mean_label, 4) AS target_mean,
       round(CASE WHEN c.events = 0 OR c.non_events = 0 THEN NULL
                  ELSE ln((c.events / t.events) / (c.non_events / t.non_events)) END, 4) AS woe
FROM labelled l JOIN per_category c USING (category) CROSS JOIN totals t;
SELECT * FROM result ORDER BY id;
SELECT CASE WHEN ((SELECT count(*) FROM result) = 11 AND (SELECT [target_mean, woe] FROM result WHERE id = 1) = [0.75, 0.9163] AND (SELECT [target_mean, woe] FROM result WHERE id = 5) = [0.3333, -0.8755] AND (SELECT [target_mean, woe] FROM result WHERE id = 9) = [0.5, -0.1823] AND (SELECT count(DISTINCT woe) FROM result WHERE category = 'A') = 1 AND (SELECT [target_mean::VARCHAR, woe::VARCHAR] FROM result WHERE id = 10) = ['1.0', NULL] AND (SELECT [target_mean::VARCHAR, woe::VARCHAR] FROM result WHERE id = 11) = ['0.0', NULL]) THEN 'as expected' ELSE error('the categories were not replaced by their mean label and weight of evidence, or a category with one label did not get a mean and no weight of evidence') END AS verdict;
```

The statement builds its own data.

It needs nothing installed beyond DuckDB itself.

## What other tools call it

Meridian's classification; an independent blind rating agreed on 95.7% of 324 rows (variant, version 3).

- RapidMiner Studio: [`Target Encoding`](https://docs.rapidminer.com/latest/studio/operators/blending/attributes/types/target_encoding.html) (read 2026-09-27)
- RapidMiner Studio: [`Weight of Evidence`](https://docs.rapidminer.com/latest/studio/operators/extensions/Operator%20Toolbox/blending/weight_of_evidence.html) (read 2026-09-27)
