Skip to content
Finowl

Gender prediction

Gender prediction confidence scores and unknown results explained

How to read confidence, confidence_kind, and sample_count in gender predictions, choose thresholds for each use case, and store unknown results the right way.

Every gender prediction from Finowl comes with more than a label. The confidence, confidence_kind, sample_count, and reason fields tell you how much weight a result can bear, and ignoring them is the most common way teams end up with misleading data. This guide walks through each field, shows how to pick thresholds for different use cases, and explains how to store unknown results so they stay useful.

The anatomy of a prediction

Here is a response from POST /v1/gender/name:

json
{
  "data": {
    "result_status": "identified",
    "gender": "female",
    "confidence": 0.98,
    "confidence_kind": "observed_frequency",
    "sample_count": 2570,
    "name": "jane",
    "country": "US",
    "source": "dataset",
    "reason": null
  }
}

The fields fall into three groups:

  • Outcome: result_status (identified or unknown) and gender.
  • Strength of evidence: confidence, confidence_kind, and sample_count.
  • Provenance: name (the candidate that was actually looked up), country, source, and reason.

Before reading any of these, remember the ground rule: a prediction is a statistical estimate of how a name is typically used, not how a person identifies. It is wrong for some people. The numbers below help you judge the estimate, not the person. Responsible gender prediction covers the uses that are and are not appropriate.

Understanding confidence_kind

confidence is a number between 0 and 1, but its meaning depends on confidence_kind. There are two kinds.

confidence_kind source sample_count What confidence means
observed_frequency dataset A number Share of observed records with this label
model_reported ai null The AI model's own stated certainty

observed_frequency

When the answer comes from the Finowl Intelligence reference dataset, confidence is an observed frequency. A value of 0.98 for "jane" means that among the records seen for that name (in that country, if you supplied one), about 98 percent carried the female label. It is grounded in counts, which is why it comes with a sample_count.

model_reported

When AI mode produces the answer, source is ai, confidence_kind is model_reported, and sample_count is null. The confidence here is the model's self-assessment. It is useful for ranking and filtering, but it is not a frequency and should not be compared directly with dataset confidences. A model-reported 0.9 and an observed 0.9 are different kinds of claims.

In practice, store confidence_kind alongside confidence and apply separate thresholds to each kind. The AI mode guide explains when AI is used and how ai_mode changes that.

Why sample_count matters

A frequency is only as reliable as the number of observations behind it. A confidence of 1.0 from three records is much weaker evidence than 0.95 from several thousand. sample_count lets you tell those apart.

A practical approach is to require a minimum sample count before trusting a dataset result for anything beyond loose analytics. Rare names with tiny counts can still be useful in aggregate, where individual errors wash out, but they deserve caution in per-person features.

Country also plays a role. Names like Andrea, Jean, and Kim lean differently depending on the country, so a lookup with a country hint can produce both a different label and a different sample count than one without. If you know where a contact is, send the ISO 3166-1 alpha-2 code.

Choosing thresholds per use case

There is no single correct threshold. The right cutoff depends on how much harm a wrong answer does in the feature that consumes it. The numbers below are reasonable starting points, not guarantees; tune them against your own data.

Use case Min confidence (dataset) Min sample_count Min confidence (AI)
Aggregate reporting 0.60 1 0.70
Low-stakes personalization 0.90 50 0.95
Anything with significant effects Do not use Do not use Do not use

Aggregate reporting

For audience composition and market analysis, a lower threshold is acceptable because errors average out across many records. Keep a visible "unknown" bucket and a "low confidence" bucket rather than folding them into the main categories. That way readers of the report can see how much of the population is actually estimated.

Low-stakes personalization

For things like a default illustration or the ordering of a category list, use a higher bar and always give people an easy way to override the result. Below the threshold, fall back to a neutral experience. The neutral experience should be good on its own, because a meaningful share of your users will see it.

Decisions that matter

No threshold makes a name-based estimate suitable for eligibility, pricing, access, or other consequential decisions. High confidence does not change that, because the prediction is still about the name, not the person.

Applying thresholds in code

This Python example calls the name endpoint and classifies the result using per-kind thresholds:

python
import os
import requests

API_URL = "https://api.finowl.co/v1/gender/name"
HEADERS = {
    "Authorization": f"Bearer {os.environ['FINOWL_API_KEY']}",
    "Content-Type": "application/json",
}

THRESHOLDS = {
    "observed_frequency": {"confidence": 0.90, "sample_count": 50},
    "model_reported": {"confidence": 0.95, "sample_count": None},
}


def classify(prediction):
    if prediction["result_status"] != "identified":
        return "unknown"
    rule = THRESHOLDS.get(prediction["confidence_kind"])
    if rule is None or prediction["confidence"] < rule["confidence"]:
        return "low_confidence"
    min_samples = rule["sample_count"]
    if min_samples is not None and (prediction["sample_count"] or 0) < min_samples:
        return "low_confidence"
    return "usable"


resp = requests.post(
    API_URL,
    headers=HEADERS,
    json={"name": "Andrea", "country": "IT"},
    timeout=15,
)
resp.raise_for_status()
prediction = resp.json()["data"]
print(prediction["gender"], prediction["confidence"], classify(prediction))

Keep the raw fields even when a record falls below your threshold. Thresholds change as products evolve, and recomputing a classification from stored fields is free, while a fresh lookup costs a credit.

Unknown results and reason codes

When Finowl cannot make a reasonable estimate, it returns result_status: "unknown" and gender: null, with a reason explaining why. An unknown is a normal, successful response. It is also charged, because the lookup ran, so do not repeat the same lookup hoping for a different answer.

reason What it means What you can do
not_found The name is not in the reference data Accept it; if the lookup was dataset-only, AI fallback may help
no_name_candidate No usable name could be extracted from the input Check the input, especially emails and usernames
ambiguous The name is used broadly across labels Accept it; a country hint may help in some cases
insufficient_evidence Too little data to estimate Accept it as unknown

When a retry with different input makes sense

A few reasons point to fixable input. no_name_candidate on an email like info@example.com is expected, because there is no personal name there; gender from email addresses explains how names are extracted. If you have a separate first name field for the same contact, looking that up instead is reasonable. For ambiguous results on names that vary by region, adding a known country can sometimes resolve them.

What does not make sense is resubmitting the identical request. The answer will be the same and you will pay for it again.

Storing results correctly

The way you store predictions decides how useful they stay. A schema that works well for most teams:

Column Example Notes
gender_predicted female or NULL Never a made-up "unknown" string in the same column as labels
gender_result_status identified / unknown Distinguishes "unknown" from "never checked"
gender_confidence 0.98 Keep alongside gender_confidence_kind
gender_reason ambiguous Only set for unknown results

Also store confidence_kind, sample_count, source, and a checked_at timestamp. The important distinction is between "we looked and the answer was unknown" and "we have not looked yet." If both are represented by an empty gender column, you will either re-enrich unknowns forever, paying each time, or skip records that were never processed.

Keep predicted values in a separate field from anything a person tells you. A self-reported gender always wins, and it should never be overwritten by a later enrichment run. Finally, mention gender lookups in your privacy notice, because names and inferred gender are personal data.

The interpreting predictions guide in the docs is a compact reference for all of these fields.

Frequently asked questions

Is a confidence of 0.95 the same whether it comes from the dataset or AI?

No. Dataset results use observed_frequency, which is a share of observed records, while AI results use model_reported, which is the model's own certainty. Apply separate thresholds to each kind.

Why is sample_count null on some results?

sample_count is null when the answer came from AI mode (source: "ai"), because there is no count of observed records behind a model's answer. Dataset results always include a count.

Am I charged for unknown results?

Yes. An unknown is a successful lookup and costs a credit like any other. Store it with its reason so you do not pay to repeat the same lookup.

What confidence threshold should I use?

It depends on the use case. A lower bar is fine for aggregate reporting, a higher one with a minimum sample count suits low-stakes personalization, and no threshold makes predictions suitable for consequential decisions.

Should I treat unknown results as a third gender category?

No. Unknown means the evidence was insufficient to estimate, not a statement about the person. Store it as NULL with result_status: "unknown" and a reason, and use a neutral experience for those records.

All articles