857,655개의 Open Food Facts 라벨을 팬더와 함께 감사: 얼마나 많은 영양 사실이 모순됩니까?

작성자

카테고리:

← 피드로
DEV Community · Franck B. · 2026-09-05 개발(SW)

Open Food Facts is the largest open food database in the world. Scan a barcode in almost any calorie app and the numbers you get come from there. At Lean, where those labels feed a calorie tracker, we asked a simple question: how many of them are internally consistent?

This post is the technical walkthrough. The full results, the category breakdown and the PDF are on Zenodo (DOI 10.5281/zenodo.22284416) and summarized on lean-app.com.

The coherence test

Every label carries two descriptions of the same energy: a declared value in kcal per 100 g, and a macronutrient split. The Atwater factors, the ones written into EU regulation 1169/2011, give the second description a physical meaning:

E_macros = 4 * protein + 4 * carbohydrates + 9 * fat
         + 7 * alcohol + 2 * fiber + 2.4 * polyols   (erythritol: 0)

Enter fullscreen mode Exit fullscreen mode

If a label declares 100 kcal but its own macros produce 250, one of the two lines is wrong, and the label alone cannot tell you which. A tracker displays one of them without blinking.

Scope and pipeline

Data: the official Open Food Facts CSV dump of 23 August 2026 (ODbL), restricted to products sold in France. The full pipeline is a few hundred lines of pandas; the core is below.

import pandas as pd

cols = ["code", "product_name", "categories_tags", "countries_tags",
        "energy-kcal_100g", "proteins_100g", "carbohydrates_100g",
        "fat_100g", "fiber_100g", "alcohol_100g", "polyols_100g"]
df = pd.read_csv("en.openfoodfacts.org.products.csv",
                 sep="\t", usecols=cols, low_memory=False)
df = df[df["countries_tags"].str.contains("en:france", na=False)]

macros = ["proteins_100g", "carbohydrates_100g", "fat_100g"]
complete = df.dropna(subset=["energy-kcal_100g"] + macros)

# aberrant entries: energy outside 1-950 kcal/100 g, impossible macro sums
ok = complete["energy-kcal_100g"].between(1, 950) & (complete[macros].sum(axis=1) <= 100)
complete, aberrant = complete[ok], complete[~ok]

opt = lambda c: complete[c].fillna(0)
complete["e_macros"] = (4 * complete["proteins_100g"] + 4 * complete["carbohydrates_100g"]
                        + 9 * complete["fat_100g"] + 7 * opt("alcohol_100g")
                        + 2 * opt("fiber_100g") + 2.4 * opt("polyols_100g"))
gap = (complete["energy-kcal_100g"] - complete["e_macros"]).abs()
rel = gap / complete["e_macros"].clip(lower=1)
complete["incoherent"] = (rel > 0.10) & (gap > 30)   # both conditions

Enter fullscreen mode Exit fullscreen mode

The double threshold matters. Counting a label as incoherent only when the gap exceeds both 10 percent and 30 kcal/100 g removes the noise of waters and teas declared at 2 kcal, where a 1 kcal difference is a 50 percent error.

Results

Bucket Products Complete and coherent 807,650 Incomplete entry (a macro or the energy missing) 391,707 Complete but incoherent (gap > 10 % and > 30 kcal) 46,757 Aberrant, discarded before the test 16,364

857,655 products had a complete, plausible label and went through the test. 46,757 of them, 5.5 percent, fail it. Add the incomplete entries and the aberrant ones, and about one product in three cannot be trusted as is by a tracker.

The failures are not random. Ranked by category (minimum 1,000 products), protein bars come first: 20.8 percent of them carry an incoherent label. The category everyone tracking a cut scans the most is the least reliable one in the whole database. Staple foods sit at the bottom of the ranking.

The gap is also heavy-tailed: 31,867 labels are off by more than 20 percent and 18,215 by more than 50 percent. Those are not rounding errors, they look like unit mix-ups (kJ typed as kcal, per-serving typed as per-100 g) at data entry.

Why a calorie tracker cares

A tracker that trusts every scan inherits these errors silently. Lean flags incoherent labels, because 4/4/9 is physics and the declared number is a transcription. But the label side is only half of the balance. The other half, how many calories you burn, is where most apps are far more wrong: a formula from 1919 or 1990 for the basal rate, multiplied by a self-declared activity level. Lean measures each component instead: basal rate from body fat, non-exercise activity from steps, workouts from tracked sessions, thermic effect from the macros eaten. If you want to try the method without the app, the TDEE calculator is free and runs in the browser.

Reproduce it

Data: the Open Food Facts CSV export (ODbL), France scope. Method and aggregates: the Zenodo record above, mirrored on OSF. If you rerun it on a newer dump and get different counts, we would like to hear about it: the database changes every day, and so does the error rate.

Franck B., founder of Lean

원문에서 계속 ↗