美國每個衛生局都會公布餐廳稽查結果,也有一長串應用程式試圖在你挑選用餐地點的當下把這些結果擺到你眼前。其前提是:違規數能告訴你這間廚房的狀況。

我本來想比較幾個轄區,看看各郡之間的標準到底差多少。結果我發現這個比較根本做不起來,而背後的原因比比較本身更有意思。

以下內容全部來自公開的即時 API。程式碼在這裡,只用標準函式庫、不需 API 金鑰即可重新產生每一個數字。

兩個郡,同一份表格,相差 2.65 倍

Boulder County 與 Tri-County(Adams、Arapahoe、Douglas)都把資料發布到科羅拉多州的開放資料平台,兩者也都使用同一份 56 項的 FDA 食品法規檢查表。相同的工具、相同的違規代碼 FC01 到 FC56、相同的州。

以兩者資料集重疊的期間,2019 年至 2022 年 10 月的例行稽查為準:

  每次稽查的違規數 n
Boulder County 2.10 1,984
Tri-County 5.57 7,886

我的第一個念頭是 Tri-County 比較嚴格。資料似乎也支持這點。Tri-County 是單一機構管轄三個郡,這把地理因素與監管機構分開了,而其中三個郡的數值分別是 5.83、5.36 與 5.54。在同一個機構之下、跨越兩條郡界,差距約 9%。跨機構則是 165%。

接著我固定住餐廳。Subway 就是 Subway:相同的企業流程、相同的設備、相同的稽核制度。

連鎖品牌 Boulder Tri-County
Subway 2.42 4.41
Starbucks 0.65 2.18
Chipotle 0.69 1.94
McDonald’s 1.47 3.11
Taco Bell 1.00 3.36
Wendy’s 0.11 4.47
合計 1.15 3.16

九個連鎖品牌,九個全都指向同一個方向,合計 2.76 倍。到這裡,我很確信這兩個相鄰的科羅拉多州衛生局在執法上確實存在真實差異。

這個數字是個分數,而我只有分子

Boulder 每個檢查項目各發布一列,每列都有一個狀態。Tri-County 則是每次稽查發布一列,附帶 56 個二元旗標。

差別在於兩種格式各能表達什麼。

稽查員記錄的內容 Boulder Tri-County
已開立 OutOut-HighOut-MediumOut-Low flag = 1
看過,沒問題 In 無法區分
沒看 Not Observed 無法區分
此處不適用 Not Applicable 無法區分

Boulder 可以說「我檢查過這項,沒問題」以及「我從沒檢查這項」。Tri-County 的格式把兩者都壓成零。

於是我計算了 Boulder 稽查員實際評估了表格的多少比例:

  表格項目數 已評估項目數 違規數 每個已評估項目的違規率
Boulder 56.8 24.1 2.10 8.7%
Tri-County 56 無從得知 5.57 若全部 56 項則為 9.9%

Boulder 的稽查員有 58% 的表格項目完全沒碰。若以實際查看的項目為分母,2.65 倍就變成大約 1.14 倍。

就連這樣都還高估了殘差,因為 Tri-County 的分母根本無法還原。如果他們的稽查員同樣只評估約 24 項,那他們的真實違規率大約是 23%,是 Boulder 的兩倍以上,效果的方向就翻轉了。Boulder 的違規率有上下界,Tri-County 的沒有。

馬里蘭州的 Montgomery County 使用 15 項的表格,同樣有三種狀態的詞彙,而它把 2% 到 6% 的項目標為「Not Observed」,Boulder 則標了 58%。並沒有全國性的慣例可供標準化,而這正是任何比較已公布數字的人都看不見這件事的原因。

同樣的問題也出現在時間軸上

如果數字會隨著紀錄慣例變動,那麼在單一轄區內,只要該轄區改變書寫方式,數字也應該會跟著動。

加州的 Marin County 公布了 12 年的稽查資料。各年度每次例行稽查的違規數:

Marin County 各年度每次例行稽查的違規數,以及同樣八位稽查員在 2019 年前後的表現 左:序列在 2019 年斷裂,之後再也沒有回升。右:每一位在斷點兩側都有工作的稽查員數值都下降。

2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025
4.08 3.73 4.97 5.02 3.63 1.42 1.21 0.74 0.95 0.93 1.01 1.07

跨越單一年度界線,從 4.30 掉到 1.11。 同期稽查量反而上升,從 2018 年的 1,351 次例行稽查增加到 2019 年的 1,514 次。違規代碼的詞彙沒有變。評等標示牌制度從 2015 年起就已經在執行。

Marin 在每一列都標示稽查員姓名,這是幾乎沒有轄區會做的事。因此我能檢查人員是否有更替。在斷點兩側各至少有 40 次例行稽查的稽查員:

稽查員 2019 年前 2019+ 變化
A 7.90 1.42 −6.48
B 5.17 1.27 −3.90
C 4.32 1.10 −3.22
D 3.89 2.09 −1.79
E 3.60 0.91 −2.68
F 2.65 0.90 −1.75
G 1.43 0.81 −0.62
H 1.02 0.93 −0.09

八位全部下降。合計 −69%。稽查員 A 是同一個人做同一份工作,卻從每次稽查 7.90 個違規降到 1.42 個。

再固定住餐廳:在斷點兩側都接受過稽查的 1,059 家業者,從 4.58 降到 1.11。

同一批人。同一批餐廳。同一套代碼。違規數只剩四分之一。

真正改變的東西,可以從稽查如何被分類看出來。完全沒發現問題的比例幾乎沒動,從 11-16% 變成 13-26%。被歸為重大的比例從 23-39% 降到 8-15%,差額由輕微類吸收,而每次有違規的稽查所逐項列出的違規數,從約 4.1-5.8 降到 1.1-2.1。一次稽查會查出問題的機率大致持平,但每次稽查被寫下來的問題數量崩落了。

檢查我看到的是不是一次資料遷移

能推翻這件事的解釋是資料來源。如果 2019 年之前的資料列出自另一套系統並被搬遷進來,那這個斷點就是資料集的產物,而非實務上的改變。

三項檢驗,全部為否:

紀錄 ID 是連續的。 整整 13 年就是一條單調遞增的序列,而且在邊界處範圍互相重疊:2018 年橫跨 25,039 到 37,081,2019 年橫跨 24,518 到 42,338。沒有重新起算,也沒有斷口。

結構描述沒有變動。 inspectorplacardcorrect_by_datecorrected_on_siteinspector_commentsinspection_frequencylicense_number 從 2014 到 2026 年每一年都是 100% 填滿。唯一改變的欄位是違規代碼,從 96-97% 降到 67-90%,而那正是這篇的發現,不是結構描述的變更。

資料匯出的指紋變動出現在別處。 這個資料集中有些字串值被字面上的引號包住。這個痕跡在 2024 年以前的資料列中佔 0%,2025 年是 83%,2026 年是 100%。

最後這一點是這裡最強的證據。這條資料管線確實有改變,但發生在 2025 年,不是 2019 年。如果那 74% 的下降是資料遷移造成的,格式化的特徵應該會跟著一起移動。

它也造成了我碰到的第一個錯誤。如果依稽查員分組時沒有去掉那些引號,你會得到 46 位稽查員。實際上只有 40 位。其中六位是同一個人出現了兩次,一次有引號,一次沒有。

像 Marin 這樣的斷點有多常見

此時一個合理的質疑是:我是去找不連續點,然後找到了一個,而衛生局本來就不斷在修改他們的表單。所以我用同樣的方法檢查了另外三條長序列。

轄區 指標 期間 最大的單年變動
科羅拉多州 Boulder County 每次例行稽查的違規數 2013-2025 13 年間從 1.60 到 2.35,沒有斷點
芝加哥 稽查不合格的比例 2010-2026 16.0% 到 24.9%,緩慢漂移,沒有斷點
紐約市 被標為關鍵的違規比例 2022-2026 52.4% 到 56.7%,沒有斷點

Marin 是異常值。Boulder 是乾淨的對照,因為它是同一個指標、涵蓋更長的期間,而且年與年之間的變動從未超過約 20%。

這張表有兩個但書。芝加哥的合格/不合格與紐約市的關鍵違規比例,都是比違規數更粗糙的工具,一個衛生局可以改變逐項列舉的程度,而這兩個指標都不會動。另外紐約市公布的序列只回溯到 2022 年,這幾乎稱不上是檢驗。

綜合來看:像 Marin 這樣的斷點並不是這類資料的常態,但也沒有罕見到可以假設它不存在。我手上有長序列的四個轄區中,就有一個出現了,而公布的資料裡沒有任何地方預告這件事。

這也強化了科羅拉多州的比較。Boulder 13 年來維持平穩,代表 2.65 倍差距中屬於它的那一側,並不是我剛好挑到某些年份所造成的產物。

誰來稽查的預測力幾乎不輸是哪家餐廳

芝加哥把它的稽查優先排序模型開源了,一份獨立檢視發現,影響力最大的單一來源是由哪位衛生稽查員執行這次稽查。那是內部資料。Marin 的稽查員欄位讓這件事得以在公開資料上檢驗。

在 15,888 次例行稽查、1,645 家業者與 40 位稽查員之中,各稽查員的原始平均值從 0.74 到 5.99 不等。其中大部分來自時代因素,因為稽查員工作的時間區間不同,而 2019 年的斷點正好落在中間。

所以我先減去業者平均,使用的是被兩位以上不同稽查員查過的 1,453 家業者,接著再減去年度平均:

控制項 稽查員間的差距 標準差
原始 8.1 倍 1.41
扣除業者平均 5.18 個違規 1.27
扣除業者與年度 3.81 個違規,為平均值 2.22 的 172% 0.86

在年度控制之下排序被打亂,這正是排除純粹時代因素解釋的關鍵。控制後最嚴格的稽查員是 2025 年才開始的,位於斷點的寬鬆側。最寬鬆的那位在 2018-2019 年工作,位於嚴格側。

對原始序列做的粗略變異數分解:業者身分約佔 24%,稽查員身分約佔 21%。

這是算術上的去平均,而非配適的模型,所以請把它當成方向與量級,而不是標準誤。稽查員與年度部分共線,而橫跨斷點的那八位稽查員承擔了識別的重擔。另外兩項發現沒有這個問題,因為它們是在精確控制之下的原始平均值。

結論落在哪裡

三個面向,違規數在三者上都站不住腳。跨轄區時,它因一個紀錄慣例而變動 2.65 倍。跨時間時,在同一批人稽查同一批餐廳的情況下變動 74%。跨稽查員時,在控制之後變動幅度達平均值的 172%。

這一切都沒有衡量稽查制度是否有效。稽查員確實找出真實的問題、關閉真實的廚房,這裡的內容並不對此表態。我衡量的是那個數字,而那個數字恰恰在你拿一個去和另一個比較時就崩壞,而這正是下游所有人唯一會拿它來做的事。

真正的風險落在查詢類應用程式的下游:以違規結果訓練的城市預測模型,以及跨地區或跨政策日期比較評等制度的研究。芝加哥的模型就是以這類結果訓練的,而它自己的稽核發現稽查員是最強的預測因子,當預測目標有一部分只是文書作業時,這正是你會預期的結果。一項橫跨某轄區自身紀錄方式變更的研究,會直接撞上 Marin 的問題,因為世界靜止不動時序列卻在移動。

實務版本:違規數只有在單一轄區內、單一時代中、並針對是誰來訪視做過調整之後,才是可解讀的。其他情況都不行。如果你要在這份資料上建東西,第一件要確認的不是那個數字,而是你的資料來源能不能表達「未觀察」,以及那個慣例在你的時間窗內有沒有保持不變。

這一切都不是餐廳獨有的。一個公布出來的計數記錄的是某個機構寫下了什麼,而我看的四個衛生局中,有一個在沒有說明的情況下改變了它的書寫方式。

如果你知道 2019 年 Marin County 究竟改了什麼,我很想聽,我會更新這篇。這裡的每個數字都能用這份程式碼對著即時 API 重新產生,歡迎檢驗我的工作。