◇ Codex edition · sibling of the Claude walkthrough

how-to-EDA-using-codex-python

Same Everrest platform, a different question and a different toolbox. Codex assembles a profiling + integrity + forensic kit, then digs out six data-trust defects the naive pass missed entirely. Every chart below came from a real run - seed 42, learn-python env, 232,155 rows generated and analyzed.

Case: Everrest · B2B2C retail platform seed = 42 pandas · sweetviz · missingno · pandera · scipy 19 visuals 8 findings, $/trust-ranked
01
Step 1

Input - schema + business context

Everrest is a B2B2C retail platform: 400 merchants sell to 20,000 consumers across six Southeast Asian markets. This time the category team is not asking "what's growing" - they are asking "can we trust the platform's data before we build this quarter's dashboards on it?" Eight tables come over from a fresh data pipeline. The discipline is refusing to trust any of them until the keys, types and joins have been proven.

orders 50,000 rows
  • order_id · pk
  • customer_id · fk (some orphaned)
  • merchant_id · fk
  • order_ts · timestamp (UTC)
  • status · delivered/shipped/paid/cancelled
order_items 107,855 rows
  • order_id · fk
  • product_id · fk (some orphaned)
  • qty · int
  • unit_price · numeric
  • discount · 0 to 0.20
payments 46,006 rows
  • payment_id · pk
  • order_id · fk
  • method · card/wallet/bnpl/cod/bank
  • amount · numeric
  • paid_ts · timestamp
merchants 400 rows
  • merchant_id · pk
  • category · 8 categories (dirty labels)
  • tier · standard/premium/enterprise
  • joined_at · date
customers 20,800 rows
  • customer_id · pk
  • signup_date · timestamp
  • channel_id · fk, 6 channels
  • region · SG/MY/ID/TH/VN/PH
products 5,000 rows
  • product_id · pk
  • merchant_id · fk
  • category · enum
  • price · numeric (current catalog)
returns 2,088 rows
  • return_id · pk
  • order_id · fk
  • reason · 5 reasons
  • return_ts · timestamp
channels 6 rows
  • channel_id · pk
  • name · organic, paid search, social, email, referral, affiliate
02
Step 2

Generate sample data - a fresh set of planted quirks

The generator reuses the canon Everrest schema (M0007 the bulk-wholesale outlier and the weekend + November seasonality stay in as real background) but plants a different six defects from the Claude edition - the kind that hide across joins, not inside a single column. Each is documented in the generator docstring, so Step 5 becomes a recall test.

generate_everrest_codex.py ↗ · excerpt (full script on GitHub)
import numpy as np
SEED = 42; rng = np.random.default_rng(SEED)

# Quirk 1: ~2.5% of line items reference a non-existent product_id.
orphan = rng.choice(df.index, size=int(0.025 * len(df)), replace=False)
df.loc[orphan, "product_id"] = [f"P9{i:04d}" for i in range(len(orphan))]

# Quirk 2: stale Sep-Nov snapshot - unit_price frozen at 0.82x catalog.
df.loc[stale, "unit_price"] = np.round(df.loc[stale, "unit_price"] * 0.82, 2)

# Quirk 3: PH payments logged in local time -> paid_ts ~8h BEFORE order_ts.
df.loc[ph, "paid_ts"] = df.loc[ph, "paid_ts"] - pd.Timedelta(hours=8)

# Quirk 5: fabricated merchant reports round-hundred amounts (Benford red flag).
df.loc[fab, "amount"] = rng.choice([100,200,300,400,500,600,700,800,900], fab.sum())
🔗 Orphan foreign keys2.5% of order_items and 1% of orders point to product/customer ids that do not exist
🏷 Stale price snapshotSep-Nov 2025 line items frozen at 0.82x the current catalog price
🕗 Timezone bugEvery PH-region payment logged 8h before its order - impossible negative latency
🔤 Dirty categoricals8 categories arrive as 14 labels: 'electronics', 'Beauty ', 'Grocary', 'apparel'
🔢 Benford / fabricationM0333 'Apex Gadget Bazaar' reports only round-hundred amounts
👥 Duplicate identities800 customers appear twice under different ids, inflating channel acquisition
03
Step 3

Objective - a trust question, not a technique

The Claude edition asked "what should we act on this quarter?" This one asks the question that has to come first: can the data even be trusted yet? Every chart in Step 5 serves one of these five sub-questions; anything that does not change a decision gets cut.

Can Everrest's new data platform be trusted for this quarter's decisions - and which integrity defects would silently corrupt every dashboard built on it?
  • Do the foreign keys actually resolve, or are we joining onto ghosts?
  • Are the numbers real - prices, amounts, timestamps - or artifacts of a broken pipeline?
  • Are the category and customer entities clean, or silently split and duplicated?
  • Once the data is cleaned, where is revenue concentrated and how does demand move?
  • Which single defect, if shipped, would embarrass the team in front of the board?
04
Step 4

Find-skills - Codex assembles its own toolbox

This is the differentiator. Before writing a line of analysis, Codex activates its agent skills to find the right tools, then pulls open-source profiling, validation and forensic libraries. The output is still a plain Python script that runs anywhere - the toolbox is how it got written well, not a runtime dependency.

Agent skills (how Codex found the rest)
OpenAI

Codex CLI

The authoring agent. Reads the schema, runs the pipeline, writes and refactors the analysis script from the terminal.

GitHub skill

superpowers

Skill framework whose find-skills routine surfaces the right specialist for a task instead of improvising from zero.

Agent skill

grill-me

Interrogates the objective before any code - "what would the business pay to know, and what would embarrass it?" - so Step 3 is sharp.

Profiling & validation libraries
OSS tool

sweetviz

One-shot profile report - types, missingness, distributions per column. The 10-minute head start; it flagged the orphan joins and price drift on its own.

OSS tool

ydata-profiling

The equivalent profiling card - drops in with ProfileReport(df) where the environment supports it. Reuse beats rebuilding describe().

OSS tool

missingno

Missingness matrix - makes it visually obvious that order_items is complete on its own columns and only gaps on JOIN.

OSS tool

pandera

Schema + referential-integrity checks as code - turns "product_id must exist in products" into an assertion the pipeline enforces.

OSS tool

Great Expectations

Promotes the defects EDA finds into permanent CI gates - the orphan keys and dirty labels never ship twice.

Method

scipy · Benford's law

First-digit distribution test - the forensic lens that isolates the fabricated round-number merchant from 400 honest ones.

Analysis discipline
Skill

dataviz discipline

Chart form follows the relationship; one consistent anomaly color (amber) means "this is the problem" on every chart.

Skill

schema-to-insights playbook

Decision-first scan: DQ gate before analysis, findings ranked by dollar and trust impact, not by p-value.

05
Step 5

Code + charts - the real run

One sectioned script: the profiling head start, then a data-quality & integrity gate, then the business questions on the cleaned data. 19 visuals, every PNG rendered at 300 DPI from the actual run, every title a finding rather than a technique. Grab the real code below - one click, copy, run.

The 10-minute head start: one profiling call

Sweetviz profiling report of the enriched order_items table: 107,855 rows, 1,096 duplicates, product_id distinct 7,696, price and category 2 percent missing, price_gap median zero with a negative tail

The sweetviz report on the enriched line-item table earns its keep before any custom code: price & category show 2% missing (that is the orphan-product join failing), price_gap has a median of 0 with a negative tail (the stale-price drift), and product_id shows 7,696 distinct values where only 5,000 products exist (the fake orphan ids). Three defects surfaced from a single call - then confirmed one by one below.

eda_codex_v2.py ↗ · the integrity gate (excerpt)
# Gate: referential integrity - do the foreign keys resolve?
prod_ids = set(products.product_id)
oi_orphan = ~order_items.product_id.isin(prod_ids)
orphan_value = order_items.loc[oi_orphan, "net"].sum()

# Normalize dirty category labels BEFORE any rollup.
mcat = merchants.assign(category_clean=lambda d: d.category.replace(CLEAN_CAT).str.strip())

# Timezone check: payment latency must never be negative.
pay["lat_h"] = (pay.paid_ts - pay.order_ts).dt.total_seconds() / 3600
neg = pay.lat_h < 0   # 100% of these are PH -> local-time logging
Heatmap scorecard of duplicate-row and null rates per table, all low
Within-table looks clean. Duplicate-row and null rates are near zero on every table - which is the trap. The real defects live across joins, not inside single columns.
missingno matrix of order_items showing every column fully populated
The gaps hide on JOIN. order_items is 100% complete on its own columns - so a single-table check passes while 2.5% of its product keys are quietly broken.
Bar chart: 2.5 percent of order_items and 1 percent of orders are orphaned foreign keys
Broken keys. 2.5% of line items reference a non-existent product; 1% of orders a non-existent customer. $154k of line value cannot be attributed. Add a pandera gate.
Bar chart: 14 raw category labels collapse to 8 after cleaning
8 categories, 14 labels. 'electronics', 'Beauty ', 'Grocary' split every rollup. $557k of revenue sits under variant labels a naive GROUP BY would miss.
Scatter of recorded unit price versus catalog price, stale points fall below the diagonal in amber
Stale prices. 30% of line items sit below the unit=catalog diagonal - a Sep-Nov snapshot frozen at 0.82x, understating $385k of revenue in that window.
Histogram of payment latency with an amber cluster at minus 8 hours
Paid before ordered. A whole region shows paid_ts ~8h before order_ts - every case is PH, logged in local time. $475k of GMV has untrustworthy timestamps.
Bar chart comparing Everrest payment leading digits to Benford's law, close match
Benford holds - platform-wide. Real payment amounts follow the expected first-digit curve, which is exactly what makes a merchant that breaks it detectable.
Bar chart: merchant M0333 has 100 percent round-hundred amounts, far above all others
The fabricator. M0333 'Apex Gadget Bazaar' reports 100% round-hundred amounts vs near-zero elsewhere - reported payments differ from its line items by $45k. Fraud review.
Bar chart of duplicate customer identities by acquisition channel
Double-counted people. 800 customers exist twice under different ids with identical signup, channel and region - 3.8% of the base. Every CAC number is overstated.
Pareto chart of revenue by merchant rank, rank 1 highlighted in amber
Concentration - after cleaning. Rank #1 is the M0007 bulk outlier, not a hero merchant. Read the platform only once it is excluded.
Histogram of order values on a log scale with a median marker
Skew. Order values span four orders of magnitude - report medians; the mean is dragged by the wholesaler.
Violin plot of order value by merchant tier on a log scale
The tier that isn't. Enterprise's long upper tail is essentially one merchant (M0007), not a tier-wide behaviour.
Correlation heatmap of order value, basket size, hour and day of week
No hidden driver. Order value moves with basket size and nothing else - no time-of-day price effect to chase.
Three-panel seasonal decomposition of daily orders
Rhythm (canon). November runs 2.1x baseline, weekends lift ~40%. Commit Q4 capacity by September.
Heatmap of orders by day of week and hour
Prime time. Weekend evenings are the platform's busiest window - staff and promote into it.
Small multiple monthly revenue lines for eight cleaned categories
Category health - on clean labels. Read only after normalization; Grocery's scale is M0007, not organic demand.
Cohort retention heatmap by signup month
Retention. Month-1 is where customers are lost - the retention play starts in week 2, not month 3.
Bar chart of revenue by region with PH highlighted in amber
Geography. PH revenue is real and sizeable - but until its timestamps are fixed, no PH conversion-time metric can be trusted.
Planted quirkCaught byImpactStatus
Orphan foreign keys (bad ETL join)Profiling + RI check$153,996 unattributable✓ caught
Stale price snapshot (Sep-Nov)Price-drift scatter$385,330 understated✓ caught
Timezone bug (PH paid before order)Payment-latency histogram$474,948 bad timestamps✓ caught
Dirty category labels (8→14)Cardinality + rollup fix$557,134 mis-bucketed✓ caught
Round-number fabrication (M0333)Benford + round-share$45,433 reconciliation gap✓ caught
Duplicate customer identitiesFingerprint dedup800 records (3.8%)✓ caught
Canon re-confirmed: M0007 outlier + seasonalityPareto + decomposition2.1x Nov · 12% of revenue✓ confirmed
06
Step 6

Expert review - a check that changes the code

A panel of five senior reviewer agents - each with 10+ years in data, data insights, and business - tore into the first pass (eda_codex_v1.py, kept in the repo). v1 ran clean and looked plausible, which is what made it dangerous. Every fix below was applied and the pipeline re-ran; the charts above are the post-review v2.

Senior Data Engineer
13 yrs · pipeline & integrity

"v1 joins order_items to products and never checks the keys resolve. 2.5% of line items point at products that do not exist - they just vanish from the join and nobody notices."

Fix applied: explicit referential-integrity gate; orphan keys quantified ($154k) and a pandera-style assertion added before any join.
Analytics Methodology Lead
11 yrs · data quality & rigor

"Your revenue-by-category chart has 'Beauty' twice and both a 'Grocery' and a 'Grocary'. You grouped on the raw label - every rollup is silently split."

Fix applied: category labels normalized (14→8) before any aggregate; $557k of mis-bucketed revenue recovered into the right categories.
Data Insights Lead
12 yrs · decision framing

"v1's latency histogram clips at zero, so the whole PH negative-latency story is hidden. And titles like 'Latency histogram' change no decision."

Fix applied: latency plotted with the negative tail exposed and split by region; every title rewritten as a finding; the fabricated merchant named on the chart.
Commercial / Business Lead
14 yrs · impact & actionability

"Nothing here is quantified and three defects aren't even looked for - a fabricated merchant and 800 duplicate customers would sail straight into the board deck."

Fix applied: Benford + round-number and duplicate-identity checks added; every finding carries a $ or trust figure and findings.md ranks all eight.
Data QA & Reproducibility
10 yrs · trust & recall

"Prove it reproduces and prove it recovers everything you planted - not just the easy orphan keys."

Fix applied: fixed seed verified on a clean re-run; all six planted quirks recovered plus the two canon facts re-confirmed - the scoreboard above is the receipt.
before → after · the rollup that lied
# v1 (before): group on the RAW label - dirty variants split the rollup
rev = orders.groupby("category").order_value.sum()
#   Grocery   $1,590,140      <-- these are the
#   Grocary   $   56,115      <-- SAME category, split in two
#   Beauty    $  613,796  |  Beauty  $198,134   <-- and again

# v2 (after): normalize first - 14 labels collapse to 8, buckets are whole
mcat["category_clean"] = mcat.category.replace(CLEAN_CAT).str.strip()
rev = orders.groupby("category_clean").order_value.sum()   # +$557k re-bucketed

Run this skill on your own schema

Install once, then hand your tables and business context over - the same 6 steps run on your data (step 2 is skipped when real data already exists).

terminal
/plugin marketplace add phoebefu6/phoebe-data-skills
/plugin install how-to-eda-codex@phoebe-data-skills