Skip to content
C.W.K.
Stream
Lesson 04 of 05 · published

One Company, Two Listings, One Row

~12 min · identity, deduplication, domain-modeling, keys

Level 0Raw Ore
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
The universe is a set of companies. The data arrives as a set of listings. Those are different sets, and the difference is not cosmetic.

The bug, which is a modeling error

Concentration asks what share of the market its ten largest companies represent. The inputs are listings — one row per traded security — and some companies have more than one. A dual-class company trades under two tickers.

Now add the detail that makes it bite: the share count the provider returns is class-adjusted. Each listing's weighted share count already accounts for the whole company. So both tickers of a dual-class company independently price the entire firm, and keeping both put one company in the top ten twice, at full size, both times.

That is not a small distortion. It inflates the numerator, misstates the denominator, and pushes a genuine tenth-largest company out of the list — all from a set that was, in the narrow sense, entirely correct. Every row was real. Every value was right. The set was just answering a different question than the one being asked.

Name the unit of your question before you pick your key. "The ten largest companies" is a question about companies. If your data is keyed by listing, account, device, session, or transaction, you have to collapse to the question's unit first — and the collapse needs a real identity, not a convenient one.

Choosing the identity

Ticker symbol is available, unique, and wrong: it identifies a listing. Company name is a string that varies by source and formatting. The right key is the regulatory filing identifier — a number issued per filing entity, which is exactly the identity a "company" has in this domain.

Two details in the implementation are worth stealing.

The fallback is per-row, not shared. A listing with no filing identifier falls back to its own symbol, not to an empty string. That matters more than it looks: an empty-string fallback would collapse every identifier-less listing into a single phantom company, which is a much worse bug than the one being fixed.

The collapse takes a maximum, never a sum. Since each listing already prices the whole company, adding them doubles it. So one listing becomes the face and the other is discarded — and the code carries a comment about it, because the next person to read it will absolutely wonder why it is not summing.

Read that comment carefully, though, because it and the code disagree. The comment says the more liquid listing is kept. The code keeps the one with the larger computed market cap; turnover only decided which listings entered the candidate pool in the first place, and becomes a tiebreak here at most. For a dual-class company the two usually agree, which is exactly why the discrepancy survives. A comment that is right most of the time is the hardest kind to notice being wrong.

Both errors are silent and they point in opposite directions. Summing dual-class caps inflates a company. Collapsing on an empty key merges unrelated companies into one. Neither raises, neither fails a type check, and both produce a ranking that looks entirely normal — which is the recurring theme of this whole track.

Code

Collapsing listings to companies, with both traps handled·python
# One row per COMPANY, not per listing. `weighted_shares_outstanding`
# is class-adjusted, so BOTH listings of a dual-class company each
# price the whole firm -- keeping both put one company in the top ten
# twice. Deduped on the filing identifier, the same identity the
# filing backfill follows; a listing with no identifier falls back to
# its OWN symbol rather than collapsing into a shared empty key.
by_company: dict[str, dict[str, Any]] = {}
for bar in candidates:
    held = cache.get(bar["symbol"])
    if not held:
        continue
    entry = {
        "symbol": bar["symbol"], "name": held["name"],
        "cik": held["cik"] or "",
        "market_cap": bar["close"] * held["shares"],
        "price": bar["close"],
    }
    company = entry["cik"] or f"sym:{entry['symbol']}"
    prior = by_company.get(company)
    # Same company, two listings: keep ONE as the face, and NEVER
    # add their caps together. Note what the tiebreak actually is
    # -- the larger computed market_cap, not the more liquid
    # listing. Turnover only decided who got INTO `candidates`.
    # (The comment in the real module says "more liquid"; the
    #  code says market_cap. Read the line, not the label.)
    if prior is None or entry["market_cap"] > prior["market_cap"]:
        by_company[company] = entry

ranked = sorted(by_company.values(),
                key=lambda r: r["market_cap"], reverse=True)

External links

Exercise

Take a ranking or aggregate you produce and write down the unit its question is about, then the unit its rows are keyed by. If they differ, find the collapse step. Check two things: whether the fallback for a missing identity is per-row or shared, and whether the collapse sums or takes an extreme. Both are easy to get backwards and neither will raise.
Hint
Shared fallbacks are the more dangerous of the two, because they scale with your data quality problems: the more rows missing an identifier, the bigger the phantom entity grows, and it always looks like one implausibly large member rather than like a bug.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.