[{"data":1,"prerenderedAt":2009},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-formula-errors-in-a-workbook-with-python":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-formula-errors-in-a-workbook-with-python":1975,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-formula-errors-in-a-workbook-with-python":1983,"children:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-formula-errors-in-a-workbook-with-python":1988,"related:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-formula-errors-in-a-workbook-with-python":1989},{"id":4,"title":5,"body":6,"dateModified":1941,"datePublished":1941,"description":1942,"extension":1943,"faq":1944,"keyword":1953,"kind":1954,"meta":1955,"navigation":395,"path":1963,"seo":1964,"slug":1967,"stem":1968,"topics":1969,"type":1973,"__hash__":1974},"docs\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-formula-errors-in-a-workbook-with-python\u002Findex.md","Find #REF!, #DIV\u002F0! and Other Formula Errors in a Workbook",{"type":7,"value":8,"toc":1927},"minimark",[9,35,318,323,357,364,368,470,483,487,510,752,766,903,907,917,959,962,1125,1141,1145,1159,1326,1339,1343,1346,1628,1636,1755,1759,1782,1786,1829,1833,1840,1844,1862,1866,1872,1878,1884,1890,1894,1923],[10,11,12,13,17,18,17,21,24,25,28,29,34],"p",{},"A workbook with a dozen broken formulas still opens, still has numbers in most cells, and reads into pandas without a complaint. That last part is the problem: pandas' Excel reader converts every error value — ",[14,15,16],"code",{},"#DIV\u002F0!",", ",[14,19,20],{},"#N\u002FA",[14,22,23],{},"#REF!"," — into ",[14,26,27],{},"NaN",", so a lookup that failed for half the rows looks exactly like data that was never filled in. A report job that reads, aggregates and emails such a file will send wrong totals with a straight face. This page, part of ",[30,31,33],"a",{"href":32},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002F","Working with Excel Formulas in Python",", shows how to find every error in a workbook with openpyxl, including the ones that have not been calculated yet.",[36,37,45,46,45,50,45,54,45,62,45,70,45,74,45,81,45,85,45,89,45,93,45,97,45,101,45,106,45,108,45,112,45,114,45,117,45,119,45,122,45,125,45,129,45,131,45,135,45,137,45,140,45,142,45,145,45,148,45,152,45,154,45,157,45,162,45,165,45,167,45,169,45,172,45,176,45,178,45,181,45,183,45,186,45,188,45,191,45,194,45,197,45,200,45,204,45,207,45,210,45,213,45,215,45,218,45,220,45,222,45,224,45,226,45,228,45,230,45,232,45,234,45,236,45,238,45,240,45,242,45,244,45,246,45,248,45,250,45,252,45,254,45,258,45,261,45,263,45,265,45,267,45,269,45,271,45,273,45,275,45,277,45,279,45,281,45,287,45,291,45,299,45,304,45,312],"svg",{"viewBox":38,"role":39,"ariaLabelledBy":40,"xmlns":43,"style":44},"24 0 712 192","img",[41,42],"fe-hidden-t","fe-hidden-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:712px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[47,48,49],"title",{"id":41},"The same cells in Excel and in a DataFrame",[51,52,53],"desc",{"id":42},"Left: the Sales sheet as a spreadsheet shows #DIV\u002F0! in the price column for South and #N\u002FA in the vs target column where the lookup failed. Right: the same range read with pandas shows NaN in both places, indistinguishable from empty cells.",[55,56],"rect",{"x":57,"y":58,"width":59,"height":60,"fill":61},"24","0","712","192","#ffffff",[55,63],{"x":64,"y":64,"width":65,"height":66,"rx":58,"fill":67,"stroke":68,"style":69},"40.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[55,71],{"x":72,"y":64,"width":73,"height":66,"rx":58,"fill":67,"stroke":68,"style":69},"72.0","63.6",[75,76,80],"text",{"x":77,"y":78,"style":79},"103.8","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[55,82],{"x":83,"y":64,"width":84,"height":66,"rx":58,"fill":67,"stroke":68,"style":69},"135.6","71.9",[75,86,88],{"x":87,"y":78,"style":79},"171.5","B",[55,90],{"x":91,"y":64,"width":92,"height":66,"rx":58,"fill":67,"stroke":68,"style":69},"207.5","78.7",[75,94,96],{"x":95,"y":78,"style":79},"246.8","C",[55,98],{"x":64,"y":99,"width":65,"height":100,"rx":58,"fill":67,"stroke":68,"style":69},"64.0","28.0",[75,102,105],{"x":103,"y":104,"style":79},"56.0","83.0","1",[55,107],{"x":72,"y":99,"width":73,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,109,111],{"x":77,"y":104,"style":110},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","region",[55,113],{"x":83,"y":99,"width":84,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,115,116],{"x":87,"y":104,"style":110},"price",[55,118],{"x":91,"y":99,"width":92,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,120,121],{"x":95,"y":104,"style":110},"vs target",[55,123],{"x":64,"y":124,"width":65,"height":100,"rx":58,"fill":67,"stroke":68,"style":69},"92.0",[75,126,128],{"x":103,"y":127,"style":79},"111.0","2",[55,130],{"x":72,"y":124,"width":73,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,132,134],{"x":77,"y":127,"style":133},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","North",[55,136],{"x":83,"y":124,"width":84,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,138,139],{"x":87,"y":127,"style":133},"100",[55,141],{"x":91,"y":124,"width":92,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,143,144],{"x":95,"y":127,"style":133},"1000",[55,146],{"x":64,"y":147,"width":65,"height":100,"rx":58,"fill":67,"stroke":68,"style":69},"120.0",[75,149,151],{"x":103,"y":150,"style":79},"139.0","3",[55,153],{"x":72,"y":147,"width":73,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,155,156],{"x":77,"y":150,"style":133},"South",[55,158],{"x":83,"y":147,"width":84,"height":100,"rx":58,"fill":159,"stroke":160,"style":161},"#fee8f2","var(--accent,#f43f8f)","stroke-width:1.5px",[75,163,16],{"x":87,"y":150,"style":164},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle",[55,166],{"x":91,"y":147,"width":92,"height":100,"rx":58,"fill":159,"stroke":160,"style":161},[75,168,20],{"x":95,"y":150,"style":164},[55,170],{"x":64,"y":171,"width":65,"height":100,"rx":58,"fill":67,"stroke":68,"style":69},"148.0",[75,173,175],{"x":103,"y":174,"style":79},"167.0","4",[55,177],{"x":72,"y":171,"width":73,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,179,180],{"x":77,"y":174,"style":133},"East",[55,182],{"x":83,"y":171,"width":84,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,184,185],{"x":87,"y":174,"style":133},"105.6",[55,187],{"x":91,"y":171,"width":92,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,189,190],{"x":95,"y":174,"style":133},"900",[55,192],{"x":193,"y":64,"width":65,"height":66,"rx":58,"fill":67,"stroke":68,"style":69},"487.5",[55,195],{"x":196,"y":64,"width":73,"height":66,"rx":58,"fill":67,"stroke":68,"style":69},"519.5",[75,198,80],{"x":199,"y":78,"style":79},"551.3",[55,201],{"x":202,"y":64,"width":203,"height":66,"rx":58,"fill":67,"stroke":68,"style":69},"583.1","58.1",[75,205,88],{"x":206,"y":78,"style":79},"612.2",[55,208],{"x":209,"y":64,"width":92,"height":66,"rx":58,"fill":67,"stroke":68,"style":69},"641.2",[75,211,96],{"x":212,"y":78,"style":79},"680.6",[55,214],{"x":193,"y":99,"width":65,"height":100,"rx":58,"fill":67,"stroke":68,"style":69},[75,216,105],{"x":217,"y":104,"style":79},"503.5",[55,219],{"x":196,"y":99,"width":73,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,221,111],{"x":199,"y":104,"style":110},[55,223],{"x":202,"y":99,"width":203,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,225,116],{"x":206,"y":104,"style":110},[55,227],{"x":209,"y":99,"width":92,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,229,121],{"x":212,"y":104,"style":110},[55,231],{"x":193,"y":124,"width":65,"height":100,"rx":58,"fill":67,"stroke":68,"style":69},[75,233,128],{"x":217,"y":127,"style":79},[55,235],{"x":196,"y":124,"width":73,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,237,134],{"x":199,"y":127,"style":133},[55,239],{"x":202,"y":124,"width":203,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,241,139],{"x":206,"y":127,"style":133},[55,243],{"x":209,"y":124,"width":92,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,245,144],{"x":212,"y":127,"style":133},[55,247],{"x":193,"y":147,"width":65,"height":100,"rx":58,"fill":67,"stroke":68,"style":69},[75,249,151],{"x":217,"y":150,"style":79},[55,251],{"x":196,"y":147,"width":73,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,253,156],{"x":199,"y":150,"style":133},[55,255],{"x":202,"y":147,"width":203,"height":100,"rx":58,"fill":256,"stroke":257,"style":161},"#fdefd8","var(--gold,#b4740a)",[75,259,27],{"x":206,"y":150,"style":260},"font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[55,262],{"x":209,"y":147,"width":92,"height":100,"rx":58,"fill":256,"stroke":257,"style":161},[75,264,27],{"x":212,"y":150,"style":260},[55,266],{"x":193,"y":171,"width":65,"height":100,"rx":58,"fill":67,"stroke":68,"style":69},[75,268,175],{"x":217,"y":174,"style":79},[55,270],{"x":196,"y":171,"width":73,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,272,180],{"x":199,"y":174,"style":133},[55,274],{"x":202,"y":171,"width":203,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,276,185],{"x":206,"y":174,"style":133},[55,278],{"x":209,"y":171,"width":92,"height":100,"rx":58,"fill":61,"stroke":68,"style":69},[75,280,190],{"x":212,"y":174,"style":133},[75,282,286],{"x":283,"y":284,"style":285},"163.1","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","in the workbook",[75,288,290],{"x":289,"y":284,"style":285},"603.8","in the DataFrame",[292,293],"line",{"x1":294,"y1":295,"x2":296,"y2":295,"stroke":297,"style":298},"296.2","108.0","473.5","var(--brand,#5b5cf0)","stroke-width:2px",[300,301],"polygon",{"points":302,"fill":303},"477.5,108.0 468.5,113.0 468.5,103.0","#5b5cf0",[55,305],{"x":306,"y":307,"width":308,"height":309,"rx":310,"fill":61,"stroke":68,"style":311},"322.3","75.0","129.0","22.0","11","stroke-width:1.2px",[75,313,317],{"x":314,"y":315,"style":316},"386.9","90.0","font-size:11.5px;font-weight:400;fill:var(--brand-strong,#4338ca);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","pd.read_excel()",[319,320,322],"h2",{"id":321},"prerequisites","Prerequisites",[324,325,330],"pre",{"className":326,"code":327,"language":328,"meta":329,"style":329},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pandas\n# LibreOffice, for recalculating files written by Python (Step 3)\n","bash","",[14,331,332,350],{"__ignoreMap":329},[333,334,336,340,344,347],"span",{"class":292,"line":335},1,[333,337,339],{"class":338},"sMTad","pip",[333,341,343],{"class":342},"srMev"," install",[333,345,346],{"class":342}," openpyxl",[333,348,349],{"class":342}," pandas\n",[333,351,353],{"class":292,"line":352},2,[333,354,356],{"class":355},"s-wDw","# LibreOffice, for recalculating files written by Python (Step 3)\n",[10,358,359,360,363],{},"The examples use ",[14,361,362],{},"report.xlsx",": a Sales sheet with a price formula that divides by a zero quantity and a lookup against a Targets sheet that is missing one region, a text-plus-number formula in H2, and a Summary sheet with one formula whose reference has been lost. The setup recalculated it with LibreOffice, so it contains cached results just like a file saved by Excel.",[319,365,367],{"id":366},"step-1-see-pandas-hide-the-errors","Step 1 — See pandas hide the errors",[324,369,373],{"className":370,"code":371,"language":372,"meta":329,"style":329},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nsales = pd.read_excel(\"report.xlsx\", sheet_name=\"Sales\")\nprint(sales[[\"region\", \"price\", \"vs target\"]])\nprint(sales[\"price\"].isna().sum(), \"missing prices\")\n","python",[14,374,375,391,397,426,452],{"__ignoreMap":329},[333,376,377,381,385,388],{"class":292,"line":335},[333,378,380],{"class":379},"s-kum","import",[333,382,384],{"class":383},"skGVy"," pandas ",[333,386,387],{"class":379},"as",[333,389,390],{"class":383}," pd\n",[333,392,393],{"class":292,"line":352},[333,394,396],{"emptyLinePlaceholder":395},true,"\n",[333,398,400,403,406,409,412,414,418,420,423],{"class":292,"line":399},3,[333,401,402],{"class":383},"sales ",[333,404,405],{"class":379},"=",[333,407,408],{"class":383}," pd.read_excel(",[333,410,411],{"class":342},"\"report.xlsx\"",[333,413,17],{"class":383},[333,415,417],{"class":416},"sa561","sheet_name",[333,419,405],{"class":379},[333,421,422],{"class":342},"\"Sales\"",[333,424,425],{"class":383},")\n",[333,427,429,433,436,439,441,444,446,449],{"class":292,"line":428},4,[333,430,432],{"class":431},"sP0c6","print",[333,434,435],{"class":383},"(sales[[",[333,437,438],{"class":342},"\"region\"",[333,440,17],{"class":383},[333,442,443],{"class":342},"\"price\"",[333,445,17],{"class":383},[333,447,448],{"class":342},"\"vs target\"",[333,450,451],{"class":383},"]])\n",[333,453,455,457,460,462,465,468],{"class":292,"line":454},5,[333,456,432],{"class":431},[333,458,459],{"class":383},"(sales[",[333,461,443],{"class":342},[333,463,464],{"class":383},"].isna().sum(), ",[333,466,467],{"class":342},"\"missing prices\"",[333,469,425],{"class":383},[10,471,472,473,475,476,479,480,482],{},"The South row shows ",[14,474,27],{}," for both the price and the target comparison. The DataFrame gives no hint that one of those is a division by zero and the other a failed lookup, and a ",[14,477,478],{},"sales[\"price\"].mean()"," would silently skip the row. pandas does this on purpose — its reader maps error cells to ",[14,481,27],{}," — so no reading option brings the error codes back. You need openpyxl.",[319,484,486],{"id":485},"step-2-scan-cached-values-for-error-cells","Step 2 — Scan cached values for error cells",[10,488,489,490,493,494,497,498,501,502,505,506,509],{},"When a workbook is saved by a spreadsheet application, each formula cell stores its last result alongside the formula. Open the file with ",[14,491,492],{},"data_only=True"," and error results appear as strings like ",[14,495,496],{},"'#DIV\u002F0!'",", with the cell's ",[14,499,500],{},"data_type"," set to ",[14,503,504],{},"'e'",". Open it a second time without ",[14,507,508],{},"data_only"," to get the formulas that produced them:",[324,511,513],{"className":370,"code":512,"language":372,"meta":329,"style":329},"from openpyxl import load_workbook\n\ndef find_errors(path):\n    values = load_workbook(path, data_only=True)\n    formulas = load_workbook(path)\n    found = []\n    for ws in values.worksheets:\n        for row in ws.iter_rows():\n            for cell in row:\n                if cell.data_type == \"e\":\n                    found.append({\"sheet\": ws.title, \"cell\": cell.coordinate, \"error\": cell.value,\n                                  \"formula\": formulas[ws.title][cell.coordinate].value})\n    return pd.DataFrame(found, columns=[\"sheet\", \"cell\", \"error\", \"formula\"])\n\nerrors = find_errors(\"report.xlsx\")\nprint(errors.to_string(index=False))\n",[14,514,515,528,532,544,563,573,584,599,613,627,645,669,678,713,718,733],{"__ignoreMap":329},[333,516,517,520,523,525],{"class":292,"line":335},[333,518,519],{"class":379},"from",[333,521,522],{"class":383}," openpyxl ",[333,524,380],{"class":379},[333,526,527],{"class":383}," load_workbook\n",[333,529,530],{"class":292,"line":352},[333,531,396],{"emptyLinePlaceholder":395},[333,533,534,537,541],{"class":292,"line":399},[333,535,536],{"class":379},"def",[333,538,540],{"class":539},"s_Opv"," find_errors",[333,542,543],{"class":383},"(path):\n",[333,545,546,549,551,554,556,558,561],{"class":292,"line":428},[333,547,548],{"class":383},"    values ",[333,550,405],{"class":379},[333,552,553],{"class":383}," load_workbook(path, ",[333,555,508],{"class":416},[333,557,405],{"class":379},[333,559,560],{"class":431},"True",[333,562,425],{"class":383},[333,564,565,568,570],{"class":292,"line":454},[333,566,567],{"class":383},"    formulas ",[333,569,405],{"class":379},[333,571,572],{"class":383}," load_workbook(path)\n",[333,574,576,579,581],{"class":292,"line":575},6,[333,577,578],{"class":383},"    found ",[333,580,405],{"class":379},[333,582,583],{"class":383}," []\n",[333,585,587,590,593,596],{"class":292,"line":586},7,[333,588,589],{"class":379},"    for",[333,591,592],{"class":383}," ws ",[333,594,595],{"class":379},"in",[333,597,598],{"class":383}," values.worksheets:\n",[333,600,602,605,608,610],{"class":292,"line":601},8,[333,603,604],{"class":379},"        for",[333,606,607],{"class":383}," row ",[333,609,595],{"class":379},[333,611,612],{"class":383}," ws.iter_rows():\n",[333,614,616,619,622,624],{"class":292,"line":615},9,[333,617,618],{"class":379},"            for",[333,620,621],{"class":383}," cell ",[333,623,595],{"class":379},[333,625,626],{"class":383}," row:\n",[333,628,630,633,636,639,642],{"class":292,"line":629},10,[333,631,632],{"class":379},"                if",[333,634,635],{"class":383}," cell.data_type ",[333,637,638],{"class":379},"==",[333,640,641],{"class":342}," \"e\"",[333,643,644],{"class":383},":\n",[333,646,648,651,654,657,660,663,666],{"class":292,"line":647},11,[333,649,650],{"class":383},"                    found.append({",[333,652,653],{"class":342},"\"sheet\"",[333,655,656],{"class":383},": ws.title, ",[333,658,659],{"class":342},"\"cell\"",[333,661,662],{"class":383},": cell.coordinate, ",[333,664,665],{"class":342},"\"error\"",[333,667,668],{"class":383},": cell.value,\n",[333,670,672,675],{"class":292,"line":671},12,[333,673,674],{"class":342},"                                  \"formula\"",[333,676,677],{"class":383},": formulas[ws.title][cell.coordinate].value})\n",[333,679,681,684,687,690,692,695,697,699,701,703,705,707,710],{"class":292,"line":680},13,[333,682,683],{"class":379},"    return",[333,685,686],{"class":383}," pd.DataFrame(found, ",[333,688,689],{"class":416},"columns",[333,691,405],{"class":379},[333,693,694],{"class":383},"[",[333,696,653],{"class":342},[333,698,17],{"class":383},[333,700,659],{"class":342},[333,702,17],{"class":383},[333,704,665],{"class":342},[333,706,17],{"class":383},[333,708,709],{"class":342},"\"formula\"",[333,711,712],{"class":383},"])\n",[333,714,716],{"class":292,"line":715},14,[333,717,396],{"emptyLinePlaceholder":395},[333,719,721,724,726,729,731],{"class":292,"line":720},15,[333,722,723],{"class":383},"errors ",[333,725,405],{"class":379},[333,727,728],{"class":383}," find_errors(",[333,730,411],{"class":342},[333,732,425],{"class":383},[333,734,736,738,741,744,746,749],{"class":292,"line":735},16,[333,737,432],{"class":431},[333,739,740],{"class":383},"(errors.to_string(",[333,742,743],{"class":416},"index",[333,745,405],{"class":379},[333,747,748],{"class":431},"False",[333,750,751],{"class":383},"))\n",[10,753,754,755,758,759,762,763,765],{},"Each row names the sheet, the cell, the error and the formula behind it, which is usually enough to see the cause at a glance: ",[14,756,757],{},"=C3\u002FB3"," with a zero in B3, a ",[14,760,761],{},"VLOOKUP"," for a region the Targets sheet does not have, text added to a number. Checking ",[14,764,500],{}," rather than matching strings means a cell that genuinely contains the text \"#N\u002FA\" — typed by a person as a placeholder — is not reported as an error.",[36,767,45,773,45,776,45,779,45,783,45,790,45,801,45,807,45,811,45,816,45,821,45,824,45,828,45,835,45,840,45,844,45,847,45,851,45,854,45,857,45,864,45,869,45,872,45,875,45,879,45,882,45,885,45,892,45,897,45,900],{"viewBox":768,"role":39,"ariaLabelledBy":769,"xmlns":43,"style":772},"4 0 752 142",[770,771],"fe-scan-t","fe-scan-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[47,774,775],{"id":770},"Scanning a workbook for formula errors",[51,777,778],{"id":771},"Four steps. Recalculate the workbook if it has no cached values. Load it twice with openpyxl, once with data_only for results and once for formulas. Visit every cell and keep those whose data_type is e. Report sheet, cell, error code and formula, then fail the job if anything was found.",[55,780],{"x":175,"y":58,"width":781,"height":782,"fill":61},"752","142",[55,784],{"x":785,"y":786,"width":787,"height":788,"rx":789,"fill":67,"stroke":68,"style":298},"20.0","16.0","153.0","110.5","12",[791,792,794],"g",{"transform":793},"translate(84.5,29.0) scale(1.000)",[795,796],"path",{"d":797,"fill":798,"stroke":799,"style":800},"M20 11a8 8 0 1 0-2.3 5.7 M20 4v7h-7","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[75,802,806],{"x":803,"y":804,"style":805},"96.5","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Recalculate if",[75,808,810],{"x":803,"y":809,"style":805},"93.0","needed",[75,812,815],{"x":803,"y":813,"style":814},"112.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","files written by Python",[292,817],{"x1":818,"y1":819,"x2":820,"y2":819,"stroke":297,"style":298},"178.0","71.2","200.0",[300,822],{"points":823,"fill":303},"204.0,71.2 195.0,76.2 195.0,66.2",[55,825],{"x":826,"y":786,"width":787,"height":788,"rx":789,"fill":827,"stroke":297,"style":298},"209.0","#f0f4ff",[791,829,831],{"transform":830},"translate(273.5,29.0) scale(1.000)",[795,832],{"d":833,"fill":798,"stroke":834,"style":800},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6","var(--brand-strong,#4338ca)",[75,836,839],{"x":837,"y":804,"style":838},"285.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Load twice",[75,841,843],{"x":837,"y":842,"style":814},"95.0","data_only=True and",[75,845,846],{"x":837,"y":813,"style":814},"formulas",[292,848],{"x1":849,"y1":819,"x2":850,"y2":819,"stroke":297,"style":298},"367.0","389.0",[300,852],{"points":853,"fill":303},"393.0,71.2 384.0,76.2 384.0,66.2",[55,855],{"x":856,"y":786,"width":787,"height":788,"rx":789,"fill":256,"stroke":257,"style":298},"398.0",[791,858,860],{"transform":859},"translate(462.5,29.0) scale(1.000)",[795,861],{"d":862,"fill":798,"stroke":863,"style":800},"M3 5h18l-7 8v6l-4 2v-8z","var(--gold-ink,#7a4e06)",[75,865,868],{"x":866,"y":804,"style":867},"474.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Keep data_type",[75,870,871],{"x":866,"y":809,"style":867},"\"e\"",[75,873,874],{"x":866,"y":813,"style":814},"error results only",[292,876],{"x1":877,"y1":819,"x2":878,"y2":819,"stroke":297,"style":298},"556.0","578.0",[300,880],{"points":881,"fill":303},"582.0,71.2 573.0,76.2 573.0,66.2",[55,883],{"x":884,"y":786,"width":787,"height":788,"rx":789,"fill":159,"stroke":160,"style":298},"587.0",[791,886,888],{"transform":887},"translate(651.5,29.0) scale(1.000)",[795,889],{"d":890,"fill":798,"stroke":891,"style":800},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--accent-ink,#be185d)",[75,893,896],{"x":894,"y":804,"style":895},"663.5","font-size:13.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Report or fail",[75,898,899],{"x":894,"y":842,"style":814},"sheet, cell, error,",[75,901,902],{"x":894,"y":813,"style":814},"formula",[319,904,906],{"id":905},"step-3-recalculate-files-that-have-no-cached-values","Step 3 — Recalculate files that have no cached values",[10,908,909,910,912,913,916],{},"Workbooks written by openpyxl or pandas contain formulas but no results — nothing has calculated them yet. Opened with ",[14,911,492],{},", every formula cell reads as ",[14,914,915],{},"None",", and the scan finds nothing:",[324,918,920],{"className":370,"code":919,"language":372,"meta":329,"style":329},"print(load_workbook(\"report_raw.xlsx\", data_only=True)[\"Sales\"][\"D3\"].value)\n# None — never calculated\n",[14,921,922,954],{"__ignoreMap":329},[333,923,924,926,929,932,934,936,938,940,943,945,948,951],{"class":292,"line":335},[333,925,432],{"class":431},[333,927,928],{"class":383},"(load_workbook(",[333,930,931],{"class":342},"\"report_raw.xlsx\"",[333,933,17],{"class":383},[333,935,508],{"class":416},[333,937,405],{"class":379},[333,939,560],{"class":431},[333,941,942],{"class":383},")[",[333,944,422],{"class":342},[333,946,947],{"class":383},"][",[333,949,950],{"class":342},"\"D3\"",[333,952,953],{"class":383},"].value)\n",[333,955,956],{"class":292,"line":352},[333,957,958],{"class":355},"# None — never calculated\n",[10,960,961],{},"Recalculate before scanning. On a server without Excel, LibreOffice in headless mode opens the file, calculates it and saves a copy with cached results:",[324,963,965],{"className":370,"code":964,"language":372,"meta":329,"style":329},"import subprocess\nfrom pathlib import Path\n\ndef recalculated(path, outdir=\"recalc\"):\n    subprocess.run([\"soffice\", \"--headless\", \"--convert-to\", \"xlsx\", \"--outdir\", outdir, str(path)],\n                   check=True, capture_output=True, timeout=120)\n    return Path(outdir) \u002F Path(path).name\n\nprint(find_errors(recalculated(\"report_raw.xlsx\"))[[\"sheet\", \"cell\", \"error\"]].to_string(index=False))\n",[14,966,967,974,986,990,1008,1045,1075,1088,1092],{"__ignoreMap":329},[333,968,969,971],{"class":292,"line":335},[333,970,380],{"class":379},[333,972,973],{"class":383}," subprocess\n",[333,975,976,978,981,983],{"class":292,"line":352},[333,977,519],{"class":379},[333,979,980],{"class":383}," pathlib ",[333,982,380],{"class":379},[333,984,985],{"class":383}," Path\n",[333,987,988],{"class":292,"line":399},[333,989,396],{"emptyLinePlaceholder":395},[333,991,992,994,997,1000,1002,1005],{"class":292,"line":428},[333,993,536],{"class":379},[333,995,996],{"class":539}," recalculated",[333,998,999],{"class":383},"(path, outdir",[333,1001,405],{"class":379},[333,1003,1004],{"class":342},"\"recalc\"",[333,1006,1007],{"class":383},"):\n",[333,1009,1010,1013,1016,1018,1021,1023,1026,1028,1031,1033,1036,1039,1042],{"class":292,"line":454},[333,1011,1012],{"class":383},"    subprocess.run([",[333,1014,1015],{"class":342},"\"soffice\"",[333,1017,17],{"class":383},[333,1019,1020],{"class":342},"\"--headless\"",[333,1022,17],{"class":383},[333,1024,1025],{"class":342},"\"--convert-to\"",[333,1027,17],{"class":383},[333,1029,1030],{"class":342},"\"xlsx\"",[333,1032,17],{"class":383},[333,1034,1035],{"class":342},"\"--outdir\"",[333,1037,1038],{"class":383},", outdir, ",[333,1040,1041],{"class":431},"str",[333,1043,1044],{"class":383},"(path)],\n",[333,1046,1047,1050,1052,1054,1056,1059,1061,1063,1065,1068,1070,1073],{"class":292,"line":575},[333,1048,1049],{"class":416},"                   check",[333,1051,405],{"class":379},[333,1053,560],{"class":431},[333,1055,17],{"class":383},[333,1057,1058],{"class":416},"capture_output",[333,1060,405],{"class":379},[333,1062,560],{"class":431},[333,1064,17],{"class":383},[333,1066,1067],{"class":416},"timeout",[333,1069,405],{"class":379},[333,1071,1072],{"class":431},"120",[333,1074,425],{"class":383},[333,1076,1077,1079,1082,1085],{"class":292,"line":586},[333,1078,683],{"class":379},[333,1080,1081],{"class":383}," Path(outdir) ",[333,1083,1084],{"class":379},"\u002F",[333,1086,1087],{"class":383}," Path(path).name\n",[333,1089,1090],{"class":292,"line":601},[333,1091,396],{"emptyLinePlaceholder":395},[333,1093,1094,1096,1099,1101,1104,1106,1108,1110,1112,1114,1117,1119,1121,1123],{"class":292,"line":615},[333,1095,432],{"class":431},[333,1097,1098],{"class":383},"(find_errors(recalculated(",[333,1100,931],{"class":342},[333,1102,1103],{"class":383},"))[[",[333,1105,653],{"class":342},[333,1107,17],{"class":383},[333,1109,659],{"class":342},[333,1111,17],{"class":383},[333,1113,665],{"class":342},[333,1115,1116],{"class":383},"]].to_string(",[333,1118,743],{"class":416},[333,1120,405],{"class":379},[333,1122,748],{"class":431},[333,1124,751],{"class":383},[10,1126,1127,1128,1131,1132,1135,1136,1140],{},"Spreadsheet applications do not all report every failure with the same code — LibreOffice, for instance, exports some errors that Excel would show as ",[14,1129,1130],{},"#NUM!"," as ",[14,1133,1134],{},"#VALUE!"," — so treat the code as a hint and the formula as the evidence. ",[30,1137,1139],{"href":1138},"\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"," compares the ways to get cached values.",[319,1142,1144],{"id":1143},"step-4-find-ref-in-formula-text","Step 4 — Find #REF! in formula text",[10,1146,1147,1148,1150,1151,1154,1155,1158],{},"One class of error is visible without any calculation. When a sheet, row or column that a formula refers to is deleted, Excel rewrites the reference inside the formula as ",[14,1149,23],{}," — ",[14,1152,1153],{},"=SUM(Old!B2:B9)"," becomes ",[14,1156,1157],{},"=SUM(#REF!)",". The broken reference is then part of the formula text in the saved file, so a plain text search finds it:",[324,1160,1162],{"className":370,"code":1161,"language":372,"meta":329,"style":329},"def find_broken_refs(path):\n    wb = load_workbook(path)\n    hits = []\n    for ws in wb.worksheets:\n        for row in ws.iter_rows():\n            for cell in row:\n                if isinstance(cell.value, str) and cell.value.startswith(\"=\") and \"#REF!\" in cell.value.upper():\n                    hits.append(f\"{ws.title}!{cell.coordinate}: {cell.value}\")\n    return hits\n\nprint(find_broken_refs(\"report_raw.xlsx\"))\n",[14,1163,1164,1173,1182,1191,1202,1212,1222,1259,1304,1311,1315],{"__ignoreMap":329},[333,1165,1166,1168,1171],{"class":292,"line":335},[333,1167,536],{"class":379},[333,1169,1170],{"class":539}," find_broken_refs",[333,1172,543],{"class":383},[333,1174,1175,1178,1180],{"class":292,"line":352},[333,1176,1177],{"class":383},"    wb ",[333,1179,405],{"class":379},[333,1181,572],{"class":383},[333,1183,1184,1187,1189],{"class":292,"line":399},[333,1185,1186],{"class":383},"    hits ",[333,1188,405],{"class":379},[333,1190,583],{"class":383},[333,1192,1193,1195,1197,1199],{"class":292,"line":428},[333,1194,589],{"class":379},[333,1196,592],{"class":383},[333,1198,595],{"class":379},[333,1200,1201],{"class":383}," wb.worksheets:\n",[333,1203,1204,1206,1208,1210],{"class":292,"line":454},[333,1205,604],{"class":379},[333,1207,607],{"class":383},[333,1209,595],{"class":379},[333,1211,612],{"class":383},[333,1213,1214,1216,1218,1220],{"class":292,"line":575},[333,1215,618],{"class":379},[333,1217,621],{"class":383},[333,1219,595],{"class":379},[333,1221,626],{"class":383},[333,1223,1224,1226,1229,1232,1234,1237,1240,1243,1246,1248,1250,1253,1256],{"class":292,"line":586},[333,1225,632],{"class":379},[333,1227,1228],{"class":431}," isinstance",[333,1230,1231],{"class":383},"(cell.value, ",[333,1233,1041],{"class":431},[333,1235,1236],{"class":383},") ",[333,1238,1239],{"class":379},"and",[333,1241,1242],{"class":383}," cell.value.startswith(",[333,1244,1245],{"class":342},"\"=\"",[333,1247,1236],{"class":383},[333,1249,1239],{"class":379},[333,1251,1252],{"class":342}," \"#REF!\"",[333,1254,1255],{"class":379}," in",[333,1257,1258],{"class":383}," cell.value.upper():\n",[333,1260,1261,1264,1267,1270,1274,1277,1280,1283,1285,1288,1290,1293,1295,1298,1300,1302],{"class":292,"line":601},[333,1262,1263],{"class":383},"                    hits.append(",[333,1265,1266],{"class":379},"f",[333,1268,1269],{"class":342},"\"",[333,1271,1273],{"class":1272},"sSjpA","{",[333,1275,1276],{"class":383},"ws.title",[333,1278,1279],{"class":1272},"}",[333,1281,1282],{"class":342},"!",[333,1284,1273],{"class":1272},[333,1286,1287],{"class":383},"cell.coordinate",[333,1289,1279],{"class":1272},[333,1291,1292],{"class":342},": ",[333,1294,1273],{"class":1272},[333,1296,1297],{"class":383},"cell.value",[333,1299,1279],{"class":1272},[333,1301,1269],{"class":342},[333,1303,425],{"class":383},[333,1305,1306,1308],{"class":292,"line":615},[333,1307,683],{"class":379},[333,1309,1310],{"class":383}," hits\n",[333,1312,1313],{"class":292,"line":629},[333,1314,396],{"emptyLinePlaceholder":395},[333,1316,1317,1319,1322,1324],{"class":292,"line":647},[333,1318,432],{"class":431},[333,1320,1321],{"class":383},"(find_broken_refs(",[333,1323,931],{"class":342},[333,1325,751],{"class":383},[10,1327,1328,1329,1333,1334,1338],{},"This check is cheap and works on any file, recalculated or not, which makes it a good first gate for templates and workbooks received from other people. The case-insensitive comparison matters: some applications rewrite the token in lower case when they save. References to ",[1330,1331,1332],"em",{},"other workbooks"," are a related source of silent breakage; ",[30,1335,1337],{"href":1336},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-external-links-and-broken-references-in-a-workbook\u002F","Find External Links and Broken References in a Workbook with Python"," covers them.",[319,1340,1342],{"id":1341},"step-5-summarise-and-fail-the-job","Step 5 — Summarise and fail the job",[10,1344,1345],{},"For a report job, the useful output is a short summary in the log and a hard stop when anything is wrong:",[324,1347,1349],{"className":370,"code":1348,"language":372,"meta":329,"style":329},"def assert_no_errors(path):\n    errors = find_errors(path)\n    broken = find_broken_refs(path)\n    if errors.empty and not broken:\n        return\n    summary = errors.groupby([\"sheet\", \"error\"]).size().rename(\"cells\").reset_index()\n    message = [f\"{len(errors)} error cell(s) and {len(broken)} broken reference(s) in {path}\"]\n    message += [f\"  {r.sheet}: {r.cells} x {r.error}\" for r in summary.itertuples()]\n    message += [f\"  {b}\" for b in broken[:10]]\n    raise ValueError(\"\\n\".join(message))\n\ntry:\n    assert_no_errors(\"report.xlsx\")\nexcept ValueError as exc:\n    print(exc)\n",[14,1350,1351,1360,1370,1380,1396,1401,1426,1476,1529,1566,1587,1591,1598,1607,1620],{"__ignoreMap":329},[333,1352,1353,1355,1358],{"class":292,"line":335},[333,1354,536],{"class":379},[333,1356,1357],{"class":539}," assert_no_errors",[333,1359,543],{"class":383},[333,1361,1362,1365,1367],{"class":292,"line":352},[333,1363,1364],{"class":383},"    errors ",[333,1366,405],{"class":379},[333,1368,1369],{"class":383}," find_errors(path)\n",[333,1371,1372,1375,1377],{"class":292,"line":399},[333,1373,1374],{"class":383},"    broken ",[333,1376,405],{"class":379},[333,1378,1379],{"class":383}," find_broken_refs(path)\n",[333,1381,1382,1385,1388,1390,1393],{"class":292,"line":428},[333,1383,1384],{"class":379},"    if",[333,1386,1387],{"class":383}," errors.empty ",[333,1389,1239],{"class":379},[333,1391,1392],{"class":379}," not",[333,1394,1395],{"class":383}," broken:\n",[333,1397,1398],{"class":292,"line":454},[333,1399,1400],{"class":379},"        return\n",[333,1402,1403,1406,1408,1411,1413,1415,1417,1420,1423],{"class":292,"line":575},[333,1404,1405],{"class":383},"    summary ",[333,1407,405],{"class":379},[333,1409,1410],{"class":383}," errors.groupby([",[333,1412,653],{"class":342},[333,1414,17],{"class":383},[333,1416,665],{"class":342},[333,1418,1419],{"class":383},"]).size().rename(",[333,1421,1422],{"class":342},"\"cells\"",[333,1424,1425],{"class":383},").reset_index()\n",[333,1427,1428,1431,1433,1436,1438,1440,1442,1445,1448,1450,1453,1455,1457,1460,1462,1465,1467,1469,1471,1473],{"class":292,"line":586},[333,1429,1430],{"class":383},"    message ",[333,1432,405],{"class":379},[333,1434,1435],{"class":383}," [",[333,1437,1266],{"class":379},[333,1439,1269],{"class":342},[333,1441,1273],{"class":1272},[333,1443,1444],{"class":431},"len",[333,1446,1447],{"class":383},"(errors)",[333,1449,1279],{"class":1272},[333,1451,1452],{"class":342}," error cell(s) and ",[333,1454,1273],{"class":1272},[333,1456,1444],{"class":431},[333,1458,1459],{"class":383},"(broken)",[333,1461,1279],{"class":1272},[333,1463,1464],{"class":342}," broken reference(s) in ",[333,1466,1273],{"class":1272},[333,1468,795],{"class":383},[333,1470,1279],{"class":1272},[333,1472,1269],{"class":342},[333,1474,1475],{"class":383},"]\n",[333,1477,1478,1480,1483,1485,1487,1490,1492,1495,1497,1499,1501,1504,1506,1509,1511,1514,1516,1518,1521,1524,1526],{"class":292,"line":601},[333,1479,1430],{"class":383},[333,1481,1482],{"class":379},"+=",[333,1484,1435],{"class":383},[333,1486,1266],{"class":379},[333,1488,1489],{"class":342},"\"  ",[333,1491,1273],{"class":1272},[333,1493,1494],{"class":383},"r.sheet",[333,1496,1279],{"class":1272},[333,1498,1292],{"class":342},[333,1500,1273],{"class":1272},[333,1502,1503],{"class":383},"r.cells",[333,1505,1279],{"class":1272},[333,1507,1508],{"class":342}," x ",[333,1510,1273],{"class":1272},[333,1512,1513],{"class":383},"r.error",[333,1515,1279],{"class":1272},[333,1517,1269],{"class":342},[333,1519,1520],{"class":379}," for",[333,1522,1523],{"class":383}," r ",[333,1525,595],{"class":379},[333,1527,1528],{"class":383}," summary.itertuples()]\n",[333,1530,1531,1533,1535,1537,1539,1541,1543,1546,1548,1550,1552,1555,1557,1560,1563],{"class":292,"line":615},[333,1532,1430],{"class":383},[333,1534,1482],{"class":379},[333,1536,1435],{"class":383},[333,1538,1266],{"class":379},[333,1540,1489],{"class":342},[333,1542,1273],{"class":1272},[333,1544,1545],{"class":383},"b",[333,1547,1279],{"class":1272},[333,1549,1269],{"class":342},[333,1551,1520],{"class":379},[333,1553,1554],{"class":383}," b ",[333,1556,595],{"class":379},[333,1558,1559],{"class":383}," broken[:",[333,1561,1562],{"class":431},"10",[333,1564,1565],{"class":383},"]]\n",[333,1567,1568,1571,1574,1577,1579,1582,1584],{"class":292,"line":629},[333,1569,1570],{"class":379},"    raise",[333,1572,1573],{"class":431}," ValueError",[333,1575,1576],{"class":383},"(",[333,1578,1269],{"class":342},[333,1580,1581],{"class":1272},"\\n",[333,1583,1269],{"class":342},[333,1585,1586],{"class":383},".join(message))\n",[333,1588,1589],{"class":292,"line":647},[333,1590,396],{"emptyLinePlaceholder":395},[333,1592,1593,1596],{"class":292,"line":671},[333,1594,1595],{"class":379},"try",[333,1597,644],{"class":383},[333,1599,1600,1603,1605],{"class":292,"line":680},[333,1601,1602],{"class":383},"    assert_no_errors(",[333,1604,411],{"class":342},[333,1606,425],{"class":383},[333,1608,1609,1612,1614,1617],{"class":292,"line":715},[333,1610,1611],{"class":379},"except",[333,1613,1573],{"class":431},[333,1615,1616],{"class":379}," as",[333,1618,1619],{"class":383}," exc:\n",[333,1621,1622,1625],{"class":292,"line":720},[333,1623,1624],{"class":431},"    print",[333,1626,1627],{"class":383},"(exc)\n",[10,1629,1630,1631,1635],{},"Counting by sheet and error type keeps the message readable when a single bad lookup has failed in five hundred rows. Put the check at the end of the job, after the workbook is written and recalculated and before it is emailed or published; ",[30,1632,1634],{"href":1633},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fvalidate-an-excel-report-before-sending-it\u002F","Validate an Excel Report Before Sending It"," shows it alongside the other pre-send checks.",[36,1637,45,1642,45,1645,45,1648,45,1651,45,1654,45,1657,45,1663,45,1666,45,1670,45,1673,45,1677,45,1679,45,1683,45,1685,45,1688,45,1690,45,1693,45,1695,45,1698,45,1700,45,1703,45,1706,45,1709,45,1711,45,1715,45,1717,45,1720,45,1723,45,1727,45,1729,45,1733,45,1735,45,1738,45,1741,45,1744,45,1746,45,1750,45,1752],{"viewBox":1638,"role":39,"ariaLabelledBy":1639,"xmlns":43,"style":772},"4 0 752 242",[1640,1641],"fe-causes-t","fe-causes-d",[47,1643,1644],{"id":1640},"Common error values and what usually causes them",[51,1646,1647],{"id":1641},"A table of error values. #DIV\u002F0! comes from dividing by zero or an empty cell. #N\u002FA comes from a lookup that found no match. #VALUE! comes from arithmetic on text or a wrong argument type. #NAME? comes from an unknown function or name. #REF! comes from a reference to a deleted sheet, row or column. Each has a typical fix.",[55,1649],{"x":175,"y":58,"width":781,"height":1650,"fill":61},"242",[55,1652],{"x":785,"y":786,"width":1653,"height":64,"rx":58,"fill":61,"stroke":68,"style":69},"85.6",[55,1655],{"x":185,"y":786,"width":1656,"height":64,"rx":58,"fill":827,"stroke":297,"style":311},"317.2",[75,1658,1662],{"x":1659,"y":1660,"style":1661},"264.2","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Usual cause",[55,1664],{"x":1665,"y":786,"width":1656,"height":64,"rx":58,"fill":827,"stroke":297,"style":311},"422.8",[75,1667,1669],{"x":1668,"y":1660,"style":1661},"581.4","Typical fix",[55,1671],{"x":785,"y":103,"width":1653,"height":1672,"rx":58,"fill":67,"stroke":68,"style":69},"34.0",[75,1674,16],{"x":65,"y":1675,"style":1676},"78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start",[55,1678],{"x":185,"y":103,"width":1656,"height":1672,"rx":58,"fill":61,"stroke":68,"style":69},[75,1680,1682],{"x":1659,"y":1681,"style":814},"77.0","divide by zero or blank",[55,1684],{"x":1665,"y":103,"width":1656,"height":1672,"rx":58,"fill":61,"stroke":68,"style":69},[75,1686,1687],{"x":1668,"y":1681,"style":814},"guard the divisor, or IFERROR by design",[55,1689],{"x":785,"y":315,"width":1653,"height":1672,"rx":58,"fill":67,"stroke":68,"style":69},[75,1691,20],{"x":65,"y":1692,"style":1676},"112.0",[55,1694],{"x":185,"y":315,"width":1656,"height":1672,"rx":58,"fill":61,"stroke":68,"style":69},[75,1696,1697],{"x":1659,"y":127,"style":814},"lookup found no match",[55,1699],{"x":1665,"y":315,"width":1656,"height":1672,"rx":58,"fill":61,"stroke":68,"style":69},[75,1701,1702],{"x":1668,"y":127,"style":814},"add the key or handle the gap",[55,1704],{"x":785,"y":1705,"width":1653,"height":1672,"rx":58,"fill":67,"stroke":68,"style":69},"124.0",[75,1707,1134],{"x":65,"y":1708,"style":1676},"146.0",[55,1710],{"x":185,"y":1705,"width":1656,"height":1672,"rx":58,"fill":61,"stroke":68,"style":69},[75,1712,1714],{"x":1659,"y":1713,"style":814},"145.0","text in arithmetic",[55,1716],{"x":1665,"y":1705,"width":1656,"height":1672,"rx":58,"fill":61,"stroke":68,"style":69},[75,1718,1719],{"x":1668,"y":1713,"style":814},"clean the input column",[55,1721],{"x":785,"y":1722,"width":1653,"height":1672,"rx":58,"fill":67,"stroke":68,"style":69},"158.0",[75,1724,1726],{"x":65,"y":1725,"style":1676},"180.0","#NAME?",[55,1728],{"x":185,"y":1722,"width":1656,"height":1672,"rx":58,"fill":61,"stroke":68,"style":69},[75,1730,1732],{"x":1659,"y":1731,"style":814},"179.0","unknown function or name",[55,1734],{"x":1665,"y":1722,"width":1656,"height":1672,"rx":58,"fill":61,"stroke":68,"style":69},[75,1736,1737],{"x":1668,"y":1731,"style":814},"fix spelling or add _xlfn.",[55,1739],{"x":785,"y":1740,"width":1653,"height":1672,"rx":58,"fill":67,"stroke":68,"style":69},"192.0",[75,1742,23],{"x":65,"y":1743,"style":1676},"214.0",[55,1745],{"x":185,"y":1740,"width":1656,"height":1672,"rx":58,"fill":61,"stroke":68,"style":69},[75,1747,1749],{"x":1659,"y":1748,"style":814},"213.0","deleted sheet or range",[55,1751],{"x":1665,"y":1740,"width":1656,"height":1672,"rx":58,"fill":61,"stroke":68,"style":69},[75,1753,1754],{"x":1668,"y":1748,"style":814},"rebuild the reference",[319,1756,1758],{"id":1757},"decide-which-errors-are-allowed","Decide which errors are allowed",[10,1760,1761,1762,1765,1766,1769,1770,1773,1774,1778,1779,1781],{},"Not every error is a bug. A price per unit is undefined when no units were sold, and a report may legitimately show that. The honest way to allow it is in the formula, where readers see the decision: ",[14,1763,1764],{},"=IF(B3=0,\"\",C3\u002FB3)"," or ",[14,1767,1768],{},"=IFERROR(C3\u002FB3,\"\")",". Avoid wrapping everything in ",[14,1771,1772],{},"IFERROR"," to make the scan pass — it converts a missing lookup key, a broken reference or text in a number column into a blank, which is precisely the silent failure this page set out to catch. Allow specific, expected cases; let everything else fail. ",[30,1775,1777],{"href":1776},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-name-error-for-new-excel-functions-written-by-python\u002F","Fix #NAME? in Formulas Written from Python"," covers the most common cause of ",[14,1780,1726],{}," in generated workbooks.",[319,1783,1785],{"id":1784},"common-pitfalls","Common pitfalls",[1787,1788,1789,1800,1810,1823],"ul",{},[1790,1791,1792,1796,1797,1799],"li",{},[1793,1794,1795],"strong",{},"Scanning a workbook that was never calculated."," Every formula reads as ",[14,1798,915],{},"; recalculate first.",[1790,1801,1802,1805,1806,1809],{},[1793,1803,1804],{},"Matching the string \"#N\u002FA\"."," Use ",[14,1807,1808],{},"data_type == \"e\""," so typed placeholders are not reported.",[1790,1811,1812,1815,1816,1818,1819,1822],{},[1793,1813,1814],{},"Trusting the DataFrame."," ",[14,1817,27],{}," after ",[14,1820,1821],{},"read_excel"," may be an error, not a gap.",[1790,1824,1825,1828],{},[1793,1826,1827],{},"Hiding errors with blanket IFERROR."," It makes scans pass and reports wrong.",[319,1830,1832],{"id":1831},"performance-notes","Performance notes",[10,1834,1835,1836,1839],{},"The scan loads the workbook twice and visits every cell, which takes a few seconds for sheets with hundreds of thousands of cells. For large files, use ",[14,1837,1838],{},"read_only=True"," for both loads and iterate the same sheets in step, or restrict the scan to the sheets and columns that contain formulas. LibreOffice recalculation is usually the slowest part — several seconds per file — so run it once and scan the result, rather than recalculating repeatedly.",[319,1841,1843],{"id":1842},"conclusion","Conclusion",[10,1845,1846,1847,1849,1850,1852,1853,1855,1856,1858,1859,1861],{},"pandas turns error cells into ",[14,1848,27],{},", so finding broken formulas needs openpyxl: load with ",[14,1851,492],{},", keep cells whose ",[14,1854,500],{}," is ",[14,1857,504],{},", and pair each with its formula from a second load. Recalculate Python-written files first so there are results to scan, search formula text for ",[14,1860,23],{}," to catch deleted references without calculating, and fail the job with a per-sheet summary. Allow only the errors you have decided are legitimate, in the formula itself.",[319,1863,1865],{"id":1864},"frequently-asked-questions","Frequently asked questions",[10,1867,1868,1871],{},[1793,1869,1870],{},"Why does pandas show NaN where Excel shows","\npandas' openpyxl reader converts every error cell to NaN deliberately. That is convenient for analysis but makes a failed formula indistinguishable from an empty cell. Scan with openpyxl when you need to know about errors.",[10,1873,1874,1877],{},[1793,1875,1876],{},"Why does openpyxl return None instead of the error?","\nThe file has no cached results. Workbooks written by openpyxl or pandas contain formulas but no calculated values until a spreadsheet application recalculates and saves them. Recalculate first, for example with LibreOffice in headless mode.",[10,1879,1880,1883],{},[1793,1881,1882],{},"Can I find","\nOften, yes. When a referenced sheet, row or column is deleted, Excel rewrites the reference in the formula text as #REF!, so searching formula strings finds those cells in any saved file, calculated or not.",[10,1885,1886,1889],{},[1793,1887,1888],{},"Should I wrap formulas in IFERROR to get rid of errors?","\nOnly where an error is an expected case, such as dividing by a zero count. Wrapping everything hides real problems like missing lookup keys and broken references. Fix the cause and keep IFERROR for the cases you have decided to allow.",[319,1891,1893],{"id":1892},"related","Related",[1787,1895,1896,1902,1909,1916],{},[1790,1897,1898,1899,1901],{},"Up one level: ",[30,1900,33],{"href":32}," — writing and reading formulas.",[1790,1903,1904,1908],{},[30,1905,1907],{"href":1906},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fread-formula-results-with-openpyxl-data-only\u002F","Read Formula Results with openpyxl data_only"," — how cached values work.",[1790,1910,1911,1915],{},[30,1912,1914],{"href":1913},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Faudit-every-formula-in-a-workbook-with-python\u002F","Audit Every Formula in a Workbook with Python"," — an inventory of every formula, erroring or not.",[1790,1917,1918,1922],{},[30,1919,1921],{"href":1920},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fhighlight-invalid-cells-in-excel-with-python\u002F","Highlight Invalid Cells in Excel with Python"," — marking problems for readers.",[1924,1925,1926],"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 pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}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 .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":329,"searchDepth":352,"depth":352,"links":1928},[1929,1930,1931,1932,1933,1934,1935,1936,1937,1938,1939,1940],{"id":321,"depth":352,"text":322},{"id":366,"depth":352,"text":367},{"id":485,"depth":352,"text":486},{"id":905,"depth":352,"text":906},{"id":1143,"depth":352,"text":1144},{"id":1341,"depth":352,"text":1342},{"id":1757,"depth":352,"text":1758},{"id":1784,"depth":352,"text":1785},{"id":1831,"depth":352,"text":1832},{"id":1842,"depth":352,"text":1843},{"id":1864,"depth":352,"text":1865},{"id":1892,"depth":352,"text":1893},"2026-09-25","pandas turns formula errors into NaN, so a broken workbook can look merely incomplete. Scan every sheet with openpyxl for cells whose cached value is an error, catch #REF! inside formula text even without a recalculation, and fail a report job before it sends a workbook full of errors.","md",[1945,1947,1949,1951],{"q":1870,"a":1946},"pandas' openpyxl reader converts every error cell to NaN deliberately. That is convenient for analysis but makes a failed formula indistinguishable from an empty cell. Scan with openpyxl when you need to know about errors.",{"q":1876,"a":1948},"The file has no cached results. Workbooks written by openpyxl or pandas contain formulas but no calculated values until a spreadsheet application recalculates and saves them. Recalculate first, for example with LibreOffice in headless mode.",{"q":1882,"a":1950},"Often, yes. When a referenced sheet, row or column is deleted, Excel rewrites the reference in the formula text as #REF!, so searching formula strings finds those cells in any saved file, calculated or not.",{"q":1888,"a":1952},"Only where an error is an expected case, such as dividing by a zero count. Wrapping everything hides real problems like missing lookup keys and broken references. Fix the cause and keep IFERROR for the cases you have decided to allow.","find formula errors like",null,{"breadcrumb":1956},[1957,1959,1962],{"name":1958,"item":1084},"Home",{"name":1960,"item":1961},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":33,"item":32},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-formula-errors-in-a-workbook-with-python",{"title":1965,"description":1966},"Find Excel Formula Errors (#REF!, #DIV\u002F0!, #N\u002FA) with Python","Scan a workbook for Excel error values with openpyxl data_only, recalculate files that have no cached values, detect #REF! in formula text, and report errors by sheet and cell before a report is sent.","find-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",[1970,1971,1972],"excel-formulas","data-validation","openpyxl","how-to","uMywTHTe-r2YW3hzAiW856qs33mY0DZ8rfj5zMCohKA",[1976,1979],{"title":1337,"path":1977,"stem":1978,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-external-links-and-broken-references-in-a-workbook","getting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-external-links-and-broken-references-in-a-workbook\u002Findex",{"title":1980,"path":1981,"stem":1982,"children":-1},"Handle Circular References and Iterative Calculation in Python","\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fhandle-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",[1984,1986],{"path":1985,"title":1960},"\u002Fgetting-started-with-python-excel-automation",{"path":1987,"title":33},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python",[],[1990,1993,1997,2000,2003,2006],{"path":1991,"title":1992,"kind":1973},"\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":1994,"title":1995,"kind":1996},"\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":1998,"title":1999,"kind":1973},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fgoal-seek-in-python-instead-of-excel","Goal Seek in Python Instead of Excel",{"path":2001,"title":2002,"kind":1973},"\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":2004,"title":2005,"kind":1973},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcall-excel-worksheet-functions-through-microsoft-graph","Call Excel Worksheet Functions Through Microsoft Graph",{"path":2007,"title":2008,"kind":1973},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frecalculate-excel-online-workbooks-with-graph-sessions","Recalculate Excel Online Workbooks with Graph Sessions",1790972075673]