[{"data":1,"prerenderedAt":1914},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Faverageifs-maxifs-minifs-in-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Faverageifs-maxifs-minifs-in-pandas":1880,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Faverageifs-maxifs-minifs-in-pandas":1889,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Faverageifs-maxifs-minifs-in-pandas":1894,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Faverageifs-maxifs-minifs-in-pandas":1895},{"id":4,"title":5,"body":6,"dateModified":1847,"datePublished":1847,"description":1848,"extension":1849,"faq":1850,"keyword":1860,"kind":1861,"meta":1862,"navigation":283,"path":1870,"seo":1871,"slug":1873,"stem":1874,"topics":1875,"type":1878,"__hash__":1879},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Faverageifs-maxifs-minifs-in-pandas\u002Findex.md","AVERAGEIFS, MAXIFS and MINIFS Equivalents in pandas",{"type":7,"value":8,"toc":1833},"minimark",[9,40,220,225,252,255,608,633,637,647,736,747,751,769,803,806,891,897,988,992,1006,1094,1119,1123,1132,1205,1216,1220,1231,1332,1338,1448,1452,1455,1558,1561,1565,1572,1665,1672,1676,1732,1736,1765,1769,1775,1781,1787,1797,1801,1829],[10,11,12,16,17,20,21,24,25,30,31,20,35,39],"p",{},[13,14,15],"code",{},"AVERAGEIFS",", ",[13,18,19],{},"MAXIFS"," and ",[13,22,23],{},"MINIFS"," answer questions like \"average order value in the North this quarter\" or \"largest deal each rep closed\". They filter a range by one or more criteria and aggregate what is left. In pandas the filter is a boolean mask and the aggregate a method call, but a faithful translation also has to match what Excel does with blanks, text and — the classic surprise — a criterion that matches nothing. This guide, part of ",[26,27,29],"a",{"href":28},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002F","Excel Formula Equivalents in pandas",", covers the one-off, table and per-row forms. Conditional sums and counts have their own guides: ",[26,32,34],{"href":33},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsumif-and-sumifs-equivalent-in-pandas\u002F","SUMIF and SUMIFS",[26,36,38],{"href":37},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fcountif-and-countifs-equivalent-in-pandas\u002F","COUNTIF and COUNTIFS",".",[41,42,50,51,50,55,50,59,50,67,50,77,50,88,50,95,50,100,50,106,50,111,50,114,50,120,50,125,50,129,50,133,50,137,50,140,50,145,50,152,50,157,50,160,50,163,50,167,50,170,50,175,50,182,50,187,50,190,50,193,50,197,50,200,50,203,50,209,50,213,50,217],"svg",{"viewBox":43,"role":44,"ariaLabelledBy":45,"xmlns":48,"style":49},"4 0 752 142","img",[46,47],"ai-anatomy-t","ai-anatomy-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[52,53,54],"title",{"id":46},"How AVERAGEIFS becomes pandas",[56,57,58],"desc",{"id":47},"The criteria ranges and criteria become one boolean mask per condition, combined with the and operator. The mask selects rows from the value column, text and blanks are converted to missing values, and mean, max or min aggregates what is left.",[60,61],"rect",{"x":62,"y":63,"width":64,"height":65,"fill":66},"4","0","752","142","#ffffff",[60,68],{"x":69,"y":70,"width":71,"height":72,"rx":73,"fill":74,"stroke":75,"style":76},"20.0","16.0","115.2","110.5","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[78,79,81],"g",{"transform":80},"translate(65.6,37.8) scale(1.000)",[82,83],"path",{"d":84,"fill":85,"stroke":86,"style":87},"M3 5h18l-7 8v6l-4 2v-8z","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[89,90,94],"text",{"x":91,"y":92,"style":93},"77.6","84.2","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Criteria pairs",[89,96,99],{"x":91,"y":97,"style":98},"103.8","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","range, criterion",[101,102],"line",{"x1":103,"y1":104,"x2":105,"y2":104,"stroke":75,"style":76},"140.2","71.2","162.2",[107,108],"polygon",{"points":109,"fill":110},"166.2,71.2 157.2,76.2 157.2,66.2","#5b5cf0",[60,112],{"x":113,"y":70,"width":71,"height":72,"rx":73,"fill":74,"stroke":75,"style":76},"171.2",[78,115,117],{"transform":116},"translate(216.8,29.0) scale(1.000)",[82,118],{"d":119,"fill":85,"stroke":86,"style":87},"M5 12.5l4.5 4.5L19 7",[89,121,124],{"x":122,"y":123,"style":93},"228.8","75.5","Boolean",[89,126,128],{"x":122,"y":127,"style":93},"93.0","masks",[89,130,132],{"x":122,"y":131,"style":98},"112.5","one per pair",[101,134],{"x1":135,"y1":104,"x2":136,"y2":104,"stroke":75,"style":76},"291.4","313.4",[107,138],{"points":139,"fill":110},"317.4,71.2 308.4,76.2 308.4,66.2",[60,141],{"x":142,"y":70,"width":71,"height":72,"rx":73,"fill":143,"stroke":144,"style":76},"322.4","#d9f4f1","var(--teal,#0f9488)",[78,146,148],{"transform":147},"translate(368.0,29.0) scale(1.000)",[82,149],{"d":150,"fill":85,"stroke":151,"style":87},"M6 4v5a5 5 0 0 0 5 5h7 M6 20v-5 M15 11l3 3-3 3","var(--teal-ink,#0b6157)",[89,153,156],{"x":154,"y":123,"style":155},"380.0","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Combine",[89,158,159],{"x":154,"y":127,"style":155},"with &",[89,161,162],{"x":154,"y":131,"style":98},"all must hold",[101,164],{"x1":165,"y1":104,"x2":166,"y2":104,"stroke":75,"style":76},"442.6","464.6",[107,168],{"points":169,"fill":110},"468.6,71.2 459.6,76.2 459.6,66.2",[60,171],{"x":172,"y":70,"width":71,"height":72,"rx":73,"fill":173,"stroke":174,"style":76},"473.6","#fdefd8","var(--gold,#b4740a)",[78,176,178],{"transform":177},"translate(519.2,29.0) scale(1.000)",[82,179],{"d":180,"fill":85,"stroke":181,"style":87},"M5 9h14 M4 15h14 M10 4l-2 16 M16 4l-2 16","var(--gold-ink,#7a4e06)",[89,183,186],{"x":184,"y":123,"style":185},"531.2","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Numeric",[89,188,189],{"x":184,"y":127,"style":185},"values",[89,191,192],{"x":184,"y":131,"style":98},"text → NaN",[101,194],{"x1":195,"y1":104,"x2":196,"y2":104,"stroke":75,"style":76},"593.8","615.8",[107,198],{"points":199,"fill":110},"619.8,71.2 610.8,76.2 610.8,66.2",[60,201],{"x":202,"y":70,"width":71,"height":72,"rx":73,"fill":74,"stroke":75,"style":76},"624.8",[78,204,206],{"transform":205},"translate(670.4,29.0) scale(1.000)",[82,207],{"d":208,"fill":85,"stroke":86,"style":87},"M4 20h16 M7 17v-5 M12 17V7 M17 17v-8",[89,210,212],{"x":211,"y":123,"style":93},"682.4","Aggregate",[89,214,216],{"x":211,"y":215,"style":98},"95.0","mean \u002F max \u002F",[89,218,219],{"x":211,"y":131,"style":98},"min",[221,222,224],"h2",{"id":223},"prerequisites","Prerequisites",[226,227,232],"pre",{"className":228,"code":229,"language":230,"meta":231,"style":231},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[13,233,234],{"__ignoreMap":231},[235,236,238,242,246,249],"span",{"class":101,"line":237},1,[235,239,241],{"class":240},"sMTad","pip",[235,243,245],{"class":244},"srMev"," install",[235,247,248],{"class":244}," pandas",[235,250,251],{"class":244}," openpyxl\n",[10,253,254],{},"A sales sheet with the usual imperfections — a blank amount and a \"TBC\" typed into a numeric column:",[226,256,260],{"className":257,"code":258,"language":259,"meta":231,"style":231},"language-python shiki shiki-themes github-light github-dark-high-contrast","import datetime as dt\n\nimport pandas as pd\n\nsales = pd.DataFrame({\n    \"Region\": [\"North\", \"North\", \"South\", \"North\", \"North\", \"East\"],\n    \"Rep\": [\"Ann\", \"Bob\", \"Cai\", \"Cai\", \"Ann\", \"Dee\"],\n    \"Date\": [dt.date(2026, 1, 3), dt.date(2026, 1, 15), dt.date(2026, 2, 1),\n             dt.date(2026, 2, 20), dt.date(2026, 3, 2), dt.date(2026, 3, 5)],\n    \"Amount\": [120, None, 300, \"TBC\", 80, 50],\n})\nsales.to_excel(\"sales.xlsx\", index=False)\ndf = pd.read_excel(\"sales.xlsx\")\namount = pd.to_numeric(df[\"Amount\"], errors=\"coerce\")\nprint(amount.tolist())        # [120.0, nan, 300.0, nan, 80.0, 50.0]\n","python",[13,261,262,278,285,298,303,315,352,388,441,486,524,530,553,568,595],{"__ignoreMap":231},[235,263,264,268,272,275],{"class":101,"line":237},[235,265,267],{"class":266},"s-kum","import",[235,269,271],{"class":270},"skGVy"," datetime ",[235,273,274],{"class":266},"as",[235,276,277],{"class":270}," dt\n",[235,279,281],{"class":101,"line":280},2,[235,282,284],{"emptyLinePlaceholder":283},true,"\n",[235,286,288,290,293,295],{"class":101,"line":287},3,[235,289,267],{"class":266},[235,291,292],{"class":270}," pandas ",[235,294,274],{"class":266},[235,296,297],{"class":270}," pd\n",[235,299,301],{"class":101,"line":300},4,[235,302,284],{"emptyLinePlaceholder":283},[235,304,306,309,312],{"class":101,"line":305},5,[235,307,308],{"class":270},"sales ",[235,310,311],{"class":266},"=",[235,313,314],{"class":270}," pd.DataFrame({\n",[235,316,318,321,324,327,329,331,333,336,338,340,342,344,346,349],{"class":101,"line":317},6,[235,319,320],{"class":244},"    \"Region\"",[235,322,323],{"class":270},": [",[235,325,326],{"class":244},"\"North\"",[235,328,16],{"class":270},[235,330,326],{"class":244},[235,332,16],{"class":270},[235,334,335],{"class":244},"\"South\"",[235,337,16],{"class":270},[235,339,326],{"class":244},[235,341,16],{"class":270},[235,343,326],{"class":244},[235,345,16],{"class":270},[235,347,348],{"class":244},"\"East\"",[235,350,351],{"class":270},"],\n",[235,353,355,358,360,363,365,368,370,373,375,377,379,381,383,386],{"class":101,"line":354},7,[235,356,357],{"class":244},"    \"Rep\"",[235,359,323],{"class":270},[235,361,362],{"class":244},"\"Ann\"",[235,364,16],{"class":270},[235,366,367],{"class":244},"\"Bob\"",[235,369,16],{"class":270},[235,371,372],{"class":244},"\"Cai\"",[235,374,16],{"class":270},[235,376,372],{"class":244},[235,378,16],{"class":270},[235,380,362],{"class":244},[235,382,16],{"class":270},[235,384,385],{"class":244},"\"Dee\"",[235,387,351],{"class":270},[235,389,391,394,397,401,403,406,408,411,414,416,418,420,422,425,427,429,431,434,436,438],{"class":101,"line":390},8,[235,392,393],{"class":244},"    \"Date\"",[235,395,396],{"class":270},": [dt.date(",[235,398,400],{"class":399},"sP0c6","2026",[235,402,16],{"class":270},[235,404,405],{"class":399},"1",[235,407,16],{"class":270},[235,409,410],{"class":399},"3",[235,412,413],{"class":270},"), dt.date(",[235,415,400],{"class":399},[235,417,16],{"class":270},[235,419,405],{"class":399},[235,421,16],{"class":270},[235,423,424],{"class":399},"15",[235,426,413],{"class":270},[235,428,400],{"class":399},[235,430,16],{"class":270},[235,432,433],{"class":399},"2",[235,435,16],{"class":270},[235,437,405],{"class":399},[235,439,440],{"class":270},"),\n",[235,442,444,447,449,451,453,455,458,460,462,464,466,468,470,472,474,476,478,480,483],{"class":101,"line":443},9,[235,445,446],{"class":270},"             dt.date(",[235,448,400],{"class":399},[235,450,16],{"class":270},[235,452,433],{"class":399},[235,454,16],{"class":270},[235,456,457],{"class":399},"20",[235,459,413],{"class":270},[235,461,400],{"class":399},[235,463,16],{"class":270},[235,465,410],{"class":399},[235,467,16],{"class":270},[235,469,433],{"class":399},[235,471,413],{"class":270},[235,473,400],{"class":399},[235,475,16],{"class":270},[235,477,410],{"class":399},[235,479,16],{"class":270},[235,481,482],{"class":399},"5",[235,484,485],{"class":270},")],\n",[235,487,489,492,494,497,499,502,504,507,509,512,514,517,519,522],{"class":101,"line":488},10,[235,490,491],{"class":244},"    \"Amount\"",[235,493,323],{"class":270},[235,495,496],{"class":399},"120",[235,498,16],{"class":270},[235,500,501],{"class":399},"None",[235,503,16],{"class":270},[235,505,506],{"class":399},"300",[235,508,16],{"class":270},[235,510,511],{"class":244},"\"TBC\"",[235,513,16],{"class":270},[235,515,516],{"class":399},"80",[235,518,16],{"class":270},[235,520,521],{"class":399},"50",[235,523,351],{"class":270},[235,525,527],{"class":101,"line":526},11,[235,528,529],{"class":270},"})\n",[235,531,533,536,539,541,545,547,550],{"class":101,"line":532},12,[235,534,535],{"class":270},"sales.to_excel(",[235,537,538],{"class":244},"\"sales.xlsx\"",[235,540,16],{"class":270},[235,542,544],{"class":543},"sa561","index",[235,546,311],{"class":266},[235,548,549],{"class":399},"False",[235,551,552],{"class":270},")\n",[235,554,556,559,561,564,566],{"class":101,"line":555},13,[235,557,558],{"class":270},"df ",[235,560,311],{"class":266},[235,562,563],{"class":270}," pd.read_excel(",[235,565,538],{"class":244},[235,567,552],{"class":270},[235,569,571,574,576,579,582,585,588,590,593],{"class":101,"line":570},14,[235,572,573],{"class":270},"amount ",[235,575,311],{"class":266},[235,577,578],{"class":270}," pd.to_numeric(df[",[235,580,581],{"class":244},"\"Amount\"",[235,583,584],{"class":270},"], ",[235,586,587],{"class":543},"errors",[235,589,311],{"class":266},[235,591,592],{"class":244},"\"coerce\"",[235,594,552],{"class":270},[235,596,598,601,604],{"class":101,"line":597},15,[235,599,600],{"class":399},"print",[235,602,603],{"class":270},"(amount.tolist())        ",[235,605,607],{"class":606},"s-wDw","# [120.0, nan, 300.0, nan, 80.0, 50.0]\n",[10,609,610,612,613,616,617,620,621,16,624,20,627,629,630,632],{},[13,611,15],{}," silently skips blank and text cells in the average range. ",[13,614,615],{},"pd.to_numeric(..., errors=\"coerce\")"," turns both into ",[13,618,619],{},"NaN",", and pandas' ",[13,622,623],{},"mean",[13,625,626],{},"max",[13,628,219],{}," skip ",[13,631,619],{}," by default, so the two behave the same. Keep a count of the coerced values, though — a \"TBC\" is information that Excel hid from you.",[221,634,636],{"id":635},"step-1-one-result-with-several-criteria","Step 1 — One result with several criteria",[10,638,639,642,643,646],{},[13,640,641],{},"=AVERAGEIFS(D:D, A:A, \"North\", C:C, \">=\"&DATE(2026,1,1), C:C, \"\u003C\"&DATE(2026,4,1))"," becomes a mask per criterion, combined with ",[13,644,645],{},"&",":",[226,648,650],{"className":257,"code":649,"language":259,"meta":231,"style":231},"north_q1 = (\n    (df[\"Region\"] == \"North\")\n    & (df[\"Date\"] >= \"2026-01-01\")\n    & (df[\"Date\"] \u003C \"2026-04-01\")\n)\nprint(amount[north_q1].mean(), amount[north_q1].max(), amount[north_q1].min())\n# 100.0 120.0 80.0\n",[13,651,652,662,681,702,720,724,731],{"__ignoreMap":231},[235,653,654,657,659],{"class":101,"line":237},[235,655,656],{"class":270},"north_q1 ",[235,658,311],{"class":266},[235,660,661],{"class":270}," (\n",[235,663,664,667,670,673,676,679],{"class":101,"line":280},[235,665,666],{"class":270},"    (df[",[235,668,669],{"class":244},"\"Region\"",[235,671,672],{"class":270},"] ",[235,674,675],{"class":266},"==",[235,677,678],{"class":244}," \"North\"",[235,680,552],{"class":270},[235,682,683,686,689,692,694,697,700],{"class":101,"line":287},[235,684,685],{"class":266},"    &",[235,687,688],{"class":270}," (df[",[235,690,691],{"class":244},"\"Date\"",[235,693,672],{"class":270},[235,695,696],{"class":266},">=",[235,698,699],{"class":244}," \"2026-01-01\"",[235,701,552],{"class":270},[235,703,704,706,708,710,712,715,718],{"class":101,"line":300},[235,705,685],{"class":266},[235,707,688],{"class":270},[235,709,691],{"class":244},[235,711,672],{"class":270},[235,713,714],{"class":266},"\u003C",[235,716,717],{"class":244}," \"2026-04-01\"",[235,719,552],{"class":270},[235,721,722],{"class":101,"line":305},[235,723,552],{"class":270},[235,725,726,728],{"class":101,"line":317},[235,727,600],{"class":399},[235,729,730],{"class":270},"(amount[north_q1].mean(), amount[north_q1].max(), amount[north_q1].min())\n",[235,732,733],{"class":101,"line":354},[235,734,735],{"class":606},"# 100.0 120.0 80.0\n",[10,737,738,739,741,742,20,744,746],{},"Four North rows match the criteria, but only two have numeric amounts — the blank and \"TBC\" are skipped — so the average is (120 + 80) \u002F 2 = 100. Recalculating the same formula in a spreadsheet gives 100 too. Wrap each condition in parentheses: ",[13,740,645],{}," binds more tightly than ",[13,743,675],{},[13,745,696],{},", and leaving them out raises a confusing error.",[221,748,750],{"id":749},"step-2-match-excel-when-nothing-matches","Step 2 — Match Excel when nothing matches",[10,752,753,754,20,756,758,759,761,762,765,766,768],{},"Here the two tools really differ. With a criterion that matches no rows, Excel's ",[13,755,19],{},[13,757,23],{}," return 0, while ",[13,760,15],{}," returns ",[13,763,764],{},"#DIV\u002F0!",". pandas returns ",[13,767,619],{}," for all three:",[226,770,772],{"className":257,"code":771,"language":259,"meta":231,"style":231},"west = df[\"Region\"] == \"West\"\nprint(amount[west].mean(), amount[west].max())       # nan nan\n",[13,773,774,793],{"__ignoreMap":231},[235,775,776,779,781,784,786,788,790],{"class":101,"line":237},[235,777,778],{"class":270},"west ",[235,780,311],{"class":266},[235,782,783],{"class":270}," df[",[235,785,669],{"class":244},[235,787,672],{"class":270},[235,789,675],{"class":266},[235,791,792],{"class":244}," \"West\"\n",[235,794,795,797,800],{"class":101,"line":280},[235,796,600],{"class":399},[235,798,799],{"class":270},"(amount[west].mean(), amount[west].max())       ",[235,801,802],{"class":606},"# nan nan\n",[10,804,805],{},"If a translated report must reproduce the workbook's numbers exactly, fill the max and min with 0:",[226,807,809],{"className":257,"code":808,"language":259,"meta":231,"style":231},"def maxifs(values, mask, excel_compatible=True):\n    result = values[mask].max()\n    return 0 if excel_compatible and pd.isna(result) else result\n\nprint(maxifs(amount, west), maxifs(amount, west, excel_compatible=False))   # 0 nan\n",[13,810,811,831,841,867,871],{"__ignoreMap":231},[235,812,813,816,820,823,825,828],{"class":101,"line":237},[235,814,815],{"class":266},"def",[235,817,819],{"class":818},"s_Opv"," maxifs",[235,821,822],{"class":270},"(values, mask, excel_compatible",[235,824,311],{"class":266},[235,826,827],{"class":399},"True",[235,829,830],{"class":270},"):\n",[235,832,833,836,838],{"class":101,"line":280},[235,834,835],{"class":270},"    result ",[235,837,311],{"class":266},[235,839,840],{"class":270}," values[mask].max()\n",[235,842,843,846,849,852,855,858,861,864],{"class":101,"line":287},[235,844,845],{"class":266},"    return",[235,847,848],{"class":399}," 0",[235,850,851],{"class":266}," if",[235,853,854],{"class":270}," excel_compatible ",[235,856,857],{"class":266},"and",[235,859,860],{"class":270}," pd.isna(result) ",[235,862,863],{"class":266},"else",[235,865,866],{"class":270}," result\n",[235,868,869],{"class":101,"line":300},[235,870,284],{"emptyLinePlaceholder":283},[235,872,873,875,878,881,883,885,888],{"class":101,"line":305},[235,874,600],{"class":399},[235,876,877],{"class":270},"(maxifs(amount, west), maxifs(amount, west, ",[235,879,880],{"class":543},"excel_compatible",[235,882,311],{"class":266},[235,884,549],{"class":399},[235,886,887],{"class":270},"))   ",[235,889,890],{"class":606},"# 0 nan\n",[10,892,893,894,896],{},"Think before choosing the compatible mode. A maximum of 0 for a region with no sales looks like a real value and will be charted as one; ",[13,895,619],{},", written to Excel as an empty cell, is the honest answer. Many teams move to blanks when they leave the workbook behind, and note the change in the report.",[41,898,50,903,50,906,50,909,50,912,50,918,50,923,50,929,50,932,50,936,50,941,50,947,50,949,50,952,50,954,50,956,50,959,50,963,50,965,50,968,50,970,50,972,50,975,50,979,50,981,50,984,50,986],{"viewBox":899,"role":44,"ariaLabelledBy":900,"xmlns":48,"style":49},"4 0 752 174",[901,902],"ai-empty-t","ai-empty-d",[52,904,905],{"id":901},"What each tool returns when no row matches",[56,907,908],{"id":902},"When the criteria match nothing, Excel's AVERAGEIFS returns the",[60,910],{"x":62,"y":63,"width":64,"height":911,"fill":66},"174",[60,913],{"x":69,"y":70,"width":914,"height":915,"rx":63,"fill":66,"stroke":916,"style":917},"158.7","40.0","var(--line,#cdd5e6)","stroke-width:1px",[60,919],{"x":920,"y":70,"width":921,"height":915,"rx":63,"fill":74,"stroke":75,"style":922},"178.7","280.6","stroke-width:1.2px",[89,924,928],{"x":925,"y":926,"style":927},"319.0","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Excel",[60,930],{"x":931,"y":70,"width":921,"height":915,"rx":63,"fill":74,"stroke":75,"style":922},"459.4",[89,933,935],{"x":934,"y":926,"style":927},"599.7","pandas",[60,937],{"x":69,"y":938,"width":914,"height":939,"rx":63,"fill":940,"stroke":916,"style":917},"56.0","34.0","#e7ebef",[89,942,946],{"x":943,"y":944,"style":945},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","AVERAGEIFS \u002F mean",[60,948],{"x":920,"y":938,"width":921,"height":939,"rx":63,"fill":66,"stroke":916,"style":917},[89,950,764],{"x":925,"y":951,"style":98},"77.0",[60,953],{"x":931,"y":938,"width":921,"height":939,"rx":63,"fill":66,"stroke":916,"style":917},[89,955,619],{"x":934,"y":951,"style":98},[60,957],{"x":69,"y":958,"width":914,"height":939,"rx":63,"fill":940,"stroke":916,"style":917},"90.0",[89,960,962],{"x":943,"y":961,"style":945},"112.0","MAXIFS \u002F max",[60,964],{"x":920,"y":958,"width":921,"height":939,"rx":63,"fill":66,"stroke":916,"style":917},[89,966,63],{"x":925,"y":967,"style":98},"111.0",[60,969],{"x":931,"y":958,"width":921,"height":939,"rx":63,"fill":66,"stroke":916,"style":917},[89,971,619],{"x":934,"y":967,"style":98},[60,973],{"x":69,"y":974,"width":914,"height":939,"rx":63,"fill":940,"stroke":916,"style":917},"124.0",[89,976,978],{"x":943,"y":977,"style":945},"146.0","MINIFS \u002F min",[60,980],{"x":920,"y":974,"width":921,"height":939,"rx":63,"fill":66,"stroke":916,"style":917},[89,982,63],{"x":925,"y":983,"style":98},"145.0",[60,985],{"x":931,"y":974,"width":921,"height":939,"rx":63,"fill":66,"stroke":916,"style":917},[89,987,619],{"x":934,"y":983,"style":98},[221,989,991],{"id":990},"step-3-criteria-with-operators-and-wildcards","Step 3 — Criteria with operators and wildcards",[10,993,994,995,16,998,1001,1002,1005],{},"Excel criteria are strings such as ",[13,996,997],{},"\">=100\"",[13,999,1000],{},"\"\u003C>North\""," or ",[13,1003,1004],{},"\"a*\"",". Translate each into the equivalent comparison. Wildcards in Excel are case-insensitive, so casefold the text before testing it:",[226,1007,1009],{"className":257,"code":1008,"language":259,"meta":231,"style":231},"big = amount >= 100                                    # \">=100\"\nnot_north = df[\"Region\"] != \"North\"                    # \"\u003C>North\"\nrep_a = df[\"Rep\"].str.casefold().str.startswith(\"a\")   # \"a*\"\nprint(amount[big].mean())                              # 210.0\nprint(amount[rep_a].mean())                            # 100.0\n",[13,1010,1011,1029,1050,1074,1084],{"__ignoreMap":231},[235,1012,1013,1016,1018,1021,1023,1026],{"class":101,"line":237},[235,1014,1015],{"class":270},"big ",[235,1017,311],{"class":266},[235,1019,1020],{"class":270}," amount ",[235,1022,696],{"class":266},[235,1024,1025],{"class":399}," 100",[235,1027,1028],{"class":606},"                                    # \">=100\"\n",[235,1030,1031,1034,1036,1038,1040,1042,1045,1047],{"class":101,"line":280},[235,1032,1033],{"class":270},"not_north ",[235,1035,311],{"class":266},[235,1037,783],{"class":270},[235,1039,669],{"class":244},[235,1041,672],{"class":270},[235,1043,1044],{"class":266},"!=",[235,1046,678],{"class":244},[235,1048,1049],{"class":606},"                    # \"\u003C>North\"\n",[235,1051,1052,1055,1057,1059,1062,1065,1068,1071],{"class":101,"line":287},[235,1053,1054],{"class":270},"rep_a ",[235,1056,311],{"class":266},[235,1058,783],{"class":270},[235,1060,1061],{"class":244},"\"Rep\"",[235,1063,1064],{"class":270},"].str.casefold().str.startswith(",[235,1066,1067],{"class":244},"\"a\"",[235,1069,1070],{"class":270},")   ",[235,1072,1073],{"class":606},"# \"a*\"\n",[235,1075,1076,1078,1081],{"class":101,"line":300},[235,1077,600],{"class":399},[235,1079,1080],{"class":270},"(amount[big].mean())                              ",[235,1082,1083],{"class":606},"# 210.0\n",[235,1085,1086,1088,1091],{"class":101,"line":305},[235,1087,600],{"class":399},[235,1089,1090],{"class":270},"(amount[rep_a].mean())                            ",[235,1092,1093],{"class":606},"# 100.0\n",[10,1095,1096,1097,1100,1101,1104,1105,1109,1110,1112,1113,1115,1116,1118],{},"For patterns with wildcards in the middle, such as ",[13,1098,1099],{},"\"A?n\"",", use ",[13,1102,1103],{},"fnmatch.fnmatchcase"," on casefolded values, as shown in ",[26,1106,1108],{"href":1107},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fxlookup-equivalent-in-pandas\u002F","XLOOKUP Equivalent in pandas",". Note that ",[13,1111,1000],{}," in Excel also matches blank region cells; in pandas ",[13,1114,1044],{}," is true for ",[13,1117,619],{}," as well, so the behaviour agrees.",[221,1120,1122],{"id":1121},"step-4-a-table-of-results-with-groupby","Step 4 — A table of results with groupby",[10,1124,1125,1126,1128,1129,646],{},"Workbooks often hold a summary block where each row repeats the formula with a different criterion — one ",[13,1127,15],{}," per region. That whole block is one ",[13,1130,1131],{},"groupby",[226,1133,1135],{"className":257,"code":1134,"language":259,"meta":231,"style":231},"table = amount.groupby(df[\"Region\"]).agg([\"mean\", \"max\", \"min\", \"count\"])\nprint(table)\n#          mean    max    min  count\n# Region\n# East     50.0   50.0   50.0      1\n# North   100.0  120.0   80.0      2\n# South   300.0  300.0  300.0      1\n",[13,1136,1137,1173,1180,1185,1190,1195,1200],{"__ignoreMap":231},[235,1138,1139,1142,1144,1147,1149,1152,1155,1157,1160,1162,1165,1167,1170],{"class":101,"line":237},[235,1140,1141],{"class":270},"table ",[235,1143,311],{"class":266},[235,1145,1146],{"class":270}," amount.groupby(df[",[235,1148,669],{"class":244},[235,1150,1151],{"class":270},"]).agg([",[235,1153,1154],{"class":244},"\"mean\"",[235,1156,16],{"class":270},[235,1158,1159],{"class":244},"\"max\"",[235,1161,16],{"class":270},[235,1163,1164],{"class":244},"\"min\"",[235,1166,16],{"class":270},[235,1168,1169],{"class":244},"\"count\"",[235,1171,1172],{"class":270},"])\n",[235,1174,1175,1177],{"class":101,"line":280},[235,1176,600],{"class":399},[235,1178,1179],{"class":270},"(table)\n",[235,1181,1182],{"class":101,"line":287},[235,1183,1184],{"class":606},"#          mean    max    min  count\n",[235,1186,1187],{"class":101,"line":300},[235,1188,1189],{"class":606},"# Region\n",[235,1191,1192],{"class":101,"line":305},[235,1193,1194],{"class":606},"# East     50.0   50.0   50.0      1\n",[235,1196,1197],{"class":101,"line":317},[235,1198,1199],{"class":606},"# North   100.0  120.0   80.0      2\n",[235,1201,1202],{"class":101,"line":354},[235,1203,1204],{"class":606},"# South   300.0  300.0  300.0      1\n",[10,1206,1207,1208,1211,1212,1215],{},"Adding ",[13,1209,1210],{},"count"," shows how many numeric values each result is based on. An average of one value is still an average, but readers deserve to know. For several grouping columns — region and rep, say — pass a list: ",[13,1213,1214],{},"amount.groupby([df[\"Region\"], df[\"Rep\"]])",". Groups with no rows at all simply do not appear, rather than showing 0 or an error as a formula would.",[221,1217,1219],{"id":1218},"step-5-per-row-results-with-transform","Step 5 — Per-row results with transform",[10,1221,1222,1223,1226,1227,1230],{},"Another common layout puts the formula in a column next to the data, filled down, so each row compares itself with its group: ",[13,1224,1225],{},"=D2\u002FAVERAGEIFS(D:D, A:A, A2)",". ",[13,1228,1229],{},"transform"," returns one value per row, aligned with the original frame:",[226,1232,1234],{"className":257,"code":1233,"language":259,"meta":231,"style":231},"df[\"region_avg\"] = amount.groupby(df[\"Region\"]).transform(\"mean\")\ndf[\"region_max\"] = amount.groupby(df[\"Region\"]).transform(\"max\")\ndf[\"share_of_best\"] = amount \u002F df[\"region_max\"]\nprint(df[[\"Region\", \"Rep\", \"region_avg\", \"share_of_best\"]].round(2).values.tolist())\n",[13,1235,1236,1259,1280,1303],{"__ignoreMap":231},[235,1237,1238,1241,1244,1246,1248,1250,1252,1255,1257],{"class":101,"line":237},[235,1239,1240],{"class":270},"df[",[235,1242,1243],{"class":244},"\"region_avg\"",[235,1245,672],{"class":270},[235,1247,311],{"class":266},[235,1249,1146],{"class":270},[235,1251,669],{"class":244},[235,1253,1254],{"class":270},"]).transform(",[235,1256,1154],{"class":244},[235,1258,552],{"class":270},[235,1260,1261,1263,1266,1268,1270,1272,1274,1276,1278],{"class":101,"line":280},[235,1262,1240],{"class":270},[235,1264,1265],{"class":244},"\"region_max\"",[235,1267,672],{"class":270},[235,1269,311],{"class":266},[235,1271,1146],{"class":270},[235,1273,669],{"class":244},[235,1275,1254],{"class":270},[235,1277,1159],{"class":244},[235,1279,552],{"class":270},[235,1281,1282,1284,1287,1289,1291,1293,1296,1298,1300],{"class":101,"line":287},[235,1283,1240],{"class":270},[235,1285,1286],{"class":244},"\"share_of_best\"",[235,1288,672],{"class":270},[235,1290,311],{"class":266},[235,1292,1020],{"class":270},[235,1294,1295],{"class":266},"\u002F",[235,1297,783],{"class":270},[235,1299,1265],{"class":244},[235,1301,1302],{"class":270},"]\n",[235,1304,1305,1307,1310,1312,1314,1316,1318,1320,1322,1324,1327,1329],{"class":101,"line":300},[235,1306,600],{"class":399},[235,1308,1309],{"class":270},"(df[[",[235,1311,669],{"class":244},[235,1313,16],{"class":270},[235,1315,1061],{"class":244},[235,1317,16],{"class":270},[235,1319,1243],{"class":244},[235,1321,16],{"class":270},[235,1323,1286],{"class":244},[235,1325,1326],{"class":270},"]].round(",[235,1328,433],{"class":399},[235,1330,1331],{"class":270},").values.tolist())\n",[10,1333,1334,1335,1337],{},"Each row receives the average and maximum of its own region, computed once per group rather than once per row as in the workbook. The rows with no numeric amount get ",[13,1336,619],{}," for their own share but still receive their region's average, just like the filled-down formula.",[41,1339,50,1344,50,1347,50,1350,50,1353,50,1358,50,1365,50,1370,50,1375,50,1380,50,1386,50,1390,50,1395,50,1399,50,1405,50,1408,50,1414,50,1419,50,1423,50,1427,50,1432,50,1435,50,1440,50,1443],{"viewBox":1340,"role":44,"ariaLabelledBy":1341,"xmlns":48,"style":49},"4 0 752 207",[1342,1343],"ai-forms-t","ai-forms-d",[52,1345,1346],{"id":1342},"groupby agg versus groupby transform",[56,1348,1349],{"id":1343},"groupby with agg returns one row per group, matching a summary block of formulas. groupby with transform returns one value per original row, matching a formula filled down next to the data.",[60,1351],{"x":62,"y":63,"width":64,"height":1352,"fill":66},"207",[60,1354],{"x":69,"y":70,"width":1355,"height":1356,"rx":1357,"fill":74,"stroke":75,"style":76},"351.0","175.0","14",[78,1359,1361],{"transform":1360},"translate(182.5,29.0) scale(1.083)",[82,1362],{"d":1363,"fill":85,"stroke":86,"style":1364},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[89,1366,1369],{"x":1367,"y":944,"style":1368},"195.5","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","groupby().agg()",[101,1371],{"x1":1372,"y1":1373,"x2":1374,"y2":1373,"stroke":75,"style":917},"36.0","91.0","355.0",[89,1376,1379],{"x":1377,"y":967,"style":1378},"60.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","one row per region",[78,1381,1383],{"transform":1382},"translate(38.5,99.5) scale(0.625)",[82,1384],{"d":119,"fill":85,"stroke":144,"style":1385},"stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[89,1387,1389],{"x":1377,"y":1388,"style":1378},"137.0","matches a summary block",[78,1391,1393],{"transform":1392},"translate(38.5,125.5) scale(0.625)",[82,1394],{"d":119,"fill":85,"stroke":144,"style":1385},[89,1396,1398],{"x":1377,"y":1397,"style":1378},"163.0","new shape, new index",[78,1400,1402],{"transform":1401},"translate(38.5,151.5) scale(0.625)",[82,1403],{"d":1404,"fill":85,"stroke":174,"style":1385},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4",[60,1406],{"x":1407,"y":70,"width":1355,"height":1356,"rx":1357,"fill":143,"stroke":144,"style":76},"389.0",[78,1409,1411],{"transform":1410},"translate(551.5,29.0) scale(1.083)",[82,1412],{"d":1413,"fill":85,"stroke":151,"style":1364},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5",[89,1415,1418],{"x":1416,"y":944,"style":1417},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","groupby().transform()",[101,1420],{"x1":1421,"y1":1373,"x2":1422,"y2":1373,"stroke":144,"style":917},"405.0","724.0",[89,1424,1426],{"x":1425,"y":967,"style":1378},"429.0","one value per data row",[78,1428,1430],{"transform":1429},"translate(407.5,99.5) scale(0.625)",[82,1431],{"d":119,"fill":85,"stroke":144,"style":1385},[89,1433,1434],{"x":1425,"y":1388,"style":1378},"matches a filled-down column",[78,1436,1438],{"transform":1437},"translate(407.5,125.5) scale(0.625)",[82,1439],{"d":119,"fill":85,"stroke":144,"style":1385},[89,1441,1442],{"x":1425,"y":1397,"style":1378},"aligns with the original frame",[78,1444,1446],{"transform":1445},"translate(407.5,151.5) scale(0.625)",[82,1447],{"d":119,"fill":85,"stroke":144,"style":1385},[221,1449,1451],{"id":1450},"step-6-report-what-excel-skipped","Step 6 — Report what Excel skipped",[10,1453,1454],{},"The workbook never told anyone that one North sale was \"TBC\" and another blank; the average simply left them out. Now that the calculation is in code, make those rows visible next to the result, per group:",[226,1456,1458],{"className":257,"code":1457,"language":259,"meta":231,"style":231},"text_values = amount.isna() & df[\"Amount\"].notna()\nblanks = df[\"Amount\"].isna()\nskipped = pd.DataFrame({\n    \"text\": text_values.groupby(df[\"Region\"]).sum(),\n    \"blank\": blanks.groupby(df[\"Region\"]).sum(),\n    \"used\": amount.notna().groupby(df[\"Region\"]).sum(),\n})\nprint(skipped.loc[\"North\"].to_dict())       # {'text': 1, 'blank': 1, 'used': 2}\n",[13,1459,1460,1479,1493,1502,1515,1527,1539,1543],{"__ignoreMap":231},[235,1461,1462,1465,1467,1470,1472,1474,1476],{"class":101,"line":237},[235,1463,1464],{"class":270},"text_values ",[235,1466,311],{"class":266},[235,1468,1469],{"class":270}," amount.isna() ",[235,1471,645],{"class":266},[235,1473,783],{"class":270},[235,1475,581],{"class":244},[235,1477,1478],{"class":270},"].notna()\n",[235,1480,1481,1484,1486,1488,1490],{"class":101,"line":280},[235,1482,1483],{"class":270},"blanks ",[235,1485,311],{"class":266},[235,1487,783],{"class":270},[235,1489,581],{"class":244},[235,1491,1492],{"class":270},"].isna()\n",[235,1494,1495,1498,1500],{"class":101,"line":287},[235,1496,1497],{"class":270},"skipped ",[235,1499,311],{"class":266},[235,1501,314],{"class":270},[235,1503,1504,1507,1510,1512],{"class":101,"line":300},[235,1505,1506],{"class":244},"    \"text\"",[235,1508,1509],{"class":270},": text_values.groupby(df[",[235,1511,669],{"class":244},[235,1513,1514],{"class":270},"]).sum(),\n",[235,1516,1517,1520,1523,1525],{"class":101,"line":305},[235,1518,1519],{"class":244},"    \"blank\"",[235,1521,1522],{"class":270},": blanks.groupby(df[",[235,1524,669],{"class":244},[235,1526,1514],{"class":270},[235,1528,1529,1532,1535,1537],{"class":101,"line":317},[235,1530,1531],{"class":244},"    \"used\"",[235,1533,1534],{"class":270},": amount.notna().groupby(df[",[235,1536,669],{"class":244},[235,1538,1514],{"class":270},[235,1540,1541],{"class":101,"line":354},[235,1542,529],{"class":270},[235,1544,1545,1547,1550,1552,1555],{"class":101,"line":390},[235,1546,600],{"class":399},[235,1548,1549],{"class":270},"(skipped.loc[",[235,1551,326],{"class":244},[235,1553,1554],{"class":270},"].to_dict())       ",[235,1556,1557],{"class":606},"# {'text': 1, 'blank': 1, 'used': 2}\n",[10,1559,1560],{},"Half of North's rows were left out of its average. That may be fine — or it may mean the North average is based on too little data to publish. Writing a \"based on n sales\" column next to each average costs nothing and prevents readers from over-trusting a figure built on one or two values. The same table helps whoever maintains the source sheet: the \"text\" count lists exactly the cells that need a number typed in.",[221,1562,1564],{"id":1563},"step-7-check-the-translation-against-the-workbook","Step 7 — Check the translation against the workbook",[10,1566,1567,1568,1571],{},"Before retiring the workbook, compare the pandas results with the values Excel calculated. Load the cached formula results with ",[13,1569,1570],{},"data_only=True"," and compare them with your numbers, allowing for floating-point noise:",[226,1573,1575],{"className":257,"code":1574,"language":259,"meta":231,"style":231},"from openpyxl import load_workbook\n\ndef cached(path, sheet, cell):\n    return load_workbook(path, data_only=True)[sheet][cell].value\n\n# expected = cached(\"sales_report.xlsx\", \"Summary\", \"B2\")\nexpected = 100.0                      # the value Excel showed for North\nassert abs(amount[north_q1].mean() - expected) \u003C 1e-9\n",[13,1576,1577,1590,1594,1604,1621,1625,1630,1643],{"__ignoreMap":231},[235,1578,1579,1582,1585,1587],{"class":101,"line":237},[235,1580,1581],{"class":266},"from",[235,1583,1584],{"class":270}," openpyxl ",[235,1586,267],{"class":266},[235,1588,1589],{"class":270}," load_workbook\n",[235,1591,1592],{"class":101,"line":280},[235,1593,284],{"emptyLinePlaceholder":283},[235,1595,1596,1598,1601],{"class":101,"line":287},[235,1597,815],{"class":266},[235,1599,1600],{"class":818}," cached",[235,1602,1603],{"class":270},"(path, sheet, cell):\n",[235,1605,1606,1608,1611,1614,1616,1618],{"class":101,"line":300},[235,1607,845],{"class":266},[235,1609,1610],{"class":270}," load_workbook(path, ",[235,1612,1613],{"class":543},"data_only",[235,1615,311],{"class":266},[235,1617,827],{"class":399},[235,1619,1620],{"class":270},")[sheet][cell].value\n",[235,1622,1623],{"class":101,"line":305},[235,1624,284],{"emptyLinePlaceholder":283},[235,1626,1627],{"class":101,"line":317},[235,1628,1629],{"class":606},"# expected = cached(\"sales_report.xlsx\", \"Summary\", \"B2\")\n",[235,1631,1632,1635,1637,1640],{"class":101,"line":354},[235,1633,1634],{"class":270},"expected ",[235,1636,311],{"class":266},[235,1638,1639],{"class":399}," 100.0",[235,1641,1642],{"class":606},"                      # the value Excel showed for North\n",[235,1644,1645,1648,1651,1654,1657,1660,1662],{"class":101,"line":390},[235,1646,1647],{"class":266},"assert",[235,1649,1650],{"class":399}," abs",[235,1652,1653],{"class":270},"(amount[north_q1].mean() ",[235,1655,1656],{"class":266},"-",[235,1658,1659],{"class":270}," expected) ",[235,1661,714],{"class":266},[235,1663,1664],{"class":399}," 1e-9\n",[10,1666,1667,1668,39],{},"A handful of such checks, one per translated formula, catches criteria that were copied wrongly and the empty-case differences from Step 2. Reading cached values is covered in ",[26,1669,1671],{"href":1670},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fread-formula-results-with-openpyxl-data-only\u002F","Read Formula Results with openpyxl data_only",[221,1673,1675],{"id":1674},"common-pitfalls","Common pitfalls",[1677,1678,1679,1690,1700,1710,1726],"ul",{},[1680,1681,1682,1686,1687,1689],"li",{},[1683,1684,1685],"strong",{},"Criteria on text numbers."," A criterion of ",[13,1688,997],{}," on a column of text numbers matches nothing in either tool; convert the column first.",[1680,1691,1692,1695,1696,1699],{},[1683,1693,1694],{},"Dates as text."," Comparing a text date column with ",[13,1697,1698],{},"\"2026-01-01\""," compares strings. Make sure the column is a real datetime.",[1680,1701,1702,1705,1706,1709],{},[1683,1703,1704],{},"Whole-column references."," ",[13,1707,1708],{},"D:D"," in Excel includes the header, which is text and therefore ignored. In pandas, read the sheet with its header row so the label never enters the column in the first place.",[1680,1711,1712,1715,1716,1718,1719,1001,1722,1725],{},[1683,1713,1714],{},"Hidden rows."," Excel's ",[13,1717,15],{}," includes rows hidden by a filter; so does pandas. If the report relied on ",[13,1720,1721],{},"SUBTOTAL",[13,1723,1724],{},"AGGREGATE"," to exclude hidden rows, filter those rows out explicitly.",[1680,1727,1728,1731],{},[1683,1729,1730],{},"Averaging averages."," A regional average computed from rep averages ignores how many sales each rep made. Aggregate the raw rows.",[221,1733,1735],{"id":1734},"conclusion","Conclusion",[10,1737,1738,16,1740,20,1742,1744,1745,16,1747,1001,1749,1751,1752,1755,1756,1758,1759,1761,1762,1764],{},[13,1739,15],{},[13,1741,19],{},[13,1743,23],{}," become a boolean mask and a ",[13,1746,623],{},[13,1748,626],{},[13,1750,219],{}," call. Convert values with ",[13,1753,1754],{},"pd.to_numeric(errors=\"coerce\")"," to reproduce Excel's skipping of blanks and text, combine criteria with parenthesised ",[13,1757,645],{},", casefold for wildcards, and use ",[13,1760,1369],{}," for summary blocks and ",[13,1763,1418],{}," for filled-down columns. Decide deliberately whether to reproduce Excel's 0 when nothing matches, and check a few results against the workbook's cached values.",[221,1766,1768],{"id":1767},"frequently-asked-questions","Frequently asked questions",[10,1770,1771,1774],{},[1683,1772,1773],{},"What does MAXIFS return when no rows match?","\nExcel returns 0, while pandas returns NaN. If a translated report must show exactly what the workbook showed, fill the NaN with 0 — but consider whether a blank is more honest, since 0 looks like a real maximum.",[10,1776,1777,1780],{},[1683,1778,1779],{},"Does AVERAGEIFS count blank or text cells?","\nNo. AVERAGEIFS ignores empty cells and text in the average range. In pandas, convert the column with pd.to_numeric(errors=\"coerce\"); mean then skips the resulting NaN values in the same way.",[10,1782,1783,1786],{},[1683,1784,1785],{},"How do I compute AVERAGEIFS for every row, like a formula filled down?","\nUse groupby on the criteria columns with transform(\"mean\"). Every row receives the average of its own group, which matches a formula filled down a column with criteria that reference the same row.",[10,1788,1789,1796],{},[1790,1791,1792,1795],"em",{},[1790,1793,1794],{},"How do I use a wildcard criterion such as \"a","\"?","*\nUse a case-insensitive string test on the criteria column, such as str.startswith after casefold, or fnmatch for patterns with ? and * in the middle.",[221,1798,1800],{"id":1799},"related","Related",[1677,1802,1803,1809,1815,1822],{},[1680,1804,1805,1806,1808],{},"Up one level: ",[26,1807,29],{"href":28}," — Excel functions translated to pandas.",[1680,1810,1811,1814],{},[26,1812,1813],{"href":33},"SUMIF and SUMIFS Equivalent in pandas"," — conditional sums.",[1680,1816,1817,1821],{},[26,1818,1820],{"href":1819},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frank-and-percentile-formulas-in-pandas\u002F","Rank and Percentile Formulas in pandas"," — ranking within groups.",[1680,1823,1824,1828],{},[26,1825,1827],{"href":1826},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fuse-different-aggregations-per-column-in-a-pivot\u002F","Use Different Aggregations per Column in a pandas Pivot"," — several aggregates in one table.",[1830,1831,1832],"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-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}",{"title":231,"searchDepth":280,"depth":280,"links":1834},[1835,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846],{"id":223,"depth":280,"text":224},{"id":635,"depth":280,"text":636},{"id":749,"depth":280,"text":750},{"id":990,"depth":280,"text":991},{"id":1121,"depth":280,"text":1122},{"id":1218,"depth":280,"text":1219},{"id":1450,"depth":280,"text":1451},{"id":1563,"depth":280,"text":1564},{"id":1674,"depth":280,"text":1675},{"id":1734,"depth":280,"text":1735},{"id":1767,"depth":280,"text":1768},{"id":1799,"depth":280,"text":1800},"2026-09-23","Translate Excel's AVERAGEIFS, MAXIFS and MINIFS to pandas — boolean masks for one-off results, groupby for tables, transform for per-row columns — and reproduce Excel's handling of blanks, text, wildcards, date ranges and the empty case where MAXIFS returns 0.","md",[1851,1853,1855,1857],{"q":1773,"a":1852},"Excel returns 0, while pandas returns NaN. If a translated report must show exactly what the workbook showed, fill the NaN with 0 — but consider whether a blank is more honest, since 0 looks like a real maximum.",{"q":1779,"a":1854},"No. AVERAGEIFS ignores empty cells and text in the average range. In pandas, convert the column with pd.to_numeric(errors=\"coerce\"); mean then skips the resulting NaN values in the same way.",{"q":1785,"a":1856},"Use groupby on the criteria columns with transform(\"mean\"). Every row receives the average of its own group, which matches a formula filled down a column with criteria that reference the same row.",{"q":1858,"a":1859},"How do I use a wildcard criterion such as \"a*\"?","Use a case-insensitive string test on the criteria column, such as str.startswith after casefold, or fnmatch for patterns with ? and * in the middle.","averageifs maxifs and minifs equivalents in pandas",null,{"breadcrumb":1863},[1864,1866,1869],{"name":1865,"item":1295},"Home",{"name":1867,"item":1868},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":29,"item":28},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Faverageifs-maxifs-minifs-in-pandas",{"title":5,"description":1872},"Reproduce AVERAGEIFS, MAXIFS and MINIFS in pandas — masks with multiple criteria, operators and date ranges, wildcards, groupby and transform, text and blank cells, and matching Excel's 0 and #DIV\u002F0! results when nothing matches.","averageifs-maxifs-minifs-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Faverageifs-maxifs-minifs-in-pandas\u002Findex",[1876,1877,935],"formula-equivalents","pivots-and-summaries","how-to","kqEb4NFDywUPF9n9Zi435Q67aIc9IDK3dS1eRoAlBrI",[1881,1885],{"title":1882,"path":1883,"stem":1884,"children":-1},"Approximate-Match VLOOKUP for Tiered Rates in pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fapproximate-match-vlookup-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fapproximate-match-vlookup-in-pandas\u002Findex",{"title":1886,"path":1887,"stem":1888,"children":-1},"CHOOSE, SWITCH and IFS Equivalents in pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fchoose-switch-and-ifs-equivalents-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fchoose-switch-and-ifs-equivalents-in-pandas\u002Findex",[1890,1892],{"path":1891,"title":29},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas",{"path":1893,"title":1867},"\u002Fadvanced-data-transformation-and-cleaning",[],[1896,1899,1902,1905,1908,1911],{"path":1897,"title":1898,"kind":1878},"\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":1900,"title":1901,"kind":1878},"\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":1903,"title":1904,"kind":1878},"\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":1906,"title":1907,"kind":1878},"\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":1909,"title":1910,"kind":1878},"\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":1912,"title":1913,"kind":1878},"\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",1790972086780]