[{"data":1,"prerenderedAt":2207},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsumif-and-sumifs-equivalent-in-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsumif-and-sumifs-equivalent-in-pandas":2199},{"id":4,"title":5,"body":6,"dateModified":2170,"datePublished":2170,"description":2171,"extension":2172,"faq":2173,"meta":2184,"navigation":189,"path":2192,"seo":2193,"slug":2195,"stem":2196,"type":2197,"__hash__":2198},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsumif-and-sumifs-equivalent-in-pandas\u002Findex.md","SUMIF and SUMIFS Equivalent in pandas",{"type":7,"value":8,"toc":2155},"minimark",[9,27,126,131,158,161,506,510,582,596,599,675,678,682,699,812,819,822,881,885,987,990,1124,1130,1134,1141,1301,1313,1317,1327,1475,1485,1491,1495,1498,1626,1633,1636,1640,1643,1729,1732,1736,1865,1869,1947,1950,1956,2028,2035,2039,2060,2064,2078,2084,2090,2096,2109,2113,2151],[10,11,12,16,17,20,21,26],"p",{},[13,14,15],"code",{},"SUMIF"," and ",[13,18,19],{},"SUMIFS"," are the workhorses of a real spreadsheet, and they are the first formulas\nanybody automating a report has to reproduce. The pandas equivalent is not a function but a pattern:\nbuild a boolean mask, select rows with it, and sum the column you want. This guide, part of\n",[22,23,25],"a",{"href":24},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002F","Excel Formula Equivalents in pandas",",\ncovers the one-condition case, the multi-condition case, and the grouped form that replaces a\nformula copied down 50,000 rows.",[28,29,37,38,37,42,37,46,37,53,37,60,37,70,37,76,37,81,37,87,37,92,37,95,37,98,37,101,37,105,37,108,37,113,37,118,37,121],"svg",{"viewBox":30,"role":31,"ariaLabelledBy":32,"xmlns":35,"style":36},"0 0 760 232","img",[33,34],"sif-steps-t","sif-steps-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:760px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[39,40,41],"title",{"id":33},"SUMIF decomposed into the three steps pandas keeps separate",[43,44,45],"desc",{"id":34},"A comparison produces a boolean mask, .loc uses the mask to select matching rows, and .sum totals the chosen column — the three operations Excel bundles into one function.",[47,48],"rect",{"x":49,"y":49,"width":50,"height":51,"fill":52},"0","760","232","#ffffff",[54,55,59],"text",{"x":56,"y":57,"style":58},"380.0","32","font-size:13px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","mask, select, aggregate",[47,61],{"x":62,"y":63,"width":64,"height":65,"rx":66,"fill":67,"stroke":68,"style":69},"24.0","74","208.0","96","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[54,71,75],{"x":72,"y":73,"style":74},"128.0","114","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","build a mask",[54,77,80],{"x":72,"y":78,"style":79},"136","font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","Region == 'North'",[82,83],"line",{"x1":84,"y1":85,"x2":86,"y2":85,"stroke":68,"style":69},"237.0","122.0","269.0",[88,89],"polygon",{"points":90,"fill":91},"269.0,122.0 260.0,117.0 260.0,127.0","#5b5cf0",[47,93],{"x":94,"y":63,"width":64,"height":65,"rx":66,"fill":67,"stroke":68,"style":69},"276.0",[54,96,97],{"x":56,"y":73,"style":74},"select rows",[54,99,100],{"x":56,"y":78,"style":79},".loc[mask, 'Revenue']",[82,102],{"x1":103,"y1":85,"x2":104,"y2":85,"stroke":68,"style":69},"489.0","521.0",[88,106],{"points":107,"fill":91},"521.0,122.0 512.0,117.0 512.0,127.0",[47,109],{"x":110,"y":63,"width":64,"height":65,"rx":66,"fill":111,"stroke":112,"style":69},"528.0","#d9f4f1","var(--teal,#0f9488)",[54,114,117],{"x":115,"y":73,"style":116},"632.0","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","aggregate",[54,119,120],{"x":115,"y":78,"style":79},".sum()",[54,122,125],{"x":56,"y":123,"style":124},"210","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","each step is an object you can print, which a formula is not",[127,128,130],"h2",{"id":129},"prerequisites","Prerequisites",[132,133,138],"pre",{"className":134,"code":135,"language":136,"meta":137,"style":137},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[13,139,140],{"__ignoreMap":137},[141,142,144,148,152,155],"span",{"class":82,"line":143},1,[141,145,147],{"class":146},"sMTad","pip",[141,149,151],{"class":150},"srMev"," install",[141,153,154],{"class":150}," pandas",[141,156,157],{"class":150}," openpyxl\n",[10,159,160],{},"Every example below runs against this sample workbook:",[132,162,166],{"className":163,"code":164,"language":165,"meta":137,"style":137},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nsales = pd.DataFrame({\n    \"Region\": [\"North\", \"South\", \"North\", \"West\", \"South\", \"North\", \"West\"],\n    \"Rep\": [\"Ana\", \"Ben\", \"Cara\", \"Dev\", \"Eve\", \"Ana\", \"Dev\"],\n    \"Product\": [\"Widget\", \"Gadget\", \"Widget\", \"Widget\", \"Gadget\", \"Gadget\", \"Widget\"],\n    \"Revenue\": [12400.0, 9800.5, 15320.25, 7010.0, 4300.75, 6120.0, 11450.5],\n    \"Units\": [124, 98, 153, 70, 43, 61, 114],\n    \"Ordered\": pd.to_datetime([\n        \"2026-01-04\", \"2026-01-19\", \"2026-02-02\", \"2026-02-27\",\n        \"2026-03-08\", \"2026-03-15\", \"2026-03-30\",\n    ]),\n})\nsales.to_excel(\"sales.xlsx\", sheet_name=\"Detail\", index=False)\n","python",[13,167,168,184,191,203,245,286,324,368,410,419,443,461,467,473],{"__ignoreMap":137},[141,169,170,174,178,181],{"class":82,"line":143},[141,171,173],{"class":172},"s-kum","import",[141,175,177],{"class":176},"skGVy"," pandas ",[141,179,180],{"class":172},"as",[141,182,183],{"class":176}," pd\n",[141,185,187],{"class":82,"line":186},2,[141,188,190],{"emptyLinePlaceholder":189},true,"\n",[141,192,194,197,200],{"class":82,"line":193},3,[141,195,196],{"class":176},"sales ",[141,198,199],{"class":172},"=",[141,201,202],{"class":176}," pd.DataFrame({\n",[141,204,206,209,212,215,218,221,223,225,227,230,232,234,236,238,240,242],{"class":82,"line":205},4,[141,207,208],{"class":150},"    \"Region\"",[141,210,211],{"class":176},": [",[141,213,214],{"class":150},"\"North\"",[141,216,217],{"class":176},", ",[141,219,220],{"class":150},"\"South\"",[141,222,217],{"class":176},[141,224,214],{"class":150},[141,226,217],{"class":176},[141,228,229],{"class":150},"\"West\"",[141,231,217],{"class":176},[141,233,220],{"class":150},[141,235,217],{"class":176},[141,237,214],{"class":150},[141,239,217],{"class":176},[141,241,229],{"class":150},[141,243,244],{"class":176},"],\n",[141,246,248,251,253,256,258,261,263,266,268,271,273,276,278,280,282,284],{"class":82,"line":247},5,[141,249,250],{"class":150},"    \"Rep\"",[141,252,211],{"class":176},[141,254,255],{"class":150},"\"Ana\"",[141,257,217],{"class":176},[141,259,260],{"class":150},"\"Ben\"",[141,262,217],{"class":176},[141,264,265],{"class":150},"\"Cara\"",[141,267,217],{"class":176},[141,269,270],{"class":150},"\"Dev\"",[141,272,217],{"class":176},[141,274,275],{"class":150},"\"Eve\"",[141,277,217],{"class":176},[141,279,255],{"class":150},[141,281,217],{"class":176},[141,283,270],{"class":150},[141,285,244],{"class":176},[141,287,289,292,294,297,299,302,304,306,308,310,312,314,316,318,320,322],{"class":82,"line":288},6,[141,290,291],{"class":150},"    \"Product\"",[141,293,211],{"class":176},[141,295,296],{"class":150},"\"Widget\"",[141,298,217],{"class":176},[141,300,301],{"class":150},"\"Gadget\"",[141,303,217],{"class":176},[141,305,296],{"class":150},[141,307,217],{"class":176},[141,309,296],{"class":150},[141,311,217],{"class":176},[141,313,301],{"class":150},[141,315,217],{"class":176},[141,317,301],{"class":150},[141,319,217],{"class":176},[141,321,296],{"class":150},[141,323,244],{"class":176},[141,325,327,330,332,336,338,341,343,346,348,351,353,356,358,361,363,366],{"class":82,"line":326},7,[141,328,329],{"class":150},"    \"Revenue\"",[141,331,211],{"class":176},[141,333,335],{"class":334},"sP0c6","12400.0",[141,337,217],{"class":176},[141,339,340],{"class":334},"9800.5",[141,342,217],{"class":176},[141,344,345],{"class":334},"15320.25",[141,347,217],{"class":176},[141,349,350],{"class":334},"7010.0",[141,352,217],{"class":176},[141,354,355],{"class":334},"4300.75",[141,357,217],{"class":176},[141,359,360],{"class":334},"6120.0",[141,362,217],{"class":176},[141,364,365],{"class":334},"11450.5",[141,367,244],{"class":176},[141,369,371,374,376,379,381,384,386,389,391,394,396,399,401,404,406,408],{"class":82,"line":370},8,[141,372,373],{"class":150},"    \"Units\"",[141,375,211],{"class":176},[141,377,378],{"class":334},"124",[141,380,217],{"class":176},[141,382,383],{"class":334},"98",[141,385,217],{"class":176},[141,387,388],{"class":334},"153",[141,390,217],{"class":176},[141,392,393],{"class":334},"70",[141,395,217],{"class":176},[141,397,398],{"class":334},"43",[141,400,217],{"class":176},[141,402,403],{"class":334},"61",[141,405,217],{"class":176},[141,407,73],{"class":334},[141,409,244],{"class":176},[141,411,413,416],{"class":82,"line":412},9,[141,414,415],{"class":150},"    \"Ordered\"",[141,417,418],{"class":176},": pd.to_datetime([\n",[141,420,422,425,427,430,432,435,437,440],{"class":82,"line":421},10,[141,423,424],{"class":150},"        \"2026-01-04\"",[141,426,217],{"class":176},[141,428,429],{"class":150},"\"2026-01-19\"",[141,431,217],{"class":176},[141,433,434],{"class":150},"\"2026-02-02\"",[141,436,217],{"class":176},[141,438,439],{"class":150},"\"2026-02-27\"",[141,441,442],{"class":176},",\n",[141,444,446,449,451,454,456,459],{"class":82,"line":445},11,[141,447,448],{"class":150},"        \"2026-03-08\"",[141,450,217],{"class":176},[141,452,453],{"class":150},"\"2026-03-15\"",[141,455,217],{"class":176},[141,457,458],{"class":150},"\"2026-03-30\"",[141,460,442],{"class":176},[141,462,464],{"class":82,"line":463},12,[141,465,466],{"class":176},"    ]),\n",[141,468,470],{"class":82,"line":469},13,[141,471,472],{"class":176},"})\n",[141,474,476,479,482,484,488,490,493,495,498,500,503],{"class":82,"line":475},14,[141,477,478],{"class":176},"sales.to_excel(",[141,480,481],{"class":150},"\"sales.xlsx\"",[141,483,217],{"class":176},[141,485,487],{"class":486},"sa561","sheet_name",[141,489,199],{"class":172},[141,491,492],{"class":150},"\"Detail\"",[141,494,217],{"class":176},[141,496,497],{"class":486},"index",[141,499,199],{"class":172},[141,501,502],{"class":334},"False",[141,504,505],{"class":176},")\n",[127,507,509],{"id":508},"sumif-with-one-condition","SUMIF with one condition",[132,511,513],{"className":163,"code":512,"language":165,"meta":137,"style":137},"# =SUMIF(A:A, \"North\", D:D)\nnorth = sales.loc[sales[\"Region\"] == \"North\", \"Revenue\"].sum()\nprint(f\"{north:,.2f}\")\n",[13,514,515,521,551],{"__ignoreMap":137},[141,516,517],{"class":82,"line":143},[141,518,520],{"class":519},"s-wDw","# =SUMIF(A:A, \"North\", D:D)\n",[141,522,523,526,528,531,534,537,540,543,545,548],{"class":82,"line":186},[141,524,525],{"class":176},"north ",[141,527,199],{"class":172},[141,529,530],{"class":176}," sales.loc[sales[",[141,532,533],{"class":150},"\"Region\"",[141,535,536],{"class":176},"] ",[141,538,539],{"class":172},"==",[141,541,542],{"class":150}," \"North\"",[141,544,217],{"class":176},[141,546,547],{"class":150},"\"Revenue\"",[141,549,550],{"class":176},"].sum()\n",[141,552,553,556,559,562,565,569,572,575,578,580],{"class":82,"line":193},[141,554,555],{"class":334},"print",[141,557,558],{"class":176},"(",[141,560,561],{"class":172},"f",[141,563,564],{"class":150},"\"",[141,566,568],{"class":567},"sSjpA","{",[141,570,571],{"class":176},"north",[141,573,574],{"class":172},":,.2f",[141,576,577],{"class":567},"}",[141,579,564],{"class":150},[141,581,505],{"class":176},[10,583,584,585,588,589,592,593,595],{},"Three things are happening, and separating them is what makes everything later easy.\n",[13,586,587],{},"sales[\"Region\"] == \"North\""," produces a boolean Series — one True or False per row. ",[13,590,591],{},".loc[mask, \"Revenue\"]"," selects the Revenue values from the rows where the mask is True. ",[13,594,120],{}," totals them.",[10,597,598],{},"Because the mask is an ordinary object you can inspect it, which Excel does not let you do:",[132,600,602],{"className":163,"code":601,"language":165,"meta":137,"style":137},"mask = sales[\"Region\"] == \"North\"\nprint(f\"{mask.sum()} of {len(mask)} rows match\")\nprint(sales.loc[mask, [\"Rep\", \"Revenue\"]])\n",[13,603,604,623,658],{"__ignoreMap":137},[141,605,606,609,611,614,616,618,620],{"class":82,"line":143},[141,607,608],{"class":176},"mask ",[141,610,199],{"class":172},[141,612,613],{"class":176}," sales[",[141,615,533],{"class":150},[141,617,536],{"class":176},[141,619,539],{"class":172},[141,621,622],{"class":150}," \"North\"\n",[141,624,625,627,629,631,633,635,638,640,643,645,648,651,653,656],{"class":82,"line":186},[141,626,555],{"class":334},[141,628,558],{"class":176},[141,630,561],{"class":172},[141,632,564],{"class":150},[141,634,568],{"class":567},[141,636,637],{"class":176},"mask.sum()",[141,639,577],{"class":567},[141,641,642],{"class":150}," of ",[141,644,568],{"class":567},[141,646,647],{"class":334},"len",[141,649,650],{"class":176},"(mask)",[141,652,577],{"class":567},[141,654,655],{"class":150}," rows match\"",[141,657,505],{"class":176},[141,659,660,662,665,668,670,672],{"class":82,"line":193},[141,661,555],{"class":334},[141,663,664],{"class":176},"(sales.loc[mask, [",[141,666,667],{"class":150},"\"Rep\"",[141,669,217],{"class":176},[141,671,547],{"class":150},[141,673,674],{"class":176},"]])\n",[10,676,677],{},"That inspection step is where translation bugs get caught. A SUMIF returning an unexpected total\ngives you no way to see which rows it added; a mask shows you immediately.",[127,679,681],{"id":680},"sumifs-with-several-conditions","SUMIFS with several conditions",[10,683,684,685,688,689,692,693,695,696,698],{},"Conditions combine with ",[13,686,687],{},"&"," for AND and ",[13,690,691],{},"|"," for OR. The parentheses are mandatory — Python's\noperator precedence binds ",[13,694,687],{}," more tightly than ",[13,697,539],{},", so omitting them produces a confusing error\nabout ambiguous truth values.",[132,700,702],{"className":163,"code":701,"language":165,"meta":137,"style":137},"# =SUMIFS(D:D, A:A, \"North\", C:C, \"Widget\")\nmask = (sales[\"Region\"] == \"North\") & (sales[\"Product\"] == \"Widget\")\nprint(sales.loc[mask, \"Revenue\"].sum())\n\n# =SUMIFS(D:D, D:D, \">10000\", A:A, \"\u003C>West\")\nmask = (sales[\"Revenue\"] > 10000) & (sales[\"Region\"] != \"West\")\nprint(sales.loc[mask, \"Revenue\"].sum())\n",[13,703,704,709,745,757,761,766,802],{"__ignoreMap":137},[141,705,706],{"class":82,"line":143},[141,707,708],{"class":519},"# =SUMIFS(D:D, A:A, \"North\", C:C, \"Widget\")\n",[141,710,711,713,715,718,720,722,724,726,729,731,733,736,738,740,743],{"class":82,"line":186},[141,712,608],{"class":176},[141,714,199],{"class":172},[141,716,717],{"class":176}," (sales[",[141,719,533],{"class":150},[141,721,536],{"class":176},[141,723,539],{"class":172},[141,725,542],{"class":150},[141,727,728],{"class":176},") ",[141,730,687],{"class":172},[141,732,717],{"class":176},[141,734,735],{"class":150},"\"Product\"",[141,737,536],{"class":176},[141,739,539],{"class":172},[141,741,742],{"class":150}," \"Widget\"",[141,744,505],{"class":176},[141,746,747,749,752,754],{"class":82,"line":193},[141,748,555],{"class":334},[141,750,751],{"class":176},"(sales.loc[mask, ",[141,753,547],{"class":150},[141,755,756],{"class":176},"].sum())\n",[141,758,759],{"class":82,"line":205},[141,760,190],{"emptyLinePlaceholder":189},[141,762,763],{"class":82,"line":247},[141,764,765],{"class":519},"# =SUMIFS(D:D, D:D, \">10000\", A:A, \"\u003C>West\")\n",[141,767,768,770,772,774,776,778,781,784,786,788,790,792,794,797,800],{"class":82,"line":288},[141,769,608],{"class":176},[141,771,199],{"class":172},[141,773,717],{"class":176},[141,775,547],{"class":150},[141,777,536],{"class":176},[141,779,780],{"class":172},">",[141,782,783],{"class":334}," 10000",[141,785,728],{"class":176},[141,787,687],{"class":172},[141,789,717],{"class":176},[141,791,533],{"class":150},[141,793,536],{"class":176},[141,795,796],{"class":172},"!=",[141,798,799],{"class":150}," \"West\"",[141,801,505],{"class":176},[141,803,804,806,808,810],{"class":82,"line":326},[141,805,555],{"class":334},[141,807,751],{"class":176},[141,809,547],{"class":150},[141,811,756],{"class":176},[10,813,814,815,818],{},"Excel expresses comparisons as strings — ",[13,816,817],{},"\">10000\""," — because a formula argument has to be text.\npandas uses real operators, which means no quoting rules to remember and no silent failure when a\ncriteria string is malformed.",[10,820,821],{},"Dates work the same way, and a half-open interval avoids the boundary double-count that catches\npeople summing month by month:",[132,823,825],{"className":163,"code":824,"language":165,"meta":137,"style":137},"# =SUMIFS(D:D, F:F, \">=2026-02-01\", F:F, \"\u003C2026-03-01\")\nfebruary = (sales[\"Ordered\"] >= \"2026-02-01\") & (sales[\"Ordered\"] \u003C \"2026-03-01\")\nprint(sales.loc[february, \"Revenue\"].sum())\n",[13,826,827,832,870],{"__ignoreMap":137},[141,828,829],{"class":82,"line":143},[141,830,831],{"class":519},"# =SUMIFS(D:D, F:F, \">=2026-02-01\", F:F, \"\u003C2026-03-01\")\n",[141,833,834,837,839,841,844,846,849,852,854,856,858,860,862,865,868],{"class":82,"line":186},[141,835,836],{"class":176},"february ",[141,838,199],{"class":172},[141,840,717],{"class":176},[141,842,843],{"class":150},"\"Ordered\"",[141,845,536],{"class":176},[141,847,848],{"class":172},">=",[141,850,851],{"class":150}," \"2026-02-01\"",[141,853,728],{"class":176},[141,855,687],{"class":172},[141,857,717],{"class":176},[141,859,843],{"class":150},[141,861,536],{"class":176},[141,863,864],{"class":172},"\u003C",[141,866,867],{"class":150}," \"2026-03-01\"",[141,869,505],{"class":176},[141,871,872,874,877,879],{"class":82,"line":193},[141,873,555],{"class":334},[141,875,876],{"class":176},"(sales.loc[february, ",[141,878,547],{"class":150},[141,880,756],{"class":176},[127,882,884],{"id":883},"the-grouped-form-that-replaces-a-copied-down-sumif","The grouped form that replaces a copied-down SUMIF",[28,886,37,891,37,894,37,897,37,900,37,909,37,915,37,921,37,926,37,930,37,934,37,937,37,942,37,946,37,949,37,952,37,955,37,963,37,968,37,973,37,976,37,980,37,983],{"viewBox":887,"role":31,"ariaLabelledBy":888,"xmlns":35,"style":36},"0 0 760 201",[889,890],"sif-transform-t","sif-transform-d",[39,892,893],{"id":889},"A copied-down SUMIF against one grouped pass",[43,895,896],{"id":890},"Filling a SUMIF down fifty thousand rows re-scans the column for every row, while groupby with transform computes each group's total once and aligns it back to every member row.",[47,898],{"x":49,"y":49,"width":50,"height":899,"fill":52},"201",[47,901],{"x":902,"y":903,"width":904,"height":905,"rx":906,"fill":907,"stroke":908,"style":69},"20","28","270.0","139","14","#fee8f2","var(--accent,#d81b73)",[54,910,914],{"x":911,"y":912,"style":913},"155.0","54","font-size:13px;font-weight:700;fill:var(--accent,#d81b73);text-anchor:middle","SUMIF filled down",[82,916],{"x1":917,"y1":918,"x2":919,"y2":918,"stroke":908,"style":920},"36","64","274.0","stroke-width:1px",[54,922,925],{"x":911,"y":923,"style":924},"86","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","50,000 formulas",[54,927,929],{"x":911,"y":928,"style":924},"109","one scan each",[54,931,933],{"x":911,"y":932,"style":924},"132","recalculates on edit",[47,935],{"x":936,"y":903,"width":904,"height":905,"rx":906,"fill":111,"stroke":112,"style":69},"470.0",[54,938,941],{"x":939,"y":912,"style":940},"605.0","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","transform('sum')",[82,943],{"x1":944,"y1":918,"x2":945,"y2":918,"stroke":112,"style":920},"486.0","724.0",[54,947,948],{"x":939,"y":923,"style":924},"one pass total",[54,950,951],{"x":939,"y":928,"style":924},"aligned to each row",[54,953,954],{"x":939,"y":932,"style":924},"computed once",[47,956],{"x":957,"y":958,"width":959,"height":960,"rx":961,"fill":962,"stroke":68},"316.0","78.5","128","38","19","#ebebfd",[54,964,967],{"x":56,"y":965,"style":966},"102.5","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","same result",[82,969],{"x1":970,"y1":971,"x2":972,"y2":971,"stroke":68,"style":69},"295.0","97.5","309.0",[88,974],{"points":975,"fill":91},"309.0,97.5 300.0,92.5 300.0,102.5",[82,977],{"x1":978,"y1":971,"x2":979,"y2":971,"stroke":68,"style":69},"449.0","463.0",[88,981],{"points":982,"fill":91},"463.0,97.5 454.0,92.5 454.0,102.5",[54,984,986],{"x":56,"y":985,"style":124},"187","the answers match; the work does not",[10,988,989],{},"A SUMIF written into every row of a 50,000-row sheet scans the whole column 50,000 times, which is\nwhy those workbooks take a minute to open. The pandas equivalent scans once and distributes the\nanswer.",[132,991,993],{"className":163,"code":992,"language":165,"meta":137,"style":137},"# Excel: =SUMIF(A:A, A2, D:D) filled down the sheet\nsales[\"Region total\"] = sales.groupby(\"Region\")[\"Revenue\"].transform(\"sum\")\n\n# Several keys, several aggregates — no extra passes\nsales[\"Region product total\"] = sales.groupby([\"Region\", \"Product\"])[\"Revenue\"].transform(\"sum\")\nsales[\"Share of region\"] = sales[\"Revenue\"] \u002F sales[\"Region total\"]\nprint(sales[[\"Region\", \"Product\", \"Revenue\", \"Region total\", \"Share of region\"]])\n",[13,994,995,1000,1030,1034,1039,1070,1097],{"__ignoreMap":137},[141,996,997],{"class":82,"line":143},[141,998,999],{"class":519},"# Excel: =SUMIF(A:A, A2, D:D) filled down the sheet\n",[141,1001,1002,1005,1008,1010,1012,1015,1017,1020,1022,1025,1028],{"class":82,"line":186},[141,1003,1004],{"class":176},"sales[",[141,1006,1007],{"class":150},"\"Region total\"",[141,1009,536],{"class":176},[141,1011,199],{"class":172},[141,1013,1014],{"class":176}," sales.groupby(",[141,1016,533],{"class":150},[141,1018,1019],{"class":176},")[",[141,1021,547],{"class":150},[141,1023,1024],{"class":176},"].transform(",[141,1026,1027],{"class":150},"\"sum\"",[141,1029,505],{"class":176},[141,1031,1032],{"class":82,"line":193},[141,1033,190],{"emptyLinePlaceholder":189},[141,1035,1036],{"class":82,"line":205},[141,1037,1038],{"class":519},"# Several keys, several aggregates — no extra passes\n",[141,1040,1041,1043,1046,1048,1050,1053,1055,1057,1059,1062,1064,1066,1068],{"class":82,"line":247},[141,1042,1004],{"class":176},[141,1044,1045],{"class":150},"\"Region product total\"",[141,1047,536],{"class":176},[141,1049,199],{"class":172},[141,1051,1052],{"class":176}," sales.groupby([",[141,1054,533],{"class":150},[141,1056,217],{"class":176},[141,1058,735],{"class":150},[141,1060,1061],{"class":176},"])[",[141,1063,547],{"class":150},[141,1065,1024],{"class":176},[141,1067,1027],{"class":150},[141,1069,505],{"class":176},[141,1071,1072,1074,1077,1079,1081,1083,1085,1087,1090,1092,1094],{"class":82,"line":288},[141,1073,1004],{"class":176},[141,1075,1076],{"class":150},"\"Share of region\"",[141,1078,536],{"class":176},[141,1080,199],{"class":172},[141,1082,613],{"class":176},[141,1084,547],{"class":150},[141,1086,536],{"class":176},[141,1088,1089],{"class":172},"\u002F",[141,1091,613],{"class":176},[141,1093,1007],{"class":150},[141,1095,1096],{"class":176},"]\n",[141,1098,1099,1101,1104,1106,1108,1110,1112,1114,1116,1118,1120,1122],{"class":82,"line":326},[141,1100,555],{"class":334},[141,1102,1103],{"class":176},"(sales[[",[141,1105,533],{"class":150},[141,1107,217],{"class":176},[141,1109,735],{"class":150},[141,1111,217],{"class":176},[141,1113,547],{"class":150},[141,1115,217],{"class":176},[141,1117,1007],{"class":150},[141,1119,217],{"class":176},[141,1121,1076],{"class":150},[141,1123,674],{"class":176},[10,1125,1126,1129],{},[13,1127,1128],{},"transform"," is the function to reach for whenever the requirement is \"give every row its group's\ntotal\". The share calculation on the last line is the reason it matters: with the group total on\neach row, a percentage-of-total column is one division rather than a second SUMIF.",[127,1131,1133],{"id":1132},"producing-a-summary-table-instead","Producing a summary table instead",[10,1135,1136,1137,1140],{},"When the output is a summary rather than an enriched detail sheet, ",[13,1138,1139],{},"groupby().agg()"," gives it\ndirectly — and gives several aggregates in one pass, which SUMIFS cannot do at all.",[132,1142,1144],{"className":163,"code":1143,"language":165,"meta":137,"style":137},"summary = sales.groupby(\"Region\", as_index=False).agg(\n    Revenue=(\"Revenue\", \"sum\"),\n    Units=(\"Units\", \"sum\"),\n    Orders=(\"Revenue\", \"size\"),\n    Best=(\"Revenue\", \"max\"),\n)\nsummary[\"Average order\"] = (summary[\"Revenue\"] \u002F summary[\"Orders\"]).round(2)\nprint(summary.sort_values(\"Revenue\", ascending=False))\n",[13,1145,1146,1169,1187,1205,1223,1241,1245,1280],{"__ignoreMap":137},[141,1147,1148,1151,1153,1155,1157,1159,1162,1164,1166],{"class":82,"line":143},[141,1149,1150],{"class":176},"summary ",[141,1152,199],{"class":172},[141,1154,1014],{"class":176},[141,1156,533],{"class":150},[141,1158,217],{"class":176},[141,1160,1161],{"class":486},"as_index",[141,1163,199],{"class":172},[141,1165,502],{"class":334},[141,1167,1168],{"class":176},").agg(\n",[141,1170,1171,1174,1176,1178,1180,1182,1184],{"class":82,"line":186},[141,1172,1173],{"class":486},"    Revenue",[141,1175,199],{"class":172},[141,1177,558],{"class":176},[141,1179,547],{"class":150},[141,1181,217],{"class":176},[141,1183,1027],{"class":150},[141,1185,1186],{"class":176},"),\n",[141,1188,1189,1192,1194,1196,1199,1201,1203],{"class":82,"line":193},[141,1190,1191],{"class":486},"    Units",[141,1193,199],{"class":172},[141,1195,558],{"class":176},[141,1197,1198],{"class":150},"\"Units\"",[141,1200,217],{"class":176},[141,1202,1027],{"class":150},[141,1204,1186],{"class":176},[141,1206,1207,1210,1212,1214,1216,1218,1221],{"class":82,"line":205},[141,1208,1209],{"class":486},"    Orders",[141,1211,199],{"class":172},[141,1213,558],{"class":176},[141,1215,547],{"class":150},[141,1217,217],{"class":176},[141,1219,1220],{"class":150},"\"size\"",[141,1222,1186],{"class":176},[141,1224,1225,1228,1230,1232,1234,1236,1239],{"class":82,"line":247},[141,1226,1227],{"class":486},"    Best",[141,1229,199],{"class":172},[141,1231,558],{"class":176},[141,1233,547],{"class":150},[141,1235,217],{"class":176},[141,1237,1238],{"class":150},"\"max\"",[141,1240,1186],{"class":176},[141,1242,1243],{"class":82,"line":288},[141,1244,505],{"class":176},[141,1246,1247,1250,1253,1255,1257,1260,1262,1264,1266,1269,1272,1275,1278],{"class":82,"line":326},[141,1248,1249],{"class":176},"summary[",[141,1251,1252],{"class":150},"\"Average order\"",[141,1254,536],{"class":176},[141,1256,199],{"class":172},[141,1258,1259],{"class":176}," (summary[",[141,1261,547],{"class":150},[141,1263,536],{"class":176},[141,1265,1089],{"class":172},[141,1267,1268],{"class":176}," summary[",[141,1270,1271],{"class":150},"\"Orders\"",[141,1273,1274],{"class":176},"]).round(",[141,1276,1277],{"class":334},"2",[141,1279,505],{"class":176},[141,1281,1282,1284,1287,1289,1291,1294,1296,1298],{"class":82,"line":370},[141,1283,555],{"class":334},[141,1285,1286],{"class":176},"(summary.sort_values(",[141,1288,547],{"class":150},[141,1290,217],{"class":176},[141,1292,1293],{"class":486},"ascending",[141,1295,199],{"class":172},[141,1297,502],{"class":334},[141,1299,1300],{"class":176},"))\n",[10,1302,1303,1304,1307,1308,1312],{},"Naming the outputs in ",[13,1305,1306],{},"agg"," is worth the extra characters: the resulting frame has flat, meaningful\ncolumn names rather than a multi-level index that then has to be flattened before it can be written\nto a sheet. That single habit removes most of the friction between an aggregation and\n",[22,1309,1311],{"href":1310},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fwrite-pandas-dataframe-to-excel-without-index\u002F","Write a Pandas DataFrame to Excel Without the Index",".",[127,1314,1316],{"id":1315},"wildcards-and-partial-matches","Wildcards and partial matches",[10,1318,1319,1320,16,1323,1326],{},"Excel's criteria accept ",[13,1321,1322],{},"*",[13,1324,1325],{},"?",". The pandas equivalents are string methods, which are more\ncapable and slightly more verbose.",[132,1328,1330],{"className":163,"code":1329,"language":165,"meta":137,"style":137},"# =SUMIF(C:C, \"Widg*\", D:D)\nstarts = sales[\"Product\"].str.startswith(\"Widg\", na=False)\nprint(sales.loc[starts, \"Revenue\"].sum())\n\n# =SUMIF(B:B, \"*a*\", D:D) — contains\ncontains = sales[\"Rep\"].str.contains(\"a\", case=False, na=False)\nprint(sales.loc[contains, \"Revenue\"].sum())\n\n# Several values at once — Excel needs one SUMIF per value plus addition\nchosen = sales[\"Region\"].isin([\"North\", \"West\"])\nprint(sales.loc[chosen, \"Revenue\"].sum())\n",[13,1331,1332,1337,1365,1376,1380,1385,1421,1432,1436,1441,1464],{"__ignoreMap":137},[141,1333,1334],{"class":82,"line":143},[141,1335,1336],{"class":519},"# =SUMIF(C:C, \"Widg*\", D:D)\n",[141,1338,1339,1342,1344,1346,1348,1351,1354,1356,1359,1361,1363],{"class":82,"line":186},[141,1340,1341],{"class":176},"starts ",[141,1343,199],{"class":172},[141,1345,613],{"class":176},[141,1347,735],{"class":150},[141,1349,1350],{"class":176},"].str.startswith(",[141,1352,1353],{"class":150},"\"Widg\"",[141,1355,217],{"class":176},[141,1357,1358],{"class":486},"na",[141,1360,199],{"class":172},[141,1362,502],{"class":334},[141,1364,505],{"class":176},[141,1366,1367,1369,1372,1374],{"class":82,"line":193},[141,1368,555],{"class":334},[141,1370,1371],{"class":176},"(sales.loc[starts, ",[141,1373,547],{"class":150},[141,1375,756],{"class":176},[141,1377,1378],{"class":82,"line":205},[141,1379,190],{"emptyLinePlaceholder":189},[141,1381,1382],{"class":82,"line":247},[141,1383,1384],{"class":519},"# =SUMIF(B:B, \"*a*\", D:D) — contains\n",[141,1386,1387,1390,1392,1394,1396,1399,1402,1404,1407,1409,1411,1413,1415,1417,1419],{"class":82,"line":288},[141,1388,1389],{"class":176},"contains ",[141,1391,199],{"class":172},[141,1393,613],{"class":176},[141,1395,667],{"class":150},[141,1397,1398],{"class":176},"].str.contains(",[141,1400,1401],{"class":150},"\"a\"",[141,1403,217],{"class":176},[141,1405,1406],{"class":486},"case",[141,1408,199],{"class":172},[141,1410,502],{"class":334},[141,1412,217],{"class":176},[141,1414,1358],{"class":486},[141,1416,199],{"class":172},[141,1418,502],{"class":334},[141,1420,505],{"class":176},[141,1422,1423,1425,1428,1430],{"class":82,"line":326},[141,1424,555],{"class":334},[141,1426,1427],{"class":176},"(sales.loc[contains, ",[141,1429,547],{"class":150},[141,1431,756],{"class":176},[141,1433,1434],{"class":82,"line":370},[141,1435,190],{"emptyLinePlaceholder":189},[141,1437,1438],{"class":82,"line":412},[141,1439,1440],{"class":519},"# Several values at once — Excel needs one SUMIF per value plus addition\n",[141,1442,1443,1446,1448,1450,1452,1455,1457,1459,1461],{"class":82,"line":421},[141,1444,1445],{"class":176},"chosen ",[141,1447,199],{"class":172},[141,1449,613],{"class":176},[141,1451,533],{"class":150},[141,1453,1454],{"class":176},"].isin([",[141,1456,214],{"class":150},[141,1458,217],{"class":176},[141,1460,229],{"class":150},[141,1462,1463],{"class":176},"])\n",[141,1465,1466,1468,1471,1473],{"class":82,"line":445},[141,1467,555],{"class":334},[141,1469,1470],{"class":176},"(sales.loc[chosen, ",[141,1472,547],{"class":150},[141,1474,756],{"class":176},[10,1476,1477,1480,1481,1484],{},[13,1478,1479],{},"na=False"," is not optional in practice. Without it, a missing value in the column makes\n",[13,1482,1483],{},"str.contains"," return NaN for that row, and using a mask containing NaN raises rather than skipping\nthe row — a failure that only appears once real data arrives.",[10,1486,1487,1490],{},[13,1488,1489],{},"isin"," deserves particular attention because it has no clean Excel equivalent at all. A SUMIF over\nfive acceptable values means five formulas added together, which is exactly the kind of thing that\nsilently stops being maintained when a sixth value appears.",[127,1492,1494],{"id":1493},"weighted-totals-and-the-sumproduct-case","Weighted totals, and the SUMPRODUCT case",[10,1496,1497],{},"Once a report grows past simple conditional sums it usually reaches for SUMPRODUCT, which multiplies\narrays element by element before totalling them. That is a natural operation on columns, so it reads\nbetter in pandas than it does in a formula bar.",[132,1499,1501],{"className":163,"code":1500,"language":165,"meta":137,"style":137},"# =SUMPRODUCT((A2:A100=\"North\")*(D2:D100)*(E2:E100))\nmask = sales[\"Region\"] == \"North\"\nweighted = (sales[\"Revenue\"] * sales[\"Units\"]).loc[mask].sum()\n\n# A weighted average price, which SUMIF alone cannot express\nby_region = sales.groupby(\"Region\").apply(\n    lambda part: (part[\"Revenue\"] * part[\"Units\"]).sum() \u002F part[\"Units\"].sum(),\n    include_groups=False,\n)\nprint(by_region.round(2))\n",[13,1502,1503,1508,1524,1546,1550,1555,1569,1600,1611,1615],{"__ignoreMap":137},[141,1504,1505],{"class":82,"line":143},[141,1506,1507],{"class":519},"# =SUMPRODUCT((A2:A100=\"North\")*(D2:D100)*(E2:E100))\n",[141,1509,1510,1512,1514,1516,1518,1520,1522],{"class":82,"line":186},[141,1511,608],{"class":176},[141,1513,199],{"class":172},[141,1515,613],{"class":176},[141,1517,533],{"class":150},[141,1519,536],{"class":176},[141,1521,539],{"class":172},[141,1523,622],{"class":150},[141,1525,1526,1529,1531,1533,1535,1537,1539,1541,1543],{"class":82,"line":193},[141,1527,1528],{"class":176},"weighted ",[141,1530,199],{"class":172},[141,1532,717],{"class":176},[141,1534,547],{"class":150},[141,1536,536],{"class":176},[141,1538,1322],{"class":172},[141,1540,613],{"class":176},[141,1542,1198],{"class":150},[141,1544,1545],{"class":176},"]).loc[mask].sum()\n",[141,1547,1548],{"class":82,"line":205},[141,1549,190],{"emptyLinePlaceholder":189},[141,1551,1552],{"class":82,"line":247},[141,1553,1554],{"class":519},"# A weighted average price, which SUMIF alone cannot express\n",[141,1556,1557,1560,1562,1564,1566],{"class":82,"line":288},[141,1558,1559],{"class":176},"by_region ",[141,1561,199],{"class":172},[141,1563,1014],{"class":176},[141,1565,533],{"class":150},[141,1567,1568],{"class":176},").apply(\n",[141,1570,1571,1574,1577,1579,1581,1583,1586,1588,1591,1593,1595,1597],{"class":82,"line":326},[141,1572,1573],{"class":172},"    lambda",[141,1575,1576],{"class":176}," part: (part[",[141,1578,547],{"class":150},[141,1580,536],{"class":176},[141,1582,1322],{"class":172},[141,1584,1585],{"class":176}," part[",[141,1587,1198],{"class":150},[141,1589,1590],{"class":176},"]).sum() ",[141,1592,1089],{"class":172},[141,1594,1585],{"class":176},[141,1596,1198],{"class":150},[141,1598,1599],{"class":176},"].sum(),\n",[141,1601,1602,1605,1607,1609],{"class":82,"line":370},[141,1603,1604],{"class":486},"    include_groups",[141,1606,199],{"class":172},[141,1608,502],{"class":334},[141,1610,442],{"class":176},[141,1612,1613],{"class":82,"line":412},[141,1614,505],{"class":176},[141,1616,1617,1619,1622,1624],{"class":82,"line":421},[141,1618,555],{"class":334},[141,1620,1621],{"class":176},"(by_region.round(",[141,1623,1277],{"class":334},[141,1625,1300],{"class":176},[10,1627,1628,1629,1632],{},"The ",[13,1630,1631],{},"include_groups=False"," argument keeps recent pandas versions from warning about the grouping\ncolumn being passed into the function; without it the code still works but emits a deprecation\nnotice that clutters a scheduled job's log.",[10,1634,1635],{},"Weighted aggregates are the point where a spreadsheet usually starts to sprawl into helper columns —\na product column here, a subtotal row there — and where moving the calculation into Python collapses\nseveral sheets into a handful of lines. The helper columns were only ever there because a formula\ncould not hold two operations at once.",[127,1637,1639],{"id":1638},"reproducing-a-subtotal-row","Reproducing a subtotal row",[10,1641,1642],{},"Reports frequently end with a total row, and a SUMIF-based sheet gets it from another formula. In\npandas the equivalent is to compute the summary and append the total explicitly, which has the\nadvantage of being visible rather than hidden in row 200.",[132,1644,1646],{"className":163,"code":1645,"language":165,"meta":137,"style":137},"summary = sales.groupby(\"Region\", as_index=False)[\"Revenue\"].sum()\ntotal = pd.DataFrame({\"Region\": [\"Total\"], \"Revenue\": [summary[\"Revenue\"].sum()]})\nwith_total = pd.concat([summary, total], ignore_index=True)\nprint(with_total)\n",[13,1647,1648,1672,1702,1722],{"__ignoreMap":137},[141,1649,1650,1652,1654,1656,1658,1660,1662,1664,1666,1668,1670],{"class":82,"line":143},[141,1651,1150],{"class":176},[141,1653,199],{"class":172},[141,1655,1014],{"class":176},[141,1657,533],{"class":150},[141,1659,217],{"class":176},[141,1661,1161],{"class":486},[141,1663,199],{"class":172},[141,1665,502],{"class":334},[141,1667,1019],{"class":176},[141,1669,547],{"class":150},[141,1671,550],{"class":176},[141,1673,1674,1677,1679,1682,1684,1686,1689,1692,1694,1697,1699],{"class":82,"line":186},[141,1675,1676],{"class":176},"total ",[141,1678,199],{"class":172},[141,1680,1681],{"class":176}," pd.DataFrame({",[141,1683,533],{"class":150},[141,1685,211],{"class":176},[141,1687,1688],{"class":150},"\"Total\"",[141,1690,1691],{"class":176},"], ",[141,1693,547],{"class":150},[141,1695,1696],{"class":176},": [summary[",[141,1698,547],{"class":150},[141,1700,1701],{"class":176},"].sum()]})\n",[141,1703,1704,1707,1709,1712,1715,1717,1720],{"class":82,"line":193},[141,1705,1706],{"class":176},"with_total ",[141,1708,199],{"class":172},[141,1710,1711],{"class":176}," pd.concat([summary, total], ",[141,1713,1714],{"class":486},"ignore_index",[141,1716,199],{"class":172},[141,1718,1719],{"class":334},"True",[141,1721,505],{"class":176},[141,1723,1724,1726],{"class":82,"line":205},[141,1725,555],{"class":334},[141,1727,1728],{"class":176},"(with_total)\n",[10,1730,1731],{},"Building the total from the summary rather than from the source frame is deliberate: it guarantees\nthe total equals the sum of the rows above it, which is exactly the property that breaks in a\nspreadsheet when a SUM range stops one row short of the data. If the two ever need to disagree —\nbecause some rows are excluded from the breakdown — that becomes an explicit decision rather than a\nrange that quietly drifted.",[127,1733,1735],{"id":1734},"common-pitfalls","Common pitfalls",[1737,1738,1739,1755],"table",{},[1740,1741,1742],"thead",{},[1743,1744,1745,1749,1752],"tr",{},[1746,1747,1748],"th",{},"Symptom",[1746,1750,1751],{},"Cause",[1746,1753,1754],{},"Fix",[1756,1757,1758,1782,1796,1813,1828,1845],"tbody",{},[1743,1759,1760,1766,1779],{},[1761,1762,1763],"td",{},[13,1764,1765],{},"ValueError: The truth value of a Series is ambiguous",[1761,1767,1768,1089,1771,1774,1775,1089,1777],{},[13,1769,1770],{},"and",[13,1772,1773],{},"or"," used instead of ",[13,1776,687],{},[13,1778,691],{},[1761,1780,1781],{},"Use the bitwise operators and parenthesise each condition",[1743,1783,1784,1787,1790],{},[1761,1785,1786],{},"Total is 0 or values are concatenated",[1761,1788,1789],{},"The column is text, not numeric",[1761,1791,1792,1795],{},[13,1793,1794],{},"pd.to_numeric(col, errors=\"coerce\")"," before summing",[1743,1797,1798,1801,1804],{},[1761,1799,1800],{},"Fewer rows match than in Excel",[1761,1802,1803],{},"Trailing spaces or case differences in the key",[1761,1805,1806,16,1809,1812],{},[13,1807,1808],{},".str.strip()",[13,1810,1811],{},".str.casefold()"," first",[1743,1814,1815,1820,1823],{},[1761,1816,1817,1819],{},[13,1818,1483],{}," raises on missing values",[1761,1821,1822],{},"NaN in the column",[1761,1824,1825,1826],{},"Pass ",[13,1827,1479],{},[1743,1829,1830,1833,1836],{},[1761,1831,1832],{},"Boundary dates counted twice",[1761,1834,1835],{},"Two inclusive comparisons",[1761,1837,1838,1839,16,1842],{},"Use a half-open interval: ",[13,1840,1841],{},">= start",[13,1843,1844],{},"\u003C end",[1743,1846,1847,1852,1855],{},[1761,1848,1849,1851],{},[13,1850,1128],{}," result is all NaN",[1761,1853,1854],{},"Grouping on a column containing NaN",[1761,1856,1857,1860,1861,1864],{},[13,1858,1859],{},"dropna=False"," on ",[13,1862,1863],{},"groupby",", or fill the key first",[127,1866,1868],{"id":1867},"performance-and-scale","Performance and scale",[28,1870,37,1876,37,1879,37,1882,37,1886,37,1890,37,1899,37,1905,37,1911,37,1915,37,1918,37,1921,37,1926,37,1930,37,1932,37,1935,37,1938,37,1942],{"viewBox":1871,"role":31,"ariaLabelledBy":1872,"xmlns":35,"style":1875},"0 0 720 196",[1873,1874],"sif-scale-t","sif-scale-d","width:100%;max-width:720px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[39,1877,1878],{"id":1873},"Conditional totals over 200,000 rows",[43,1880,1881],{"id":1874},"A vectorised masked sum in pandas finishes in milliseconds, a grouped transform is barely slower, and a copied-down SUMIF recalculated by Excel is orders of magnitude more work.",[47,1883],{"x":49,"y":49,"width":1884,"height":1885,"fill":52},"720","196",[54,1887,914],{"x":902,"y":1888,"style":1889},"56","font-size:12px;font-weight:600;fill:var(--text,#172033);text-anchor:start",[47,1891],{"x":1892,"y":1893,"width":1894,"height":1895,"rx":1896,"fill":1897,"stroke":1898},"200","40","357.8","26","6","#e7ebef","var(--line,#cdd5e6)",[47,1900],{"x":899,"y":1901,"width":1902,"height":1903,"rx":1904,"fill":907,"stroke":908},"41","355.8","24","5",[54,1906,1910],{"x":1907,"y":1908,"style":1909},"569.8","58","font-size:12px;font-weight:700;fill:var(--accent,#d81b73);text-anchor:start","quadratic scanning",[54,1912,1914],{"x":902,"y":1913,"style":1889},"100","groupby transform",[47,1916],{"x":1892,"y":1917,"width":1894,"height":1895,"rx":1896,"fill":1897,"stroke":1898},"84",[47,1919],{"x":899,"y":1920,"width":903,"height":1903,"rx":1904,"fill":111,"stroke":112},"85",[54,1922,1925],{"x":1907,"y":1923,"style":1924},"102","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","one pass",[54,1927,1929],{"x":902,"y":1928,"style":1889},"144","masked sum",[47,1931],{"x":1892,"y":959,"width":1894,"height":1895,"rx":1896,"fill":1897,"stroke":1898},[47,1933],{"x":899,"y":1934,"width":903,"height":1903,"rx":1904,"fill":111,"stroke":112},"129",[54,1936,1925],{"x":1907,"y":1937,"style":1924},"146",[54,1939,1941],{"x":902,"y":902,"style":1940},"font-size:11.5px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:start","relative cost",[54,1943,1946],{"x":1944,"y":1945,"style":124},"360.0","186","the gap widens with every row added",[10,1948,1949],{},"The performance difference is not marginal. A conditional total over 200,000 rows is a single\nvectorised pass in pandas — milliseconds — while the same work as a copied-down SUMIF is a quadratic\namount of scanning that Excel repeats on every recalculation.",[10,1951,1952,1953,1955],{},"Two habits keep the pandas version fast as data grows. Compute masks once and reuse them rather than\nrebuilding the same comparison for each aggregate. And prefer a single ",[13,1954,1139],{}," over\nseveral separate masked sums when the groups are the same, because each pass over the frame costs\nsomething even when that something is small.",[132,1957,1959],{"className":163,"code":1958,"language":165,"meta":137,"style":137},"# One pass, three answers\nby_region = sales.groupby(\"Region\")[\"Revenue\"].agg([\"sum\", \"mean\", \"count\"])\n\n# Rather than three passes over the same rows\nnorth = sales.loc[sales[\"Region\"] == \"North\", \"Revenue\"]\n",[13,1960,1961,1966,1997,2001,2006],{"__ignoreMap":137},[141,1962,1963],{"class":82,"line":143},[141,1964,1965],{"class":519},"# One pass, three answers\n",[141,1967,1968,1970,1972,1974,1976,1978,1980,1983,1985,1987,1990,1992,1995],{"class":82,"line":186},[141,1969,1559],{"class":176},[141,1971,199],{"class":172},[141,1973,1014],{"class":176},[141,1975,533],{"class":150},[141,1977,1019],{"class":176},[141,1979,547],{"class":150},[141,1981,1982],{"class":176},"].agg([",[141,1984,1027],{"class":150},[141,1986,217],{"class":176},[141,1988,1989],{"class":150},"\"mean\"",[141,1991,217],{"class":176},[141,1993,1994],{"class":150},"\"count\"",[141,1996,1463],{"class":176},[141,1998,1999],{"class":82,"line":193},[141,2000,190],{"emptyLinePlaceholder":189},[141,2002,2003],{"class":82,"line":205},[141,2004,2005],{"class":519},"# Rather than three passes over the same rows\n",[141,2007,2008,2010,2012,2014,2016,2018,2020,2022,2024,2026],{"class":82,"line":247},[141,2009,525],{"class":176},[141,2011,199],{"class":172},[141,2013,530],{"class":176},[141,2015,533],{"class":150},[141,2017,536],{"class":176},[141,2019,539],{"class":172},[141,2021,542],{"class":150},[141,2023,217],{"class":176},[141,2025,547],{"class":150},[141,2027,1096],{"class":176},[10,2029,2030,2031,1312],{},"For genuinely large files, the read is the bottleneck long before the aggregation is — which is the\nargument in ",[22,2032,2034],{"href":2033},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fread-large-excel-file-in-chunks-with-pandas\u002F","Read a Large Excel File in Chunks with Pandas",[127,2036,2038],{"id":2037},"conclusion","Conclusion",[10,2040,2041,2043,2044,217,2047,2049,2050,2052,2053,2056,2057,2059],{},[13,2042,15],{}," becomes ",[13,2045,2046],{},"df.loc[mask, col].sum()",[13,2048,19],{}," becomes the same thing with conditions joined by\n",[13,2051,687],{},", and a SUMIF copied down a column becomes ",[13,2054,2055],{},"groupby().transform(\"sum\")"," — one pass instead of one\nper row. Build the mask as a named variable so you can inspect what matched, coerce numeric columns\nbefore summing, and reach for ",[13,2058,1139],{}," when the output is a summary table rather than an\nenriched detail sheet.",[127,2061,2063],{"id":2062},"frequently-asked-questions","Frequently asked questions",[10,2065,2066,2070,2071,2074,2075,2077],{},[2067,2068,2069],"strong",{},"How do I write SUMIFS with a date range?","\nBuild the mask with two comparisons combined by &, exactly as you would for any other pair of conditions: (df",[141,2072,2073],{},"'Ordered'"," >= start) & (df",[141,2076,2073],{}," \u003C end). Using a half-open interval avoids the classic double-counting of the boundary day.",[10,2079,2080,2083],{},[2067,2081,2082],{},"What is the difference between transform('sum') and groupby().sum()?","\ngroupby().sum() returns one row per group. transform('sum') returns one value per original row — the group's total, aligned back to every member. transform is the direct equivalent of a SUMIF copied down a column.",[10,2085,2086,2089],{},[2067,2087,2088],{},"Why does my sum come out as 0 or as concatenated text?","\nThe column is text rather than numeric. Excel coerces silently; pandas does not. Run pd.to_numeric(col, errors='coerce') first, then check how many NaN values it produced.",[10,2091,2092,2095],{},[2067,2093,2094],{},"How do I reproduce SUMIF's wildcard criteria?","\nUse .str.contains() with a regular expression, or .str.startswith(). Excel's asterisk becomes .* and its question mark becomes a single-character match; remember to pass na=False so missing values do not raise.",[10,2097,2098,2101,2102,2108],{},[2067,2099,2100],{},"Can I sum several columns at once?","\nYes — pass a list of column names to the selection: df.loc",[141,2103,2104,2105],{},"mask, ",[141,2106,2107],{},"'Revenue', 'Units'",".sum(). Excel needs a separate SUMIFS per column, which is one of the places the translation is plainly better.",[127,2110,2112],{"id":2111},"related","Related",[2114,2115,2116,2123,2130,2137,2144],"ul",{},[2117,2118,2119,2120,2122],"li",{},"Up one level: ",[22,2121,25],{"href":24}," — the whole function map and the pattern behind it.",[2117,2124,2125,2129],{},[22,2126,2128],{"href":2127},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fcountif-and-countifs-equivalent-in-pandas\u002F","COUNTIF and COUNTIFS Equivalent in pandas"," — the same masks, counted instead of summed.",[2117,2131,2132,2136],{},[22,2133,2135],{"href":2134},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frunning-totals-and-year-over-year-growth-in-pandas\u002F","Running Totals and Year-Over-Year Growth in pandas"," — cumulative and period-over-period versions.",[2117,2138,2139,2143],{},[22,2140,2142],{"href":2141},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcreate-pivot-table-from-excel-with-pandas\u002F","Create a Pivot Table from Excel with Pandas"," — the cross-tab form of the same aggregation.",[2117,2145,2146,2150],{},[22,2147,2149],{"href":2148},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fgroup-excel-rows-by-month-and-quarter-with-pandas\u002F","Group Excel Rows by Month and Quarter with Pandas"," — date-based grouping without building the mask by hand.",[2152,2153,2154],"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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":137,"searchDepth":186,"depth":186,"links":2156},[2157,2158,2159,2160,2161,2162,2163,2164,2165,2166,2167,2168,2169],{"id":129,"depth":186,"text":130},{"id":508,"depth":186,"text":509},{"id":680,"depth":186,"text":681},{"id":883,"depth":186,"text":884},{"id":1132,"depth":186,"text":1133},{"id":1315,"depth":186,"text":1316},{"id":1493,"depth":186,"text":1494},{"id":1638,"depth":186,"text":1639},{"id":1734,"depth":186,"text":1735},{"id":1867,"depth":186,"text":1868},{"id":2037,"depth":186,"text":2038},{"id":2062,"depth":186,"text":2063},{"id":2111,"depth":186,"text":2112},"2026-09-04","Translate conditional totals into pandas: boolean masks, several conditions joined with &, wildcards as string methods, and transform for a SUMIF copied down a column.","md",[2174,2176,2178,2180,2182],{"q":2069,"a":2175},"Build the mask with two comparisons combined by &, exactly as you would for any other pair of conditions: (df['Ordered'] >= start) & (df['Ordered'] \u003C end). Using a half-open interval avoids the classic double-counting of the boundary day.",{"q":2082,"a":2177},"groupby().sum() returns one row per group. transform('sum') returns one value per original row — the group's total, aligned back to every member. transform is the direct equivalent of a SUMIF copied down a column.",{"q":2088,"a":2179},"The column is text rather than numeric. Excel coerces silently; pandas does not. Run pd.to_numeric(col, errors='coerce') first, then check how many NaN values it produced.",{"q":2094,"a":2181},"Use .str.contains() with a regular expression, or .str.startswith(). Excel's asterisk becomes .* and its question mark becomes a single-character match; remember to pass na=False so missing values do not raise.",{"q":2100,"a":2183},"Yes — pass a list of column names to the selection: df.loc[mask, ['Revenue', 'Units']].sum(). Excel needs a separate SUMIFS per column, which is one of the places the translation is plainly better.",{"breadcrumb":2185},[2186,2188,2191],{"name":2187,"item":1089},"Home",{"name":2189,"item":2190},"Advanced Data Transformation and Cleaning","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":25,"item":24},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsumif-and-sumifs-equivalent-in-pandas",{"title":5,"description":2194},"Reproduce SUMIF and SUMIFS in pandas with boolean masks and .loc, handle date ranges and wildcards, and replace a copied-down SUMIF with groupby().transform('sum').","sumif-and-sumifs-equivalent-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsumif-and-sumifs-equivalent-in-pandas\u002Findex","how-to","OduJjnoTeirmgydM5rQK9jBWAIDM4xcdCqToEeeMSck",[2200,2203],{"title":2135,"path":2201,"stem":2202,"children":-1},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frunning-totals-and-year-over-year-growth-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frunning-totals-and-year-over-year-growth-in-pandas\u002Findex",{"title":2204,"path":2205,"stem":2206,"children":-1},"Handling Missing Data in Excel Reports with Pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports","advanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Findex",1788710154434]