[{"data":1,"prerenderedAt":2253},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmedian-mode-and-quartile-equivalents-in-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmedian-mode-and-quartile-equivalents-in-pandas":2219,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmedian-mode-and-quartile-equivalents-in-pandas":2228,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmedian-mode-and-quartile-equivalents-in-pandas":2233,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmedian-mode-and-quartile-equivalents-in-pandas":2234},{"id":4,"title":5,"body":6,"dateModified":2186,"datePublished":2186,"description":2187,"extension":2188,"faq":2189,"keyword":2198,"kind":2199,"meta":2200,"navigation":137,"path":2208,"seo":2209,"slug":2212,"stem":2213,"topics":2214,"type":2217,"__hash__":2218},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmedian-mode-and-quartile-equivalents-in-pandas\u002Findex.md","MEDIAN, MODE and QUARTILE Equivalents in pandas",{"type":7,"value":8,"toc":2171},"minimark",[9,55,60,91,105,109,220,250,254,300,319,323,330,494,509,616,620,631,782,807,929,933,940,1119,1146,1150,1616,1638,1769,1773,1776,1964,1980,1984,2001,2005,2061,2065,2103,2107,2113,2119,2128,2134,2138,2167],[10,11,12,13,17,18,17,21,17,24,17,27,30,31,34,35,17,38,41,42,45,46,48,49,54],"p",{},"Averages hide skew, so reports on delivery times, order values or salaries lean on the median, the quartiles and the most common value instead. Excel has a small family of functions for them — ",[14,15,16],"code",{},"MEDIAN",", ",[14,19,20],{},"MODE.SNGL",[14,22,23],{},"MODE.MULT",[14,25,26],{},"QUARTILE.INC",[14,28,29],{},"QUARTILE.EXC"," and the matching ",[14,32,33],{},"PERCENTILE"," pair — and pandas has ",[14,36,37],{},"median",[14,39,40],{},"mode"," and ",[14,43,44],{},"quantile",". Most of them line up directly. Two do not, and those two are where a migrated report starts disagreeing with the spreadsheet it replaced: the \"exclusive\" quartiles, which use a different interpolation, and ",[14,47,20],{},", which breaks ties differently from pandas. This page maps each function, shows the differences on real numbers and checks them against the spreadsheet. It belongs to ",[50,51,53],"a",{"href":52},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002F","Excel Formula Equivalents in pandas",".",[56,57,59],"h2",{"id":58},"prerequisites","Prerequisites",[61,62,67],"pre",{"className":63,"code":64,"language":65,"meta":66,"style":66},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas numpy openpyxl\n","bash","",[14,68,69],{"__ignoreMap":66},[70,71,74,78,82,85,88],"span",{"class":72,"line":73},"line",1,[70,75,77],{"class":76},"sMTad","pip",[70,79,81],{"class":80},"srMev"," install",[70,83,84],{"class":80}," pandas",[70,86,87],{"class":80}," numpy",[70,89,90],{"class":80}," openpyxl\n",[10,92,93,96,97,100,101,104],{},[14,94,95],{},"deliveries.xlsx"," holds delivery times in days for two regions, eleven numbers in total, plus one cell containing the text ",[14,98,99],{},"n\u002Fa"," — the sort of value that Excel's statistics functions skip without comment. LibreOffice (",[14,102,103],{},"soffice",") recalculates the spreadsheet formulas in Step 6.",[56,106,108],{"id":107},"step-1-median-and-what-excel-ignores","Step 1 — MEDIAN, and what Excel ignores",[61,110,114],{"className":111,"code":112,"language":113,"meta":66,"style":66},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ndf = pd.read_excel(\"deliveries.xlsx\")\nprint(df[\"DeliveryDays\"].dtype)\ndays = pd.to_numeric(df[\"DeliveryDays\"], errors=\"coerce\")\nprint(\"median:\", days.median(), \"| values used:\", days.notna().sum())\n","python",[14,115,116,132,139,157,173,200],{"__ignoreMap":66},[70,117,118,122,126,129],{"class":72,"line":73},[70,119,121],{"class":120},"s-kum","import",[70,123,125],{"class":124},"skGVy"," pandas ",[70,127,128],{"class":120},"as",[70,130,131],{"class":124}," pd\n",[70,133,135],{"class":72,"line":134},2,[70,136,138],{"emptyLinePlaceholder":137},true,"\n",[70,140,142,145,148,151,154],{"class":72,"line":141},3,[70,143,144],{"class":124},"df ",[70,146,147],{"class":120},"=",[70,149,150],{"class":124}," pd.read_excel(",[70,152,153],{"class":80},"\"deliveries.xlsx\"",[70,155,156],{"class":124},")\n",[70,158,160,164,167,170],{"class":72,"line":159},4,[70,161,163],{"class":162},"sP0c6","print",[70,165,166],{"class":124},"(df[",[70,168,169],{"class":80},"\"DeliveryDays\"",[70,171,172],{"class":124},"].dtype)\n",[70,174,176,179,181,184,186,189,193,195,198],{"class":72,"line":175},5,[70,177,178],{"class":124},"days ",[70,180,147],{"class":120},[70,182,183],{"class":124}," pd.to_numeric(df[",[70,185,169],{"class":80},[70,187,188],{"class":124},"], ",[70,190,192],{"class":191},"sa561","errors",[70,194,147],{"class":120},[70,196,197],{"class":80},"\"coerce\"",[70,199,156],{"class":124},[70,201,203,205,208,211,214,217],{"class":72,"line":202},6,[70,204,163],{"class":162},[70,206,207],{"class":124},"(",[70,209,210],{"class":80},"\"median:\"",[70,212,213],{"class":124},", days.median(), ",[70,215,216],{"class":80},"\"| values used:\"",[70,218,219],{"class":124},", days.notna().sum())\n",[10,221,222,223,225,226,229,230,233,234,237,238,241,242,244,245,249],{},"Because of the ",[14,224,99],{},", the column arrives as text-and-numbers, and calling ",[14,227,228],{},".median()"," on it directly fails. Excel's ",[14,231,232],{},"MEDIAN(A2:A13)"," silently skips text and empty cells and uses the eleven numbers. ",[14,235,236],{},"pd.to_numeric(..., errors=\"coerce\")"," reproduces that: text becomes ",[14,239,240],{},"NaN",", which every pandas statistic skips by default. The median is 12. Whether skipping is acceptable is a separate question — ",[14,243,99],{}," may mean \"not delivered yet\", which is not the same as \"no data\" — and ",[50,246,248],{"href":247},"\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Freplace-placeholder-text-with-nan-in-pandas\u002F","Replace Placeholder Text with NaN in pandas"," shows how to decide that explicitly.",[56,251,253],{"id":252},"step-2-quartileinc-and-percentileinc","Step 2 — QUARTILE.INC and PERCENTILE.INC",[61,255,257],{"className":111,"code":256,"language":113,"meta":66,"style":66},"print(\"Q1, Q3 (INC):\", days.quantile([0.25, 0.75]).tolist())\nprint(\"90th percentile (INC):\", days.quantile(0.9))\n",[14,258,259,282],{"__ignoreMap":66},[70,260,261,263,265,268,271,274,276,279],{"class":72,"line":73},[70,262,163],{"class":162},[70,264,207],{"class":124},[70,266,267],{"class":80},"\"Q1, Q3 (INC):\"",[70,269,270],{"class":124},", days.quantile([",[70,272,273],{"class":162},"0.25",[70,275,17],{"class":124},[70,277,278],{"class":162},"0.75",[70,280,281],{"class":124},"]).tolist())\n",[70,283,284,286,288,291,294,297],{"class":72,"line":134},[70,285,163],{"class":162},[70,287,207],{"class":124},[70,289,290],{"class":80},"\"90th percentile (INC):\"",[70,292,293],{"class":124},", days.quantile(",[70,295,296],{"class":162},"0.9",[70,298,299],{"class":124},"))\n",[10,301,302,303,305,306,17,308,311,312,315,316,318],{},"pandas' default ",[14,304,44],{}," uses linear interpolation between the closest ranks, which is exactly the method of ",[14,307,26],{},[14,309,310],{},"PERCENTILE.INC"," and the older ",[14,313,314],{},"QUARTILE","\u002F",[14,317,33],{},": Q1 = 7, Q3 = 16.5, 90th percentile = 22. \"Inclusive\" means the 0th and 100th percentiles are the minimum and maximum.",[56,320,322],{"id":321},"step-3-quartileexc-and-percentileexc-need-another-method","Step 3 — QUARTILE.EXC and PERCENTILE.EXC need another method",[10,324,325,326,329],{},"The exclusive versions place the quantiles differently — they treat the data as a sample and never return the minimum or maximum for interior percentiles. pandas has no option for this, but NumPy does: method ",[14,327,328],{},"\"weibull\""," is the same formula.",[61,331,333],{"className":111,"code":332,"language":113,"meta":66,"style":66},"import numpy as np\n\nclean = days.dropna().to_numpy()\nprint(\"Q1, Q3 (EXC):\", np.quantile(clean, [0.25, 0.75], method=\"weibull\").tolist())\nprint(\"90th percentile (EXC):\", round(float(np.quantile(clean, 0.9, method=\"weibull\")), 2))\ntry:\n    np.quantile(clean, 0.05, method=\"weibull\")\n    print(\"5th percentile (EXC) computed\")\nexcept Exception as exc:\n    print(exc)\n",[14,334,335,347,351,361,391,431,439,458,471,486],{"__ignoreMap":66},[70,336,337,339,342,344],{"class":72,"line":73},[70,338,121],{"class":120},[70,340,341],{"class":124}," numpy ",[70,343,128],{"class":120},[70,345,346],{"class":124}," np\n",[70,348,349],{"class":72,"line":134},[70,350,138],{"emptyLinePlaceholder":137},[70,352,353,356,358],{"class":72,"line":141},[70,354,355],{"class":124},"clean ",[70,357,147],{"class":120},[70,359,360],{"class":124}," days.dropna().to_numpy()\n",[70,362,363,365,367,370,373,375,377,379,381,384,386,388],{"class":72,"line":159},[70,364,163],{"class":162},[70,366,207],{"class":124},[70,368,369],{"class":80},"\"Q1, Q3 (EXC):\"",[70,371,372],{"class":124},", np.quantile(clean, [",[70,374,273],{"class":162},[70,376,17],{"class":124},[70,378,278],{"class":162},[70,380,188],{"class":124},[70,382,383],{"class":191},"method",[70,385,147],{"class":120},[70,387,328],{"class":80},[70,389,390],{"class":124},").tolist())\n",[70,392,393,395,397,400,402,405,407,410,413,415,417,419,421,423,426,429],{"class":72,"line":175},[70,394,163],{"class":162},[70,396,207],{"class":124},[70,398,399],{"class":80},"\"90th percentile (EXC):\"",[70,401,17],{"class":124},[70,403,404],{"class":162},"round",[70,406,207],{"class":124},[70,408,409],{"class":162},"float",[70,411,412],{"class":124},"(np.quantile(clean, ",[70,414,296],{"class":162},[70,416,17],{"class":124},[70,418,383],{"class":191},[70,420,147],{"class":120},[70,422,328],{"class":80},[70,424,425],{"class":124},")), ",[70,427,428],{"class":162},"2",[70,430,299],{"class":124},[70,432,433,436],{"class":72,"line":202},[70,434,435],{"class":120},"try",[70,437,438],{"class":124},":\n",[70,440,442,445,448,450,452,454,456],{"class":72,"line":441},7,[70,443,444],{"class":124},"    np.quantile(clean, ",[70,446,447],{"class":162},"0.05",[70,449,17],{"class":124},[70,451,383],{"class":191},[70,453,147],{"class":120},[70,455,328],{"class":80},[70,457,156],{"class":124},[70,459,461,464,466,469],{"class":72,"line":460},8,[70,462,463],{"class":162},"    print",[70,465,207],{"class":124},[70,467,468],{"class":80},"\"5th percentile (EXC) computed\"",[70,470,156],{"class":124},[70,472,474,477,480,483],{"class":72,"line":473},9,[70,475,476],{"class":120},"except",[70,478,479],{"class":162}," Exception",[70,481,482],{"class":120}," as",[70,484,485],{"class":124}," exc:\n",[70,487,489,491],{"class":72,"line":488},10,[70,490,463],{"class":162},[70,492,493],{"class":124},"(exc)\n",[10,495,496,497,500,501,504,505,508],{},"Q3 moves from 16.5 to 18 and the 90th percentile from 22 to 28.4 — differences large enough to change a service-level report. NumPy clamps very low and very high percentiles to the minimum and maximum, where Excel's ",[14,498,499],{},".EXC"," functions return ",[14,502,503],{},"#NUM!"," because the sample is too small to estimate them; for eleven values that is anything below 1\u002F12 or above 11\u002F12. If a report must reproduce Excel's errors, check ",[14,506,507],{},"1\u002F(n+1) \u003C= p \u003C= n\u002F(n+1)"," before computing.",[510,511,519,520,519,524,519,528,519,536,519,546,519,553,519,560,519,565,519,568,519,572,519,577,519,582,519,585,519,589,519,592,519,596,519,599,519,603,519,606,519,610],"svg",{"viewBox":512,"role":513,"ariaLabelledBy":514,"xmlns":517,"style":518},"4 0 744 206","img",[515,516],"mq-methods-t","mq-methods-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:744px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[521,522,523],"title",{"id":515},"The same eleven delivery times, two quartile methods",[525,526,527],"desc",{"id":516},"For eleven delivery times, the inclusive method gives Q1 7, Q3 16.5 and a 90th percentile of 22. The exclusive method gives Q1 7, Q3 18 and a 90th percentile of 28.4.",[529,530],"rect",{"x":531,"y":532,"width":533,"height":534,"fill":535},"4","0","744","206","#ffffff",[529,537],{"x":538,"y":539,"width":540,"height":541,"rx":542,"fill":543,"stroke":544,"style":545},"20.0","16.0","189.6","28.0","7","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[547,548,552],"text",{"x":549,"y":550,"style":551},"114.8","34.5","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Q3 · INC (pandas default)",[529,554],{"x":555,"y":539,"width":556,"height":541,"rx":542,"fill":557,"stroke":558,"style":559},"219.6","276.3","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.5px",[547,561,564],{"x":562,"y":550,"style":563},"503.9","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:start","16.5",[529,566],{"x":538,"y":567,"width":540,"height":541,"rx":542,"fill":543,"stroke":544,"style":545},"56.0",[547,569,571],{"x":549,"y":570,"style":551},"74.5","Q3 · EXC (weibull)",[529,573],{"x":555,"y":567,"width":574,"height":541,"rx":542,"fill":575,"stroke":576,"style":559},"301.4","#fdefd8","var(--gold,#b4740a)",[547,578,581],{"x":579,"y":570,"style":580},"529.0","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:start","18",[529,583],{"x":538,"y":584,"width":540,"height":541,"rx":542,"fill":543,"stroke":544,"style":545},"96.0",[547,586,588],{"x":549,"y":587,"style":551},"114.5","P90 · INC",[529,590],{"x":555,"y":584,"width":591,"height":541,"rx":542,"fill":557,"stroke":558,"style":559},"368.4",[547,593,595],{"x":594,"y":587,"style":563},"596.0","22",[529,597],{"x":538,"y":598,"width":540,"height":541,"rx":542,"fill":543,"stroke":544,"style":545},"136.0",[547,600,602],{"x":549,"y":601,"style":551},"154.5","P90 · EXC",[529,604],{"x":555,"y":598,"width":605,"height":541,"rx":542,"fill":575,"stroke":576,"style":559},"475.5",[547,607,609],{"x":608,"y":601,"style":580},"703.1","28.4",[547,611,615],{"x":612,"y":613,"style":614},"380.0","186.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","Q1 is 7 with both methods; the difference grows towards the tails.",[56,617,619],{"id":618},"step-4-modesngl-modemult-and-ties","Step 4 — MODE.SNGL, MODE.MULT and ties",[10,621,622,623,625,626,630],{},"The delivery times contain two values that each appear twice, 7 and 15. pandas returns every mode, sorted; Excel's ",[14,624,20],{}," returns one value — the mode that appears ",[627,628,629],"em",{},"first in the data",":",[61,632,634],{"className":111,"code":633,"language":113,"meta":66,"style":66},"print(\"pandas mode():\", days.mode().tolist())\n\ndef mode_sngl(s):\n    s = s.dropna()\n    counts = s.value_counts()\n    top = counts[counts == counts.max()].index\n    if counts.max() == 1:\n        return np.nan                      # Excel: #N\u002FA when no value repeats\n    return s[s.isin(top)].iloc[0]          # first occurrence in the original order\n\nprint(\"MODE.SNGL:\", mode_sngl(days))\nprint(\"MODE.SNGL on reversed data:\", mode_sngl(days[::-1]))\n",[14,635,636,648,652,664,674,684,700,715,727,743,747,760],{"__ignoreMap":66},[70,637,638,640,642,645],{"class":72,"line":73},[70,639,163],{"class":162},[70,641,207],{"class":124},[70,643,644],{"class":80},"\"pandas mode():\"",[70,646,647],{"class":124},", days.mode().tolist())\n",[70,649,650],{"class":72,"line":134},[70,651,138],{"emptyLinePlaceholder":137},[70,653,654,657,661],{"class":72,"line":141},[70,655,656],{"class":120},"def",[70,658,660],{"class":659},"s_Opv"," mode_sngl",[70,662,663],{"class":124},"(s):\n",[70,665,666,669,671],{"class":72,"line":159},[70,667,668],{"class":124},"    s ",[70,670,147],{"class":120},[70,672,673],{"class":124}," s.dropna()\n",[70,675,676,679,681],{"class":72,"line":175},[70,677,678],{"class":124},"    counts ",[70,680,147],{"class":120},[70,682,683],{"class":124}," s.value_counts()\n",[70,685,686,689,691,694,697],{"class":72,"line":202},[70,687,688],{"class":124},"    top ",[70,690,147],{"class":120},[70,692,693],{"class":124}," counts[counts ",[70,695,696],{"class":120},"==",[70,698,699],{"class":124}," counts.max()].index\n",[70,701,702,705,708,710,713],{"class":72,"line":441},[70,703,704],{"class":120},"    if",[70,706,707],{"class":124}," counts.max() ",[70,709,696],{"class":120},[70,711,712],{"class":162}," 1",[70,714,438],{"class":124},[70,716,717,720,723],{"class":72,"line":460},[70,718,719],{"class":120},"        return",[70,721,722],{"class":124}," np.nan                      ",[70,724,726],{"class":725},"s-wDw","# Excel: #N\u002FA when no value repeats\n",[70,728,729,732,735,737,740],{"class":72,"line":473},[70,730,731],{"class":120},"    return",[70,733,734],{"class":124}," s[s.isin(top)].iloc[",[70,736,532],{"class":162},[70,738,739],{"class":124},"]          ",[70,741,742],{"class":725},"# first occurrence in the original order\n",[70,744,745],{"class":72,"line":488},[70,746,138],{"emptyLinePlaceholder":137},[70,748,750,752,754,757],{"class":72,"line":749},11,[70,751,163],{"class":162},[70,753,207],{"class":124},[70,755,756],{"class":80},"\"MODE.SNGL:\"",[70,758,759],{"class":124},", mode_sngl(days))\n",[70,761,763,765,767,770,773,776,779],{"class":72,"line":762},12,[70,764,163],{"class":162},[70,766,207],{"class":124},[70,768,769],{"class":80},"\"MODE.SNGL on reversed data:\"",[70,771,772],{"class":124},", mode_sngl(days[::",[70,774,775],{"class":120},"-",[70,777,778],{"class":162},"1",[70,780,781],{"class":124},"]))\n",[10,783,784,785,788,789,791,792,795,796,798,799,802,803,806],{},"On this data both give 7, but for different reasons: pandas because 7 sorts before 15, Excel because 7 occurs first. Reverse the rows and ",[14,786,787],{},"mode_sngl"," returns 15 — as ",[14,790,20],{}," would — while ",[14,793,794],{},"days.mode()[0]"," still says 7. ",[14,797,23],{}," corresponds to the full ",[14,800,801],{},"mode()"," list, except that Excel returns the modes in order of appearance. When no value repeats, Excel returns ",[14,804,805],{},"#N\u002FA","; pandas returns every value, so check the maximum count.",[510,808,519,814,519,817,519,820,519,824,519,830,519,841,519,847,519,852,519,858,519,865,519,869,519,874,519,878,519,883,519,888,519,895,519,900,519,904,519,908,519,913,519,916,519,921,519,924],{"viewBox":809,"role":513,"ariaLabelledBy":810,"xmlns":517,"style":813},"4 0 752 207",[811,812],"mq-ties-t","mq-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",[521,815,816],{"id":811},"Breaking a tie between two modes",[525,818,819],{"id":812},"With 7 and 15 each appearing twice, pandas mode returns both values in sorted order and taking the first gives 7 regardless of row order. Excel MODE.SNGL returns the tied value that appears first in the range, which changes if the rows are reordered.",[529,821],{"x":531,"y":532,"width":822,"height":823,"fill":535},"752","207",[529,825],{"x":538,"y":539,"width":826,"height":827,"rx":828,"fill":557,"stroke":558,"style":829},"351.0","175.0","14","stroke-width:2px",[831,832,834],"g",{"transform":833},"translate(182.5,29.0) scale(1.083)",[835,836],"path",{"d":837,"fill":838,"stroke":839,"style":840},"M12 3c-4 0-4 1.6-4 3v2h4.2v1H6c-2 0-3 1.4-3 4s1 4 3 4h2v-2.6c0-1.6 1.3-2.9 2.9-2.9h4.2c1.3 0 2.4-1.1 2.4-2.4V6c0-1.6-1.8-3-5.5-3z M10 5.5h.01 M12 21c4 0 4-1.6 4-3v-2h-4.2v-1H18c2 0 3-1.4 3-4s-1-4-3-4h-2v2.6c0 1.6-1.3 2.9-2.9 2.9H8.9c-1.3 0-2.4 1.1-2.4 2.4V18c0 1.6 1.8 3 5.5 3z M14 18.5h.01","none","var(--brand-strong,#4338ca)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[547,842,846],{"x":843,"y":844,"style":845},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","pandas: s.mode()",[72,848],{"x1":849,"y1":850,"x2":851,"y2":850,"stroke":558,"style":545},"36.0","91.0","355.0",[547,853,857],{"x":854,"y":855,"style":856},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","returns [7, 15]",[831,859,861],{"transform":860},"translate(38.5,99.5) scale(0.625)",[835,862],{"d":863,"fill":838,"stroke":576,"style":864},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[547,866,868],{"x":854,"y":867,"style":856},"137.0","sorted, so [0] is always 7",[831,870,872],{"transform":871},"translate(38.5,125.5) scale(0.625)",[835,873],{"d":863,"fill":838,"stroke":576,"style":864},[547,875,877],{"x":854,"y":876,"style":856},"163.0","no value repeats: returns all values",[831,879,881],{"transform":880},"translate(38.5,151.5) scale(0.625)",[835,882],{"d":863,"fill":838,"stroke":576,"style":864},[529,884],{"x":885,"y":539,"width":826,"height":827,"rx":828,"fill":886,"stroke":887,"style":829},"389.0","#d9f4f1","var(--teal,#0f9488)",[831,889,891],{"transform":890},"translate(551.5,29.0) scale(1.083)",[835,892],{"d":893,"fill":838,"stroke":894,"style":840},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","var(--teal-ink,#0b6157)",[547,896,899],{"x":897,"y":844,"style":898},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Excel: MODE.SNGL",[72,901],{"x1":902,"y1":850,"x2":903,"y2":850,"stroke":887,"style":545},"405.0","724.0",[547,905,907],{"x":906,"y":855,"style":856},"429.0","returns one value",[831,909,911],{"transform":910},"translate(407.5,99.5) scale(0.625)",[835,912],{"d":863,"fill":838,"stroke":576,"style":864},[547,914,915],{"x":906,"y":867,"style":856},"first tied value in the range",[831,917,919],{"transform":918},"translate(407.5,125.5) scale(0.625)",[835,920],{"d":863,"fill":838,"stroke":576,"style":864},[547,922,923],{"x":906,"y":876,"style":856},"no value repeats: #N\u002FA",[831,925,927],{"transform":926},"translate(407.5,151.5) scale(0.625)",[835,928],{"d":863,"fill":838,"stroke":576,"style":864},[56,930,932],{"id":931},"step-5-per-group","Step 5 — Per group",[10,934,935,936,939],{},"Reports usually need the statistics per region, product or month. ",[14,937,938],{},"groupby"," with named aggregations gives the whole summary at once:",[61,941,943],{"className":111,"code":942,"language":113,"meta":66,"style":66},"df[\"Days\"] = days\nsummary = df.groupby(\"Region\")[\"Days\"].agg(\n    count=\"count\",\n    median=\"median\",\n    q1=lambda s: s.quantile(0.25),\n    q3=lambda s: s.quantile(0.75),\n    p90_exc=lambda s: float(np.quantile(s.dropna(), 0.9, method=\"weibull\")),\n    mode=mode_sngl,\n)\nsummary[\"iqr\"] = summary[\"q3\"] - summary[\"q1\"]\nprint(summary.round(2))\n",[14,944,945,961,982,995,1007,1023,1036,1064,1074,1078,1108],{"__ignoreMap":66},[70,946,947,950,953,956,958],{"class":72,"line":73},[70,948,949],{"class":124},"df[",[70,951,952],{"class":80},"\"Days\"",[70,954,955],{"class":124},"] ",[70,957,147],{"class":120},[70,959,960],{"class":124}," days\n",[70,962,963,966,968,971,974,977,979],{"class":72,"line":134},[70,964,965],{"class":124},"summary ",[70,967,147],{"class":120},[70,969,970],{"class":124}," df.groupby(",[70,972,973],{"class":80},"\"Region\"",[70,975,976],{"class":124},")[",[70,978,952],{"class":80},[70,980,981],{"class":124},"].agg(\n",[70,983,984,987,989,992],{"class":72,"line":141},[70,985,986],{"class":191},"    count",[70,988,147],{"class":120},[70,990,991],{"class":80},"\"count\"",[70,993,994],{"class":124},",\n",[70,996,997,1000,1002,1005],{"class":72,"line":159},[70,998,999],{"class":191},"    median",[70,1001,147],{"class":120},[70,1003,1004],{"class":80},"\"median\"",[70,1006,994],{"class":124},[70,1008,1009,1012,1015,1018,1020],{"class":72,"line":175},[70,1010,1011],{"class":191},"    q1",[70,1013,1014],{"class":120},"=lambda",[70,1016,1017],{"class":124}," s: s.quantile(",[70,1019,273],{"class":162},[70,1021,1022],{"class":124},"),\n",[70,1024,1025,1028,1030,1032,1034],{"class":72,"line":202},[70,1026,1027],{"class":191},"    q3",[70,1029,1014],{"class":120},[70,1031,1017],{"class":124},[70,1033,278],{"class":162},[70,1035,1022],{"class":124},[70,1037,1038,1041,1043,1046,1048,1051,1053,1055,1057,1059,1061],{"class":72,"line":441},[70,1039,1040],{"class":191},"    p90_exc",[70,1042,1014],{"class":120},[70,1044,1045],{"class":124}," s: ",[70,1047,409],{"class":162},[70,1049,1050],{"class":124},"(np.quantile(s.dropna(), ",[70,1052,296],{"class":162},[70,1054,17],{"class":124},[70,1056,383],{"class":191},[70,1058,147],{"class":120},[70,1060,328],{"class":80},[70,1062,1063],{"class":124},")),\n",[70,1065,1066,1069,1071],{"class":72,"line":460},[70,1067,1068],{"class":191},"    mode",[70,1070,147],{"class":120},[70,1072,1073],{"class":124},"mode_sngl,\n",[70,1075,1076],{"class":72,"line":473},[70,1077,156],{"class":124},[70,1079,1080,1083,1086,1088,1090,1093,1096,1098,1100,1102,1105],{"class":72,"line":488},[70,1081,1082],{"class":124},"summary[",[70,1084,1085],{"class":80},"\"iqr\"",[70,1087,955],{"class":124},[70,1089,147],{"class":120},[70,1091,1092],{"class":124}," summary[",[70,1094,1095],{"class":80},"\"q3\"",[70,1097,955],{"class":124},[70,1099,775],{"class":120},[70,1101,1092],{"class":124},[70,1103,1104],{"class":80},"\"q1\"",[70,1106,1107],{"class":124},"]\n",[70,1109,1110,1112,1115,1117],{"class":72,"line":749},[70,1111,163],{"class":162},[70,1113,1114],{"class":124},"(summary.round(",[70,1116,428],{"class":162},[70,1118,299],{"class":124},[10,1120,1121,1122,1124,1125,1127,1128,1130,1131,1133,1134,1137,1138,1141,1142,54],{},"The ",[14,1123,40],{}," column is empty for both regions: within each region no delivery time occurs twice, which is exactly the case where ",[14,1126,20],{}," returns ",[14,1129,805],{},", and the helper returns ",[14,1132,240],{}," to match. This replaces a grid of ",[14,1135,1136],{},"MEDIAN(IF(...))"," array formulas or helper columns in the spreadsheet. The interquartile range (",[14,1139,1140],{},"q3 − q1",") is the usual basis for flagging unusually slow deliveries, as described in ",[50,1143,1145],{"href":1144},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fdetect-outliers-in-excel-data-with-pandas\u002F","Detect Outliers in Excel Data with pandas",[56,1147,1149],{"id":1148},"step-6-check-against-the-spreadsheet","Step 6 — Check against the spreadsheet",[61,1151,1153],{"className":111,"code":1152,"language":113,"meta":66,"style":66},"import subprocess\nfrom openpyxl import Workbook, load_workbook\n\nwb = Workbook()\nws = wb.active\nfor v in df[\"DeliveryDays\"]:\n    ws.append([v])\nformulas = {\n    \"MEDIAN\": \"=MEDIAN(A1:A12)\",\n    \"MODE.SNGL\": \"=_xlfn.MODE.SNGL(A1:A12)\",\n    \"QUARTILE.INC 3\": \"=_xlfn.QUARTILE.INC(A1:A12,3)\",\n    \"QUARTILE.EXC 3\": \"=_xlfn.QUARTILE.EXC(A1:A12,3)\",\n    \"PERCENTILE.EXC 0.9\": \"=_xlfn.PERCENTILE.EXC(A1:A12,0.9)\",\n}\nfor i, f in enumerate(formulas.values(), start=1):\n    ws[f\"C{i}\"] = f\nwb.save(\"stats_check.xlsx\")\nsubprocess.run([\"soffice\", \"--headless\", \"--convert-to\", \"xlsx\", \"--outdir\", \"recalc\", \"stats_check.xlsx\"],\n               check=True, capture_output=True)\nsheet = load_workbook(\"recalc\u002Fstats_check.xlsx\", data_only=True).active\nmine = [days.median(), mode_sngl(days), days.quantile(0.75),\n        float(np.quantile(clean, 0.75, method=\"weibull\")), float(np.quantile(clean, 0.9, method=\"weibull\"))]\nfor (name, _), i, m in zip(formulas.items(), range(1, 6), mine):\n    print(f\"{name:20} sheet {sheet[f'C{i}'].value!s:>6}  pandas {round(m, 4)!s:>6}\")\n",[14,1154,1155,1162,1175,1179,1189,1199,1218,1223,1233,1246,1258,1270,1282,1295,1301,1327,1359,1370,1411,1433,1459,1474,1511,1542],{"__ignoreMap":66},[70,1156,1157,1159],{"class":72,"line":73},[70,1158,121],{"class":120},[70,1160,1161],{"class":124}," subprocess\n",[70,1163,1164,1167,1170,1172],{"class":72,"line":134},[70,1165,1166],{"class":120},"from",[70,1168,1169],{"class":124}," openpyxl ",[70,1171,121],{"class":120},[70,1173,1174],{"class":124}," Workbook, load_workbook\n",[70,1176,1177],{"class":72,"line":141},[70,1178,138],{"emptyLinePlaceholder":137},[70,1180,1181,1184,1186],{"class":72,"line":159},[70,1182,1183],{"class":124},"wb ",[70,1185,147],{"class":120},[70,1187,1188],{"class":124}," Workbook()\n",[70,1190,1191,1194,1196],{"class":72,"line":175},[70,1192,1193],{"class":124},"ws ",[70,1195,147],{"class":120},[70,1197,1198],{"class":124}," wb.active\n",[70,1200,1201,1204,1207,1210,1213,1215],{"class":72,"line":202},[70,1202,1203],{"class":120},"for",[70,1205,1206],{"class":124}," v ",[70,1208,1209],{"class":120},"in",[70,1211,1212],{"class":124}," df[",[70,1214,169],{"class":80},[70,1216,1217],{"class":124},"]:\n",[70,1219,1220],{"class":72,"line":441},[70,1221,1222],{"class":124},"    ws.append([v])\n",[70,1224,1225,1228,1230],{"class":72,"line":460},[70,1226,1227],{"class":124},"formulas ",[70,1229,147],{"class":120},[70,1231,1232],{"class":124}," {\n",[70,1234,1235,1238,1241,1244],{"class":72,"line":473},[70,1236,1237],{"class":80},"    \"MEDIAN\"",[70,1239,1240],{"class":124},": ",[70,1242,1243],{"class":80},"\"=MEDIAN(A1:A12)\"",[70,1245,994],{"class":124},[70,1247,1248,1251,1253,1256],{"class":72,"line":488},[70,1249,1250],{"class":80},"    \"MODE.SNGL\"",[70,1252,1240],{"class":124},[70,1254,1255],{"class":80},"\"=_xlfn.MODE.SNGL(A1:A12)\"",[70,1257,994],{"class":124},[70,1259,1260,1263,1265,1268],{"class":72,"line":749},[70,1261,1262],{"class":80},"    \"QUARTILE.INC 3\"",[70,1264,1240],{"class":124},[70,1266,1267],{"class":80},"\"=_xlfn.QUARTILE.INC(A1:A12,3)\"",[70,1269,994],{"class":124},[70,1271,1272,1275,1277,1280],{"class":72,"line":762},[70,1273,1274],{"class":80},"    \"QUARTILE.EXC 3\"",[70,1276,1240],{"class":124},[70,1278,1279],{"class":80},"\"=_xlfn.QUARTILE.EXC(A1:A12,3)\"",[70,1281,994],{"class":124},[70,1283,1285,1288,1290,1293],{"class":72,"line":1284},13,[70,1286,1287],{"class":80},"    \"PERCENTILE.EXC 0.9\"",[70,1289,1240],{"class":124},[70,1291,1292],{"class":80},"\"=_xlfn.PERCENTILE.EXC(A1:A12,0.9)\"",[70,1294,994],{"class":124},[70,1296,1298],{"class":72,"line":1297},14,[70,1299,1300],{"class":124},"}\n",[70,1302,1304,1306,1309,1311,1314,1317,1320,1322,1324],{"class":72,"line":1303},15,[70,1305,1203],{"class":120},[70,1307,1308],{"class":124}," i, f ",[70,1310,1209],{"class":120},[70,1312,1313],{"class":162}," enumerate",[70,1315,1316],{"class":124},"(formulas.values(), ",[70,1318,1319],{"class":191},"start",[70,1321,147],{"class":120},[70,1323,778],{"class":162},[70,1325,1326],{"class":124},"):\n",[70,1328,1330,1333,1336,1339,1343,1346,1349,1352,1354,1356],{"class":72,"line":1329},16,[70,1331,1332],{"class":124},"    ws[",[70,1334,1335],{"class":120},"f",[70,1337,1338],{"class":80},"\"C",[70,1340,1342],{"class":1341},"sSjpA","{",[70,1344,1345],{"class":124},"i",[70,1347,1348],{"class":1341},"}",[70,1350,1351],{"class":80},"\"",[70,1353,955],{"class":124},[70,1355,147],{"class":120},[70,1357,1358],{"class":124}," f\n",[70,1360,1362,1365,1368],{"class":72,"line":1361},17,[70,1363,1364],{"class":124},"wb.save(",[70,1366,1367],{"class":80},"\"stats_check.xlsx\"",[70,1369,156],{"class":124},[70,1371,1373,1376,1379,1381,1384,1386,1389,1391,1394,1396,1399,1401,1404,1406,1408],{"class":72,"line":1372},18,[70,1374,1375],{"class":124},"subprocess.run([",[70,1377,1378],{"class":80},"\"soffice\"",[70,1380,17],{"class":124},[70,1382,1383],{"class":80},"\"--headless\"",[70,1385,17],{"class":124},[70,1387,1388],{"class":80},"\"--convert-to\"",[70,1390,17],{"class":124},[70,1392,1393],{"class":80},"\"xlsx\"",[70,1395,17],{"class":124},[70,1397,1398],{"class":80},"\"--outdir\"",[70,1400,17],{"class":124},[70,1402,1403],{"class":80},"\"recalc\"",[70,1405,17],{"class":124},[70,1407,1367],{"class":80},[70,1409,1410],{"class":124},"],\n",[70,1412,1414,1417,1419,1422,1424,1427,1429,1431],{"class":72,"line":1413},19,[70,1415,1416],{"class":191},"               check",[70,1418,147],{"class":120},[70,1420,1421],{"class":162},"True",[70,1423,17],{"class":124},[70,1425,1426],{"class":191},"capture_output",[70,1428,147],{"class":120},[70,1430,1421],{"class":162},[70,1432,156],{"class":124},[70,1434,1436,1439,1441,1444,1447,1449,1452,1454,1456],{"class":72,"line":1435},20,[70,1437,1438],{"class":124},"sheet ",[70,1440,147],{"class":120},[70,1442,1443],{"class":124}," load_workbook(",[70,1445,1446],{"class":80},"\"recalc\u002Fstats_check.xlsx\"",[70,1448,17],{"class":124},[70,1450,1451],{"class":191},"data_only",[70,1453,147],{"class":120},[70,1455,1421],{"class":162},[70,1457,1458],{"class":124},").active\n",[70,1460,1462,1465,1467,1470,1472],{"class":72,"line":1461},21,[70,1463,1464],{"class":124},"mine ",[70,1466,147],{"class":120},[70,1468,1469],{"class":124}," [days.median(), mode_sngl(days), days.quantile(",[70,1471,278],{"class":162},[70,1473,1022],{"class":124},[70,1475,1477,1480,1482,1484,1486,1488,1490,1492,1494,1496,1498,1500,1502,1504,1506,1508],{"class":72,"line":1476},22,[70,1478,1479],{"class":162},"        float",[70,1481,412],{"class":124},[70,1483,278],{"class":162},[70,1485,17],{"class":124},[70,1487,383],{"class":191},[70,1489,147],{"class":120},[70,1491,328],{"class":80},[70,1493,425],{"class":124},[70,1495,409],{"class":162},[70,1497,412],{"class":124},[70,1499,296],{"class":162},[70,1501,17],{"class":124},[70,1503,383],{"class":191},[70,1505,147],{"class":120},[70,1507,328],{"class":80},[70,1509,1510],{"class":124},"))]\n",[70,1512,1514,1516,1519,1521,1524,1527,1530,1532,1534,1536,1539],{"class":72,"line":1513},23,[70,1515,1203],{"class":120},[70,1517,1518],{"class":124}," (name, _), i, m ",[70,1520,1209],{"class":120},[70,1522,1523],{"class":162}," zip",[70,1525,1526],{"class":124},"(formulas.items(), ",[70,1528,1529],{"class":162},"range",[70,1531,207],{"class":124},[70,1533,778],{"class":162},[70,1535,17],{"class":124},[70,1537,1538],{"class":162},"6",[70,1540,1541],{"class":124},"), mine):\n",[70,1543,1545,1547,1549,1551,1553,1555,1558,1561,1563,1566,1568,1571,1573,1576,1578,1580,1582,1585,1588,1591,1593,1596,1598,1600,1603,1605,1608,1610,1612,1614],{"class":72,"line":1544},24,[70,1546,463],{"class":162},[70,1548,207],{"class":124},[70,1550,1335],{"class":120},[70,1552,1351],{"class":80},[70,1554,1342],{"class":1341},[70,1556,1557],{"class":124},"name",[70,1559,1560],{"class":120},":20",[70,1562,1348],{"class":1341},[70,1564,1565],{"class":80}," sheet ",[70,1567,1342],{"class":1341},[70,1569,1570],{"class":124},"sheet[",[70,1572,1335],{"class":120},[70,1574,1575],{"class":80},"'C",[70,1577,1342],{"class":1341},[70,1579,1345],{"class":124},[70,1581,1348],{"class":1341},[70,1583,1584],{"class":80},"'",[70,1586,1587],{"class":124},"].value",[70,1589,1590],{"class":120},"!s:>6",[70,1592,1348],{"class":1341},[70,1594,1595],{"class":80},"  pandas ",[70,1597,1342],{"class":1341},[70,1599,404],{"class":162},[70,1601,1602],{"class":124},"(m, ",[70,1604,531],{"class":162},[70,1606,1607],{"class":124},")",[70,1609,1590],{"class":120},[70,1611,1348],{"class":1341},[70,1613,1351],{"class":80},[70,1615,156],{"class":124},[10,1617,1121,1618,17,1621,41,1623,1625,1626,1629,1630,1633,1634,54],{},[14,1619,1620],{},".INC",[14,1622,499],{},[14,1624,20],{}," results agree, with the text cell skipped on both sides. Note the ",[14,1627,1628],{},"_xlfn."," prefix: the dotted function names were added in Excel 2010 and must be written that way from Python, or the spreadsheet shows ",[14,1631,1632],{},"#NAME?"," — see ",[50,1635,1637],{"href":1636},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-name-error-for-new-excel-functions-written-by-python\u002F","Fix #NAME? Errors for New Excel Functions Written by Python",[510,1639,519,1644,519,1647,519,1650,519,1653,519,1657,519,1662,519,1668,519,1671,519,1675,519,1678,519,1682,519,1684,519,1689,519,1691,519,1698,519,1701,519,1705,519,1707,519,1710,519,1712,519,1717,519,1720,519,1724,519,1726,519,1730,519,1732,519,1735,519,1738,519,1741,519,1743,519,1747,519,1749,519,1752,519,1755,519,1758,519,1760,519,1764,519,1766],{"viewBox":1640,"role":513,"ariaLabelledBy":1641,"xmlns":517,"style":813},"4 0 752 242",[1642,1643],"mq-map-t","mq-map-d",[521,1645,1646],{"id":1642},"Excel statistics functions and their pandas equivalents",[525,1648,1649],{"id":1643},"MEDIAN maps to Series.median. QUARTILE.INC and PERCENTILE.INC map to Series.quantile with the default linear method. QUARTILE.EXC and PERCENTILE.EXC map to numpy quantile with method weibull. MODE.MULT maps to Series.mode, and MODE.SNGL needs the first-occurrence rule.",[529,1651],{"x":531,"y":532,"width":822,"height":1652,"fill":535},"242",[529,1654],{"x":538,"y":539,"width":1655,"height":1656,"rx":532,"fill":535,"stroke":544,"style":545},"245.7","40.0",[529,1658],{"x":1659,"y":539,"width":1660,"height":1656,"rx":532,"fill":557,"stroke":558,"style":1661},"265.7","237.2","stroke-width:1.2px",[547,1663,1667],{"x":1664,"y":1665,"style":1666},"384.2","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","pandas \u002F NumPy",[529,1669],{"x":1670,"y":539,"width":1660,"height":1656,"rx":532,"fill":557,"stroke":558,"style":1661},"502.8",[547,1672,1674],{"x":1673,"y":1665,"style":1666},"621.4","same result?",[529,1676],{"x":538,"y":567,"width":1655,"height":1677,"rx":532,"fill":543,"stroke":544,"style":545},"34.0",[547,1679,16],{"x":1680,"y":844,"style":1681},"32.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start",[529,1683],{"x":1659,"y":567,"width":1660,"height":1677,"rx":532,"fill":535,"stroke":544,"style":545},[547,1685,1688],{"x":1664,"y":1686,"style":1687},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","s.median()",[529,1690],{"x":1670,"y":567,"width":1660,"height":1677,"rx":532,"fill":535,"stroke":544,"style":545},[831,1692,1694],{"transform":1693},"translate(612.4,64.0) scale(0.750)",[835,1695],{"d":1696,"fill":838,"stroke":887,"style":1697},"M5 12.5l4.5 4.5L19 7","stroke-width:2.40px;stroke-linecap:round;stroke-linejoin:round",[529,1699],{"x":538,"y":1700,"width":1655,"height":1677,"rx":532,"fill":543,"stroke":544,"style":545},"90.0",[547,1702,1704],{"x":1680,"y":1703,"style":1681},"112.0","QUARTILE.INC, PERCENTILE.INC",[529,1706],{"x":1659,"y":1700,"width":1660,"height":1677,"rx":532,"fill":535,"stroke":544,"style":545},[547,1708,1709],{"x":1664,"y":855,"style":1687},"s.quantile(p)",[529,1711],{"x":1670,"y":1700,"width":1660,"height":1677,"rx":532,"fill":535,"stroke":544,"style":545},[831,1713,1715],{"transform":1714},"translate(612.4,98.0) scale(0.750)",[835,1716],{"d":1696,"fill":838,"stroke":887,"style":1697},[529,1718],{"x":538,"y":1719,"width":1655,"height":1677,"rx":532,"fill":543,"stroke":544,"style":545},"124.0",[547,1721,1723],{"x":1680,"y":1722,"style":1681},"146.0","QUARTILE.EXC, PERCENTILE.EXC",[529,1725],{"x":1659,"y":1719,"width":1660,"height":1677,"rx":532,"fill":535,"stroke":544,"style":545},[547,1727,1729],{"x":1664,"y":1728,"style":1687},"145.0","np.quantile(…, method=\"weibull\")",[529,1731],{"x":1670,"y":1719,"width":1660,"height":1677,"rx":532,"fill":535,"stroke":544,"style":545},[547,1733,1734],{"x":1673,"y":1728,"style":1687},"yes, except #NUM! tails",[529,1736],{"x":538,"y":1737,"width":1655,"height":1677,"rx":532,"fill":543,"stroke":544,"style":545},"158.0",[547,1739,23],{"x":1680,"y":1740,"style":1681},"180.0",[529,1742],{"x":1659,"y":1737,"width":1660,"height":1677,"rx":532,"fill":535,"stroke":544,"style":545},[547,1744,1746],{"x":1664,"y":1745,"style":1687},"179.0","s.mode()",[529,1748],{"x":1670,"y":1737,"width":1660,"height":1677,"rx":532,"fill":535,"stroke":544,"style":545},[547,1750,1751],{"x":1673,"y":1745,"style":1687},"order differs",[529,1753],{"x":538,"y":1754,"width":1655,"height":1677,"rx":532,"fill":543,"stroke":544,"style":545},"192.0",[547,1756,20],{"x":1680,"y":1757,"style":1681},"214.0",[529,1759],{"x":1659,"y":1754,"width":1660,"height":1677,"rx":532,"fill":535,"stroke":544,"style":545},[547,1761,1763],{"x":1664,"y":1762,"style":1687},"213.0","first tied value",[529,1765],{"x":1670,"y":1754,"width":1660,"height":1677,"rx":532,"fill":535,"stroke":544,"style":545},[547,1767,1768],{"x":1673,"y":1762,"style":1687},"needs a helper",[56,1770,1772],{"id":1771},"step-7-a-five-number-summary-for-the-report","Step 7 — A five-number summary for the report",[10,1774,1775],{},"Medians and quartiles are most useful together: the minimum, Q1, median, Q3 and maximum — the \"five-number summary\" — describe a distribution in one row and are exactly what a box-and-whisker chart draws. Build it per region and write it with a label that says which quartile method was used, so nobody has to guess later:",[61,1777,1779],{"className":111,"code":1778,"language":113,"meta":66,"style":66},"five = df.groupby(\"Region\")[\"Days\"].describe()[[\"min\", \"25%\", \"50%\", \"75%\", \"max\"]]\nfive.columns = [\"Min\", \"Q1\", \"Median\", \"Q3\", \"Max\"]\nfive[\"Method\"] = \"QUARTILE.INC (linear)\"\nwith pd.ExcelWriter(\"delivery_summary.xlsx\", engine=\"xlsxwriter\") as xw:\n    five.to_excel(xw, sheet_name=\"Summary\")\n    xw.sheets[\"Summary\"].set_column(\"A:F\", 12)\n    xw.sheets[\"Summary\"].set_column(\"G:G\", 24)\nprint(five)\n",[14,1780,1781,1825,1860,1875,1904,1919,1939,1957],{"__ignoreMap":66},[70,1782,1783,1786,1788,1790,1792,1794,1796,1799,1802,1804,1807,1809,1812,1814,1817,1819,1822],{"class":72,"line":73},[70,1784,1785],{"class":124},"five ",[70,1787,147],{"class":120},[70,1789,970],{"class":124},[70,1791,973],{"class":80},[70,1793,976],{"class":124},[70,1795,952],{"class":80},[70,1797,1798],{"class":124},"].describe()[[",[70,1800,1801],{"class":80},"\"min\"",[70,1803,17],{"class":124},[70,1805,1806],{"class":80},"\"25%\"",[70,1808,17],{"class":124},[70,1810,1811],{"class":80},"\"50%\"",[70,1813,17],{"class":124},[70,1815,1816],{"class":80},"\"75%\"",[70,1818,17],{"class":124},[70,1820,1821],{"class":80},"\"max\"",[70,1823,1824],{"class":124},"]]\n",[70,1826,1827,1830,1832,1835,1838,1840,1843,1845,1848,1850,1853,1855,1858],{"class":72,"line":134},[70,1828,1829],{"class":124},"five.columns ",[70,1831,147],{"class":120},[70,1833,1834],{"class":124}," [",[70,1836,1837],{"class":80},"\"Min\"",[70,1839,17],{"class":124},[70,1841,1842],{"class":80},"\"Q1\"",[70,1844,17],{"class":124},[70,1846,1847],{"class":80},"\"Median\"",[70,1849,17],{"class":124},[70,1851,1852],{"class":80},"\"Q3\"",[70,1854,17],{"class":124},[70,1856,1857],{"class":80},"\"Max\"",[70,1859,1107],{"class":124},[70,1861,1862,1865,1868,1870,1872],{"class":72,"line":141},[70,1863,1864],{"class":124},"five[",[70,1866,1867],{"class":80},"\"Method\"",[70,1869,955],{"class":124},[70,1871,147],{"class":120},[70,1873,1874],{"class":80}," \"QUARTILE.INC (linear)\"\n",[70,1876,1877,1880,1883,1886,1888,1891,1893,1896,1899,1901],{"class":72,"line":159},[70,1878,1879],{"class":120},"with",[70,1881,1882],{"class":124}," pd.ExcelWriter(",[70,1884,1885],{"class":80},"\"delivery_summary.xlsx\"",[70,1887,17],{"class":124},[70,1889,1890],{"class":191},"engine",[70,1892,147],{"class":120},[70,1894,1895],{"class":80},"\"xlsxwriter\"",[70,1897,1898],{"class":124},") ",[70,1900,128],{"class":120},[70,1902,1903],{"class":124}," xw:\n",[70,1905,1906,1909,1912,1914,1917],{"class":72,"line":175},[70,1907,1908],{"class":124},"    five.to_excel(xw, ",[70,1910,1911],{"class":191},"sheet_name",[70,1913,147],{"class":120},[70,1915,1916],{"class":80},"\"Summary\"",[70,1918,156],{"class":124},[70,1920,1921,1924,1926,1929,1932,1934,1937],{"class":72,"line":202},[70,1922,1923],{"class":124},"    xw.sheets[",[70,1925,1916],{"class":80},[70,1927,1928],{"class":124},"].set_column(",[70,1930,1931],{"class":80},"\"A:F\"",[70,1933,17],{"class":124},[70,1935,1936],{"class":162},"12",[70,1938,156],{"class":124},[70,1940,1941,1943,1945,1947,1950,1952,1955],{"class":72,"line":441},[70,1942,1923],{"class":124},[70,1944,1916],{"class":80},[70,1946,1928],{"class":124},[70,1948,1949],{"class":80},"\"G:G\"",[70,1951,17],{"class":124},[70,1953,1954],{"class":162},"24",[70,1956,156],{"class":124},[70,1958,1959,1961],{"class":72,"line":460},[70,1960,163],{"class":162},[70,1962,1963],{"class":124},"(five)\n",[10,1965,1966,1969,1970,1972,1973,1975,1976,54],{},[14,1967,1968],{},"describe()"," uses the same linear method as ",[14,1971,44],{},", so its 25%, 50% and 75% columns are the ",[14,1974,1620],{}," quartiles. Stating the method in the output costs one column and prevents the most common dispute when a Python report replaces a spreadsheet: two correct numbers that differ because they answer slightly different questions. To turn the summary into a chart, see ",[50,1977,1979],{"href":1978},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-box-and-whisker-chart-in-excel-with-python\u002F","Create a Box and Whisker Chart in Excel with Python",[56,1981,1983],{"id":1982},"performance-notes","Performance notes",[10,1985,1986,1987,41,1990,1992,1993,1996,1997,2000],{},"All of these statistics need sorted data or a partial sort, so they cost more than a mean, but pandas and NumPy implement them in compiled code: a median or set of quantiles over a million values takes a few tens of milliseconds. The slow part in the examples above is the Python-level ",[14,1988,1989],{},"lambda",[14,1991,787],{}," called once per group; for thousands of groups, compute ",[14,1994,1995],{},"quantile([0.25, 0.75])"," once on the grouped object (",[14,1998,1999],{},"df.groupby(\"Region\")[\"Days\"].quantile([0.25, 0.75]).unstack()",") instead of separate lambdas, and keep the custom mode helper for the columns that need Excel's exact tie-breaking.",[56,2002,2004],{"id":2003},"common-pitfalls","Common pitfalls",[2006,2007,2008,2020,2031,2040,2055],"ul",{},[2009,2010,2011,2015,2016,2019],"li",{},[2012,2013,2014],"strong",{},"Text in numeric columns."," Excel skips it; pandas refuses to compute. Coerce with ",[14,2017,2018],{},"pd.to_numeric(errors=\"coerce\")"," — after deciding what the text means.",[2009,2021,2022,2025,2026,41,2028,2030],{},[2012,2023,2024],{},"Assuming one quartile definition."," ",[14,2027,1620],{},[14,2029,499],{}," differ, more so on small samples; confirm which one the original report used.",[2009,2032,2033,2039],{},[2012,2034,2035,2038],{},[14,2036,2037],{},"mode()[0]"," as MODE.SNGL."," Correct only when the first-occurring tied value also happens to be the smallest.",[2009,2041,2042,2025,2045,2047,2048,2050,2051,2054],{},[2012,2043,2044],{},"Empty groups.",[14,2046,37],{}," of an empty group is ",[14,2049,240],{},"; filter groups by ",[14,2052,2053],{},"count"," before reporting.",[2009,2056,2057,2060],{},[2012,2058,2059],{},"Rounding before computing."," Round the outputs for display, never the inputs.",[56,2062,2064],{"id":2063},"conclusion","Conclusion",[10,2066,2067,2069,2070,2073,2074,2076,2077,2080,2081,2083,2084,2086,2087,2090,2091,2093,2094,2096,2097,2099,2100,2102],{},[14,2068,16],{}," is ",[14,2071,2072],{},"median()",", the ",[14,2075,1620],{}," quartiles and percentiles are pandas' default ",[14,2078,2079],{},"quantile()",", and the ",[14,2082,499],{}," versions are NumPy's ",[14,2085,44],{}," with ",[14,2088,2089],{},"method=\"weibull\"",". Mode needs care: pandas returns all modes sorted, while ",[14,2092,20],{}," returns the first tied value in the data and ",[14,2095,805],{}," when nothing repeats. Coerce text to ",[14,2098,240],{}," to mirror Excel's skipping, compute per group with named aggregations, and confirm against recalculated spreadsheet formulas — written with the ",[14,2101,1628],{}," prefix.",[56,2104,2106],{"id":2105},"frequently-asked-questions","Frequently asked questions",[10,2108,2109,2112],{},[2012,2110,2111],{},"Which pandas quantile method matches Excel QUARTILE.INC?","\nThe default, linear interpolation: s.quantile(0.25) and s.quantile(0.75) equal QUARTILE.INC(range, 1) and QUARTILE.INC(range, 3), and s.quantile(p) equals PERCENTILE.INC.",[10,2114,2115,2118],{},[2012,2116,2117],{},"How do I calculate QUARTILE.EXC in Python?","\nUse numpy.quantile(values, q, method=\"weibull\") on the non-missing values. pandas' quantile has no equivalent option.",[10,2120,2121,2124,2125,2127],{},[2012,2122,2123],{},"Why does pandas mode() give a different answer from Excel MODE?","\nWhen several values tie, pandas returns all of them sorted, so mode()",[70,2126,532],{}," is the smallest. MODE.SNGL returns the tied value that appears first in the range, which depends on row order.",[10,2129,2130,2133],{},[2012,2131,2132],{},"Does pandas median ignore text like Excel does?","\nNot automatically. Convert the column with pd.to_numeric(column, errors=\"coerce\"), which turns text into NaN; median and quantile then skip it, as Excel skips text cells.",[56,2135,2137],{"id":2136},"related","Related",[2006,2139,2140,2146,2153,2160],{},[2009,2141,2142,2143,2145],{},"Up one level: ",[50,2144,53],{"href":52}," — Excel functions translated to pandas.",[2009,2147,2148,2152],{},[50,2149,2151],{"href":2150},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fstdev-and-var-equivalents-in-pandas\u002F","STDEV and VAR Equivalents in pandas"," — spread measures and their sample-versus-population defaults.",[2009,2154,2155,2159],{},[50,2156,2158],{"href":2157},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frank-and-percentile-formulas-in-pandas\u002F","Rank and Percentile Formulas in pandas"," — RANK and PERCENTRANK.",[2009,2161,2162,2166],{},[50,2163,2165],{"href":2164},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Faverageifs-maxifs-minifs-in-pandas\u002F","AVERAGEIFS, MAXIFS and MINIFS in pandas"," — conditional statistics per group.",[2168,2169,2170],"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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":66,"searchDepth":134,"depth":134,"links":2172},[2173,2174,2175,2176,2177,2178,2179,2180,2181,2182,2183,2184,2185],{"id":58,"depth":134,"text":59},{"id":107,"depth":134,"text":108},{"id":252,"depth":134,"text":253},{"id":321,"depth":134,"text":322},{"id":618,"depth":134,"text":619},{"id":931,"depth":134,"text":932},{"id":1148,"depth":134,"text":1149},{"id":1771,"depth":134,"text":1772},{"id":1982,"depth":134,"text":1983},{"id":2003,"depth":134,"text":2004},{"id":2063,"depth":134,"text":2064},{"id":2105,"depth":134,"text":2106},{"id":2136,"depth":134,"text":2137},"2026-10-02","Match Excel's MEDIAN, MODE.SNGL, MODE.MULT, QUARTILE.INC, QUARTILE.EXC, PERCENTILE.INC and PERCENTILE.EXC in pandas: which quantile method equals which Excel function, why pandas' mode differs from MODE.SNGL when there is a tie, how Excel ignores text in ranges, per-group medians and quartiles, and a spreadsheet cross-check.","md",[2190,2192,2194,2196],{"q":2111,"a":2191},"The default, linear interpolation: s.quantile(0.25) and s.quantile(0.75) equal QUARTILE.INC(range, 1) and QUARTILE.INC(range, 3), and s.quantile(p) equals PERCENTILE.INC.",{"q":2117,"a":2193},"Use numpy.quantile(values, q, method=\"weibull\") on the non-missing values. pandas' quantile has no equivalent option.",{"q":2123,"a":2195},"When several values tie, pandas returns all of them sorted, so mode()[0] is the smallest. MODE.SNGL returns the tied value that appears first in the range, which depends on row order.",{"q":2132,"a":2197},"Not automatically. Convert the column with pd.to_numeric(column, errors=\"coerce\"), which turns text into NaN; median and quantile then skip it, as Excel skips text cells.","median mode and quartile equivalents in pandas",null,{"breadcrumb":2201},[2202,2204,2207],{"name":2203,"item":315},"Home",{"name":2205,"item":2206},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":53,"item":52},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmedian-mode-and-quartile-equivalents-in-pandas",{"title":2210,"description":2211},"Excel MEDIAN, MODE, QUARTILE and PERCENTILE in pandas","pandas equivalents of Excel MEDIAN, MODE.SNGL\u002FMODE.MULT and QUARTILE\u002FPERCENTILE .INC and .EXC: quantile() for INC, numpy method=\"weibull\" for EXC, tie-breaking for MODE, ignoring text, groupby statistics, and verified results.","median-mode-and-quartile-equivalents-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmedian-mode-and-quartile-equivalents-in-pandas\u002Findex",[2215,2216],"formula-equivalents","pandas","how-to","8VIafoA_eviZAOWjvT_PeZSRII3E-wm1If9S317DvuI",[2220,2224],{"title":2221,"path":2222,"stem":2223,"children":-1},"Match Excel Rounding in pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmatch-excel-rounding-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmatch-excel-rounding-in-pandas\u002Findex",{"title":2225,"path":2226,"stem":2227,"children":-1},"PMT, NPV, IRR and XIRR Excel Functions in Python","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fpmt-npv-and-irr-in-python","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fpmt-npv-and-irr-in-python\u002Findex",[2229,2231],{"path":2230,"title":53},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas",{"path":2232,"title":2205},"\u002Fadvanced-data-transformation-and-cleaning",[],[2235,2238,2241,2244,2247,2250],{"path":2236,"title":2237,"kind":2217},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fvlookup-equivalent-in-pandas-for-excel-files","The VLOOKUP Equivalent in pandas for Excel Files",{"path":2239,"title":2240,"kind":2217},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fextract-numbers-and-codes-from-text-with-pandas","Extract Numbers and Codes from Text with pandas",{"path":2242,"title":2243,"kind":2217},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fsplit-full-names-into-first-and-last-names-with-pandas","Split Full Names into First and Last Names with pandas",{"path":2245,"title":2246,"kind":2217},"\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":2248,"title":2249,"kind":2217},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcalculate-percent-of-total-per-group-in-pandas","Calculate Percent of Total per Group in pandas",{"path":2251,"title":2252,"kind":2217},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcount-distinct-values-per-group-in-pandas","Count Distinct Values per Group in pandas",1790972088402]