{"id":"l-pda-4-1","courseId":"python-data-analysis","lang":"en","kind":"lesson","summary":"Summarise data by group with groupby, compute several named statistics at once with agg, and choose correctly between size and count, and between an indexed and a flat result.","html":"<p>“What’s our average order value?” sounds like one number, but <code dir=\"ltr\">order_items</code> has no order value column: an order with four products has four rows. You first need one row per order, with the order’s revenue summed from its lines. “Revenue per product”, “orders per channel” and “sales per country” are the same shape of question. In a spreadsheet you would build a pivot table; in pandas you use <code dir=\"ltr\">groupby</code>, the method you will call more than any other.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Split, apply, combine</h2>\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\">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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">by_product </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\">\"product_id\"</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\">(by_product.sort_values(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">ascending</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\">).head(</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\">2</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\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">round</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(by_product.sum(), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</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\">(items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].sum(), </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=\"0\" hidden></div></div>\n<p>Read the middle line as three steps. <strong>Split</strong> the rows into groups, one per <code dir=\"ltr\">product_id</code>. <strong>Apply</strong> a calculation, <code dir=\"ltr\">sum</code>, to the <code dir=\"ltr\">revenue</code> column of each group. <strong>Combine</strong> the results into a Series indexed by the group key. Product 21, the Standing Desk, earned 60,017.83 dollars, almost twice the next product.</p>\n<p>The last line is a habit worth copying: the group totals add up to the overall total. If they do not, rows went missing (for example, rows whose key is <code dir=\"ltr\">NaN</code>, which <code dir=\"ltr\">groupby</code> drops by default) or were duplicated before you grouped.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 240\" role=\"img\" aria-labelledby=\"l-pda-4-1-t1\"><defs><marker id=\"arrow-l-pda-4-1-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-1-t1\">Six order lines with order keys A, A, B, A, C, C. Split groups them into three boxes by key. Apply sums revenue in each box. Combine produces a three-row result: A, B and C with their totals.</title>\n  <text class=\"d-label-strong\" x=\"80\" y=\"24\" text-anchor=\"middle\">rows</text>\n  <rect class=\"d-box\" x=\"20\" y=\"36\" width=\"120\" height=\"26\" rx=\"5\"/><text class=\"d-code\" x=\"80\" y=\"54\" text-anchor=\"middle\">A  88.20</text>\n  <rect class=\"d-box\" x=\"20\" y=\"66\" width=\"120\" height=\"26\" rx=\"5\"/><text class=\"d-code\" x=\"80\" y=\"84\" text-anchor=\"middle\">A  33.60</text>\n  <rect class=\"d-box\" x=\"20\" y=\"96\" width=\"120\" height=\"26\" rx=\"5\"/><text class=\"d-code\" x=\"80\" y=\"114\" text-anchor=\"middle\">B  19.95</text>\n  <rect class=\"d-box\" x=\"20\" y=\"126\" width=\"120\" height=\"26\" rx=\"5\"/><text class=\"d-code\" x=\"80\" y=\"144\" text-anchor=\"middle\">A 270.90</text>\n  <rect class=\"d-box\" x=\"20\" y=\"156\" width=\"120\" height=\"26\" rx=\"5\"/><text class=\"d-code\" x=\"80\" y=\"174\" text-anchor=\"middle\">C  96.60</text>\n  <rect class=\"d-box\" x=\"20\" y=\"186\" width=\"120\" height=\"26\" rx=\"5\"/><text class=\"d-code\" x=\"80\" y=\"204\" text-anchor=\"middle\">C  96.60</text>\n  <path class=\"d-arrow\" d=\"M150 120 L190 120\" marker-end=\"url(#arrow-l-pda-4-1-1)\"/>\n  <text class=\"d-label-strong\" x=\"290\" y=\"24\" text-anchor=\"middle\">split</text>\n  <rect class=\"d-box-primary\" x=\"200\" y=\"36\" width=\"180\" height=\"58\" rx=\"8\"/><text class=\"d-label\" x=\"290\" y=\"70\" text-anchor=\"middle\">A: 3 rows</text>\n  <rect class=\"d-box-accent\" x=\"200\" y=\"102\" width=\"180\" height=\"40\" rx=\"8\"/><text class=\"d-label\" x=\"290\" y=\"127\" text-anchor=\"middle\">B: 1 row</text>\n  <rect class=\"d-box-success\" x=\"200\" y=\"150\" width=\"180\" height=\"58\" rx=\"8\"/><text class=\"d-label\" x=\"290\" y=\"184\" text-anchor=\"middle\">C: 2 rows</text>\n  <path class=\"d-arrow\" d=\"M390 120 L430 120\" marker-end=\"url(#arrow-l-pda-4-1-1)\"/>\n  <text class=\"d-label-strong\" x=\"490\" y=\"24\" text-anchor=\"middle\">apply sum</text>\n  <text class=\"d-code\" x=\"490\" y=\"70\" text-anchor=\"middle\">392.70</text>\n  <text class=\"d-code\" x=\"490\" y=\"127\" text-anchor=\"middle\">19.95</text>\n  <text class=\"d-code\" x=\"490\" y=\"184\" text-anchor=\"middle\">193.20</text>\n  <path class=\"d-arrow\" d=\"M545 120 L575 120\" marker-end=\"url(#arrow-l-pda-4-1-1)\"/>\n  <text class=\"d-label-strong\" x=\"635\" y=\"24\" text-anchor=\"middle\">combine</text>\n  <rect class=\"d-box\" x=\"585\" y=\"60\" width=\"100\" height=\"120\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"635\" y=\"92\" text-anchor=\"middle\">A 392.70</text>\n  <text class=\"d-code\" x=\"635\" y=\"124\" text-anchor=\"middle\">B  19.95</text>\n  <text class=\"d-code\" x=\"635\" y=\"156\" text-anchor=\"middle\">C 193.20</text>\n</svg><figcaption>groupby splits rows by key, applies a function to each group, and combines the results</figcaption></figure>\n<h2 id=\"sec-2\" tabindex=\"-1\">Several statistics at once: named aggregation</h2>\n<p>One number per group is rarely enough. <code dir=\"ltr\">agg</code> with <strong>named aggregation</strong> computes several, and names each output column:</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\">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>\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\">).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\">    lines</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_item_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\">    units</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"quantity\"</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\">    max_discount</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"discount\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"max\"</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:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(per_order.head().round(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</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\">(per_order.shape, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"| average order value:\"</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\">(per_order[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].mean(), </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=\"1\" hidden></div></div>\n<p>Each keyword is an output column, and each value is a pair: (input column, function). Functions are given by name as strings: <code dir=\"ltr\">&quot;sum&quot;</code>, <code dir=\"ltr\">&quot;mean&quot;</code>, <code dir=\"ltr\">&quot;median&quot;</code>, <code dir=\"ltr\">&quot;min&quot;</code>, <code dir=\"ltr\">&quot;max&quot;</code>, <code dir=\"ltr\">&quot;count&quot;</code>, <code dir=\"ltr\">&quot;size&quot;</code>, <code dir=\"ltr\">&quot;nunique&quot;</code>, <code dir=\"ltr\">&quot;first&quot;</code>, <code dir=\"ltr\">&quot;last&quot;</code>, <code dir=\"ltr\">&quot;std&quot;</code>. The result has one row per order, 2,066 of them, and the average order value is 228.43 dollars, across all statuses, cancellations included, for now.</p>\n<p>You will also see the older style <code dir=\"ltr\">agg({&quot;revenue&quot;: &quot;sum&quot;, &quot;discount&quot;: &quot;max&quot;})</code>, or <code dir=\"ltr\">agg([&quot;sum&quot;, &quot;mean&quot;])</code> on one column. They work, but they reuse the input names, so a table with two statistics of the same column ends up with confusing headers. Named aggregation says exactly what each column is.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">size versus count</h2>\n<p>Two ways to count, and they answer different questions:</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\">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>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders.groupby(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"channel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).size())                   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\"># rows per group</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders.groupby(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"channel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"coupon_code\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].count())   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\"># non-missing coupons per group</span></span></code></pre><div class=\"run-output\" data-run-output=\"2\" hidden></div></div>\n<p><code dir=\"ltr\">size()</code> counts rows: 1,146 web orders. <code dir=\"ltr\">count()</code> counts the non-missing values of a column: 208 of those web orders have a coupon code. Use <code dir=\"ltr\">size</code> (or <code dir=\"ltr\">count</code> on a column that is never missing, such as an id) for “how many”, and <code dir=\"ltr\">count</code> for “how many have a value”.</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>Counting the wrong thing</span></p>\n<p>“Orders per customer” computed on <code dir=\"ltr\">order_items</code> with <code dir=\"ltr\">size()</code> counts <strong>lines</strong>, not orders: a four-product order counts four times. Either group a table that has one row per order, or count distinct ids with <code dir=\"ltr\">(&quot;order_id&quot;, &quot;nunique&quot;)</code>. Before you aggregate, say what one row represents.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Several keys and flat results</h2>\n<p>Group by a list of columns to cross two dimensions. The result has a two-level index (a <strong>MultiIndex</strong>), and <code dir=\"ltr\">unstack()</code> spreads the inner level into columns, which reads like a pivot table:</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">by_channel_status </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders.groupby([</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"channel\"</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\">]).size()</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(by_channel_status.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\">(by_channel_status.unstack())</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">flat </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders.groupby(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"channel\"</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\">).agg(</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\">    avg_fee</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"shipping_fee\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"mean\"</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:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(flat.round(</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><code dir=\"ltr\">as_index=False</code> keeps the group keys as ordinary columns instead of moving them into the index, which is handy when the result feeds a merge, a chart or a CSV export. <code dir=\"ltr\">reset_index()</code> on an indexed result does the same afterwards.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">Group results on every row: transform</h2>\n<p>Sometimes you want the group’s number next to each row rather than one row per group. “What share of its order does each line represent?” needs every line to know its order’s total. <code dir=\"ltr\">transform</code> runs the same group calculation but returns a result aligned to the original rows:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"4\"><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=\"4\" 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\">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\">items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_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.groupby(</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\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].transform(</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:#1F2328;--shiki-dark:#E6EDF3\">items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"share_of_order\"</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\">\"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\">\"order_revenue\"</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\">(items[[</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\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"share_of_order\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]].head(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">5</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=\"4\" hidden></div></div>\n<p>Order 1001 has four lines, and the Burr Coffee Grinder line carries 62% of its value. The rule of thumb: <code dir=\"ltr\">agg</code> when you want a summary table, <code dir=\"ltr\">transform</code> when you want a new column on the detailed table.</p>\n<aside class=\"callout callout-tip\" 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=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Grouping a categorical column</span></p>\n<p>If you converted a column to <code dir=\"ltr\">category</code> (Lesson 3.2), pass <code dir=\"ltr\">observed=True</code> to <code dir=\"ltr\">groupby</code>. It keeps only the categories that actually appear, and it silences the warning pandas 2 prints about the default changing.</p>\n</aside>\n<p>The exercise builds the per-order table you will use from here on. Next you combine it with <code dir=\"ltr\">orders</code> and <code dir=\"ltr\">customers</code>, because the questions that matter, such as “revenue by country, excluding cancellations”, need columns from more than one table.</p>\n","toc":[{"id":"sec-1","text":"Split, apply, combine","level":2},{"id":"sec-2","text":"Several statistics at once: named aggregation","level":2},{"id":"sec-3","text":"size versus count","level":2},{"id":"sec-4","text":"Several keys and flat results","level":2},{"id":"sec-5","text":"Group results on every row: transform","level":2}],"takeaways":["<code dir=\"ltr\">groupby</code> splits rows into groups by key, applies a calculation to each group, and combines the results into one table.","Named aggregation, <code dir=\"ltr\">agg(revenue=(&quot;revenue&quot;, &quot;sum&quot;), lines=(&quot;order_item_id&quot;, &quot;count&quot;))</code>, gives each output column a clear name.","<code dir=\"ltr\">size()</code> counts rows per group; <code dir=\"ltr\">count()</code> counts non-missing values in a column, so the two differ when the column has gaps.","Grouping by several keys gives a MultiIndex; <code dir=\"ltr\">as_index=False</code> or <code dir=\"ltr\">reset_index()</code> turns the keys back into ordinary columns.","Check that group results add back up to the overall total, so you know no rows were lost or double-counted."],"further":[{"title":"Group by: split-apply-combine","url":"https://pandas.pydata.org/docs/user_guide/groupby.html"},{"title":"pandas.core.groupby.DataFrameGroupBy.agg","url":"https://pandas.pydata.org/docs/reference/api/pandas.core.groupby.DataFrameGroupBy.agg.html"}],"quiz":[{"q":"<p><code dir=\"ltr\">orders.groupby(&quot;channel&quot;)[&quot;coupon_code&quot;].count()</code> gives 208 for web, but <code dir=\"ltr\">orders.groupby(&quot;channel&quot;).size()</code> gives 1,146. Why?</p>","options":[{"text":"One of them is filtering out cancelled orders.","why":"Neither call filters by status; both see every row."},{"text":"<code dir=\"ltr\">count()</code> counts only non-missing coupon codes, while <code dir=\"ltr\">size()</code> counts every row in the group.","why":"Correct. 208 web orders used a coupon; 1,146 web orders exist. Use <code dir=\"ltr\">size()</code> to count rows and <code dir=\"ltr\">count()</code> to count filled values."},{"text":"<code dir=\"ltr\">count()</code> counts distinct coupon codes.","why":"Distinct values are <code dir=\"ltr\">nunique()</code>. <code dir=\"ltr\">count()</code> counts every non-missing value, repeats included."},{"text":"<code dir=\"ltr\">size()</code> double-counts orders with several items.","why":"<code dir=\"ltr\">orders</code> has one row per order, so there is nothing to double-count."}],"answer":1},{"q":"<p>Which call returns one row per order with columns named <code dir=\"ltr\">revenue</code> and <code dir=\"ltr\">lines</code>?</p>","options":[{"text":"<code dir=\"ltr\">items.groupby(&quot;order_id&quot;).agg(revenue=(&quot;revenue&quot;, &quot;sum&quot;), lines=(&quot;order_item_id&quot;, &quot;count&quot;))</code>","why":"Correct. Each keyword becomes an output column, defined by a (column, function) pair."},{"text":"<code dir=\"ltr\">items.groupby(&quot;order_id&quot;)[&quot;revenue&quot;, &quot;order_item_id&quot;].sum()</code>","why":"Selecting several columns needs a list (double brackets), and even then you get two sums with the original names, not a count."},{"text":"<code dir=\"ltr\">items.agg(revenue=(&quot;revenue&quot;, &quot;sum&quot;), lines=(&quot;order_item_id&quot;, &quot;count&quot;))</code>","why":"Without <code dir=\"ltr\">groupby</code> this aggregates the whole table into single totals, not one row per order."}],"answer":0},{"q":"<p>You group <code dir=\"ltr\">items</code> by order and sum revenue, then sum that result. The grand total is 3% higher than <code dir=\"ltr\">items[&quot;revenue&quot;].sum()</code>. What is the most likely explanation?</p>","options":[{"text":"groupby always rounds values up.","why":"groupby does not round anything; sums of groups equal the total sum exactly, up to float noise."},{"text":"The groupby dropped the orders with missing keys.","why":"Dropping rows with missing keys would make the grouped total lower, not higher."},{"text":"Floating-point error.","why":"Float error is around the 12th decimal place, never 3%."},{"text":"The table you grouped is not the table you think; for example, it was joined to another table that duplicated rows.","why":"Correct. Grouping cannot invent money, so a higher total means the input had extra rows. Lesson 4.2 shows how joins cause exactly this."}],"answer":3}],"exercise":{"kind":"python","files":["order_items.csv"],"starter":"import pandas as pd\n\nitems = pd.read_csv(\"order_items.csv\")\nitems[\"revenue\"] = items[\"quantity\"] * items[\"unit_price\"] * (1 - items[\"discount\"])\n\n# 1. One row per order_id with columns: revenue (sum), lines (number of rows), units (sum of quantity)\nper_order = None\n\n# 2. Average order value (mean revenue per order), rounded to 2 decimals\navg_order_value = None\n\n# 3. Share of orders with 2 or more lines, rounded to 3 decimals\nmulti_line_share = None\n\n# 4. product_id with the highest total revenue\ntop_product = None\n","solution":"import pandas as pd\n\nitems = pd.read_csv(\"order_items.csv\")\nitems[\"revenue\"] = items[\"quantity\"] * items[\"unit_price\"] * (1 - items[\"discount\"])\n\nper_order = items.groupby(\"order_id\").agg(\n    revenue=(\"revenue\", \"sum\"),\n    lines=(\"order_item_id\", \"count\"),\n    units=(\"quantity\", \"sum\"),\n)\n\navg_order_value = round(per_order[\"revenue\"].mean(), 2)\nmulti_line_share = round((per_order[\"lines\"] >= 2).mean(), 3)\ntop_product = items.groupby(\"product_id\")[\"revenue\"].sum().idxmax()\nprint(per_order.head())\n","checks":[{"id":"shape","test":"isinstance(per_order, pd.DataFrame) and len(per_order) == 2066 and {'revenue', 'lines', 'units'} <= set(per_order.columns)"},{"id":"order-1001","test":"(per_order.loc[1001, 'revenue'] if 1001 in per_order.index else per_order.set_index('order_id').loc[1001, 'revenue']).round(2) == 438.9"},{"id":"lines-units","test":"int(per_order['lines'].sum()) == 3991 and int(per_order['units'].sum()) == int(items['quantity'].sum())"},{"id":"aov","test":"round(float(avg_order_value), 2) == 228.43"},{"id":"multi","test":"round(float(multi_line_share), 3) == 0.551"},{"id":"top","test":"int(top_product) == 21"}],"title":"Build the per-order table","prompt":"<p>Using <code dir=\"ltr\">items</code> (revenue column already added), build <code dir=\"ltr\">per_order</code>: one row per <code dir=\"ltr\">order_id</code> with the columns <code dir=\"ltr\">revenue</code> (sum), <code dir=\"ltr\">lines</code> (number of order lines) and <code dir=\"ltr\">units</code> (sum of <code dir=\"ltr\">quantity</code>). Then set <code dir=\"ltr\">avg_order_value</code> to the mean order revenue rounded to 2 decimals, <code dir=\"ltr\">multi_line_share</code> to the share of orders with 2 or more lines rounded to 3 decimals, and <code dir=\"ltr\">top_product</code> to the <code dir=\"ltr\">product_id</code> with the highest total revenue.</p>\n","hints":["<code dir=\"ltr\">items.groupby(&quot;order_id&quot;).agg(revenue=(&quot;revenue&quot;, &quot;sum&quot;), ...)</code> with one keyword per output column.","Count lines with <code dir=\"ltr\">(&quot;order_item_id&quot;, &quot;count&quot;)</code> or <code dir=\"ltr\">(&quot;order_id&quot;, &quot;size&quot;)</code>. A share is the mean of a boolean: <code dir=\"ltr\">(per_order[&quot;lines&quot;] &gt;= 2).mean()</code>.","<code dir=\"ltr\">items.groupby(&quot;product_id&quot;)[&quot;revenue&quot;].sum().idxmax()</code> returns the label of the largest group."],"explanation":"<p>Named aggregation turns 3,991 lines into 2,066 orders in one call, with readable column names. The checks confirm the parts add up: the <code dir=\"ltr\">lines</code> column sums back to 3,991 and units to the total quantity, so no line was lost or counted twice. Average order value is 228.43 dollars, and 55.1% of orders contain more than one product, a useful fact when someone proposes a promotion on single items. Product 21, the Standing Desk 140cm, is the top earner. <code dir=\"ltr\">as_index=False</code> would also be accepted: then <code dir=\"ltr\">order_id</code> is a column instead of the index.</p>\n","checkLabels":{"shape":"<code dir=\"ltr\">per_order</code> has 2,066 rows and the columns revenue, lines, units","order-1001":"Order 1001 has revenue 438.90","lines-units":"Lines and units add back up to the item table’s totals","aov":"<code dir=\"ltr\">avg_order_value</code> is 228.43","multi":"<code dir=\"ltr\">multi_line_share</code> is 0.551","top":"<code dir=\"ltr\">top_product</code> is 21"}},"words":690,"readMinutes":3}