[{"data":1,"prerenderedAt":2288},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fnumber-rows-within-each-group-in-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fnumber-rows-within-each-group-in-pandas":2256,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fnumber-rows-within-each-group-in-pandas":2264,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fnumber-rows-within-each-group-in-pandas":2269,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fnumber-rows-within-each-group-in-pandas":2270},{"id":4,"title":5,"body":6,"dateModified":2221,"datePublished":2221,"description":2222,"extension":2223,"faq":2224,"keyword":2233,"kind":2234,"meta":2235,"navigation":323,"path":2245,"seo":2246,"slug":2249,"stem":2250,"topics":2251,"type":2254,"__hash__":2255},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fnumber-rows-within-each-group-in-pandas\u002Findex.md","Number Rows Within Each Group in pandas",{"type":7,"value":8,"toc":2207},"minimark",[9,37,42,70,76,281,285,295,443,450,453,566,570,579,736,761,886,890,893,1029,1039,1043,1054,1218,1237,1375,1379,1388,1447,1451,1461,1662,1716,1762,1768,1772,1777,2032,2038,2042,2094,2098,2135,2139,2145,2151,2157,2163,2167,2203],[10,11,12,13,17,18,22,23,26,27,30,31,36],"p",{},"\"Which order is this for the customer — their first, second, fifth?\" \"Number the lines of each invoice.\" \"Keep only the latest reading per sensor.\" All of these need a number that counts rows ",[14,15,16],"em",{},"within"," a group and restarts at 1 for the next group. In Excel the usual trick is ",[19,20,21],"code",{},"=COUNTIF($A$2:A2, A2)"," filled down a sorted sheet; in SQL it is ",[19,24,25],{},"ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)",". In pandas it is one method, ",[19,28,29],{},"cumcount",", plus a few decisions about order and ties that this page makes explicit. It is part of ",[32,33,35],"a",{"href":34},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002F","Creating Pivot Tables from Excel Data",".",[38,39,41],"h2",{"id":40},"prerequisites","Prerequisites",[43,44,49],"pre",{"className":45,"code":46,"language":47,"meta":48,"style":48},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[19,50,51],{"__ignoreMap":48},[52,53,56,60,64,67],"span",{"class":54,"line":55},"line",1,[52,57,59],{"class":58},"sMTad","pip",[52,61,63],{"class":62},"srMev"," install",[52,65,66],{"class":62}," pandas",[52,68,69],{"class":62}," openpyxl\n",[10,71,72,75],{},[19,73,74],{},"orders.xlsx"," has eleven orders from three customers, in the order they were exported, which is not date order. Birch placed two orders on the same day, and several orders share amounts, which matters for ranking.",[77,78,86,87,86,91,86,95,86,103,86,112,86,119,86,123,86,127,86,132,86,137,86,139,86,142,86,145,86,148,86,150,86,153,86,156,86,159,86,161,86,164,86,167,86,171,86,173,86,176,86,179,86,182,86,184,86,187,86,190,86,193,86,197,86,201,86,203,86,205,86,209,86,213,86,215,86,217,86,219,86,222,86,224,86,226,86,228,86,231,86,233,86,235,86,237,86,239,86,241,86,243,86,245,86,247,86,253,86,257,86,262,86,267,86,275],"svg",{"viewBox":79,"role":80,"ariaLabelledBy":81,"xmlns":84,"style":85},"105 0 550 224","img",[82,83],"ng-idea-t","ng-idea-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:550px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[88,89,90],"title",{"id":82},"A number that restarts for every customer",[92,93,94],"desc",{"id":83},"On the left orders sorted by customer and date. On the right the same rows with an order number per customer that runs 1, 2, 3, 4 for Alder and restarts at 1 for Birch.",[96,97],"rect",{"x":98,"y":99,"width":100,"height":101,"fill":102},"105","0","550","224","#ffffff",[96,104],{"x":105,"y":106,"width":107,"height":108,"rx":99,"fill":109,"stroke":110,"style":111},"121.2","40.0","79.6","28.0","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.5px",[113,114,118],"text",{"x":115,"y":116,"style":117},"161.0","59.0","font-size:11px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Customer",[96,120],{"x":121,"y":106,"width":122,"height":108,"rx":99,"fill":109,"stroke":110,"style":111},"200.9","93.7",[113,124,126],{"x":125,"y":116,"style":117},"247.7","OrderDate",[96,128],{"x":105,"y":129,"width":107,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},"68.0","var(--line,#cdd5e6)","stroke-width:1px",[113,133,136],{"x":115,"y":134,"style":135},"87.0","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Alder",[96,138],{"x":121,"y":129,"width":122,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,140,141],{"x":125,"y":134,"style":135},"2026-01-15",[96,143],{"x":105,"y":144,"width":107,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},"96.0",[113,146,136],{"x":115,"y":147,"style":135},"115.0",[96,149],{"x":121,"y":144,"width":122,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,151,152],{"x":125,"y":147,"style":135},"2026-01-28",[96,154],{"x":105,"y":155,"width":107,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},"124.0",[113,157,136],{"x":115,"y":158,"style":135},"143.0",[96,160],{"x":121,"y":155,"width":122,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,162,163],{"x":125,"y":158,"style":135},"2026-02-27",[96,165],{"x":105,"y":166,"width":107,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},"152.0",[113,168,170],{"x":115,"y":169,"style":135},"171.0","Birch",[96,172],{"x":121,"y":166,"width":122,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,174,175],{"x":125,"y":169,"style":135},"2026-01-30",[96,177],{"x":105,"y":178,"width":107,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},"180.0",[113,180,170],{"x":115,"y":181,"style":135},"199.0",[96,183],{"x":121,"y":178,"width":122,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,185,186],{"x":125,"y":181,"style":135},"2026-02-10",[96,188],{"x":189,"y":106,"width":107,"height":108,"rx":99,"fill":109,"stroke":110,"style":111},"486.8",[113,191,118],{"x":192,"y":116,"style":117},"526.6",[96,194],{"x":195,"y":106,"width":196,"height":108,"rx":99,"fill":109,"stroke":110,"style":111},"566.4","72.3",[113,198,200],{"x":199,"y":116,"style":117},"602.6","OrderNo",[96,202],{"x":189,"y":129,"width":107,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,204,136],{"x":192,"y":134,"style":135},[96,206],{"x":195,"y":129,"width":196,"height":108,"rx":99,"fill":207,"stroke":208,"style":111},"#d9f4f1","var(--teal,#0f9488)",[113,210,212],{"x":199,"y":134,"style":211},"font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","1",[96,214],{"x":189,"y":144,"width":107,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,216,136],{"x":192,"y":147,"style":135},[96,218],{"x":195,"y":144,"width":196,"height":108,"rx":99,"fill":207,"stroke":208,"style":111},[113,220,221],{"x":199,"y":147,"style":211},"2",[96,223],{"x":189,"y":155,"width":107,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,225,136],{"x":192,"y":158,"style":135},[96,227],{"x":195,"y":155,"width":196,"height":108,"rx":99,"fill":207,"stroke":208,"style":111},[113,229,230],{"x":199,"y":158,"style":211},"3",[96,232],{"x":189,"y":166,"width":107,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,234,170],{"x":192,"y":169,"style":135},[96,236],{"x":195,"y":166,"width":196,"height":108,"rx":99,"fill":207,"stroke":208,"style":111},[113,238,212],{"x":199,"y":169,"style":211},[96,240],{"x":189,"y":178,"width":107,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,242,170],{"x":192,"y":181,"style":135},[96,244],{"x":195,"y":178,"width":196,"height":108,"rx":99,"fill":207,"stroke":208,"style":111},[113,246,221],{"x":199,"y":181,"style":211},[113,248,252],{"x":249,"y":250,"style":251},"207.9","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","before",[113,254,256],{"x":255,"y":250,"style":251},"562.8","after",[54,258],{"x1":259,"y1":155,"x2":260,"y2":155,"stroke":110,"style":261},"304.6","472.8","stroke-width:2px",[263,264],"polygon",{"points":265,"fill":266},"476.8,124.0 467.8,129.0 467.8,119.0","#5b5cf0",[96,268],{"x":269,"y":270,"width":271,"height":272,"rx":273,"fill":102,"stroke":130,"style":274},"329.8","91.0","121.8","22.0","11","stroke-width:1.2px",[113,276,280],{"x":277,"y":278,"style":279},"390.7","106.0","font-size:11.5px;font-weight:400;fill:var(--brand-strong,#4338ca);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","cumcount() + 1",[38,282,284],{"id":283},"step-1-sort-then-cumcount","Step 1 — Sort, then cumcount",[10,286,287,290,291,294],{},[19,288,289],{},"groupby(...).cumcount()"," numbers each row within its group, starting at 0, in the order the rows appear. The order is the whole point, so sort first; ",[19,292,293],{},"+ 1"," makes it start at 1 like Excel's and SQL's:",[43,296,300],{"className":297,"code":298,"language":299,"meta":48,"style":48},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\norders = pd.read_excel(\"orders.xlsx\", parse_dates=[\"OrderDate\"])\norders = orders.sort_values([\"Customer\", \"OrderDate\", \"OrderID\"]).reset_index(drop=True)\norders[\"OrderNo\"] = orders.groupby(\"Customer\").cumcount() + 1\nprint(orders.to_string(index=False))\n","python",[19,301,302,318,325,358,395,423],{"__ignoreMap":48},[52,303,304,308,312,315],{"class":54,"line":55},[52,305,307],{"class":306},"s-kum","import",[52,309,311],{"class":310},"skGVy"," pandas ",[52,313,314],{"class":306},"as",[52,316,317],{"class":310}," pd\n",[52,319,321],{"class":54,"line":320},2,[52,322,324],{"emptyLinePlaceholder":323},true,"\n",[52,326,328,331,334,337,340,343,347,349,352,355],{"class":54,"line":327},3,[52,329,330],{"class":310},"orders ",[52,332,333],{"class":306},"=",[52,335,336],{"class":310}," pd.read_excel(",[52,338,339],{"class":62},"\"orders.xlsx\"",[52,341,342],{"class":310},", ",[52,344,346],{"class":345},"sa561","parse_dates",[52,348,333],{"class":306},[52,350,351],{"class":310},"[",[52,353,354],{"class":62},"\"OrderDate\"",[52,356,357],{"class":310},"])\n",[52,359,361,363,365,368,371,373,375,377,380,383,386,388,392],{"class":54,"line":360},4,[52,362,330],{"class":310},[52,364,333],{"class":306},[52,366,367],{"class":310}," orders.sort_values([",[52,369,370],{"class":62},"\"Customer\"",[52,372,342],{"class":310},[52,374,354],{"class":62},[52,376,342],{"class":310},[52,378,379],{"class":62},"\"OrderID\"",[52,381,382],{"class":310},"]).reset_index(",[52,384,385],{"class":345},"drop",[52,387,333],{"class":306},[52,389,391],{"class":390},"sP0c6","True",[52,393,394],{"class":310},")\n",[52,396,398,401,404,407,409,412,414,417,420],{"class":54,"line":397},5,[52,399,400],{"class":310},"orders[",[52,402,403],{"class":62},"\"OrderNo\"",[52,405,406],{"class":310},"] ",[52,408,333],{"class":306},[52,410,411],{"class":310}," orders.groupby(",[52,413,370],{"class":62},[52,415,416],{"class":310},").cumcount() ",[52,418,419],{"class":306},"+",[52,421,422],{"class":390}," 1\n",[52,424,426,429,432,435,437,440],{"class":54,"line":425},6,[52,427,428],{"class":390},"print",[52,430,431],{"class":310},"(orders.to_string(",[52,433,434],{"class":345},"index",[52,436,333],{"class":306},[52,438,439],{"class":390},"False",[52,441,442],{"class":310},"))\n",[10,444,445,446,449],{},"Each customer's orders now run 1, 2, 3… in date order: Alder's four orders from January to March, Birch's four, Cedar's three. ",[19,447,448],{},"OrderID"," in the sort key breaks the tie between Birch's two orders on 20 April, so the numbering is the same every time the script runs. Without a tie-breaker, rows with equal sort keys keep whatever order they arrived in, which can change between exports.",[10,451,452],{},"Descending order gives \"most recent first\", and keeping number 1 is the common \"latest row per group\" pattern:",[43,454,456],{"className":297,"code":455,"language":299,"meta":48,"style":48},"latest = (orders.sort_values([\"Customer\", \"OrderDate\", \"OrderID\"], ascending=[True, False, False])\n                .assign(Recency=lambda d: d.groupby(\"Customer\").cumcount() + 1))\nprint(latest.loc[latest[\"Recency\"] == 1, [\"Customer\", \"OrderID\", \"OrderDate\"]].to_string(index=False))\n",[19,457,458,500,525],{"__ignoreMap":48},[52,459,460,463,465,468,470,472,474,476,478,481,484,486,488,490,492,494,496,498],{"class":54,"line":55},[52,461,462],{"class":310},"latest ",[52,464,333],{"class":306},[52,466,467],{"class":310}," (orders.sort_values([",[52,469,370],{"class":62},[52,471,342],{"class":310},[52,473,354],{"class":62},[52,475,342],{"class":310},[52,477,379],{"class":62},[52,479,480],{"class":310},"], ",[52,482,483],{"class":345},"ascending",[52,485,333],{"class":306},[52,487,351],{"class":310},[52,489,391],{"class":390},[52,491,342],{"class":310},[52,493,439],{"class":390},[52,495,342],{"class":310},[52,497,439],{"class":390},[52,499,357],{"class":310},[52,501,502,505,508,511,514,516,518,520,523],{"class":54,"line":320},[52,503,504],{"class":310},"                .assign(",[52,506,507],{"class":345},"Recency",[52,509,510],{"class":306},"=lambda",[52,512,513],{"class":310}," d: d.groupby(",[52,515,370],{"class":62},[52,517,416],{"class":310},[52,519,419],{"class":306},[52,521,522],{"class":390}," 1",[52,524,442],{"class":310},[52,526,527,529,532,535,537,540,542,545,547,549,551,553,555,558,560,562,564],{"class":54,"line":327},[52,528,428],{"class":390},[52,530,531],{"class":310},"(latest.loc[latest[",[52,533,534],{"class":62},"\"Recency\"",[52,536,406],{"class":310},[52,538,539],{"class":306},"==",[52,541,522],{"class":390},[52,543,544],{"class":310},", [",[52,546,370],{"class":62},[52,548,342],{"class":310},[52,550,379],{"class":62},[52,552,342],{"class":310},[52,554,354],{"class":62},[52,556,557],{"class":310},"]].to_string(",[52,559,434],{"class":345},[52,561,333],{"class":306},[52,563,439],{"class":390},[52,565,442],{"class":310},[38,567,569],{"id":568},"step-2-rank-instead-of-counting-when-ties-matter","Step 2 — Rank instead of counting when ties matter",[10,571,572,574,575,578],{},[19,573,29],{}," always gives distinct numbers, even to rows that are equal on the sort key. When equal values should share a position, as in \"rank each customer's orders by amount\", use ",[19,576,577],{},"rank"," with the tie rule you need:",[43,580,582],{"className":297,"code":581,"language":299,"meta":48,"style":48},"by_amount = orders[orders[\"Customer\"] == \"Birch\"][[\"OrderID\", \"Amount\"]].copy()\ng = orders[orders[\"Customer\"] == \"Birch\"].groupby(\"Customer\")[\"Amount\"]\nfor method in (\"first\", \"min\", \"dense\"):\n    by_amount[method] = g.rank(method=method, ascending=False).astype(int)\nprint(by_amount.sort_values(\"Amount\", ascending=False).to_string(index=False))\n",[19,583,584,616,646,676,708],{"__ignoreMap":48},[52,585,586,589,591,594,596,598,600,603,606,608,610,613],{"class":54,"line":55},[52,587,588],{"class":310},"by_amount ",[52,590,333],{"class":306},[52,592,593],{"class":310}," orders[orders[",[52,595,370],{"class":62},[52,597,406],{"class":310},[52,599,539],{"class":306},[52,601,602],{"class":62}," \"Birch\"",[52,604,605],{"class":310},"][[",[52,607,379],{"class":62},[52,609,342],{"class":310},[52,611,612],{"class":62},"\"Amount\"",[52,614,615],{"class":310},"]].copy()\n",[52,617,618,621,623,625,627,629,631,633,636,638,641,643],{"class":54,"line":320},[52,619,620],{"class":310},"g ",[52,622,333],{"class":306},[52,624,593],{"class":310},[52,626,370],{"class":62},[52,628,406],{"class":310},[52,630,539],{"class":306},[52,632,602],{"class":62},[52,634,635],{"class":310},"].groupby(",[52,637,370],{"class":62},[52,639,640],{"class":310},")[",[52,642,612],{"class":62},[52,644,645],{"class":310},"]\n",[52,647,648,651,654,657,660,663,665,668,670,673],{"class":54,"line":327},[52,649,650],{"class":306},"for",[52,652,653],{"class":310}," method ",[52,655,656],{"class":306},"in",[52,658,659],{"class":310}," (",[52,661,662],{"class":62},"\"first\"",[52,664,342],{"class":310},[52,666,667],{"class":62},"\"min\"",[52,669,342],{"class":310},[52,671,672],{"class":62},"\"dense\"",[52,674,675],{"class":310},"):\n",[52,677,678,681,683,686,689,691,694,696,698,700,703,706],{"class":54,"line":360},[52,679,680],{"class":310},"    by_amount[method] ",[52,682,333],{"class":306},[52,684,685],{"class":310}," g.rank(",[52,687,688],{"class":345},"method",[52,690,333],{"class":306},[52,692,693],{"class":310},"method, ",[52,695,483],{"class":345},[52,697,333],{"class":306},[52,699,439],{"class":390},[52,701,702],{"class":310},").astype(",[52,704,705],{"class":390},"int",[52,707,394],{"class":310},[52,709,710,712,715,717,719,721,723,725,728,730,732,734],{"class":54,"line":397},[52,711,428],{"class":390},[52,713,714],{"class":310},"(by_amount.sort_values(",[52,716,612],{"class":62},[52,718,342],{"class":310},[52,720,483],{"class":345},[52,722,333],{"class":306},[52,724,439],{"class":390},[52,726,727],{"class":310},").to_string(",[52,729,434],{"class":345},[52,731,333],{"class":306},[52,733,439],{"class":390},[52,735,442],{"class":310},[10,737,738,739,742,743,745,746,749,750,753,754,757,758,760],{},"Birch has three orders of 480. ",[19,740,741],{},"first"," numbers them 1, 2, 3 in row order, like ",[19,744,29],{},"; ",[19,747,748],{},"min"," gives all three rank 1 and the next order rank 4, like Excel's ",[19,751,752],{},"RANK.EQ"," and sports tables; ",[19,755,756],{},"dense"," gives them 1 and the next order 2, with no gaps. Choose the one whose meaning matches the question; for \"top 3 orders per customer\", ",[19,759,748],{}," can return more than three rows, which is often correct.",[77,762,86,768,86,771,86,774,86,779,86,784,86,788,86,792,86,795,86,799,86,802,86,806,86,809,86,812,86,816,86,818,86,820,86,822,86,824,86,826,86,828,86,831,86,833,86,835,86,837,86,839,86,841,86,843,86,845,86,848,86,851,86,853,86,855,86,857,86,859,86,861,86,863,86,866,86,870,86,872,86,875,86,879,86,882,86,884],{"viewBox":763,"role":80,"ariaLabelledBy":764,"xmlns":84,"style":767},"254 0 252 172",[765,766],"ng-ties-t","ng-ties-d","width:100%;max-width:252px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[88,769,770],{"id":765},"Three ways to number tied values",[92,772,773],{"id":766},"A table of Birch's orders by amount with three rank methods. The three orders of 480 get 1, 2, 3 with first, all get 1 with min and dense, and the order of 130 gets 4 with first and min but 2 with dense.",[96,775],{"x":776,"y":99,"width":777,"height":778,"fill":102},"254","252","172",[96,780],{"x":781,"y":782,"width":783,"height":108,"rx":99,"fill":109,"stroke":110,"style":111},"270.2","16.0","68.9",[113,785,787],{"x":259,"y":786,"style":117},"35.0","Amount",[96,789],{"x":790,"y":782,"width":791,"height":108,"rx":99,"fill":109,"stroke":110,"style":111},"339.1","47.1",[113,793,741],{"x":794,"y":786,"style":117},"362.6",[96,796],{"x":797,"y":782,"width":798,"height":108,"rx":99,"fill":109,"stroke":110,"style":111},"386.2","44.9",[113,800,748],{"x":801,"y":786,"style":117},"408.6",[96,803],{"x":804,"y":782,"width":805,"height":108,"rx":99,"fill":109,"stroke":110,"style":111},"431.1","58.7",[113,807,756],{"x":808,"y":786,"style":117},"460.4",[96,810],{"x":781,"y":811,"width":783,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},"44.0",[113,813,815],{"x":259,"y":814,"style":135},"63.0","480",[96,817],{"x":790,"y":811,"width":791,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,819,212],{"x":794,"y":814,"style":135},[96,821],{"x":797,"y":811,"width":798,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,823,212],{"x":801,"y":814,"style":135},[96,825],{"x":804,"y":811,"width":805,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,827,212],{"x":808,"y":814,"style":135},[96,829],{"x":781,"y":830,"width":783,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},"72.0",[113,832,815],{"x":259,"y":270,"style":135},[96,834],{"x":790,"y":830,"width":791,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,836,221],{"x":794,"y":270,"style":135},[96,838],{"x":797,"y":830,"width":798,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,840,212],{"x":801,"y":270,"style":135},[96,842],{"x":804,"y":830,"width":805,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,844,212],{"x":808,"y":270,"style":135},[96,846],{"x":781,"y":847,"width":783,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},"100.0",[113,849,815],{"x":259,"y":850,"style":135},"119.0",[96,852],{"x":790,"y":847,"width":791,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,854,230],{"x":794,"y":850,"style":135},[96,856],{"x":797,"y":847,"width":798,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,858,212],{"x":801,"y":850,"style":135},[96,860],{"x":804,"y":847,"width":805,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,862,212],{"x":808,"y":850,"style":135},[96,864],{"x":781,"y":865,"width":783,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},"128.0",[113,867,869],{"x":259,"y":868,"style":135},"147.0","130",[96,871],{"x":790,"y":865,"width":791,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,873,874],{"x":794,"y":868,"style":135},"4",[96,876],{"x":797,"y":865,"width":798,"height":108,"rx":99,"fill":877,"stroke":878,"style":111},"#fdefd8","var(--gold,#b4740a)",[113,880,874],{"x":801,"y":868,"style":881},"font-size:11px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[96,883],{"x":804,"y":865,"width":805,"height":108,"rx":99,"fill":207,"stroke":208,"style":111},[113,885,221],{"x":808,"y":868,"style":211},[38,887,889],{"id":888},"step-3-restart-the-count-on-consecutive-runs","Step 3 — Restart the count on consecutive runs",[10,891,892],{},"Sometimes the group is not a column value but a run of consecutive rows: \"how many orders in a row above 200\", or a status that restarts its count each time it changes. Build a run ID that increases whenever the value differs from the previous row, then count within runs:",[43,894,896],{"className":297,"code":895,"language":299,"meta":48,"style":48},"seq = orders[orders[\"Customer\"] == \"Alder\"].copy()\nseq[\"Big\"] = seq[\"Amount\"] >= 150\nseq[\"Run\"] = (seq[\"Big\"] != seq[\"Big\"].shift()).cumsum()\nseq[\"InRun\"] = seq.groupby(\"Run\").cumcount() + 1\nprint(seq[[\"OrderDate\", \"Amount\", \"Big\", \"Run\", \"InRun\"]].to_string(index=False))\n",[19,897,898,919,944,972,994],{"__ignoreMap":48},[52,899,900,903,905,907,909,911,913,916],{"class":54,"line":55},[52,901,902],{"class":310},"seq ",[52,904,333],{"class":306},[52,906,593],{"class":310},[52,908,370],{"class":62},[52,910,406],{"class":310},[52,912,539],{"class":306},[52,914,915],{"class":62}," \"Alder\"",[52,917,918],{"class":310},"].copy()\n",[52,920,921,924,927,929,931,934,936,938,941],{"class":54,"line":320},[52,922,923],{"class":310},"seq[",[52,925,926],{"class":62},"\"Big\"",[52,928,406],{"class":310},[52,930,333],{"class":306},[52,932,933],{"class":310}," seq[",[52,935,612],{"class":62},[52,937,406],{"class":310},[52,939,940],{"class":306},">=",[52,942,943],{"class":390}," 150\n",[52,945,946,948,951,953,955,958,960,962,965,967,969],{"class":54,"line":327},[52,947,923],{"class":310},[52,949,950],{"class":62},"\"Run\"",[52,952,406],{"class":310},[52,954,333],{"class":306},[52,956,957],{"class":310}," (seq[",[52,959,926],{"class":62},[52,961,406],{"class":310},[52,963,964],{"class":306},"!=",[52,966,933],{"class":310},[52,968,926],{"class":62},[52,970,971],{"class":310},"].shift()).cumsum()\n",[52,973,974,976,979,981,983,986,988,990,992],{"class":54,"line":360},[52,975,923],{"class":310},[52,977,978],{"class":62},"\"InRun\"",[52,980,406],{"class":310},[52,982,333],{"class":306},[52,984,985],{"class":310}," seq.groupby(",[52,987,950],{"class":62},[52,989,416],{"class":310},[52,991,419],{"class":306},[52,993,422],{"class":390},[52,995,996,998,1001,1003,1005,1007,1009,1011,1013,1015,1017,1019,1021,1023,1025,1027],{"class":54,"line":397},[52,997,428],{"class":390},[52,999,1000],{"class":310},"(seq[[",[52,1002,354],{"class":62},[52,1004,342],{"class":310},[52,1006,612],{"class":62},[52,1008,342],{"class":310},[52,1010,926],{"class":62},[52,1012,342],{"class":310},[52,1014,950],{"class":62},[52,1016,342],{"class":310},[52,1018,978],{"class":62},[52,1020,557],{"class":310},[52,1022,434],{"class":345},[52,1024,333],{"class":306},[52,1026,439],{"class":390},[52,1028,442],{"class":310},[10,1030,1031,1032,1035,1036,1038],{},"The comparison with ",[19,1033,1034],{},"shift()"," is ",[19,1037,391],{}," at the first row of each run, and the cumulative sum turns those into run IDs 1, 2, 3. The same pattern finds streaks in sensor readings, consecutive late deliveries or unbroken months of growth.",[38,1040,1042],{"id":1041},"step-4-3-of-4-counting-from-the-end-and-the-group-size","Step 4 — \"3 of 4\": counting from the end and the group size",[10,1044,1045,1046,1049,1050,1053],{},"Two companions of the running number answer the other common questions. ",[19,1047,1048],{},"cumcount(ascending=False)"," counts from the end of each group, so 0 marks the last row, which flags \"the customer's most recent order\" without a second sort. ",[19,1051,1052],{},"transform(\"size\")"," puts the group's row count on every row, which gives labels like \"order 3 of 4\" and lets you filter customers with at least three orders:",[43,1055,1057],{"className":297,"code":1056,"language":299,"meta":48,"style":48},"g = orders.groupby(\"Customer\")\norders[\"FromEnd\"] = g.cumcount(ascending=False)\norders[\"OrdersTotal\"] = g[\"OrderID\"].transform(\"size\")\norders[\"Label\"] = \"order \" + orders[\"OrderNo\"].astype(str) + \" of \" + orders[\"OrdersTotal\"].astype(str)\norders[\"IsLatest\"] = orders[\"FromEnd\"] == 0\nprint(orders[[\"Customer\", \"OrderDate\", \"Label\", \"IsLatest\"]].to_string(index=False))\n",[19,1058,1059,1071,1093,1117,1165,1187],{"__ignoreMap":48},[52,1060,1061,1063,1065,1067,1069],{"class":54,"line":55},[52,1062,620],{"class":310},[52,1064,333],{"class":306},[52,1066,411],{"class":310},[52,1068,370],{"class":62},[52,1070,394],{"class":310},[52,1072,1073,1075,1078,1080,1082,1085,1087,1089,1091],{"class":54,"line":320},[52,1074,400],{"class":310},[52,1076,1077],{"class":62},"\"FromEnd\"",[52,1079,406],{"class":310},[52,1081,333],{"class":306},[52,1083,1084],{"class":310}," g.cumcount(",[52,1086,483],{"class":345},[52,1088,333],{"class":306},[52,1090,439],{"class":390},[52,1092,394],{"class":310},[52,1094,1095,1097,1100,1102,1104,1107,1109,1112,1115],{"class":54,"line":327},[52,1096,400],{"class":310},[52,1098,1099],{"class":62},"\"OrdersTotal\"",[52,1101,406],{"class":310},[52,1103,333],{"class":306},[52,1105,1106],{"class":310}," g[",[52,1108,379],{"class":62},[52,1110,1111],{"class":310},"].transform(",[52,1113,1114],{"class":62},"\"size\"",[52,1116,394],{"class":310},[52,1118,1119,1121,1124,1126,1128,1131,1134,1137,1139,1142,1145,1148,1150,1153,1155,1157,1159,1161,1163],{"class":54,"line":360},[52,1120,400],{"class":310},[52,1122,1123],{"class":62},"\"Label\"",[52,1125,406],{"class":310},[52,1127,333],{"class":306},[52,1129,1130],{"class":62}," \"order \"",[52,1132,1133],{"class":306}," +",[52,1135,1136],{"class":310}," orders[",[52,1138,403],{"class":62},[52,1140,1141],{"class":310},"].astype(",[52,1143,1144],{"class":390},"str",[52,1146,1147],{"class":310},") ",[52,1149,419],{"class":306},[52,1151,1152],{"class":62}," \" of \"",[52,1154,1133],{"class":306},[52,1156,1136],{"class":310},[52,1158,1099],{"class":62},[52,1160,1141],{"class":310},[52,1162,1144],{"class":390},[52,1164,394],{"class":310},[52,1166,1167,1169,1172,1174,1176,1178,1180,1182,1184],{"class":54,"line":397},[52,1168,400],{"class":310},[52,1170,1171],{"class":62},"\"IsLatest\"",[52,1173,406],{"class":310},[52,1175,333],{"class":306},[52,1177,1136],{"class":310},[52,1179,1077],{"class":62},[52,1181,406],{"class":310},[52,1183,539],{"class":306},[52,1185,1186],{"class":390}," 0\n",[52,1188,1189,1191,1194,1196,1198,1200,1202,1204,1206,1208,1210,1212,1214,1216],{"class":54,"line":425},[52,1190,428],{"class":390},[52,1192,1193],{"class":310},"(orders[[",[52,1195,370],{"class":62},[52,1197,342],{"class":310},[52,1199,354],{"class":62},[52,1201,342],{"class":310},[52,1203,1123],{"class":62},[52,1205,342],{"class":310},[52,1207,1171],{"class":62},[52,1209,557],{"class":310},[52,1211,434],{"class":345},[52,1213,333],{"class":306},[52,1215,439],{"class":390},[52,1217,442],{"class":310},[10,1219,1220,1221,1224,1225,1228,1229,1232,1233,1236],{},"These are the building blocks of most \"per customer\" logic in reports: first order (",[19,1222,1223],{},"OrderNo == 1","), latest order (",[19,1226,1227],{},"FromEnd == 0","), repeat customers (",[19,1230,1231],{},"OrdersTotal >= 2","), and the gap between consecutive orders, which is ",[19,1234,1235],{},"g[\"OrderDate\"].diff()"," on the same sorted frame.",[77,1238,86,1244,86,1247,86,1250,86,1254,86,1257,86,1260,86,1263,86,1266,86,1270,86,1274,86,1278,86,1282,86,1286,86,1290,86,1292,86,1294,86,1296,86,1298,86,1300,86,1302,86,1304,86,1306,86,1308,86,1311,86,1313,86,1315,86,1317,86,1319,86,1321,86,1323,86,1325,86,1327,86,1329,86,1332,86,1334,86,1336,86,1338,86,1340,86,1342,86,1344,86,1346,86,1348,86,1350,86,1353,86,1355,86,1358,86,1360,86,1362,86,1364,86,1366,86,1368,86,1370,86,1372],{"viewBox":1239,"role":80,"ariaLabelledBy":1240,"xmlns":84,"style":1243},"152 0 455 172",[1241,1242],"ng-companions-t","ng-companions-d","width:100%;max-width:455px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[88,1245,1246],{"id":1241},"The running number and its companions",[92,1248,1249],{"id":1242},"A table of Alder's four orders with the running number, the count from the end, the group size and a label. The first order has number 1 and 3 from the end; the last has number 4, 0 from the end, and is flagged as the latest.",[96,1251],{"x":1252,"y":99,"width":1253,"height":778,"fill":102},"152","455",[96,1255],{"x":1256,"y":782,"width":122,"height":108,"rx":99,"fill":109,"stroke":110,"style":111},"168.7",[113,1258,126],{"x":1259,"y":786,"style":117},"215.6",[96,1261],{"x":1262,"y":782,"width":196,"height":108,"rx":99,"fill":109,"stroke":110,"style":111},"262.4",[113,1264,200],{"x":1265,"y":786,"style":117},"298.6",[96,1267],{"x":1268,"y":782,"width":1269,"height":108,"rx":99,"fill":109,"stroke":110,"style":111},"334.8","74.6",[113,1271,1273],{"x":1272,"y":786,"style":117},"372.1","FromEnd",[96,1275],{"x":1276,"y":782,"width":1277,"height":108,"rx":99,"fill":109,"stroke":110,"style":111},"409.4","91.8",[113,1279,1281],{"x":1280,"y":786,"style":117},"455.2","OrdersTotal",[96,1283],{"x":1284,"y":782,"width":1285,"height":108,"rx":99,"fill":109,"stroke":110,"style":111},"501.1","90.1",[113,1287,1289],{"x":1288,"y":786,"style":117},"546.2","Label",[96,1291],{"x":1256,"y":811,"width":122,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,1293,141],{"x":1259,"y":814,"style":135},[96,1295],{"x":1262,"y":811,"width":196,"height":108,"rx":99,"fill":207,"stroke":208,"style":111},[113,1297,212],{"x":1265,"y":814,"style":211},[96,1299],{"x":1268,"y":811,"width":1269,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,1301,230],{"x":1272,"y":814,"style":135},[96,1303],{"x":1276,"y":811,"width":1277,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,1305,874],{"x":1280,"y":814,"style":135},[96,1307],{"x":1284,"y":811,"width":1285,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,1309,1310],{"x":1288,"y":814,"style":135},"order 1 of 4",[96,1312],{"x":1256,"y":830,"width":122,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,1314,152],{"x":1259,"y":270,"style":135},[96,1316],{"x":1262,"y":830,"width":196,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,1318,221],{"x":1265,"y":270,"style":135},[96,1320],{"x":1268,"y":830,"width":1269,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,1322,221],{"x":1272,"y":270,"style":135},[96,1324],{"x":1276,"y":830,"width":1277,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,1326,874],{"x":1280,"y":270,"style":135},[96,1328],{"x":1284,"y":830,"width":1285,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,1330,1331],{"x":1288,"y":270,"style":135},"order 2 of 4",[96,1333],{"x":1256,"y":847,"width":122,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,1335,163],{"x":1259,"y":850,"style":135},[96,1337],{"x":1262,"y":847,"width":196,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,1339,230],{"x":1265,"y":850,"style":135},[96,1341],{"x":1268,"y":847,"width":1269,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,1343,212],{"x":1272,"y":850,"style":135},[96,1345],{"x":1276,"y":847,"width":1277,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,1347,874],{"x":1280,"y":850,"style":135},[96,1349],{"x":1284,"y":847,"width":1285,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,1351,1352],{"x":1288,"y":850,"style":135},"order 3 of 4",[96,1354],{"x":1256,"y":865,"width":122,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,1356,1357],{"x":1259,"y":868,"style":135},"2026-03-15",[96,1359],{"x":1262,"y":865,"width":196,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,1361,874],{"x":1265,"y":868,"style":135},[96,1363],{"x":1268,"y":865,"width":1269,"height":108,"rx":99,"fill":877,"stroke":878,"style":111},[113,1365,99],{"x":1272,"y":868,"style":881},[96,1367],{"x":1276,"y":865,"width":1277,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,1369,874],{"x":1280,"y":868,"style":135},[96,1371],{"x":1284,"y":865,"width":1285,"height":108,"rx":99,"fill":102,"stroke":130,"style":131},[113,1373,1374],{"x":1288,"y":868,"style":135},"order 4 of 4",[38,1376,1378],{"id":1377},"step-5-number-the-groups-themselves","Step 5 — Number the groups themselves",[10,1380,1381,1383,1384,1387],{},[19,1382,29],{}," numbers rows within groups; ",[19,1385,1386],{},"ngroup"," numbers the groups, which is useful as a compact key, as a stable ID to share instead of customer names, or for alternating row shading by customer in the output:",[43,1389,1391],{"className":297,"code":1390,"language":299,"meta":48,"style":48},"orders[\"CustomerNo\"] = orders.groupby(\"Customer\", sort=True).ngroup() + 1\nprint(orders[[\"Customer\", \"CustomerNo\"]].drop_duplicates().to_string(index=False))\n",[19,1392,1393,1424],{"__ignoreMap":48},[52,1394,1395,1397,1400,1402,1404,1406,1408,1410,1413,1415,1417,1420,1422],{"class":54,"line":55},[52,1396,400],{"class":310},[52,1398,1399],{"class":62},"\"CustomerNo\"",[52,1401,406],{"class":310},[52,1403,333],{"class":306},[52,1405,411],{"class":310},[52,1407,370],{"class":62},[52,1409,342],{"class":310},[52,1411,1412],{"class":345},"sort",[52,1414,333],{"class":306},[52,1416,391],{"class":390},[52,1418,1419],{"class":310},").ngroup() ",[52,1421,419],{"class":306},[52,1423,422],{"class":390},[52,1425,1426,1428,1430,1432,1434,1436,1439,1441,1443,1445],{"class":54,"line":320},[52,1427,428],{"class":390},[52,1429,1193],{"class":310},[52,1431,370],{"class":62},[52,1433,342],{"class":310},[52,1435,1399],{"class":62},[52,1437,1438],{"class":310},"]].drop_duplicates().to_string(",[52,1440,434],{"class":345},[52,1442,333],{"class":306},[52,1444,439],{"class":390},[52,1446,442],{"class":310},[38,1448,1450],{"id":1449},"step-6-write-it-to-excel-as-values-or-as-formulas","Step 6 — Write it to Excel, as values or as formulas",[10,1452,1453,1454,1456,1457,1460],{},"Usually the computed numbers are written as values. If the sheet is going to be sorted, filtered and extended by hand, a formula that recomputes may be preferred. The Excel equivalent of ",[19,1455,280],{}," on a sheet sorted by customer is ",[19,1458,1459],{},"COUNTIF"," over an expanding range:",[43,1462,1464],{"className":297,"code":1463,"language":299,"meta":48,"style":48},"from openpyxl import load_workbook\n\nout = orders[[\"Customer\", \"OrderDate\", \"OrderID\", \"Amount\", \"OrderNo\"]]\nout.to_excel(\"orders_numbered.xlsx\", index=False)\nwb = load_workbook(\"orders_numbered.xlsx\")\nws = wb.active\nws[\"F1\"] = \"OrderNo (formula)\"\nfor r in range(2, ws.max_row + 1):\n    ws[f\"F{r}\"] = f\"=COUNTIF($A$2:A{r},A{r})\"\nwb.save(\"orders_numbered.xlsx\")\n",[19,1465,1466,1479,1483,1514,1532,1546,1556,1572,1599,1652],{"__ignoreMap":48},[52,1467,1468,1471,1474,1476],{"class":54,"line":55},[52,1469,1470],{"class":306},"from",[52,1472,1473],{"class":310}," openpyxl ",[52,1475,307],{"class":306},[52,1477,1478],{"class":310}," load_workbook\n",[52,1480,1481],{"class":54,"line":320},[52,1482,324],{"emptyLinePlaceholder":323},[52,1484,1485,1488,1490,1493,1495,1497,1499,1501,1503,1505,1507,1509,1511],{"class":54,"line":327},[52,1486,1487],{"class":310},"out ",[52,1489,333],{"class":306},[52,1491,1492],{"class":310}," orders[[",[52,1494,370],{"class":62},[52,1496,342],{"class":310},[52,1498,354],{"class":62},[52,1500,342],{"class":310},[52,1502,379],{"class":62},[52,1504,342],{"class":310},[52,1506,612],{"class":62},[52,1508,342],{"class":310},[52,1510,403],{"class":62},[52,1512,1513],{"class":310},"]]\n",[52,1515,1516,1519,1522,1524,1526,1528,1530],{"class":54,"line":360},[52,1517,1518],{"class":310},"out.to_excel(",[52,1520,1521],{"class":62},"\"orders_numbered.xlsx\"",[52,1523,342],{"class":310},[52,1525,434],{"class":345},[52,1527,333],{"class":306},[52,1529,439],{"class":390},[52,1531,394],{"class":310},[52,1533,1534,1537,1539,1542,1544],{"class":54,"line":397},[52,1535,1536],{"class":310},"wb ",[52,1538,333],{"class":306},[52,1540,1541],{"class":310}," load_workbook(",[52,1543,1521],{"class":62},[52,1545,394],{"class":310},[52,1547,1548,1551,1553],{"class":54,"line":425},[52,1549,1550],{"class":310},"ws ",[52,1552,333],{"class":306},[52,1554,1555],{"class":310}," wb.active\n",[52,1557,1559,1562,1565,1567,1569],{"class":54,"line":1558},7,[52,1560,1561],{"class":310},"ws[",[52,1563,1564],{"class":62},"\"F1\"",[52,1566,406],{"class":310},[52,1568,333],{"class":306},[52,1570,1571],{"class":62}," \"OrderNo (formula)\"\n",[52,1573,1575,1577,1580,1582,1585,1588,1590,1593,1595,1597],{"class":54,"line":1574},8,[52,1576,650],{"class":306},[52,1578,1579],{"class":310}," r ",[52,1581,656],{"class":306},[52,1583,1584],{"class":390}," range",[52,1586,1587],{"class":310},"(",[52,1589,221],{"class":390},[52,1591,1592],{"class":310},", ws.max_row ",[52,1594,419],{"class":306},[52,1596,522],{"class":390},[52,1598,675],{"class":310},[52,1600,1602,1605,1608,1611,1615,1618,1621,1624,1626,1628,1631,1634,1636,1638,1640,1643,1645,1647,1649],{"class":54,"line":1601},9,[52,1603,1604],{"class":310},"    ws[",[52,1606,1607],{"class":306},"f",[52,1609,1610],{"class":62},"\"F",[52,1612,1614],{"class":1613},"sSjpA","{",[52,1616,1617],{"class":310},"r",[52,1619,1620],{"class":1613},"}",[52,1622,1623],{"class":62},"\"",[52,1625,406],{"class":310},[52,1627,333],{"class":306},[52,1629,1630],{"class":306}," f",[52,1632,1633],{"class":62},"\"=COUNTIF($A$2:A",[52,1635,1614],{"class":1613},[52,1637,1617],{"class":310},[52,1639,1620],{"class":1613},[52,1641,1642],{"class":62},",A",[52,1644,1614],{"class":1613},[52,1646,1617],{"class":310},[52,1648,1620],{"class":1613},[52,1650,1651],{"class":62},")\"\n",[52,1653,1655,1658,1660],{"class":54,"line":1654},10,[52,1656,1657],{"class":310},"wb.save(",[52,1659,1521],{"class":62},[52,1661,394],{"class":310},[43,1663,1665],{"className":45,"code":1664,"language":47,"meta":48,"style":48},"soffice -env:UserInstallation=file:\u002F\u002F$PWD\u002Flo_profile --headless --convert-to xlsx --outdir recalculated orders_numbered.xlsx > \u002Fdev\u002Fnull 2>&1\nls recalculated\n",[19,1666,1667,1708],{"__ignoreMap":48},[52,1668,1669,1672,1675,1678,1681,1684,1687,1690,1693,1696,1699,1702,1705],{"class":54,"line":55},[52,1670,1671],{"class":58},"soffice",[52,1673,1674],{"class":390}," -env:UserInstallation=file:\u002F\u002F",[52,1676,1677],{"class":310},"$PWD",[52,1679,1680],{"class":390},"\u002Flo_profile",[52,1682,1683],{"class":390}," --headless",[52,1685,1686],{"class":390}," --convert-to",[52,1688,1689],{"class":62}," xlsx",[52,1691,1692],{"class":390}," --outdir",[52,1694,1695],{"class":62}," recalculated",[52,1697,1698],{"class":62}," orders_numbered.xlsx",[52,1700,1701],{"class":306}," >",[52,1703,1704],{"class":62}," \u002Fdev\u002Fnull",[52,1706,1707],{"class":306}," 2>&1\n",[52,1709,1710,1713],{"class":54,"line":320},[52,1711,1712],{"class":58},"ls",[52,1714,1715],{"class":62}," recalculated\n",[43,1717,1719],{"className":297,"code":1718,"language":299,"meta":48,"style":48},"check = pd.read_excel(\"recalculated\u002Forders_numbered.xlsx\")\nprint(\"formula matches pandas:\", (check[\"OrderNo\"] == check[\"OrderNo (formula)\"]).all())\n",[19,1720,1721,1735],{"__ignoreMap":48},[52,1722,1723,1726,1728,1730,1733],{"class":54,"line":55},[52,1724,1725],{"class":310},"check ",[52,1727,333],{"class":306},[52,1729,336],{"class":310},[52,1731,1732],{"class":62},"\"recalculated\u002Forders_numbered.xlsx\"",[52,1734,394],{"class":310},[52,1736,1737,1739,1741,1744,1747,1749,1751,1753,1756,1759],{"class":54,"line":320},[52,1738,428],{"class":390},[52,1740,1587],{"class":310},[52,1742,1743],{"class":62},"\"formula matches pandas:\"",[52,1745,1746],{"class":310},", (check[",[52,1748,403],{"class":62},[52,1750,406],{"class":310},[52,1752,539],{"class":306},[52,1754,1755],{"class":310}," check[",[52,1757,1758],{"class":62},"\"OrderNo (formula)\"",[52,1760,1761],{"class":310},"]).all())\n",[10,1763,1764,1765,1767],{},"The formula counts matches from the top of the column down to the current row, so it only numbers in date order if the sheet stays sorted by customer and date; re-sort the sheet and the formula renumbers in the new order, which may or may not be what the reader wants. On large sheets ",[19,1766,1459],{}," over expanding ranges is slow, since each row scans all rows above it; values from pandas avoid that.",[38,1769,1771],{"id":1770},"performance","Performance",[10,1773,1774,1776],{},[19,1775,29],{}," is vectorised and scales to large tables; the expensive part is the sort, not the numbering. A quick measurement on a million rows spread over 50,000 customers:",[43,1778,1780],{"className":297,"code":1779,"language":299,"meta":48,"style":48},"import time\nimport numpy as np\n\nrng = np.random.default_rng(0)\nbig = pd.DataFrame({\"Customer\": rng.integers(0, 50_000, 1_000_000),\n                    \"OrderDate\": pd.Timestamp(\"2025-01-01\") + pd.to_timedelta(rng.integers(0, 600, 1_000_000), unit=\"D\")})\nt0 = time.perf_counter()\nbig = big.sort_values([\"Customer\", \"OrderDate\"], kind=\"stable\")\nt1 = time.perf_counter()\nbig[\"OrderNo\"] = big.groupby(\"Customer\").cumcount() + 1\nt2 = time.perf_counter()\nprint(f\"sort {t1 - t0:.2f} s, cumcount {t2 - t1:.2f} s, max orders per customer {big['OrderNo'].max()}\")\n",[19,1781,1782,1789,1801,1805,1819,1849,1892,1902,1929,1938,1960,1970],{"__ignoreMap":48},[52,1783,1784,1786],{"class":54,"line":55},[52,1785,307],{"class":306},[52,1787,1788],{"class":310}," time\n",[52,1790,1791,1793,1796,1798],{"class":54,"line":320},[52,1792,307],{"class":306},[52,1794,1795],{"class":310}," numpy ",[52,1797,314],{"class":306},[52,1799,1800],{"class":310}," np\n",[52,1802,1803],{"class":54,"line":327},[52,1804,324],{"emptyLinePlaceholder":323},[52,1806,1807,1810,1812,1815,1817],{"class":54,"line":360},[52,1808,1809],{"class":310},"rng ",[52,1811,333],{"class":306},[52,1813,1814],{"class":310}," np.random.default_rng(",[52,1816,99],{"class":390},[52,1818,394],{"class":310},[52,1820,1821,1824,1826,1829,1831,1834,1836,1838,1841,1843,1846],{"class":54,"line":397},[52,1822,1823],{"class":310},"big ",[52,1825,333],{"class":306},[52,1827,1828],{"class":310}," pd.DataFrame({",[52,1830,370],{"class":62},[52,1832,1833],{"class":310},": rng.integers(",[52,1835,99],{"class":390},[52,1837,342],{"class":310},[52,1839,1840],{"class":390},"50_000",[52,1842,342],{"class":310},[52,1844,1845],{"class":390},"1_000_000",[52,1847,1848],{"class":310},"),\n",[52,1850,1851,1854,1857,1860,1862,1864,1867,1869,1871,1874,1876,1878,1881,1884,1886,1889],{"class":54,"line":425},[52,1852,1853],{"class":62},"                    \"OrderDate\"",[52,1855,1856],{"class":310},": pd.Timestamp(",[52,1858,1859],{"class":62},"\"2025-01-01\"",[52,1861,1147],{"class":310},[52,1863,419],{"class":306},[52,1865,1866],{"class":310}," pd.to_timedelta(rng.integers(",[52,1868,99],{"class":390},[52,1870,342],{"class":310},[52,1872,1873],{"class":390},"600",[52,1875,342],{"class":310},[52,1877,1845],{"class":390},[52,1879,1880],{"class":310},"), ",[52,1882,1883],{"class":345},"unit",[52,1885,333],{"class":306},[52,1887,1888],{"class":62},"\"D\"",[52,1890,1891],{"class":310},")})\n",[52,1893,1894,1897,1899],{"class":54,"line":1558},[52,1895,1896],{"class":310},"t0 ",[52,1898,333],{"class":306},[52,1900,1901],{"class":310}," time.perf_counter()\n",[52,1903,1904,1906,1908,1911,1913,1915,1917,1919,1922,1924,1927],{"class":54,"line":1574},[52,1905,1823],{"class":310},[52,1907,333],{"class":306},[52,1909,1910],{"class":310}," big.sort_values([",[52,1912,370],{"class":62},[52,1914,342],{"class":310},[52,1916,354],{"class":62},[52,1918,480],{"class":310},[52,1920,1921],{"class":345},"kind",[52,1923,333],{"class":306},[52,1925,1926],{"class":62},"\"stable\"",[52,1928,394],{"class":310},[52,1930,1931,1934,1936],{"class":54,"line":1601},[52,1932,1933],{"class":310},"t1 ",[52,1935,333],{"class":306},[52,1937,1901],{"class":310},[52,1939,1940,1943,1945,1947,1949,1952,1954,1956,1958],{"class":54,"line":1654},[52,1941,1942],{"class":310},"big[",[52,1944,403],{"class":62},[52,1946,406],{"class":310},[52,1948,333],{"class":306},[52,1950,1951],{"class":310}," big.groupby(",[52,1953,370],{"class":62},[52,1955,416],{"class":310},[52,1957,419],{"class":306},[52,1959,422],{"class":390},[52,1961,1963,1966,1968],{"class":54,"line":1962},11,[52,1964,1965],{"class":310},"t2 ",[52,1967,333],{"class":306},[52,1969,1901],{"class":310},[52,1971,1973,1975,1977,1979,1982,1984,1986,1989,1992,1995,1997,2000,2002,2004,2006,2009,2011,2013,2016,2018,2020,2023,2026,2028,2030],{"class":54,"line":1972},12,[52,1974,428],{"class":390},[52,1976,1587],{"class":310},[52,1978,1607],{"class":306},[52,1980,1981],{"class":62},"\"sort ",[52,1983,1614],{"class":1613},[52,1985,1933],{"class":310},[52,1987,1988],{"class":306},"-",[52,1990,1991],{"class":310}," t0",[52,1993,1994],{"class":306},":.2f",[52,1996,1620],{"class":1613},[52,1998,1999],{"class":62}," s, cumcount ",[52,2001,1614],{"class":1613},[52,2003,1965],{"class":310},[52,2005,1988],{"class":306},[52,2007,2008],{"class":310}," t1",[52,2010,1994],{"class":306},[52,2012,1620],{"class":1613},[52,2014,2015],{"class":62}," s, max orders per customer ",[52,2017,1614],{"class":1613},[52,2019,1942],{"class":310},[52,2021,2022],{"class":62},"'OrderNo'",[52,2024,2025],{"class":310},"].max()",[52,2027,1620],{"class":1613},[52,2029,1623],{"class":62},[52,2031,394],{"class":310},[10,2033,2034,2035,2037],{},"Both steps take a fraction of a second here: roughly a tenth of a second to sort and a few hundredths to number, on the machine used for this page. The Excel ",[19,2036,1459],{}," version of the same numbering compares every row with every row above it, so its work grows with the square of the row count; that is why the formula approach is fine for a few thousand rows and painful for a few hundred thousand, and why the numbers are better computed in pandas and written as values for large sheets.",[38,2039,2041],{"id":2040},"common-pitfalls","Common pitfalls",[2043,2044,2045,2056,2062,2075,2085],"ul",{},[2046,2047,2048,2052,2053,2055],"li",{},[2049,2050,2051],"strong",{},"Not sorting first."," ",[19,2054,29],{}," follows row order; sort by group and the ordering column.",[2046,2057,2058,2061],{},[2049,2059,2060],{},"No tie-breaker."," Add a unique column to the sort so the numbering is reproducible.",[2046,2063,2064,2070,2071,2074],{},[2049,2065,2066,2067,2069],{},"Using ",[19,2068,29],{}," for rankings with ties."," Use ",[19,2072,2073],{},"rank(method=...)"," when equal values should share a position.",[2046,2076,2077,2052,2082,2084],{},[2049,2078,2079,2080,36],{},"Forgetting ",[19,2081,293],{},[19,2083,29],{}," starts at 0.",[2046,2086,2087,2090,2091,2093],{},[2049,2088,2089],{},"Resetting the index too early."," Keep the original index if you need to join the numbers back to the unsorted data; ",[19,2092,29],{}," returns values aligned to it.",[38,2095,2097],{"id":2096},"conclusion","Conclusion",[10,2099,2100,2101,2104,2105,2108,2109,2112,2113,2115,2116,342,2118,2120,2121,2123,2124,2127,2128,2131,2132,2134],{},"Sort by the group and the ordering columns, including a tie-breaker, and use ",[19,2102,2103],{},"groupby(...).cumcount() + 1"," for a row number that restarts per group: the pandas version of ",[19,2106,2107],{},"ROW_NUMBER() OVER (PARTITION BY ...)"," and of ",[19,2110,2111],{},"COUNTIF($A$2:A2, A2)",". Use ",[19,2114,577],{}," with ",[19,2117,741],{},[19,2119,748],{}," or ",[19,2122,756],{}," when ties should share positions, a ",[19,2125,2126],{},"shift","-and-",[19,2129,2130],{},"cumsum"," run ID to restart on consecutive runs, and ",[19,2133,1386],{}," to number the groups themselves.",[38,2136,2138],{"id":2137},"frequently-asked-questions","Frequently asked questions",[10,2140,2141,2144],{},[2049,2142,2143],{},"How do I number rows within groups in pandas?","\nSort the DataFrame, then use df.groupby(\"group\").cumcount() + 1. The count restarts at 1 for each group and follows the row order.",[10,2146,2147,2150],{},[2049,2148,2149],{},"What is the pandas equivalent of SQL ROW_NUMBER() OVER (PARTITION BY ...)?","\ngroupby(partition columns).cumcount() + 1 after sorting by the ORDER BY columns. For RANK and DENSE_RANK, group the column and call rank with method=\"min\" or method=\"dense\".",[10,2152,2153,2156],{},[2049,2154,2155],{},"How do I get the latest row per group?","\nSort by the date descending, number rows with cumcount, and keep rows where the number is 1. Alternatively use groupby(...).head(1) after sorting.",[10,2158,2159,2162],{},[2049,2160,2161],{},"What is the Excel formula for a running count per group?","\n=COUNTIF($A$2:A2,A2) in row 2, filled down a sheet sorted by the group column. It counts how many times the current value has appeared so far.",[38,2164,2166],{"id":2165},"related","Related",[2043,2168,2169,2175,2182,2189,2196],{},[2046,2170,2171,2172,2174],{},"Up one level: ",[32,2173,35],{"href":34}," — summarising Excel data by group.",[2046,2176,2177,2181],{},[32,2178,2180],{"href":2179},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fget-the-top-n-rows-per-group-in-pandas\u002F","Get the Top N Rows per Group in pandas"," — the next step after numbering.",[2046,2183,2184,2188],{},[32,2185,2187],{"href":2186},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-cohort-retention-table-from-excel-data\u002F","Build a Cohort Retention Table from Excel Data"," — first orders per customer.",[2046,2190,2191,2195],{},[32,2192,2194],{"href":2193},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-an-rfm-customer-segmentation-from-excel-data\u002F","Build an RFM Customer Segmentation from Excel Data"," — order counts and recency.",[2046,2197,2198,2202],{},[32,2199,2201],{"href":2200},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frunning-totals-and-year-over-year-growth-in-pandas\u002F","Running Totals and Year-Over-Year Growth in pandas"," — cumulative sums per group.",[2204,2205,2206],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":48,"searchDepth":320,"depth":320,"links":2208},[2209,2210,2211,2212,2213,2214,2215,2216,2217,2218,2219,2220],{"id":40,"depth":320,"text":41},{"id":283,"depth":320,"text":284},{"id":568,"depth":320,"text":569},{"id":888,"depth":320,"text":889},{"id":1041,"depth":320,"text":1042},{"id":1377,"depth":320,"text":1378},{"id":1449,"depth":320,"text":1450},{"id":1770,"depth":320,"text":1771},{"id":2040,"depth":320,"text":2041},{"id":2096,"depth":320,"text":2097},{"id":2137,"depth":320,"text":2138},{"id":2165,"depth":320,"text":2166},"2026-09-29","Add a running number that restarts for every customer, region or order — the pandas equivalent of Excel's COUNTIF($A$2:A2,A2) and SQL's ROW_NUMBER() OVER (PARTITION BY ...): cumcount in a chosen order, ranks that handle ties, numbering consecutive runs, numbering the groups themselves, and writing the result to Excel.","md",[2225,2227,2229,2231],{"q":2143,"a":2226},"Sort the DataFrame, then use df.groupby(\"group\").cumcount() + 1. The count restarts at 1 for each group and follows the row order.",{"q":2149,"a":2228},"groupby(partition columns).cumcount() + 1 after sorting by the ORDER BY columns. For RANK and DENSE_RANK, group the column and call rank with method=\"min\" or method=\"dense\".",{"q":2155,"a":2230},"Sort by the date descending, number rows with cumcount, and keep rows where the number is 1. Alternatively use groupby(...).head(1) after sorting.",{"q":2161,"a":2232},"=COUNTIF($A$2:A2,A2) in row 2, filled down a sheet sorted by the group column. It counts how many times the current value has appeared so far.","number rows within each group in pandas",null,{"breadcrumb":2236},[2237,2240,2243],{"name":2238,"item":2239},"Home","\u002F",{"name":2241,"item":2242},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":2244,"item":34},"Creating Pivot Tables from Excel Data with Pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fnumber-rows-within-each-group-in-pandas",{"title":2247,"description":2248},"Number Rows Within Each Group in pandas (cumcount, rank)","Number rows per group in pandas like Excel COUNTIF($A$2:A2,A2) or SQL ROW_NUMBER: groupby cumcount with sorting, rank methods for ties, restart numbering on consecutive runs, ngroup for group IDs, and live Excel formulas.","number-rows-within-each-group-in-pandas","advanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fnumber-rows-within-each-group-in-pandas\u002Findex",[2252,2253],"pivots-and-summaries","formula-equivalents","how-to","fwGBN_FyYe-XQ0JBrTyxUeTdwbSlePympnQCYz6NzLk",[2257,2260],{"title":2180,"path":2258,"stem":2259,"children":-1},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fget-the-top-n-rows-per-group-in-pandas","advanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fget-the-top-n-rows-per-group-in-pandas\u002Findex",{"title":2261,"path":2262,"stem":2263,"children":-1},"Pivot Text Values Instead of Numbers in pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fpivot-text-values-instead-of-numbers-in-pandas","advanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fpivot-text-values-instead-of-numbers-in-pandas\u002Findex",[2265,2267],{"path":2266,"title":2244},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data",{"path":2268,"title":2241},"\u002Fadvanced-data-transformation-and-cleaning",[],[2271,2274,2277,2280,2283,2286],{"path":2272,"title":2273,"kind":2254},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-customer-churn-report-from-subscription-data","Build a Customer Churn Report from Subscription Data",{"path":2275,"title":2276,"kind":2254},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-xlwings-basics\u002Frefresh-pivot-tables-and-queries-in-excel-with-python","Refresh Excel Pivot Tables and Queries with Python",{"path":2278,"title":2279,"kind":2254},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-a-native-excel-pivot-table-with-python","Add a Native Excel Pivot Table with Python",{"path":2281,"title":2282,"kind":2254},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-subtotals-and-grand-totals-to-a-pandas-pivot-table","Add Subtotals and Grand Totals to a pandas Pivot Table",{"path":2284,"title":2285,"kind":2254},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbin-values-into-ranges-with-pandas-cut","Bin Values into Ranges Like Excel FREQUENCY with pd.cut",{"path":2287,"title":2187,"kind":2254},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-cohort-retention-table-from-excel-data",1790972086540]