DEV Community

Jukka Blomberg
Jukka Blomberg

Posted on

A butter croissant scores 39. A plain bagel scores 1. Here is the arithmetic.

Two things from the same bakery counter, run through the same formula in the FatTonic Food Index:

  • Croissant (1 medium, 60 g): 39 / 100, band Poor.
  • Bagel (1 plain, 100 g): 1 / 100, band Avoid. The same score as a can of cola.

The croissant has six times the fat. So why does it win?

The formula

The score is one line, published with the data:

GL    = GI × (carbs − fiber) ÷ 100
Score = 100 − 2.2·GL − 0.8·sugar − 8·processing + 1.6·fiber + 0.7·protein
Enter fullscreen mode Exit fullscreen mode

Amounts are per serving, in grams; processing is 0–3; the result is kept within 1–100. Fat is not in it. The score asks one question: how hard does a serving's carbohydrate land?

The two rows

Croissant Bagel
Serving 60 g 100 g
Carbs / fiber (g) 27 / 1 52 / 2
Glycemic index 67 72
Glycemic load 17 36
Sugar (g) 6 8
Protein (g) 5 10
Processing 3 3
Score 39 1

The bagel loses on one line. A glycemic load of 36 costs it 79.2 points before anything else is counted. Its extra protein (+7.0) can't pay that back. The croissant's load of 17 costs 37.4.

Two caveats:

  1. Serving size does a lot of the work. A bagel serving here is 100 g; a croissant is 60 g. Score both per 100 g and the croissant drops to about 13; the gap narrows from 38 points to about 12. We score the serving people actually eat.
  2. The glycemic index values are third-party. For 163 of the 200 foods the per-food GI source is not yet recorded, and the data page says so. The nutrients are not guesses: 194 of 200 scored foods are checked against a named USDA FoodData Central record (the bagel is record 174899, the croissant 174987).

And one thing the score is not: a verdict on whether a food is good for you. A croissant is still butter and white flour. It just carries half the glycemic load of a bagel, per serving.

Check it yourself

import csv, io, urllib.request

url = "https://fattonic.com/data/export/foods.csv"
rows = csv.DictReader(io.TextIOWrapper(urllib.request.urlopen(url), "utf-8"))

def score(r):
    gl = round(int(r["gi"]) * (int(r["carbs_g"]) - int(r["fiber_g"])) / 100)
    s = (100 - 2.2 * gl - 0.8 * int(r["sugar_g"]) - 8 * int(r["processed"])
         + 1.6 * int(r["fiber_g"]) + 0.7 * int(r["protein_g"]))
    return max(1, min(100, round(s)))

for r in rows:
    if r["name"] in ("Croissant", "Bagel", "Cola / Soda", "Sourdough Bread"):
        print(f'{r["name"]:<16} GL {r["gl"]:>3}  published {r["score"]:>3}  recomputed {score(r):>3}')
Enter fullscreen mode Exit fullscreen mode

It recomputes the published score from the published inputs. Sourdough, if you were wondering, scores 61 a slice.

Information, not medical advice.

Top comments (0)