Table of Contents
Provenance: Statistics — Biases
Working log behind Biases. Every query with its denominator, the report script and its unedited output, the folds and their full residue, the probes that were run and the ones that were rejected, the quotes checked, the external sources verified and rejected, and what could not be established.
Corpus-level caveats — the venue scope, the selection funnel, the provisional years, the duplicate records — are on Corpus and are not restated here. Citation keys are shared with the content page and the single Bibliography; this page adds no bibliography entries of its own.
Voice here is a working log, not prose. It is for someone checking a number.
The run
| Item | Value |
|---|---|
| Date | 2026-08-20 |
| Corpus | data/extract/run1, 5,859 extracted papers, 7 venues, 2010–2026 |
| Page status | New page. statistics:biases was a red link promised from start and linked from six existing pages: Sampling, Fingerprinting (twice), Public relations, Hypothesis testing, Pvalue corrections and Study preregistration. Nothing on the wiki covered it |
| Scripts added | scripts/biases_report.mjs, scripts/biases_quotecheck.mjs |
| Scripts reused unchanged | scripts/lib.mjs (populations, sentinels, paperCounts, YEAR_BUCKETS), scripts/geo.mjs (normalizeLocation), scripts/check_page_numbers.mjs (the stale-figure guard), scripts/bibgen.mjs |
| Code published on the page | none. The page publishes no runnable script — its argument is about design and reporting, not about a computation a reader would run |
| Models | Opus 5 (author, all queries, all folds, all verification); the review layer below |
start edit needed | None. start already links the page |
Scope decision: create, not broaden
Three neighbours overlap this page and each was considered as the host instead.
| Candidate | Why not |
|---|---|
| Sampling (52 KB) | Its subject is how you draw: method, size, unit, and what to record so the draw is repeatable. It already covers the list catalogue, the exact-string undercount, the Alexa→Tranco transition and attrition-as-a-denominator. This page is about what the draw does to the estimate you report and what you may claim from it. Sampling names Statistics:Biases as its own downstream page (“coverage and selection bias, downstream of everything on this page”), so the boundary was set by an earlier run |
| Crawling location (29 KB) | Owns the vantage point as a design choice — how to pick one, how to verify it, which infrastructure. Its figures are computed on the 1,120 crawled papers. This page states the vantage point as a bias with a measured direction and gives the exposure on the wider 3,908-paper measuredFrom population, which that page does not compute |
| Hypothesis testing (64 KB) | Its subject is inference after the sample is fixed. It already links here for “what a fixed population and exclusion rule protect you from before any test is run” |
Deliberate division of labour. These belong to the neighbours and are linked, not restated: the list catalogue and their provenance (Website selection); the per-list spelling undercount and the sampler that records its own provenance (Sampling); choosing and verifying a vantage point, and the EEA/GDPR trend (Crawling location); non-independence and clustering (Hypothesis testing); fixing the exclusion rule in advance (Study preregistration).
Two figures are re-derived that a neighbour also reports, on purpose:
- The Alexa→Tranco transition. Sampling computes it on its own 1,153-paper web-unit population; this page computes it on the 793 papers using any ranking list, by an independent regex fold. The two land within 3% on Alexa (463 there, 475 here) and that is the only external check either page has on its fold — see the fold section for why “within 3%” is the honest phrasing and “agree” was not.
- Vantage-point location shares. Crawling location reports 182 US papers of 296 stating on the
crawledpopulation; this page reports 639 of 1,135 onmeasuredFrom. Different populations, same shape. Stated as such on both.
One thing the page deliberately does not attempt. It does not rank the four biases by size. The effect sizes come from different papers with different designs and denominators, and a ranking would imply a comparison that was never run.
Populations and denominators
Every query names its own population. There is no page-wide denominator, which is the point of the page.
population definition papers ------------ --------------------------------------------------------- ------ all every extracted paper in the corpus 5859 sampled has >=1 population tuple (drew a study population) 5712 crawled crawlConfig != null OR studyTypes has automated-web-crawl 1120 webCrawled crawled AND platforms includes web 857 measuredFrom has >=1 vantage tuple 3908 repeated crawled AND some temporal.snapshots > 1 250
crawled, measuredFrom and webCrawled come from scripts/lib.mjs unchanged and match the counts published in data/extract/OVERVIEW.md. Two are defined in biases_report.mjs itself:
const sampled = rows.filter((p) => p.population.length > 0); const repeated = crawled.filter((p) => p.temporal.some((t) => typeof t.snapshots === 'number' && t.snapshots > 1));
repeated is a floor, not a count of longitudinal work. Only 409 of the 1,120 crawling papers (36.5%) state a snapshot count at all, so a paper that crawled monthly for a year and never wrote the number down is not in it. This is said on the page.
repeated is also too narrow in a way that was caught by accident. It requires crawled, and the extraction classifies [1Efstratiou, Alexandros (2025): "On YouTube Search API Use in Research", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] — a paper with a section headed 4.3 Attrition, sixteen snapshots, and the best treatment of query-sampler attrition in the corpus — as network-scan-or-probe with crawlConfig: null. So the paper most relevant to this page's survivorship section is not in the population the survivorship probe reads. The report therefore prints the probe a second time over every paper with more than one snapshot regardless of study type (817 readable), and the rate is the same, 0.7%. Both numbers are on the page.
The ranking-list fold
population.sourceList is free text. It is folded to an alphanumeric skeleton by skeleton() in lib.mjs and then matched against nine unanchored regular expressions:
const RANKING_LISTS = [ ['Tranco', /tranco/], ['Alexa', /alexa/], ['Cisco Umbrella', /umbrella|opendns/], ['Majestic', /majestic/], ['Chrome UX Report (CrUX)', /crux|chromeuxreport|chromeuserexperience/], ['Quantcast', /quantcast/], ['SimilarWeb', /similarweb/], ['Common Crawl', /commoncrawl/], ['Statvoo / Domcop / other rank vendor', /statvoo|domcop|ahrefs|semrush|builtwith/], ];
Two bugs in the first version of this fold, both found in review, both of which moved published figures. They are recorded in detail because either one alone would have produced a table that looks authoritative and is wrong.
| Bug | Effect | Fix |
|---|---|---|
isRankingList() returned the first matching label and stopped | A sourceList string routinely names several lists at once — Alexa/Tranco top 10,000; Alexa, Umbrella, Majestic, Tranco, Secrank, and Trexa top lists; Common Crawl and Tranco; Majestic Top 1M and Umbrella Top 1M. Every list after the first was silently dropped, which undercounted Tranco, Umbrella, Majestic, CrUX and Common Crawl — including in the Alexa→Tranco table that carries the page's central argument | rankingLists() now returns every matching label. Umbrella 18 → 27 papers, Majestic 15 → 27, CrUX 29 → 30 |
| The patterns were anchored to the start of the skeleton | Anchoring is what made the first bug invisible: Alexa/Tranco top 10,000 skeletons to alexatranco10000, which a start-anchored Tranco pattern cannot match even after the first bug was fixed. 27 Tranco strings, 48 Alexa strings, 10 Majestic strings and 16 CrUX strings were unreachable | Unanchored. Rank-list papers 773 → 795; Alexa 464 → 477; Tranco 253 → 267; CrUX 30 → 36. The 795 and 477 were themselves wrong — see the third bug below |
A third bug, produced by the fix for the second, and found on re-review. Unanchoring /alexa/ re-exposed the substring problem the anchors had been suppressing, and the first exclusion list was built only from the strings the author had looked at. The re-review found two it missed:
| String | Why it is not the Alexa list | What it corrupted |
|---|---|---|
Alexa, Google Assistant, IFTTT, SmartThings, Apple HomeKit, OpenHAB, and HomeAssistant integration lists | A smart-home paper. “Alexa” here is the assistant, one member of an integration set | This paper is in webCrawled, so it inflated the page's opening headline figure — the number most readers will quote |
Alexander Mylnikov | A person's surname. alexander contains alexa after punctuation stripping | The corpus-wide 795 and 477 |
Fixed with an assistant-ecosystem keyword list plus a negative lookahead for the surname, because alexander is a prefix collision rather than a topic. Final figures: 793 rank-list papers, 479 of webCrawled, Alexa 475. The lesson is that the exclusion list is only as good as the strings someone looked at, so all 16 are printed by the report, and anyone unanchoring another pattern should expect the same class of surprise.
Why anchoring was there in the first place, and what replaced it. “Alexa” is also Amazon's voice assistant, and the corpus contains skill-store populations. Unanchoring /alexa/ therefore needs an explicit exclusion instead:
const ALEXA_IS_THE_LIST = /alexa(?!nder)/; // surname, not a list const ALEXA_NOT_A_LIST = /skill|marketplace|voiceassistant|echodot|amazonecho|\balexastore|smartspeaker |googleassistant|smartthings|homekit|openhab|homeassistant|ifttt|siri|googlehome/;
All 16 excluded strings, printed by the report so the exclusion is auditable:
2x US Alexa skill store 1x third-party Alexa skill-name aggregation database 1x Amazon Alexa skill market 1x Alexa skill market 1x Amazon Alexa Skill Store 1x Amazon Alexa skills store 1x Alexa skill stores: US, UK, AU, CA, DE, JP, FR 1x Alexa, Google Assistant, IFTTT, SmartThings, Apple HomeKit, OpenHAB, and HomeAssistant integration lists 1x Amazon Alexa marketplace 1x Amazon Alexa marketplace, US market 1x Alexa skills with complete traceability 1x Alexander Mylnikov 1x Alexa skill marketplace 1x Alexa skill marketplaces 1x privacy-policy pairs across Alexa marketplaces 1x custom extracted email list from Amazon Alexa skill descriptions
Every one is the voice assistant. None is a ranking-list population. The remaining unanchored patterns were checked the same way: /tranco/, /majestic/, /quantcast/ and /crux/ produced no non-list matches in the corpus at all.
This is still a matcher, not a synonym fold. A list named in a way none of the nine catch is counted as not a ranking list, so 793 remains a lower bound and every share against it is conservative in the same direction.
Residue: 10,011 distinct unmatched sourceList skeletons. They are not ranking lists — the head is custom seed list (516 papers), Google Play (143), Prolific (122), Google Play Store (83), GitHub (73), Amazon Mechanical Turk (68), AndroZoo (54), CIFAR-10 (52), RIPE Atlas (48), VirusTotal (39). The report prints the top 25 and the total. Printing all 10,011 was considered and rejected: they are the corpus's entire population vocabulary, most of it about apps, datasets and participant pools, and none of it about web popularity rankings.
Cross-check against the independent fold on Sampling. That page's sample_fold.mjs reaches 463 Alexa papers and 262 Tranco on its own 1,153-paper web-unit population; this page reaches 475 and 267 on a different population by a different method.
And this is where the cross-check has to be described honestly, because it is the weakest thing on the page. It “held” through all three bug fixes — the buggy version reported 464 / 253, which is closer to Sampling's 463 / 262 than the corrected 475 / 267 is. A cross-check that is satisfied equally by the right answer and by three bugs is not a check on correctness; it only bounds how far apart two methods can be. All three bugs were found by reading the code and by a reviewer reading the corpus, not by the cross-check. The page therefore says the two folds “land within 3% of each other” and does not say they “agree”.
The location fold
vantage.locations is folded through scripts/geo.mjs (normalizeLocation), reused unchanged from Crawling location. It maps a string to country, region, multi or unmapped.
tuple-level fold kinds: [["country",2405],["unmapped",403],["region",331],["multi",203]] 403 of 3342 location strings unmapped (12.1%), 354 distinct
Why the exact-string figure is on the page. Folded, 639 papers name the United States. Counting the exact string United States gives 238 — a 62.8% undercount. The task brief records this measured at 280 → 498 on 2026-08-05 via site_queries.mjs –page vantage; the numbers here differ because the population is measuredFrom (3,908 papers) rather than the crawling papers, and because geo.mjs folds more aggressively than name_fold.mjs. The ratio is the claim, and it survived the change of population.
The full 354-string residue is in the report output, reproduced verbatim below, and it is worth reading rather than skimming: it is not noise. It contains real countries the fold lacks (Gambia, Libya, Niger, Zambia, Sudan, Lesotho, Dominican Republic, Jamaica, Trinidad and Tobago), US state abbreviations (MA, NH, RI, CT, AZ, MD, NC, NV), cities (Hangzhou, Guangzhou, Belo Horizonte, Tallinn, Moscow), cloud region codes (ap-southeast-2, westus, FRA, SYD), and — the largest group — indoor micro-locations from wireless and IoT papers (anechoic chamber, balcony, underground parking lot, football field, coffee shop). That last group is the honest reason the residue is 12% rather than 2%: measuredFrom includes papers whose “vantage point” is a room, and a country fold cannot map a room. The page's 71.0%-silence figure is not affected — those papers are in the “no mappable location” row either way — but a reader building a country table from this field should exclude non-web platforms first.
Fixing the residue was considered and rejected for this run: adding the missing countries would move the United States figure by 0 and the tail by single papers, and adding the micro-locations would require a second fold dimension (indoor/outdoor) that no page needs. It is left visible instead, which is the rule.
Full-text probes: what was run, and what it can and cannot see
Both probes read data/fulltext/<year>/<venue>/<slug>/paper.cols.txt, collapse all whitespace to single spaces, and lower-case — because a PDF line break inside a phrase silently undercounts an uncollapsed match.
Probe 1: attrition vocabulary
Seventeen phrases over the 250 repeated papers. All 250 had a readable paper.cols.txt (0 missing).
61 papers (24.4%) contain at least one attrition phrase unreachable 19 | churn 19 | no longer available 12 | failed to load 9 | still alive 4 could not be reached 3 | went offline 3 | survivorship 2 | attrition 0 | survivor bias 0 dropped out of 0 | no longer resolve 0 | no longer resolved 0 | no longer reachable 0 ceased to exist 0 | sites that disappeared 0 | domains that disappeared 0
The named-bias subset (attrition, survivorship, survivor bias, survivorship bias) across four populations:
| Population | Read | Names the bias | Share |
|---|---|---|---|
repeated (crawled) | 250 | 2 | 0.8% |
| snapshots > 1, any study type | 817 | 6 | 0.7% |
crawled | 1,120 | 8 | 0.7% |
| all papers | 5,855 | 39 | 0.7% |
Why the four rows. One row would be a curiosity; four rows that agree at 0.7% are a finding about the field's vocabulary rather than about one population definition. attrition returning exactly 0 in 250 papers was treated as suspicious and checked independently: grep -rli attrition over the whole data/fulltext tree returns 22 paths, none of which is in the repeated set. (That grep is only a sanity check, not a figure — the workdir notes record that grep -rl silently skips papers whose paper.cols.txt is binary-detected, which is why every published figure comes from the streaming Node probe instead.)
What the probe cannot see. It finds phrases, not concepts. A paper that writes “we successfully loaded 91,204 of 100,000 sites” and never uses the vocabulary is a miss. So 24.4% is an upper bound on papers that say anything, and 0.8% is close to the true rate for papers that name the bias — the page says both and does not present either as a measure of good practice. Turning the bound into an estimate would need hand-coding; it is listed as an open question on the page.
Probe 2: a limitations heading
A heading, not the word — matched against a whole line, so a paper that mentions limitations in prose does not count:
const HEAD = /^\s*(?:[0-9]+(?:\.[0-9]+)*\.?\s+|[IVX]+\.\s+|[A-Z]\.\s+)? (?:limitations?|threats? to validity)(?:\s+and\s+[a-z ]{1,30})?\s*$/i;
Run on the raw (not whitespace-collapsed) line array, because a heading is a line. Every figure is a lower bound: a paper that discusses limitations without a heading, or whose heading is Discussion and Caveats, counts as a miss. That is stated on the page next to the table.
Rejected variants, each because it inflated the count with false positives on inspection: matching limitation anywhere in a line (hits every “a limitation of this approach” sentence and every related-work summary); matching discussion (a discussion section is not a limitations section); dropping the end-of-line anchor (hits table-of-contents lines and running headers).
Quote checking
scripts/biases_quotecheck.mjs checks the 32 quotes and per-paper figures the page publishes, against three invented-sentence negative controls.
The exact-match problem, and how it was solved. decolumn.mjs can emit a figure caption or a running header in the middle of a sentence that is contiguous in the PDF, and the insertion often lands mid-clause. An exact substring match therefore fails on quotes that are genuinely verbatim. Three approaches were tried:
| Approach | Result | Disposition |
|---|---|---|
Exact substring, whitespace-collapsed, .cols then .txt | 16/25. All 9 failures inspected by hand and found verbatim in the PDF flow | Rejected — too many false failures to be useful |
| Split on ellipses and clause boundaries, all fragments must match | 18/25. The remaining failures were insertions landing mid-clause, which no clause splitter can see | Rejected |
| Share of k-word shingles present, threshold | 6-word shingles: 17/25. One insertion costs exactly k−1 shingles, so a short verbatim quote broken once scores 50–77% while a paraphrase scores 75%. A share threshold cannot separate the two | Rejected |
| Run lengths of missing 3-word shingles | 32/32 located, 3/3 negative controls rejected | Adopted |
The adopted rule: cut the quote into overlapping 3-word shingles, look each up, and take the maximal runs of consecutive missing shingles. One inserted span costs k−1 = 2 consecutive shingles, or k = 3 if it also splits a hyphenated word (post-2020 → post … 2020 loses three, not two). A quote passes iff every missing run is ≤ 3 long and there are ≤ 5 runs. A paraphrase fails, because its missing shingles come in long or scattered runs — the three invented-sentence controls score runs of [1,1,12], [12,3] and [1,8,4].
The bound of 5 runs is set by the worst real case, and the bound was raised after seeing it, which is worth saying out loud because it is how a check gets fitted to its data. Hils et al.'s methods sentence is spliced four times by the decolumner, runs [2,2,3,2], and failed at ≤ 3 runs. The sentence had already been read in the PDF and is verbatim, so the choice was between dropping a correct quote and loosening the bound. The bound was loosened, and the three controls were re-run at the looser bound and still fail by a wide margin (their worst runs are 12, 12 and 8, against a cap of 3). A reader who thinks that is too generous should tighten it to ≤ 3 runs and expect exactly one failure, hils2021_privacy.
Every quote was also read in context by hand, which is how the one real problem was found and resolved:
| Item | What happened |
|---|---|
efstratiou2025_youtube, “API samples videos from empirical distributions…” | Initially judged a paraphrase on the basis of a nearby but different sentence in the paper (“the API may similarly sample from topic-wide empirical distributions”), and the page was drafted quoting the wrong one. A targeted regex for relative density of topical found the extraction's quote verbatim. The extraction was right and the reviewer of it — me — was wrong. The page quotes the verbatim sentence |
aqeel2020_landing | Not in data/fulltext. The page cites it for the landing-page/internal-page distinction and quotes nothing from it, so there is nothing to check. Recorded here rather than silently cited |
lerner2016_internet, “16.1% of all requests attempted to 'escape'…” | The extraction's quote has an ellipsis where the paper names its tool. Located; the page quotes it with the ellipsis intact |
hils2021_privacy, “…adopt TCF 2.x” | Wrong, and unchecked. The page had stitched two clauses of the paper together and changed TCF 2.0 to TCF 2.x. Found by the citations reviewer, not by this script — because the quote was not in this script's list. Fixed on the page and added to the list |
scheitle2018_long, “up to 50% daily change” | A paraphrase typeset as a quote, and also unchecked. The paper says “daily churn of up to 50% of domains”. Same cause: the string was typed from reading the paper rather than lifted from evidence.quote, so it never entered this script's list. Fixed and added |
jueckstock2021_realistic, “nearly 20% of domains” | Attributed to the wrong variable. The page originally presented this as a vantage-point effect. The paper attributes it to the browser configuration: “nearly 20% of domains' traffic strongly correlated to choice of BC.” The vantage-point figures are ~5% of content-providing domains and “over 7% of domains and over 5% of total HTTP traffic volume”. Found by the author while re-reading the passage; the page now has a separate table row for the browser-configuration axis and says the vantage point does not fix it. All three strings added to this script |
urban2020beyond, the 36% cookie increase | The page first wrote “a 36% difference from the unit alone” as if derived from the paper's own means (55 → 78, which is +42%). The 36% is the paper's own headline figure, stated one sentence earlier, and the two are not reconciled in the paper. The page now quotes both sentences and says so, rather than picking whichever number suits it |
bozzolan2026_llmweb | An arXiv preprint, not a corpus paper, so it is not in data/fulltext. Its quoted abstract sentence was verified by fetching https://arxiv.org/abs/2510.08101 directly |
Unedited output:
PASS scheitle2018_long 26 shingles [paper.cols.txt:100% runs=[-] paper.txt:77% runs=[3,2,1]] "we find the amount of NX-DOMAIN responses in both the Umbrella (11…" PASS scheitle2018_long 7 shingles [paper.cols.txt:100% runs=[-] paper.txt:100% runs=[-]] "all three only on 99k out of 1M domains." PASS scheitle2018_long 29 shingles [paper.cols.txt:100% runs=[-] paper.txt:100% runs=[-]] "all Top 1M lists exceeding the general population by at least a fa…" PASS scheitle2018_long 17 shingles [paper.cols.txt:100% runs=[-] paper.txt:94% runs=[1]] "At 11-13%, we find IPv6 enablement across top lists to significant…" PASS scheitle2018_long 7 shingles [paper.cols.txt:100% runs=[-] paper.txt:100% runs=[-]] "revealing daily churn of up to 50% of domains." PASS scheitle2018_long 10 shingles [paper.cols.txt:80% runs=[2] paper.txt:70% runs=[2,1]] "10k probes at 1 query per day ... achieve a rank of 38k" PASS ruth2022_toppling 12 shingles [paper.cols.txt:83% runs=[2] paper.txt:83% runs=[2]] "Top lists exhibit noticeable and irregular geographic biases ... a…" PASS ruth2022_toppling 7 shingles [paper.cols.txt:86% runs=[1] paper.txt:86% runs=[1]] "is the most accurate top list compared to Cloud" PASS ruth2022_toppling 1 shingles [paper.cols.txt:100% runs=[-] paper.txt:100% runs=[-]] "across all metrics" PASS ruth2022_toppling 16 shingles [paper.cols.txt:100% runs=[-] paper.txt:88% runs=[2]] "of the 1,790 domains we measure in the Alexa top 10K, 70% of them …" PASS ruth2022_toppling 15 shingles [paper.cols.txt:100% runs=[-] paper.txt:93% runs=[1]] "our analysis reveals certain categories of websites (e.g., adult a…" PASS ruth2022_toppling 10 shingles [paper.cols.txt:100% runs=[-] paper.txt:70% runs=[3]] "Top lists better approximate client behavior on desktop platforms …" PASS hils2021_privacy 30 shingles [paper.cols.txt:70% runs=[2,2,3,2] paper.txt:70% runs=[2,2,3,2]] "in order to avoid survivorship bias in our observations. Picking a…" PASS singh2017_torexit 35 shingles [paper.cols.txt:100% runs=[-] paper.txt:74% runs=[2,3,3,1]] "several of the selected exit relays intermittently went offline, w…" PASS jueckstock2021_realistic 13 shingles [paper.cols.txt:77% runs=[2,1] paper.txt:77% runs=[2,1]] "Around 5% of content-providing domains show significant measuremen…" PASS jueckstock2021_realistic 14 shingles [paper.cols.txt:79% runs=[1,2] paper.txt:79% runs=[1,2]] "sites that always failed to load from a particular vantage point (…" PASS jueckstock2021_realistic 2 shingles [paper.cols.txt:100% runs=[-] paper.txt:100% runs=[-]] "nearly 20% of domains" PASS jueckstock2021_realistic 4 shingles [paper.cols.txt:50% runs=[2] paper.txt:50% runs=[2]] "strongly correlated to choice of BC." PASS jueckstock2021_realistic 10 shingles [paper.cols.txt:100% runs=[-] paper.txt:100% runs=[-]] "over 7% of domains and over 5% of total HTTP traffic volume" PASS zeber2020representativeness 32 shingles [paper.cols.txt:100% runs=[-] paper.txt:100% runs=[-]] "we find that the average similarity in third parties is low, with …" PASS zeber2020representativeness 21 shingles [paper.cols.txt:100% runs=[-] paper.txt:81% runs=[2,2]] "crawler site visits issued requests to a median of 11.6 third-part…" PASS annamalai2025_beyond 17 shingles [paper.cols.txt:100% runs=[-] paper.txt:82% runs=[2,1]] "the real user browsing sessions detected 471 such fingerprinting w…" PASS urban2020beyond 30 shingles [paper.cols.txt:100% runs=[-] paper.txt:83% runs=[3,2]] "subsites set considerably more (36 %) cookies than the respective …" PASS urban2020beyond 16 shingles [paper.cols.txt:100% runs=[-] paper.txt:88% runs=[2]] "of the measured websites do not embed any trackers on the landing …" PASS demir2022_reproducibility 29 shingles [paper.cols.txt:93% runs=[2] paper.txt:93% runs=[2]] "profiles from the USA are tracked most in terms of distinct tracke…" PASS demir2023_similarity 13 shingles [paper.cols.txt:85% runs=[2] paper.txt:85% runs=[2]] "The results show that when comparing two different profiles, 48% o…" PASS demir2023_similarity 13 shingles [paper.cols.txt:100% runs=[-] paper.txt:92% runs=[1]] "only 32% of the cookies appear in all profiles and 42% only in one…" PASS efstratiou2025_youtube 9 shingles [paper.cols.txt:89% runs=[1] paper.txt:89% runs=[1]] "The results suggest that drop-ins and drop-outs are the normative …" PASS efstratiou2025_youtube 5 shingles [paper.cols.txt:100% runs=[-] paper.txt:100% runs=[-]] "tends to return shorter, more popular videos" PASS efstratiou2025_youtube 29 shingles [paper.cols.txt:93% runs=[2] paper.txt:93% runs=[2]] "API samples videos from empirical distributions, returning results…" PASS singh2025_where 12 shingles [paper.cols.txt:100% runs=[-] paper.txt:83% runs=[2]] "websites in 91% of the examined countries (21/23) embed trackers h…" PASS lerner2016_internet 4 shingles [paper.cols.txt:100% runs=[-] paper.txt:100% runs=[-]] "16.1% of all requests attempted to" PASS CONTROL invented (ruth) 16 shingles [paper.cols.txt:13% runs=[1,1,12] BAD paper.txt:6% runs=[3,12] BAD] "we conclude that the Tranco list is the single most accurate ranki…" PASS CONTROL invented (jueckstock) 17 shingles [paper.cols.txt:12% runs=[12,3] BAD paper.txt:12% runs=[12,3] BAD] "we found that the choice of vantage point had no measurable effect…" PASS CONTROL invented (efstratiou) 15 shingles [paper.cols.txt:13% runs=[1,8,4] BAD paper.txt:13% runs=[1,8,4] BAD] "the YouTube Search API returned a stable and reproducible set of v…" 35/35 checks passed (32 quotes must locate, 3 controls must not); 0 failed. Rule: a quote locates iff every run of missing 3-shingles is <=3 long and there are <=5 runs.
Note how often paper.txt fails where paper.cols.txt passes. That is the two-column reading-order repair working, and it is the reason the brief says to read .cols.
External sources: verified, and rejected
Verified against a primary source, 2026-08-20
| Claim on the page | How verified |
|---|---|
| Alexa's ranking service retired 2022-05-01; APIs 2022-12-15 | Amazon's own retirement notice. support.alexa.com no longer resolves (curl: Could not resolve host), so read via the Internet Archive: “we made the difficult decision to retire Alexa.com on May 1, 2022 … The APIs will be retired on December 15, 2022.” The retirement of the support page for the retirement notice is itself the point |
alexa.com now redirects to Amazon's voice assistant | curl -L: 200, effective URL https://alexa.amazon.com/about |
| Tranco removed the Alexa ranking | Tranco front page, fetched: “The Alexa ranking has been removed from the default Tranco list, as it is no longer available.” |
| CrUX and Cloudflare Radar folded into Tranco's default list from 2023-08-01 | Same page: “The Chrome User Experience Report and Cloudflare Radar rankings have been integrated into the default Tranco list, starting from the daily updated list of August 1, 2023.” Also records Farsight integrated since 2022-05-01 |
| Cisco Umbrella top-1m still published | curl 200 on the S3 index and the top-1m.csv.zip link it names |
| Majestic Million still published | curl 200 on majestic.com/reports/majestic-million |
| Tranco still live and daily-updated | curl 200; front page states “A daily update to the list is made available by 0:00 UTC” |
Rejected, or could not be established
| Item | Disposition |
|---|---|
| Cloudflare Radar domain rankings page | curl returns 403 (bot management). Not cited directly; the Radar→Tranco integration is cited from Tranco's own page instead, which is the better primary source for the claim actually made |
| A post-2022 replacement for Ruth et al.'s list-against-ground-truth comparison | Not found, in the corpus or outside it. Listed as an open question on the page rather than filled with a weaker source |
| A named “survivorship bias correction” method for web crawls | Does not exist in this corpus. The page states the reporting discipline (three denominators, balanced panel) rather than inventing a method name |
| Any SEO or vendor listicle on “best website ranking lists” | Not consulted. The brief records a previous run's industry notes leaning on listicles; nothing on this page needs a vendor's opinion, and every list claim above resolves to the list operator's own page or to a peer-reviewed measurement |
The stale-figure guard
node scripts/biases_report.mjs > scripts/biases_report-output.txt node scripts/biases_quotecheck.mjs > scripts/biases_quotecheck-output.txt cat scripts/biases_report-output.txt scripts/biases_quotecheck-output.txt > out/biases_all.txt node scripts/check_page_numbers.mjs pages/statistics_biases.txt out/biases_all.txt # -> OK — every figure in the page traces to out/biases_all.txt
Run whole-page, with no section markers, because the page repeats corpus figures in its opening box, its bias sections, its currency table and its worked example — all outside any “Use in Publications” window. The workdir notes record 29 stale figures surviving a refresh because the check was windowed.
What the guard does and does not prove. It proves every number on the page appears somewhere in the report output. It does not prove a number is the right one for the sentence it sits in — and that gap bit this page. After the fold fix moved 15 figures, three stale ones survived in the “Methodology and limitations” bullets (773 is a floor, 463 against 464 here, 25 evidence quotes) and the guard passed, because all three numbers appear in the report for unrelated reasons: 773 is the tail of the page's own “2,773”, 463 is the cross-check figure from Sampling, and 25 is a row count. They were caught by re-reading the section by hand. Anyone refreshing this page after a fold change must re-read the prose, not just re-run the guard.
The guard's first run reported 23 unaccounted figures, every one of them a legitimate non-corpus number. Rather than adding 23 entries to the shared ALLOW map in check_page_numbers.mjs, biases_report.mjs gained a Section Z that prints every such number with its source: per-paper figures with the paper's key, figures taken from a neighbouring wiki page, corpus facts documented in data/extract/README.md, the external dates above, and the three illustrative numbers in worked examples that are not measurements at all. Section Z is the audit trail for the digits the report does not compute, and it is why the guard now passes with no shared-state edit.
Report script and its unedited output
scripts/biases_report.mjs, 763 lines of output. Reproduce with:
node scripts/biases_report.mjs # ~20 s, reads 6,105 full texts across two probes node scripts/biases_quotecheck.mjs # ~5 s, 35 checks
The output is committed at scripts/biases_report-output.txt and scripts/biases_quotecheck-output.txt. It contains, in order: the population table; the ranking-list fold with its 25-row residue head; the Alexa/Tranco period table; samplingMethod over sampled with the sentinel counted separately; top-n against stratified over webCrawled; list-version reporting over three populations; the repeated population and snapshot distribution; probe 1 in full including the four-population named-bias table; probe 2 with its per-period table; the location fold with all 354 unmapped strings printed; single-vantage exposure; vantage.infrastructure; population.unit over webCrawled; sampling-method exposure for keyword-derived populations; the detection-prevalence denominator-risk block; the per-venue and per-year corpus tables; and Section Z.
What could not be established
- Which sites drop out of a crawl, and how. No paper in this corpus characterises the composition of the set that fails to load. Everything the page says about the direction of survivorship bias is reasoning from mechanism, not a measurement, and is worded as such. Sampling reached the same conclusion independently.
- How many repeated-crawl papers report attrition numerically without the vocabulary. The probe cannot see it. This is the single figure that would turn the page's 0.8% from a bound into an estimate.
- Whether LLM-based classification carries its own selection bias in this literature. LLM classification appears in the 2025–2026 slice; a bias characterisation of it does not. Stated as an open question rather than speculated about.
- A defensible ranking of the four biases by magnitude. The effect sizes come from incomparable designs. Not attempted.
- Denominator risk, quantified. The report prints that 4,390 of 5,655 papers with a detection tuple (77.6%) state at least one prevalence containing a
%, and that 45.9% of the 26,316 stated prevalences do. This was not put on the page as a figure:metricandprevalencebeing separate fields makes it possible for them to disagree, but establishing that they do disagree in a given paper needs the paper's own denominator read frompaper.cols.txt, which was not done at scale. The block stays in the report as a pointer for a future run.
Judgement calls
| Call | Why | A reasonable person might have |
|---|---|---|
| New page rather than broadening Sampling | Six existing pages already promise statistics:biases by that name, and sampling explicitly names it as its downstream page | Folded selection bias into sampling and vantage bias into crawling location, leaving only survivorship and denominators here — which would have left a page too thin to link |
| Dated Alexa-based selection as superseded, not merely dated | The list does not exist and cannot be re-drawn. That is stronger than unfashionable | Called it “historical” and left it at that |
| Did not call top-n outdated | The corpus says the opposite: top-n consolidated to 64.7% of web sampling in 2025–2026. It is current practice with a known bias, which is a different claim from being superseded | Read the growth of stratified sampling as a transition. It is not one — it plateaued at 8.9% |
| Kept the 0.8% attrition figure despite it being a keyword probe | Four independent populations agree at 0.7%, and the page states the bound explicitly in both directions | Dropped it as unpublishable. That would have removed the page's clearest finding about the field |
| Reported the limitations-heading probe as a lower bound and published it anyway | The 3.8% → 30.1% trend is robust to the bound: any missed heading is missed in every period | Withheld it for imprecision |
Used measuredFrom (3,908) for vantage exposure, not crawled (1,120) | Crawling location already owns the crawling population; the wider one is a different and stronger statement (71.0% silence against 70.8% there, on 3.5× the papers) | Reused the crawling figures and said nothing new |
| No published code block | The page's argument is about design and reporting. A simulation would illustrate a statistical point that Hypothesis testing already publishes code for | Published a survivorship simulation. It would have duplicated that page's clustering demo in a new costume |
| No discussion block on this page | The convention was set by the earlier provenance: pages and is followed here: comments belong on the content page, where a reader with a correction to a figure will be reading | Added one, on the theory that a disputed query is worth discussing separately from a disputed claim |
| Left the geo fold residue unfixed | It is 12.1% and mostly indoor micro-locations from non-web papers; fixing it moves no published figure | Extended geo.mjs. That would change figures on Crawling location without reviewing them |
Post-publish checks
Both were run after saving, and both found something.
1. In-page anchors — one was broken. The page's own section links rely on DokuWiki's section-ID cleaning, which strips punctuation, colons and a leading 1.. Extracting every id= from the rendered DOM and every href=“#…” and differencing them found one broken link out of 62: [[#Worked Example]] does not resolve, because the heading is Worked Example: The Biases of This Site's Own Corpus and DokuWiki's id for it is worked_examplethe_biases_of_this_site_s_own_corpus. A partial heading text is not a valid anchor, even though a truncated one reads fine in the source. The digit-prefixed headings ([[#1. Selection Bias: Your Sample Is a Ranking List]]) do resolve, because the link side and the heading side clean identically. Fixed by linking the cleaned id with a display label.
# the check, worth re-running after any heading edit
curl -s "https://measuretheweb.org/doku.php?id=statistics:biases" |
python3 -c 'import re,sys; h=sys.stdin.read();
ids=set(re.findall(r"<h[1-6][^>]*\bid=\"([^\"]+)\"",h));
links=set(re.findall(r"href=\"#([^\"]+)\"",h));
print([l for l in links if l not in ids and not l.startswith(("fn","ref__","dokuwiki__","discussion__"))])'
# -> [] after the fix
2. Citations needed a cache purge. On first load the content page rendered 16 of 18 references: the markers [9] and [14] were missing, and those are exactly the two entries added to Bibliography in this run. The bibtex4dw plugin caches the parsed bibliography, so a page saved in the same sitting as a new entry renders as if the entry did not exist — with no error and no unresolved marker to notice. Fetching the bibliography and then the page with &purge=true fixed it; both pages then rendered every reference.
curl -s ".../doku.php?id=literature:bibliography&purge=true" > /dev/null
curl -s ".../doku.php?id=statistics:biases&purge=true" > /dev/null
# then count rendered <dt>[n]</dt> against the page's distinct {[key]} count
Do not check this by looking for a broken citation. There isn't one. The only symptom is a reference list shorter than the number of distinct keys, which is why the check is a count and not a read.
3. Rendered structure, both pages. statistics:biases: 18 references, 11 h2, 25 h3, 17 tables, 4 WRAP boxes, 3 footnotes. provenance:statistics:biases: 1 reference, 17 h2, 14 h3, 17 tables, 11 pre blocks, 0 footnotes. No unclosed block, no literal wiki markup in the output, no truncated page.
Notes for the next run
- Two duplicate citekey pairs exist in Bibliography and were noticed while checking this page's keys:
lerner2016_internet/lerner2016internetandboettger2025_regional/bottger2025_regional(alsobouhoula2024_automated/bouhoula2024automated). This page useslerner2016_internet. Not fixed here — deduplicating keys touches every page that cites them and is its own item. repeatedshould probably not requirecrawled. See theefstratiou2025_youtubecase above. Any future page about longitudinal measurement should define it ontemporal.snapshotsalone and say so.population.unitputs 41.4% of web-crawling papers inother. Any unit-based figure from this field, on any page, should print that share next to it.
The run itself
| Item | Value |
|---|---|
| Corpus at the time | 5,859 extracted papers, 7 venues, 2010–2026, data/extract/run1 |
| New bibliography entries | 2 — efstratiou2025_youtube (IMC 2025, DOI from OpenAlex via bibgen.mjs), singh2017_torexit (USENIX Security 2017; USENIX index records carry no authors, so authors were fetched from the landing page with curl and a browser User-Agent, per the brief) |
| Duplicate check before appending | key-string collision check (both free) and a DOI and title scan of the existing 430 entries (no match) |
| Accidental exposure | none. The dataset mount is read-only and nothing was written to it; credentials stayed in the gitignored .env |
| Mistakes caught in review | the efstratiou2025_youtube quote misjudged as a paraphrase (§ Quote checking); the shingle-share quote check that could not separate a broken verbatim quote from a paraphrase, replaced with the run-length rule; three separate bugs in the ranking-list fold, each one exposed by the fix for the previous (§ The ranking-list fold); the review-layer findings below |
| Mistakes caught after publishing | one broken same-page anchor, and two citations silently missing from the rendered reference list until the plugin cache was purged (§ Post-publish checks). Both were invisible in the source and only findable in the rendered DOM |
Review log
Three focused reviewers in parallel on a frozen snapshot, then fixes, then a generic pass. All were told explicitly that the author's context may not be exhaustive, and all were handed the page text, both scripts, their unedited output and these notes.
Author's own pass, before the reviewers
Run first, because the sibling logs record that reviewers do not check rhetorical sentences. It ran twice — once before the reviewers and once after their fixes were in — and found sixteen things in prose and in the methodology bullets. Twelve were outright wrong, including two self-contradictions and two claims stated backwards. None of the five focused reviewer passes flagged any of them.
| # | Finding | Disposition |
|---|---|---|
| A1 | “the most common statistical error in longitudinal web measurement — more common than any test-choice error.” Unsupported. No error rates were measured | FIXED. Replaced with the claim the page can support: this is the one bias of the four that requires no decision to commit |
| A2 | “This is the largest population on this page” of the keyword-derived population. False: 398 purposive against 467 rank-list papers, both of 857 | FIXED. Now “the least examined of the four”, and a sentence added that purposive does not separate keyword-derived draws, so 46.4% bounds the exposure rather than measuring it |
| A3 | “No bias-correction method specific to LLM-based classification appears.” Never queried. The claim was written from an impression | QUERIED, then FIXED. New section 6 in the report: 166 of the 1,185 papers in the 2025–2026 slice name a model in a classification tuple (14.0%); 4 of the 166 mention bias or calibration, and all four were read and are about something else. The claim now carries its numbers |
| A4 | “the only defence against the biases you would otherwise discover post hoc” | FIXED to “the strongest available defence” |
| A5 | “the widest gaps on this page” contradicted the provenance page's own decision not to rank the four biases | FIXED. Reworded to say the last two rows are not about geography and report the largest differences in that table, with an explicit note that this is not a ranking of the biases |
| A6 | “Any Alexa-based figure published before roughly 2024.” The date is arbitrary — the retirement is 2022-05-01, and what matters is the frame, not the publication year | FIXED to “any figure drawn from an Alexa list” |
| A7 | “Five papers” heading a list of five bullets containing six papers (Demir et al. is two) | FIXED to “Five entries” |
| A8 | The opening WRAP header claimed “every one of them is larger than a typical reported effect”, and the lede said “several of them are larger than the effects papers report.” Neither was measured — no distribution of reported effect sizes was computed | FIXED. The header now says only that each bias has been measured by somebody in this literature; the lede gives the observed range instead of a comparison |
| A9 | Section 2's heading said the two survivorship effects “push in opposite directions” and the paragraph three lines below said they “both inflate an adoption trend, so they do not cancel”. A direct self-contradiction inside one subsection | FIXED. The heading is now “and they do not cancel”, and the paragraph explains that they arrive from opposite ends of the panel and push the estimate the same way |
| A10 | “The gap is the survivors” — the gap is the non-survivors | FIXED |
| A11 | “your denominator is pre-weighted towards exactly the tail you were probably trying to characterise” — backwards. A sampler that prefers popular items weights away from the tail | FIXED |
| A12 | “Nobody has characterised the drop-out set” — a claim about the world from a seven-venue corpus | FIXED to “The drop-out set has not been characterised”, scoped to the corpus, and the external sweep's near-miss (the PAM 2025 refusals paper) recorded in the same bullet so the next run knows it was considered |
| A13 | Three stale figures survived the fold fix in the page's own “Methodology and limitations” bullets — 773 is a floor (now 795), 463 against 464 here (now 477), 25 evidence quotes (now 31). check_page_numbers.mjs passed anyway, because 773, 463 and 25 all appear elsewhere in the report for unrelated reasons (773 is the tail of “2,773”; 463 is the cross-check figure; 25 is a row count). The guard proves every number appears in the report; it cannot prove a number is the right one for the sentence it sits in | FIXED, and recorded as a limitation of the guard. Anyone refreshing this page after a fold change must re-read the Methodology bullets by hand — the guard will not save them |
| A14 | The worked example labelled provisional venue-years as “drop-in bias”. It is truncation | FIXED |
| A15 | Related Pages said Zeber et al. and Annamalai et al. “show [these axes] matter more than geography.” Neither paper makes a geographic comparison | FIXED. The comparison is now attributed to this page, with a pointer to the table it is read off |
| A16 | Three cells in the currency table read as trends when they are cross-population comparisons or three-paper anecdotes: list-version reporting “up from a corpus-wide 43.0%”; real-user validation as “the clear direction of travel”; attrition reporting as “current and under-used” | FIXED, all three. Each now names its populations, or says explicitly that three papers is a direction and not a rate, or — for attrition — that there is no trend at all, only a gap |
''sonnet'' — figures against the script
| # | Finding | Disposition |
|---|---|---|
| B0 | Re-ran both scripts: output reproduces byte-for-byte. Traced every figure in the WRAP box, all four bias sections, the currency table, every “Use in Publications” table and the worked example against the report; every cell matched and every percentage recomputed correctly | Noted. This is what the check is for |
| B1 | BLOCKING. “IEEE S&P is 780 of 780” in the worked example contradicts the venue table two paragraphs above it on the same page, which says 767 — as does OVERVIEW.md. 780 is the selection count; 767 is the extraction count | ACCEPTED and FIXED. The page now says 767 records are in the extraction, explains that retrieval and extraction are different steps, and adds “do not quote the selection count as if it were the extraction count” to the lesson. Section Z of the report was corrected too — it had carried the same conflation |
| B2 | Bug: isRankingList() returns on first match, so a sourceList naming several lists credits only one. Undercounts Tranco, Umbrella, Majestic, Common Crawl — including in the Alexa→Tranco table | ACCEPTED and FIXED, and the fix exposed a second bug (the anchors) that the reviewer did not find. See the fold section above for both, and for the 15 figures they moved |
| B3 | No defects found in: year-bucket boundaries, snapshot buckets, sentinel handling, paper-vs-tuple counting, or the run-length quote rule. It also reported no false positives in the list matcher, having spot-checked all 388 alexa-containing and all 24 umbrella-containing sourceList strings | The list-matcher part of this verdict was wrong, and the same reviewer overturned it on re-review (B5). The spot-check was done against the anchored patterns, which could not reach the strings that later turned out to be false positives. A clean verdict on code that is about to change is a verdict about the old code |
| B4 | Re-review, blocking ×4: four stale pre-fix figures survived the fold fix — 773 is a floor, Alexa 464 here (twice, on both pages), 25 evidence quotes — and the provenance page's Scope-decision section contradicted its own fold section 90 lines later. It also identified the root cause: Section Z of the report hard-coded (this page: 464), so check_page_numbers.mjs blessed 464 as accounted | ALL ACCEPTED and FIXED. The Section Z line no longer carries this page's own figure at all, only Sampling's, so the guard can never again bless one of this page's own stale numbers through it |
| B5 | Re-review, blocking: two false positives in the unanchored /alexa/ pattern that the exclusion list missed — a smart-home integration set and an author's surname. The first is in webCrawled and inflated the opening headline | ACCEPTED and FIXED. This is the third fold bug; see the fold section for the mechanism and the final figures (793 / 479 / 475). The reviewer also correctly flagged that this log's earlier “no false positives in the list matcher” line was overstated, since that verdict came from a spot-check of the anchored version. Rewritten |
| B6 | Re-review, nit: the LLM regex's /gemini/ matches Gemini, a 2019 binary-code-similarity model, in a classification.method field. It is outside the year >= 2025 filter so no published figure is affected, and the reviewer hand-checked every gemini/claude/llama/mistral hit in the 2025–2026 slice and found all genuine | ACCEPTED as a latent fragility, not fixed. Narrowing the pattern would change no figure on the page. Recorded so a future run widening the year range knows to fix it first |
| B7 | Re-review: the relaxed quote rule was tested against the three controls and against an attempt to construct a passing paraphrase; none found. Flagged as worth re-scrutiny if more quotes are added under the looser rule | Noted. The rule's parameters and the reason they were loosened are stated above so a future run can tighten them |
''sonnet'' — citations and quotes
| # | Finding | Disposition |
|---|---|---|
| C1 | All 17 citekeys resolve; no dangling citations. No key collision and no DOI, title or author match against the existing 430 entries for either new entry | Noted |
| C2 | efstratiou2025_youtube is missing pages, which Crossref gives as 919–927 | ACCEPTED and FIXED |
| C3 | singh2017_torexit: title, all seven authors and pages 325–341 verified verbatim against the USENIX page | Noted |
| C4 | The Hils et al. quote silently changes a version number. The page had “certain to adopt TCF 2.x”; the paper says “TCF 2.0”. The page had stitched together two different clauses. And the quote was not in the quotecheck's list, so the check could not have caught it | ACCEPTED and FIXED. The page now carries the paper's continuous sentence, and the quote was added to biases_quotecheck.mjs |
| C5 | “up to 50% daily change” is typeset as a verbatim quote and is a paraphrase. No such string is in the paper; its wordings are “daily churn of up to 50% of domains” and “up to 50% churn per day for some lists”. Also outside the quotecheck's list | ACCEPTED and FIXED. The page now quotes “daily churn of up to 50% of domains”, and this and the Umbrella rank-manipulation quote were added to the quotecheck |
| C6 | Non-quote claims spot-checked (Ruth on CrUX accuracy, Hils on survivorship, Zeber, Annamalai, Jueckstock's refusenik counts, Urban, Singh, Lerner) all supported by their sources, refusenik counts 72/69/30/11/2 reproduced exactly from Table 1 | Noted |
| C7 | Both footnote external claims verified independently against primary sources | Noted |
| C8 | Three duplicate-key pairs confirmed in the live bibliography: lerner2016internet/lerner2016_internet, bottger2025_regional/boettger2025_regional, bouhoula2024automated/bouhoula2024_automated | ACCEPTED as a finding, not fixed here. Already recorded under Notes for the next run; deduplicating touches every citing page and is its own item |
Re-review after the fixes (same reviewer, its context intact, new snapshot): all citekeys still resolve, no new duplicates, C2/C4/C5 confirmed fixed, and it independently verified two things the author had changed on his own initiative — the VP/BC re-attribution (it reconstructed the spliced sentence and confirmed 5% is the VP axis while “over 7% of domains and over 5% of total HTTP traffic volume” and “nearly 20% of domains' traffic … strongly correlated to choice of BC” are both the BC axis) and the bozzolan2026_llmweb abstract quote and both claims drawn from it. One remaining finding: a third efstratiou2025_youtube quote (“tends to return shorter, more popular videos”) was outside the check's list — the same class of gap as C4 and C5. It had in fact been added between the reviewer's snapshot and its report, so it was already closed; the list stands at 31 quotes and the reviewer's point about relying on “verified by tooling” rather than “verified by hand” is the right one to record.
C4 and C5 together are the most useful thing the review layer produced. Both quotes were outside the quotecheck's list, and both were wrong in exactly the way the quotecheck exists to prevent. The lesson is procedural: the check is only as good as the completeness of its input list, and the list was assembled by hand from the extraction's evidence.quote values — so the two quotes the author typed from a paper rather than lifting from the extraction were the two that were unchecked and wrong. Nine quotes have since been added; the list now stands at 32.
''sonnet'' — external currency, as of 2026-08-20
| # | Finding | Disposition |
|---|---|---|
| D1 | Tranco, Umbrella top-1m, Majestic Million, CrUX, SimilarWeb, Quantcast, Statvoo, Common Crawl: all live, all verified 200. Both Tranco quotes and both Alexa retirement dates confirmed verbatim | Noted |
| D2 | radar.cloudflare.com/domains returns 403 (bot-walled) — the page already handles this by citing Tranco's own page instead | Noted; no change |
| D3 | The standalone CrUX Dashboard was retired in late 2025; the dataset, API and BigQuery export are unaffected. The page's CrUX claims are all about the dataset and remain accurate | ACCEPTED as a nit and FIXED — a footnote now distinguishes the dataset from the retired dashboard, because a reader following an old tutorial will hit the dead one |
| D4 | arXiv:2510.08101, LLM-Assisted Web Measurements (Bozzolan, Calzavara & Cazzaro, 2026) answers the page's own invitation “if you know of one, please add it” for LLM-classification bias | ACCEPTED and FIXED, and it turned out to be a better fit than the reviewer knew. Its abstract states the premise of the page's own §4© — that researchers “rely on ad-hoc techniques to bias datasets toward specific website classes of interest” — so it is cited twice: as the documented alternative to a keyword-built population, and in the narrowed open question. It was already in the bibliography as bozzolan2026_llmweb, so no new entry was needed. The open question now says what is still missing (a per-class error and calibration profile) rather than claiming nothing exists |
| D5 | DomCop's list has migrated to an OpenPageRank / KeywordsEverywhere URL | NOTED, not changed. “Statvoo / DomCop / other rank vendor” is a catch-all bucket label for 8 papers and is not linked or cited; renaming it would imply the corpus papers used the new product |
| D6 | “Web Crawl Refusals: Insights From Common Crawl” (Ansar, Sperotto & Holz, PAM 2025) is adjacent to the drop-out open question but is about server-side blocking generally, not the composition of the lost set | REJECTED for the page. The reviewer's own assessment is that it does not satisfy the question as stated, and citing a short paper for a claim it does not make is worse than leaving the question open. Recorded here so the next run does not re-find it and reach a different conclusion without knowing this one was made |
| D7 | No standards, RFCs or versioned specs are referenced on either page; nothing to obsolete | Noted |
| D8 | Every internal link target on both pages returns 200 | Noted, and re-checked independently against dw.mjs info for the four provenance: and statistics: targets |
''fable'' — generic
| # | Finding | Disposition |
|---|---|---|
| E1 | Blocking: the four stale pre-fix figures (same as B4, found independently), and it also traced the root cause to Section Z's hard-coded (this page: 464) | FIXED. Two reviewers finding this independently is the strongest signal in the log that a stale-figure sweep after a fold change cannot be delegated to the guard |
| E2 | Blocking: the quote count is stated three different ways — “25 quotes” on the content page, “the 25 quotes” and “~5 s, 28 checks” here, against the committed output's “33/33 (30 quotes)” | FIXED, all four places, and the report line count too. It is the page's trust anchor and it was the least maintained number on it |
| E3 | Should-fix, arguably blocking: the opening bold claim “every one of them is larger than a typical reported effect” makes a stronger version of the cross-bias comparison this log explicitly says was “Not attempted” | ACCEPTED and FIXED — the author had already caught the same sentence (A8) between the snapshot and this report. Two independent hits on one sentence |
| E4 | Should-fix: the snapshot-distribution rows sum to 269 of 250 with no note. The report's own column header says the bands overlap; the wiki table dropped it | ACCEPTED and FIXED. The page now states the sum and why the bands overlap, and the report prints the row sum against the population so the next run cannot drop it again |
| E5 | Should-fix: the refusenik table row listed “72 from cloud, 69 naive, 30 stealth, 11 residential, 2 university” under a vantage point heading — but naive and stealth are browser configurations. The row re-conflated the two axes the earlier fix had just separated | ACCEPTED and FIXED. The row is now “either axis”, carries the paper's own definition, and splits the counts by axis |
| E6 | Should-fix: “CrUX came out as the most accurate list across all their metrics” adds a universal not in the checked quote | REJECTED on the facts, and the check tightened. The paper says “the Chrome User Experience Report (CrUX) dataset, which is the most accurate top list compared to Cloudflare across all metrics” — “across all metrics” is the paper's own. The reviewer was right that the quotecheck's string stopped short of it, so the phrase was added as a 32nd checked fragment. A correct rejection still improved the check |
| E7 | Nit: the lede says the page “closes with a worked example” and two sections follow it | REJECTED. “Closes with” reads as the last substantive argument, which it is; What to Report and Open Questions are apparatus. Changing it would cost a clear sentence to fix a reading nobody has |
| E8 | Nit: the Scheitle table partially duplicates Top-n is a census of the head — HTTP/2, IPv6 and CDN figures appear on both | ACCEPTED as a known overlap, not removed. The reviewer's own advice was not to restructure. This page adds the NXDOMAIN row and the claims-licensing frame; the shared rows are what make the table readable without a click-through. It is the one visible overlap with a neighbour and is recorded here so a future editor knows it is deliberate |
| E9 | Nit: the in-page anchors ([[#1. Selection Bias: Your Sample Is a Ranking List]]) rely on DokuWiki's section-ID cleaning of punctuation and colons; worth one click-through after publish. All cross-page anchors verified to exist | ACCEPTED as a post-publish check, listed below |
| E10 | Clean and explicitly not to be changed: all block syntax balanced, footnotes well-formed, no discussion block here and one on the content page, every citekey resolves with no duplicate-under-another-key, reading order and neighbour boundaries right, and this log reads as a working log rather than self-congratulation | Noted, and left alone |
What the review layer was worth
| Reviewer | Findings | Accepted | What it caught that nothing else would have |
|---|---|---|---|
| author's own pass, twice | 16 | 16 | Every one was in prose or in a methodology bullet. Twelve were false, backwards or stale (A1–A3, A5, A8–A15) and no focused reviewer flagged any of them. If a future run has budget for only one pass, it is this one |
sonnet figures/script | 3 (+1 clean report) | 3 | The IEEE S&P self-contradiction, and the fold bug that moved 15 published figures. The single highest-value slot |
sonnet citations/quotes | 8, then 1 on re-review | 8 | Two quotes that were wrong and outside the quote check. Neither the guard nor the quotecheck could see them. On re-review it also independently confirmed the author's own VP/BC re-attribution, which is the check that matters most for a reviewer's credibility |
sonnet external currency | 8 | 4 fixed, 2 noted, 1 rejected, 1 informational | The Bozzolan preprint, which changed two sections rather than one, and the CrUX Dashboard/dataset distinction |
fable generic | 10 | 6 fixed, 2 rejected, 2 noted | Three defects nothing else caught: the snapshot table summing to 269, the refusenik row re-conflating VP and BC, and the quote count stated three ways. It also independently confirmed two findings from the other passes, which is how you tell a reviewer is reading rather than pattern-matching |
Related
- Biases — the page these notes are for.
- Corpus — corpus-wide provenance: venues, funnel, provisional years, duplicates.
- sampling and hypothesis_testing — the sibling logs whose populations this page's figures were cross-checked against.
References
- [1]
- Efstratiou, Alexandros (2025): "On YouTube Search API Use in Research", in: Proceedings of the ACM Internet Measurement Conference. (DOI)
