[{"data":1,"prerenderedAt":2074},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fscan-reduce-and-map-equivalents-in-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fscan-reduce-and-map-equivalents-in-pandas":2040,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fscan-reduce-and-map-equivalents-in-pandas":2049,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fscan-reduce-and-map-equivalents-in-pandas":2054,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fscan-reduce-and-map-equivalents-in-pandas":2055},{"id":4,"title":5,"body":6,"dateModified":2006,"datePublished":2006,"description":2007,"extension":2008,"faq":2009,"keyword":2018,"kind":2019,"meta":2020,"navigation":128,"path":2029,"seo":2030,"slug":2033,"stem":2034,"topics":2035,"type":2038,"__hash__":2039},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fscan-reduce-and-map-equivalents-in-pandas\u002Findex.md","SCAN, REDUCE, MAP and BYROW Equivalents in pandas",{"type":7,"value":8,"toc":1993},"minimark",[9,43,48,76,86,90,100,207,234,238,260,419,439,627,631,643,750,759,885,888,892,898,1035,1064,1193,1197,1206,1403,1430,1434,1440,1671,1684,1822,1826,1886,1890,1928,1932,1938,1944,1950,1956,1960,1989],[10,11,12,16,17,20,21,24,25,28,29,32,33,36,37,42],"p",{},[13,14,15],"code",{},"LAMBDA"," turned Excel formulas into small programs, and its helper functions are where that shows most: ",[13,18,19],{},"SCAN"," walks down a column carrying a running value, ",[13,22,23],{},"REDUCE"," does the same but returns only the final value, ",[13,26,27],{},"MAP"," applies a calculation to every element, and ",[13,30,31],{},"BYROW"," and ",[13,34,35],{},"BYCOL"," produce one result per row or column. A workbook built around them — a stock balance that may not go negative, a fee schedule that compounds, a per-row validation — has to be translated when it becomes a scheduled Python job. Most helpers map to vectorised pandas operations that are faster and simpler than the formula. One pattern does not: a running value whose next step depends on the previous result in a non-linear way, which needs a genuine loop. This page shows which is which. It belongs to ",[38,39,41],"a",{"href":40},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002F","Excel Formula Equivalents in pandas",".",[44,45,47],"h2",{"id":46},"prerequisites","Prerequisites",[49,50,55],"pre",{"className":51,"code":52,"language":53,"meta":54,"style":54},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[13,56,57],{"__ignoreMap":54},[58,59,62,66,70,73],"span",{"class":60,"line":61},"line",1,[58,63,65],{"class":64},"sMTad","pip",[58,67,69],{"class":68},"srMev"," install",[58,71,72],{"class":68}," pandas",[58,74,75],{"class":68}," openpyxl\n",[10,77,78,81,82,85],{},[13,79,80],{},"stock_moves.xlsx"," lists daily stock movements for one product: receipts are positive, sales negative. The LAMBDA helpers are Excel 365 functions; LibreOffice does not evaluate them (version 26.2 returned ",[13,83,84],{},"#NAME?","), so the Excel behaviour described follows Microsoft's documentation, and each Python translation is checked against an independent calculation instead.",[44,87,89],{"id":88},"step-1-scan-that-is-really-a-cumulative-sum","Step 1 — SCAN that is really a cumulative sum",[10,91,92,95,96,99],{},[13,93,94],{},"=SCAN(0, C2:C9, LAMBDA(acc, x, acc + x))"," returns the running total after each movement. That is ",[13,97,98],{},"cumsum",":",[49,101,105],{"className":102,"code":103,"language":104,"meta":54,"style":54},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nmoves = pd.read_excel(\"stock_moves.xlsx\", parse_dates=[\"Date\"])\nmoves[\"Balance\"] = moves[\"Qty\"].cumsum()\nprint(moves.to_string(index=False))\n","python",[13,106,107,123,130,163,186],{"__ignoreMap":54},[58,108,109,113,117,120],{"class":60,"line":61},[58,110,112],{"class":111},"s-kum","import",[58,114,116],{"class":115},"skGVy"," pandas ",[58,118,119],{"class":111},"as",[58,121,122],{"class":115}," pd\n",[58,124,126],{"class":60,"line":125},2,[58,127,129],{"emptyLinePlaceholder":128},true,"\n",[58,131,133,136,139,142,145,148,152,154,157,160],{"class":60,"line":132},3,[58,134,135],{"class":115},"moves ",[58,137,138],{"class":111},"=",[58,140,141],{"class":115}," pd.read_excel(",[58,143,144],{"class":68},"\"stock_moves.xlsx\"",[58,146,147],{"class":115},", ",[58,149,151],{"class":150},"sa561","parse_dates",[58,153,138],{"class":111},[58,155,156],{"class":115},"[",[58,158,159],{"class":68},"\"Date\"",[58,161,162],{"class":115},"])\n",[58,164,166,169,172,175,177,180,183],{"class":60,"line":165},4,[58,167,168],{"class":115},"moves[",[58,170,171],{"class":68},"\"Balance\"",[58,173,174],{"class":115},"] ",[58,176,138],{"class":111},[58,178,179],{"class":115}," moves[",[58,181,182],{"class":68},"\"Qty\"",[58,184,185],{"class":115},"].cumsum()\n",[58,187,189,193,196,199,201,204],{"class":60,"line":188},5,[58,190,192],{"class":191},"sP0c6","print",[58,194,195],{"class":115},"(moves.to_string(",[58,197,198],{"class":150},"index",[58,200,138],{"class":111},[58,202,203],{"class":191},"False",[58,205,206],{"class":115},"))\n",[10,208,209,210,212,213,215,216,219,220,32,223,226,227,229,230,233],{},"Most ",[13,211,19],{}," formulas in practice are one of the cumulative operations pandas already has: ",[13,214,98],{}," for running totals, ",[13,217,218],{},"cumprod"," for compounding, ",[13,221,222],{},"cummax",[13,224,225],{},"cummin"," for running extremes. The initial value of ",[13,228,19],{}," corresponds to adding a constant (",[13,231,232],{},"opening + moves[\"Qty\"].cumsum()","). These are vectorised, so they run in compiled code and handle millions of rows instantly. The running balance goes negative on 4 October and again on 6 October: the sheet records more sales than stock, which is where the next step comes in.",[44,235,237],{"id":236},"step-2-scan-with-state-a-balance-that-cannot-go-negative","Step 2 — SCAN with state: a balance that cannot go negative",[10,239,240,241,244,245,249,250,253,254,257,258,99],{},"Suppose sales beyond the stock on hand become back-orders and the balance should stop at zero: ",[13,242,243],{},"=SCAN(0, C2:C9, LAMBDA(acc, x, MAX(0, acc + x)))",". Each step now depends on the ",[246,247,248],"em",{},"clipped"," previous result, so ",[13,251,252],{},"cumsum().clip(lower=0)"," is wrong — clipping after the fact does not carry the zero forward. The faithful equivalent is ",[13,255,256],{},"itertools.accumulate",", the standard library's ",[13,259,19],{},[49,261,263],{"className":102,"code":262,"language":104,"meta":54,"style":54},"from itertools import accumulate\n\nfloored = list(accumulate(moves[\"Qty\"], lambda acc, x: max(0, acc + x), initial=0))[1:]\nmoves[\"Balance (floored)\"] = floored\nmoves[\"Naive clip\"] = moves[\"Qty\"].cumsum().clip(lower=0)\nprint(moves[[\"Date\", \"Qty\", \"Balance\", \"Balance (floored)\", \"Naive clip\"]].to_string(index=False))\n",[13,264,265,278,282,340,354,382],{"__ignoreMap":54},[58,266,267,270,273,275],{"class":60,"line":61},[58,268,269],{"class":111},"from",[58,271,272],{"class":115}," itertools ",[58,274,112],{"class":111},[58,276,277],{"class":115}," accumulate\n",[58,279,280],{"class":60,"line":125},[58,281,129],{"emptyLinePlaceholder":128},[58,283,284,287,289,292,295,297,300,303,306,309,312,315,318,321,324,327,329,331,334,337],{"class":60,"line":132},[58,285,286],{"class":115},"floored ",[58,288,138],{"class":111},[58,290,291],{"class":191}," list",[58,293,294],{"class":115},"(accumulate(moves[",[58,296,182],{"class":68},[58,298,299],{"class":115},"], ",[58,301,302],{"class":111},"lambda",[58,304,305],{"class":115}," acc, x: ",[58,307,308],{"class":191},"max",[58,310,311],{"class":115},"(",[58,313,314],{"class":191},"0",[58,316,317],{"class":115},", acc ",[58,319,320],{"class":111},"+",[58,322,323],{"class":115}," x), ",[58,325,326],{"class":150},"initial",[58,328,138],{"class":111},[58,330,314],{"class":191},[58,332,333],{"class":115},"))[",[58,335,336],{"class":191},"1",[58,338,339],{"class":115},":]\n",[58,341,342,344,347,349,351],{"class":60,"line":165},[58,343,168],{"class":115},[58,345,346],{"class":68},"\"Balance (floored)\"",[58,348,174],{"class":115},[58,350,138],{"class":111},[58,352,353],{"class":115}," floored\n",[58,355,356,358,361,363,365,367,369,372,375,377,379],{"class":60,"line":188},[58,357,168],{"class":115},[58,359,360],{"class":68},"\"Naive clip\"",[58,362,174],{"class":115},[58,364,138],{"class":111},[58,366,179],{"class":115},[58,368,182],{"class":68},[58,370,371],{"class":115},"].cumsum().clip(",[58,373,374],{"class":150},"lower",[58,376,138],{"class":111},[58,378,314],{"class":191},[58,380,381],{"class":115},")\n",[58,383,385,387,390,392,394,396,398,400,402,404,406,408,411,413,415,417],{"class":60,"line":384},6,[58,386,192],{"class":191},[58,388,389],{"class":115},"(moves[[",[58,391,159],{"class":68},[58,393,147],{"class":115},[58,395,182],{"class":68},[58,397,147],{"class":115},[58,399,171],{"class":68},[58,401,147],{"class":115},[58,403,346],{"class":68},[58,405,147],{"class":115},[58,407,360],{"class":68},[58,409,410],{"class":115},"]].to_string(",[58,412,198],{"class":150},[58,414,138],{"class":111},[58,416,203],{"class":191},[58,418,206],{"class":115},[10,420,421,422,425,426,428,429,431,432,435,436,438],{},"On 5 October the floored balance is 50 — the receipt arrives into an empty store — while clipping the plain cumulative sum shows 25, because it still remembers the 25 units sold that were never there. From then on the two disagree on every row. ",[13,423,424],{},"accumulate","'s ",[13,427,326],{}," argument plays the role of ",[13,430,19],{},"'s first argument and adds one extra element at the start, which ",[13,433,434],{},"[1:]"," drops so that the result has one value per row, as ",[13,437,19],{}," does.",[440,441,449,450,449,454,449,458,449,465,449,474,449,481,449,486,449,490,449,497,449,502,449,506,449,510,449,513,449,517,449,520,449,524,449,527,449,533,449,537,449,541,449,545,449,549,449,553,449,557,449,561,449,567,449,572,449,577,449,581,449,585,449,588,449,592,449,597,449,600,449,603,449,606,449,609,449,614,449,617,449,622],"svg",{"viewBox":442,"role":443,"ariaLabelledBy":444,"xmlns":447,"style":448},"4 0 748 280","img",[445,446],"sc-balance-t","sc-balance-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:748px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[451,452,453],"title",{"id":445},"Running stock balance with and without a floor at zero",[455,456,457],"desc",{"id":446},"Daily stock balance for eight movements. The plain running sum dips to minus 25 on 4 October and minus 65 on 6 October. The floored balance stops at zero and then rises to 50, 0, 100 and 70, because receipts arrive into an empty store.",[459,460],"rect",{"x":461,"y":314,"width":462,"height":463,"fill":464},"4","748","280","#ffffff",[459,466],{"x":467,"y":468,"width":469,"height":469,"rx":470,"fill":471,"stroke":472,"style":473},"64.0","18.0","14.0","3","#d9f4f1","var(--teal,#0f9488)","stroke-width:1.5px",[475,476,480],"text",{"x":477,"y":478,"style":479},"84.0","29.5","font-size:12px;font-weight:600;fill:var(--text,#172033);text-anchor:start","floored (accumulate)",[459,482],{"x":483,"y":468,"width":469,"height":469,"rx":470,"fill":484,"stroke":485,"style":473},"241.1","#fdefd8","var(--gold,#b4740a)",[475,487,489],{"x":488,"y":478,"style":479},"261.1","naive clip of cumsum",[60,491],{"x1":492,"y1":493,"x2":494,"y2":493,"stroke":495,"style":496},"51.3","242.0","736.0","var(--line,#cdd5e6)","stroke-width:1px",[475,498,314],{"x":499,"y":500,"style":501},"29.6","246.0","font-size:11px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle",[60,503],{"x1":492,"y1":504,"x2":494,"y2":504,"stroke":495,"style":505},"178.7","stroke-width:1px;stroke-dasharray:5 4",[475,507,509],{"x":499,"y":508,"style":501},"182.7","50",[60,511],{"x1":492,"y1":512,"x2":494,"y2":512,"stroke":495,"style":505},"115.3",[475,514,516],{"x":499,"y":515,"style":501},"119.3","100",[60,518],{"x1":492,"y1":519,"x2":494,"y2":519,"stroke":495,"style":505},"52.0",[475,521,523],{"x":499,"y":522,"style":501},"56.0","150",[60,525],{"x1":492,"y1":493,"x2":494,"y2":493,"stroke":526,"style":473},"var(--muted,#5b6780)",[475,528,532],{"x":529,"y":530,"style":531},"94.1","260.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","1 Oct",[475,534,536],{"x":535,"y":530,"style":531},"179.7","2 Oct",[475,538,540],{"x":539,"y":530,"style":531},"265.3","3 Oct",[475,542,544],{"x":543,"y":530,"style":531},"350.8","4 Oct",[475,546,548],{"x":547,"y":530,"style":531},"436.4","5 Oct",[475,550,552],{"x":551,"y":530,"style":531},"522.0","6 Oct",[475,554,556],{"x":555,"y":530,"style":531},"607.6","7 Oct",[475,558,560],{"x":559,"y":530,"style":531},"693.2","8 Oct",[459,562],{"x":563,"y":564,"width":565,"height":566,"rx":470,"fill":471,"stroke":472,"style":473},"65.1","90.0","28.0","152.0",[459,568],{"x":569,"y":570,"width":565,"height":571,"rx":470,"fill":471,"stroke":472,"style":473},"150.7","147.0","95.0",[459,573],{"x":574,"y":575,"width":565,"height":576,"rx":470,"fill":471,"stroke":472,"style":473},"236.3","223.0","19.0",[459,578],{"x":579,"y":493,"width":565,"height":580,"rx":470,"fill":471,"stroke":472,"style":473},"321.9","0.0",[459,582],{"x":583,"y":504,"width":565,"height":584,"rx":470,"fill":471,"stroke":472,"style":473},"407.5","63.3",[459,586],{"x":587,"y":493,"width":565,"height":580,"rx":470,"fill":471,"stroke":472,"style":473},"493.1",[459,589],{"x":590,"y":512,"width":565,"height":591,"rx":470,"fill":471,"stroke":472,"style":473},"578.7","126.7",[459,593],{"x":594,"y":595,"width":565,"height":596,"rx":470,"fill":471,"stroke":472,"style":473},"664.2","153.3","88.7",[459,598],{"x":599,"y":564,"width":565,"height":566,"rx":470,"fill":484,"stroke":485,"style":473},"95.1",[459,601],{"x":602,"y":570,"width":565,"height":571,"rx":470,"fill":484,"stroke":485,"style":473},"180.7",[459,604],{"x":605,"y":575,"width":565,"height":576,"rx":470,"fill":484,"stroke":485,"style":473},"266.3",[459,607],{"x":608,"y":493,"width":565,"height":580,"rx":470,"fill":484,"stroke":485,"style":473},"351.8",[459,610],{"x":611,"y":612,"width":565,"height":613,"rx":470,"fill":484,"stroke":485,"style":473},"437.4","210.3","31.7",[459,615],{"x":616,"y":493,"width":565,"height":580,"rx":470,"fill":484,"stroke":485,"style":473},"523.0",[459,618],{"x":619,"y":620,"width":565,"height":621,"rx":470,"fill":484,"stroke":485,"style":473},"608.6","197.7","44.3",[459,623],{"x":624,"y":625,"width":565,"height":626,"rx":470,"fill":484,"stroke":485,"style":473},"694.2","235.7","6.3",[44,628,630],{"id":629},"step-3-reduce-is-the-last-value-of-scan","Step 3 — REDUCE is the last value of SCAN",[10,632,633,635,636,638,639,642],{},[13,634,23],{}," takes the same arguments as ",[13,637,19],{}," and returns only the final accumulated value. ",[13,640,641],{},"functools.reduce"," is the direct equivalent, and for plain sums an aggregation is simpler still:",[49,644,646],{"className":102,"code":645,"language":104,"meta":54,"style":54},"from functools import reduce\n\nclosing = reduce(lambda acc, x: max(0, acc + x), moves[\"Qty\"], 0)\nprint(\"closing stock:\", closing, \"| plain total:\", moves[\"Qty\"].sum())\nassert closing == moves[\"Balance (floored)\"].iloc[-1]\n",[13,647,648,660,664,701,724],{"__ignoreMap":54},[58,649,650,652,655,657],{"class":60,"line":61},[58,651,269],{"class":111},[58,653,654],{"class":115}," functools ",[58,656,112],{"class":111},[58,658,659],{"class":150}," reduce\n",[58,661,662],{"class":60,"line":125},[58,663,129],{"emptyLinePlaceholder":128},[58,665,666,669,671,674,676,678,680,682,684,686,688,690,693,695,697,699],{"class":60,"line":132},[58,667,668],{"class":115},"closing ",[58,670,138],{"class":111},[58,672,673],{"class":150}," reduce",[58,675,311],{"class":115},[58,677,302],{"class":111},[58,679,305],{"class":115},[58,681,308],{"class":191},[58,683,311],{"class":115},[58,685,314],{"class":191},[58,687,317],{"class":115},[58,689,320],{"class":111},[58,691,692],{"class":115}," x), moves[",[58,694,182],{"class":68},[58,696,299],{"class":115},[58,698,314],{"class":191},[58,700,381],{"class":115},[58,702,703,705,707,710,713,716,719,721],{"class":60,"line":165},[58,704,192],{"class":191},[58,706,311],{"class":115},[58,708,709],{"class":68},"\"closing stock:\"",[58,711,712],{"class":115},", closing, ",[58,714,715],{"class":68},"\"| plain total:\"",[58,717,718],{"class":115},", moves[",[58,720,182],{"class":68},[58,722,723],{"class":115},"].sum())\n",[58,725,726,729,732,735,737,739,742,745,747],{"class":60,"line":188},[58,727,728],{"class":111},"assert",[58,730,731],{"class":115}," closing ",[58,733,734],{"class":111},"==",[58,736,179],{"class":115},[58,738,346],{"class":68},[58,740,741],{"class":115},"].iloc[",[58,743,744],{"class":111},"-",[58,746,336],{"class":191},[58,748,749],{"class":115},"]\n",[10,751,752,753,755,756,758],{},"The closing stock of 70 equals the last value of the floored ",[13,754,19],{}," — a cheap consistency check worth keeping in tests whenever both appear. ",[13,757,23],{}," formulas in spreadsheets often build up something richer than a number, such as a running text or an array of results; in Python, return a tuple or a small object from the lambda, or — usually clearer — write a plain loop with named variables:",[49,760,762],{"className":102,"code":761,"language":104,"meta":54,"style":54},"stock, short = 0, 0\nfor qty in moves[\"Qty\"]:\n    stock += qty\n    if stock \u003C 0:\n        short += -stock\n        stock = 0\nprint(f\"closing stock {stock}, units short (back-ordered) {short}\")\n",[13,763,764,779,797,808,824,837,847],{"__ignoreMap":54},[58,765,766,769,771,774,776],{"class":60,"line":61},[58,767,768],{"class":115},"stock, short ",[58,770,138],{"class":111},[58,772,773],{"class":191}," 0",[58,775,147],{"class":115},[58,777,778],{"class":191},"0\n",[58,780,781,784,787,790,792,794],{"class":60,"line":125},[58,782,783],{"class":111},"for",[58,785,786],{"class":115}," qty ",[58,788,789],{"class":111},"in",[58,791,179],{"class":115},[58,793,182],{"class":68},[58,795,796],{"class":115},"]:\n",[58,798,799,802,805],{"class":60,"line":132},[58,800,801],{"class":115},"    stock ",[58,803,804],{"class":111},"+=",[58,806,807],{"class":115}," qty\n",[58,809,810,813,816,819,821],{"class":60,"line":165},[58,811,812],{"class":111},"    if",[58,814,815],{"class":115}," stock ",[58,817,818],{"class":111},"\u003C",[58,820,773],{"class":191},[58,822,823],{"class":115},":\n",[58,825,826,829,831,834],{"class":60,"line":188},[58,827,828],{"class":115},"        short ",[58,830,804],{"class":111},[58,832,833],{"class":111}," -",[58,835,836],{"class":115},"stock\n",[58,838,839,842,844],{"class":60,"line":384},[58,840,841],{"class":115},"        stock ",[58,843,138],{"class":111},[58,845,846],{"class":191}," 0\n",[58,848,850,852,854,857,860,864,867,870,873,875,878,880,883],{"class":60,"line":849},7,[58,851,192],{"class":191},[58,853,311],{"class":115},[58,855,856],{"class":111},"f",[58,858,859],{"class":68},"\"closing stock ",[58,861,863],{"class":862},"sSjpA","{",[58,865,866],{"class":115},"stock",[58,868,869],{"class":862},"}",[58,871,872],{"class":68},", units short (back-ordered) ",[58,874,863],{"class":862},[58,876,877],{"class":115},"short",[58,879,869],{"class":862},[58,881,882],{"class":68},"\"",[58,884,381],{"class":115},[10,886,887],{},"The loop reports both results at once: 70 units in stock and 65 units that could not be supplied. When the state has more than one part, a loop with names is easier to read and review than a nested lambda in either language.",[44,889,891],{"id":890},"step-4-map-is-an-element-wise-expression","Step 4 — MAP is an element-wise expression",[10,893,894,897],{},[13,895,896],{},"=MAP(C2:C9, LAMBDA(x, IF(x \u003C 0, -x, 0)))"," returns the units sold per row (sales as positive numbers, receipts as zero). Element-wise calculations are what pandas does by default:",[49,899,901],{"className":102,"code":900,"language":104,"meta":54,"style":54},"moves[\"Sold\"] = (-moves[\"Qty\"]).clip(lower=0)\nmoves[\"Received\"] = moves[\"Qty\"].clip(lower=0)\nmoves[\"Kind\"] = moves[\"Qty\"].map(lambda x: \"in\" if x > 0 else \"out\")\nprint(moves[[\"Qty\", \"Sold\", \"Received\", \"Kind\"]].to_string(index=False))\n",[13,902,903,934,960,1005],{"__ignoreMap":54},[58,904,905,907,910,912,914,917,919,921,923,926,928,930,932],{"class":60,"line":61},[58,906,168],{"class":115},[58,908,909],{"class":68},"\"Sold\"",[58,911,174],{"class":115},[58,913,138],{"class":111},[58,915,916],{"class":115}," (",[58,918,744],{"class":111},[58,920,168],{"class":115},[58,922,182],{"class":68},[58,924,925],{"class":115},"]).clip(",[58,927,374],{"class":150},[58,929,138],{"class":111},[58,931,314],{"class":191},[58,933,381],{"class":115},[58,935,936,938,941,943,945,947,949,952,954,956,958],{"class":60,"line":125},[58,937,168],{"class":115},[58,939,940],{"class":68},"\"Received\"",[58,942,174],{"class":115},[58,944,138],{"class":111},[58,946,179],{"class":115},[58,948,182],{"class":68},[58,950,951],{"class":115},"].clip(",[58,953,374],{"class":150},[58,955,138],{"class":111},[58,957,314],{"class":191},[58,959,381],{"class":115},[58,961,962,964,967,969,971,973,975,978,980,983,986,989,992,995,997,1000,1003],{"class":60,"line":132},[58,963,168],{"class":115},[58,965,966],{"class":68},"\"Kind\"",[58,968,174],{"class":115},[58,970,138],{"class":111},[58,972,179],{"class":115},[58,974,182],{"class":68},[58,976,977],{"class":115},"].map(",[58,979,302],{"class":111},[58,981,982],{"class":115}," x: ",[58,984,985],{"class":68},"\"in\"",[58,987,988],{"class":111}," if",[58,990,991],{"class":115}," x ",[58,993,994],{"class":111},">",[58,996,773],{"class":191},[58,998,999],{"class":111}," else",[58,1001,1002],{"class":68}," \"out\"",[58,1004,381],{"class":115},[58,1006,1007,1009,1011,1013,1015,1017,1019,1021,1023,1025,1027,1029,1031,1033],{"class":60,"line":165},[58,1008,192],{"class":191},[58,1010,389],{"class":115},[58,1012,182],{"class":68},[58,1014,147],{"class":115},[58,1016,909],{"class":68},[58,1018,147],{"class":115},[58,1020,940],{"class":68},[58,1022,147],{"class":115},[58,1024,966],{"class":68},[58,1026,410],{"class":115},[58,1028,198],{"class":150},[58,1030,138],{"class":111},[58,1032,203],{"class":191},[58,1034,206],{"class":115},[10,1036,1037,1038,1041,1042,1045,1046,1049,1050,1053,1054,1056,1057,1060,1061,42],{},"Prefer the vectorised form (",[13,1039,1040],{},"clip",", arithmetic, ",[13,1043,1044],{},"np.where",") over ",[13,1047,1048],{},"Series.map"," with a lambda: both give the same result, but the vectorised form is many times faster on large columns. ",[13,1051,1052],{},"map"," with a function is still the right tool for logic that has no vectorised counterpart, such as calling a lookup or parsing function per value. ",[13,1055,27],{}," with several arrays — ",[13,1058,1059],{},"MAP(A2:A9, B2:B9, LAMBDA(a, b, …))"," — is simply an expression of two columns, such as ",[13,1062,1063],{},"df[\"A\"] * df[\"B\"]",[440,1065,449,1071,449,1074,449,1077,449,1081,449,1087,449,1094,449,1100,449,1103,449,1107,449,1111,449,1116,449,1118,449,1122,449,1124,449,1126,449,1128,449,1131,449,1133,449,1137,449,1139,449,1142,449,1145,449,1148,449,1150,449,1154,449,1156,449,1159,449,1162,449,1165,449,1167,449,1171,449,1173,449,1176,449,1179,449,1182,449,1184,449,1188,449,1190],{"viewBox":1066,"role":443,"ariaLabelledBy":1067,"xmlns":447,"style":1070},"4 0 752 242",[1068,1069],"sc-map-t","sc-map-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[451,1072,1073],{"id":1068},"LAMBDA helper functions and their Python equivalents",[455,1075,1076],{"id":1069},"SCAN maps to cumsum, cumprod, cummax or cummin for simple running values and to itertools.accumulate when each step depends on a clipped or otherwise non-linear previous result. REDUCE maps to functools.reduce or an aggregation. MAP maps to vectorised expressions or Series.map. BYROW and BYCOL map to aggregations along axis 1 and axis 0.",[459,1078],{"x":461,"y":314,"width":1079,"height":1080,"fill":464},"752","242",[459,1082],{"x":1083,"y":1084,"width":1085,"height":1086,"rx":314,"fill":464,"stroke":495,"style":496},"20.0","16.0","81.4","40.0",[459,1088],{"x":1089,"y":1084,"width":1090,"height":1086,"rx":314,"fill":1091,"stroke":1092,"style":1093},"101.4","319.3","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[475,1095,1099],{"x":1096,"y":1097,"style":1098},"261.0","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","simple case",[459,1101],{"x":1102,"y":1084,"width":1090,"height":1086,"rx":314,"fill":1091,"stroke":1092,"style":1093},"420.7",[475,1104,1106],{"x":1105,"y":1097,"style":1098},"580.3","general case",[459,1108],{"x":1083,"y":522,"width":1085,"height":1109,"rx":314,"fill":1110,"stroke":495,"style":496},"34.0","#e7ebef",[475,1112,19],{"x":1113,"y":1114,"style":1115},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start",[459,1117],{"x":1089,"y":522,"width":1090,"height":1109,"rx":314,"fill":464,"stroke":495,"style":496},[475,1119,1121],{"x":1096,"y":1120,"style":531},"77.0","cumsum \u002F cumprod \u002F cummax",[459,1123],{"x":1102,"y":522,"width":1090,"height":1109,"rx":314,"fill":464,"stroke":495,"style":496},[475,1125,256],{"x":1105,"y":1120,"style":531},[459,1127],{"x":1083,"y":564,"width":1085,"height":1109,"rx":314,"fill":1110,"stroke":495,"style":496},[475,1129,23],{"x":1113,"y":1130,"style":1115},"112.0",[459,1132],{"x":1089,"y":564,"width":1090,"height":1109,"rx":314,"fill":464,"stroke":495,"style":496},[475,1134,1136],{"x":1096,"y":1135,"style":531},"111.0","sum \u002F agg",[459,1138],{"x":1102,"y":564,"width":1090,"height":1109,"rx":314,"fill":464,"stroke":495,"style":496},[475,1140,1141],{"x":1105,"y":1135,"style":531},"functools.reduce or a loop",[459,1143],{"x":1083,"y":1144,"width":1085,"height":1109,"rx":314,"fill":1110,"stroke":495,"style":496},"124.0",[475,1146,27],{"x":1113,"y":1147,"style":1115},"146.0",[459,1149],{"x":1089,"y":1144,"width":1090,"height":1109,"rx":314,"fill":464,"stroke":495,"style":496},[475,1151,1153],{"x":1096,"y":1152,"style":531},"145.0","vectorised expression",[459,1155],{"x":1102,"y":1144,"width":1090,"height":1109,"rx":314,"fill":464,"stroke":495,"style":496},[475,1157,1158],{"x":1105,"y":1152,"style":531},"Series.map(func)",[459,1160],{"x":1083,"y":1161,"width":1085,"height":1109,"rx":314,"fill":1110,"stroke":495,"style":496},"158.0",[475,1163,31],{"x":1113,"y":1164,"style":1115},"180.0",[459,1166],{"x":1089,"y":1161,"width":1090,"height":1109,"rx":314,"fill":464,"stroke":495,"style":496},[475,1168,1170],{"x":1096,"y":1169,"style":531},"179.0","df.sum(axis=1) etc.",[459,1172],{"x":1102,"y":1161,"width":1090,"height":1109,"rx":314,"fill":464,"stroke":495,"style":496},[475,1174,1175],{"x":1105,"y":1169,"style":531},"df.apply(func, axis=1)",[459,1177],{"x":1083,"y":1178,"width":1085,"height":1109,"rx":314,"fill":1110,"stroke":495,"style":496},"192.0",[475,1180,35],{"x":1113,"y":1181,"style":1115},"214.0",[459,1183],{"x":1089,"y":1178,"width":1090,"height":1109,"rx":314,"fill":464,"stroke":495,"style":496},[475,1185,1187],{"x":1096,"y":1186,"style":531},"213.0","df.sum() \u002F df.agg",[459,1189],{"x":1102,"y":1178,"width":1090,"height":1109,"rx":314,"fill":464,"stroke":495,"style":496},[475,1191,1192],{"x":1105,"y":1186,"style":531},"df.apply(func)",[44,1194,1196],{"id":1195},"step-5-byrow-and-bycol","Step 5 — BYROW and BYCOL",[10,1198,1199,1202,1203,1205],{},[13,1200,1201],{},"BYROW(range, LAMBDA(row, MAX(row)))"," returns one value per row, ",[13,1204,35],{}," one per column. On a wide table — weekly sales per product, say — they are aggregations along an axis:",[49,1207,1209],{"className":102,"code":1208,"language":104,"meta":54,"style":54},"grid = pd.DataFrame({\"Mon\": [12, 7, 0], \"Tue\": [9, 11, 4], \"Wed\": [15, 6, 2]}, index=[\"Drill\", \"Saw\", \"Rake\"])\nprint(grid.assign(Peak=grid.max(axis=1), Total=grid.sum(axis=1)))       # BYROW\nprint(grid.agg([\"sum\", \"mean\"]))                                         # BYCOL\nprint(grid.apply(lambda row: (row > 5).sum(), axis=1).rename(\"Days over 5\"))\n",[13,1210,1211,1302,1348,1369],{"__ignoreMap":54},[58,1212,1213,1216,1218,1221,1224,1227,1230,1232,1235,1237,1239,1241,1244,1246,1249,1251,1254,1256,1258,1260,1263,1265,1268,1270,1273,1275,1278,1281,1283,1285,1287,1290,1292,1295,1297,1300],{"class":60,"line":61},[58,1214,1215],{"class":115},"grid ",[58,1217,138],{"class":111},[58,1219,1220],{"class":115}," pd.DataFrame({",[58,1222,1223],{"class":68},"\"Mon\"",[58,1225,1226],{"class":115},": [",[58,1228,1229],{"class":191},"12",[58,1231,147],{"class":115},[58,1233,1234],{"class":191},"7",[58,1236,147],{"class":115},[58,1238,314],{"class":191},[58,1240,299],{"class":115},[58,1242,1243],{"class":68},"\"Tue\"",[58,1245,1226],{"class":115},[58,1247,1248],{"class":191},"9",[58,1250,147],{"class":115},[58,1252,1253],{"class":191},"11",[58,1255,147],{"class":115},[58,1257,461],{"class":191},[58,1259,299],{"class":115},[58,1261,1262],{"class":68},"\"Wed\"",[58,1264,1226],{"class":115},[58,1266,1267],{"class":191},"15",[58,1269,147],{"class":115},[58,1271,1272],{"class":191},"6",[58,1274,147],{"class":115},[58,1276,1277],{"class":191},"2",[58,1279,1280],{"class":115},"]}, ",[58,1282,198],{"class":150},[58,1284,138],{"class":111},[58,1286,156],{"class":115},[58,1288,1289],{"class":68},"\"Drill\"",[58,1291,147],{"class":115},[58,1293,1294],{"class":68},"\"Saw\"",[58,1296,147],{"class":115},[58,1298,1299],{"class":68},"\"Rake\"",[58,1301,162],{"class":115},[58,1303,1304,1306,1309,1312,1314,1317,1320,1322,1324,1327,1330,1332,1335,1337,1339,1341,1344],{"class":60,"line":125},[58,1305,192],{"class":191},[58,1307,1308],{"class":115},"(grid.assign(",[58,1310,1311],{"class":150},"Peak",[58,1313,138],{"class":111},[58,1315,1316],{"class":115},"grid.max(",[58,1318,1319],{"class":150},"axis",[58,1321,138],{"class":111},[58,1323,336],{"class":191},[58,1325,1326],{"class":115},"), ",[58,1328,1329],{"class":150},"Total",[58,1331,138],{"class":111},[58,1333,1334],{"class":115},"grid.sum(",[58,1336,1319],{"class":150},[58,1338,138],{"class":111},[58,1340,336],{"class":191},[58,1342,1343],{"class":115},")))       ",[58,1345,1347],{"class":1346},"s-wDw","# BYROW\n",[58,1349,1350,1352,1355,1358,1360,1363,1366],{"class":60,"line":132},[58,1351,192],{"class":191},[58,1353,1354],{"class":115},"(grid.agg([",[58,1356,1357],{"class":68},"\"sum\"",[58,1359,147],{"class":115},[58,1361,1362],{"class":68},"\"mean\"",[58,1364,1365],{"class":115},"]))                                         ",[58,1367,1368],{"class":1346},"# BYCOL\n",[58,1370,1371,1373,1376,1378,1381,1383,1386,1389,1391,1393,1395,1398,1401],{"class":60,"line":165},[58,1372,192],{"class":191},[58,1374,1375],{"class":115},"(grid.apply(",[58,1377,302],{"class":111},[58,1379,1380],{"class":115}," row: (row ",[58,1382,994],{"class":111},[58,1384,1385],{"class":191}," 5",[58,1387,1388],{"class":115},").sum(), ",[58,1390,1319],{"class":150},[58,1392,138],{"class":111},[58,1394,336],{"class":191},[58,1396,1397],{"class":115},").rename(",[58,1399,1400],{"class":68},"\"Days over 5\"",[58,1402,206],{"class":115},[10,1404,1405,32,1408,1411,1412,1414,1415,1418,1419,1421,1422,1425,1426,42],{},[13,1406,1407],{},"max(axis=1)",[13,1409,1410],{},"sum(axis=1)"," are the vectorised ",[13,1413,31],{},"; ",[13,1416,1417],{},"agg"," with a list gives several per-column results at once, the ",[13,1420,35],{}," equivalent. ",[13,1423,1424],{},"apply(..., axis=1)"," handles arbitrary row logic but runs in Python once per row, so keep it for logic that genuinely needs it. A sheet laid out like this often arrives in long form first; reshaping between the two is covered in ",[38,1427,1429],{"href":1428},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Funpivot-a-wide-excel-sheet-with-pandas-melt\u002F","Unpivot a Wide Excel Sheet with pandas melt",[44,1431,1433],{"id":1432},"step-6-per-group-and-back-to-excel","Step 6 — Per group and back to Excel",[10,1435,1436,1437,1439],{},"Running balances in real data are per product or per account. ",[13,1438,19],{}," cannot reset per group without extra conditions; in pandas the group-by makes it explicit:",[49,1441,1443],{"className":102,"code":1442,"language":104,"meta":54,"style":54},"multi = pd.concat([moves.assign(Product=\"Hose\"), moves.assign(Product=\"Rake\", Qty=moves[\"Qty\"] \u002F\u002F 2)])\nmulti[\"Balance\"] = multi.groupby(\"Product\")[\"Qty\"].transform(\n    lambda s: pd.Series(list(accumulate(s, lambda a, x: max(0, a + x), initial=0))[1:], index=s.index))\nwith pd.ExcelWriter(\"stock_balances.xlsx\", engine=\"xlsxwriter\", datetime_format=\"dd mmm\") as xw:\n    multi[[\"Product\", \"Date\", \"Type\", \"Qty\", \"Balance\"]].to_excel(xw, index=False, sheet_name=\"Balances\")\nprint(multi.groupby(\"Product\")[\"Balance\"].last())\n",[13,1444,1445,1494,1519,1571,1610,1655],{"__ignoreMap":54},[58,1446,1447,1450,1452,1455,1458,1460,1463,1466,1468,1470,1472,1474,1477,1479,1481,1483,1485,1488,1491],{"class":60,"line":61},[58,1448,1449],{"class":115},"multi ",[58,1451,138],{"class":111},[58,1453,1454],{"class":115}," pd.concat([moves.assign(",[58,1456,1457],{"class":150},"Product",[58,1459,138],{"class":111},[58,1461,1462],{"class":68},"\"Hose\"",[58,1464,1465],{"class":115},"), moves.assign(",[58,1467,1457],{"class":150},[58,1469,138],{"class":111},[58,1471,1299],{"class":68},[58,1473,147],{"class":115},[58,1475,1476],{"class":150},"Qty",[58,1478,138],{"class":111},[58,1480,168],{"class":115},[58,1482,182],{"class":68},[58,1484,174],{"class":115},[58,1486,1487],{"class":111},"\u002F\u002F",[58,1489,1490],{"class":191}," 2",[58,1492,1493],{"class":115},")])\n",[58,1495,1496,1499,1501,1503,1505,1508,1511,1514,1516],{"class":60,"line":125},[58,1497,1498],{"class":115},"multi[",[58,1500,171],{"class":68},[58,1502,174],{"class":115},[58,1504,138],{"class":111},[58,1506,1507],{"class":115}," multi.groupby(",[58,1509,1510],{"class":68},"\"Product\"",[58,1512,1513],{"class":115},")[",[58,1515,182],{"class":68},[58,1517,1518],{"class":115},"].transform(\n",[58,1520,1521,1524,1527,1530,1533,1535,1538,1540,1542,1544,1547,1549,1551,1553,1555,1557,1559,1561,1564,1566,1568],{"class":60,"line":132},[58,1522,1523],{"class":111},"    lambda",[58,1525,1526],{"class":115}," s: pd.Series(",[58,1528,1529],{"class":191},"list",[58,1531,1532],{"class":115},"(accumulate(s, ",[58,1534,302],{"class":111},[58,1536,1537],{"class":115}," a, x: ",[58,1539,308],{"class":191},[58,1541,311],{"class":115},[58,1543,314],{"class":191},[58,1545,1546],{"class":115},", a ",[58,1548,320],{"class":111},[58,1550,323],{"class":115},[58,1552,326],{"class":150},[58,1554,138],{"class":111},[58,1556,314],{"class":191},[58,1558,333],{"class":115},[58,1560,336],{"class":191},[58,1562,1563],{"class":115},":], ",[58,1565,198],{"class":150},[58,1567,138],{"class":111},[58,1569,1570],{"class":115},"s.index))\n",[58,1572,1573,1576,1579,1582,1584,1587,1589,1592,1594,1597,1599,1602,1605,1607],{"class":60,"line":165},[58,1574,1575],{"class":111},"with",[58,1577,1578],{"class":115}," pd.ExcelWriter(",[58,1580,1581],{"class":68},"\"stock_balances.xlsx\"",[58,1583,147],{"class":115},[58,1585,1586],{"class":150},"engine",[58,1588,138],{"class":111},[58,1590,1591],{"class":68},"\"xlsxwriter\"",[58,1593,147],{"class":115},[58,1595,1596],{"class":150},"datetime_format",[58,1598,138],{"class":111},[58,1600,1601],{"class":68},"\"dd mmm\"",[58,1603,1604],{"class":115},") ",[58,1606,119],{"class":111},[58,1608,1609],{"class":115}," xw:\n",[58,1611,1612,1615,1617,1619,1621,1623,1626,1628,1630,1632,1634,1637,1639,1641,1643,1645,1648,1650,1653],{"class":60,"line":188},[58,1613,1614],{"class":115},"    multi[[",[58,1616,1510],{"class":68},[58,1618,147],{"class":115},[58,1620,159],{"class":68},[58,1622,147],{"class":115},[58,1624,1625],{"class":68},"\"Type\"",[58,1627,147],{"class":115},[58,1629,182],{"class":68},[58,1631,147],{"class":115},[58,1633,171],{"class":68},[58,1635,1636],{"class":115},"]].to_excel(xw, ",[58,1638,198],{"class":150},[58,1640,138],{"class":111},[58,1642,203],{"class":191},[58,1644,147],{"class":115},[58,1646,1647],{"class":150},"sheet_name",[58,1649,138],{"class":111},[58,1651,1652],{"class":68},"\"Balances\"",[58,1654,381],{"class":115},[58,1656,1657,1659,1662,1664,1666,1668],{"class":60,"line":384},[58,1658,192],{"class":191},[58,1660,1661],{"class":115},"(multi.groupby(",[58,1663,1510],{"class":68},[58,1665,1513],{"class":115},[58,1667,171],{"class":68},[58,1669,1670],{"class":115},"].last())\n",[10,1672,1673,1676,1677,1679,1680,42],{},[13,1674,1675],{},"transform"," runs the accumulation separately for each product and returns results aligned with the original rows, ready to write as a column. The output file holds values, so it opens correctly in every spreadsheet — unlike a workbook full of ",[13,1678,19],{}," formulas, which only calculates in Excel 365. Running totals in general are covered in ",[38,1681,1683],{"href":1682},"\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",[440,1685,449,1690,449,1693,449,1696,449,1699,449,1705,449,1716,449,1721,449,1725,449,1729,449,1732,449,1737,449,1744,449,1749,449,1754,449,1761,449,1764,449,1768,449,1770,449,1773,449,1777,449,1780,449,1782,449,1788,449,1790,449,1793,449,1795,449,1798,449,1802,449,1805,449,1808,449,1815,449,1819],{"viewBox":1686,"role":443,"ariaLabelledBy":1687,"xmlns":447,"style":1070},"4 0 752 272",[1688,1689],"sc-choose-t","sc-choose-d",[451,1691,1692],{"id":1688},"Choosing the Python form of a SCAN formula",[455,1694,1695],{"id":1689},"If the step is a plain sum, product, maximum or minimum, use the cumulative pandas method. If each step depends on a clipped or conditional previous result, use itertools.accumulate. If several values are carried, write a loop with named variables. Group-wise versions use groupby with transform.",[459,1697],{"x":461,"y":314,"width":1079,"height":1698,"fill":464},"272",[459,1700],{"x":1701,"y":1084,"width":1702,"height":1703,"rx":1229,"fill":1091,"stroke":1092,"style":1704},"179.4","401.1","75.5","stroke-width:2px",[1706,1707,1709],"g",{"transform":1708},"translate(368.0,29.0) scale(1.000)",[1710,1711],"path",{"d":1712,"fill":1713,"stroke":1714,"style":1715},"M20 11a8 8 0 1 0-2.3 5.7 M20 4v7h-7","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[475,1717,1720],{"x":1718,"y":1703,"style":1719},"380.0","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","What does each step do with the previous result?",[60,1722],{"x1":1718,"y1":1723,"x2":1718,"y2":1724,"stroke":1092,"style":1704},"91.5","111.5",[60,1726],{"x1":1727,"y1":1724,"x2":1728,"y2":1724,"stroke":1092,"style":1704},"134.7","625.3",[60,1730],{"x1":1727,"y1":1724,"x2":1727,"y2":1731,"stroke":1092,"style":1704},"157.5",[1733,1734],"polygon",{"points":1735,"fill":1736},"134.7,161.5 129.7,152.5 139.7,152.5","#5b5cf0",[459,1738],{"x":1739,"y":1740,"width":1741,"height":1742,"rx":1743,"fill":464,"stroke":495,"style":1093},"74.0","121.5","121.4","21.0","10",[475,1745,1748],{"x":1727,"y":1746,"style":1747},"136.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","plain + × max min",[459,1750],{"x":1083,"y":1751,"width":1752,"height":1753,"rx":1229,"fill":471,"stroke":472,"style":1704},"163.5","229.3","93.0",[1706,1755,1757],{"transform":1756},"translate(122.7,176.5) scale(1.000)",[1710,1758],{"d":1759,"fill":1713,"stroke":1760,"style":1715},"M13 2L4 14h7l-1 8 9-12h-7z","var(--teal-ink,#0b6157)",[475,1762,1121],{"x":1727,"y":575,"style":1763},"font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[475,1765,1767],{"x":1727,"y":1766,"style":531},"242.5","vectorised",[60,1769],{"x1":1718,"y1":1724,"x2":1718,"y2":1731,"stroke":1092,"style":1704},[1733,1771],{"points":1772,"fill":1736},"380.0,161.5 375.0,152.5 385.0,152.5",[459,1774],{"x":1775,"y":1740,"width":1776,"height":1742,"rx":1743,"fill":464,"stroke":495,"style":1093},"320.2","119.7",[475,1778,1779],{"x":1718,"y":1746,"style":1747},"clips or branches",[459,1781],{"x":539,"y":1751,"width":1752,"height":1753,"rx":1229,"fill":1091,"stroke":1092,"style":1704},[1706,1783,1785],{"transform":1784},"translate(368.0,176.5) scale(1.000)",[1710,1786],{"d":1787,"fill":1713,"stroke":1714,"style":1715},"M12 3c-4 0-4 1.6-4 3v2h4.2v1H6c-2 0-3 1.4-3 4s1 4 3 4h2v-2.6c0-1.6 1.3-2.9 2.9-2.9h4.2c1.3 0 2.4-1.1 2.4-2.4V6c0-1.6-1.8-3-5.5-3z M10 5.5h.01 M12 21c4 0 4-1.6 4-3v-2h-4.2v-1H18c2 0 3-1.4 3-4s-1-4-3-4h-2v2.6c0 1.6-1.3 2.9-2.9 2.9H8.9c-1.3 0-2.4 1.1-2.4 2.4V18c0 1.6 1.8 3 5.5 3z M14 18.5h.01",[475,1789,256],{"x":1718,"y":575,"style":1719},[475,1791,1792],{"x":1718,"y":1766,"style":531},"faithful to SCAN",[60,1794],{"x1":1728,"y1":1724,"x2":1728,"y2":1731,"stroke":1092,"style":1704},[1733,1796],{"points":1797,"fill":1736},"625.3,161.5 620.3,152.5 630.3,152.5",[459,1799],{"x":1800,"y":1740,"width":1801,"height":1742,"rx":1743,"fill":464,"stroke":495,"style":1093},"551.6","147.5",[475,1803,1804],{"x":1728,"y":1746,"style":1747},"carries several values",[459,1806],{"x":1807,"y":1751,"width":1752,"height":1753,"rx":1229,"fill":484,"stroke":485,"style":1704},"510.7",[1706,1809,1811],{"transform":1810},"translate(613.3,176.5) scale(1.000)",[1710,1812],{"d":1813,"fill":1713,"stroke":1814,"style":1715},"M9 8l-4 4 4 4 M15 8l4 4-4 4","var(--gold-ink,#7a4e06)",[475,1816,1818],{"x":1728,"y":575,"style":1817},"font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Plain loop",[475,1820,1821],{"x":1728,"y":1766,"style":531},"named state variables",[44,1823,1825],{"id":1824},"common-pitfalls","Common pitfalls",[1827,1828,1829,1843,1857,1871,1877],"ul",{},[1830,1831,1832,1836,1837,1840,1841,42],"li",{},[1833,1834,1835],"strong",{},"Clipping a cumulative sum."," ",[13,1838,1839],{},"cumsum().clip(0)"," is not a floored running balance; use ",[13,1842,424],{},[1830,1844,1845,1850,1851,1853,1854,1856],{},[1833,1846,1847,1848,42],{},"Forgetting ",[13,1849,326],{}," Without it, ",[13,1852,424],{}," starts from the first element, not from ",[13,1855,19],{},"'s initial value.",[1830,1858,1859,1865,1866,32,1868,1870],{},[1833,1860,1861,1864],{},[13,1862,1863],{},"apply"," everywhere."," Row-wise ",[13,1867,1863],{},[13,1869,1052],{}," with lambdas are slow; reach for vectorised methods first.",[1830,1872,1873,1876],{},[1833,1874,1875],{},"Ignoring groups."," A running balance across products is meaningless; group before accumulating.",[1830,1878,1879,1882,1883,1885],{},[1833,1880,1881],{},"Live LAMBDA formulas in shared files."," They show ",[13,1884,84],{}," outside Excel 365; write the results as values.",[44,1887,1889],{"id":1888},"conclusion","Conclusion",[10,1891,1892,1893,1895,1896,147,1898,147,1900,1902,1903,1414,1905,1907,1908,1910,1911,32,1913,1915,1916,1918,1919,1921,1922,1924,1925,1927],{},"Most LAMBDA helpers become one vectorised pandas call: ",[13,1894,19],{}," with a plain step is ",[13,1897,98],{},[13,1899,218],{},[13,1901,222],{}," or ",[13,1904,225],{},[13,1906,23],{}," is an aggregation; ",[13,1909,27],{}," is an element-wise expression; ",[13,1912,31],{},[13,1914,35],{}," are aggregations along an axis. The exception is a running value whose step depends non-linearly on the previous result — a balance floored at zero, a capped accrual — which only ",[13,1917,256],{}," (or a loop) reproduces faithfully. Group with ",[13,1920,1675],{}," for per-entity balances, check ",[13,1923,23],{}," against the last ",[13,1926,19],{}," value, and write the results as values so every spreadsheet can open them.",[44,1929,1931],{"id":1930},"frequently-asked-questions","Frequently asked questions",[10,1933,1934,1937],{},[1833,1935,1936],{},"What is the Python equivalent of Excel SCAN?","\nitertools.accumulate(values, func, initial=start), dropping the first element so there is one result per row. For plain running sums, products, maxima or minima, pandas' cumsum, cumprod, cummax and cummin are simpler and faster.",[10,1939,1940,1943],{},[1833,1941,1942],{},"Why doesn't cumsum().clip(lower=0) match my SCAN with MAX(0, ...)?","\nClipping after the cumulative sum does not feed the clipped value into the next step. The SCAN formula resets the balance to zero and continues from there, which requires an accumulation that applies the floor at each step, such as itertools.accumulate.",[10,1945,1946,1949],{},[1833,1947,1948],{},"How do I translate Excel REDUCE to Python?","\nUse functools.reduce(func, values, initial) for a single accumulated value, or an aggregation such as sum when the step is a plain addition. For several accumulated values, a loop with named variables is clearer.",[10,1951,1952,1955],{},[1833,1953,1954],{},"What replaces BYROW in pandas?","\nRow-wise aggregations such as df.max(axis=1) or df.sum(axis=1), or df.apply(func, axis=1) for custom logic that has no vectorised form.",[44,1957,1959],{"id":1958},"related","Related",[1827,1961,1962,1968,1975,1982],{},[1830,1963,1964,1965,1967],{},"Up one level: ",[38,1966,41],{"href":40}," — Excel functions in pandas.",[1830,1969,1970,1974],{},[38,1971,1973],{"href":1972},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fwrite-let-and-lambda-formulas-from-python\u002F","Write LET and LAMBDA Formulas from Python"," — when the workbook should keep the formulas.",[1830,1976,1977,1981],{},[38,1978,1980],{"href":1979},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fexcel-if-formulas-as-pandas-conditional-columns\u002F","Excel IF Formulas as pandas Conditional Columns"," — vectorised conditions for MAP-style logic.",[1830,1983,1984,1988],{},[38,1985,1987],{"href":1986},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fcalculate-fifo-inventory-valuation-with-python\u002F","Calculate FIFO Inventory Valuation with Python"," — a stateful inventory calculation in practice.",[1990,1991,1992],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":54,"searchDepth":125,"depth":125,"links":1994},[1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005],{"id":46,"depth":125,"text":47},{"id":88,"depth":125,"text":89},{"id":236,"depth":125,"text":237},{"id":629,"depth":125,"text":630},{"id":890,"depth":125,"text":891},{"id":1195,"depth":125,"text":1196},{"id":1432,"depth":125,"text":1433},{"id":1824,"depth":125,"text":1825},{"id":1888,"depth":125,"text":1889},{"id":1930,"depth":125,"text":1931},{"id":1958,"depth":125,"text":1959},"2026-10-02","Excel's LAMBDA helper functions — SCAN for running results, REDUCE for a single accumulated value, MAP for element-by-element calculations, BYROW and BYCOL for per-row and per-column results — translated to pandas and the Python standard library: when a vectorised cumsum, cumprod or clip does the job, when only an itertools.accumulate loop reproduces SCAN (a balance that cannot go below zero), and how to keep the logic testable.","md",[2010,2012,2014,2016],{"q":1936,"a":2011},"itertools.accumulate(values, func, initial=start), dropping the first element so there is one result per row. For plain running sums, products, maxima or minima, pandas' cumsum, cumprod, cummax and cummin are simpler and faster.",{"q":1942,"a":2013},"Clipping after the cumulative sum does not feed the clipped value into the next step. The SCAN formula resets the balance to zero and continues from there, which requires an accumulation that applies the floor at each step, such as itertools.accumulate.",{"q":1948,"a":2015},"Use functools.reduce(func, values, initial) for a single accumulated value, or an aggregation such as sum when the step is a plain addition. For several accumulated values, a loop with named variables is clearer.",{"q":1954,"a":2017},"Row-wise aggregations such as df.max(axis=1) or df.sum(axis=1), or df.apply(func, axis=1) for custom logic that has no vectorised form.","scan reduce and map equivalents in pandas",null,{"breadcrumb":2021},[2022,2025,2028],{"name":2023,"item":2024},"Home","\u002F",{"name":2026,"item":2027},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":41,"item":40},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fscan-reduce-and-map-equivalents-in-pandas",{"title":2031,"description":2032},"Excel SCAN, REDUCE, MAP and BYROW Equivalents in pandas","Python versions of Excel LAMBDA helpers: SCAN as cumsum\u002Fcumprod or itertools.accumulate for stateful running balances, REDUCE as functools.reduce or agg, MAP as vectorised expressions, BYROW\u002FBYCOL as axis aggregations, with tested examples.","scan-reduce-and-map-equivalents-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fscan-reduce-and-map-equivalents-in-pandas\u002Findex",[2036,2037],"formula-equivalents","pandas","how-to","6Wf3f_xak-XMJYE-Ww7JYuDnSnCSlN9vOc41-PQLJ6w",[2041,2045],{"title":2042,"path":2043,"stem":2044,"children":-1},"Running Totals and Year-Over-Year Growth in pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frunning-totals-and-year-over-year-growth-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frunning-totals-and-year-over-year-growth-in-pandas\u002Findex",{"title":2046,"path":2047,"stem":2048,"children":-1},"SEQUENCE and RANDARRAY Equivalents in Python","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsequence-and-randarray-equivalents-in-python","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsequence-and-randarray-equivalents-in-python\u002Findex",[2050,2052],{"path":2051,"title":41},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas",{"path":2053,"title":2026},"\u002Fadvanced-data-transformation-and-cleaning",[],[2056,2059,2062,2065,2068,2071],{"path":2057,"title":2058,"kind":2038},"\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":2060,"title":2061,"kind":2038},"\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":2063,"title":2064,"kind":2038},"\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":2066,"title":2067,"kind":2038},"\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":2069,"title":2070,"kind":2038},"\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":2072,"title":2073,"kind":2038},"\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",1790972088558]