[{"data":1,"prerenderedAt":2667},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fhandle-circular-references-and-iterative-calculation-in-python":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fhandle-circular-references-and-iterative-calculation-in-python":2634,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fhandle-circular-references-and-iterative-calculation-in-python":2642,"children:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fhandle-circular-references-and-iterative-calculation-in-python":2647,"related:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fhandle-circular-references-and-iterative-calculation-in-python":2648},{"id":4,"title":5,"body":6,"dateModified":2602,"datePublished":2602,"description":2603,"extension":2604,"faq":2605,"keyword":2614,"kind":2615,"meta":2616,"navigation":105,"path":2624,"seo":2625,"slug":2628,"stem":2629,"topics":2630,"type":2632,"__hash__":2633},"docs\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fhandle-circular-references-and-iterative-calculation-in-python\u002Findex.md","Handle Circular References and Iterative Calculation in Python",{"type":7,"value":8,"toc":2589},"minimark",[9,32,37,66,73,77,487,490,636,640,844,859,863,916,938,1054,1058,1061,1646,1661,1665,1668,2014,2020,2170,2176,2283,2287,2290,2464,2470,2474,2509,2513,2524,2528,2534,2540,2546,2552,2556,2585],[10,11,12,13,17,18,21,22,25,26,31],"p",{},"Most circular references in spreadsheets are mistakes, and Excel warns about them for good reason. Some are intentional. The classic one in finance: interest earned depends on the average cash balance, but the closing balance includes the interest. Another: a bonus pool of 10% of profit ",[14,15,16],"em",{},"after"," the bonus. Modellers make these work by switching on ",[14,19,20],{},"iterative calculation",", which tells the spreadsheet to recalculate the loop repeatedly until the numbers stop changing. A Python script that generates or edits such a model has to set that switch in the file — otherwise every cell in the loop shows an error — and a script that only ",[14,23,24],{},"reads"," the numbers is often better off solving the loop itself. This page covers detection, the workbook setting and the Python solution. It belongs to ",[27,28,30],"a",{"href":29},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002F","Working with Excel Formulas in Python",".",[33,34,36],"h2",{"id":35},"prerequisites","Prerequisites",[38,39,44],"pre",{"className":40,"code":41,"language":42,"meta":43,"style":43},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pandas\n","bash","",[45,46,47],"code",{"__ignoreMap":43},[48,49,52,56,60,63],"span",{"class":50,"line":51},"line",1,[48,53,55],{"class":54},"sMTad","pip",[48,57,59],{"class":58},"srMev"," install",[48,61,62],{"class":58}," openpyxl",[48,64,65],{"class":58}," pandas\n",[10,67,68,69,72],{},"LibreOffice (",[45,70,71],{},"soffice",") is used to recalculate the generated workbooks and show what a spreadsheet application does with them.",[33,74,76],{"id":75},"step-1-build-a-model-with-a-deliberate-loop","Step 1 — Build a model with a deliberate loop",[38,78,82],{"className":79,"code":80,"language":81,"meta":43,"style":43},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import Workbook\n\ndef cash_model(path, iterate):\n    wb = Workbook()\n    ws = wb.active\n    ws.title = \"Cash\"\n    ws.append([\"\", \"Q1\", \"Q2\", \"Q3\", \"Q4\"])\n    ws.append([\"Opening balance\", 100000, \"=B5\", \"=C5\", \"=D5\"])\n    ws.append([\"Net cash flow\", 25000, -40000, 15000, 30000])\n    ws.append([\"Interest (4% p.a. on average balance)\"] + [f\"=0.04\u002F4*({c}2+{c}5)\u002F2\" for c in \"BCDE\"])\n    ws.append([\"Closing balance\"] + [f\"={c}2+{c}3+{c}4\" for c in \"BCDE\"])\n    ws[\"G1\"], ws[\"G2\"] = \"Rate\", 0.04\n    if iterate:\n        wb.calculation.iterate = True\n        wb.calculation.iterateCount = 100\n        wb.calculation.iterateDelta = 0.0001\n    wb.save(path)\n\ncash_model(\"cash_no_iter.xlsx\", iterate=False)\ncash_model(\"cash_iter.xlsx\", iterate=True)\n","python",[45,83,84,100,107,120,132,143,154,187,218,251,310,365,392,401,412,423,434,440,445,468],{"__ignoreMap":43},[48,85,86,90,94,97],{"class":50,"line":51},[48,87,89],{"class":88},"s-kum","from",[48,91,93],{"class":92},"skGVy"," openpyxl ",[48,95,96],{"class":88},"import",[48,98,99],{"class":92}," Workbook\n",[48,101,103],{"class":50,"line":102},2,[48,104,106],{"emptyLinePlaceholder":105},true,"\n",[48,108,110,113,117],{"class":50,"line":109},3,[48,111,112],{"class":88},"def",[48,114,116],{"class":115},"s_Opv"," cash_model",[48,118,119],{"class":92},"(path, iterate):\n",[48,121,123,126,129],{"class":50,"line":122},4,[48,124,125],{"class":92},"    wb ",[48,127,128],{"class":88},"=",[48,130,131],{"class":92}," Workbook()\n",[48,133,135,138,140],{"class":50,"line":134},5,[48,136,137],{"class":92},"    ws ",[48,139,128],{"class":88},[48,141,142],{"class":92}," wb.active\n",[48,144,146,149,151],{"class":50,"line":145},6,[48,147,148],{"class":92},"    ws.title ",[48,150,128],{"class":88},[48,152,153],{"class":58}," \"Cash\"\n",[48,155,157,160,163,166,169,171,174,176,179,181,184],{"class":50,"line":156},7,[48,158,159],{"class":92},"    ws.append([",[48,161,162],{"class":58},"\"\"",[48,164,165],{"class":92},", ",[48,167,168],{"class":58},"\"Q1\"",[48,170,165],{"class":92},[48,172,173],{"class":58},"\"Q2\"",[48,175,165],{"class":92},[48,177,178],{"class":58},"\"Q3\"",[48,180,165],{"class":92},[48,182,183],{"class":58},"\"Q4\"",[48,185,186],{"class":92},"])\n",[48,188,190,192,195,197,201,203,206,208,211,213,216],{"class":50,"line":189},8,[48,191,159],{"class":92},[48,193,194],{"class":58},"\"Opening balance\"",[48,196,165],{"class":92},[48,198,200],{"class":199},"sP0c6","100000",[48,202,165],{"class":92},[48,204,205],{"class":58},"\"=B5\"",[48,207,165],{"class":92},[48,209,210],{"class":58},"\"=C5\"",[48,212,165],{"class":92},[48,214,215],{"class":58},"\"=D5\"",[48,217,186],{"class":92},[48,219,221,223,226,228,231,233,236,239,241,244,246,249],{"class":50,"line":220},9,[48,222,159],{"class":92},[48,224,225],{"class":58},"\"Net cash flow\"",[48,227,165],{"class":92},[48,229,230],{"class":199},"25000",[48,232,165],{"class":92},[48,234,235],{"class":88},"-",[48,237,238],{"class":199},"40000",[48,240,165],{"class":92},[48,242,243],{"class":199},"15000",[48,245,165],{"class":92},[48,247,248],{"class":199},"30000",[48,250,186],{"class":92},[48,252,254,256,259,262,265,268,271,274,278,281,284,287,289,291,293,296,299,302,305,308],{"class":50,"line":253},10,[48,255,159],{"class":92},[48,257,258],{"class":58},"\"Interest (4% p.a. on average balance)\"",[48,260,261],{"class":92},"] ",[48,263,264],{"class":88},"+",[48,266,267],{"class":92}," [",[48,269,270],{"class":88},"f",[48,272,273],{"class":58},"\"=0.04\u002F4*(",[48,275,277],{"class":276},"sSjpA","{",[48,279,280],{"class":92},"c",[48,282,283],{"class":276},"}",[48,285,286],{"class":58},"2+",[48,288,277],{"class":276},[48,290,280],{"class":92},[48,292,283],{"class":276},[48,294,295],{"class":58},"5)\u002F2\"",[48,297,298],{"class":88}," for",[48,300,301],{"class":92}," c ",[48,303,304],{"class":88},"in",[48,306,307],{"class":58}," \"BCDE\"",[48,309,186],{"class":92},[48,311,313,315,318,320,322,324,326,329,331,333,335,337,339,341,343,346,348,350,352,355,357,359,361,363],{"class":50,"line":312},11,[48,314,159],{"class":92},[48,316,317],{"class":58},"\"Closing balance\"",[48,319,261],{"class":92},[48,321,264],{"class":88},[48,323,267],{"class":92},[48,325,270],{"class":88},[48,327,328],{"class":58},"\"=",[48,330,277],{"class":276},[48,332,280],{"class":92},[48,334,283],{"class":276},[48,336,286],{"class":58},[48,338,277],{"class":276},[48,340,280],{"class":92},[48,342,283],{"class":276},[48,344,345],{"class":58},"3+",[48,347,277],{"class":276},[48,349,280],{"class":92},[48,351,283],{"class":276},[48,353,354],{"class":58},"4\"",[48,356,298],{"class":88},[48,358,301],{"class":92},[48,360,304],{"class":88},[48,362,307],{"class":58},[48,364,186],{"class":92},[48,366,368,371,374,377,380,382,384,387,389],{"class":50,"line":367},12,[48,369,370],{"class":92},"    ws[",[48,372,373],{"class":58},"\"G1\"",[48,375,376],{"class":92},"], ws[",[48,378,379],{"class":58},"\"G2\"",[48,381,261],{"class":92},[48,383,128],{"class":88},[48,385,386],{"class":58}," \"Rate\"",[48,388,165],{"class":92},[48,390,391],{"class":199},"0.04\n",[48,393,395,398],{"class":50,"line":394},13,[48,396,397],{"class":88},"    if",[48,399,400],{"class":92}," iterate:\n",[48,402,404,407,409],{"class":50,"line":403},14,[48,405,406],{"class":92},"        wb.calculation.iterate ",[48,408,128],{"class":88},[48,410,411],{"class":199}," True\n",[48,413,415,418,420],{"class":50,"line":414},15,[48,416,417],{"class":92},"        wb.calculation.iterateCount ",[48,419,128],{"class":88},[48,421,422],{"class":199}," 100\n",[48,424,426,429,431],{"class":50,"line":425},16,[48,427,428],{"class":92},"        wb.calculation.iterateDelta ",[48,430,128],{"class":88},[48,432,433],{"class":199}," 0.0001\n",[48,435,437],{"class":50,"line":436},17,[48,438,439],{"class":92},"    wb.save(path)\n",[48,441,443],{"class":50,"line":442},18,[48,444,106],{"emptyLinePlaceholder":105},[48,446,448,451,454,456,460,462,465],{"class":50,"line":447},19,[48,449,450],{"class":92},"cash_model(",[48,452,453],{"class":58},"\"cash_no_iter.xlsx\"",[48,455,165],{"class":92},[48,457,459],{"class":458},"sa561","iterate",[48,461,128],{"class":88},[48,463,464],{"class":199},"False",[48,466,467],{"class":92},")\n",[48,469,471,473,476,478,480,482,485],{"class":50,"line":470},20,[48,472,450],{"class":92},[48,474,475],{"class":58},"\"cash_iter.xlsx\"",[48,477,165],{"class":92},[48,479,459],{"class":458},[48,481,128],{"class":88},[48,483,484],{"class":199},"True",[48,486,467],{"class":92},[10,488,489],{},"Each quarter's interest is a quarter of 4% on the average of the opening and closing balances, and the closing balance is opening plus cash flow plus interest. Interest needs closing, closing needs interest: row 4 and row 5 refer to each other in every column. Each quarter's opening balance is the previous quarter's closing balance, so the loop runs through the whole model.",[491,492,500,501,500,505,500,509,500,517,500,527,500,538,500,545,500,550,500,556,500,561,500,566,500,573,500,578,500,581,500,585,500,588,500,591,500,596,500,600,500,603,500,607,500,610,500,616,500,622,500,627,500,630],"svg",{"viewBox":493,"role":494,"ariaLabelledBy":495,"xmlns":498,"style":499},"4 0 752 151","img",[496,497],"circ-loop-t","circ-loop-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[502,503,504],"title",{"id":496},"The circular dependency in each quarter",[506,507,508],"desc",{"id":497},"The closing balance is opening plus net cash flow plus interest. Interest is calculated on the average of opening and closing. Closing therefore depends on interest, which depends on closing, forming a loop.",[510,511],"rect",{"x":512,"y":513,"width":514,"height":515,"fill":516},"4","0","752","151","#ffffff",[510,518],{"x":519,"y":520,"width":521,"height":522,"rx":523,"fill":524,"stroke":525,"style":526},"20.0","16.0","153.0","93.0","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[528,529,531],"g",{"transform":530},"translate(84.5,29.0) scale(1.000)",[532,533],"path",{"d":534,"fill":535,"stroke":536,"style":537},"M5 6c0-1.7 3.1-3 7-3s7 1.3 7 3-3.1 3-7 3-7-1.3-7-3z M5 6v12c0 1.7 3.1 3 7 3s7-1.3 7-3V6 M5 12c0 1.7 3.1 3 7 3s7-1.3 7-3","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[539,540,544],"text",{"x":541,"y":542,"style":543},"96.5","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Opening balance",[539,546,549],{"x":541,"y":547,"style":548},"95.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","B2 (previous closing)",[50,551],{"x1":552,"y1":553,"x2":554,"y2":553,"stroke":555,"style":526},"178.0","62.5","200.0","var(--brand,#5b5cf0)",[557,558],"polygon",{"points":559,"fill":560},"204.0,62.5 195.0,67.5 195.0,57.5","#5b5cf0",[510,562],{"x":563,"y":520,"width":521,"height":522,"rx":523,"fill":564,"stroke":565,"style":526},"209.0","#fdefd8","var(--gold,#b4740a)",[528,567,569],{"transform":568},"translate(273.5,29.0) scale(1.000)",[532,570],{"d":571,"fill":535,"stroke":572,"style":537},"M20 11a8 8 0 1 0-2.3 5.7 M20 4v7h-7","var(--gold-ink,#7a4e06)",[539,574,577],{"x":575,"y":542,"style":576},"285.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Interest",[539,579,580],{"x":575,"y":547,"style":548},"rate × (B2 + B5) \u002F 2",[50,582],{"x1":583,"y1":553,"x2":584,"y2":553,"stroke":555,"style":526},"367.0","389.0",[557,586],{"points":587,"fill":560},"393.0,62.5 384.0,67.5 384.0,57.5",[510,589],{"x":590,"y":520,"width":521,"height":522,"rx":523,"fill":564,"stroke":565,"style":526},"398.0",[528,592,594],{"transform":593},"translate(462.5,29.0) scale(1.000)",[532,595],{"d":534,"fill":535,"stroke":572,"style":537},[539,597,599],{"x":598,"y":542,"style":576},"474.5","Closing balance",[539,601,602],{"x":598,"y":547,"style":548},"B2 + B3 + B4",[50,604],{"x1":605,"y1":553,"x2":606,"y2":553,"stroke":555,"style":526},"556.0","578.0",[557,608],{"points":609,"fill":560},"582.0,62.5 573.0,67.5 573.0,57.5",[510,611],{"x":612,"y":520,"width":521,"height":522,"rx":523,"fill":613,"stroke":614,"style":615},"587.0","#fce9e9","var(--danger,#dc2626)","stroke-width:2px;stroke-dasharray:6 4",[528,617,619],{"transform":618},"translate(651.5,29.0) scale(1.000)",[532,620],{"d":571,"fill":535,"stroke":621,"style":537},"var(--accent-ink,#be185d)",[539,623,626],{"x":624,"y":542,"style":625},"663.5","font-size:13.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","back to Interest",[539,628,629],{"x":624,"y":547,"style":548},"B5 feeds B4",[539,631,635],{"x":632,"y":633,"style":634},"380.0","131.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","Interest needs the closing balance and the closing balance includes the interest.",[33,637,639],{"id":638},"step-2-see-what-happens-with-and-without-iteration","Step 2 — See what happens with and without iteration",[38,641,643],{"className":79,"code":642,"language":81,"meta":43,"style":43},"import subprocess\nfrom openpyxl import load_workbook\n\nfor f in (\"cash_no_iter.xlsx\", \"cash_iter.xlsx\"):\n    subprocess.run([\"soffice\", \"--headless\", \"--convert-to\", \"xlsx\", \"--outdir\", \"recalc\", f],\n                   check=True, capture_output=True)\n    ws = load_workbook(f\"recalc\u002F{f}\", data_only=True)[\"Cash\"]\n    print(f\"{f:18} interest Q1 = {ws['B4'].value!s:>10}   closing Q4 = {ws['E5'].value}\")\n",[45,644,645,652,663,667,689,725,745,786],{"__ignoreMap":43},[48,646,647,649],{"class":50,"line":51},[48,648,96],{"class":88},[48,650,651],{"class":92}," subprocess\n",[48,653,654,656,658,660],{"class":50,"line":102},[48,655,89],{"class":88},[48,657,93],{"class":92},[48,659,96],{"class":88},[48,661,662],{"class":92}," load_workbook\n",[48,664,665],{"class":50,"line":109},[48,666,106],{"emptyLinePlaceholder":105},[48,668,669,672,675,677,680,682,684,686],{"class":50,"line":122},[48,670,671],{"class":88},"for",[48,673,674],{"class":92}," f ",[48,676,304],{"class":88},[48,678,679],{"class":92}," (",[48,681,453],{"class":58},[48,683,165],{"class":92},[48,685,475],{"class":58},[48,687,688],{"class":92},"):\n",[48,690,691,694,697,699,702,704,707,709,712,714,717,719,722],{"class":50,"line":134},[48,692,693],{"class":92},"    subprocess.run([",[48,695,696],{"class":58},"\"soffice\"",[48,698,165],{"class":92},[48,700,701],{"class":58},"\"--headless\"",[48,703,165],{"class":92},[48,705,706],{"class":58},"\"--convert-to\"",[48,708,165],{"class":92},[48,710,711],{"class":58},"\"xlsx\"",[48,713,165],{"class":92},[48,715,716],{"class":58},"\"--outdir\"",[48,718,165],{"class":92},[48,720,721],{"class":58},"\"recalc\"",[48,723,724],{"class":92},", f],\n",[48,726,727,730,732,734,736,739,741,743],{"class":50,"line":145},[48,728,729],{"class":458},"                   check",[48,731,128],{"class":88},[48,733,484],{"class":199},[48,735,165],{"class":92},[48,737,738],{"class":458},"capture_output",[48,740,128],{"class":88},[48,742,484],{"class":199},[48,744,467],{"class":92},[48,746,747,749,751,754,756,759,761,763,765,768,770,773,775,777,780,783],{"class":50,"line":156},[48,748,137],{"class":92},[48,750,128],{"class":88},[48,752,753],{"class":92}," load_workbook(",[48,755,270],{"class":88},[48,757,758],{"class":58},"\"recalc\u002F",[48,760,277],{"class":276},[48,762,270],{"class":92},[48,764,283],{"class":276},[48,766,767],{"class":58},"\"",[48,769,165],{"class":92},[48,771,772],{"class":458},"data_only",[48,774,128],{"class":88},[48,776,484],{"class":199},[48,778,779],{"class":92},")[",[48,781,782],{"class":58},"\"Cash\"",[48,784,785],{"class":92},"]\n",[48,787,788,791,794,796,798,800,802,805,807,810,812,815,818,821,824,826,829,831,833,836,838,840,842],{"class":50,"line":189},[48,789,790],{"class":199},"    print",[48,792,793],{"class":92},"(",[48,795,270],{"class":88},[48,797,767],{"class":58},[48,799,277],{"class":276},[48,801,270],{"class":92},[48,803,804],{"class":88},":18",[48,806,283],{"class":276},[48,808,809],{"class":58}," interest Q1 = ",[48,811,277],{"class":276},[48,813,814],{"class":92},"ws[",[48,816,817],{"class":58},"'B4'",[48,819,820],{"class":92},"].value",[48,822,823],{"class":88},"!s:>10",[48,825,283],{"class":276},[48,827,828],{"class":58},"   closing Q4 = ",[48,830,277],{"class":276},[48,832,814],{"class":92},[48,834,835],{"class":58},"'E5'",[48,837,820],{"class":92},[48,839,283],{"class":276},[48,841,767],{"class":58},[48,843,467],{"class":92},[10,845,846,847,850,851,854,855,858],{},"Without iterative calculation, every cell in the loop is an error — LibreOffice stores ",[45,848,849],{},"#VALUE!"," in the file (its internal \"circular reference\" error) and Excel shows a circular-reference warning and zeros. With the three calculation settings in the file, the spreadsheet loops until changes fall below ",[45,852,853],{},"iterateDelta"," or ",[45,856,857],{},"iterateCount"," passes have run, and the balances converge: Q1 interest of 1,130.65 and a Q4 closing balance of 134,336.45.",[33,860,862],{"id":861},"step-3-the-three-settings-openpyxl-writes","Step 3 — The three settings openpyxl writes",[38,864,866],{"className":79,"code":865,"language":81,"meta":43,"style":43},"wb = load_workbook(\"cash_iter.xlsx\")\ncalc = wb.calculation\nprint(\"iterate:\", calc.iterate, \"| max iterations:\", calc.iterateCount, \"| max change:\", calc.iterateDelta)\n",[45,867,868,881,891],{"__ignoreMap":43},[48,869,870,873,875,877,879],{"class":50,"line":51},[48,871,872],{"class":92},"wb ",[48,874,128],{"class":88},[48,876,753],{"class":92},[48,878,475],{"class":58},[48,880,467],{"class":92},[48,882,883,886,888],{"class":50,"line":102},[48,884,885],{"class":92},"calc ",[48,887,128],{"class":88},[48,889,890],{"class":92}," wb.calculation\n",[48,892,893,896,898,901,904,907,910,913],{"class":50,"line":109},[48,894,895],{"class":199},"print",[48,897,793],{"class":92},[48,899,900],{"class":58},"\"iterate:\"",[48,902,903],{"class":92},", calc.iterate, ",[48,905,906],{"class":58},"\"| max iterations:\"",[48,908,909],{"class":92},", calc.iterateCount, ",[48,911,912],{"class":58},"\"| max change:\"",[48,914,915],{"class":92},", calc.iterateDelta)\n",[10,917,918,919,165,922,925,926,929,930,933,934,31],{},"These map to Excel's ",[14,920,921],{},"File › Options › Formulas › Enable iterative calculation",[14,923,924],{},"Maximum Iterations"," and ",[14,927,928],{},"Maximum Change",". They are stored per workbook, so the setting travels with the file — but Excel applies the setting of the ",[14,931,932],{},"first"," workbook opened in a session to all workbooks open at the same time, which is a common source of \"it works on my machine\" with circular models. A delta of 0.0001 is fine for currency; 100 iterations is far more than a well-behaved loop like this one needs — it converges in a handful. Calculation settings in general are discussed in ",[27,935,937],{"href":936},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Frecalculate-excel-formulas-without-excel-in-python\u002F","Recalculate Excel Formulas Without Excel in Python",[491,939,500,944,500,947,500,950,500,953,500,958,500,965,500,971,500,977,500,983,500,991,500,995,500,1000,500,1004,500,1009,500,1013,500,1020,500,1025,500,1029,500,1033,500,1038,500,1041,500,1046,500,1049],{"viewBox":940,"role":494,"ariaLabelledBy":941,"xmlns":498,"style":499},"4 0 752 207",[942,943],"circ-settings-t","circ-settings-d",[502,945,946],{"id":942},"The same model with and without iterative calculation",[506,948,949],{"id":943},"Without the iterate setting the loop cells show errors or zeros and Excel warns about a circular reference. With iterate true, iterateCount 100 and iterateDelta 0.0001 the model converges and shows interest and balances.",[510,951],{"x":512,"y":513,"width":514,"height":952,"fill":516},"207",[510,954],{"x":519,"y":520,"width":955,"height":956,"rx":957,"fill":613,"stroke":614,"style":526},"351.0","175.0","14",[528,959,961],{"transform":960},"translate(182.5,29.0) scale(1.083)",[532,962],{"d":963,"fill":535,"stroke":621,"style":964},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[539,966,970],{"x":967,"y":968,"style":969},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","iterate = False (default)",[50,972],{"x1":973,"y1":974,"x2":975,"y2":974,"stroke":614,"style":976},"36.0","91.0","355.0","stroke-width:1px",[539,978,982],{"x":979,"y":980,"style":981},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","loop cells: error or 0",[528,984,986],{"transform":985},"translate(38.5,99.5) scale(0.625)",[532,987],{"d":988,"fill":535,"stroke":989,"style":990},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[539,992,994],{"x":979,"y":993,"style":981},"137.0","circular reference warning",[528,996,998],{"transform":997},"translate(38.5,125.5) scale(0.625)",[532,999],{"d":988,"fill":535,"stroke":989,"style":990},[539,1001,1003],{"x":979,"y":1002,"style":981},"163.0","downstream totals wrong",[528,1005,1007],{"transform":1006},"translate(38.5,151.5) scale(0.625)",[532,1008],{"d":988,"fill":535,"stroke":989,"style":990},[510,1010],{"x":584,"y":520,"width":955,"height":956,"rx":957,"fill":1011,"stroke":1012,"style":526},"#d9f4f1","var(--teal,#0f9488)",[528,1014,1016],{"transform":1015},"translate(551.5,29.0) scale(1.083)",[532,1017],{"d":1018,"fill":535,"stroke":1019,"style":964},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[539,1021,1024],{"x":1022,"y":968,"style":1023},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","iterate = True",[50,1026],{"x1":1027,"y1":974,"x2":1028,"y2":974,"stroke":1012,"style":976},"405.0","724.0",[539,1030,1032],{"x":1031,"y":980,"style":981},"429.0","recalculates until stable",[528,1034,1036],{"transform":1035},"translate(407.5,99.5) scale(0.625)",[532,1037],{"d":1018,"fill":535,"stroke":1012,"style":990},[539,1039,1040],{"x":1031,"y":993,"style":981},"iterateCount caps passes",[528,1042,1044],{"transform":1043},"translate(407.5,125.5) scale(0.625)",[532,1045],{"d":1018,"fill":535,"stroke":1012,"style":990},[539,1047,1048],{"x":1031,"y":1002,"style":981},"iterateDelta sets precision",[528,1050,1052],{"transform":1051},"translate(407.5,151.5) scale(0.625)",[532,1053],{"d":1018,"fill":535,"stroke":1012,"style":990},[33,1055,1057],{"id":1056},"step-4-find-circular-references-from-python","Step 4 — Find circular references from Python",[10,1059,1060],{},"Before turning iteration on, know which loops exist: an accidental circular reference hidden among deliberate ones is the most dangerous kind, because iteration silences its warning too. A small dependency graph built from the formulas finds them:",[38,1062,1064],{"className":79,"code":1063,"language":81,"meta":43,"style":43},"import re\nfrom openpyxl.utils import range_boundaries, get_column_letter\n\nREF = re.compile(r\"\\$?([A-Z]{1,3})\\$?(\\d+)(?::\\$?([A-Z]{1,3})\\$?(\\d+))?\")\n\ndef cells_in(match):\n    c1, r1, c2, r2 = match.groups()\n    if c2 is None:\n        return [f\"{c1}{r1}\"]\n    min_col, min_row, max_col, max_row = range_boundaries(f\"{c1}{r1}:{c2}{r2}\")\n    return [f\"{get_column_letter(c)}{r}\" for c in range(min_col, max_col + 1) for r in range(min_row, max_row + 1)]\n\ndef find_cycles(ws):\n    graph = {}\n    for row in ws.iter_rows():\n        for cell in row:\n            if isinstance(cell.value, str) and cell.value.startswith(\"=\"):\n                graph[cell.coordinate] = {ref for m in REF.finditer(cell.value) for ref in cells_in(m)}\n    cycles, state = [], {}\n    def visit(node, path):\n        state[node] = \"active\"\n        for nxt in graph.get(node, ()):\n            if state.get(nxt) == \"active\":\n                cycles.append(path[path.index(nxt):] + [nxt])\n            elif nxt not in state:\n                visit(nxt, path + [nxt])\n        state[node] = \"done\"\n    for node in graph:\n        if node not in state:\n            visit(node, [node])\n    return cycles\n\nfor cycle in find_cycles(load_workbook(\"cash_no_iter.xlsx\")[\"Cash\"]):\n    print(\" -> \".join(cycle))\n",[45,1065,1066,1073,1085,1089,1173,1177,1187,1197,1213,1241,1283,1346,1350,1360,1370,1383,1396,1423,1456,1466,1477,1488,1501,1517,1528,1545,1555,1565,1578,1592,1598,1606,1611,1633],{"__ignoreMap":43},[48,1067,1068,1070],{"class":50,"line":51},[48,1069,96],{"class":88},[48,1071,1072],{"class":92}," re\n",[48,1074,1075,1077,1080,1082],{"class":50,"line":102},[48,1076,89],{"class":88},[48,1078,1079],{"class":92}," openpyxl.utils ",[48,1081,96],{"class":88},[48,1083,1084],{"class":92}," range_boundaries, get_column_letter\n",[48,1086,1087],{"class":50,"line":109},[48,1088,106],{"emptyLinePlaceholder":105},[48,1090,1091,1094,1097,1100,1103,1105,1109,1112,1115,1118,1121,1124,1127,1129,1131,1134,1136,1139,1142,1144,1146,1148,1150,1152,1154,1156,1158,1160,1162,1164,1167,1169,1171],{"class":50,"line":122},[48,1092,1093],{"class":199},"REF",[48,1095,1096],{"class":88}," =",[48,1098,1099],{"class":92}," re.compile(",[48,1101,1102],{"class":88},"r",[48,1104,767],{"class":58},[48,1106,1108],{"class":1107},"s_b0D","\\$",[48,1110,1111],{"class":88},"?",[48,1113,1114],{"class":199},"([",[48,1116,1117],{"class":276},"A-Z",[48,1119,1120],{"class":199},"]",[48,1122,1123],{"class":88},"{1,3}",[48,1125,1126],{"class":199},")",[48,1128,1108],{"class":1107},[48,1130,1111],{"class":88},[48,1132,1133],{"class":199},"(\\d",[48,1135,264],{"class":88},[48,1137,1138],{"class":199},")(?:",[48,1140,1141],{"class":58},":",[48,1143,1108],{"class":1107},[48,1145,1111],{"class":88},[48,1147,1114],{"class":199},[48,1149,1117],{"class":276},[48,1151,1120],{"class":199},[48,1153,1123],{"class":88},[48,1155,1126],{"class":199},[48,1157,1108],{"class":1107},[48,1159,1111],{"class":88},[48,1161,1133],{"class":199},[48,1163,264],{"class":88},[48,1165,1166],{"class":199},"))",[48,1168,1111],{"class":88},[48,1170,767],{"class":58},[48,1172,467],{"class":92},[48,1174,1175],{"class":50,"line":134},[48,1176,106],{"emptyLinePlaceholder":105},[48,1178,1179,1181,1184],{"class":50,"line":145},[48,1180,112],{"class":88},[48,1182,1183],{"class":115}," cells_in",[48,1185,1186],{"class":92},"(match):\n",[48,1188,1189,1192,1194],{"class":50,"line":156},[48,1190,1191],{"class":92},"    c1, r1, c2, r2 ",[48,1193,128],{"class":88},[48,1195,1196],{"class":92}," match.groups()\n",[48,1198,1199,1201,1204,1207,1210],{"class":50,"line":189},[48,1200,397],{"class":88},[48,1202,1203],{"class":92}," c2 ",[48,1205,1206],{"class":88},"is",[48,1208,1209],{"class":199}," None",[48,1211,1212],{"class":92},":\n",[48,1214,1215,1218,1220,1222,1224,1226,1229,1232,1235,1237,1239],{"class":50,"line":220},[48,1216,1217],{"class":88},"        return",[48,1219,267],{"class":92},[48,1221,270],{"class":88},[48,1223,767],{"class":58},[48,1225,277],{"class":276},[48,1227,1228],{"class":92},"c1",[48,1230,1231],{"class":276},"}{",[48,1233,1234],{"class":92},"r1",[48,1236,283],{"class":276},[48,1238,767],{"class":58},[48,1240,785],{"class":92},[48,1242,1243,1246,1248,1251,1253,1255,1257,1259,1261,1263,1265,1267,1269,1272,1274,1277,1279,1281],{"class":50,"line":253},[48,1244,1245],{"class":92},"    min_col, min_row, max_col, max_row ",[48,1247,128],{"class":88},[48,1249,1250],{"class":92}," range_boundaries(",[48,1252,270],{"class":88},[48,1254,767],{"class":58},[48,1256,277],{"class":276},[48,1258,1228],{"class":92},[48,1260,1231],{"class":276},[48,1262,1234],{"class":92},[48,1264,283],{"class":276},[48,1266,1141],{"class":58},[48,1268,277],{"class":276},[48,1270,1271],{"class":92},"c2",[48,1273,1231],{"class":276},[48,1275,1276],{"class":92},"r2",[48,1278,283],{"class":276},[48,1280,767],{"class":58},[48,1282,467],{"class":92},[48,1284,1285,1288,1290,1292,1294,1296,1299,1301,1303,1305,1307,1309,1311,1313,1316,1319,1321,1324,1327,1329,1332,1334,1336,1339,1341,1343],{"class":50,"line":312},[48,1286,1287],{"class":88},"    return",[48,1289,267],{"class":92},[48,1291,270],{"class":88},[48,1293,767],{"class":58},[48,1295,277],{"class":276},[48,1297,1298],{"class":92},"get_column_letter(c)",[48,1300,1231],{"class":276},[48,1302,1102],{"class":92},[48,1304,283],{"class":276},[48,1306,767],{"class":58},[48,1308,298],{"class":88},[48,1310,301],{"class":92},[48,1312,304],{"class":88},[48,1314,1315],{"class":199}," range",[48,1317,1318],{"class":92},"(min_col, max_col ",[48,1320,264],{"class":88},[48,1322,1323],{"class":199}," 1",[48,1325,1326],{"class":92},") ",[48,1328,671],{"class":88},[48,1330,1331],{"class":92}," r ",[48,1333,304],{"class":88},[48,1335,1315],{"class":199},[48,1337,1338],{"class":92},"(min_row, max_row ",[48,1340,264],{"class":88},[48,1342,1323],{"class":199},[48,1344,1345],{"class":92},")]\n",[48,1347,1348],{"class":50,"line":367},[48,1349,106],{"emptyLinePlaceholder":105},[48,1351,1352,1354,1357],{"class":50,"line":394},[48,1353,112],{"class":88},[48,1355,1356],{"class":115}," find_cycles",[48,1358,1359],{"class":92},"(ws):\n",[48,1361,1362,1365,1367],{"class":50,"line":403},[48,1363,1364],{"class":92},"    graph ",[48,1366,128],{"class":88},[48,1368,1369],{"class":92}," {}\n",[48,1371,1372,1375,1378,1380],{"class":50,"line":414},[48,1373,1374],{"class":88},"    for",[48,1376,1377],{"class":92}," row ",[48,1379,304],{"class":88},[48,1381,1382],{"class":92}," ws.iter_rows():\n",[48,1384,1385,1388,1391,1393],{"class":50,"line":425},[48,1386,1387],{"class":88},"        for",[48,1389,1390],{"class":92}," cell ",[48,1392,304],{"class":88},[48,1394,1395],{"class":92}," row:\n",[48,1397,1398,1401,1404,1407,1410,1412,1415,1418,1421],{"class":50,"line":436},[48,1399,1400],{"class":88},"            if",[48,1402,1403],{"class":199}," isinstance",[48,1405,1406],{"class":92},"(cell.value, ",[48,1408,1409],{"class":199},"str",[48,1411,1326],{"class":92},[48,1413,1414],{"class":88},"and",[48,1416,1417],{"class":92}," cell.value.startswith(",[48,1419,1420],{"class":58},"\"=\"",[48,1422,688],{"class":92},[48,1424,1425,1428,1430,1433,1435,1438,1440,1443,1446,1448,1451,1453],{"class":50,"line":442},[48,1426,1427],{"class":92},"                graph[cell.coordinate] ",[48,1429,128],{"class":88},[48,1431,1432],{"class":92}," {ref ",[48,1434,671],{"class":88},[48,1436,1437],{"class":92}," m ",[48,1439,304],{"class":88},[48,1441,1442],{"class":199}," REF",[48,1444,1445],{"class":92},".finditer(cell.value) ",[48,1447,671],{"class":88},[48,1449,1450],{"class":92}," ref ",[48,1452,304],{"class":88},[48,1454,1455],{"class":92}," cells_in(m)}\n",[48,1457,1458,1461,1463],{"class":50,"line":447},[48,1459,1460],{"class":92},"    cycles, state ",[48,1462,128],{"class":88},[48,1464,1465],{"class":92}," [], {}\n",[48,1467,1468,1471,1474],{"class":50,"line":470},[48,1469,1470],{"class":88},"    def",[48,1472,1473],{"class":115}," visit",[48,1475,1476],{"class":92},"(node, path):\n",[48,1478,1480,1483,1485],{"class":50,"line":1479},21,[48,1481,1482],{"class":92},"        state[node] ",[48,1484,128],{"class":88},[48,1486,1487],{"class":58}," \"active\"\n",[48,1489,1491,1493,1496,1498],{"class":50,"line":1490},22,[48,1492,1387],{"class":88},[48,1494,1495],{"class":92}," nxt ",[48,1497,304],{"class":88},[48,1499,1500],{"class":92}," graph.get(node, ()):\n",[48,1502,1504,1506,1509,1512,1515],{"class":50,"line":1503},23,[48,1505,1400],{"class":88},[48,1507,1508],{"class":92}," state.get(nxt) ",[48,1510,1511],{"class":88},"==",[48,1513,1514],{"class":58}," \"active\"",[48,1516,1212],{"class":92},[48,1518,1520,1523,1525],{"class":50,"line":1519},24,[48,1521,1522],{"class":92},"                cycles.append(path[path.index(nxt):] ",[48,1524,264],{"class":88},[48,1526,1527],{"class":92}," [nxt])\n",[48,1529,1531,1534,1536,1539,1542],{"class":50,"line":1530},25,[48,1532,1533],{"class":88},"            elif",[48,1535,1495],{"class":92},[48,1537,1538],{"class":88},"not",[48,1540,1541],{"class":88}," in",[48,1543,1544],{"class":92}," state:\n",[48,1546,1548,1551,1553],{"class":50,"line":1547},26,[48,1549,1550],{"class":92},"                visit(nxt, path ",[48,1552,264],{"class":88},[48,1554,1527],{"class":92},[48,1556,1558,1560,1562],{"class":50,"line":1557},27,[48,1559,1482],{"class":92},[48,1561,128],{"class":88},[48,1563,1564],{"class":58}," \"done\"\n",[48,1566,1568,1570,1573,1575],{"class":50,"line":1567},28,[48,1569,1374],{"class":88},[48,1571,1572],{"class":92}," node ",[48,1574,304],{"class":88},[48,1576,1577],{"class":92}," graph:\n",[48,1579,1581,1584,1586,1588,1590],{"class":50,"line":1580},29,[48,1582,1583],{"class":88},"        if",[48,1585,1572],{"class":92},[48,1587,1538],{"class":88},[48,1589,1541],{"class":88},[48,1591,1544],{"class":92},[48,1593,1595],{"class":50,"line":1594},30,[48,1596,1597],{"class":92},"            visit(node, [node])\n",[48,1599,1601,1603],{"class":50,"line":1600},31,[48,1602,1287],{"class":88},[48,1604,1605],{"class":92}," cycles\n",[48,1607,1609],{"class":50,"line":1608},32,[48,1610,106],{"emptyLinePlaceholder":105},[48,1612,1614,1616,1619,1621,1624,1626,1628,1630],{"class":50,"line":1613},33,[48,1615,671],{"class":88},[48,1617,1618],{"class":92}," cycle ",[48,1620,304],{"class":88},[48,1622,1623],{"class":92}," find_cycles(load_workbook(",[48,1625,453],{"class":58},[48,1627,779],{"class":92},[48,1629,782],{"class":58},[48,1631,1632],{"class":92},"]):\n",[48,1634,1636,1638,1640,1643],{"class":50,"line":1635},34,[48,1637,790],{"class":199},[48,1639,793],{"class":92},[48,1641,1642],{"class":58},"\" -> \"",[48,1644,1645],{"class":92},".join(cycle))\n",[10,1647,1648,1649,1652,1653,1656,1657,31],{},"The search reports one loop per quarter, such as ",[45,1650,1651],{},"B4 -> B5 -> B4",". It only follows same-sheet references written as A1 addresses — cross-sheet references, defined names and ",[45,1654,1655],{},"INDIRECT"," need more parsing — but that covers most models. Review the list: every loop on it should be one somebody intended. A wider view of formula dependencies is in ",[27,1658,1660],{"href":1659},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fmap-formula-dependencies-in-a-workbook-with-python\u002F","Map Formula Dependencies in a Workbook with Python",[33,1662,1664],{"id":1663},"step-5-solve-the-loop-in-python-instead","Step 5 — Solve the loop in Python instead",[10,1666,1667],{},"A report that only needs the numbers should not depend on a spreadsheet's iteration settings. The same loop can be solved directly. Fixed-point iteration mirrors what the spreadsheet does:",[38,1669,1671],{"className":79,"code":1670,"language":81,"meta":43,"style":43},"import pandas as pd\n\nflows = [25000, -40000, 15000, 30000]\nq_rate = 0.04 \u002F 4\n\ndef quarter_fixed_point(opening, flow, tol=1e-9):\n    interest = 0.0\n    for i in range(100):\n        closing = opening + flow + interest\n        new_interest = q_rate * (opening + closing) \u002F 2\n        if abs(new_interest - interest) \u003C tol:\n            return new_interest, closing, i + 1\n        interest = new_interest\n    raise RuntimeError(\"did not converge\")\n\nrows, opening = [], 100000.0\nfor q, flow in zip([\"Q1\", \"Q2\", \"Q3\", \"Q4\"], flows):\n    interest, closing, n = quarter_fixed_point(opening, flow)\n    rows.append({\"Quarter\": q, \"Opening\": opening, \"Interest\": interest, \"Closing\": closing, \"Iterations\": n})\n    opening = closing\nprint(pd.DataFrame(rows).round(2).to_string(index=False))\n",[45,1672,1673,1686,1690,1717,1733,1737,1754,1764,1782,1802,1829,1850,1863,1873,1888,1892,1905,1936,1946,1981,1991],{"__ignoreMap":43},[48,1674,1675,1677,1680,1683],{"class":50,"line":51},[48,1676,96],{"class":88},[48,1678,1679],{"class":92}," pandas ",[48,1681,1682],{"class":88},"as",[48,1684,1685],{"class":92}," pd\n",[48,1687,1688],{"class":50,"line":102},[48,1689,106],{"emptyLinePlaceholder":105},[48,1691,1692,1695,1697,1699,1701,1703,1705,1707,1709,1711,1713,1715],{"class":50,"line":109},[48,1693,1694],{"class":92},"flows ",[48,1696,128],{"class":88},[48,1698,267],{"class":92},[48,1700,230],{"class":199},[48,1702,165],{"class":92},[48,1704,235],{"class":88},[48,1706,238],{"class":199},[48,1708,165],{"class":92},[48,1710,243],{"class":199},[48,1712,165],{"class":92},[48,1714,248],{"class":199},[48,1716,785],{"class":92},[48,1718,1719,1722,1724,1727,1730],{"class":50,"line":122},[48,1720,1721],{"class":92},"q_rate ",[48,1723,128],{"class":88},[48,1725,1726],{"class":199}," 0.04",[48,1728,1729],{"class":88}," \u002F",[48,1731,1732],{"class":199}," 4\n",[48,1734,1735],{"class":50,"line":134},[48,1736,106],{"emptyLinePlaceholder":105},[48,1738,1739,1741,1744,1747,1749,1752],{"class":50,"line":145},[48,1740,112],{"class":88},[48,1742,1743],{"class":115}," quarter_fixed_point",[48,1745,1746],{"class":92},"(opening, flow, tol",[48,1748,128],{"class":88},[48,1750,1751],{"class":199},"1e-9",[48,1753,688],{"class":92},[48,1755,1756,1759,1761],{"class":50,"line":156},[48,1757,1758],{"class":92},"    interest ",[48,1760,128],{"class":88},[48,1762,1763],{"class":199}," 0.0\n",[48,1765,1766,1768,1771,1773,1775,1777,1780],{"class":50,"line":189},[48,1767,1374],{"class":88},[48,1769,1770],{"class":92}," i ",[48,1772,304],{"class":88},[48,1774,1315],{"class":199},[48,1776,793],{"class":92},[48,1778,1779],{"class":199},"100",[48,1781,688],{"class":92},[48,1783,1784,1787,1789,1792,1794,1797,1799],{"class":50,"line":220},[48,1785,1786],{"class":92},"        closing ",[48,1788,128],{"class":88},[48,1790,1791],{"class":92}," opening ",[48,1793,264],{"class":88},[48,1795,1796],{"class":92}," flow ",[48,1798,264],{"class":88},[48,1800,1801],{"class":92}," interest\n",[48,1803,1804,1807,1809,1812,1815,1818,1820,1823,1826],{"class":50,"line":253},[48,1805,1806],{"class":92},"        new_interest ",[48,1808,128],{"class":88},[48,1810,1811],{"class":92}," q_rate ",[48,1813,1814],{"class":88},"*",[48,1816,1817],{"class":92}," (opening ",[48,1819,264],{"class":88},[48,1821,1822],{"class":92}," closing) ",[48,1824,1825],{"class":88},"\u002F",[48,1827,1828],{"class":199}," 2\n",[48,1830,1831,1833,1836,1839,1841,1844,1847],{"class":50,"line":312},[48,1832,1583],{"class":88},[48,1834,1835],{"class":199}," abs",[48,1837,1838],{"class":92},"(new_interest ",[48,1840,235],{"class":88},[48,1842,1843],{"class":92}," interest) ",[48,1845,1846],{"class":88},"\u003C",[48,1848,1849],{"class":92}," tol:\n",[48,1851,1852,1855,1858,1860],{"class":50,"line":367},[48,1853,1854],{"class":88},"            return",[48,1856,1857],{"class":92}," new_interest, closing, i ",[48,1859,264],{"class":88},[48,1861,1862],{"class":199}," 1\n",[48,1864,1865,1868,1870],{"class":50,"line":394},[48,1866,1867],{"class":92},"        interest ",[48,1869,128],{"class":88},[48,1871,1872],{"class":92}," new_interest\n",[48,1874,1875,1878,1881,1883,1886],{"class":50,"line":403},[48,1876,1877],{"class":88},"    raise",[48,1879,1880],{"class":199}," RuntimeError",[48,1882,793],{"class":92},[48,1884,1885],{"class":58},"\"did not converge\"",[48,1887,467],{"class":92},[48,1889,1890],{"class":50,"line":414},[48,1891,106],{"emptyLinePlaceholder":105},[48,1893,1894,1897,1899,1902],{"class":50,"line":425},[48,1895,1896],{"class":92},"rows, opening ",[48,1898,128],{"class":88},[48,1900,1901],{"class":92}," [], ",[48,1903,1904],{"class":199},"100000.0\n",[48,1906,1907,1909,1912,1914,1917,1919,1921,1923,1925,1927,1929,1931,1933],{"class":50,"line":436},[48,1908,671],{"class":88},[48,1910,1911],{"class":92}," q, flow ",[48,1913,304],{"class":88},[48,1915,1916],{"class":199}," zip",[48,1918,1114],{"class":92},[48,1920,168],{"class":58},[48,1922,165],{"class":92},[48,1924,173],{"class":58},[48,1926,165],{"class":92},[48,1928,178],{"class":58},[48,1930,165],{"class":92},[48,1932,183],{"class":58},[48,1934,1935],{"class":92},"], flows):\n",[48,1937,1938,1941,1943],{"class":50,"line":442},[48,1939,1940],{"class":92},"    interest, closing, n ",[48,1942,128],{"class":88},[48,1944,1945],{"class":92}," quarter_fixed_point(opening, flow)\n",[48,1947,1948,1951,1954,1957,1960,1963,1966,1969,1972,1975,1978],{"class":50,"line":447},[48,1949,1950],{"class":92},"    rows.append({",[48,1952,1953],{"class":58},"\"Quarter\"",[48,1955,1956],{"class":92},": q, ",[48,1958,1959],{"class":58},"\"Opening\"",[48,1961,1962],{"class":92},": opening, ",[48,1964,1965],{"class":58},"\"Interest\"",[48,1967,1968],{"class":92},": interest, ",[48,1970,1971],{"class":58},"\"Closing\"",[48,1973,1974],{"class":92},": closing, ",[48,1976,1977],{"class":58},"\"Iterations\"",[48,1979,1980],{"class":92},": n})\n",[48,1982,1983,1986,1988],{"class":50,"line":470},[48,1984,1985],{"class":92},"    opening ",[48,1987,128],{"class":88},[48,1989,1990],{"class":92}," closing\n",[48,1992,1993,1995,1998,2001,2004,2007,2009,2011],{"class":50,"line":1479},[48,1994,895],{"class":199},[48,1996,1997],{"class":92},"(pd.DataFrame(rows).round(",[48,1999,2000],{"class":199},"2",[48,2002,2003],{"class":92},").to_string(",[48,2005,2006],{"class":458},"index",[48,2008,128],{"class":88},[48,2010,464],{"class":199},[48,2012,2013],{"class":92},"))\n",[10,2015,2016,2017,1141],{},"Each quarter converges in seven iterations to a tolerance of a billionth, because the loop's \"gain\" — half of a 1% quarterly rate — is tiny. Better still, this loop has a closed form. Substituting closing into the interest formula and solving for interest gives ",[45,2018,2019],{},"interest = r·(2·opening + flow) \u002F (2 − r)",[38,2021,2023],{"className":79,"code":2022,"language":81,"meta":43,"style":43},"opening = 100000.0\nfor q, flow in zip([\"Q1\", \"Q2\", \"Q3\", \"Q4\"], flows):\n    interest = q_rate * (2 * opening + flow) \u002F (2 - q_rate)\n    closing = opening + flow + interest\n    print(f\"{q}: interest {interest:,.2f}  closing {closing:,.2f}\")\n    opening = closing\n",[45,2024,2025,2035,2063,2099,2116,2162],{"__ignoreMap":43},[48,2026,2027,2030,2032],{"class":50,"line":51},[48,2028,2029],{"class":92},"opening ",[48,2031,128],{"class":88},[48,2033,2034],{"class":199}," 100000.0\n",[48,2036,2037,2039,2041,2043,2045,2047,2049,2051,2053,2055,2057,2059,2061],{"class":50,"line":102},[48,2038,671],{"class":88},[48,2040,1911],{"class":92},[48,2042,304],{"class":88},[48,2044,1916],{"class":199},[48,2046,1114],{"class":92},[48,2048,168],{"class":58},[48,2050,165],{"class":92},[48,2052,173],{"class":58},[48,2054,165],{"class":92},[48,2056,178],{"class":58},[48,2058,165],{"class":92},[48,2060,183],{"class":58},[48,2062,1935],{"class":92},[48,2064,2065,2067,2069,2071,2073,2075,2077,2080,2082,2084,2087,2089,2091,2093,2096],{"class":50,"line":109},[48,2066,1758],{"class":92},[48,2068,128],{"class":88},[48,2070,1811],{"class":92},[48,2072,1814],{"class":88},[48,2074,679],{"class":92},[48,2076,2000],{"class":199},[48,2078,2079],{"class":88}," *",[48,2081,1791],{"class":92},[48,2083,264],{"class":88},[48,2085,2086],{"class":92}," flow) ",[48,2088,1825],{"class":88},[48,2090,679],{"class":92},[48,2092,2000],{"class":199},[48,2094,2095],{"class":88}," -",[48,2097,2098],{"class":92}," q_rate)\n",[48,2100,2101,2104,2106,2108,2110,2112,2114],{"class":50,"line":122},[48,2102,2103],{"class":92},"    closing ",[48,2105,128],{"class":88},[48,2107,1791],{"class":92},[48,2109,264],{"class":88},[48,2111,1796],{"class":92},[48,2113,264],{"class":88},[48,2115,1801],{"class":92},[48,2117,2118,2120,2122,2124,2126,2128,2131,2133,2136,2138,2141,2144,2146,2149,2151,2154,2156,2158,2160],{"class":50,"line":134},[48,2119,790],{"class":199},[48,2121,793],{"class":92},[48,2123,270],{"class":88},[48,2125,767],{"class":58},[48,2127,277],{"class":276},[48,2129,2130],{"class":92},"q",[48,2132,283],{"class":276},[48,2134,2135],{"class":58},": interest ",[48,2137,277],{"class":276},[48,2139,2140],{"class":92},"interest",[48,2142,2143],{"class":88},":,.2f",[48,2145,283],{"class":276},[48,2147,2148],{"class":58},"  closing ",[48,2150,277],{"class":276},[48,2152,2153],{"class":92},"closing",[48,2155,2143],{"class":88},[48,2157,283],{"class":276},[48,2159,767],{"class":58},[48,2161,467],{"class":92},[48,2163,2164,2166,2168],{"class":50,"line":145},[48,2165,1985],{"class":92},[48,2167,128],{"class":88},[48,2169,1990],{"class":92},[10,2171,2172,2173,31],{},"The algebraic answer matches the fixed-point one to the cent and needs no loop at all. The same rearrangement works for the bonus example: a bonus of 10% of profit after bonus is ",[45,2174,2175],{},"0.1 × profit \u002F 1.1",[491,2177,500,2183,500,2186,500,2189,500,2193,500,2198,500,2203,500,2207,500,2211,500,2214,500,2218,500,2221,500,2225,500,2229,500,2234,500,2238,500,2241,500,2245,500,2248,500,2252,500,2256,500,2259,500,2262,500,2264,500,2266,500,2270,500,2274,500,2278,500,2281],{"viewBox":2178,"role":494,"ariaLabelledBy":2179,"xmlns":498,"style":2182},"4 0 748 252",[2180,2181],"circ-converge-t","circ-converge-d","width:100%;max-width:748px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[502,2184,2185],{"id":2180},"How fast the Q1 interest converges",[506,2187,2188],{"id":2181},"Interest estimate by iteration for Q1. Starting from zero, the estimate jumps to about 1,125 after the first pass and settles at about 1,130.65 within three passes.",[510,2190],{"x":512,"y":513,"width":2191,"height":2192,"fill":516},"748","252",[50,2194],{"x1":2195,"y1":2196,"x2":2197,"y2":2196,"stroke":525,"style":976},"53.4","214.0","736.0",[539,2199,513],{"x":2200,"y":2201,"style":2202},"30.7","218.0","font-size:11px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle",[50,2204],{"x1":2195,"y1":2205,"x2":2197,"y2":2205,"stroke":525,"style":2206},"150.7","stroke-width:1px;stroke-dasharray:5 4",[539,2208,2210],{"x":2200,"y":2209,"style":2202},"154.7","500",[50,2212],{"x1":2195,"y1":2213,"x2":2197,"y2":2213,"stroke":525,"style":2206},"87.3",[539,2215,2217],{"x":2200,"y":2216,"style":2202},"91.3","1k",[50,2219],{"x1":2195,"y1":2220,"x2":2197,"y2":2220,"stroke":525,"style":2206},"24.0",[539,2222,2224],{"x":2200,"y":2223,"style":2202},"28.0","1.5k",[50,2226],{"x1":2195,"y1":2196,"x2":2197,"y2":2196,"stroke":2227,"style":2228},"var(--muted,#5b6780)","stroke-width:1.5px",[539,2230,2233],{"x":2231,"y":2232,"style":548},"121.7","232.0","start",[539,2235,2237],{"x":2236,"y":2232,"style":548},"258.2","1",[539,2239,2000],{"x":2240,"y":2232,"style":548},"394.7",[539,2242,2244],{"x":2243,"y":2232,"style":548},"531.2","3",[539,2246,512],{"x":2247,"y":2232,"style":548},"667.7",[532,2249],{"d":2250,"fill":535,"stroke":555,"style":2251},"M121.7,214.0 L258.2,71.5 L394.7,70.8 L531.2,70.8 L667.7,70.8","stroke-width:2.5px;stroke-linejoin:round;stroke-linecap:round",[2253,2254],"circle",{"cx":2231,"cy":2196,"r":2255,"fill":516,"stroke":555,"style":526},"4.0",[2253,2257],{"cx":2236,"cy":2258,"r":2255,"fill":516,"stroke":555,"style":526},"71.5",[2253,2260],{"cx":2240,"cy":2261,"r":2255,"fill":516,"stroke":555,"style":526},"70.8",[2253,2263],{"cx":2243,"cy":2261,"r":2255,"fill":516,"stroke":555,"style":526},[2253,2265],{"cx":2247,"cy":2261,"r":2255,"fill":516,"stroke":555,"style":526},[539,2267,513],{"x":2231,"y":2268,"style":2269},"204.0","font-size:10.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[539,2271,2273],{"x":2236,"y":2272,"style":2269},"61.5","1.125k",[539,2275,2277],{"x":2240,"y":2276,"style":2269},"60.8","1.13063k",[539,2279,2280],{"x":2243,"y":2276,"style":2269},"1.13065k",[539,2282,2280],{"x":2247,"y":2276,"style":2269},[33,2284,2286],{"id":2285},"step-6-compare-the-workbook-with-the-python-result","Step 6 — Compare the workbook with the Python result",[10,2288,2289],{},"When a workbook keeps its circular formulas for the people who use it, check the spreadsheet's converged values against the exact Python figures:",[38,2291,2293],{"className":79,"code":2292,"language":81,"meta":43,"style":43},"ws = load_workbook(\"recalc\u002Fcash_iter.xlsx\", data_only=True)[\"Cash\"]\nsheet_interest = [ws[f\"{c}4\"].value for c in \"BCDE\"]\npython_interest = [r[\"Interest\"] for r in rows]\nfor q, a, b in zip(\"1234\", sheet_interest, python_interest):\n    print(f\"Q{q}: sheet {a:,.4f}  python {b:,.4f}  diff {abs(a - b):.6f}\")\n",[45,2294,2295,2321,2356,2379,2398],{"__ignoreMap":43},[48,2296,2297,2300,2302,2304,2307,2309,2311,2313,2315,2317,2319],{"class":50,"line":51},[48,2298,2299],{"class":92},"ws ",[48,2301,128],{"class":88},[48,2303,753],{"class":92},[48,2305,2306],{"class":58},"\"recalc\u002Fcash_iter.xlsx\"",[48,2308,165],{"class":92},[48,2310,772],{"class":458},[48,2312,128],{"class":88},[48,2314,484],{"class":199},[48,2316,779],{"class":92},[48,2318,782],{"class":58},[48,2320,785],{"class":92},[48,2322,2323,2326,2328,2331,2333,2335,2337,2339,2341,2343,2346,2348,2350,2352,2354],{"class":50,"line":102},[48,2324,2325],{"class":92},"sheet_interest ",[48,2327,128],{"class":88},[48,2329,2330],{"class":92}," [ws[",[48,2332,270],{"class":88},[48,2334,767],{"class":58},[48,2336,277],{"class":276},[48,2338,280],{"class":92},[48,2340,283],{"class":276},[48,2342,354],{"class":58},[48,2344,2345],{"class":92},"].value ",[48,2347,671],{"class":88},[48,2349,301],{"class":92},[48,2351,304],{"class":88},[48,2353,307],{"class":58},[48,2355,785],{"class":92},[48,2357,2358,2361,2363,2366,2368,2370,2372,2374,2376],{"class":50,"line":109},[48,2359,2360],{"class":92},"python_interest ",[48,2362,128],{"class":88},[48,2364,2365],{"class":92}," [r[",[48,2367,1965],{"class":58},[48,2369,261],{"class":92},[48,2371,671],{"class":88},[48,2373,1331],{"class":92},[48,2375,304],{"class":88},[48,2377,2378],{"class":92}," rows]\n",[48,2380,2381,2383,2386,2388,2390,2392,2395],{"class":50,"line":122},[48,2382,671],{"class":88},[48,2384,2385],{"class":92}," q, a, b ",[48,2387,304],{"class":88},[48,2389,1916],{"class":199},[48,2391,793],{"class":92},[48,2393,2394],{"class":58},"\"1234\"",[48,2396,2397],{"class":92},", sheet_interest, python_interest):\n",[48,2399,2400,2402,2404,2406,2409,2411,2413,2415,2418,2420,2422,2425,2427,2430,2432,2435,2437,2439,2442,2444,2447,2450,2452,2455,2458,2460,2462],{"class":50,"line":134},[48,2401,790],{"class":199},[48,2403,793],{"class":92},[48,2405,270],{"class":88},[48,2407,2408],{"class":58},"\"Q",[48,2410,277],{"class":276},[48,2412,2130],{"class":92},[48,2414,283],{"class":276},[48,2416,2417],{"class":58},": sheet ",[48,2419,277],{"class":276},[48,2421,27],{"class":92},[48,2423,2424],{"class":88},":,.4f",[48,2426,283],{"class":276},[48,2428,2429],{"class":58},"  python ",[48,2431,277],{"class":276},[48,2433,2434],{"class":92},"b",[48,2436,2424],{"class":88},[48,2438,283],{"class":276},[48,2440,2441],{"class":58},"  diff ",[48,2443,277],{"class":276},[48,2445,2446],{"class":199},"abs",[48,2448,2449],{"class":92},"(a ",[48,2451,235],{"class":88},[48,2453,2454],{"class":92}," b)",[48,2456,2457],{"class":88},":.6f",[48,2459,283],{"class":276},[48,2461,767],{"class":58},[48,2463,467],{"class":92},[10,2465,2466,2467,2469],{},"The differences are below the ",[45,2468,853],{}," set in the file — the spreadsheet stops iterating once changes are smaller than that, so its answer is accurate to roughly that precision. A test that asserts the difference stays below a cent catches the day someone accidentally adds a second, divergent loop.",[33,2471,2473],{"id":2472},"common-pitfalls","Common pitfalls",[2475,2476,2477,2485,2491,2497,2503],"ul",{},[2478,2479,2480,2484],"li",{},[2481,2482,2483],"strong",{},"Turning iteration on to hide an error."," It silences accidental loops too; list every cycle first.",[2478,2486,2487,2490],{},[2481,2488,2489],{},"Expecting the setting to be per file in Excel."," The first workbook opened sets the mode for the session.",[2478,2492,2493,2496],{},[2481,2494,2495],{},"Too large a delta."," A delta of 1 on currency amounts leaves visible rounding; use 0.001 or smaller.",[2478,2498,2499,2502],{},[2481,2500,2501],{},"Divergent loops."," If a loop's gain is above 1 — a formula that multiplies the circular value by more than one — iteration never settles; rewrite the model.",[2478,2504,2505,2508],{},[2481,2506,2507],{},"Reading cached values from a script-built file."," openpyxl stores no results; recalculate first.",[33,2510,2512],{"id":2511},"conclusion","Conclusion",[10,2514,2515,2516,165,2519,925,2521,2523],{},"Deliberate circular references need iterative calculation, which openpyxl sets through ",[45,2517,2518],{},"wb.calculation.iterate",[45,2520,857],{},[45,2522,853],{},"; without it the loop cells show errors. Find every cycle from Python before switching iteration on, so accidental loops are not silenced. For reporting, solve the circularity in Python — by fixed-point iteration or, better, algebraically — and use the workbook's iterated values only as a cross-check.",[33,2525,2527],{"id":2526},"frequently-asked-questions","Frequently asked questions",[10,2529,2530,2533],{},[2481,2531,2532],{},"How do I enable iterative calculation with openpyxl?","\nSet wb.calculation.iterate = True and, optionally, wb.calculation.iterateCount (maximum iterations) and wb.calculation.iterateDelta (maximum change) before saving the workbook.",[10,2535,2536,2539],{},[2481,2537,2538],{},"Why do my circular formulas show errors after Python writes the file?","\nIterative calculation is off by default, so spreadsheet applications treat the loop as an error. Enable it in the workbook's calculation settings, or remove the circularity.",[10,2541,2542,2545],{},[2481,2543,2544],{},"How can I find circular references in an Excel file with Python?","\nBuild a graph from each formula cell to the cells its formula references, then search the graph for cycles with a depth-first search. Cells that lead back to themselves are part of a circular reference.",[10,2547,2548,2551],{},[2481,2549,2550],{},"How do I calculate interest on an average balance without a circular reference?","\nSolve for interest algebraically: with quarterly rate r, interest = r × (2 × opening + flow) \u002F (2 − r). The closing balance is then opening + flow + interest, with no loop needed.",[33,2553,2555],{"id":2554},"related","Related",[2475,2557,2558,2564,2571,2578],{},[2478,2559,2560,2561,2563],{},"Up one level: ",[27,2562,30],{"href":29}," — writing, auditing and recalculating formulas.",[2478,2565,2566,2570],{},[27,2567,2569],{"href":2568},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-formula-errors-in-a-workbook-with-python\u002F","Find Formula Errors in a Workbook with Python"," — locating error values after a recalculation.",[2478,2572,2573,2577],{},[27,2574,2576],{"href":2575},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-cash-flow-forecast-report\u002F","Build a Cash Flow Forecast Report"," — a model where this circularity appears.",[2478,2579,2580,2584],{},[27,2581,2583],{"href":2582},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fgoal-seek-in-python-instead-of-excel\u002F","Goal Seek in Python Instead of Excel"," — solving for an input numerically.",[2586,2587,2588],"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 .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s_b0D, html code.shiki .s_b0D{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#72F088;--shiki-dark-font-weight:bold}",{"title":43,"searchDepth":102,"depth":102,"links":2590},[2591,2592,2593,2594,2595,2596,2597,2598,2599,2600,2601],{"id":35,"depth":102,"text":36},{"id":75,"depth":102,"text":76},{"id":638,"depth":102,"text":639},{"id":861,"depth":102,"text":862},{"id":1056,"depth":102,"text":1057},{"id":1663,"depth":102,"text":1664},{"id":2285,"depth":102,"text":2286},{"id":2472,"depth":102,"text":2473},{"id":2511,"depth":102,"text":2512},{"id":2526,"depth":102,"text":2527},{"id":2554,"depth":102,"text":2555},"2026-10-02","Financial models often contain deliberate circular references — interest on an average cash balance, a bonus calculated on profit after the bonus. Detect cycles in a workbook from Python, enable Excel's iterative calculation with openpyxl (iterate, iterateCount, iterateDelta), see what happens without it, and solve the same circularity in Python by fixed-point iteration or algebra so reports do not depend on iteration settings.","md",[2606,2608,2610,2612],{"q":2532,"a":2607},"Set wb.calculation.iterate = True and, optionally, wb.calculation.iterateCount (maximum iterations) and wb.calculation.iterateDelta (maximum change) before saving the workbook.",{"q":2538,"a":2609},"Iterative calculation is off by default, so spreadsheet applications treat the loop as an error. Enable it in the workbook's calculation settings, or remove the circularity.",{"q":2544,"a":2611},"Build a graph from each formula cell to the cells its formula references, then search the graph for cycles with a depth-first search. Cells that lead back to themselves are part of a circular reference.",{"q":2550,"a":2613},"Solve for interest algebraically: with quarterly rate r, interest = r × (2 × opening + flow) \u002F (2 − r). The closing balance is then opening + flow + interest, with no loop needed.","handle circular references and iterative calculation in python",null,{"breadcrumb":2617},[2618,2620,2623],{"name":2619,"item":1825},"Home",{"name":2621,"item":2622},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":30,"item":29},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fhandle-circular-references-and-iterative-calculation-in-python",{"title":2626,"description":2627},"Circular References and Iterative Calculation from Python","Work with circular formulas from Python: find cycles in a workbook, turn on iterative calculation with openpyxl wb.calculation.iterate, iterateCount and iterateDelta, what LibreOffice and Excel show without it, and solving interest-on-average-balance in pandas.","handle-circular-references-and-iterative-calculation-in-python","getting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fhandle-circular-references-and-iterative-calculation-in-python\u002Findex",[2631],"excel-formulas","how-to","hjxsOOv3n_Q8kOvzOvDUmnoOenHiFdyiDvC8U5HB9S8",[2635,2639],{"title":2636,"path":2637,"stem":2638,"children":-1},"Find #REF!, #DIV\u002F0! and Other Formula Errors in a Workbook","\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-formula-errors-in-a-workbook-with-python","getting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-formula-errors-in-a-workbook-with-python\u002Findex",{"title":1660,"path":2640,"stem":2641,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fmap-formula-dependencies-in-a-workbook-with-python","getting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fmap-formula-dependencies-in-a-workbook-with-python\u002Findex",[2643,2645],{"path":2644,"title":2621},"\u002Fgetting-started-with-python-excel-automation",{"path":2646,"title":30},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python",[],[2649,2652,2656,2658,2661,2664],{"path":2650,"title":2651,"kind":2632},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fstore-formula-results-with-xlsxwriter","Store Formula Results with xlsxwriter So Every Viewer Shows Values",{"path":2653,"title":2654,"kind":2655},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-cells-with-formula-errors-using-conditional-formatting","Highlight Cells with Formula Errors Using Conditional Formatting","fix",{"path":2657,"title":2583,"kind":2632},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fgoal-seek-in-python-instead-of-excel",{"path":2659,"title":2660,"kind":2632},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcheck-that-totals-in-a-report-add-up","Check That Totals in a Report Add Up",{"path":2662,"title":2663,"kind":2632},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcall-excel-worksheet-functions-through-microsoft-graph","Call Excel Worksheet Functions Through Microsoft Graph",{"path":2665,"title":2666,"kind":2632},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frecalculate-excel-online-workbooks-with-graph-sessions","Recalculate Excel Online Workbooks with Graph Sessions",1790972075698]