# install presidio via pip if not yet installed
#!pip install presidio-analyzer
#!pip install presidio-evaluator
import datetime
import pprint
from collections import Counter
import numpy as np
import pandas as pd
from presidio_evaluator import InputSample
from presidio_evaluator.data_generator import PresidioSentenceFaker
Generate fake PII data using the Presidio Sentence Faker
The Presidio Sentence Faker enables you to generate a synthetic dataset from sentence templates. Example templates:
I live at {{address}}
You can email me at {{email}}. Thanks, {{first_name}}
What's your last name? It's {{last_name}}
Every time I see you falling I get down on my knees and pray
Simple example
This uses the default generator to create 10 samples based on three templates
sentence_templates = [
"My name is {{name}}",
"Please send it to {{address}}",
"I just moved to {{city}} from {{country}}",
]
sentence_faker = PresidioSentenceFaker(
"en_US", lower_case_ratio=0.05, sentence_templates=sentence_templates
)
fake_sentence_results = sentence_faker.generate_new_fake_sentences(10)
# Print the spans of the first sample
print(fake_sentence_results[0].masked)
print(fake_sentence_results[0].spans)
Using default entity providers
Using default entity mapping between the entities in the templates and the ones in the output dataset
Using default provider aliases
Sampling: 100%|██████████| 10/10 [00:00<00:00, 4370.89it/s]
I just moved to {{GPE}} from {{GPE}}
[Span(type: GPE, value: Spain, char_span: [45: 50]), Span(type: GPE, value: Valverde de Valdelacasa, char_span: [16: 39])]
Generate a full dataset
In this example we generate a large dataset with multiple entity types and save it in in JSON and CONLL03 formats. This uses the default sentence templates included in this package.
number_of_samples = 1500
lower_case_ratio = 0.05
locale = "en"
cur_time = datetime.date.today().strftime("%B_%d_%Y")
output_file = f"../data/generated_size_{number_of_samples}_date_{cur_time}.json"
output_conll = f"../data/generated_size_{number_of_samples}_date_{cur_time}.tsv"
The PresidioSentenceFaker is based on the Faker library. It loads FakeNameGenerator data by default
to extend the set of fake values and creates a SentenceFaker
which returns a fake person record (with multiple values) instead of one value,
allowing dependencies between values belonging to the same fake person
(e.g. name = Michael Smith with the email michael.smith@gmail.com).
FakeNameGenerator.com_3000.csv is included in this package and can be sourced from https://www.fakenamegenerator.com/order.php
sentence_faker = PresidioSentenceFaker("en_US", lower_case_ratio=0.05)
Using default entity providers
Using default entity mapping between the entities in the templates and the ones in the output dataset
Using default provider aliases
pd.DataFrame(sentence_faker._sentence_faker.records).head()
| number | gender | nationality | prefix | first_name | middle_initial | last_name | street_name | city | state_abbr | ... | company | domain_name | person | name | first_name_female | first_name_male | prefix_female | prefix_male | last_name_female | last_name_male | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | female | Czech | Mrs. | Marie | J | Hamanová | P.O. Box 255 | Kangerlussuaq | QE | ... | Simple Solutions | MarathonDancing.gl | Marie Hamanová | Marie Hamanová | Marie | Mrs. | Hamanová | |||
| 1 | 2 | female | French | Ms. | Patricia | G | Desrosiers | Avenida Noruega 42 | Vila Real | VR | ... | Formula Gray | LostMillions.com.pt | Patricia Desrosiers | Patricia Desrosiers | Patricia | Ms. | Desrosiers | |||
| 2 | 3 | female | American | Ms. | Debra | O | Neal | 1659 Hoog St | Brakpan | GA | ... | Dahlkemper's | MediumTube.co.za | Debra Neal | Debra Neal | Debra | Ms. | Neal | |||
| 3 | 4 | male | French | Mr. | Peverell | C | Racine | 183 Epimenidou Street | Limassol | LI | ... | Quickbiz | ImproveLook.com.cy | Peverell Racine | Peverell Racine | Peverell | Mr. | Racine | |||
| 4 | 5 | female | Slovenian | Mrs. | Iolanda | S | Tratnik | Karu põik 61 | Pärnu | PR | ... | Dubrow's Cafeteria | PostTan.com.ee | Iolanda Tratnik | Iolanda Tratnik | Iolanda | Mrs. | Tratnik |
5 rows × 37 columns
PresidioSentenceFaker adds additional providers by default, which are not included in the Faker package.
These can be found in presidio_evaluator.data_generator.faker_extensions.providers
It is possible to create providers for additional entity types by extending Faker's BaseProvider class,
and calling add_provider on the PresidioSentenceFaker instance.
For example:
import random
from faker.providers import BaseProvider
class MarsIdProvider(BaseProvider):
def mars_id(self):
# Generate a random row number between 1 and 50
row = random.randint(1, 50)
# Generate a random letter for the seat location from A-K
location = random.choice("ABCDEFGHIJK")
# Return the seat in the format "row-letter" (e.g., "25A")
return f"{row}{location}"
sentence_faker.add_provider(MarsIdProvider)
# Now a new `mars_id` entity can be generated if a template has `mars_id` in it.
from presidio_evaluator.data_generator.faker_extensions.providers import *
IpAddressProvider # Both Ipv4 and IPv6 IP addresses
NationalityProvider # Read countries + nationalities from file
OrganizationProvider # Read organization names from file
UsDriverLicenseProvider # Read US driver license numbers from file
AgeProvider # Age values (unavailable on Faker
AddressProviderNew # Extend the default address formats
PhoneNumberProviderNew # Extend the default phone number formats
ReligionProvider # Read religions from file
presidio_evaluator.data_generator.faker_extensions.providers.ReligionProvider
PresidioSentenceFaker.PROVIDER_ALIASES can be extended to add additional provider aliases for when templates have
a different entity name than what the providers emit.
# Create entity aliases (e.g. if your provider supports "name" but templates contain "person").
provider_aliases = PresidioSentenceFaker.PROVIDER_ALIASES
provider_aliases
# To customize, call `PresidioSentenceFaker(locale="en_US",...,provider_aliases=provider_aliases)`
[('name', 'person'),
('credit_card_number', 'credit_card'),
('date_of_birth', 'birthday')]
Generate data
fake_records = sentence_faker.generate_new_fake_sentences(num_samples=number_of_samples)
pprint.pprint(fake_records[0])
Sampling: 100%|██████████| 1500/1500 [00:00<00:00, 8316.22it/s]
Full text: I'll meet you at 323 Postbox 78
Apt. 637
Slædepatruljen Sirius
Greenlander after the concert.
Spans: [Span(type: STREET_ADDRESS, value: 323 Postbox 78
Apt. 637
Slædepatruljen Sirius
Greenlander, char_span: [17: 77])]
Verify randomness of dataset
count_per_template_id = Counter([sample.template_id for sample in fake_records])
print(f"Total: {sum(count_per_template_id.values())}")
print(f"Avg # of records per template: {np.mean(list(count_per_template_id.values()))}")
print(
f"Median # of records per template: {np.median(list(count_per_template_id.values()))}"
)
print(f"Std: {np.std(list(count_per_template_id.values()))}")
Total: 1500
Avg # of records per template: 7.142857142857143
Median # of records per template: 7.0
Std: 2.7513756608669206
Which entities did we generate?
count_per_entity = Counter()
for record in fake_records:
count_per_entity.update(Counter([span.entity_type for span in record.spans]))
count_per_entity
Counter({'PERSON': 875,
'STREET_ADDRESS': 647,
'GPE': 462,
'ORGANIZATION': 260,
'CREDIT_CARD': 146,
'PHONE_NUMBER': 101,
'DATE_TIME': 96,
'TITLE': 88,
'AGE': 73,
'NRP': 61,
'EMAIL_ADDRESS': 47,
'ZIP_CODE': 39,
'DOMAIN_NAME': 28,
'IBAN_CODE': 22,
'US_SSN': 11,
'IP_ADDRESS': 11,
'US_DRIVER_LICENSE': 11})
for record in fake_records[:10]:
print(record)
Full text: I'll meet you at 323 Postbox 78
Apt. 637
Slædepatruljen Sirius
Greenlander after the concert.
Spans: [Span(type: STREET_ADDRESS, value: 323 Postbox 78
Apt. 637
Slædepatruljen Sirius
Greenlander, char_span: [17: 77])]
Full text: The Adomos SA Orchestra was founded in 2014. Since then, it has grown from a volunteer community orchestra to a fully professional orchestra serving Portugal
Spans: [Span(type: GPE, value: Portugal, char_span: [149: 157]), Span(type: DATE_TIME, value: 2014, char_span: [39: 43]), Span(type: ORGANIZATION, value: Adomos SA, char_span: [4: 13])]
Full text: It's like that since 4/26/1954
Spans: [Span(type: DATE_TIME, value: 4/26/1954, char_span: [21: 30])]
Full text: One of the most depressing songs on the list. He's injured from the waist down from New Zealand, but Rinoka just has to get laid. Don't go to town, Lisa!
Spans: [Span(type: PERSON, value: Lisa, char_span: [148: 152]), Span(type: PERSON, value: Rinoka, char_span: [101: 107]), Span(type: GPE, value: New Zealand, char_span: [84: 95])]
Full text: Celebrating its 10th year in Maniitsoq, Marshall, Hernandez and Simpson is a 501(c)3 that invites songwriters from around the world to Tomášhaven to share the universal language of music in collaborations designed to bridge cultures, build friendships and cultivate peace.
Spans: [Span(type: GPE, value: Tomášhaven, char_span: [135: 145]), Span(type: ORGANIZATION, value: Marshall, Hernandez and Simpson, char_span: [40: 71]), Span(type: GPE, value: Maniitsoq, char_span: [29: 38])]
Full text: I would like to remove my kid Milada from the will. How do I do that?
Spans: [Span(type: PERSON, value: Milada, char_span: [30: 36])]
Full text: A great song made even greater by a mandolin coda (not by Hugolina Cazares).
Spans: [Span(type: PERSON, value: Hugolina Cazares, char_span: [58: 74])]
Full text: Who's coming to New Zealand with me?
Spans: [Span(type: GPE, value: New Zealand, char_span: [16: 27])]
Full text: For my take on Ms. Portič, see Guilty Pleasures: 5 Musicians Of The 70s You're Supposed To Hate (But Secretly Love)
Spans: [Span(type: PERSON, value: Portič, char_span: [19: 25]), Span(type: TITLE, value: Ms., char_span: [15: 18])]
Full text: Blink-182 pay tribute here to the Switzerland. Producer Jiří Lukášek explained to Fuse TV: "We all liked the idea of writing a song about our state, where we live and love. To me it's the most beautiful place in the world, this song was us giving credit to how lucky we are to have lived here and grown up here, raising families here, the whole thing."
Spans: [Span(type: PERSON, value: Jiří Lukášek, char_span: [56: 68]), Span(type: GPE, value: Switzerland, char_span: [34: 45])]
Save as json
InputSample.to_json(dataset=fake_records, output_file=output_file)
output_file
'../data/generated_size_1500_date_January_08_2025.json'
Create a CONLL like data frame
conll = InputSample.create_conll_dataset(dataset=fake_records)
conll.head(10)
0%| | 0/1500 [00:00<?, ?it/s]
loading model en_core_web_sm
100%|██████████| 1500/1500 [00:04<00:00, 320.23it/s]
| text | pos | tag | template_id | label | sentence | |
|---|---|---|---|---|---|---|
| 0 | I | PRON | PRP | 46 | O | 0 |
| 1 | 'll | AUX | MD | 46 | O | 0 |
| 2 | meet | VERB | VB | 46 | O | 0 |
| 3 | you | PRON | PRP | 46 | O | 0 |
| 4 | at | ADP | IN | 46 | O | 0 |
| 5 | 323 | NUM | CD | 46 | B-STREET_ADDRESS | 0 |
| 6 | Postbox | PROPN | NNP | 46 | I-STREET_ADDRESS | 0 |
| 7 | 78 | NUM | CD | 46 | I-STREET_ADDRESS | 0 |
| 8 | \n | SPACE | _SP | 46 | I-STREET_ADDRESS | 0 |
| 9 | Apt | PROPN | NNP | 46 | I-STREET_ADDRESS | 0 |
conll.to_csv(output_conll, sep="\t")
print(f"CoNLL2003 dataset structure output location: {output_conll}")
CoNLL2003 dataset structure output location: ../data/generated_size_1500_date_January_08_2025.tsv
Next steps
- Evaluate Presidio using fake data: Sample
- Split to train/test/validation while ensuring sentences originiating from the same template are all on the same subset: Sample
- Conduct a small exploratory data analysis on the generated data: Sample
Copyright notice:
Data generated for evaluation was created using Fake Name Generator.
Fake Name Generator identities by the Fake Name Generator are licensed under a Creative Commons Attribution-Share Alike 3.0 United States License. Fake Name Generator and the Fake Name Generator logo are trademarks of Corban Works, LLC.