Skip to content

Commit 2e97411

Browse files
authored
Hierarchical evaluation improvements - Presidio Evaluator v-next (#172)
1 parent e209388 commit 2e97411

127 files changed

Lines changed: 554213 additions & 9191 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
contents: read
1414
strategy:
1515
matrix:
16-
python-version: ['3.10', '3.11', '3.12', '3.13']
16+
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
1717

1818
steps:
1919
- uses: actions/checkout@v4
@@ -32,33 +32,30 @@ jobs:
3232
uses: actions/setup-python@v5
3333
with:
3434
python-version: ${{ matrix.python-version }}
35-
36-
- name: Cache Poetry dependencies
35+
36+
- name: Install uv
37+
uses: astral-sh/setup-uv@v5
38+
39+
- name: Cache uv dependencies
3740
uses: actions/cache@v4
3841
with:
39-
path: |
40-
~/.cache/pypoetry
41-
~/.cache/pip
42-
key: ${{ runner.os }}-poetry-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
42+
path: ~/.cache/uv
43+
key: ${{ runner.os }}-uv-${{ matrix.python-version }}-${{ hashFiles('**/uv.lock') }}
4344
restore-keys: |
44-
${{ runner.os }}-poetry-${{ matrix.python-version }}-
45+
${{ runner.os }}-uv-${{ matrix.python-version }}-
4546
4647
- name: Install dependencies
4748
run: |
48-
python -m pip install --upgrade pip
49-
pip install poetry
50-
poetry install --with dev,ner
51-
poetry run python -m spacy download en_core_web_sm
52-
poetry run python -m spacy download en_core_web_lg
49+
uv venv --seed
50+
uv sync --extra dev
51+
uv run python -m spacy download en_core_web_sm
52+
uv run python -m spacy download en_core_web_lg
5353
5454
- name: Run pytest
5555
run: |
56-
poetry run pytest --runslow
56+
uv run pytest --runslow
5757
5858
- name: Clean up after tests
5959
if: always()
6060
run: |
61-
# Clean up pip cache and poetry cache to free space for next matrix job
62-
pip cache purge || true
63-
poetry cache clear pypi --all -n || true
6461
df -h

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,11 @@ datasets/
191191
*.spacy
192192
*.pickle
193193
/poetry.lock
194+
195+
196+
*.xml
197+
*.json
198+
*.html
199+
*.svg
200+
!synth_dataset_v2.json
201+
test_us002.py

.pre-commit-config.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
repos:
2+
- repo: local
3+
hooks:
4+
- id: ruff-format
5+
name: ruff format
6+
language: system
7+
entry: uv run ruff format --check
8+
types: [python]
9+
pass_filenames: false
10+
11+
- id: ruff-check
12+
name: ruff check
13+
language: system
14+
entry: uv run ruff check
15+
types: [python]
16+
pass_filenames: false
17+
18+
- id: pytest
19+
name: pytest (unit tests only)
20+
language: system
21+
entry: uv run pytest -m "not slow and not integration" --ignore=tests/integration -q
22+
pass_filenames: false
23+
always_run: true

AGENTS.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# AGENTS.md — Codebase Patterns
2+
3+
## Project Setup
4+
- Python managed with `uv` — always use `uv run` for Python, never bare `python` or `pip`
5+
- Virtualenv at `.venv/`
6+
- Quality checks: `uv run ruff check --fix`, `uv run ruff format`, `uv run pytest`
7+
- Fast entity-mapping tests: `pytest tests/entity_mapping/`
8+
9+
## CanonicalMapper
10+
- Single-phase (Identify-only) — no projection phase
11+
- `analyze(results_df, min_severity='WARNING')` — COLLISION_SAME_BRANCH (INFO) hidden unless `min_severity='INFO'`
12+
- `get_mapped_results_dataframe()` returns `MappedResults` (frozen dataclass with `.original`, `.binary`, `.branch`, `.detailed`)
13+
- `get_mapping()` returns `{label: resolved}` dict — UNRESOLVED labels excluded
14+
- `get_issues()` filters by `_min_severity` — call after `analyze()` or `map()`
15+
16+
## Five Issue Types (IssueType enum)
17+
- UNRESOLVED (ERROR, blocking)
18+
- COLLISION_CROSS_BRANCH (WARNING, blocking) — only raised when cross-branch co-occurrences outnumber same-branch ones for the prediction label
19+
- PREDICTION_ONLY (WARNING, blocking)
20+
- DATASET_ONLY (WARNING, non-blocking)
21+
- COLLISION_SAME_BRANCH (INFO, non-blocking)
22+
23+
## _Resolution dataclass fields
24+
- `tier` — identification tier (EXACT, COUNTRY, COUNTRY_FALLBACK, FUZZY, UNRESOLVED)
25+
- `resolved` — resolved canonical entity name (or None for UNRESOLVED)
26+
- `score` — fuzzy match score (0.0–1.0, None for non-FUZZY tiers)
27+
- NO `canonical`, `projected`, or `projection_type` fields
28+
29+
## IssueSeverity enum
30+
- Values are lowercase: `'error'`, `'warning'`, `'info'`
31+
- Use `.lower()` when converting from string; do NOT use `.upper()`
32+
33+
## Hierarchical Evaluation
34+
- `calculate_hierarchical_scores(mapped_results: MappedResults)` returns `{"binary", "branch", "detailed"}`
35+
- NOT `{"L0", "L1", "L2"}` — use the string level names
36+
- `MappedResults` is in `presidio_evaluator/entity_mapping/data_objects.py`
37+
38+
## level_helpers
39+
- `to_binary(label)` and `to_branch(label)` are now **instance methods on `EntityHierarchy`** (moved from the deleted `level_helpers.py`)
40+
- Call via `hierarchy_instance.to_binary(label)` / `hierarchy_instance.to_branch(label)`
41+
- `EntityHierarchy` has no imports from `mapper.py` — no circular deps
42+
- For branch lookups use `EntityHierarchy(canonical_depth=10)`
43+
44+
## Notebooks
45+
- NB4: `4_Evaluate_Presidio_Analyzer.ipynb` — standard Presidio evaluation
46+
- NB5: `5_Evaluate_Custom_Presidio_Analyzer.ipynb` — custom model evaluation
47+
- NB6: `6_Interactive_Entity_Mapping.ipynb` — interactive mapping tutorial
48+
- All notebooks use `mapped_results = mapper.get_mapped_results_dataframe()` (not `mapped_df`)
49+
- `edit_notebook_file` requires VSC cell IDs (`#VSC-xxxx`) — use `copilot_getNotebookSummary` to get current IDs
50+
51+
## Git
52+
- Pre-commit hooks: ruff-format, ruff-check, pytest (unit tests only)
53+
- Branch: `ralph/canonical-mapper-single-phase`

CHANGELOG.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,40 @@
11
# CHANGELOG
22

3+
## Unreleased
4+
5+
> **Migration guide:** See [docs/migration-guide.md](docs/migration-guide.md) for step-by-step upgrade instructions.
6+
7+
### Breaking Changes
8+
9+
- **`evaluate_all()` removed** — raises `DeprecationError` at runtime. Replace with the three-step pipeline: `predict_dataset()``CanonicalMapper.get_mapped_results_dataframe()``calculate_score_on_df()`.
10+
- **`entity_mapping` parameter removed** from `SpanEvaluator`, `TokenEvaluator`, and `BaseEvaluator` — entity mapping is now the responsibility of `CanonicalMapper`.
11+
- **`compare_by_io` parameter removed** from evaluator constructors — BIO/BILUO prefix stripping is now performed by `CanonicalMapper`.
12+
- **`BaseEvaluator.from_dataset()` removed** — use `model.predict_dataset(dataset)` directly.
13+
- **Non-Presidio model wrappers removed**: `FlairModel`, `SpacyModel`, `StanzaModel`, `AzureAITextAnalyticsWrapper`. Add models directly through Presidio to evaluate them.
14+
- **Minimum Python version raised to 3.11** (was 3.10) — required by `numpy >= 2.4.0`.
15+
- **Package manager changed from Poetry to uv** — install with `uv sync`, run with `uv run`.
16+
17+
### New Features
18+
19+
- **`BaseModel.predict_dataset(dataset)`** — runs the model on a list of `InputSample` objects and returns a 5-column DataFrame (`sentence_id`, `token`, `annotation`, `prediction`, `start_indices`).
20+
- **`CanonicalMapper`** — replaces `EntityMappingHelper` with an improved four-tier auto-resolution strategy (`EXACT`, `COUNTRY`, `FUZZY`, `PENDING`). Key methods:
21+
- `CanonicalMapper.from_dataset(dataset)` — builds a mapper from dataset labels.
22+
- `mapper.get_mapped_results_dataframe(results_df)` — applies entity mapping to a predictions DataFrame.
23+
- `mapper.get_mapping(mode='html' | 'text')` — returns the final `{raw_label: canonical | None}` dict.
24+
- `mapper.map({"LABEL": "CANONICAL"})` — manually resolve pending labels.
25+
- `mapper.render_html()` — display the resolution audit table in Jupyter.
26+
- **`TokenEvaluator.calculate_score_on_df(results_df)`** — score token-level predictions from a DataFrame.
27+
- **`SpanEvaluator.calculate_score_on_df(per_type, results_df)`** — score span-level predictions from a DataFrame.
28+
- **Ruff** — added as the project linter and formatter (`ruff.toml` at project root).
29+
- **Pre-commit hooks**`ruff format`, `ruff check`, and `pytest` run automatically before every commit (`.pre-commit-config.yaml`).
30+
- **Test reorganisation** — tests are now grouped by topic (`tests/data_generator/`, `tests/entity_mapping/`, `tests/evaluation/`, `tests/models/`, `tests/integration/`). Integration tests are tagged with `pytest.mark.integration`.
31+
32+
### Deprecations
33+
34+
- **`evaluator.get_results_dataframe()`** — soft `DeprecationWarning` emitted at runtime. Replace with `model.predict_dataset(dataset)`.
35+
36+
37+
338
## Version 0.2.5
439

540
### Improvements

NOTICE

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ tqdm
351351

352352
`tqdm` is a product of collaborative work.
353353
Unless otherwise stated, all authors (see commit logs) retain copyright
354-
for their respective work, and release the work under the MIT licence
354+
for their respective work, and release the work under the MIT license
355355
(text below).
356356

357357
Exceptions or notable authors are listed below
@@ -368,7 +368,7 @@ in reverse chronological order:
368368
[PR #96]: https://github.com/tqdm/tqdm/pull/96
369369

370370

371-
Mozilla Public Licence (MPL) v. 2.0 - Exhibit A
371+
Mozilla Public license (MPL) v. 2.0 - Exhibit A
372372
-----------------------------------------------
373373

374374
This Source Code Form is subject to the terms of the

README.md

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Presidio-research
22

3-
This package provides evaluation and data-science capabilities for
3+
This package provides evaluation and data-science capabilities for
44
[Presidio](https://github.com/microsoft/presidio) and PII detection models in general.
55

66
It also includes a fake data generator that creates synthetic sentences based on templates and fake PII.
@@ -20,16 +20,13 @@ The easiest way to get started is by reviewing the notebooks.
2020
- [Notebook 3](notebooks/3_Split_by_pattern_number.ipynb): Provides tools to split the dataset into train/test/validation sets while avoiding leakage due to the same pattern appearing in multiple folds (only applicable for synthetically generated data).
2121
- [Notebook 4](notebooks/4_Evaluate_Presidio_Analyzer.ipynb): Shows how to use the evaluation tools to evaluate how well Presidio detects PII. Note that this is using the vanilla Presidio, and the results aren't very accurate.
2222
- [Notebook 5](notebooks/5_Evaluate_Custom_Presidio_Analyzer.ipynb): Shows how one can configure Presidio to detect PII much more accurately, and boost the f score in ~30%.
23+
- [Notebook 6](notebooks/6_Interactive_Entity_Mapping.ipynb): Explains the entity mapping process, which is crucial when evaluating multiple models each returning a different set of entities.
2324

2425
### Installation
2526

26-
>Note: Presidio evaluator requires Python version 3.9 or higher.
27-
2827
#### From PyPI
2928

3029
``` sh
31-
conda create --name presidio python=3.12
32-
conda activate presidio
3330
pip install presidio-evaluator
3431
python -m spacy download en_core_web_sm # for tokenization
3532
python -m spacy download en_core_web_lg # for NER
@@ -43,31 +40,29 @@ To install the package:
4340
2. Install all dependencies:
4441

4542
``` sh
46-
# Install package+dependencies
47-
pip install poetry
48-
poetry install --with=dev
43+
# Install uv if not already installed
44+
pip install uv
4945

50-
# Download tge spaCy pipeline used for tokenization
51-
poetry run python -m spacy download en_core_web_sm
46+
# Install package + dev dependencies
47+
uv sync --extra dev
5248

53-
# To install with all additional NER dependencies (e.g. Flair, Stanza), run:
54-
# poetry install --with='ner,dev'
49+
# Download the spaCy pipeline used for tokenization
50+
uv run python -m spacy download en_core_web_sm
5551

5652
# To use the default Presidio configuration, a spaCy model is required:
57-
poetry run python -m spacy download en_core_web_lg
53+
uv run python -m spacy download en_core_web_lg
5854

5955
# Verify installation
60-
pytest
56+
uv run pytest
6157
```
6258

63-
Note that some dependencies (such as Flair and Stanza) are not automatically installed to reduce installation complexity.
59+
Note that some dependencies (such as Flair and Stanza) are no longer supported. Use Presidio Analyzer directly to add custom NER models.
6460

6561
## What's in this package?
6662

6763
1. **Fake data generator** for PII recognizers and NER models
6864
2. **Data representation layer** for data generation, modeling and analysis
69-
3. Multiple **Model/Recognizer evaluation** files (e.g. for Presidio, Spacy, Flair, Azure AI Language)
70-
4. **Training and modeling code** for multiple models
65+
3. **Model/Recognizer evaluation** for Presidio Analyzer and custom Presidio recognizers
7166
5. Helper functions for **results analysis**
7267

7368
## 1. Data generation
@@ -120,13 +115,6 @@ The standardized structure, `List[InputSample]`, can be translated into differen
120115
InputSample.create_spacy_dataset(dataset, output_path="dataset.spacy")
121116
```
122117

123-
- Flair
124-
```python
125-
from presidio_evaluator import InputSample
126-
dataset = InputSample.read_dataset_json("data/synth_dataset_v2.json")
127-
flair = InputSample.create_flair_dataset(dataset)
128-
```
129-
130118
- json
131119
```python
132120
from presidio_evaluator import InputSample
@@ -140,6 +128,7 @@ The presidio-evaluator framework allows you to evaluate Presidio as a system, a
140128

141129
## For more information
142130

131+
- [Blog post on PII evaluation](https://omri-mendels.medium.com/evaluating-pii-detection-models-fa0c745d7a4c)
143132
- [Blog post on NLP approaches to data anonymization](https://towardsdatascience.com/nlp-approaches-to-data-anonymization-1fb5bde6b929)
144133
- [How to evaluate PII Detection output with Presidio Evaluator](https://tranguyen221.medium.com/how-to-evaluate-pii-detection-output-with-presidio-evaluator-3f2684ba3091)
145134
- [Conference talk about leveraging Presidio and utilizing NLP approaches for data anonymization](https://youtu.be/Tl773LANRwY)

docs/.markdownlint.json

Lines changed: 0 additions & 4 deletions
This file was deleted.

0 commit comments

Comments
 (0)