[{"data":1,"prerenderedAt":1984},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frank-and-percentile-formulas-in-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frank-and-percentile-formulas-in-pandas":1975},{"id":4,"title":5,"body":6,"dateModified":1948,"datePublished":1948,"description":1949,"extension":1950,"faq":1951,"meta":1960,"navigation":195,"path":1968,"seo":1969,"slug":1971,"stem":1972,"type":1973,"__hash__":1974},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frank-and-percentile-formulas-in-pandas\u002Findex.md","RANK and PERCENTILE Formulas in pandas",{"type":7,"value":8,"toc":1933},"minimark",[9,19,134,139,167,328,331,335,514,529,535,539,618,621,706,713,761,768,772,862,876,880,1063,1073,1077,1080,1263,1266,1270,1273,1372,1383,1386,1390,1396,1527,1530,1582,1589,1593,1706,1710,1795,1798,1803,1832,1835,1839,1854,1858,1865,1875,1881,1887,1891,1929],[10,11,12,13,18],"p",{},"Ranking is where two implementations of the same report most often disagree, because the interesting\npart is not the ordering but what happens to ties — and Excel has three ranking functions with\ndifferent answers. pandas puts the choice in one argument, which makes the decision explicit rather\nthan a consequence of which formula somebody reached for. This guide is part of\n",[14,15,17],"a",{"href":16},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002F","Excel Formula Equivalents in pandas",".",[20,21,29,30,29,34,29,38,29,45,29,55,29,62,29,69,29,74,29,78,29,82,29,87,29,92,29,96,29,99,29,102,29,105,29,110,29,115,29,119,29,122,29,125,29,128],"svg",{"viewBox":22,"role":23,"ariaLabelledBy":24,"xmlns":27,"style":28},"0 0 760 209","img",[25,26],"rk-ties-t","rk-ties-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:760px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[31,32,33],"title",{"id":25},"Three answers to the same tie",[35,36,37],"desc",{"id":26},"With two values tied for first, the minimum method gives both rank one and skips rank two, the average method gives both one and a half, and dense ranking gives both rank one and continues at two.",[39,40],"rect",{"x":41,"y":41,"width":42,"height":43,"fill":44},"0","760","209","#ffffff",[39,46],{"x":47,"y":48,"width":49,"height":50,"rx":51,"fill":52,"stroke":53,"style":54},"20.0","26","226.7","141","14","#d9f4f1","var(--teal,#0f9488)","stroke-width:2px",[56,57,61],"text",{"x":58,"y":59,"style":60},"133.35","52","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","method='min'",[63,64],"line",{"x1":65,"y1":66,"x2":67,"y2":66,"stroke":53,"style":68},"36.0","62","230.7","stroke-width:1px",[56,70,73],{"x":58,"y":71,"style":72},"84","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","matches RANK.EQ",[56,75,77],{"x":58,"y":76,"style":72},"107","1, 1, 3",[56,79,81],{"x":58,"y":80,"style":72},"130","next rank skipped",[39,83],{"x":84,"y":48,"width":49,"height":50,"rx":51,"fill":85,"stroke":86,"style":54},"266.7","#fdefd8","var(--gold,#b4740a)",[56,88,91],{"x":89,"y":59,"style":90},"380.04999999999995","font-size:13px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","method='average'",[63,93],{"x1":94,"y1":66,"x2":95,"y2":66,"stroke":86,"style":68},"282.7","477.4",[56,97,98],{"x":89,"y":71,"style":72},"matches RANK.AVG",[56,100,101],{"x":89,"y":76,"style":72},"1.5, 1.5, 3",[56,103,104],{"x":89,"y":80,"style":72},"pandas default",[39,106],{"x":107,"y":48,"width":49,"height":50,"rx":51,"fill":108,"stroke":109,"style":54},"513.4","#f0f4ff","var(--brand,#5b5cf0)",[56,111,114],{"x":112,"y":59,"style":113},"626.75","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","method='dense'",[63,116],{"x1":117,"y1":66,"x2":118,"y2":66,"stroke":109,"style":68},"529.4","724.0999999999999",[56,120,121],{"x":112,"y":71,"style":72},"no formula equivalent",[56,123,124],{"x":112,"y":76,"style":72},"1, 1, 2",[56,126,127],{"x":112,"y":80,"style":72},"no gaps",[56,129,133],{"x":130,"y":131,"style":132},"380.0","195","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","the default is not the one the formula means",[135,136,138],"h2",{"id":137},"prerequisites","Prerequisites",[140,141,146],"pre",{"className":142,"code":143,"language":144,"meta":145,"style":145},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[147,148,149],"code",{"__ignoreMap":145},[150,151,153,157,161,164],"span",{"class":63,"line":152},1,[150,154,156],{"class":155},"sMTad","pip",[150,158,160],{"class":159},"srMev"," install",[150,162,163],{"class":159}," pandas",[150,165,166],{"class":159}," openpyxl\n",[140,168,172],{"className":169,"code":170,"language":171,"meta":145,"style":145},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nreps = pd.DataFrame({\n    \"Rep\": [\"Ana\", \"Ben\", \"Cara\", \"Dev\", \"Eve\", \"Fin\"],\n    \"Region\": [\"North\", \"South\", \"North\", \"West\", \"South\", \"North\"],\n    \"Revenue\": [24500.0, 15320.0, 24500.0, 9800.0, 31200.0, 15320.0],\n})\n","python",[147,173,174,190,197,209,250,285,322],{"__ignoreMap":145},[150,175,176,180,184,187],{"class":63,"line":152},[150,177,179],{"class":178},"s-kum","import",[150,181,183],{"class":182},"skGVy"," pandas ",[150,185,186],{"class":178},"as",[150,188,189],{"class":182}," pd\n",[150,191,193],{"class":63,"line":192},2,[150,194,196],{"emptyLinePlaceholder":195},true,"\n",[150,198,200,203,206],{"class":63,"line":199},3,[150,201,202],{"class":182},"reps ",[150,204,205],{"class":178},"=",[150,207,208],{"class":182}," pd.DataFrame({\n",[150,210,212,215,218,221,224,227,229,232,234,237,239,242,244,247],{"class":63,"line":211},4,[150,213,214],{"class":159},"    \"Rep\"",[150,216,217],{"class":182},": [",[150,219,220],{"class":159},"\"Ana\"",[150,222,223],{"class":182},", ",[150,225,226],{"class":159},"\"Ben\"",[150,228,223],{"class":182},[150,230,231],{"class":159},"\"Cara\"",[150,233,223],{"class":182},[150,235,236],{"class":159},"\"Dev\"",[150,238,223],{"class":182},[150,240,241],{"class":159},"\"Eve\"",[150,243,223],{"class":182},[150,245,246],{"class":159},"\"Fin\"",[150,248,249],{"class":182},"],\n",[150,251,253,256,258,261,263,266,268,270,272,275,277,279,281,283],{"class":63,"line":252},5,[150,254,255],{"class":159},"    \"Region\"",[150,257,217],{"class":182},[150,259,260],{"class":159},"\"North\"",[150,262,223],{"class":182},[150,264,265],{"class":159},"\"South\"",[150,267,223],{"class":182},[150,269,260],{"class":159},[150,271,223],{"class":182},[150,273,274],{"class":159},"\"West\"",[150,276,223],{"class":182},[150,278,265],{"class":159},[150,280,223],{"class":182},[150,282,260],{"class":159},[150,284,249],{"class":182},[150,286,288,291,293,297,299,302,304,306,308,311,313,316,318,320],{"class":63,"line":287},6,[150,289,290],{"class":159},"    \"Revenue\"",[150,292,217],{"class":182},[150,294,296],{"class":295},"sP0c6","24500.0",[150,298,223],{"class":182},[150,300,301],{"class":295},"15320.0",[150,303,223],{"class":182},[150,305,296],{"class":295},[150,307,223],{"class":182},[150,309,310],{"class":295},"9800.0",[150,312,223],{"class":182},[150,314,315],{"class":295},"31200.0",[150,317,223],{"class":182},[150,319,301],{"class":295},[150,321,249],{"class":182},[150,323,325],{"class":63,"line":324},7,[150,326,327],{"class":182},"})\n",[10,329,330],{},"Two pairs of tied values — Ana with Cara, and Ben with Fin — which is exactly what makes a ranking\ninteresting.",[135,332,334],{"id":333},"rank-and-choosing-what-a-tie-means","RANK, and choosing what a tie means",[140,336,338],{"className":169,"code":337,"language":171,"meta":145,"style":145},"# =RANK(C2, C:C) \u002F =RANK.EQ — ties share the lowest rank, the next is skipped\nreps[\"Rank_EQ\"] = reps[\"Revenue\"].rank(method=\"min\", ascending=False).astype(int)\n\n# =RANK.AVG — ties share the average of the ranks they span\nreps[\"Rank_AVG\"] = reps[\"Revenue\"].rank(method=\"average\", ascending=False)\n\n# Dense: no gaps after a tie — no Excel equivalent without a helper column\nreps[\"Rank_Dense\"] = reps[\"Revenue\"].rank(method=\"dense\", ascending=False).astype(int)\n\nprint(reps.sort_values(\"Revenue\", ascending=False))\n",[147,339,340,346,396,400,405,439,443,448,487,492],{"__ignoreMap":145},[150,341,342],{"class":63,"line":152},[150,343,345],{"class":344},"s-wDw","# =RANK(C2, C:C) \u002F =RANK.EQ — ties share the lowest rank, the next is skipped\n",[150,347,348,351,354,357,359,362,365,368,372,374,377,379,382,384,387,390,393],{"class":63,"line":192},[150,349,350],{"class":182},"reps[",[150,352,353],{"class":159},"\"Rank_EQ\"",[150,355,356],{"class":182},"] ",[150,358,205],{"class":178},[150,360,361],{"class":182}," reps[",[150,363,364],{"class":159},"\"Revenue\"",[150,366,367],{"class":182},"].rank(",[150,369,371],{"class":370},"sa561","method",[150,373,205],{"class":178},[150,375,376],{"class":159},"\"min\"",[150,378,223],{"class":182},[150,380,381],{"class":370},"ascending",[150,383,205],{"class":178},[150,385,386],{"class":295},"False",[150,388,389],{"class":182},").astype(",[150,391,392],{"class":295},"int",[150,394,395],{"class":182},")\n",[150,397,398],{"class":63,"line":199},[150,399,196],{"emptyLinePlaceholder":195},[150,401,402],{"class":63,"line":211},[150,403,404],{"class":344},"# =RANK.AVG — ties share the average of the ranks they span\n",[150,406,407,409,412,414,416,418,420,422,424,426,429,431,433,435,437],{"class":63,"line":252},[150,408,350],{"class":182},[150,410,411],{"class":159},"\"Rank_AVG\"",[150,413,356],{"class":182},[150,415,205],{"class":178},[150,417,361],{"class":182},[150,419,364],{"class":159},[150,421,367],{"class":182},[150,423,371],{"class":370},[150,425,205],{"class":178},[150,427,428],{"class":159},"\"average\"",[150,430,223],{"class":182},[150,432,381],{"class":370},[150,434,205],{"class":178},[150,436,386],{"class":295},[150,438,395],{"class":182},[150,440,441],{"class":63,"line":287},[150,442,196],{"emptyLinePlaceholder":195},[150,444,445],{"class":63,"line":324},[150,446,447],{"class":344},"# Dense: no gaps after a tie — no Excel equivalent without a helper column\n",[150,449,451,453,456,458,460,462,464,466,468,470,473,475,477,479,481,483,485],{"class":63,"line":450},8,[150,452,350],{"class":182},[150,454,455],{"class":159},"\"Rank_Dense\"",[150,457,356],{"class":182},[150,459,205],{"class":178},[150,461,361],{"class":182},[150,463,364],{"class":159},[150,465,367],{"class":182},[150,467,371],{"class":370},[150,469,205],{"class":178},[150,471,472],{"class":159},"\"dense\"",[150,474,223],{"class":182},[150,476,381],{"class":370},[150,478,205],{"class":178},[150,480,386],{"class":295},[150,482,389],{"class":182},[150,484,392],{"class":295},[150,486,395],{"class":182},[150,488,490],{"class":63,"line":489},9,[150,491,196],{"emptyLinePlaceholder":195},[150,493,495,498,501,503,505,507,509,511],{"class":63,"line":494},10,[150,496,497],{"class":295},"print",[150,499,500],{"class":182},"(reps.sort_values(",[150,502,364],{"class":159},[150,504,223],{"class":182},[150,506,381],{"class":370},[150,508,205],{"class":178},[150,510,386],{"class":295},[150,512,513],{"class":182},"))\n",[10,515,516,519,520,523,524,528],{},[147,517,518],{},"ascending=False"," is required to match Excel's default of ranking largest first; pandas ranks\nsmallest first unless told otherwise, which is the single most common source of an inverted ranking.\n",[147,521,522],{},"method=\"min\""," is what RANK and RANK.EQ do, and it is ",[525,526,527],"em",{},"not"," the pandas default — leaving the argument\nout gives averaged ranks and half-integer values that look like a bug to anyone comparing against the\nspreadsheet.",[10,530,531,534],{},[147,532,533],{},"method=\"dense\""," has no simple formula equivalent and is often what a report actually wants: two\nfirst places are followed by second, not by third.",[135,536,538],{"id":537},"ranking-within-a-group","Ranking within a group",[20,540,29,545,29,548,29,551,29,554,29,559,29,566,29,572,29,577,29,582,29,587,29,590,29,593,29,596,29,600,29,603,29,606,29,611,29,614],{"viewBox":541,"role":23,"ariaLabelledBy":542,"xmlns":27,"style":28},"0 0 760 232",[543,544],"rk-group-t","rk-group-d",[31,546,547],{"id":543},"Ranking inside each group",[35,549,550],{"id":544},"Grouping by region and ranking within each group produces a position per row that aligns back to the original frame, replacing an array formula that counts how many rows in the same category score higher.",[39,552],{"x":41,"y":41,"width":42,"height":553,"fill":44},"232",[56,555,558],{"x":130,"y":556,"style":557},"32","font-size:13px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","grouped ranking",[39,560],{"x":561,"y":562,"width":563,"height":564,"rx":565,"fill":108,"stroke":109,"style":54},"24.0","74","208.0","96","12",[56,567,571],{"x":568,"y":569,"style":570},"128.0","114","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","groupby('Region')",[56,573,576],{"x":568,"y":574,"style":575},"136","font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","one ranking per region",[63,578],{"x1":579,"y1":580,"x2":581,"y2":580,"stroke":109,"style":54},"237.0","122.0","269.0",[583,584],"polygon",{"points":585,"fill":586},"269.0,122.0 260.0,117.0 260.0,127.0","#5b5cf0",[39,588],{"x":589,"y":562,"width":563,"height":564,"rx":565,"fill":108,"stroke":109,"style":54},"276.0",[56,591,592],{"x":130,"y":569,"style":570},"rank(method='min')",[56,594,595],{"x":130,"y":574,"style":575},"position within the group",[63,597],{"x1":598,"y1":580,"x2":599,"y2":580,"stroke":109,"style":54},"489.0","521.0",[583,601],{"points":602,"fill":586},"521.0,122.0 512.0,117.0 512.0,127.0",[39,604],{"x":605,"y":562,"width":563,"height":564,"rx":565,"fill":52,"stroke":53,"style":54},"528.0",[56,607,610],{"x":608,"y":569,"style":609},"632.0","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","aligned to rows",[56,612,613],{"x":608,"y":574,"style":575},"filter on rank == 1",[56,615,617],{"x":130,"y":616,"style":132},"210","an array formula in Excel; one call here",[10,619,620],{},"Ranking each region separately is an array formula in Excel and one call here.",[140,622,624],{"className":169,"code":623,"language":171,"meta":145,"style":145},"reps[\"Rank_In_Region\"] = (\n    reps.groupby(\"Region\")[\"Revenue\"]\n        .rank(method=\"min\", ascending=False)\n        .astype(int)\n)\nprint(reps.sort_values([\"Region\", \"Rank_In_Region\"]))\n",[147,625,626,640,656,677,686,690],{"__ignoreMap":145},[150,627,628,630,633,635,637],{"class":63,"line":152},[150,629,350],{"class":182},[150,631,632],{"class":159},"\"Rank_In_Region\"",[150,634,356],{"class":182},[150,636,205],{"class":178},[150,638,639],{"class":182}," (\n",[150,641,642,645,648,651,653],{"class":63,"line":192},[150,643,644],{"class":182},"    reps.groupby(",[150,646,647],{"class":159},"\"Region\"",[150,649,650],{"class":182},")[",[150,652,364],{"class":159},[150,654,655],{"class":182},"]\n",[150,657,658,661,663,665,667,669,671,673,675],{"class":63,"line":199},[150,659,660],{"class":182},"        .rank(",[150,662,371],{"class":370},[150,664,205],{"class":178},[150,666,376],{"class":159},[150,668,223],{"class":182},[150,670,381],{"class":370},[150,672,205],{"class":178},[150,674,386],{"class":295},[150,676,395],{"class":182},[150,678,679,682,684],{"class":63,"line":211},[150,680,681],{"class":182},"        .astype(",[150,683,392],{"class":295},[150,685,395],{"class":182},[150,687,688],{"class":63,"line":252},[150,689,395],{"class":182},[150,691,692,694,697,699,701,703],{"class":63,"line":287},[150,693,497],{"class":295},[150,695,696],{"class":182},"(reps.sort_values([",[150,698,647],{"class":159},[150,700,223],{"class":182},[150,702,632],{"class":159},[150,704,705],{"class":182},"]))\n",[10,707,708,709,712],{},"The result aligns back to every row, so the frame keeps its shape — the same ",[147,710,711],{},"transform","-style\nbehaviour that makes grouped totals easy. Filtering to the top performer per region is then a\ncomparison rather than a sort-and-slice:",[140,714,716],{"className":169,"code":715,"language":171,"meta":145,"style":145},"top_per_region = reps[reps[\"Rank_In_Region\"] == 1]\nprint(top_per_region[[\"Region\", \"Rep\", \"Revenue\"]])\n",[147,717,718,740],{"__ignoreMap":145},[150,719,720,723,725,728,730,732,735,738],{"class":63,"line":152},[150,721,722],{"class":182},"top_per_region ",[150,724,205],{"class":178},[150,726,727],{"class":182}," reps[reps[",[150,729,632],{"class":159},[150,731,356],{"class":182},[150,733,734],{"class":178},"==",[150,736,737],{"class":295}," 1",[150,739,655],{"class":182},[150,741,742,744,747,749,751,754,756,758],{"class":63,"line":192},[150,743,497],{"class":295},[150,745,746],{"class":182},"(top_per_region[[",[150,748,647],{"class":159},[150,750,223],{"class":182},[150,752,753],{"class":159},"\"Rep\"",[150,755,223],{"class":182},[150,757,364],{"class":159},[150,759,760],{"class":182},"]])\n",[10,762,763,764,767],{},"Using the rank rather than ",[147,765,766],{},"groupby().head(1)"," keeps every member of a tie, which is usually correct\nfor a \"top performer\" list and is the sort of detail that only surfaces when two people genuinely tie.",[135,769,771],{"id":770},"large-small-and-the-top-n","LARGE, SMALL and the top N",[140,773,775],{"className":169,"code":774,"language":171,"meta":145,"style":145},"# =LARGE(C:C, 2) — the second largest value\nprint(reps[\"Revenue\"].nlargest(2).iloc[-1])\n\n# The top three rows, not just the values\nprint(reps.nlargest(3, \"Revenue\"))\n\n# =SMALL(C:C, 1)\nprint(reps[\"Revenue\"].nsmallest(1).iloc[0])\n",[147,776,777,782,809,813,818,834,838,843],{"__ignoreMap":145},[150,778,779],{"class":63,"line":152},[150,780,781],{"class":344},"# =LARGE(C:C, 2) — the second largest value\n",[150,783,784,786,789,791,794,797,800,803,806],{"class":63,"line":192},[150,785,497],{"class":295},[150,787,788],{"class":182},"(reps[",[150,790,364],{"class":159},[150,792,793],{"class":182},"].nlargest(",[150,795,796],{"class":295},"2",[150,798,799],{"class":182},").iloc[",[150,801,802],{"class":178},"-",[150,804,805],{"class":295},"1",[150,807,808],{"class":182},"])\n",[150,810,811],{"class":63,"line":199},[150,812,196],{"emptyLinePlaceholder":195},[150,814,815],{"class":63,"line":211},[150,816,817],{"class":344},"# The top three rows, not just the values\n",[150,819,820,822,825,828,830,832],{"class":63,"line":252},[150,821,497],{"class":295},[150,823,824],{"class":182},"(reps.nlargest(",[150,826,827],{"class":295},"3",[150,829,223],{"class":182},[150,831,364],{"class":159},[150,833,513],{"class":182},[150,835,836],{"class":63,"line":287},[150,837,196],{"emptyLinePlaceholder":195},[150,839,840],{"class":63,"line":324},[150,841,842],{"class":344},"# =SMALL(C:C, 1)\n",[150,844,845,847,849,851,854,856,858,860],{"class":63,"line":450},[150,846,497],{"class":295},[150,848,788],{"class":182},[150,850,364],{"class":159},[150,852,853],{"class":182},"].nsmallest(",[150,855,805],{"class":295},[150,857,799],{"class":182},[150,859,41],{"class":295},[150,861,808],{"class":182},[10,863,864,867,868,871,872,875],{},[147,865,866],{},"nlargest"," on a DataFrame returns whole rows, which is what a report needs and what LARGE cannot\ngive — the formula returns a value, and getting the name beside it requires an INDEX\u002FMATCH back into\nthe column. Ties at the boundary are kept or dropped according to the ",[147,869,870],{},"keep"," argument, with\n",[147,873,874],{},"keep=\"all\""," returning every row tied at the cutoff.",[135,877,879],{"id":878},"percentile-quartile-and-percentrank","PERCENTILE, QUARTILE and PERCENTRANK",[140,881,883],{"className":169,"code":882,"language":171,"meta":145,"style":145},"# =PERCENTILE.INC(C:C, 0.9)\nprint(reps[\"Revenue\"].quantile(0.9))\n\n# =QUARTILE(C:C, 1) and =MEDIAN(C:C)\nprint(reps[\"Revenue\"].quantile([0.25, 0.5, 0.75]))\n\n# =PERCENTRANK.INC(C:C, C2) — each row's position as a proportion\nreps[\"Percentile\"] = reps[\"Revenue\"].rank(pct=True)\n\n# Assign quartile labels in one call\nreps[\"Quartile\"] = pd.qcut(reps[\"Revenue\"], 4, labels=[\"Q1\", \"Q2\", \"Q3\", \"Q4\"])\nprint(reps[[\"Rep\", \"Revenue\", \"Percentile\", \"Quartile\"]])\n",[147,884,885,890,906,910,915,941,945,950,977,981,986,1039],{"__ignoreMap":145},[150,886,887],{"class":63,"line":152},[150,888,889],{"class":344},"# =PERCENTILE.INC(C:C, 0.9)\n",[150,891,892,894,896,898,901,904],{"class":63,"line":192},[150,893,497],{"class":295},[150,895,788],{"class":182},[150,897,364],{"class":159},[150,899,900],{"class":182},"].quantile(",[150,902,903],{"class":295},"0.9",[150,905,513],{"class":182},[150,907,908],{"class":63,"line":199},[150,909,196],{"emptyLinePlaceholder":195},[150,911,912],{"class":63,"line":211},[150,913,914],{"class":344},"# =QUARTILE(C:C, 1) and =MEDIAN(C:C)\n",[150,916,917,919,921,923,926,929,931,934,936,939],{"class":63,"line":252},[150,918,497],{"class":295},[150,920,788],{"class":182},[150,922,364],{"class":159},[150,924,925],{"class":182},"].quantile([",[150,927,928],{"class":295},"0.25",[150,930,223],{"class":182},[150,932,933],{"class":295},"0.5",[150,935,223],{"class":182},[150,937,938],{"class":295},"0.75",[150,940,705],{"class":182},[150,942,943],{"class":63,"line":287},[150,944,196],{"emptyLinePlaceholder":195},[150,946,947],{"class":63,"line":324},[150,948,949],{"class":344},"# =PERCENTRANK.INC(C:C, C2) — each row's position as a proportion\n",[150,951,952,954,957,959,961,963,965,967,970,972,975],{"class":63,"line":450},[150,953,350],{"class":182},[150,955,956],{"class":159},"\"Percentile\"",[150,958,356],{"class":182},[150,960,205],{"class":178},[150,962,361],{"class":182},[150,964,364],{"class":159},[150,966,367],{"class":182},[150,968,969],{"class":370},"pct",[150,971,205],{"class":178},[150,973,974],{"class":295},"True",[150,976,395],{"class":182},[150,978,979],{"class":63,"line":489},[150,980,196],{"emptyLinePlaceholder":195},[150,982,983],{"class":63,"line":494},[150,984,985],{"class":344},"# Assign quartile labels in one call\n",[150,987,989,991,994,996,998,1001,1003,1006,1009,1011,1014,1016,1019,1022,1024,1027,1029,1032,1034,1037],{"class":63,"line":988},11,[150,990,350],{"class":182},[150,992,993],{"class":159},"\"Quartile\"",[150,995,356],{"class":182},[150,997,205],{"class":178},[150,999,1000],{"class":182}," pd.qcut(reps[",[150,1002,364],{"class":159},[150,1004,1005],{"class":182},"], ",[150,1007,1008],{"class":295},"4",[150,1010,223],{"class":182},[150,1012,1013],{"class":370},"labels",[150,1015,205],{"class":178},[150,1017,1018],{"class":182},"[",[150,1020,1021],{"class":159},"\"Q1\"",[150,1023,223],{"class":182},[150,1025,1026],{"class":159},"\"Q2\"",[150,1028,223],{"class":182},[150,1030,1031],{"class":159},"\"Q3\"",[150,1033,223],{"class":182},[150,1035,1036],{"class":159},"\"Q4\"",[150,1038,808],{"class":182},[150,1040,1042,1044,1047,1049,1051,1053,1055,1057,1059,1061],{"class":63,"line":1041},12,[150,1043,497],{"class":295},[150,1045,1046],{"class":182},"(reps[[",[150,1048,753],{"class":159},[150,1050,223],{"class":182},[150,1052,364],{"class":159},[150,1054,223],{"class":182},[150,1056,956],{"class":159},[150,1058,223],{"class":182},[150,1060,993],{"class":159},[150,1062,760],{"class":182},[10,1064,1065,1068,1069,1072],{},[147,1066,1067],{},"quantile"," accepts a list and returns all the requested percentiles in one pass, which is the\nfive-number summary in a line. ",[147,1070,1071],{},"qcut"," is the one with no formula equivalent: it splits the data into\nequal-sized buckets and labels each row, where the spreadsheet version needs a PERCENTILE call per\nboundary plus a nested IF to assign the label.",[135,1074,1076],{"id":1075},"reproducing-a-leaderboard-exactly","Reproducing a leaderboard exactly",[10,1078,1079],{},"Putting it together, a ranked report with ties handled deliberately and a share-of-total column:",[140,1081,1083],{"className":169,"code":1082,"language":171,"meta":145,"style":145},"board = reps.sort_values(\"Revenue\", ascending=False).copy()\nboard[\"Rank\"] = board[\"Revenue\"].rank(method=\"min\", ascending=False).astype(int)\nboard[\"Share\"] = board[\"Revenue\"] \u002F board[\"Revenue\"].sum()\nboard[\"Cumulative\"] = board[\"Share\"].cumsum()\nboard[\"Gap_To_Top\"] = board[\"Revenue\"].max() - board[\"Revenue\"]\n\nprint(board[[\"Rank\", \"Rep\", \"Revenue\", \"Share\", \"Cumulative\", \"Gap_To_Top\"]].to_string(index=False))\n",[147,1084,1085,1108,1147,1174,1192,1218,1222],{"__ignoreMap":145},[150,1086,1087,1090,1092,1095,1097,1099,1101,1103,1105],{"class":63,"line":152},[150,1088,1089],{"class":182},"board ",[150,1091,205],{"class":178},[150,1093,1094],{"class":182}," reps.sort_values(",[150,1096,364],{"class":159},[150,1098,223],{"class":182},[150,1100,381],{"class":370},[150,1102,205],{"class":178},[150,1104,386],{"class":295},[150,1106,1107],{"class":182},").copy()\n",[150,1109,1110,1113,1116,1118,1120,1123,1125,1127,1129,1131,1133,1135,1137,1139,1141,1143,1145],{"class":63,"line":192},[150,1111,1112],{"class":182},"board[",[150,1114,1115],{"class":159},"\"Rank\"",[150,1117,356],{"class":182},[150,1119,205],{"class":178},[150,1121,1122],{"class":182}," board[",[150,1124,364],{"class":159},[150,1126,367],{"class":182},[150,1128,371],{"class":370},[150,1130,205],{"class":178},[150,1132,376],{"class":159},[150,1134,223],{"class":182},[150,1136,381],{"class":370},[150,1138,205],{"class":178},[150,1140,386],{"class":295},[150,1142,389],{"class":182},[150,1144,392],{"class":295},[150,1146,395],{"class":182},[150,1148,1149,1151,1154,1156,1158,1160,1162,1164,1167,1169,1171],{"class":63,"line":199},[150,1150,1112],{"class":182},[150,1152,1153],{"class":159},"\"Share\"",[150,1155,356],{"class":182},[150,1157,205],{"class":178},[150,1159,1122],{"class":182},[150,1161,364],{"class":159},[150,1163,356],{"class":182},[150,1165,1166],{"class":178},"\u002F",[150,1168,1122],{"class":182},[150,1170,364],{"class":159},[150,1172,1173],{"class":182},"].sum()\n",[150,1175,1176,1178,1181,1183,1185,1187,1189],{"class":63,"line":211},[150,1177,1112],{"class":182},[150,1179,1180],{"class":159},"\"Cumulative\"",[150,1182,356],{"class":182},[150,1184,205],{"class":178},[150,1186,1122],{"class":182},[150,1188,1153],{"class":159},[150,1190,1191],{"class":182},"].cumsum()\n",[150,1193,1194,1196,1199,1201,1203,1205,1207,1210,1212,1214,1216],{"class":63,"line":252},[150,1195,1112],{"class":182},[150,1197,1198],{"class":159},"\"Gap_To_Top\"",[150,1200,356],{"class":182},[150,1202,205],{"class":178},[150,1204,1122],{"class":182},[150,1206,364],{"class":159},[150,1208,1209],{"class":182},"].max() ",[150,1211,802],{"class":178},[150,1213,1122],{"class":182},[150,1215,364],{"class":159},[150,1217,655],{"class":182},[150,1219,1220],{"class":63,"line":287},[150,1221,196],{"emptyLinePlaceholder":195},[150,1223,1224,1226,1229,1231,1233,1235,1237,1239,1241,1243,1245,1247,1249,1251,1254,1257,1259,1261],{"class":63,"line":324},[150,1225,497],{"class":295},[150,1227,1228],{"class":182},"(board[[",[150,1230,1115],{"class":159},[150,1232,223],{"class":182},[150,1234,753],{"class":159},[150,1236,223],{"class":182},[150,1238,364],{"class":159},[150,1240,223],{"class":182},[150,1242,1153],{"class":159},[150,1244,223],{"class":182},[150,1246,1180],{"class":159},[150,1248,223],{"class":182},[150,1250,1198],{"class":159},[150,1252,1253],{"class":182},"]].to_string(",[150,1255,1256],{"class":370},"index",[150,1258,205],{"class":178},[150,1260,386],{"class":295},[150,1262,513],{"class":182},[10,1264,1265],{},"Each of those columns is a separate formula in a spreadsheet, and three of them reference an absolute\nrange that has to be maintained as rows are added. Here they are four expressions over the same\nframe, and adding a row changes nothing.",[135,1267,1269],{"id":1268},"ranking-on-more-than-one-column","Ranking on more than one column",[10,1271,1272],{},"A leaderboard usually has a tie-break rule — highest revenue, then most units, then alphabetically —\nand expressing it in a spreadsheet means a composite helper column with weights chosen so the parts\ncannot interfere. pandas ranks a sorted frame instead, which states the rule directly.",[140,1274,1276],{"className":169,"code":1275,"language":171,"meta":145,"style":145},"ordered = reps.sort_values(\n    [\"Revenue\", \"Rep\"],\n    ascending=[False, True],\n).reset_index(drop=True)\nordered[\"Position\"] = ordered.index + 1\nprint(ordered[[\"Position\", \"Rep\", \"Revenue\"]])\n",[147,1277,1278,1288,1301,1318,1332,1353],{"__ignoreMap":145},[150,1279,1280,1283,1285],{"class":63,"line":152},[150,1281,1282],{"class":182},"ordered ",[150,1284,205],{"class":178},[150,1286,1287],{"class":182}," reps.sort_values(\n",[150,1289,1290,1293,1295,1297,1299],{"class":63,"line":192},[150,1291,1292],{"class":182},"    [",[150,1294,364],{"class":159},[150,1296,223],{"class":182},[150,1298,753],{"class":159},[150,1300,249],{"class":182},[150,1302,1303,1306,1308,1310,1312,1314,1316],{"class":63,"line":199},[150,1304,1305],{"class":370},"    ascending",[150,1307,205],{"class":178},[150,1309,1018],{"class":182},[150,1311,386],{"class":295},[150,1313,223],{"class":182},[150,1315,974],{"class":295},[150,1317,249],{"class":182},[150,1319,1320,1323,1326,1328,1330],{"class":63,"line":211},[150,1321,1322],{"class":182},").reset_index(",[150,1324,1325],{"class":370},"drop",[150,1327,205],{"class":178},[150,1329,974],{"class":295},[150,1331,395],{"class":182},[150,1333,1334,1337,1340,1342,1344,1347,1350],{"class":63,"line":252},[150,1335,1336],{"class":182},"ordered[",[150,1338,1339],{"class":159},"\"Position\"",[150,1341,356],{"class":182},[150,1343,205],{"class":178},[150,1345,1346],{"class":182}," ordered.index ",[150,1348,1349],{"class":178},"+",[150,1351,1352],{"class":295}," 1\n",[150,1354,1355,1357,1360,1362,1364,1366,1368,1370],{"class":63,"line":287},[150,1356,497],{"class":295},[150,1358,1359],{"class":182},"(ordered[[",[150,1361,1339],{"class":159},[150,1363,223],{"class":182},[150,1365,753],{"class":159},[150,1367,223],{"class":182},[150,1369,364],{"class":159},[150,1371,760],{"class":182},[10,1373,1374,1375,1378,1379,1382],{},"Sorting by the tie-break columns and numbering the result gives a strict ordering with no ties at\nall, which is what a published leaderboard normally wants. When genuine ties should remain visible,\nkeep the rank column alongside the position so the report can show both — ",[147,1376,1377],{},"Rank"," for the sporting\nanswer and ",[147,1380,1381],{},"Position"," for the row order.",[10,1384,1385],{},"The distinction matters more than it sounds. A \"top ten\" built from row order silently drops the\neleventh person who tied for tenth; one built from a rank includes them. Deciding which is intended\nis a business question, and having both columns available makes it one somebody can answer.",[135,1387,1389],{"id":1388},"percentiles-on-grouped-data","Percentiles on grouped data",[10,1391,1392,1393,1395],{},"Comparing each row against its own group's distribution — a rep against their region rather than the\ncompany — is another array formula in Excel and a ",[147,1394,711],{}," here.",[140,1397,1399],{"className":169,"code":1398,"language":171,"meta":145,"style":145},"reps[\"Region_Percentile\"] = (\n    reps.groupby(\"Region\")[\"Revenue\"].rank(pct=True).round(3)\n)\nreps[\"Region_Median\"] = reps.groupby(\"Region\")[\"Revenue\"].transform(\"median\")\nreps[\"Above_Region_Median\"] = reps[\"Revenue\"] > reps[\"Region_Median\"]\n\nprint(reps[[\"Rep\", \"Region\", \"Revenue\", \"Region_Percentile\", \"Above_Region_Median\"]])\n",[147,1400,1401,1414,1439,1443,1471,1497,1501],{"__ignoreMap":145},[150,1402,1403,1405,1408,1410,1412],{"class":63,"line":152},[150,1404,350],{"class":182},[150,1406,1407],{"class":159},"\"Region_Percentile\"",[150,1409,356],{"class":182},[150,1411,205],{"class":178},[150,1413,639],{"class":182},[150,1415,1416,1418,1420,1422,1424,1426,1428,1430,1432,1435,1437],{"class":63,"line":192},[150,1417,644],{"class":182},[150,1419,647],{"class":159},[150,1421,650],{"class":182},[150,1423,364],{"class":159},[150,1425,367],{"class":182},[150,1427,969],{"class":370},[150,1429,205],{"class":178},[150,1431,974],{"class":295},[150,1433,1434],{"class":182},").round(",[150,1436,827],{"class":295},[150,1438,395],{"class":182},[150,1440,1441],{"class":63,"line":199},[150,1442,395],{"class":182},[150,1444,1445,1447,1450,1452,1454,1457,1459,1461,1463,1466,1469],{"class":63,"line":211},[150,1446,350],{"class":182},[150,1448,1449],{"class":159},"\"Region_Median\"",[150,1451,356],{"class":182},[150,1453,205],{"class":178},[150,1455,1456],{"class":182}," reps.groupby(",[150,1458,647],{"class":159},[150,1460,650],{"class":182},[150,1462,364],{"class":159},[150,1464,1465],{"class":182},"].transform(",[150,1467,1468],{"class":159},"\"median\"",[150,1470,395],{"class":182},[150,1472,1473,1475,1478,1480,1482,1484,1486,1488,1491,1493,1495],{"class":63,"line":252},[150,1474,350],{"class":182},[150,1476,1477],{"class":159},"\"Above_Region_Median\"",[150,1479,356],{"class":182},[150,1481,205],{"class":178},[150,1483,361],{"class":182},[150,1485,364],{"class":159},[150,1487,356],{"class":182},[150,1489,1490],{"class":178},">",[150,1492,361],{"class":182},[150,1494,1449],{"class":159},[150,1496,655],{"class":182},[150,1498,1499],{"class":63,"line":287},[150,1500,196],{"emptyLinePlaceholder":195},[150,1502,1503,1505,1507,1509,1511,1513,1515,1517,1519,1521,1523,1525],{"class":63,"line":324},[150,1504,497],{"class":295},[150,1506,1046],{"class":182},[150,1508,753],{"class":159},[150,1510,223],{"class":182},[150,1512,647],{"class":159},[150,1514,223],{"class":182},[150,1516,364],{"class":159},[150,1518,223],{"class":182},[150,1520,1407],{"class":159},[150,1522,223],{"class":182},[150,1524,1477],{"class":159},[150,1526,760],{"class":182},[10,1528,1529],{},"The median-per-group column is the useful one in practice, because a percentile on a group of three\nrows is not a meaningful statistic while \"above or below the regional median\" is. Guarding for small\ngroups is worth doing explicitly rather than letting the report imply precision it does not have:",[140,1531,1533],{"className":169,"code":1532,"language":171,"meta":145,"style":145},"sizes = reps.groupby(\"Region\")[\"Revenue\"].transform(\"size\")\nreps.loc[sizes \u003C 5, \"Region_Percentile\"] = pd.NA\n",[147,1534,1535,1557],{"__ignoreMap":145},[150,1536,1537,1540,1542,1544,1546,1548,1550,1552,1555],{"class":63,"line":152},[150,1538,1539],{"class":182},"sizes ",[150,1541,205],{"class":178},[150,1543,1456],{"class":182},[150,1545,647],{"class":159},[150,1547,650],{"class":182},[150,1549,364],{"class":159},[150,1551,1465],{"class":182},[150,1553,1554],{"class":159},"\"size\"",[150,1556,395],{"class":182},[150,1558,1559,1562,1565,1568,1570,1572,1574,1576,1579],{"class":63,"line":192},[150,1560,1561],{"class":182},"reps.loc[sizes ",[150,1563,1564],{"class":178},"\u003C",[150,1566,1567],{"class":295}," 5",[150,1569,223],{"class":182},[150,1571,1407],{"class":159},[150,1573,356],{"class":182},[150,1575,205],{"class":178},[150,1577,1578],{"class":182}," pd.",[150,1580,1581],{"class":295},"NA\n",[10,1583,1584,1585,18],{},"Blanking a statistic that the sample cannot support is the sort of judgement a spreadsheet makes hard\nand a script makes trivial, and it is the same instinct behind the checks in\n",[14,1586,1588],{"href":1587},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fvalidate-an-excel-report-before-sending-it\u002F","Validate an Excel Report Before Sending It",[135,1590,1592],{"id":1591},"common-pitfalls","Common pitfalls",[1594,1595,1596,1612],"table",{},[1597,1598,1599],"thead",{},[1600,1601,1602,1606,1609],"tr",{},[1603,1604,1605],"th",{},"Symptom",[1603,1607,1608],{},"Cause",[1603,1610,1611],{},"Fix",[1613,1614,1615,1630,1646,1665,1681,1692],"tbody",{},[1600,1616,1617,1621,1624],{},[1618,1619,1620],"td",{},"Ranks are inverted",[1618,1622,1623],{},"pandas ranks ascending by default",[1618,1625,1626,1627,1629],{},"Pass ",[147,1628,518],{}," to match Excel",[1600,1631,1632,1635,1641],{},[1618,1633,1634],{},"Ranks come out as 1.5, 3.5",[1618,1636,1637,1640],{},[147,1638,1639],{},"method=\"average\""," is the default",[1618,1642,1626,1643,1645],{},[147,1644,522],{}," to match RANK.EQ",[1600,1647,1648,1651,1654],{},[1618,1649,1650],{},"Ranks differ from the sheet after a tie",[1618,1652,1653],{},"Excel skips the next rank; dense does not",[1618,1655,1656,1657,1660,1661,1664],{},"Choose ",[147,1658,1659],{},"min"," or ",[147,1662,1663],{},"dense"," deliberately",[1600,1666,1667,1672,1675],{},[1618,1668,1669,1671],{},[147,1670,1071],{}," raises about duplicate edges",[1618,1673,1674],{},"Too many identical values for that many buckets",[1618,1676,1626,1677,1680],{},[147,1678,1679],{},"duplicates=\"drop\"",", or use fewer buckets",[1600,1682,1683,1686,1689],{},[1618,1684,1685],{},"Percentiles differ slightly from Excel",[1618,1687,1688],{},"PERCENTILE.EXC uses a different definition",[1618,1690,1691],{},"Compare against PERCENTILE.INC, or accept the difference",[1600,1693,1694,1697,1700],{},[1618,1695,1696],{},"Ranking includes missing values",[1618,1698,1699],{},"NaN is ranked last by default",[1618,1701,1626,1702,1705],{},[147,1703,1704],{},"na_option=\"keep\""," to leave them NaN",[135,1707,1709],{"id":1708},"performance-and-scale","Performance and scale",[20,1711,29,1717,29,1720,29,1723,29,1727,29,1733,29,1741,29,1750,29,1756,29,1760,29,1762,29,1766,29,1771,29,1775,29,1778,29,1781,29,1786,29,1790],{"viewBox":1712,"role":23,"ariaLabelledBy":1713,"xmlns":27,"style":1716},"0 0 720 196",[1714,1715],"rk-cost-t","rk-cost-d","width:100%;max-width:720px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[31,1718,1719],{"id":1714},"Ranking 200,000 rows",[35,1721,1722],{"id":1715},"Excel's RANK scans the whole column once per row, while a pandas rank sorts the column once and nlargest avoids the full sort entirely when only the top rows are needed.",[39,1724],{"x":41,"y":41,"width":1725,"height":1726,"fill":44},"720","196",[56,1728,1732],{"x":1729,"y":1730,"style":1731},"20","56","font-size:12px;font-weight:600;fill:var(--text,#172033);text-anchor:start","RANK filled down",[39,1734],{"x":1735,"y":1736,"width":1737,"height":48,"rx":1738,"fill":1739,"stroke":1740},"200","40","364.7","6","#e7ebef","var(--line,#cdd5e6)",[39,1742],{"x":1743,"y":1744,"width":1745,"height":1746,"rx":1747,"fill":1748,"stroke":1749},"201","41","362.7","24","5","#fee8f2","var(--accent,#d81b73)",[56,1751,1755],{"x":1752,"y":1753,"style":1754},"576.7","58","font-size:12px;font-weight:700;fill:var(--accent,#d81b73);text-anchor:start","scan per row",[56,1757,1759],{"x":1729,"y":1758,"style":1731},"100","Series.rank",[39,1761],{"x":1735,"y":71,"width":1737,"height":48,"rx":1738,"fill":1739,"stroke":1740},[39,1763],{"x":1743,"y":1764,"width":1765,"height":1746,"rx":1747,"fill":52,"stroke":53},"85","28",[56,1767,1770],{"x":1752,"y":1768,"style":1769},"102","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","one sort",[56,1772,1774],{"x":1729,"y":1773,"style":1731},"144","nlargest(10)",[39,1776],{"x":1735,"y":1777,"width":1737,"height":48,"rx":1738,"fill":1739,"stroke":1740},"128",[39,1779],{"x":1743,"y":1780,"width":1765,"height":1746,"rx":1747,"fill":85,"stroke":86},"129",[56,1782,1785],{"x":1752,"y":1783,"style":1784},"146","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:start","partial selection",[56,1787,1789],{"x":1729,"y":1729,"style":1788},"font-size:11.5px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:start","relative cost",[56,1791,1794],{"x":1792,"y":1793,"style":132},"360.0","186","for a top-ten list, do not sort the whole frame",[10,1796,1797],{},"Ranking sorts, so it costs more than a sum but far less than the Excel equivalent, which for\nRANK is a scan of the whole column per row. On 200,000 rows a pandas rank is a fraction of a second;\nthe formula version is the kind of thing that makes a workbook take a minute to recalculate.",[10,1799,1800,1802],{},[147,1801,866],{}," is worth preferring over a full sort when only the top few rows are needed — it uses a\npartial selection rather than ordering everything, which is a meaningful saving on large frames:",[140,1804,1806],{"className":169,"code":1805,"language":171,"meta":145,"style":145},"# Better than sort_values(...).head(10) on a large frame\ntop_ten = reps.nlargest(10, \"Revenue\")\n",[147,1807,1808,1813],{"__ignoreMap":145},[150,1809,1810],{"class":63,"line":152},[150,1811,1812],{"class":344},"# Better than sort_values(...).head(10) on a large frame\n",[150,1814,1815,1818,1820,1823,1826,1828,1830],{"class":63,"line":192},[150,1816,1817],{"class":182},"top_ten ",[150,1819,205],{"class":178},[150,1821,1822],{"class":182}," reps.nlargest(",[150,1824,1825],{"class":295},"10",[150,1827,223],{"class":182},[150,1829,364],{"class":159},[150,1831,395],{"class":182},[10,1833,1834],{},"Grouped ranking costs proportionally more because it sorts within every group, but it is still one\npass over the data rather than the nested scanning an array formula performs.",[135,1836,1838],{"id":1837},"conclusion","Conclusion",[10,1840,1841,1842,1844,1845,1847,1848,1850,1851,1853],{},"Match Excel by passing both arguments explicitly: ",[147,1843,518],{}," for the direction and\n",[147,1846,522],{}," for RANK.EQ semantics. Beyond that, pandas offers what the spreadsheet does not —\ndense ranking without gaps, ranking within groups in one call, ",[147,1849,866],{}," returning whole rows rather\nthan values, and ",[147,1852,1071],{}," assigning quartile labels without a nested IF.",[135,1855,1857],{"id":1856},"frequently-asked-questions","Frequently asked questions",[10,1859,1860,1864],{},[1861,1862,1863],"strong",{},"Which rank method matches Excel's RANK?","\nmethod='min' matches RANK and RANK.EQ: tied values all take the lowest rank in the tie and the next rank is skipped. RANK.AVG corresponds to method='average', which is pandas' default — so the default does not match the formula most people mean.",[10,1866,1867,1870,1871,1874],{},[1861,1868,1869],{},"How do I rank within a group?","\ngroupby(key)",[150,1872,1873],{},"col",".rank(...), which ranks inside each group and aligns back to every row. Excel needs an array formula counting how many rows in the same category exceed the current one.",[10,1876,1877,1880],{},[1861,1878,1879],{},"Does PERCENTILE.INC or PERCENTILE.EXC match quantile?","\nquantile with interpolation='linear' matches PERCENTILE.INC, which is the inclusive definition. PERCENTILE.EXC uses a different formula that pandas has no direct flag for; it is close but not identical at the extremes.",[10,1882,1883,1886],{},[1861,1884,1885],{},"What is the difference between rank(pct=True) and PERCENTRANK?","\nThey agree in spirit: both express a rank as a proportion. PERCENTRANK.INC scales so the lowest value is 0 and the highest is 1, while rank(pct=True) divides the rank by the count, so the highest is 1 and the lowest is 1\u002Fn.",[135,1888,1890],{"id":1889},"related","Related",[1892,1893,1894,1901,1908,1915,1922],"ul",{},[1895,1896,1897,1898,1900],"li",{},"Up one level: ",[14,1899,17],{"href":16}," — the wider function map.",[1895,1902,1903,1907],{},[14,1904,1906],{"href":1905},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsumif-and-sumifs-equivalent-in-pandas\u002F","SUMIF and SUMIFS Equivalent in pandas"," — the group totals a leaderboard's share column divides by.",[1895,1909,1910,1914],{},[14,1911,1913],{"href":1912},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fsort-excel-rows-with-python-before-writing\u002F","Sort Excel Rows with Python Before Writing"," — presenting a ranked frame in the sheet.",[1895,1916,1917,1921],{},[14,1918,1920],{"href":1919},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-data-bars-and-colour-scales-with-openpyxl\u002F","Add Data Bars and Colour Scales with openpyxl"," — showing a ranking visually once it is computed.",[1895,1923,1924,1928],{},[14,1925,1927],{"href":1926},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcreate-pivot-table-from-excel-with-pandas\u002F","Create a Pivot Table from Excel with Pandas"," — ranking within a cross-tab.",[1930,1931,1932],"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 .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}",{"title":145,"searchDepth":192,"depth":192,"links":1934},[1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947],{"id":137,"depth":192,"text":138},{"id":333,"depth":192,"text":334},{"id":537,"depth":192,"text":538},{"id":770,"depth":192,"text":771},{"id":878,"depth":192,"text":879},{"id":1075,"depth":192,"text":1076},{"id":1268,"depth":192,"text":1269},{"id":1388,"depth":192,"text":1389},{"id":1591,"depth":192,"text":1592},{"id":1708,"depth":192,"text":1709},{"id":1837,"depth":192,"text":1838},{"id":1856,"depth":192,"text":1857},{"id":1889,"depth":192,"text":1890},"2026-09-04","Match RANK.EQ with method='min' and ascending=False, rank within groups in one call, and replace LARGE, PERCENTILE and QUARTILE with nlargest, quantile and qcut.","md",[1952,1954,1956,1958],{"q":1863,"a":1953},"method='min' matches RANK and RANK.EQ: tied values all take the lowest rank in the tie and the next rank is skipped. RANK.AVG corresponds to method='average', which is pandas' default — so the default does not match the formula most people mean.",{"q":1869,"a":1955},"groupby(key)[col].rank(...), which ranks inside each group and aligns back to every row. Excel needs an array formula counting how many rows in the same category exceed the current one.",{"q":1879,"a":1957},"quantile with interpolation='linear' matches PERCENTILE.INC, which is the inclusive definition. PERCENTILE.EXC uses a different formula that pandas has no direct flag for; it is close but not identical at the extremes.",{"q":1885,"a":1959},"They agree in spirit: both express a rank as a proportion. PERCENTRANK.INC scales so the lowest value is 0 and the highest is 1, while rank(pct=True) divides the rank by the count, so the highest is 1 and the lowest is 1\u002Fn.",{"breadcrumb":1961},[1962,1964,1967],{"name":1963,"item":1166},"Home",{"name":1965,"item":1966},"Advanced Data Transformation and Cleaning","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":17,"item":16},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frank-and-percentile-formulas-in-pandas",{"title":5,"description":1970},"Translate RANK, RANK.EQ, RANK.AVG, LARGE, SMALL, PERCENTILE and QUARTILE into pandas — with the tie-handling argument that decides whether your numbers match the sheet.","rank-and-percentile-formulas-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frank-and-percentile-formulas-in-pandas\u002Findex","how-to","iSCnzz4N5L8GDQPSfmc9oOQ8mr_YXg0oDkq5UtWrxbk",[1976,1980],{"title":1977,"path":1978,"stem":1979,"children":-1},"INDEX MATCH Equivalent in pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Findex-match-equivalent-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Findex-match-equivalent-in-pandas\u002Findex",{"title":1981,"path":1982,"stem":1983,"children":-1},"Running Totals and Year-Over-Year Growth in pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frunning-totals-and-year-over-year-growth-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frunning-totals-and-year-over-year-growth-in-pandas\u002Findex",1788710154434]