{"id":"l-pda-5-3","courseId":"python-data-analysis","lang":"en","kind":"lesson","summary":"Answer a real business question end to end - define the metric, check the data, decompose revenue into orders, customers and order value, test each explanation, and write a conclusion with its caveats.","html":"<p>The head of growth’s message has been waiting since Lesson 1.1: “Q4 2025 revenue was far higher than any quarter before. Was it Black Friday, more customers, bigger baskets, or a data mistake? I need an answer I can repeat to the board.” This lesson works through the answer the way you would at work: define, check, decompose, test explanations, conclude. Every step uses a tool you already know.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Step 1: define the metric and check the data</h2>\n<p>The metric is the course’s revenue definition from Lesson 4.2: line revenue after discounts, excluding shipping and cancelled orders. Build one row per order, and assert the facts the analysis relies on before trusting a single number:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Python</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"python\" data-index=\"0\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pandas </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">items </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_items.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">parse_dates</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"quantity\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"unit_price\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> -</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"discount\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">per_order </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items.groupby(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">as_index</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">False</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].sum()</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">df </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders.merge(per_order, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">on</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">how</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"left\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">validate</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"one_to_one\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">assert</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> df[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].is_unique</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">assert</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> df[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].notna().all(), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"an order has no items\"</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">df </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> df[df[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"status\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"cancelled\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].copy()</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">df[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"quarter\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> df[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].dt.to_period(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Q\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(df.groupby(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"quarter\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].sum().round(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(df.loc[df[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"quarter\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">==</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"2025Q4\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"status\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].value_counts())</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>Revenue went from 80,523 dollars in Q3 2025 to 146,825 in Q4, up 82%. No duplicates, no orders without items. One caveat surfaces immediately: 63 Q4 orders worth 11,321 dollars, about 8% of the quarter, are still <code dir=\"ltr\">processing</code> or <code dir=\"ltr\">shipped</code>. They count as revenue under the definition, but some could still be cancelled. Note it now; it goes in the final answer.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Step 2: decompose the change</h2>\n<p>Revenue is orders times average order value, and orders are active customers times orders per customer. Computing each factor shows which one moved:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Python</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"python\" data-index=\"1\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pandas </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">items </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_items.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">parse_dates</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"quantity\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"unit_price\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> -</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"discount\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">df </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders.merge(items.groupby(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">as_index</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">False</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].sum(), </span><span style=\"color:#953800;--shiki-dark:#FFA657\">on</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">validate</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"one_to_one\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">df </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> df[df[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"status\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"cancelled\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].copy()</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">df[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"quarter\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> df[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].dt.to_period(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Q\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">q </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> df.groupby(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"quarter\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).agg(</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">    revenue</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"sum\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">    orders</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"count\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">    customers</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"customer_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"nunique\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">q[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"aov\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> q[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> q[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">q[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders_per_customer\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> q[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> q[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"customers\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(q.round(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).tail(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">5</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((q.loc[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"2025Q4\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> q.loc[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"2025Q3\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).round(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>The second print is the decomposition in one line. Orders rose 98% while average order value <strong>fell</strong> 8%: Q4 was about volume, not bigger baskets, which matches the medians you saw in Lesson 5.1. The extra orders came from both directions: 54% more active customers, and each ordered more often (2.28 orders per customer against 1.77).</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">Step 3: put it next to last year</h2>\n<p>A jump is only surprising compared with what normally happens. Lesson 4.4 already showed the comparison that matters:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"Scrollable table\" tabindex=\"0\"><table><thead>\n<tr>\n<th></th>\n<th>Q3 → Q4 growth</th>\n<th>Year-over-year growth in Q4</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>2024</td>\n<td>+73%</td>\n<td>(no 2023 data)</td>\n</tr>\n<tr>\n<td>2025</td>\n<td>+82%</td>\n<td>+180%</td>\n</tr>\n</tbody>\n</table></div><p>Q4 was 73% above Q3 in 2024 too, and November was the biggest month in both years. 2025’s Q3-to-Q4 rise is only somewhat stronger than the usual seasonal lift, and Q4’s 180% growth on the year before sits inside the range of the other 2025 quarters (145% to 200%). The board-level message starts to form: the business roughly tripled year over year (2025 revenue was 2.7 times 2024’s), and Q4 is that bigger business hitting its normal holiday peak.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">Step 4: test the popular explanations</h2>\n<p>Each explanation gets one specific number:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">Python</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"python\" data-index=\"2\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pandas </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">items </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_items.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">parse_dates</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"quantity\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"unit_price\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> -</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"discount\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">df </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders.merge(items.groupby(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">as_index</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">False</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].sum(), </span><span style=\"color:#953800;--shiki-dark:#FFA657\">on</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">validate</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"one_to_one\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">df </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> df[df[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"status\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"cancelled\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].copy()</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">df[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"quarter\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> df[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].dt.to_period(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Q\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">first_q </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> df.groupby(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"customer_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].min().dt.to_period(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Q\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).rename(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"first_quarter\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">df </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> df.merge(first_q, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">on</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"customer_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">validate</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"many_to_one\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">df[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"customer_type\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (df[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"quarter\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">==</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> df[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"first_quarter\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]).map({</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">True</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"new\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">False</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"returning\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">})</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">q4 </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> df[df[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"quarter\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">==</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"2025Q4\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">total </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> q4[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].sum()</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(q4.groupby(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"customer_type\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].sum().div(total).round(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">bf </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> q4[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"coupon_code\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">==</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"BLACKFRIDAY25\"</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Black Friday coupon orders:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, bf.sum(), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"| share of Q4 revenue:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">round</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(q4.loc[bf, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].sum() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> total, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(q4.groupby(q4[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].dt.month)[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].sum().round(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span></code></pre><div class=\"run-output\" data-run-output=\"2\" hidden></div></div>\n<p><strong>Black Friday?</strong> The <code dir=\"ltr\">BLACKFRIDAY25</code> coupon was used on 69 Q4 orders, bringing 8.4% of the quarter’s revenue. November was huge, 75,334 dollars, but most of it came from orders without that coupon. Black Friday helped; it does not explain the jump. <strong>New customers?</strong> Customers placing their first order brought 36% of Q4 revenue, while 64% came from returning customers, who also account for two thirds of the 66,302-dollar rise over Q3. The jump rests mostly on an existing customer base that kept growing and kept coming back. <strong>Bigger baskets?</strong> Ruled out in Step 2. <strong>Price rise?</strong> The 5% increase took effect in March 2025, so it lifts Q3 and Q4 equally and explains none of the change between them. <strong>Data mistake?</strong> The checks in Step 1 found no duplicates or orphaned orders; the only open risk is the 8% not yet delivered.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 200\" role=\"img\" aria-labelledby=\"l-pda-5-3-t1\"><defs><marker id=\"arrow-l-pda-5-3-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-pda-5-3-t1\">Five steps in a row: define and check, decompose, compare with last year, test explanations, conclude. Under each step, its finding: data clean with 8 percent undelivered; orders up 98 percent and order value down 8 percent; 2024 had a 73 percent Q4 lift too; Black Friday coupons 8 percent and returning customers 64 percent; seasonal peak on a business that tripled.</title>\n  <rect class=\"d-box\" x=\"10\" y=\"20\" width=\"124\" height=\"50\" rx=\"10\"/><text class=\"d-label-strong\" x=\"72\" y=\"50\" text-anchor=\"middle\">Check</text>\n  <rect class=\"d-box\" x=\"148\" y=\"20\" width=\"124\" height=\"50\" rx=\"10\"/><text class=\"d-label-strong\" x=\"210\" y=\"50\" text-anchor=\"middle\">Decompose</text>\n  <rect class=\"d-box\" x=\"286\" y=\"20\" width=\"124\" height=\"50\" rx=\"10\"/><text class=\"d-label-strong\" x=\"348\" y=\"50\" text-anchor=\"middle\">Last year</text>\n  <rect class=\"d-box\" x=\"424\" y=\"20\" width=\"124\" height=\"50\" rx=\"10\"/><text class=\"d-label-strong\" x=\"486\" y=\"50\" text-anchor=\"middle\">Test ideas</text>\n  <rect class=\"d-box-success\" x=\"562\" y=\"20\" width=\"128\" height=\"50\" rx=\"10\"/><text class=\"d-label-strong\" x=\"626\" y=\"50\" text-anchor=\"middle\">Conclude</text>\n  <path class=\"d-arrow\" d=\"M134 45 L146 45\" marker-end=\"url(#arrow-l-pda-5-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M272 45 L284 45\" marker-end=\"url(#arrow-l-pda-5-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M410 45 L422 45\" marker-end=\"url(#arrow-l-pda-5-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M548 45 L560 45\" marker-end=\"url(#arrow-l-pda-5-3-1)\"/>\n  <text class=\"d-label-muted\" x=\"72\" y=\"100\" text-anchor=\"middle\">clean;</text><text class=\"d-label-muted\" x=\"72\" y=\"120\" text-anchor=\"middle\">8% undelivered</text>\n  <text class=\"d-label-muted\" x=\"210\" y=\"100\" text-anchor=\"middle\">orders +98%</text><text class=\"d-label-muted\" x=\"210\" y=\"120\" text-anchor=\"middle\">AOV -8%</text>\n  <text class=\"d-label-muted\" x=\"348\" y=\"100\" text-anchor=\"middle\">2024 Q4 lift</text><text class=\"d-label-muted\" x=\"348\" y=\"120\" text-anchor=\"middle\">was +73%</text>\n  <text class=\"d-label-muted\" x=\"486\" y=\"100\" text-anchor=\"middle\">coupon 8%;</text><text class=\"d-label-muted\" x=\"486\" y=\"120\" text-anchor=\"middle\">returning 64%</text>\n  <text class=\"d-label-muted\" x=\"626\" y=\"100\" text-anchor=\"middle\">seasonal peak</text><text class=\"d-label-muted\" x=\"626\" y=\"120\" text-anchor=\"middle\">on a 2.7x business</text>\n</svg><figcaption>From question to answer: each step narrows the explanation</figcaption></figure>\n<h2 id=\"sec-5\" tabindex=\"-1\">Step 5: write the answer</h2>\n<p>The analysis is only finished when someone else can repeat it in a meeting. Lead with the conclusion, give the evidence in numbers, end with the caveats:</p>\n<blockquote>\n<p><strong>Q4 2025 revenue (146,825 dollars, +82% on Q3) was mainly Cartwheel’s normal holiday peak on a business that grew 2.7x year over year.</strong> The rise came from order volume (+98%), not order size (average order value fell 8%), with 54% more active customers ordering more often. Q4 was 73% above Q3 in 2024 as well. Returning customers produced 64% of Q4 revenue; Black Friday coupon orders produced 8.4%. <strong>Caveats:</strong> 8% of Q4 revenue is from orders not yet delivered, and the decline in order value is partly the cost of Q4 discounts, which more than tripled on Q3 (5,581 dollars versus 1,615).</p>\n</blockquote>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Stopping at the first plausible story</span></p>\n<p>“It was Black Friday” fits the timing and would have been accepted in most meetings. It explains 8% of the quarter. The habit that protects you is to give every explanation a number and ask what share of the change it accounts for, before you write it in a slide.</p>\n</aside>\n<p>In the exercise you rebuild the decomposition in code. The final lesson turns this analysis into something you can rerun next quarter without starting over.</p>\n","toc":[{"id":"sec-1","text":"Step 1: define the metric and check the data","level":2},{"id":"sec-2","text":"Step 2: decompose the change","level":2},{"id":"sec-3","text":"Step 3: put it next to last year","level":2},{"id":"sec-4","text":"Step 4: test the popular explanations","level":2},{"id":"sec-5","text":"Step 5: write the answer","level":2}],"takeaways":["Start a business question by writing down the metric and its exact definition, then check the data that feeds it before any analysis.","Decompose a total into drivers (revenue = orders × average order value; orders = customers × orders per customer) to see which one moved.","Compare against the same period last year and against the seasonal pattern, so a normal peak is not mistaken for a breakthrough.","Test each popular explanation with a specific number, and say how much of the change it can and cannot explain.","A finished answer is a short conclusion, the evidence behind it, and the caveats that could change it."],"further":[{"title":"Group by: split-apply-combine","url":"https://pandas.pydata.org/docs/user_guide/groupby.html"},{"title":"Time series / date functionality","url":"https://pandas.pydata.org/docs/user_guide/timeseries.html"}],"quiz":[{"q":"<p>Q4 revenue rose 82% on Q3 while orders rose 98% and average order value fell 8%. What drove the increase?</p>","options":[{"text":"Bigger baskets, because revenue grew","why":"Average order value fell, so baskets got slightly smaller. Revenue grew despite that, not because of it."},{"text":"The number of orders, which grew faster than revenue itself","why":"Correct. Revenue = orders × average order value. Nearly doubling orders with a slightly lower order value gives an 82% rise."},{"text":"Price increases","why":"The 5% price rise happened in March 2025 and applies to Q3 and Q4 alike, so it cannot explain growth between them."},{"text":"It is impossible to tell without a chart.","why":"The decomposition answers it numerically; a chart would show the same thing."}],"answer":1},{"q":"<p>Q4 2024 was also 73% above Q3 2024. Why does that matter for the Q4 2025 story?</p>","options":[{"text":"It does not; 2024 is old data.","why":"The previous year is the best baseline for a seasonal business; ignoring it is how normal peaks become false headlines."},{"text":"It proves the 2025 numbers are wrong.","why":"A similar pattern in both years supports the 2025 data rather than casting doubt on it."},{"text":"It shows the 2024 data is duplicated into 2025.","why":"The levels are very different (52,537 versus 146,825); only the seasonal shape repeats."},{"text":"Most of the Q3-to-Q4 jump is a seasonal pattern that happens every year, so 2025’s jump is only slightly larger than normal.","why":"Correct. An 82% rise against a usual rise of about 73% is a modest surprise on top of a predictable peak."}],"answer":3},{"q":"<p>63 Q4 orders worth 11,321 dollars are still <code dir=\"ltr\">processing</code> or <code dir=\"ltr\">shipped</code> at the end of the data. How should the report treat them?</p>","options":[{"text":"Include them, and state as a caveat that about 8% of Q4 revenue is not yet delivered and may shrink if some are cancelled.","why":"Correct. They meet the revenue definition today; naming the risk lets readers judge it."},{"text":"Exclude them silently to be safe.","why":"That changes the revenue definition without telling anyone, and makes Q4 look smaller than every other report."},{"text":"Count them twice, since they will be delivered in January.","why":"An order counts once, in the period it was placed, whatever its delivery date."}],"answer":0}],"exercise":{"kind":"python","files":["order_items.csv","orders.csv"],"starter":"import pandas as pd\n\nitems = pd.read_csv(\"order_items.csv\")\norders = pd.read_csv(\"orders.csv\", parse_dates=[\"order_date\"])\nitems[\"revenue\"] = items[\"quantity\"] * items[\"unit_price\"] * (1 - items[\"discount\"])\ndf = orders.merge(items.groupby(\"order_id\", as_index=False)[\"revenue\"].sum(), on=\"order_id\", validate=\"one_to_one\")\ndf = df[df[\"status\"] != \"cancelled\"].copy()\ndf[\"quarter\"] = df[\"order_date\"].dt.to_period(\"Q\")\n\n# 1. q: one row per quarter with columns revenue, orders, customers (distinct), aov\nq = None\n\n# 2. Growth from 2025Q3 to 2025Q4 as fractions (0.5 = +50%), each rounded to 3 decimals\nrevenue_growth = None\norders_growth = None\naov_growth = None\n\n# 3. Share of 2025Q4 revenue from orders with the BLACKFRIDAY25 coupon, rounded to 3 decimals\nbf_share = None\n\n# 4. Q3-to-Q4 revenue growth in 2024, rounded to 3 decimals (the seasonal baseline)\ngrowth_2024 = None\n","solution":"import pandas as pd\n\nitems = pd.read_csv(\"order_items.csv\")\norders = pd.read_csv(\"orders.csv\", parse_dates=[\"order_date\"])\nitems[\"revenue\"] = items[\"quantity\"] * items[\"unit_price\"] * (1 - items[\"discount\"])\ndf = orders.merge(items.groupby(\"order_id\", as_index=False)[\"revenue\"].sum(), on=\"order_id\", validate=\"one_to_one\")\ndf = df[df[\"status\"] != \"cancelled\"].copy()\ndf[\"quarter\"] = df[\"order_date\"].dt.to_period(\"Q\")\n\nq = df.groupby(\"quarter\").agg(\n    revenue=(\"revenue\", \"sum\"),\n    orders=(\"order_id\", \"count\"),\n    customers=(\"customer_id\", \"nunique\"),\n)\nq[\"aov\"] = q[\"revenue\"] / q[\"orders\"]\n\nchange = q.loc[\"2025Q4\"] / q.loc[\"2025Q3\"] - 1\nrevenue_growth = round(change[\"revenue\"], 3)\norders_growth = round(change[\"orders\"], 3)\naov_growth = round(change[\"aov\"], 3)\n\nq4 = df[df[\"quarter\"] == \"2025Q4\"]\nbf_share = round(q4.loc[q4[\"coupon_code\"] == \"BLACKFRIDAY25\", \"revenue\"].sum() / q4[\"revenue\"].sum(), 3)\n\ngrowth_2024 = round(q.loc[\"2024Q4\", \"revenue\"] / q.loc[\"2024Q3\", \"revenue\"] - 1, 3)\nprint(change.round(3), bf_share, growth_2024)\n","checks":[{"id":"table","test":"isinstance(q, pd.DataFrame) and len(q) == 8 and {'revenue', 'orders', 'customers', 'aov'} <= set(q.columns)"},{"id":"q4-row","test":"int(q.iloc[-1]['orders']) == 642 and int(q.iloc[-1]['customers']) == 282 and abs(float(q.iloc[-1]['aov']) - 228.70) < 0.01"},{"id":"growth","test":"abs(float(revenue_growth) - 0.823) < 0.0011 and abs(float(orders_growth) - 0.981) < 0.0011"},{"id":"aov-growth","test":"abs(float(aov_growth) - (-0.080)) < 0.0011"},{"id":"bf","test":"abs(float(bf_share) - 0.084) < 0.0011"},{"id":"baseline","test":"abs(float(growth_2024) - 0.727) < 0.0011"}],"title":"Decompose the Q4 jump","prompt":"<p><code dir=\"ltr\">df</code> has one row per non-cancelled order with <code dir=\"ltr\">revenue</code>, <code dir=\"ltr\">customer_id</code>, <code dir=\"ltr\">coupon_code</code> and a <code dir=\"ltr\">quarter</code> period. Build <code dir=\"ltr\">q</code>, one row per quarter with <code dir=\"ltr\">revenue</code> (sum), <code dir=\"ltr\">orders</code> (count), <code dir=\"ltr\">customers</code> (distinct customers) and <code dir=\"ltr\">aov</code> (revenue ÷ orders). Then compute the Q3-to-Q4 2025 growth of revenue, orders and AOV as fractions (<code dir=\"ltr\">revenue_growth</code>, <code dir=\"ltr\">orders_growth</code>, <code dir=\"ltr\">aov_growth</code>), the share of Q4 2025 revenue from <code dir=\"ltr\">BLACKFRIDAY25</code> orders (<code dir=\"ltr\">bf_share</code>), and the 2024 Q3-to-Q4 revenue growth (<code dir=\"ltr\">growth_2024</code>). Round each of these five numbers to 3 decimals.</p>\n","hints":["<code dir=\"ltr\">df.groupby(&quot;quarter&quot;).agg(revenue=(&quot;revenue&quot;, &quot;sum&quot;), orders=(&quot;order_id&quot;, &quot;count&quot;), customers=(&quot;customer_id&quot;, &quot;nunique&quot;))</code>, then add <code dir=\"ltr\">aov</code> as a column.","Rows of <code dir=\"ltr\">q</code> are labelled by period; <code dir=\"ltr\">q.loc[&quot;2025Q4&quot;] / q.loc[&quot;2025Q3&quot;] - 1</code> gives every column’s growth at once.","For the coupon share, filter Q4 2025 first, then divide the coupon orders’ revenue by all Q4 revenue."],"explanation":"<p>Dividing one row of <code dir=\"ltr\">q</code> by another computes all the growth rates in one step, because the two rows share the same columns. Revenue +82.3% = orders +98.1% combined with AOV −8.0% (1.981 × 0.920 ≈ 1.823): the decomposition is exact, which is why it is such a powerful first move. Black Friday coupon orders were 8.4% of Q4 revenue, and the 2024 baseline shows Q4 was already about 73% above Q3 the year before. Together these numbers support the conclusion from the lesson: a normal seasonal peak on a much bigger business, driven by order volume.</p>\n","checkLabels":{"table":"<code dir=\"ltr\">q</code> has 8 quarters with revenue, orders, customers and aov","q4-row":"Q4 2025 shows 642 orders, 282 customers and an AOV of 228.70","growth":"Revenue grew 82.3% and orders 98.1%","aov-growth":"AOV fell 8.0%","bf":"Black Friday coupon orders are 8.4% of Q4 revenue","baseline":"2024’s Q3-to-Q4 growth was 72.7%"}},"words":770,"readMinutes":4}