[{"data":1,"prerenderedAt":1789},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fget-the-top-n-rows-per-group-in-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fget-the-top-n-rows-per-group-in-pandas":1756,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fget-the-top-n-rows-per-group-in-pandas":1765,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fget-the-top-n-rows-per-group-in-pandas":1770,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fget-the-top-n-rows-per-group-in-pandas":1771},{"id":4,"title":5,"body":6,"dateModified":1721,"datePublished":1721,"description":1722,"extension":1723,"faq":1724,"keyword":1733,"kind":1734,"meta":1735,"navigation":374,"path":1744,"seo":1745,"slug":1748,"stem":1749,"topics":1750,"type":1754,"__hash__":1755},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fget-the-top-n-rows-per-group-in-pandas\u002Findex.md","Get the Top N Rows per Group in pandas",{"type":7,"value":8,"toc":1706},"minimark",[9,32,300,305,333,339,343,346,477,490,494,500,595,614,713,717,720,821,824,828,831,941,944,948,951,1104,1114,1176,1180,1187,1359,1374,1378,1381,1542,1550,1554,1609,1613,1627,1631,1641,1645,1651,1657,1663,1669,1673,1702],[10,11,12,13,17,18,21,22,25,26,31],"p",{},"\"The top three products in each region\" is one of the most requested views in sales reporting, and one of the easiest to get subtly wrong. Excel users build it with ",[14,15,16],"code",{},"LARGE"," and ",[14,19,20],{},"INDEX\u002FMATCH",", or a pivot table filtered to the top 10 with the \"Top 10\" value filter per row field. In pandas it is a sort and a ",[14,23,24],{},"groupby().head()"," — but the answer depends on three decisions that the one-liner makes silently: what happens to ties at the cut-off, whether you rank rows or totals, and whether the rest of the data disappears or is shown as \"Other\". This page, part of ",[27,28,30],"a",{"href":29},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002F","Creating Pivot Tables from Excel Data with Pandas",", makes each decision explicit.",[33,34,42,43,42,47,42,51,42,59,42,66,42,75,42,79,42,85,42,89,42,93,42,97,42,101,42,105,42,110,42,112,42,116,42,118,42,121,42,123,42,126,42,129,42,133,42,138,42,142,42,144,42,147,42,149,42,152,42,155,42,159,42,161,42,164,42,166,42,169,42,171,42,174,42,177,42,181,42,183,42,185,42,187,42,190,42,192,42,195,42,198,42,202,42,204,42,207,42,209,42,211,42,215,42,218,42,221,42,225,42,227,42,229,42,231,42,234,42,236,42,239,42,242,42,246,42,248,42,251,42,253,42,255,42,257,42,260,42,263,42,267,42,269,42,271,42,273,42,275,42,277,42,280,42,283,42,287,42,289,42,291,42,293,42,295,42,297],"svg",{"viewBox":35,"role":36,"ariaLabelledBy":37,"xmlns":40,"style":41},"216 0 328 340","img",[38,39],"top-per-group-t","top-per-group-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:328px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[44,45,46],"title",{"id":38},"The top two products in each region",[48,49,50],"desc",{"id":39},"A sales table sorted by region and sales. The top two rows of each region are highlighted: North Widgets 900 and Gadgets 700 (Gizmos is tied on 700 but falls outside head(2)), South Gadgets 650 and Widgets 400, and East Gizmos 500, the only product there.",[52,53],"rect",{"x":54,"y":55,"width":56,"height":57,"fill":58},"216","0","328","340","#ffffff",[60,61,65],"text",{"x":62,"y":63,"style":64},"380.0","31.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","sort_values + groupby(\"region\").head(2)",[52,67],{"x":68,"y":69,"width":70,"height":71,"rx":55,"fill":72,"stroke":73,"style":74},"255.7","48.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[52,76],{"x":77,"y":69,"width":78,"height":71,"rx":55,"fill":72,"stroke":73,"style":74},"287.7","63.6",[60,80,84],{"x":81,"y":82,"style":83},"319.5","65.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[52,86],{"x":87,"y":69,"width":88,"height":71,"rx":55,"fill":72,"stroke":73,"style":74},"351.3","96.7",[60,90,92],{"x":91,"y":82,"style":83},"399.6","B",[52,94],{"x":95,"y":69,"width":96,"height":71,"rx":55,"fill":72,"stroke":73,"style":74},"448.0","56.4",[60,98,100],{"x":99,"y":82,"style":83},"476.1","C",[52,102],{"x":68,"y":103,"width":70,"height":104,"rx":55,"fill":72,"stroke":73,"style":74},"72.0","28.0",[60,106,109],{"x":107,"y":108,"style":83},"271.7","91.0","1",[52,111],{"x":77,"y":103,"width":78,"height":104,"rx":55,"fill":58,"stroke":73,"style":74},[60,113,115],{"x":81,"y":108,"style":114},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","region",[52,117],{"x":87,"y":103,"width":88,"height":104,"rx":55,"fill":58,"stroke":73,"style":74},[60,119,120],{"x":91,"y":108,"style":114},"product",[52,122],{"x":95,"y":103,"width":96,"height":104,"rx":55,"fill":58,"stroke":73,"style":74},[60,124,125],{"x":99,"y":108,"style":114},"sales",[52,127],{"x":68,"y":128,"width":70,"height":104,"rx":55,"fill":72,"stroke":73,"style":74},"100.0",[60,130,132],{"x":107,"y":131,"style":83},"119.0","2",[52,134],{"x":77,"y":128,"width":78,"height":104,"rx":55,"fill":135,"stroke":136,"style":137},"#d9f4f1","var(--teal,#0f9488)","stroke-width:1.5px",[60,139,141],{"x":81,"y":131,"style":140},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","East",[52,143],{"x":87,"y":128,"width":88,"height":104,"rx":55,"fill":135,"stroke":136,"style":137},[60,145,146],{"x":91,"y":131,"style":140},"Gizmos",[52,148],{"x":95,"y":128,"width":96,"height":104,"rx":55,"fill":135,"stroke":136,"style":137},[60,150,151],{"x":99,"y":131,"style":140},"500",[52,153],{"x":68,"y":154,"width":70,"height":104,"rx":55,"fill":72,"stroke":73,"style":74},"128.0",[60,156,158],{"x":107,"y":157,"style":83},"147.0","3",[52,160],{"x":77,"y":154,"width":78,"height":104,"rx":55,"fill":135,"stroke":136,"style":137},[60,162,163],{"x":81,"y":157,"style":140},"North",[52,165],{"x":87,"y":154,"width":88,"height":104,"rx":55,"fill":135,"stroke":136,"style":137},[60,167,168],{"x":91,"y":157,"style":140},"Widgets",[52,170],{"x":95,"y":154,"width":96,"height":104,"rx":55,"fill":135,"stroke":136,"style":137},[60,172,173],{"x":99,"y":157,"style":140},"900",[52,175],{"x":68,"y":176,"width":70,"height":104,"rx":55,"fill":72,"stroke":73,"style":74},"156.0",[60,178,180],{"x":107,"y":179,"style":83},"175.0","4",[52,182],{"x":77,"y":176,"width":78,"height":104,"rx":55,"fill":135,"stroke":136,"style":137},[60,184,163],{"x":81,"y":179,"style":140},[52,186],{"x":87,"y":176,"width":88,"height":104,"rx":55,"fill":135,"stroke":136,"style":137},[60,188,189],{"x":91,"y":179,"style":140},"Gadgets",[52,191],{"x":95,"y":176,"width":96,"height":104,"rx":55,"fill":135,"stroke":136,"style":137},[60,193,194],{"x":99,"y":179,"style":140},"700",[52,196],{"x":68,"y":197,"width":70,"height":104,"rx":55,"fill":72,"stroke":73,"style":74},"184.0",[60,199,201],{"x":107,"y":200,"style":83},"203.0","5",[52,203],{"x":77,"y":197,"width":78,"height":104,"rx":55,"fill":58,"stroke":73,"style":74},[60,205,163],{"x":81,"y":200,"style":206},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle",[52,208],{"x":87,"y":197,"width":88,"height":104,"rx":55,"fill":58,"stroke":73,"style":74},[60,210,146],{"x":91,"y":200,"style":206},[52,212],{"x":95,"y":197,"width":96,"height":104,"rx":55,"fill":213,"stroke":214,"style":137},"#fdefd8","var(--gold,#b4740a)",[60,216,194],{"x":99,"y":200,"style":217},"font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[52,219],{"x":68,"y":220,"width":70,"height":104,"rx":55,"fill":72,"stroke":73,"style":74},"212.0",[60,222,224],{"x":107,"y":223,"style":83},"231.0","6",[52,226],{"x":77,"y":220,"width":78,"height":104,"rx":55,"fill":58,"stroke":73,"style":74},[60,228,163],{"x":81,"y":223,"style":206},[52,230],{"x":87,"y":220,"width":88,"height":104,"rx":55,"fill":58,"stroke":73,"style":74},[60,232,233],{"x":91,"y":223,"style":206},"Doohickeys",[52,235],{"x":95,"y":220,"width":96,"height":104,"rx":55,"fill":58,"stroke":73,"style":74},[60,237,238],{"x":99,"y":223,"style":206},"300",[52,240],{"x":68,"y":241,"width":70,"height":104,"rx":55,"fill":72,"stroke":73,"style":74},"240.0",[60,243,245],{"x":107,"y":244,"style":83},"259.0","7",[52,247],{"x":77,"y":241,"width":78,"height":104,"rx":55,"fill":135,"stroke":136,"style":137},[60,249,250],{"x":81,"y":244,"style":140},"South",[52,252],{"x":87,"y":241,"width":88,"height":104,"rx":55,"fill":135,"stroke":136,"style":137},[60,254,189],{"x":91,"y":244,"style":140},[52,256],{"x":95,"y":241,"width":96,"height":104,"rx":55,"fill":135,"stroke":136,"style":137},[60,258,259],{"x":99,"y":244,"style":140},"650",[52,261],{"x":68,"y":262,"width":70,"height":104,"rx":55,"fill":72,"stroke":73,"style":74},"268.0",[60,264,266],{"x":107,"y":265,"style":83},"287.0","8",[52,268],{"x":77,"y":262,"width":78,"height":104,"rx":55,"fill":135,"stroke":136,"style":137},[60,270,250],{"x":81,"y":265,"style":140},[52,272],{"x":87,"y":262,"width":88,"height":104,"rx":55,"fill":135,"stroke":136,"style":137},[60,274,168],{"x":91,"y":265,"style":140},[52,276],{"x":95,"y":262,"width":96,"height":104,"rx":55,"fill":135,"stroke":136,"style":137},[60,278,279],{"x":99,"y":265,"style":140},"400",[52,281],{"x":68,"y":282,"width":70,"height":104,"rx":55,"fill":72,"stroke":73,"style":74},"296.0",[60,284,286],{"x":107,"y":285,"style":83},"315.0","9",[52,288],{"x":77,"y":282,"width":78,"height":104,"rx":55,"fill":58,"stroke":73,"style":74},[60,290,250],{"x":81,"y":285,"style":206},[52,292],{"x":87,"y":282,"width":88,"height":104,"rx":55,"fill":58,"stroke":73,"style":74},[60,294,168],{"x":91,"y":285,"style":206},[52,296],{"x":95,"y":282,"width":96,"height":104,"rx":55,"fill":58,"stroke":73,"style":74},[60,298,299],{"x":99,"y":285,"style":206},"350",[301,302,304],"h2",{"id":303},"prerequisites","Prerequisites",[306,307,312],"pre",{"className":308,"code":309,"language":310,"meta":311,"style":311},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,313,314],{"__ignoreMap":311},[315,316,319,323,327,330],"span",{"class":317,"line":318},"line",1,[315,320,322],{"class":321},"sMTad","pip",[315,324,326],{"class":325},"srMev"," install",[315,328,329],{"class":325}," pandas",[315,331,332],{"class":325}," openpyxl\n",[10,334,335,338],{},[14,336,337],{},"sales.xlsx"," has ten rows across three regions. North has a tie on 700 between Gadgets and Gizmos; South lists Widgets twice, as two separate orders; East has a single product.",[301,340,342],{"id":341},"step-1-sort-then-take-the-first-n-of-each-group","Step 1 — Sort, then take the first N of each group",[10,344,345],{},"Sort by the group ascending and the value descending, so each group's largest rows come first, then keep the first N rows of every group:",[306,347,351],{"className":348,"code":349,"language":350,"meta":311,"style":311},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nsales = pd.read_excel(\"sales.xlsx\")\ntop2 = (sales.sort_values([\"region\", \"sales\"], ascending=[True, False])\n             .groupby(\"region\")\n             .head(2))\nprint(top2.to_string(index=False))\n","python",[14,352,353,369,376,394,438,448,459],{"__ignoreMap":311},[315,354,355,359,363,366],{"class":317,"line":318},[315,356,358],{"class":357},"s-kum","import",[315,360,362],{"class":361},"skGVy"," pandas ",[315,364,365],{"class":357},"as",[315,367,368],{"class":361}," pd\n",[315,370,372],{"class":317,"line":371},2,[315,373,375],{"emptyLinePlaceholder":374},true,"\n",[315,377,379,382,385,388,391],{"class":317,"line":378},3,[315,380,381],{"class":361},"sales ",[315,383,384],{"class":357},"=",[315,386,387],{"class":361}," pd.read_excel(",[315,389,390],{"class":325},"\"sales.xlsx\"",[315,392,393],{"class":361},")\n",[315,395,397,400,402,405,408,411,414,417,421,423,426,430,432,435],{"class":317,"line":396},4,[315,398,399],{"class":361},"top2 ",[315,401,384],{"class":357},[315,403,404],{"class":361}," (sales.sort_values([",[315,406,407],{"class":325},"\"region\"",[315,409,410],{"class":361},", ",[315,412,413],{"class":325},"\"sales\"",[315,415,416],{"class":361},"], ",[315,418,420],{"class":419},"sa561","ascending",[315,422,384],{"class":357},[315,424,425],{"class":361},"[",[315,427,429],{"class":428},"sP0c6","True",[315,431,410],{"class":361},[315,433,434],{"class":428},"False",[315,436,437],{"class":361},"])\n",[315,439,441,444,446],{"class":317,"line":440},5,[315,442,443],{"class":361},"             .groupby(",[315,445,407],{"class":325},[315,447,393],{"class":361},[315,449,451,454,456],{"class":317,"line":450},6,[315,452,453],{"class":361},"             .head(",[315,455,132],{"class":428},[315,457,458],{"class":361},"))\n",[315,460,462,465,468,471,473,475],{"class":317,"line":461},7,[315,463,464],{"class":428},"print",[315,466,467],{"class":361},"(top2.to_string(",[315,469,470],{"class":419},"index",[315,472,384],{"class":357},[315,474,434],{"class":428},[315,476,458],{"class":361},[10,478,479,482,483,410,486,489],{},[14,480,481],{},"head(2)"," keeps at most two rows per region: East keeps its only row, North keeps Widgets and Gadgets, South keeps Gadgets and one of its Widgets rows. Unlike ",[14,484,485],{},"groupby().nlargest()",[14,487,488],{},"head"," keeps every column and returns an ordinary DataFrame, which makes it the most convenient form for reports. Groups smaller than N simply return all their rows.",[301,491,493],{"id":492},"step-2-notice-what-happened-to-the-tie","Step 2 — Notice what happened to the tie",[10,495,496,497,499],{},"North's Gadgets and Gizmos both sold 700, and ",[14,498,481],{}," kept Gadgets and dropped Gizmos. Nothing in the data justifies that: the sort put Gadgets first because it happened to come first in the file. For a leaderboard, arbitrary tie-breaking is a problem — the same data sorted differently next week produces a different \"top two\". Either break ties deliberately with a second sort key, or include every tied row:",[306,501,503],{"className":348,"code":502,"language":350,"meta":311,"style":311},"sales[\"rank\"] = sales.groupby(\"region\")[\"sales\"].rank(method=\"min\", ascending=False)\nwith_ties = sales[sales[\"rank\"] \u003C= 2].sort_values([\"region\", \"rank\"])\nprint(with_ties.to_string(index=False))\n",[14,504,505,549,580],{"__ignoreMap":311},[315,506,507,510,513,516,518,521,523,526,528,531,534,536,539,541,543,545,547],{"class":317,"line":318},[315,508,509],{"class":361},"sales[",[315,511,512],{"class":325},"\"rank\"",[315,514,515],{"class":361},"] ",[315,517,384],{"class":357},[315,519,520],{"class":361}," sales.groupby(",[315,522,407],{"class":325},[315,524,525],{"class":361},")[",[315,527,413],{"class":325},[315,529,530],{"class":361},"].rank(",[315,532,533],{"class":419},"method",[315,535,384],{"class":357},[315,537,538],{"class":325},"\"min\"",[315,540,410],{"class":361},[315,542,420],{"class":419},[315,544,384],{"class":357},[315,546,434],{"class":428},[315,548,393],{"class":361},[315,550,551,554,556,559,561,563,566,569,572,574,576,578],{"class":317,"line":371},[315,552,553],{"class":361},"with_ties ",[315,555,384],{"class":357},[315,557,558],{"class":361}," sales[sales[",[315,560,512],{"class":325},[315,562,515],{"class":361},[315,564,565],{"class":357},"\u003C=",[315,567,568],{"class":428}," 2",[315,570,571],{"class":361},"].sort_values([",[315,573,407],{"class":325},[315,575,410],{"class":361},[315,577,512],{"class":325},[315,579,437],{"class":361},[315,581,582,584,587,589,591,593],{"class":317,"line":378},[315,583,464],{"class":428},[315,585,586],{"class":361},"(with_ties.to_string(",[315,588,470],{"class":419},[315,590,384],{"class":357},[315,592,434],{"class":428},[315,594,458],{"class":361},[10,596,597,600,601,604,605,608,609,613],{},[14,598,599],{},"rank(method=\"min\")"," gives tied values the same, lowest rank — both 700s are rank 2 — so filtering on ",[14,602,603],{},"rank \u003C= 2"," keeps North's top three rows. This is how Excel's own \"Top 10\" filter behaves, and the same rule as ",[14,606,607],{},"RANK.EQ",". ",[27,610,612],{"href":611},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frank-and-percentile-formulas-in-pandas\u002F","RANK and PERCENTILE Formulas in pandas"," compares the ranking methods.",[33,615,42,621,42,624,42,627,42,631,42,637,42,644,42,650,42,653,42,657,42,661,42,666,42,668,42,673,42,675,42,678,42,681,42,685,42,687,42,690,42,692,42,695,42,698,42,702,42,704,42,708,42,710],{"viewBox":616,"role":36,"ariaLabelledBy":617,"xmlns":40,"style":620},"4 0 752 174",[618,619],"top-ties-t","top-ties-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[44,622,623],{"id":618},"What each method returns for North's tie at 700",[48,625,626],{"id":619},"A comparison of three methods for the top two in North, where Gadgets and Gizmos tie on 700. head(2) after sorting returns Widgets and Gadgets, dropping Gizmos by file order. A second sort key such as product name returns Widgets and whichever product sorts first. rank with method min and rank \u003C= 2 returns Widgets, Gadgets and Gizmos.",[52,628],{"x":180,"y":55,"width":629,"height":630,"fill":58},"752","174",[52,632],{"x":633,"y":634,"width":635,"height":636,"rx":55,"fill":58,"stroke":73,"style":74},"20.0","16.0","253.0","40.0",[52,638],{"x":639,"y":634,"width":640,"height":636,"rx":55,"fill":641,"stroke":642,"style":643},"273.0","233.5","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[60,645,649],{"x":646,"y":647,"style":648},"389.8","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Rows returned",[52,651],{"x":652,"y":634,"width":640,"height":636,"rx":55,"fill":641,"stroke":642,"style":643},"506.5",[60,654,656],{"x":655,"y":647,"style":648},"623.3","Tie decided by",[52,658],{"x":633,"y":659,"width":635,"height":660,"rx":55,"fill":72,"stroke":73,"style":74},"56.0","34.0",[60,662,665],{"x":70,"y":663,"style":664},"78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","head(2) after sorting",[52,667],{"x":639,"y":659,"width":640,"height":660,"rx":55,"fill":58,"stroke":73,"style":74},[60,669,672],{"x":646,"y":670,"style":671},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Widgets, Gadgets",[52,674],{"x":652,"y":659,"width":640,"height":660,"rx":55,"fill":58,"stroke":73,"style":74},[60,676,677],{"x":655,"y":670,"style":671},"file order (arbitrary)",[52,679],{"x":633,"y":680,"width":635,"height":660,"rx":55,"fill":72,"stroke":73,"style":74},"90.0",[60,682,684],{"x":70,"y":683,"style":664},"112.0","head(2), sort by sales then product",[52,686],{"x":639,"y":680,"width":640,"height":660,"rx":55,"fill":58,"stroke":73,"style":74},[60,688,672],{"x":646,"y":689,"style":671},"111.0",[52,691],{"x":652,"y":680,"width":640,"height":660,"rx":55,"fill":58,"stroke":73,"style":74},[60,693,694],{"x":655,"y":689,"style":671},"alphabetical order",[52,696],{"x":633,"y":697,"width":635,"height":660,"rx":55,"fill":72,"stroke":73,"style":74},"124.0",[60,699,701],{"x":70,"y":700,"style":664},"146.0","rank(method=\"min\") \u003C= 2",[52,703],{"x":639,"y":697,"width":640,"height":660,"rx":55,"fill":58,"stroke":73,"style":74},[60,705,707],{"x":646,"y":706,"style":671},"145.0","Widgets, Gadgets, Gizmos",[52,709],{"x":652,"y":697,"width":640,"height":660,"rx":55,"fill":58,"stroke":73,"style":74},[60,711,712],{"x":655,"y":706,"style":671},"ties all kept",[301,714,716],{"id":715},"step-3-break-ties-with-a-second-key-when-exactly-n-are-needed","Step 3 — Break ties with a second key when exactly N are needed",[10,718,719],{},"Sometimes the report must show exactly N rows — a fixed-size table on a dashboard. Then add a second sort key that means something: more units, more recent, alphabetical as a last resort. Stable ordering makes the output reproducible:",[306,721,723],{"className":348,"code":722,"language":350,"meta":311,"style":311},"exact = (sales.sort_values([\"region\", \"sales\", \"product\"], ascending=[True, False, True], kind=\"stable\")\n              .groupby(\"region\")\n              .head(2))\nprint(exact[[\"region\", \"product\", \"sales\"]].to_string(index=False))\n",[14,724,725,775,784,793],{"__ignoreMap":311},[315,726,727,730,732,734,736,738,740,742,745,747,749,751,753,755,757,759,761,763,765,768,770,773],{"class":317,"line":318},[315,728,729],{"class":361},"exact ",[315,731,384],{"class":357},[315,733,404],{"class":361},[315,735,407],{"class":325},[315,737,410],{"class":361},[315,739,413],{"class":325},[315,741,410],{"class":361},[315,743,744],{"class":325},"\"product\"",[315,746,416],{"class":361},[315,748,420],{"class":419},[315,750,384],{"class":357},[315,752,425],{"class":361},[315,754,429],{"class":428},[315,756,410],{"class":361},[315,758,434],{"class":428},[315,760,410],{"class":361},[315,762,429],{"class":428},[315,764,416],{"class":361},[315,766,767],{"class":419},"kind",[315,769,384],{"class":357},[315,771,772],{"class":325},"\"stable\"",[315,774,393],{"class":361},[315,776,777,780,782],{"class":317,"line":371},[315,778,779],{"class":361},"              .groupby(",[315,781,407],{"class":325},[315,783,393],{"class":361},[315,785,786,789,791],{"class":317,"line":378},[315,787,788],{"class":361},"              .head(",[315,790,132],{"class":428},[315,792,458],{"class":361},[315,794,795,797,800,802,804,806,808,810,813,815,817,819],{"class":317,"line":396},[315,796,464],{"class":428},[315,798,799],{"class":361},"(exact[[",[315,801,407],{"class":325},[315,803,410],{"class":361},[315,805,744],{"class":325},[315,807,410],{"class":361},[315,809,413],{"class":325},[315,811,812],{"class":361},"]].to_string(",[315,814,470],{"class":419},[315,816,384],{"class":357},[315,818,434],{"class":428},[315,820,458],{"class":361},[10,822,823],{},"The tie in North is now decided alphabetically — Gadgets before Gizmos — every time the report runs, whatever order the rows arrive in. Say so in a footnote if readers might wonder why one of two equal products was left out.",[301,825,827],{"id":826},"step-4-rank-totals-not-rows-when-items-repeat","Step 4 — Rank totals, not rows, when items repeat",[10,829,830],{},"South lists Widgets twice: 400 and 350, two orders. \"Top products in South\" is a question about products, so the two rows should be combined before ranking. Ranking raw rows put Widgets in the top two with a single 400 order, which understates it and could, with more duplicates, list the same product twice:",[306,832,834],{"className":348,"code":833,"language":350,"meta":311,"style":311},"totals = sales.groupby([\"region\", \"product\"], as_index=False)[\"sales\"].sum()\ntop_products = (totals.sort_values([\"region\", \"sales\", \"product\"], ascending=[True, False, True])\n                      .groupby(\"region\")\n                      .head(2))\nprint(top_products.to_string(index=False))\n",[14,835,836,868,908,917,926],{"__ignoreMap":311},[315,837,838,841,843,846,848,850,852,854,857,859,861,863,865],{"class":317,"line":318},[315,839,840],{"class":361},"totals ",[315,842,384],{"class":357},[315,844,845],{"class":361}," sales.groupby([",[315,847,407],{"class":325},[315,849,410],{"class":361},[315,851,744],{"class":325},[315,853,416],{"class":361},[315,855,856],{"class":419},"as_index",[315,858,384],{"class":357},[315,860,434],{"class":428},[315,862,525],{"class":361},[315,864,413],{"class":325},[315,866,867],{"class":361},"].sum()\n",[315,869,870,873,875,878,880,882,884,886,888,890,892,894,896,898,900,902,904,906],{"class":317,"line":371},[315,871,872],{"class":361},"top_products ",[315,874,384],{"class":357},[315,876,877],{"class":361}," (totals.sort_values([",[315,879,407],{"class":325},[315,881,410],{"class":361},[315,883,413],{"class":325},[315,885,410],{"class":361},[315,887,744],{"class":325},[315,889,416],{"class":361},[315,891,420],{"class":419},[315,893,384],{"class":357},[315,895,425],{"class":361},[315,897,429],{"class":428},[315,899,410],{"class":361},[315,901,434],{"class":428},[315,903,410],{"class":361},[315,905,429],{"class":428},[315,907,437],{"class":361},[315,909,910,913,915],{"class":317,"line":378},[315,911,912],{"class":361},"                      .groupby(",[315,914,407],{"class":325},[315,916,393],{"class":361},[315,918,919,922,924],{"class":317,"line":396},[315,920,921],{"class":361},"                      .head(",[315,923,132],{"class":428},[315,925,458],{"class":361},[315,927,928,930,933,935,937,939],{"class":317,"line":440},[315,929,464],{"class":428},[315,931,932],{"class":361},"(top_products.to_string(",[315,934,470],{"class":419},[315,936,384],{"class":357},[315,938,434],{"class":428},[315,940,458],{"class":361},[10,942,943],{},"After aggregation, South's Widgets total 750 and rank first, above Gadgets at 650 — a different answer from the row-level ranking. Decide which question the report answers — \"largest orders\" or \"best products\" — and aggregate accordingly.",[301,945,947],{"id":946},"step-5-fold-the-rest-into-other","Step 5 — Fold the rest into \"Other\"",[10,949,950],{},"A top-N table drops everything below the cut-off, so its numbers no longer add up to the region's total. Readers comparing it with another report notice immediately. Keep the total intact by relabelling everything outside the top N as \"Other\" and grouping again:",[306,952,954],{"className":348,"code":953,"language":350,"meta":311,"style":311},"totals[\"rank\"] = totals.groupby(\"region\")[\"sales\"].rank(method=\"first\", ascending=False)\ntotals[\"label\"] = totals[\"product\"].where(totals[\"rank\"] \u003C= 2, \"Other\")\nsummary = totals.groupby([\"region\", \"label\"], as_index=False)[\"sales\"].sum()\nprint(summary.to_string(index=False))\nprint(summary.groupby(\"region\")[\"sales\"].sum().to_dict() == sales.groupby(\"region\")[\"sales\"].sum().to_dict())\n",[14,955,956,995,1029,1059,1074],{"__ignoreMap":311},[315,957,958,961,963,965,967,970,972,974,976,978,980,982,985,987,989,991,993],{"class":317,"line":318},[315,959,960],{"class":361},"totals[",[315,962,512],{"class":325},[315,964,515],{"class":361},[315,966,384],{"class":357},[315,968,969],{"class":361}," totals.groupby(",[315,971,407],{"class":325},[315,973,525],{"class":361},[315,975,413],{"class":325},[315,977,530],{"class":361},[315,979,533],{"class":419},[315,981,384],{"class":357},[315,983,984],{"class":325},"\"first\"",[315,986,410],{"class":361},[315,988,420],{"class":419},[315,990,384],{"class":357},[315,992,434],{"class":428},[315,994,393],{"class":361},[315,996,997,999,1002,1004,1006,1009,1011,1014,1016,1018,1020,1022,1024,1027],{"class":317,"line":371},[315,998,960],{"class":361},[315,1000,1001],{"class":325},"\"label\"",[315,1003,515],{"class":361},[315,1005,384],{"class":357},[315,1007,1008],{"class":361}," totals[",[315,1010,744],{"class":325},[315,1012,1013],{"class":361},"].where(totals[",[315,1015,512],{"class":325},[315,1017,515],{"class":361},[315,1019,565],{"class":357},[315,1021,568],{"class":428},[315,1023,410],{"class":361},[315,1025,1026],{"class":325},"\"Other\"",[315,1028,393],{"class":361},[315,1030,1031,1034,1036,1039,1041,1043,1045,1047,1049,1051,1053,1055,1057],{"class":317,"line":378},[315,1032,1033],{"class":361},"summary ",[315,1035,384],{"class":357},[315,1037,1038],{"class":361}," totals.groupby([",[315,1040,407],{"class":325},[315,1042,410],{"class":361},[315,1044,1001],{"class":325},[315,1046,416],{"class":361},[315,1048,856],{"class":419},[315,1050,384],{"class":357},[315,1052,434],{"class":428},[315,1054,525],{"class":361},[315,1056,413],{"class":325},[315,1058,867],{"class":361},[315,1060,1061,1063,1066,1068,1070,1072],{"class":317,"line":396},[315,1062,464],{"class":428},[315,1064,1065],{"class":361},"(summary.to_string(",[315,1067,470],{"class":419},[315,1069,384],{"class":357},[315,1071,434],{"class":428},[315,1073,458],{"class":361},[315,1075,1076,1078,1081,1083,1085,1087,1090,1093,1095,1097,1099,1101],{"class":317,"line":440},[315,1077,464],{"class":428},[315,1079,1080],{"class":361},"(summary.groupby(",[315,1082,407],{"class":325},[315,1084,525],{"class":361},[315,1086,413],{"class":325},[315,1088,1089],{"class":361},"].sum().to_dict() ",[315,1091,1092],{"class":357},"==",[315,1094,520],{"class":361},[315,1096,407],{"class":325},[315,1098,525],{"class":361},[315,1100,413],{"class":325},[315,1102,1103],{"class":361},"].sum().to_dict())\n",[10,1105,1106,1107,1110,1111,1113],{},"North shows its top two products and an \"Other\" line holding the remaining 1,100; every region's lines still sum to the region's total, and the final check confirms it. ",[14,1108,1109],{},"method=\"first\""," gives each product a distinct rank so that exactly two are named per region; use ",[14,1112,538],{}," if tied products should both be named.",[33,1115,42,1121,42,1124,42,1127,42,1131,42,1134,42,1137,42,1142,42,1146,42,1150,42,1153,42,1156,42,1159,42,1162,42,1164,42,1168,42,1171],{"viewBox":1116,"role":36,"ariaLabelledBy":1117,"xmlns":40,"style":1120},"4 0 744 172",[1118,1119],"top-other-t","top-other-d","width:100%;max-width:744px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[44,1122,1123],{"id":1118},"North's sales as top two plus Other",[48,1125,1126],{"id":1119},"Bars for North after folding the rest into Other: Widgets 900, Gadgets 700, and Other 1,100 made up of Gizmos 700, Doohickeys 300 and Whatsits 100. The three bars add to North's total of 2,700.",[52,1128],{"x":180,"y":55,"width":1129,"height":1130,"fill":58},"744","172",[60,1132,1133],{"x":62,"y":63,"style":64},"North — total 2,700",[52,1135],{"x":633,"y":69,"width":1136,"height":104,"rx":245,"fill":72,"stroke":73,"style":74},"146.8",[60,1138,168],{"x":1139,"y":1140,"style":1141},"93.4","66.5","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle",[52,1143],{"x":1144,"y":69,"width":1145,"height":104,"rx":245,"fill":135,"stroke":136,"style":137},"176.8","424.9",[60,1147,173],{"x":1148,"y":1140,"style":1149},"609.7","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start",[52,1151],{"x":633,"y":1152,"width":1136,"height":104,"rx":245,"fill":72,"stroke":73,"style":74},"88.0",[60,1154,189],{"x":1139,"y":1155,"style":1141},"106.5",[52,1157],{"x":1144,"y":1152,"width":1158,"height":104,"rx":245,"fill":135,"stroke":136,"style":137},"330.5",[60,1160,194],{"x":1161,"y":1155,"style":1149},"515.3",[52,1163],{"x":633,"y":154,"width":1136,"height":104,"rx":245,"fill":72,"stroke":73,"style":74},[60,1165,1167],{"x":1139,"y":1166,"style":1141},"146.5","Other (3 products)",[52,1169],{"x":1144,"y":154,"width":1170,"height":104,"rx":245,"fill":72,"stroke":73,"style":137},"519.3",[60,1172,1175],{"x":1173,"y":1166,"style":1174},"704.2","font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:start","1100",[301,1177,1179],{"id":1178},"lay-the-leaders-out-side-by-side","Lay the leaders out side by side",[10,1181,1182,1183,1186],{},"Dashboards often show the top N as a compact grid — one row per region, one column per rank — rather than a long list. Number the rows within each group with ",[14,1184,1185],{},"cumcount"," and pivot:",[306,1188,1190],{"className":348,"code":1189,"language":350,"meta":311,"style":311},"board = top_products.assign(position=top_products.groupby(\"region\").cumcount() + 1)\nboard[\"entry\"] = board[\"product\"] + \" (\" + board[\"sales\"].astype(str) + \")\"\nleaderboard = board.pivot(index=\"region\", columns=\"position\", values=\"entry\")\nleaderboard.columns = [f\"#{c}\" for c in leaderboard.columns]\nprint(leaderboard.fillna(\"\").to_string())\n",[14,1191,1192,1223,1268,1305,1346],{"__ignoreMap":311},[315,1193,1194,1197,1199,1202,1205,1207,1210,1212,1215,1218,1221],{"class":317,"line":318},[315,1195,1196],{"class":361},"board ",[315,1198,384],{"class":357},[315,1200,1201],{"class":361}," top_products.assign(",[315,1203,1204],{"class":419},"position",[315,1206,384],{"class":357},[315,1208,1209],{"class":361},"top_products.groupby(",[315,1211,407],{"class":325},[315,1213,1214],{"class":361},").cumcount() ",[315,1216,1217],{"class":357},"+",[315,1219,1220],{"class":428}," 1",[315,1222,393],{"class":361},[315,1224,1225,1228,1231,1233,1235,1238,1240,1242,1244,1247,1250,1252,1254,1257,1260,1263,1265],{"class":317,"line":371},[315,1226,1227],{"class":361},"board[",[315,1229,1230],{"class":325},"\"entry\"",[315,1232,515],{"class":361},[315,1234,384],{"class":357},[315,1236,1237],{"class":361}," board[",[315,1239,744],{"class":325},[315,1241,515],{"class":361},[315,1243,1217],{"class":357},[315,1245,1246],{"class":325}," \" (\"",[315,1248,1249],{"class":357}," +",[315,1251,1237],{"class":361},[315,1253,413],{"class":325},[315,1255,1256],{"class":361},"].astype(",[315,1258,1259],{"class":428},"str",[315,1261,1262],{"class":361},") ",[315,1264,1217],{"class":357},[315,1266,1267],{"class":325}," \")\"\n",[315,1269,1270,1273,1275,1278,1280,1282,1284,1286,1289,1291,1294,1296,1299,1301,1303],{"class":317,"line":378},[315,1271,1272],{"class":361},"leaderboard ",[315,1274,384],{"class":357},[315,1276,1277],{"class":361}," board.pivot(",[315,1279,470],{"class":419},[315,1281,384],{"class":357},[315,1283,407],{"class":325},[315,1285,410],{"class":361},[315,1287,1288],{"class":419},"columns",[315,1290,384],{"class":357},[315,1292,1293],{"class":325},"\"position\"",[315,1295,410],{"class":361},[315,1297,1298],{"class":419},"values",[315,1300,384],{"class":357},[315,1302,1230],{"class":325},[315,1304,393],{"class":361},[315,1306,1307,1310,1312,1315,1318,1321,1325,1328,1331,1334,1337,1340,1343],{"class":317,"line":396},[315,1308,1309],{"class":361},"leaderboard.columns ",[315,1311,384],{"class":357},[315,1313,1314],{"class":361}," [",[315,1316,1317],{"class":357},"f",[315,1319,1320],{"class":325},"\"#",[315,1322,1324],{"class":1323},"sSjpA","{",[315,1326,1327],{"class":361},"c",[315,1329,1330],{"class":1323},"}",[315,1332,1333],{"class":325},"\"",[315,1335,1336],{"class":357}," for",[315,1338,1339],{"class":361}," c ",[315,1341,1342],{"class":357},"in",[315,1344,1345],{"class":361}," leaderboard.columns]\n",[315,1347,1348,1350,1353,1356],{"class":317,"line":440},[315,1349,464],{"class":428},[315,1351,1352],{"class":361},"(leaderboard.fillna(",[315,1354,1355],{"class":325},"\"\"",[315,1357,1358],{"class":361},").to_string())\n",[10,1360,1361,1362,1365,1366,1369,1370,1373],{},"East has only one product, so its ",[14,1363,1364],{},"#2"," cell is empty — filled with an empty string for display rather than ",[14,1367,1368],{},"NaN",". Because ",[14,1371,1372],{},"top_products"," came from the tie-broken sort in Step 4, the columns are stable from run to run. The grid fits naturally at the top of a summary sheet, with the detailed table further down.",[301,1375,1377],{"id":1376},"step-6-write-one-sheet-with-a-block-per-group","Step 6 — Write one sheet with a block per group",[10,1379,1380],{},"A report usually wants the top N for every group in one place. Write the summary with a blank row between regions, which reads better than one long table:",[306,1382,1384],{"className":348,"code":1383,"language":350,"meta":311,"style":311},"from openpyxl import Workbook\n\nwb = Workbook()\nws = wb.active\nws.title = \"Top products\"\nws.append([\"Region\", \"Product\", \"Sales\"])\nfor region, block in summary.groupby(\"region\"):\n    for row in block.sort_values(\"sales\", ascending=False).itertuples(index=False):\n        ws.append([row.region, row.label, row.sales])\n    ws.append([])\nwb.save(\"top_products.xlsx\")\nprint(ws.max_row, \"rows written\")\n",[14,1385,1386,1399,1403,1413,1423,1433,1453,1471,1506,1512,1518,1529],{"__ignoreMap":311},[315,1387,1388,1391,1394,1396],{"class":317,"line":318},[315,1389,1390],{"class":357},"from",[315,1392,1393],{"class":361}," openpyxl ",[315,1395,358],{"class":357},[315,1397,1398],{"class":361}," Workbook\n",[315,1400,1401],{"class":317,"line":371},[315,1402,375],{"emptyLinePlaceholder":374},[315,1404,1405,1408,1410],{"class":317,"line":378},[315,1406,1407],{"class":361},"wb ",[315,1409,384],{"class":357},[315,1411,1412],{"class":361}," Workbook()\n",[315,1414,1415,1418,1420],{"class":317,"line":396},[315,1416,1417],{"class":361},"ws ",[315,1419,384],{"class":357},[315,1421,1422],{"class":361}," wb.active\n",[315,1424,1425,1428,1430],{"class":317,"line":440},[315,1426,1427],{"class":361},"ws.title ",[315,1429,384],{"class":357},[315,1431,1432],{"class":325}," \"Top products\"\n",[315,1434,1435,1438,1441,1443,1446,1448,1451],{"class":317,"line":450},[315,1436,1437],{"class":361},"ws.append([",[315,1439,1440],{"class":325},"\"Region\"",[315,1442,410],{"class":361},[315,1444,1445],{"class":325},"\"Product\"",[315,1447,410],{"class":361},[315,1449,1450],{"class":325},"\"Sales\"",[315,1452,437],{"class":361},[315,1454,1455,1458,1461,1463,1466,1468],{"class":317,"line":461},[315,1456,1457],{"class":357},"for",[315,1459,1460],{"class":361}," region, block ",[315,1462,1342],{"class":357},[315,1464,1465],{"class":361}," summary.groupby(",[315,1467,407],{"class":325},[315,1469,1470],{"class":361},"):\n",[315,1472,1474,1477,1480,1482,1485,1487,1489,1491,1493,1495,1498,1500,1502,1504],{"class":317,"line":1473},8,[315,1475,1476],{"class":357},"    for",[315,1478,1479],{"class":361}," row ",[315,1481,1342],{"class":357},[315,1483,1484],{"class":361}," block.sort_values(",[315,1486,413],{"class":325},[315,1488,410],{"class":361},[315,1490,420],{"class":419},[315,1492,384],{"class":357},[315,1494,434],{"class":428},[315,1496,1497],{"class":361},").itertuples(",[315,1499,470],{"class":419},[315,1501,384],{"class":357},[315,1503,434],{"class":428},[315,1505,1470],{"class":361},[315,1507,1509],{"class":317,"line":1508},9,[315,1510,1511],{"class":361},"        ws.append([row.region, row.label, row.sales])\n",[315,1513,1515],{"class":317,"line":1514},10,[315,1516,1517],{"class":361},"    ws.append([])\n",[315,1519,1521,1524,1527],{"class":317,"line":1520},11,[315,1522,1523],{"class":361},"wb.save(",[315,1525,1526],{"class":325},"\"top_products.xlsx\"",[315,1528,393],{"class":361},[315,1530,1532,1534,1537,1540],{"class":317,"line":1531},12,[315,1533,464],{"class":428},[315,1535,1536],{"class":361},"(ws.max_row, ",[315,1538,1539],{"class":325},"\"rows written\"",[315,1541,393],{"class":361},[10,1543,1544,1545,1549],{},"Sorting \"Other\" to its natural position by value keeps the block readable; if \"Other\" should always come last, sort by a key that puts it there. ",[27,1546,1548],{"href":1547},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-a-native-excel-pivot-table-with-python\u002F","Add a Native Excel Pivot Table with Python"," is the alternative when readers want to change N themselves in Excel.",[301,1551,1553],{"id":1552},"common-pitfalls","Common pitfalls",[1555,1556,1557,1572,1581,1587,1593,1599],"ul",{},[1558,1559,1560,1567,1568,1571],"li",{},[1561,1562,1563,1564,1566],"strong",{},"Forgetting to sort before ",[14,1565,488],{},"."," ",[14,1569,1570],{},"groupby().head(n)"," returns the first n rows in the current order, not the largest.",[1558,1573,1574,1577,1578,1580],{},[1561,1575,1576],{},"Silent tie-breaking."," Include ties with ",[14,1579,599],{},", or break them with a meaningful second key.",[1558,1582,1583,1586],{},[1561,1584,1585],{},"Ranking rows when items repeat."," Aggregate first when the question is about products, customers or reps.",[1558,1588,1589,1592],{},[1561,1590,1591],{},"Top-N tables that do not add up."," Add an \"Other\" line so totals reconcile.",[1558,1594,1595,1598],{},[1561,1596,1597],{},"Ranking before filtering."," If the report covers one quarter, filter to that quarter first; ranking the whole year and then filtering keeps products that were top for the year but not in the period.",[1558,1600,1601,1604,1605,1608],{},[1561,1602,1603],{},"Missing group labels."," Rows with a blank region are dropped by ",[14,1606,1607],{},"groupby","; label them first or they never appear in any top list.",[301,1610,1612],{"id":1611},"performance-notes","Performance notes",[10,1614,1615,1616,1618,1619,1622,1623,1626],{},"Sorting once and taking ",[14,1617,488],{}," per group is fast for millions of rows. ",[14,1620,1621],{},"groupby().rank()"," is also vectorised. ",[14,1624,1625],{},"groupby().nlargest(n)"," works on a single Series and returns a MultiIndex that needs joining back to the other columns — convenient for quick checks, less so for reports.",[301,1628,1630],{"id":1629},"conclusion","Conclusion",[10,1632,1633,1634,1636,1637,1640],{},"Sort by group and value, then ",[14,1635,1570],{}," for the top rows per group. Decide ties deliberately — ",[14,1638,1639],{},"rank(method=\"min\") \u003C= n"," to include them, a second sort key to break them — aggregate first when items repeat so you rank totals rather than rows, and fold the rest into an \"Other\" line so the numbers still add up to each group's total.",[301,1642,1644],{"id":1643},"frequently-asked-questions","Frequently asked questions",[10,1646,1647,1650],{},[1561,1648,1649],{},"What is the simplest way to get the top 3 rows per group?","\nSort by the group, then by the value in descending order, and take the first three rows of each group with groupby(\"region\").head(3). The sort is what makes the first rows the largest.",[10,1652,1653,1656],{},[1561,1654,1655],{},"How are ties handled?","\nhead(n) returns exactly n rows, so a tie at the cut-off is broken by sort order, which can be arbitrary. Use rank(method=\"min\") and keep rows with rank \u003C= n to include every tied row, or add a second sort column to break ties deliberately.",[10,1658,1659,1662],{},[1561,1660,1661],{},"Should I rank rows or totals?","\nRank whatever the question is about. If a product appears on several rows per region, \"top products\" means the top summed totals, so aggregate with groupby().sum() first and rank the result.",[10,1664,1665,1668],{},[1561,1666,1667],{},"How do I show the rest as \"Other\"?","\nMark the top N rows, replace the label of every other row with \"Other\", and group again. The totals then still add up to the full amount, which a plain top-N list does not.",[301,1670,1672],{"id":1671},"related","Related",[1555,1674,1675,1681,1688,1695],{},[1558,1676,1677,1678,1680],{},"Up one level: ",[27,1679,30],{"href":29}," — pivots, subtotals and exports.",[1558,1682,1683,1687],{},[27,1684,1686],{"href":1685},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-top-and-bottom-values-with-openpyxl\u002F","Highlight Top and Bottom Values in Excel with openpyxl"," — marking the top rows in the sheet itself.",[1558,1689,1690,1694],{},[27,1691,1693],{"href":1692},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Frun-a-pareto-analysis-on-excel-data-with-pandas\u002F","Run a Pareto Analysis on Excel Data with pandas"," — how many items make up most of the total.",[1558,1696,1697,1701],{},[27,1698,1700],{"href":1699},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcalculate-percent-of-total-per-group-in-pandas\u002F","Calculate Percent of Total per Group in pandas"," — the share each top item represents.",[1703,1704,1705],"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":311,"searchDepth":371,"depth":371,"links":1707},[1708,1709,1710,1711,1712,1713,1714,1715,1716,1717,1718,1719,1720],{"id":303,"depth":371,"text":304},{"id":341,"depth":371,"text":342},{"id":492,"depth":371,"text":493},{"id":715,"depth":371,"text":716},{"id":826,"depth":371,"text":827},{"id":946,"depth":371,"text":947},{"id":1178,"depth":371,"text":1179},{"id":1376,"depth":371,"text":1377},{"id":1552,"depth":371,"text":1553},{"id":1611,"depth":371,"text":1612},{"id":1629,"depth":371,"text":1630},{"id":1643,"depth":371,"text":1644},{"id":1671,"depth":371,"text":1672},"2026-09-25","List the three best-selling products in each region, the largest invoice per customer, or the top five reps per quarter. Use sort_values with groupby().head(), decide what happens to ties with rank, rank aggregated totals rather than raw rows, and fold everything else into an \"Other\" line.","md",[1725,1727,1729,1731],{"q":1649,"a":1726},"Sort by the group, then by the value in descending order, and take the first three rows of each group with groupby(\"region\").head(3). The sort is what makes the first rows the largest.",{"q":1655,"a":1728},"head(n) returns exactly n rows, so a tie at the cut-off is broken by sort order, which can be arbitrary. Use rank(method=\"min\") and keep rows with rank \u003C= n to include every tied row, or add a second sort column to break ties deliberately.",{"q":1661,"a":1730},"Rank whatever the question is about. If a product appears on several rows per region, \"top products\" means the top summed totals, so aggregate with groupby().sum() first and rank the result.",{"q":1667,"a":1732},"Mark the top N rows, replace the label of every other row with \"Other\", and group again. The totals then still add up to the full amount, which a plain top-N list does not.","get the top n rows per group in pandas",null,{"breadcrumb":1736},[1737,1740,1743],{"name":1738,"item":1739},"Home","\u002F",{"name":1741,"item":1742},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":30,"item":29},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fget-the-top-n-rows-per-group-in-pandas",{"title":1746,"description":1747},"Top N per Group in pandas (groupby head, nlargest, rank with ties)","Select the top N rows within each group in pandas: sort_values plus groupby head, groupby nlargest, rank with min or dense for ties, top N of summed totals, and an Other bucket for the rest.","get-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",[1751,1752,1753],"pivots-and-summaries","pandas","formula-equivalents","how-to","VEh6e6kK7_Ct6sH84L334ewuwiqPeR1rZQyrvffYHLs",[1757,1761],{"title":1758,"path":1759,"stem":1760,"children":-1},"Export a Pandas Pivot Table to Excel (Formatted)","\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fexport-pandas-pivot-table-to-excel-formatted","advanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fexport-pandas-pivot-table-to-excel-formatted\u002Findex",{"title":1762,"path":1763,"stem":1764,"children":-1},"Number Rows Within Each Group in pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fnumber-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",[1766,1768],{"path":1767,"title":30},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data",{"path":1769,"title":1741},"\u002Fadvanced-data-transformation-and-cleaning",[],[1772,1775,1778,1780,1783,1786],{"path":1773,"title":1774,"kind":1754},"\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":1776,"title":1777,"kind":1754},"\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":1779,"title":1548,"kind":1754},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-a-native-excel-pivot-table-with-python",{"path":1781,"title":1782,"kind":1754},"\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":1784,"title":1785,"kind":1754},"\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":1787,"title":1788,"kind":1754},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-cohort-retention-table-from-excel-data","Build a Cohort Retention Table from Excel Data",1790972086511]