From systems to signals.
A Field Guide · Decision Dashboards
Given nothing but a public trail of Facebook posts and comments, how confidently can you guess that the person behind the account is a man over 65 who votes Republican? This is how we would build that estimate, step by step, with a Bayesian scoring model and a healthy respect for how often it will be wrong.
Most profiling efforts fail before any math happens, because the question is vague. Ours is not. We want one number: the probability that a single anonymous account belongs to a man, aged 65 or older, who identifies as or consistently leans Republican, conditional on everything the account has publicly written and shared.
P( 65+ ∩ Male ∩ Republican | post history )That is a joint probability, and it matters. The three traits are not independent: older men are more likely than the general population to lean Republican, and older Republicans on Facebook skew male. If we scored each trait separately and multiplied the results, we would understate the overlap and get the wrong answer.
So the model works in two passes. First it estimates each trait on its own, which tells you where the evidence is coming from. Then it scores the combined persona as a single class against everyone else, which is the number you actually act on.
Here is the worked example account after pass one. Every margin is high, but no single trait carries the answer alone.
Illustrative output. Multiplying the margins gives 65 percent, which understates the joint estimate of 82 percent. The gap is exactly the correlation the two-pass design exists to catch.
The whole model is one idea applied patiently: start with what you knew before the evidence, then update it every time the account does something informative. Nothing more exotic than that.
Formally, Bayes' theorem says the posterior odds equal the prior odds times the likelihood ratio of the evidence:
P(persona | evidence)P(not persona | evidence) = P(persona)P(not persona) × P(evidence | persona)P(evidence | not persona)Multiplying ratios is clumsy, so we work in logarithms, where multiplication becomes addition. The model is then just a running total. Every signal the account exhibits adds its weight; every expected signal that is missing subtracts. This is the naive Bayes assumption: treat the signals as if they were independent given the class, accept that they are not perfectly so, and correct for the resulting overconfidence during calibration.
log-odds(persona | evidence) = log-odds(persona) + Σ log LRiConvert back with the logistic function: probability = 1 / (1 + e−log-odds)
Why log-odds and not raw percentages. A percentage cannot show you its work. Log-odds can. Each signal's contribution stays visible as a number you can audit, challenge, and defend, which is the difference between a model you can explain and a score you have to take on faith.
A signal is anything an account does that is more common among 65-plus Republican men than among everyone else. The weight below each one is its log-likelihood ratio: how much it moves the running total when present. Weights are learned from a labeled training set of accounts whose owners' traits are known, then frozen before the model ever touches a new account.
Signal Weights, Worked Example Model
Log-likelihood ratio added when the signal is present. A weight of 0.80 roughly doubles the odds on its own.
Three things are worth noticing. First, no single signal is decisive. Even the strongest one only shifts the total by a fraction of what is needed. Second, the signals are mundane: what time you post, how you punctuate, what you share. Demographics leak through habits, not through confessions. Third, every weight can go negative. An account that never mentions family, never posts before noon, and shares no political content is actively scoring against the persona.
Where the weights come from. Take a labeled sample of accounts, count how often each signal appears in the persona group versus everyone else, and divide. A phrase used by 12 percent of persona accounts and 3 percent of others has a likelihood ratio of 4, which becomes a weight of about 1.39 in log space. That is the entire training process.
This is the part most explainers skip. Toggle the signals below and the model reruns in front of you: the running log-odds total, the waterfall of contributions, and the final probability all update together. Start with everything on, then take evidence away and watch the estimate fall back toward the base rate.
Log-Odds Waterfall
From the prior on the left to the posterior on the right. Each bar is one signal's contribution to the running total.
The prior is fixed at 5.2 percent, a log-odds of −2.90. Weights are illustrative, sized to the training patterns described above. Turn everything off and the model correctly returns to the base rate, because with no evidence there is nothing to update.
A model you cannot distrust is a model you should not use. Two failure modes matter here, and both are arithmetic, not bad intentions.
Even a good model drowns in false positives when the thing it hunts is rare. Picture 100 random accounts. About 5 fit the persona. The model catches 9 of every 10 true matches but still wrongly flags 5 of every 95 others. The result: it flags 9 or 10 accounts, and roughly half of them are innocent of the label.
100 Random Accounts, Rounded Counts
A 90 percent sensitivity model with a 5 percent false-positive rate, applied to a 5 percent base-rate population.
Precision lands near 50 percent. Raise the flagging threshold to cut false alarms and you start missing true matches. That trade is not a bug to fix. It is the cost of looking for something rare.
The ROC curve below summarizes every threshold the model could use. An area under the curve of 0.93 looks strong on paper, and it is, for ranking a population. But the curve describes the model's average behavior across millions of accounts. It says nothing certain about the single account in front of you, which could be a 40-year-old libertarian woman who types in all caps and loves her grandchildren.
Model Performance, Held-Out Test Set
True-positive rate against false-positive rate as the flagging threshold sweeps from strict to loose.
An estimate like this is a statistical statement about patterns, never a fact about a person. Public posts are not consent to be profiled, platform terms restrict this kind of inference, and political affiliation is a sensitive attribute in many jurisdictions. We build models like this to measure audiences in aggregate, with consent and disclosure, not to tag individuals. If your use case needs a name attached to a score, that is the point to stop and redesign the question.
Authored by Scott Isaacson.