We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.

This lesson's interactive features are locked, please to keep using them

Grain Validation

The grain of a table is what one row represents. An orders table might have one row per order, or one row per line item. Getting this wrong leads to double-counting and incorrect numbers in every report that uses the table.

Why Grain Matters

If your orders table has a grain of "one row per order" but accidentally contains duplicate order IDs, every aggregation will be inflated:

# This looks fine...
orders = pd.DataFrame(
    {
        "order_id": ["ORD-7K2M", "ORD-9P4X", "ORD-9P4X", "ORD-3D8V"],
        "revenue": [50, 75, 75, 100],
    }
)

# But total revenue is wrong
print(orders["revenue"].sum())  # 300, but it should be 225

Order ORD-9P4X appears twice, and revenue is overcounted by $75. This is a grain violation.

Validating

Use .duplicated() to check whether your grain is valid:

# Check for duplicate order IDs
duplicates = orders[orders.duplicated(subset=["order_id"], keep=False)]
print(f"Found {len(duplicates)} rows with duplicate order_ids")

If you get zero duplicates, your grain is valid.

Multi-Column Grain

Sometimes multiple columns define the grain. An order line item table might have a grain of "one row per order + product combination":

line_items = pd.DataFrame(
    {
        "order_id": ["ORD-7K2M", "ORD-7K2M", "ORD-9P4X", "ORD-9P4X"],
        "product_id": ["CHL-482", "TST-731", "CHL-482", "OVN-205"],
        "quantity": [2, 1, 3, 1],
    }
)

# Validate: no duplicate order_id + product_id combinations
dupes = line_items.duplicated(subset=["order_id", "product_id"])
print(f"Grain violations: {dupes.sum()}")  # 0, so the grain is valid