CoolFace
Datasetpublic

CXu0630/mtg-print-distribution

MTG Print Distribution Expected pull rates for every Magic: The Gathering booster product (Play Booster, Collector Booster, Draft Booster, Set Booster, Theme Booster, Jumpstart, ...) across 191 sets — for each product, the expected percentage of a pack made up of every distinguishable card variant (rarity, finish, frame effects, borderless, and promo-tag combination), plus how many distinct cards are eligible for each one. Each product also carries its real current market price.… See the full description on the dataset page: https://huggingface.co/datasets/CXu0630/mtg-print-distribution.

sourceHugging Facemitupdated 6d agoView on Hugging Face
1likes55downloads
Dataset Card

MTG Print Distribution

Expected pull rates for every Magic: The Gathering booster product (Play Booster, Collector Booster, Draft Booster, Set Booster, Theme Booster, Jumpstart, ...) across 191 sets — for each product, the expected percentage of a pack made up of every distinguishable card variant (rarity, finish, frame effects, borderless, and promo-tag combination), plus how many distinct cards are eligible for each one. Each product also carries its real current market price.

Computed from MTGJSON's weighted booster/sheet collation data, with prices from TCGplayer.

Disclaimer

Magic: The Gathering is a trademark of Wizards of the Coast, LLC. This is an unofficial dataset, not produced or endorsed by Wizards of the Coast.

Data sources

  • [MTGJSON](https://mtgjson.com) (MIT License, © 2018–Present Zach Halpern) — the primary source: every set's booster/sheets weighted collation structure, and each card's rarity, frameEffects, borderColor, and promoTypes.
  • [Scryfall](https://scryfall.com) — canonical set identity/metadata only (scryfall_id, code, name, set_type, release_date, card_count, digital, block, parent_set_code, icon_svg_uri).
  • [TCGplayer](https://www.tcgplayer.com) — each product's real market price (booster_products.price_usd), fetched from TCGplayer's public, unauthenticated pricepoints endpoint at export time. This is a live market snapshot, not part of the print-run probability model, and isn't refreshed automatically — see price_fetched_at and "Known limitations".
  • Everything else (every rate, pool size, and the synthetic default config described below) is computed from MTGJSON and Scryfall.

Configs (tables)

This is a normalized, multi-table dataset — four separate configs, linked by id,:

sets (192 rows: 191 real sets + 1 synthetic "default")
  PK: scryfall_id
    │
    ▼
booster_products (386 rows)
  PK: id · FK: set_scryfall_id → sets.scryfall_id
    │
    ▼
booster_rates (8,063 rows)  <- default config
  PK: id · FK: booster_product_id → booster_products.id

data_gaps (22 rows) — standalone; sets/products the pipeline couldn't
  fully resolve

Load a specific table:

python
from datasets import load_dataset

rates = load_dataset("CXu0630/mtg-print-distribution", "booster_rates")["train"]
sets = load_dataset("CXu0630/mtg-print-distribution", "sets")["train"]
products = load_dataset("CXu0630/mtg-print-distribution", "booster_products")["train"]

Or with pandas, joining across tables:

python
import pandas as pd

rates = pd.read_parquet("hf://datasets/CXu0630/mtg-print-distribution/booster_rates.parquet")
products = pd.read_parquet("hf://datasets/CXu0630/mtg-print-distribution/booster_products.parquet")
sets = pd.read_parquet("hf://datasets/CXu0630/mtg-print-distribution/sets.parquet")

full = (
    rates
    .merge(products, left_on="booster_product_id", right_on="id", suffixes=("", "_product"))
    .merge(sets, left_on="set_scryfall_id", right_on="scryfall_id", suffixes=("", "_set"))
)
mkm_play = full[(full["code"] == "mkm") & (full["pack_code"] == "play")]
print(mkm_play[["rarity", "finish", "frame_effects", "borderless", "rate_percent"]]
      .sort_values("rate_percent", ascending=False))

Schema

sets

ColumnTypeSourceDescription
scryfall_idstring (PK)ScryfallScryfall's set UUID
codestringScryfallset code, e.g. mkm
mtgjson_codestringMTGJSONthe set's code in MTGJSON's files
namestringScryfallset name
set_typestringScryfalle.g. expansion, core, masters
release_datestringScryfallISO date
card_countintScryfalltotal distinct cards in the set (average across all real sets, for the synthetic default row)
digitalboolScryfalltrue for Arena-only sets
blockstringScryfallmay be null
parent_set_codestringScryfallmay be null
icon_svg_uristringScryfallmay be null
updated_atstringcomputedbookkeeping timestamp

booster_products

One row per purchasable booster-pack product per set (e.g. a set's Play Booster and Collector Booster are two rows).

ColumnTypeSourceDescription
idint (PK)computed
set_scryfall_idstring (FK → sets.scryfall_id)
pack_codestringMTGJSON, resolvedMTGJSON's booster dict key for this product, e.g. play, collector
product_namestringMTGJSONe.g. "Murders at Karlov Manor Play Booster Pack"
subtypestringMTGJSONMTGJSON's sealedProduct subtype, e.g. play
cards_per_packfloatcomputedexpected cards per pack
sourcestringcomputedmtgjson, manual_override, or computed_average (the synthetic default set)
warningsstringcomputeddata-quality notes, if any
computed_atstringcomputedbookkeeping timestamp
price_usdfloat, nullableTCGplayerreal current market price for this exact product; null if unresolvable (no matching TCGplayer listing, e.g. a long-discontinued product) or for the synthetic default set's two products, which have no real product to price
price_fetched_atstring, nullablecomputedUTC timestamp price_usd was fetched at; null alongside a null price

booster_rates (default config)

One row per distinguishable card-variant bucket for a booster_products row. Rates for one product sum to ~100%.

ColumnTypeSourceDescription
idint (PK)computed
booster_product_idint (FK → booster_products.id)
raritystringMTGJSONcommon/uncommon/rare/mythic/special/...
finishstringMTGJSON, derivednonfoil/foil/etched/surgefoil/galaxyfoil/...
frame_effectsstringMTGJSONraw frameEffects, semicolon-joined, e.g. inverted;showcase; none if empty
borderlessboolMTGJSONfrom borderColor == "borderless"
promo_tagsstringMTGJSONraw promoTypes (minus finish-related tags already in finish), semicolon-joined; none if empty
expected_countfloatcomputedaverage cards of this bucket per pack
rate_percentfloatcomputedexpected_count / cards_per_pack * 100
card_pool_sizefloatcomputeddistinct cards eligible for this bucket (integer for a real set; averaged/fractional for the synthetic default set)
avg_card_rate_percentfloatcomputedrate_percent / card_pool_size — the odds of pulling one particular card from this bucket. This is an average, not necessarily each card's exact rate: if a bucket is fed by more than one sheet (e.g. a dedicated rarity sheet plus a "wildcard" sheet), individual cards can have different combined weights — one card's real rate can be several times another's within the same bucket.

The default set (sets.code == "default") is a synthetic all-time average across every real Play/Collector Booster, deliberately keyed on just (rarity, foil, borderless)frame_effects and promo_tags are always "none" there, since the finer dimensions are mostly set-specific one-offs that don't generalize.

data_gaps

Sets or products the pipeline couldn't fully resolve (no Scryfall match, no MTGJSON booster data, or an ambiguous product match).

Known limitations

  • `avg_card_rate_percent` is an average, not exact per-card odds since the same card can be drawn from different sets.
  • The `finish`/`frame_effects`/`promo_tags` split relies on naming conventions, not a strictly documented MTGJSON guarantee (e.g. etched finishes are detected by sheet name containing "etched"). See the source repository for full reasoning and edge cases.
  • Welcome Boosters aren't modeled — MTGJSON has no collation data for them.
  • `price_usd` is a point-in-time market snapshot (see price_fetched_at), fetched from TCGplayer's public, unauthenticated pricepoints endpoint — not their documented API, so it could change format without notice. Prices fluctuate day to day and are not refreshed automatically on a schedule; ~10% of products have no resolvable price (old/discontinued products TCGplayer or MTGJSON's sealedProduct data don't cover).
  • Snapshot as of the last build — a set released after that won't appear until re-built from a refreshed MTGJSON pull.