Syllabus Lesson 130 of 156 · Tracking the person, not the device
Tracking the person, not the device

Cross-device matching

Why it matters You might use a phone on the bus, a laptop at work and a tablet on the sofa, each with its own cookies. Ad companies would much rather know they are all one person. This lab shows the clues they use, how often those clues are wrong, and which clue is by far the strongest: logging in.

Joining the dots

Picture a detective with three separate notebooks, one per device. Each notebook lists where the device went online, and when. The detective's job is to decide which notebooks belong to the same person. Nobody tells them; they guess from overlaps.

Cross-device matching: deciding that several devices belong to one person. Deterministic matching uses a sure link, such as the same account logged in on both. Probabilistic matching uses likely links, such as the same home network or the same hours of use, and accepts some mistakes.

IP address: the network address a device's traffic comes from. Every device behind one home router usually shares the home's address, and many phones on a mobile network can share one address too.

Three clues

  • A shared login: the same account on two devices. Very strong.
  • A shared IP address: suggests the same household, office or mobile network, not necessarily the same person.
  • Similar hours: two devices active at the same times of day.

Our practice households come from synthetic_households(), which returns a list of events and the answer key. Each event is {"device": "phone-004", "ip": "203.0.113.7", "hour": 21, "login": "acct-1f2e3d"}, with "login": None when nobody was logged in. The addresses are from ranges reserved for documentation, never used on the real internet.

Sets make overlaps easy

A Python set is a bag of distinct values, written set() when empty. s.add(x) puts a value in. a & b gives the values in both sets and a | b the values in either. An empty set counts as false in an if:

a = {9, 21, 22}
b = {8, 21, 22, 23}
print(a & b)                    # {21, 22}
print(len(a & b) / len(a | b))  # 2 / 5 = 0.4, the share of hours in common
if a & b:
    print("they overlap")

Scoring and judging

You will give each pair of devices a score: 3 for a shared login, 1 for a shared IP, plus the share of hours in common (0 to 1). Pairs at or above a threshold count as "same person". Then you measure two things:

Precision: of the pairs you claimed, the fraction that were right. Recall: of the true pairs, the fraction you found. A careful matcher has high precision; a greedy one has high recall.

In other languages

Set overlap in JavaScript (JavaScript has no & operator for sets; newer engines add an intersection() method, and filtering works everywhere):

const both = [...a.logins].some(x => b.logins.has(x));
const common = [...a.hours].filter(h => b.hours.has(h)).length;
const either = new Set([...a.hours, ...b.hours]).size;
const score = (both ? 3 : 0) + (either ? common / either : 0);

And in C#:

var common = a.Hours.Intersect(b.Hours).Count();
var either = a.Hours.Union(b.Hours).Count();
double score = (a.Logins.Overlaps(b.Logins) ? 3 : 0) + (either > 0 ? (double)common / either : 0);

What you should see

At threshold 1.0, a shared address alone is enough, and precision collapses: housemates, colleagues and strangers on the same mobile network all get merged. At 1.5 the matcher is right about seven times in ten and finds about seven in ten of the true pairs. At 3.0 only logins count: almost every claim is right, but most true pairs are missed. Logging in hands over the one clue that is nearly certain.

The defence

Stay logged out where you do not need an account, and use different accounts (or none) on devices you want kept apart. A VPN changes the IP clue, but not the login or the hours. The final lesson of this module puts these together.

Recap

  • Devices are joined by logins (near-certain), shared IP addresses and similar hours (both guesswork).
  • Shared addresses join households and networks, not people, so they produce many false matches.
  • Precision and recall show the trade-off between careful and greedy matching.
  • Logging in is the strongest clue you give away.
Your turn

Write four functions. summarise(events) returns a dict mapping each device to {"ips": set, "hours": set, "logins": set}, leaving None out of logins. pair_score(a, b) takes two such summaries and returns 3 if their login sets share a value, plus 1 if their IP sets share a value, plus the number of hours in both divided by the number of hours in either (0 if both are empty). match_devices(summaries, threshold) returns a set of tuples (a, b) with a < b for every pair of device names whose score is at least threshold. precision_recall(predicted, truth) returns a tuple (precision, recall), using 0 when a set is empty.

Spotted a problem in this lesson? Report it

Code · runs in your browser
Output