Data anonymizer

Redact identifier columns, generalize quasi-identifiers (age bins, zip prefixes, dates to years), and measure the k-anonymity you achieved. Runs entirely in your browser, nothing is uploaded.

Try:
Anonymized CSV + report

About this tool

This data anonymizer applies the classic k-anonymity workflow to a pasted CSV — the same three-step model used by research de-identification software. Identifier columns (names, emails, SSNs) are replaced outright with a fixed label. Quasi-identifier columns — the attributes that don't identify anyone alone but do in combination, like age + zip code + gender — are generalized: numeric values fall into fixed-width bins (3430-39), ISO dates collapse to the year (1987-04-121987), and text keeps only its first few characters (LondonLon***). Rows are then grouped by their generalized quasi-identifier combination, and the report tells you the achieved k — the size of the smallest group, i.e. how many records every person hides among.

Set a target k to see which share of rows still falls below it, or enable suppression to drop those rows so the remaining dataset is guaranteed k-anonymous. Need finer control? Give any quasi-identifier its own generalization level with a :level suffix — zipcode:100 bins that column by 100 while ages keep the default width. Naming a sensitive column (e.g. diagnosis) adds the distinct l-diversity: the minimum number of different sensitive values inside any group, a guard against everyone in a group sharing the same diagnosis.

Worked example

Input (quasi-identifiers age,zipcode:100,gender, identifier name, sensitive diagnosis):

name,age,zipcode,gender,diagnosis
Ada,34,13053,F,Flu
Bea,38,13068,F,Cold
Cal,52,14850,M,Flu
Dan,57,14853,M,Fever

Output:

name,age,zipcode,gender,diagnosis
[REDACTED],30-39,13000-13099,F,Flu
[REDACTED],30-39,13000-13099,F,Cold
[REDACTED],50-59,14800-14899,M,Flu
[REDACTED],50-59,14800-14899,M,Fever

K-anonymity report
Data rows: 4 (0 suppressed)
Quasi-identifiers: age, zipcode, gender
Redacted identifiers: name
Equivalence classes: 2 (smallest 2, largest 2)
Achieved k = 2 — every row is indistinguishable from at least 1 other row(s)
Target k = 2: MET — no rows fall below the target
Distinct l-diversity on 'diagnosis': l = 2

Limits and edge cases

FAQ

What is k-anonymity and what counts as a "good" k?

A dataset is k-anonymous when every row shares its quasi-identifier combination (its equivalence class) with at least k-1 other rows, so no individual can be singled out among fewer than k records. k = 1 means at least one person is unique and re-identifiable by linking; k = 2 is the bare minimum of protection, and public-release guidance often asks for k = 5 or more. Raise k by widening bins, keeping fewer text characters, or suppressing the outlier rows.

What is the difference between identifier and quasi-identifier columns?

Direct identifiers (name, email, phone, SSN) point at one person on their own, so they are replaced entirely with the label — no analytic value is preserved. Quasi-identifiers (age, zip code, gender, birth date) look harmless alone but combine into a fingerprint; they are generalized just enough to blur that fingerprint while keeping the data usable. Columns you list in neither role pass through unchanged.

How do I give each column its own generalization level?

Append :level to any entry in the quasi-identifier list. For a numeric column the level is the bin width (zipcode:100 groups 13053 into 13000-13099); for a text column it is the number of characters kept (city:2 turns London into Lo****). Columns without a suffix use the global "bin width" and "characters kept" settings.

Does anonymizing quasi-identifiers guarantee privacy?

No — k-anonymity protects against linking on the quasi-identifiers you selected, but it cannot help if you missed one, and a group can still leak information when everyone in it shares the same sensitive value. Name a sensitive column to check the distinct l-diversity (l = 1 means some group is homogeneous), and treat the result as one layer of a broader de-identification review, not a certification.

Why did my zip codes turn into ranges instead of 130** prefixes?

Columns whose values all parse as numbers are binned numerically, so 13053 with a width of 100 becomes 13000-13099 — the same grouping a two-digit truncation gives, just written as a range. Codes with leading zeros (01035) don't survive numeric parsing and are therefore kept as text and prefix-masked (010**). Either way, rows with the same prefix land in the same equivalence class.

Developer & Automation Access

Run it from the terminal

Same engine as this page, headless — via the gizza CLI:

gizza tool data-anonymizer "name,age,zipcode,gender
Ada,34,13053,F
Bea,38,13068,F" 'quasi=age,zipcode:100,gender'

New to the CLI? Get gizza →

Open it by URL

Pre-fill and auto-run this tool with query parameters — the names match the API/CLI:

https://gizza.ai/tools/data-anonymizer/?data=name%2Cage%2Czipcode%2Cgender%0AAda%2C34%2C13053%2CF%0ABea%2C38%2C13068%2CF&quasi=age%2Czipcode%3A100%2Cgender&identifiers=name%2Cemail&sensitive=diagnosis&k=2&numeric_bin=10&text_keep=3&dates_to_year=true&suppress=true&header=true&delimiter=%2C&label=%5BREDACTED%5D&output=both

Machine-readable descriptor: tool.json — title + parameters JSON Schema for agents.