{"id":"l-pda-4-4","courseId":"python-data-analysis","lang":"en","kind":"lesson","summary":"Turn timestamped orders into monthly, quarterly and weekly series with resample, compare periods with pct_change, smooth noise with rolling, and extract calendar parts with the dt accessor.","html":"<p>The monthly figures you have used since Lesson 1.3 came from somewhere: a few thousand timestamped orders, bucketed by calendar month. Time is the axis of almost every business question (“Is revenue growing?”, “Is this November normal?”, “When do people shop?”), and pandas has tools built for it. Before using them, you need one row per order with its revenue and timestamp.</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\">order_revenue </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>\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\"> orders.merge(order_revenue, </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 style=\"color:#1F2328;--shiki-dark:#E6EDF3\">orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders[orders[</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\">]</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders[[</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\">\"order_date\"</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\">]].head(</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=\"0\" hidden></div></div>\n<p>Every run block below starts with these lines, shortened to keep them readable. In your own notebook you would run them once.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">resample: buckets of time</h2>\n<p><code dir=\"ltr\">resample</code> groups rows into regular time periods. It needs the dates in the index:</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\">orders </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\">orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders[orders[</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\">]</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">by_time </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders.set_index(</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\">monthly </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> by_time.resample(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"ME\"</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\">quarterly </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> by_time.resample(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"QE\"</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:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">len</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(monthly), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"months\"</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\">(monthly.tail(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).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\">(quarterly.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=\"1\" hidden></div></div>\n<p>The string is a frequency alias: <code dir=\"ltr\">&quot;D&quot;</code> for days, <code dir=\"ltr\">&quot;W&quot;</code> for weeks ending Sunday, <code dir=\"ltr\">&quot;ME&quot;</code> for month ends, <code dir=\"ltr\">&quot;QE&quot;</code> for quarter ends, <code dir=\"ltr\">&quot;YE&quot;</code> for year ends. pandas 2.2 renamed <code dir=\"ltr\">&quot;M&quot;</code>, <code dir=\"ltr\">&quot;Q&quot;</code> and <code dir=\"ltr\">&quot;Y&quot;</code> to the <code dir=\"ltr\">E</code> versions to make clear that the labels are period <strong>ends</strong>, and the old names now raise a deprecation warning. Periods are a different object and keep the short names: <code dir=\"ltr\">to_period(&quot;M&quot;)</code> and <code dir=\"ltr\">to_period(&quot;Q&quot;)</code> are correct, because a period stands for the whole month or quarter rather than its last day. The quarterly series is the one the head of growth is staring at: 80,523 dollars in Q3 2025, then 146,825 in Q4.</p>\n<h3 id=\"sec-2\" tabindex=\"-1\">resample versus to_period</h3>\n<p>You can also group by a period: <code dir=\"ltr\">orders.groupby(orders[&quot;order_date&quot;].dt.to_period(&quot;M&quot;))[&quot;revenue&quot;].sum()</code>. The result looks the same here, with labels like <code dir=\"ltr\">2025-11</code> instead of <code dir=\"ltr\">2025-11-30</code>. The difference shows when a period has no data: <code dir=\"ltr\">resample</code> includes it with a sum of 0, while grouping skips it entirely. For charts and growth rates you want every period present, so <code dir=\"ltr\">resample</code> is the safer default.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 180\" role=\"img\" aria-labelledby=\"l-pda-4-4-t1\"><defs><marker id=\"arrow-l-pda-4-4-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-4-4-t1\">A timeline from October to December with order dots scattered along it. Vertical lines split it into three monthly buckets. Each bucket is labelled with its last day: 2025-10-31, 2025-11-30 and 2025-12-31, with the summed revenue under each label.</title>\n  <path class=\"d-line\" d=\"M30 70 L670 70\"/>\n  <path class=\"d-line\" d=\"M30 45 L30 95\"/>\n  <path class=\"d-line\" d=\"M243 45 L243 95\"/>\n  <path class=\"d-line\" d=\"M457 45 L457 95\"/>\n  <path class=\"d-line\" d=\"M670 45 L670 95\"/>\n  <circle class=\"d-dot\" cx=\"60\" cy=\"70\" r=\"5\"/><circle class=\"d-dot\" cx=\"110\" cy=\"70\" r=\"5\"/><circle class=\"d-dot\" cx=\"190\" cy=\"70\" r=\"5\"/>\n  <circle class=\"d-dot\" cx=\"260\" cy=\"70\" r=\"5\"/><circle class=\"d-dot\" cx=\"290\" cy=\"70\" r=\"5\"/><circle class=\"d-dot\" cx=\"320\" cy=\"70\" r=\"5\"/><circle class=\"d-dot\" cx=\"350\" cy=\"70\" r=\"5\"/><circle class=\"d-dot\" cx=\"380\" cy=\"70\" r=\"5\"/><circle class=\"d-dot\" cx=\"400\" cy=\"70\" r=\"5\"/><circle class=\"d-dot\" cx=\"420\" cy=\"70\" r=\"5\"/><circle class=\"d-dot\" cx=\"440\" cy=\"70\" r=\"5\"/>\n  <circle class=\"d-dot\" cx=\"490\" cy=\"70\" r=\"5\"/><circle class=\"d-dot\" cx=\"540\" cy=\"70\" r=\"5\"/><circle class=\"d-dot\" cx=\"590\" cy=\"70\" r=\"5\"/><circle class=\"d-dot\" cx=\"630\" cy=\"70\" r=\"5\"/>\n  <text class=\"d-label-muted\" x=\"136\" y=\"35\" text-anchor=\"middle\">October</text>\n  <text class=\"d-label-muted\" x=\"350\" y=\"35\" text-anchor=\"middle\">November</text>\n  <text class=\"d-label-muted\" x=\"563\" y=\"35\" text-anchor=\"middle\">December</text>\n  <text class=\"d-code\" x=\"136\" y=\"125\" text-anchor=\"middle\">2025-10-31</text>\n  <text class=\"d-code\" x=\"350\" y=\"125\" text-anchor=\"middle\">2025-11-30</text>\n  <text class=\"d-code\" x=\"563\" y=\"125\" text-anchor=\"middle\">2025-12-31</text>\n  <text class=\"d-label\" x=\"136\" y=\"152\" text-anchor=\"middle\">30,339</text>\n  <text class=\"d-label-strong\" x=\"350\" y=\"152\" text-anchor=\"middle\">75,334</text>\n  <text class=\"d-label\" x=\"563\" y=\"152\" text-anchor=\"middle\">41,153</text>\n</svg><figcaption>resample puts each timestamp into a fixed bucket and labels the bucket by its end</figcaption></figure>\n<h2 id=\"sec-3\" tabindex=\"-1\">Comparing periods</h2>\n<p>Raw series answer “how much”; changes answer “how fast”:</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\">orders </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\">orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders[orders[</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\">]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">by_time </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders.set_index(</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">quarterly </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> by_time.resample(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"QE\"</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:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(pd.DataFrame({</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: quarterly.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:#0A3069;--shiki-dark:#A5D6FF\">    \"vs_prev_quarter\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: quarterly.pct_change().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:#0A3069;--shiki-dark:#A5D6FF\">    \"vs_year_ago\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: quarterly.pct_change(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}))</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">monthly </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> by_time.resample(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"ME\"</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:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(monthly.rolling(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).mean().tail(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).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><code dir=\"ltr\">pct_change()</code> divides each value by the previous one and subtracts 1. Q4 2025 is up 82% on Q3, which is the jump everyone noticed. But <code dir=\"ltr\">pct_change(4)</code> compares each quarter with the same quarter a year earlier, and that column tells a calmer story: Q4 2025 is up 180% on Q4 2024, while Q1, Q2 and Q3 grew by 200%, 145% and 165%. Q4 grew about as fast as the rest of the year. A large part of the “jump” is a seasonal pattern that was there in 2024 too: Q4 2024 was 73% above Q3 2024.</p>\n<p><code dir=\"ltr\">rolling(3).mean()</code> averages each month with the two before it, which smooths out single spikes such as November. Use it to see a trend; use the raw series to see events.</p>\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>Comparing a partial period with full ones</span></p>\n<p>Weekly bins end on Sunday, so <code dir=\"ltr\">resample(&quot;W&quot;)</code> labels the last bin 2026-01-04 even though the data ends on 30 December 2025, and that “week” holds two days of orders. Partial first and last periods always look like a collapse or a slump. Check <code dir=\"ltr\">series.index.min()</code> and <code dir=\"ltr\">max()</code> against the data’s real date range, and drop or flag partial periods before computing growth.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">The dt accessor: calendar parts</h2>\n<p>For patterns within a period, such as days of the week or hours of the day, extract the part with <code dir=\"ltr\">.dt</code> and group by it:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"3\"><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=\"3\" 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\">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\">orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders[orders[</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\">]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">d </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders[</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:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders.groupby(d.dt.day_name()).size().sort_values())</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders.groupby(d.dt.hour).size().head(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4</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\">(d.dt.to_period(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Q\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).value_counts().sort_index().tail(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span></code></pre><div class=\"run-output\" data-run-output=\"3\" hidden></div></div>\n<p>Orders are spread fairly evenly across the week, from 241 on Mondays to 298 on Tuesdays and Saturdays, so there is no strong weekday effect to explain. Other useful parts: <code dir=\"ltr\">dt.year</code>, <code dir=\"ltr\">dt.quarter</code>, <code dir=\"ltr\">dt.month</code>, <code dir=\"ltr\">dt.isocalendar().week</code>, <code dir=\"ltr\">dt.date</code> and <code dir=\"ltr\">dt.normalize()</code> (midnight of the same day, which you used in Lesson 3.4).</p>\n<p>Daily resampling is where assumptions get tested. <code dir=\"ltr\">by_time.resample(&quot;D&quot;).size()</code> gives one count per calendar day, 729 of them from the first order to the last, and two facts fall out at once: 119 days had no orders at all (in a series built with <code dir=\"ltr\">groupby</code>, those days would simply be missing), and the busiest single day was 10 November 2025 with 16 orders, almost three weeks before Black Friday (28 November). The other top days, 6, 8 and 26 November, are spread across the month too. Whatever drove November, it was not one big promotional day; keep that in mind for the case study.</p>\n<p>In the exercise you build the monthly series and the year-over-year view yourself. Next you rank and bin customers, turning a continuous measure like total spend into segments.</p>\n","toc":[{"id":"sec-1","text":"resample: buckets of time","level":2},{"id":"sec-2","text":"resample versus to_period","level":3},{"id":"sec-3","text":"Comparing periods","level":2},{"id":"sec-4","text":"The dt accessor: calendar parts","level":2}],"takeaways":["<code dir=\"ltr\">df.set_index(&quot;order_date&quot;).resample(&quot;ME&quot;)[&quot;revenue&quot;].sum()</code> buckets rows into calendar months; <code dir=\"ltr\">&quot;QE&quot;</code>, <code dir=\"ltr\">&quot;W&quot;</code> and <code dir=\"ltr\">&quot;D&quot;</code> give quarters, weeks and days.","In pandas 2.2 and later, <code dir=\"ltr\">resample</code> writes month and quarter ends as <code dir=\"ltr\">&quot;ME&quot;</code> and <code dir=\"ltr\">&quot;QE&quot;</code>; the old <code dir=\"ltr\">&quot;M&quot;</code> and <code dir=\"ltr\">&quot;Q&quot;</code> aliases are deprecated there, while periods such as <code dir=\"ltr\">to_period(&quot;M&quot;)</code> keep the short names.","resample includes empty periods (a month with no orders shows 0), while grouping by <code dir=\"ltr\">dt.to_period(&quot;M&quot;)</code> only lists periods that have data.","<code dir=\"ltr\">pct_change()</code> compares each period with the previous one, and <code dir=\"ltr\">pct_change(4)</code> on quarters compares with the same quarter a year earlier.","Resample labels mark the end of each bin, so a weekly series can show a 2026 label for orders placed in December 2025; check the first and last periods for partial data."],"further":[{"title":"Time series / date functionality - resampling","url":"https://pandas.pydata.org/docs/user_guide/timeseries.html#resampling"},{"title":"Offset aliases","url":"https://pandas.pydata.org/docs/user_guide/timeseries.html#offset-aliases"},{"title":"pandas.Series.pct_change","url":"https://pandas.pydata.org/docs/reference/api/pandas.Series.pct_change.html"}],"quiz":[{"q":"<p>Quarterly revenue is in <code dir=\"ltr\">q</code>, oldest first. Which expression gives each quarter’s growth versus the same quarter one year earlier?</p>","options":[{"text":"<code dir=\"ltr\">q.pct_change()</code>","why":"That compares each quarter with the one right before it, so Q4 is compared with Q3, which mixes growth with seasonality."},{"text":"<code dir=\"ltr\">q.pct_change(4)</code>","why":"Correct. With quarterly data, four periods back is the same quarter last year, which removes the seasonal pattern from the comparison."},{"text":"<code dir=\"ltr\">q.diff(4)</code>","why":"<code dir=\"ltr\">diff</code> gives the change in dollars, not a percentage."},{"text":"<code dir=\"ltr\">q.rolling(4).mean()</code>","why":"This smooths the series over a year; it does not compare periods."}],"answer":1},{"q":"<p>A weekly order count made with <code dir=\"ltr\">resample(&quot;W&quot;)</code> ends with a week labelled 2026-01-04 that has only 11 orders. What is going on?</p>","options":[{"text":"The data contains orders from 2026 that should be removed.","why":"The data ends on 30 December 2025. The label is the week’s end date, not the order dates."},{"text":"<code dir=\"ltr\">resample</code> invented a week that does not exist.","why":"The week of 29 December to 4 January exists; the data just stops partway through it."},{"text":"The last week is labelled by its Sunday, 4 January, and only contains two days of data, so it looks artificially low.","why":"Correct. Weekly bins end on Sunday by default. Drop or mark partial periods before comparing them with full ones."}],"answer":2},{"q":"<p>Which code gives the number of orders per weekday name?</p>","options":[{"text":"<code dir=\"ltr\">orders.groupby(orders[&quot;order_date&quot;].dt.day_name()).size()</code>","why":"Correct. The <code dir=\"ltr\">dt</code> accessor extracts the weekday name per row, and grouping by that Series counts orders per day name."},{"text":"<code dir=\"ltr\">orders.resample(&quot;D&quot;).size()</code>","why":"This counts orders per calendar day (729 rows), not per weekday, and it needs a datetime index."},{"text":"<code dir=\"ltr\">orders[&quot;order_date&quot;].day_name().value_counts()</code>","why":"Datetime methods on a column live behind <code dir=\"ltr\">.dt</code>; without it, pandas raises <code dir=\"ltr\">AttributeError</code>."}],"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\"])\norder_revenue = items.groupby(\"order_id\", as_index=False)[\"revenue\"].sum()\norders = orders.merge(order_revenue, on=\"order_id\", how=\"left\", validate=\"one_to_one\")\norders = orders[orders[\"status\"] != \"cancelled\"]\n\n# 1. Monthly revenue (Series, one value per calendar month, month-end labels)\nmonthly = None\n\n# 2. Monthly number of orders\nmonthly_orders = None\n\n# 3. Quarterly revenue, and its growth versus the same quarter a year earlier\nquarterly = None\nyoy = None\n\n# 4. Weekday name with the highest total revenue, e.g. \"Monday\"\nbest_weekday = 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\"])\norder_revenue = items.groupby(\"order_id\", as_index=False)[\"revenue\"].sum()\norders = orders.merge(order_revenue, on=\"order_id\", how=\"left\", validate=\"one_to_one\")\norders = orders[orders[\"status\"] != \"cancelled\"]\n\nby_time = orders.set_index(\"order_date\")\nmonthly = by_time.resample(\"ME\")[\"revenue\"].sum()\nmonthly_orders = by_time.resample(\"ME\")[\"order_id\"].count()\n\nquarterly = by_time.resample(\"QE\")[\"revenue\"].sum()\nyoy = quarterly.pct_change(4)\n\nbest_weekday = orders.groupby(orders[\"order_date\"].dt.day_name())[\"revenue\"].sum().idxmax()\nprint(yoy.round(3))\n","checks":[{"id":"monthly","test":"isinstance(monthly, pd.Series) and len(monthly) == 24 and abs(float(monthly.loc['2025-11'].iloc[0]) - 75333.9) < 1"},{"id":"monthly-orders","test":"isinstance(monthly_orders, pd.Series) and len(monthly_orders) == 24 and int(monthly_orders.loc['2025-11'].iloc[0]) == 325"},{"id":"quarterly","test":"isinstance(quarterly, pd.Series) and len(quarterly) == 8 and abs(float(quarterly.iloc[-1]) - 146825.44) < 1"},{"id":"yoy","test":"isinstance(yoy, pd.Series) and round(float(yoy.iloc[-1]), 3) == 1.795 and yoy.iloc[:4].isna().all()"},{"id":"weekday","test":"best_weekday == 'Saturday'"}],"title":"Monthly, quarterly, year over year","prompt":"<p><code dir=\"ltr\">orders</code> has one row per non-cancelled order, with <code dir=\"ltr\">order_date</code> and <code dir=\"ltr\">revenue</code>. Build <code dir=\"ltr\">monthly</code> (revenue per calendar month, month-end labels) and <code dir=\"ltr\">monthly_orders</code> (number of orders per month) with <code dir=\"ltr\">resample</code>. Build <code dir=\"ltr\">quarterly</code> (revenue per quarter) and <code dir=\"ltr\">yoy</code>, its growth versus the same quarter a year earlier as a fraction (1.0 means +100%). Finally set <code dir=\"ltr\">best_weekday</code> to the name of the weekday with the highest total revenue.</p>\n","hints":["Put the dates in the index first: <code dir=\"ltr\">by_time = orders.set_index(&quot;order_date&quot;)</code>, then <code dir=\"ltr\">by_time.resample(&quot;ME&quot;)[&quot;revenue&quot;].sum()</code>.","Count orders with <code dir=\"ltr\">[&quot;order_id&quot;].count()</code> (or <code dir=\"ltr\">.size()</code>) on the same resampler. Quarters use <code dir=\"ltr\">&quot;QE&quot;</code>.","<code dir=\"ltr\">quarterly.pct_change(4)</code> compares with four quarters earlier. For the weekday: <code dir=\"ltr\">orders.groupby(orders[&quot;order_date&quot;].dt.day_name())[&quot;revenue&quot;].sum().idxmax()</code>."],"explanation":"<p><code dir=\"ltr\">resample</code> needs a datetime index, which <code dir=\"ltr\">set_index(&quot;order_date&quot;)</code> provides; <code dir=\"ltr\">&quot;ME&quot;</code> and <code dir=\"ltr\">&quot;QE&quot;</code> label each bucket by its last day. November 2025 had 325 orders and 75,334 dollars of revenue. <code dir=\"ltr\">pct_change(4)</code> on a quarterly series is the year-over-year view: the first four values are <code dir=\"ltr\">NaN</code> because 2023 is not in the data, and Q4 2025 grew 179.5% on Q4 2024, in line with the 145-200% growth of the other 2025 quarters. Saturday brings in the most revenue, though the weekday differences are small compared with the seasonal ones.</p>\n","checkLabels":{"monthly":"<code dir=\"ltr\">monthly</code> has 24 months, with November 2025 at about 75,334","monthly-orders":"<code dir=\"ltr\">monthly_orders</code> shows 325 orders in November 2025","quarterly":"<code dir=\"ltr\">quarterly</code> has 8 quarters, ending at about 146,825","yoy":"<code dir=\"ltr\">yoy</code> is 1.795 for Q4 2025 and empty for 2024","weekday":"<code dir=\"ltr\">best_weekday</code> is the top weekday by revenue"}},"words":724,"readMinutes":3}