# Schema with linked location fields
schema = pb.Schema(
name=pb.string_field(preset="name"),
city=pb.string_field(preset="city"),
address=pb.string_field(preset="address"),
postcode=pb.string_field(preset="postcode"),
latitude=pb.string_field(preset="latitude"),
longitude=pb.string_field(preset="longitude"),
)Country-Specific Data
One of the most powerful features is generating locale-aware data. Use the country= parameter to generate data specific to a country. This affects names, cities, addresses, and other locale-sensitive presets.
Let’s create a schema that includes several location-related fields. When generating data for a specific country, Pointblank ensures consistency across related fields. The city, address, postcode, and coordinates will all correspond to the same location:
Here’s German data with authentic names and addresses from cities like Berlin, Munich, and Hamburg. Notice how the latitude/longitude coordinates match real locations in Germany:
pb.preview(pb.generate_dataset(schema, n=200, seed=23, country="DE"))PolarsRows200Columns6 |
||||||
Japanese data includes names in romanized form and addresses from cities like Tokyo, Osaka, and Kyoto. The coordinates fall within Japan’s geographic boundaries:
pb.preview(pb.generate_dataset(schema, n=200, seed=23, country="JP"))PolarsRows200Columns6 |
||||||
Brazilian data features Portuguese names and addresses from cities like São Paulo, Rio de Janeiro, and Brasília. The postal codes follow Brazil’s CEP format:
pb.preview(pb.generate_dataset(schema, n=200, seed=23, country="BR"))PolarsRows200Columns6 |
||||||
This location coherence is valuable when testing geospatial applications, address validation systems, or any scenario where realistic, internally-consistent location data matters.
Data Coherence
Pointblank automatically links related columns to produce realistic rows. There are three coherence systems that activate based on which presets appear together in a schema:
Address coherence activates when any address-related preset is present (address, city, state, postcode, latitude, longitude, phone_number, license_plate). All of these fields will refer to the same location within each row.
Person coherence activates when any person-related preset is present (name, name_full, first_name, last_name, email, user_name). The email and username are derived from the person’s name.
Business coherence activates when both job and company are present. When active:
- the company and job title are drawn from the same industry (e.g., a nurse will work at a hospital, not a law firm).
name_fullgains profession-matched titles: a doctor may appear as “Dr. Ana Sousa” and a professor as “Prof. Tanaka Yuki”. For German-speaking countries (DE, AT, CH), the honorific stacks before the professional title (e.g., “Herr Dr. med. Klaus Weber”).- integer columns whose name contains
age(e.g.,age,person_age) are automatically constrained to working-age range (22–65).
Here’s an example showing all three coherence systems working together:
schema = pb.Schema(
name=pb.string_field(preset="name_full"),
email=pb.string_field(preset="email"),
company=pb.string_field(preset="company"),
job=pb.string_field(preset="job"),
city=pb.string_field(preset="city"),
state=pb.string_field(preset="state"),
license_plate=pb.string_field(preset="license_plate"),
age=pb.int_field(),
)
pb.preview(pb.generate_dataset(schema, n=100, seed=23, country="DE"))PolarsRows100Columns8 |
||||||||
License plate coherence is part of address coherence. For CA, US, DE, AU, and GB, license plates follow real subregion-specific formats when location fields are present. For example, an Ontario row produces plates like "CABC 123" while a British Columbia row produces "AB1 23C". Letters I, O, Q, and U are excluded from plate generation, matching real-world restrictions.
Supported Countries
Pointblank currently supports 100 countries with full locale data for realistic test data generation. You can use either ISO 3166-1 alpha-2 codes (e.g., "US") or alpha-3 codes (e.g., "USA").
Europe (38 countries):
- Armenia (
AM), Austria (AT), Azerbaijan (AZ), Belgium (BE), Bulgaria (BG), Croatia (HR), Cyprus (CY), Czech Republic (CZ), Denmark (DK), Estonia (EE), Finland (FI), France (FR), Georgia (GE), Germany (DE), Greece (GR), Hungary (HU), Iceland (IS), Ireland (IE), Italy (IT), Latvia (LV), Lithuania (LT), Luxembourg (LU), Malta (MT), Moldova (MD), Netherlands (NL), Norway (NO), Poland (PL), Portugal (PT), Romania (RO), Russia (RU), Serbia (RS), Slovakia (SK), Slovenia (SI), Spain (ES), Sweden (SE), Switzerland (CH), Ukraine (UA), United Kingdom (GB)
Americas (19 countries):
- Argentina (
AR), Bolivia (BO), Brazil (BR), Canada (CA), Chile (CL), Colombia (CO), Costa Rica (CR), Dominican Republic (DO), Ecuador (EC), El Salvador (SV), Guatemala (GT), Honduras (HN), Jamaica (JM), Mexico (MX), Panama (PA), Paraguay (PY), Peru (PE), United States (US), Uruguay (UY)
Asia-Pacific (22 countries):
- Australia (
AU), Bangladesh (BD), Cambodia (KH), China (CN), Hong Kong (HK), India (IN), Indonesia (ID), Japan (JP), Kazakhstan (KZ), Malaysia (MY), Myanmar (MM), Nepal (NP), New Zealand (NZ), Pakistan (PK), Philippines (PH), Singapore (SG), South Korea (KR), Sri Lanka (LK), Taiwan (TW), Thailand (TH), Uzbekistan (UZ), Vietnam (VN)
Middle East & Africa (21 countries):
- Algeria (
DZ), Cameroon (CM), Egypt (EG), Ethiopia (ET), Ghana (GH), Israel (IL), Jordan (JO), Kenya (KE), Lebanon (LB), Morocco (MA), Mozambique (MZ), Nigeria (NG), Rwanda (RW), Saudi Arabia (SA), Senegal (SN), South Africa (ZA), Tanzania (TZ), Tunisia (TN), Turkey (TR), Uganda (UG), United Arab Emirates (AE)
Additional countries and expanded coverage are planned for future releases.
Mixing Multiple Countries
When you need test data that spans multiple locales (e.g., simulating an international customer base), you can pass a list or dict to the country= parameter instead of a single string.
Passing a list of country codes splits rows equally across those countries. Here, 200 rows are divided evenly among the US, Germany, and Japan (~67 each):
schema = pb.Schema(
name=pb.string_field(preset="name"),
city=pb.string_field(preset="city"),
postcode=pb.string_field(preset="postcode"),
)
pb.preview(pb.generate_dataset(schema, n=200, seed=23, country=["US", "DE", "JP"]))PolarsRows200Columns3 |
|||
To control the proportion of rows per country, pass a dict mapping country codes to weights. The following generates 200 rows with 70% from the US, 20% from Germany, and 10% from France:
pb.preview(
pb.generate_dataset(
schema, n=200, seed=23,
country={"US": 0.7, "DE": 0.2, "FR": 0.1},
)
)PolarsRows200Columns3 |
|||
Weights are auto-normalized, so {"US": 7, "DE": 2, "FR": 1} is equivalent to the example above. Row counts are allocated using largest-remainder apportionment, ensuring they always sum to exactly n.
By default, rows from different countries are interleaved randomly (shuffle=True). Set shuffle=False to keep rows grouped by country in the order the countries are listed:
pb.preview(
pb.generate_dataset(
schema, n=120, seed=23,
country=["US", "DE", "JP"], shuffle=False,
)
)PolarsRows120Columns3 |
|||
All coherence systems (address, person, business) work correctly within each country’s batch of rows. A French row will have a French name with a matching French email; a Japanese row will have a Japanese name with a matching Japanese email. Non-preset columns (integers, floats, booleans, dates) are generated independently for each batch but still respect their field constraints.
Frequency-Weighted Sampling
By default, names and cities are sampled uniformly at random from the locale data, giving every entry the same probability of being selected. Real-world distributions are far from uniform though: “James” and “Maria” appear orders of magnitude more often than “Thaddeus” or “Xiomara”, and more people live in New York City than in Flagstaff. The weighted=True parameter makes generated data reflect this natural skew.
schema = pb.Schema(
name=pb.string_field(preset="name"),
city=pb.string_field(preset="city"),
)
pb.preview(pb.generate_dataset(schema, n=200, seed=23, country="US", weighted=True))PolarsRows200Columns2 |
||
With weighting enabled you will see popular names like James, John, Mary, and Patricia appear more frequently, while unusual names surface only occasionally. Similarly, cities like New York, Los Angeles, and Chicago dominate the output while smaller cities appear less often.
The feature works by organizing locale data into four frequency tiers. Each tier has a sampling probability that determines how likely its members are to be selected:
| Tier | Probability | Contents |
|---|---|---|
| very_common | 45% | The top ~10% of entries by real-world frequency |
| common | 30% | The next ~20% of entries |
| uncommon | 20% | The next ~30% of entries |
| rare | 5% | The remaining ~40% of entries |
When a value is needed, a tier is first chosen according to these probabilities and then a single entry is picked uniformly at random within that tier. This two-step approach keeps sampling fast while producing a realistic long-tail distribution. Setting weighted=False pools all entries across every tier and samples them uniformly, which can be useful when you want an even spread rather than a realistic distribution.
Weighted sampling combines seamlessly with multi-country mixing. Each country’s batch uses its own tiered data independently, so a mixed dataset will have weighted US names alongside weighted German names:
pb.preview(
pb.generate_dataset(
schema,
n=200,
seed=23,
country={"US": 0.6, "DE": 0.4},
weighted=True,
)
)PolarsRows200Columns2 |
||
All 100 supported country locales have tiered name and location data, so weighted=True produces realistic frequency distributions for every country.