[{"data":1,"prerenderedAt":1815},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-differences-between-two-columns-with-openpyxl":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-differences-between-two-columns-with-openpyxl":1782,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-differences-between-two-columns-with-openpyxl":1790,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-differences-between-two-columns-with-openpyxl":1795,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-differences-between-two-columns-with-openpyxl":1796},{"id":4,"title":5,"body":6,"dateModified":1747,"datePublished":1747,"description":1748,"extension":1749,"faq":1750,"keyword":1759,"kind":1760,"meta":1761,"navigation":365,"path":1770,"seo":1771,"slug":1774,"stem":1775,"topics":1776,"type":1780,"__hash__":1781},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-differences-between-two-columns-with-openpyxl\u002Findex.md","Highlight Differences Between Two Columns with openpyxl",{"type":7,"value":8,"toc":1732},"minimark",[9,24,272,277,305,311,474,478,485,510,526,530,543,566,569,592,595,905,909,912,938,950,954,961,984,997,1001,1009,1163,1166,1170,1173,1206,1213,1344,1348,1351,1574,1586,1590,1635,1639,1646,1650,1667,1671,1677,1683,1689,1695,1699,1728],[10,11,12,13,17,18,23],"p",{},"Reconciliations start with the question \"where do these two columns disagree?\" — the export from system A against system B, the forecast against the actual, this month's price list against last month's. A conditional format answers it inside the workbook, for every row, and keeps answering as values are corrected. The formula is one comparison, but what counts as \"different\" is a decision with at least four parts: letter case, blanks versus zeros, stray spaces and rounding noise. A plain ",[14,15,16],"code",{},"\u003C>"," makes those decisions for you, and not always the way a reconciliation needs. This page, part of ",[19,20,22],"a",{"href":21},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002F","Applying Conditional Formatting With openpyxl",", shows each choice; every result below comes from rendering the workbook with LibreOffice.",[25,26,34,35,34,39,34,43,34,51,34,58,34,67,34,71,34,77,34,81,34,85,34,89,34,93,34,97,34,102,34,104,34,108,34,110,34,113,34,115,34,118,34,121,34,125,34,127,34,130,34,132,34,135,34,137,34,139,34,142,34,146,34,151,34,155,34,157,34,159,34,161,34,164,34,167,34,171,34,173,34,176,34,178,34,181,34,183,34,185,34,188,34,192,34,194,34,197,34,199,34,202,34,204,34,207,34,210,34,214,34,216,34,219,34,221,34,224,34,226,34,228,34,231,34,235,34,237,34,240,34,242,34,245,34,247,34,250,34,253,34,257,34,259,34,262,34,264,34,267,34,269],"svg",{"viewBox":27,"role":28,"ariaLabelledBy":29,"xmlns":32,"style":33},"239 0 281 312","img",[30,31],"dif-result-t","dif-result-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:281px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[36,37,38],"title",{"id":30},"Rows where system A and system B disagree",[40,41,42],"desc",{"id":31},"A comparison sheet with seven items. The plain \u003C> rule highlights rows b (100 against 101), f (50.004 against 50.001) and g (\"x \" against \"x\"). It does not highlight c, where a blank is compared with 0, or d, where North is compared with north, because the comparison ignores case and treats blank as zero.",[44,45],"rect",{"x":46,"y":47,"width":48,"height":49,"fill":50},"239","0","281","312","#ffffff",[52,53,57],"text",{"x":54,"y":55,"style":56},"380.0","31.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Rule =$B2\u003C>$C2 on A2:C8",[44,59],{"x":60,"y":61,"width":62,"height":63,"rx":47,"fill":64,"stroke":65,"style":66},"255.5","48.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[44,68],{"x":69,"y":61,"width":70,"height":63,"rx":47,"fill":64,"stroke":65,"style":66},"287.5","51.0",[52,72,76],{"x":73,"y":74,"style":75},"313.1","65.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[44,78],{"x":79,"y":61,"width":80,"height":63,"rx":47,"fill":64,"stroke":65,"style":66},"338.6","82.6",[52,82,84],{"x":83,"y":74,"style":75},"379.9","B",[44,86],{"x":87,"y":61,"width":88,"height":63,"rx":47,"fill":64,"stroke":65,"style":66},"421.2","83.3",[52,90,92],{"x":91,"y":74,"style":75},"462.8","C",[44,94],{"x":60,"y":95,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},"72.0","28.0",[52,98,101],{"x":99,"y":100,"style":75},"271.5","91.0","1",[44,103],{"x":69,"y":95,"width":70,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,105,107],{"x":73,"y":100,"style":106},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","item",[44,109],{"x":79,"y":95,"width":80,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,111,112],{"x":83,"y":100,"style":106},"system_a",[44,114],{"x":87,"y":95,"width":88,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,116,117],{"x":91,"y":100,"style":106},"system_b",[44,119],{"x":60,"y":120,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},"100.0",[52,122,124],{"x":99,"y":123,"style":75},"119.0","2",[44,126],{"x":69,"y":120,"width":70,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,128,19],{"x":73,"y":123,"style":129},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle",[44,131],{"x":79,"y":120,"width":80,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,133,134],{"x":83,"y":123,"style":129},"100",[44,136],{"x":87,"y":120,"width":88,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,138,134],{"x":91,"y":123,"style":129},[44,140],{"x":60,"y":141,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},"128.0",[52,143,145],{"x":99,"y":144,"style":75},"147.0","3",[44,147],{"x":69,"y":141,"width":70,"height":96,"rx":47,"fill":148,"stroke":149,"style":150},"#fee8f2","var(--accent,#f43f8f)","stroke-width:1.5px",[52,152,154],{"x":73,"y":144,"style":153},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","b",[44,156],{"x":79,"y":141,"width":80,"height":96,"rx":47,"fill":148,"stroke":149,"style":150},[52,158,134],{"x":83,"y":144,"style":153},[44,160],{"x":87,"y":141,"width":88,"height":96,"rx":47,"fill":148,"stroke":149,"style":150},[52,162,163],{"x":91,"y":144,"style":153},"101",[44,165],{"x":60,"y":166,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},"156.0",[52,168,170],{"x":99,"y":169,"style":75},"175.0","4",[44,172],{"x":69,"y":166,"width":70,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,174,175],{"x":73,"y":169,"style":129},"c",[44,177],{"x":79,"y":166,"width":80,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,179,180],{"x":83,"y":169,"style":129},"(blank)",[44,182],{"x":87,"y":166,"width":88,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,184,47],{"x":91,"y":169,"style":129},[44,186],{"x":60,"y":187,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},"184.0",[52,189,191],{"x":99,"y":190,"style":75},"203.0","5",[44,193],{"x":69,"y":187,"width":70,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,195,196],{"x":73,"y":190,"style":129},"d",[44,198],{"x":79,"y":187,"width":80,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,200,201],{"x":83,"y":190,"style":129},"North",[44,203],{"x":87,"y":187,"width":88,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,205,206],{"x":91,"y":190,"style":129},"north",[44,208],{"x":60,"y":209,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},"212.0",[52,211,213],{"x":99,"y":212,"style":75},"231.0","6",[44,215],{"x":69,"y":209,"width":70,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,217,218],{"x":73,"y":212,"style":129},"e",[44,220],{"x":79,"y":209,"width":80,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,222,223],{"x":83,"y":212,"style":129},"0.3",[44,225],{"x":87,"y":209,"width":88,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,227,223],{"x":91,"y":212,"style":129},[44,229],{"x":60,"y":230,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},"240.0",[52,232,234],{"x":99,"y":233,"style":75},"259.0","7",[44,236],{"x":69,"y":230,"width":70,"height":96,"rx":47,"fill":148,"stroke":149,"style":150},[52,238,239],{"x":73,"y":233,"style":153},"f",[44,241],{"x":79,"y":230,"width":80,"height":96,"rx":47,"fill":148,"stroke":149,"style":150},[52,243,244],{"x":83,"y":233,"style":153},"50.004",[44,246],{"x":87,"y":230,"width":88,"height":96,"rx":47,"fill":148,"stroke":149,"style":150},[52,248,249],{"x":91,"y":233,"style":153},"50.001",[44,251],{"x":60,"y":252,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},"268.0",[52,254,256],{"x":99,"y":255,"style":75},"287.0","8",[44,258],{"x":69,"y":252,"width":70,"height":96,"rx":47,"fill":148,"stroke":149,"style":150},[52,260,261],{"x":73,"y":255,"style":153},"g",[44,263],{"x":79,"y":252,"width":80,"height":96,"rx":47,"fill":148,"stroke":149,"style":150},[52,265,266],{"x":83,"y":255,"style":153},"\"x \"",[44,268],{"x":87,"y":252,"width":88,"height":96,"rx":47,"fill":148,"stroke":149,"style":150},[52,270,271],{"x":91,"y":255,"style":153},"\"x\"",[273,274,276],"h2",{"id":275},"prerequisites","Prerequisites",[278,279,284],"pre",{"className":280,"code":281,"language":282,"meta":283,"style":283},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pandas\n","bash","",[14,285,286],{"__ignoreMap":283},[287,288,291,295,299,302],"span",{"class":289,"line":290},"line",1,[287,292,294],{"class":293},"sMTad","pip",[287,296,298],{"class":297},"srMev"," install",[287,300,301],{"class":297}," openpyxl",[287,303,304],{"class":297}," pandas\n",[10,306,307,310],{},[14,308,309],{},"compare.xlsx"," holds seven items chosen to exercise every edge case: an exact match, a real difference, blank against zero, a difference in case only, two equal decimals, a difference in the third decimal place, and a trailing space. A second sheet holds last month's system A values.",[278,312,316],{"className":313,"code":314,"language":315,"meta":283,"style":283},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import load_workbook\nfrom openpyxl.formatting.rule import FormulaRule\nfrom openpyxl.styles import PatternFill\n\nRED = PatternFill(\"solid\", bgColor=\"FFC7CE\")\n\ndef add_rule(path, out, formula, cell_range=\"A2:C8\", sheet=\"Compare\"):\n    wb = load_workbook(path)\n    wb[sheet].conditional_formatting.add(cell_range, FormulaRule(formula=[formula], fill=RED))\n    wb.save(out)\n","python",[14,317,318,334,347,360,367,399,404,433,444,468],{"__ignoreMap":283},[287,319,320,324,328,331],{"class":289,"line":290},[287,321,323],{"class":322},"s-kum","from",[287,325,327],{"class":326},"skGVy"," openpyxl ",[287,329,330],{"class":322},"import",[287,332,333],{"class":326}," load_workbook\n",[287,335,337,339,342,344],{"class":289,"line":336},2,[287,338,323],{"class":322},[287,340,341],{"class":326}," openpyxl.formatting.rule ",[287,343,330],{"class":322},[287,345,346],{"class":326}," FormulaRule\n",[287,348,350,352,355,357],{"class":289,"line":349},3,[287,351,323],{"class":322},[287,353,354],{"class":326}," openpyxl.styles ",[287,356,330],{"class":322},[287,358,359],{"class":326}," PatternFill\n",[287,361,363],{"class":289,"line":362},4,[287,364,366],{"emptyLinePlaceholder":365},true,"\n",[287,368,370,374,377,380,383,386,390,393,396],{"class":289,"line":369},5,[287,371,373],{"class":372},"sP0c6","RED",[287,375,376],{"class":322}," =",[287,378,379],{"class":326}," PatternFill(",[287,381,382],{"class":297},"\"solid\"",[287,384,385],{"class":326},", ",[287,387,389],{"class":388},"sa561","bgColor",[287,391,392],{"class":322},"=",[287,394,395],{"class":297},"\"FFC7CE\"",[287,397,398],{"class":326},")\n",[287,400,402],{"class":289,"line":401},6,[287,403,366],{"emptyLinePlaceholder":365},[287,405,407,410,414,417,419,422,425,427,430],{"class":289,"line":406},7,[287,408,409],{"class":322},"def",[287,411,413],{"class":412},"s_Opv"," add_rule",[287,415,416],{"class":326},"(path, out, formula, cell_range",[287,418,392],{"class":322},[287,420,421],{"class":297},"\"A2:C8\"",[287,423,424],{"class":326},", sheet",[287,426,392],{"class":322},[287,428,429],{"class":297},"\"Compare\"",[287,431,432],{"class":326},"):\n",[287,434,436,439,441],{"class":289,"line":435},8,[287,437,438],{"class":326},"    wb ",[287,440,392],{"class":322},[287,442,443],{"class":326}," load_workbook(path)\n",[287,445,447,450,453,455,458,461,463,465],{"class":289,"line":446},9,[287,448,449],{"class":326},"    wb[sheet].conditional_formatting.add(cell_range, FormulaRule(",[287,451,452],{"class":388},"formula",[287,454,392],{"class":322},[287,456,457],{"class":326},"[formula], ",[287,459,460],{"class":388},"fill",[287,462,392],{"class":322},[287,464,373],{"class":372},[287,466,467],{"class":326},"))\n",[287,469,471],{"class":289,"line":470},10,[287,472,473],{"class":326},"    wb.save(out)\n",[273,475,477],{"id":476},"step-1-highlight-rows-where-the-columns-differ","Step 1 — Highlight rows where the columns differ",[10,479,480,481,484],{},"Apply the rule to the whole row range, anchoring the two compared columns with ",[14,482,483],{},"$"," so every cell in a row tests the same pair:",[278,486,488],{"className":313,"code":487,"language":315,"meta":283,"style":283},"add_rule(\"compare.xlsx\", \"diff_basic.xlsx\", \"$B2\u003C>$C2\")\n",[14,489,490],{"__ignoreMap":283},[287,491,492,495,498,500,503,505,508],{"class":289,"line":290},[287,493,494],{"class":326},"add_rule(",[287,496,497],{"class":297},"\"compare.xlsx\"",[287,499,385],{"class":326},[287,501,502],{"class":297},"\"diff_basic.xlsx\"",[287,504,385],{"class":326},[287,506,507],{"class":297},"\"$B2\u003C>$C2\"",[287,509,398],{"class":326},[10,511,512,513,515,516,518,519,521,522,525],{},"Rows b, f and g are highlighted. Row c, a blank against a zero, is not; nor is row d, \"North\" against \"north\". Both are consequences of how spreadsheet comparisons work, not of openpyxl: ",[14,514,392],{}," and ",[14,517,16],{}," ignore letter case, and an empty cell compares equal to ",[14,520,47],{}," and to ",[14,523,524],{},"\"\"",". For many reports that is exactly right. For a reconciliation, it can hide real discrepancies.",[273,527,529],{"id":528},"step-2-make-case-and-blanks-count","Step 2 — Make case and blanks count",[10,531,532,535,536,538,539,542],{},[14,533,534],{},"EXACT"," compares text character for character, case included. As a side effect it converts both values to text first, so a blank (",[14,537,524],{},") and a zero (",[14,540,541],{},"\"0\"",") now differ too:",[278,544,546],{"className":313,"code":545,"language":315,"meta":283,"style":283},"add_rule(\"compare.xlsx\", \"diff_exact.xlsx\", \"NOT(EXACT($B2,$C2))\")\n",[14,547,548],{"__ignoreMap":283},[287,549,550,552,554,556,559,561,564],{"class":289,"line":290},[287,551,494],{"class":326},[287,553,497],{"class":297},[287,555,385],{"class":326},[287,557,558],{"class":297},"\"diff_exact.xlsx\"",[287,560,385],{"class":326},[287,562,563],{"class":297},"\"NOT(EXACT($B2,$C2))\"",[287,565,398],{"class":326},[10,567,568],{},"Rows b, c, d, f and g are highlighted. If you want case to count but blank and zero to stay equal — or the reverse — say so explicitly. This version keeps the case-insensitive comparison but adds a check that both sides are either blank or filled:",[278,570,572],{"className":313,"code":571,"language":315,"meta":283,"style":283},"add_rule(\"compare.xlsx\", \"diff_blanks.xlsx\", \"OR($B2\u003C>$C2,ISBLANK($B2)\u003C>ISBLANK($C2))\")\n",[14,573,574],{"__ignoreMap":283},[287,575,576,578,580,582,585,587,590],{"class":289,"line":290},[287,577,494],{"class":326},[287,579,497],{"class":297},[287,581,385],{"class":326},[287,583,584],{"class":297},"\"diff_blanks.xlsx\"",[287,586,385],{"class":326},[287,588,589],{"class":297},"\"OR($B2\u003C>$C2,ISBLANK($B2)\u003C>ISBLANK($C2))\"",[287,591,398],{"class":326},[10,593,594],{},"Now row c is flagged — a missing value is not the same as a reported zero — while row d, which differs only in case, is not.",[25,596,34,602,34,605,34,608,34,612,34,618,34,625,34,630,34,633,34,637,34,640,34,644,34,647,34,651,34,655,34,660,34,662,34,671,34,673,34,678,34,680,34,685,34,687,34,692,34,695,34,699,34,701,34,709,34,711,34,716,34,718,34,723,34,725,34,730,34,733,34,737,34,739,34,744,34,746,34,751,34,753,34,758,34,760,34,765,34,768,34,772,34,774,34,779,34,781,34,786,34,788,34,793,34,795,34,800,34,803,34,807,34,809,34,814,34,816,34,821,34,823,34,828,34,830,34,835,34,838,34,842,34,844,34,849,34,851,34,856,34,858,34,863,34,865,34,870,34,873,34,877,34,879,34,884,34,886,34,891,34,893,34,898,34,900],{"viewBox":597,"role":28,"ariaLabelledBy":598,"xmlns":32,"style":601},"4 0 752 310",[599,600],"dif-rules-t","dif-rules-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[36,603,604],{"id":599},"Which rows each comparison flags",[40,606,607],{"id":600},"A grid of the seven test rows against four rules. The plain \u003C> rule flags b, f and g. NOT EXACT flags b, c, d, f and g. The tolerance rule flags b and g but not f, whose difference is 0.003. The TRIM rule flags b, c and f, treating \"x \" and \"x\" as equal. Row a and row e are never flagged.",[44,609],{"x":170,"y":47,"width":610,"height":611,"fill":50},"752","310",[44,613],{"x":614,"y":615,"width":616,"height":617,"rx":47,"fill":50,"stroke":65,"style":66},"20.0","16.0","143.5","40.0",[44,619],{"x":620,"y":615,"width":621,"height":617,"rx":47,"fill":622,"stroke":623,"style":624},"163.5","144.1","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[52,626,16],{"x":627,"y":628,"style":629},"235.5","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[44,631],{"x":632,"y":615,"width":621,"height":617,"rx":47,"fill":622,"stroke":623,"style":624},"307.6",[52,634,636],{"x":635,"y":628,"style":629},"379.7","NOT(EXACT())",[44,638],{"x":639,"y":615,"width":621,"height":617,"rx":47,"fill":622,"stroke":623,"style":624},"451.7",[52,641,643],{"x":642,"y":628,"style":629},"523.8","tolerance 0.005",[44,645],{"x":646,"y":615,"width":621,"height":617,"rx":47,"fill":622,"stroke":623,"style":624},"595.9",[52,648,650],{"x":649,"y":628,"style":629},"667.9","TRIM both sides",[44,652],{"x":614,"y":653,"width":616,"height":654,"rx":47,"fill":64,"stroke":65,"style":66},"56.0","34.0",[52,656,659],{"x":62,"y":657,"style":658},"78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","a  100 \u002F 100",[44,661],{"x":620,"y":653,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,663,665],{"transform":664},"translate(227.5,65.0) scale(0.667)",[666,667],"path",{"d":668,"fill":669,"stroke":149,"style":670},"M6 6l12 12 M18 6L6 18","none","stroke-width:2.70px;stroke-linecap:round;stroke-linejoin:round",[44,672],{"x":632,"y":653,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,674,676],{"transform":675},"translate(371.7,65.0) scale(0.667)",[666,677],{"d":668,"fill":669,"stroke":149,"style":670},[44,679],{"x":639,"y":653,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,681,683],{"transform":682},"translate(515.8,65.0) scale(0.667)",[666,684],{"d":668,"fill":669,"stroke":149,"style":670},[44,686],{"x":646,"y":653,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,688,690],{"transform":689},"translate(659.9,65.0) scale(0.667)",[666,691],{"d":668,"fill":669,"stroke":149,"style":670},[44,693],{"x":614,"y":694,"width":616,"height":654,"rx":47,"fill":64,"stroke":65,"style":66},"90.0",[52,696,698],{"x":62,"y":697,"style":658},"112.0","b  100 \u002F 101",[44,700],{"x":620,"y":694,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,702,704],{"transform":703},"translate(226.5,98.0) scale(0.750)",[666,705],{"d":706,"fill":669,"stroke":707,"style":708},"M5 12.5l4.5 4.5L19 7","var(--teal,#0f9488)","stroke-width:2.40px;stroke-linecap:round;stroke-linejoin:round",[44,710],{"x":632,"y":694,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,712,714],{"transform":713},"translate(370.7,98.0) scale(0.750)",[666,715],{"d":706,"fill":669,"stroke":707,"style":708},[44,717],{"x":639,"y":694,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,719,721],{"transform":720},"translate(514.8,98.0) scale(0.750)",[666,722],{"d":706,"fill":669,"stroke":707,"style":708},[44,724],{"x":646,"y":694,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,726,728],{"transform":727},"translate(658.9,98.0) scale(0.750)",[666,729],{"d":706,"fill":669,"stroke":707,"style":708},[44,731],{"x":614,"y":732,"width":616,"height":654,"rx":47,"fill":64,"stroke":65,"style":66},"124.0",[52,734,736],{"x":62,"y":735,"style":658},"146.0","c  blank \u002F 0",[44,738],{"x":620,"y":732,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,740,742],{"transform":741},"translate(227.5,133.0) scale(0.667)",[666,743],{"d":668,"fill":669,"stroke":149,"style":670},[44,745],{"x":632,"y":732,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,747,749],{"transform":748},"translate(370.7,132.0) scale(0.750)",[666,750],{"d":706,"fill":669,"stroke":707,"style":708},[44,752],{"x":639,"y":732,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,754,756],{"transform":755},"translate(515.8,133.0) scale(0.667)",[666,757],{"d":668,"fill":669,"stroke":149,"style":670},[44,759],{"x":646,"y":732,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,761,763],{"transform":762},"translate(658.9,132.0) scale(0.750)",[666,764],{"d":706,"fill":669,"stroke":707,"style":708},[44,766],{"x":614,"y":767,"width":616,"height":654,"rx":47,"fill":64,"stroke":65,"style":66},"158.0",[52,769,771],{"x":62,"y":770,"style":658},"180.0","d  North \u002F north",[44,773],{"x":620,"y":767,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,775,777],{"transform":776},"translate(227.5,167.0) scale(0.667)",[666,778],{"d":668,"fill":669,"stroke":149,"style":670},[44,780],{"x":632,"y":767,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,782,784],{"transform":783},"translate(370.7,166.0) scale(0.750)",[666,785],{"d":706,"fill":669,"stroke":707,"style":708},[44,787],{"x":639,"y":767,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,789,791],{"transform":790},"translate(515.8,167.0) scale(0.667)",[666,792],{"d":668,"fill":669,"stroke":149,"style":670},[44,794],{"x":646,"y":767,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,796,798],{"transform":797},"translate(659.9,167.0) scale(0.667)",[666,799],{"d":668,"fill":669,"stroke":149,"style":670},[44,801],{"x":614,"y":802,"width":616,"height":654,"rx":47,"fill":64,"stroke":65,"style":66},"192.0",[52,804,806],{"x":62,"y":805,"style":658},"214.0","e  0.3 \u002F 0.3",[44,808],{"x":620,"y":802,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,810,812],{"transform":811},"translate(227.5,201.0) scale(0.667)",[666,813],{"d":668,"fill":669,"stroke":149,"style":670},[44,815],{"x":632,"y":802,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,817,819],{"transform":818},"translate(371.7,201.0) scale(0.667)",[666,820],{"d":668,"fill":669,"stroke":149,"style":670},[44,822],{"x":639,"y":802,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,824,826],{"transform":825},"translate(515.8,201.0) scale(0.667)",[666,827],{"d":668,"fill":669,"stroke":149,"style":670},[44,829],{"x":646,"y":802,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,831,833],{"transform":832},"translate(659.9,201.0) scale(0.667)",[666,834],{"d":668,"fill":669,"stroke":149,"style":670},[44,836],{"x":614,"y":837,"width":616,"height":654,"rx":47,"fill":64,"stroke":65,"style":66},"226.0",[52,839,841],{"x":62,"y":840,"style":658},"248.0","f  50.004 \u002F 50.001",[44,843],{"x":620,"y":837,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,845,847],{"transform":846},"translate(226.5,234.0) scale(0.750)",[666,848],{"d":706,"fill":669,"stroke":707,"style":708},[44,850],{"x":632,"y":837,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,852,854],{"transform":853},"translate(370.7,234.0) scale(0.750)",[666,855],{"d":706,"fill":669,"stroke":707,"style":708},[44,857],{"x":639,"y":837,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,859,861],{"transform":860},"translate(515.8,235.0) scale(0.667)",[666,862],{"d":668,"fill":669,"stroke":149,"style":670},[44,864],{"x":646,"y":837,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,866,868],{"transform":867},"translate(658.9,234.0) scale(0.750)",[666,869],{"d":706,"fill":669,"stroke":707,"style":708},[44,871],{"x":614,"y":872,"width":616,"height":654,"rx":47,"fill":64,"stroke":65,"style":66},"260.0",[52,874,876],{"x":62,"y":875,"style":658},"282.0","g  \"x \" \u002F \"x\"",[44,878],{"x":620,"y":872,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,880,882],{"transform":881},"translate(226.5,268.0) scale(0.750)",[666,883],{"d":706,"fill":669,"stroke":707,"style":708},[44,885],{"x":632,"y":872,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,887,889],{"transform":888},"translate(370.7,268.0) scale(0.750)",[666,890],{"d":706,"fill":669,"stroke":707,"style":708},[44,892],{"x":639,"y":872,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,894,896],{"transform":895},"translate(514.8,268.0) scale(0.750)",[666,897],{"d":706,"fill":669,"stroke":707,"style":708},[44,899],{"x":646,"y":872,"width":621,"height":654,"rx":47,"fill":50,"stroke":65,"style":66},[261,901,903],{"transform":902},"translate(659.9,269.0) scale(0.667)",[666,904],{"d":668,"fill":669,"stroke":149,"style":670},[273,906,908],{"id":907},"step-3-ignore-rounding-noise-in-numbers","Step 3 — Ignore rounding noise in numbers",[10,910,911],{},"Two systems that store amounts at different precision disagree in the third decimal place without disagreeing about anything that matters. Compare numbers with a tolerance, and fall back to a plain comparison for text so the same rule works on mixed columns:",[278,913,915],{"className":313,"code":914,"language":315,"meta":283,"style":283},"add_rule(\"compare.xlsx\", \"diff_tolerance.xlsx\",\n         \"IF(AND(ISNUMBER($B2),ISNUMBER($C2)),ABS($B2-$C2)>0.005,$B2\u003C>$C2)\")\n",[14,916,917,931],{"__ignoreMap":283},[287,918,919,921,923,925,928],{"class":289,"line":290},[287,920,494],{"class":326},[287,922,497],{"class":297},[287,924,385],{"class":326},[287,926,927],{"class":297},"\"diff_tolerance.xlsx\"",[287,929,930],{"class":326},",\n",[287,932,933,936],{"class":289,"line":336},[287,934,935],{"class":297},"         \"IF(AND(ISNUMBER($B2),ISNUMBER($C2)),ABS($B2-$C2)>0.005,$B2\u003C>$C2)\"",[287,937,398],{"class":326},[10,939,940,941,944,945,949],{},"Row f — 50.004 against 50.001 — is no longer flagged; row b, a whole unit apart, still is. Choose the tolerance from the business rule: half a cent for currency, a rounding unit for quantities. Keep it in a labelled cell (",[14,942,943],{},"$H$1",") rather than in the formula if people will want to change it. ",[19,946,948],{"href":947},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-floating-point-noise-in-excel-output\u002F","Fix Floating-Point Noise Like 0.1 + 0.2 ≠ 0.3 in Excel Output"," explains why exact comparison of computed decimals is fragile in the first place.",[273,951,953],{"id":952},"step-4-ignore-stray-spaces-in-text","Step 4 — Ignore stray spaces in text",[10,955,956,957,960],{},"Trailing spaces are the most common false difference in text exports. ",[14,958,959],{},"TRIM"," both sides before comparing:",[278,962,964],{"className":313,"code":963,"language":315,"meta":283,"style":283},"add_rule(\"compare.xlsx\", \"diff_trim.xlsx\", \"TRIM($B2)\u003C>TRIM($C2)\")\n",[14,965,966],{"__ignoreMap":283},[287,967,968,970,972,974,977,979,982],{"class":289,"line":290},[287,969,494],{"class":326},[287,971,497],{"class":297},[287,973,385],{"class":326},[287,975,976],{"class":297},"\"diff_trim.xlsx\"",[287,978,385],{"class":326},[287,980,981],{"class":297},"\"TRIM($B2)\u003C>TRIM($C2)\"",[287,983,398],{"class":326},[10,985,986,987,989,990,992,993,996],{},"Row g is no longer flagged. Note that ",[14,988,959],{}," also turns numbers into text, so this rule treats blank and zero as different, like ",[14,991,534],{},". For a column that holds only text, that is irrelevant; for mixed columns, combine the tests in one formula — ",[14,994,995],{},"IF(AND(ISNUMBER(...)), tolerance, TRIM comparison)"," — so each type gets the comparison it needs.",[273,998,1000],{"id":999},"show-which-side-is-higher","Show which side is higher",[10,1002,1003,1004,1008],{},"For budget against actual, or forecast against result, ",[1005,1006,1007],"em",{},"which way"," the difference goes matters as much as whether there is one. Two rules on the same range do it — one for each direction — and because each tests a different condition, their priority never conflicts:",[278,1010,1012],{"className":313,"code":1011,"language":315,"meta":283,"style":283},"wb = load_workbook(\"compare.xlsx\")\nws = wb[\"Compare\"]\nnum = \"AND(ISNUMBER($B2),ISNUMBER($C2))\"\nws.conditional_formatting.add(\"A2:C8\", FormulaRule(formula=[f\"AND({num},$C2-$B2>0.005)\"],\n                                                   fill=PatternFill(\"solid\", bgColor=\"C6EFCE\")))\nws.conditional_formatting.add(\"A2:C8\", FormulaRule(formula=[f\"AND({num},$B2-$C2>0.005)\"], fill=RED))\nwb.save(\"diff_direction.xlsx\")\n",[14,1013,1014,1028,1043,1053,1091,1115,1153],{"__ignoreMap":283},[287,1015,1016,1019,1021,1024,1026],{"class":289,"line":290},[287,1017,1018],{"class":326},"wb ",[287,1020,392],{"class":322},[287,1022,1023],{"class":326}," load_workbook(",[287,1025,497],{"class":297},[287,1027,398],{"class":326},[287,1029,1030,1033,1035,1038,1040],{"class":289,"line":336},[287,1031,1032],{"class":326},"ws ",[287,1034,392],{"class":322},[287,1036,1037],{"class":326}," wb[",[287,1039,429],{"class":297},[287,1041,1042],{"class":326},"]\n",[287,1044,1045,1048,1050],{"class":289,"line":349},[287,1046,1047],{"class":326},"num ",[287,1049,392],{"class":322},[287,1051,1052],{"class":297}," \"AND(ISNUMBER($B2),ISNUMBER($C2))\"\n",[287,1054,1055,1058,1060,1063,1065,1067,1070,1072,1075,1079,1082,1085,1088],{"class":289,"line":362},[287,1056,1057],{"class":326},"ws.conditional_formatting.add(",[287,1059,421],{"class":297},[287,1061,1062],{"class":326},", FormulaRule(",[287,1064,452],{"class":388},[287,1066,392],{"class":322},[287,1068,1069],{"class":326},"[",[287,1071,239],{"class":322},[287,1073,1074],{"class":297},"\"AND(",[287,1076,1078],{"class":1077},"sSjpA","{",[287,1080,1081],{"class":326},"num",[287,1083,1084],{"class":1077},"}",[287,1086,1087],{"class":297},",$C2-$B2>0.005)\"",[287,1089,1090],{"class":326},"],\n",[287,1092,1093,1096,1098,1101,1103,1105,1107,1109,1112],{"class":289,"line":369},[287,1094,1095],{"class":388},"                                                   fill",[287,1097,392],{"class":322},[287,1099,1100],{"class":326},"PatternFill(",[287,1102,382],{"class":297},[287,1104,385],{"class":326},[287,1106,389],{"class":388},[287,1108,392],{"class":322},[287,1110,1111],{"class":297},"\"C6EFCE\"",[287,1113,1114],{"class":326},")))\n",[287,1116,1117,1119,1121,1123,1125,1127,1129,1131,1133,1135,1137,1139,1142,1145,1147,1149,1151],{"class":289,"line":401},[287,1118,1057],{"class":326},[287,1120,421],{"class":297},[287,1122,1062],{"class":326},[287,1124,452],{"class":388},[287,1126,392],{"class":322},[287,1128,1069],{"class":326},[287,1130,239],{"class":322},[287,1132,1074],{"class":297},[287,1134,1078],{"class":1077},[287,1136,1081],{"class":326},[287,1138,1084],{"class":1077},[287,1140,1141],{"class":297},",$B2-$C2>0.005)\"",[287,1143,1144],{"class":326},"], ",[287,1146,460],{"class":388},[287,1148,392],{"class":322},[287,1150,373],{"class":372},[287,1152,467],{"class":326},[287,1154,1155,1158,1161],{"class":289,"line":406},[287,1156,1157],{"class":326},"wb.save(",[287,1159,1160],{"class":297},"\"diff_direction.xlsx\"",[287,1162,398],{"class":326},[10,1164,1165],{},"Row b, where system B is higher, turns green; a row where system A is higher would turn red; rows within the tolerance, and text rows, stay plain. Swap the colours to match what \"good\" means in your report — for costs, higher is the red one.",[273,1167,1169],{"id":1168},"step-5-compare-with-another-sheet","Step 5 — Compare with another sheet",[10,1171,1172],{},"The same technique compares a column with the same column on another sheet — this month against last month. Quote the sheet name, because it contains a space:",[278,1174,1176],{"className":313,"code":1175,"language":315,"meta":283,"style":283},"add_rule(\"compare.xlsx\", \"diff_vs_last_month.xlsx\", \"$B2\u003C>'Last month'!$B2\", cell_range=\"B2:B8\")\n",[14,1177,1178],{"__ignoreMap":283},[287,1179,1180,1182,1184,1186,1189,1191,1194,1196,1199,1201,1204],{"class":289,"line":290},[287,1181,494],{"class":326},[287,1183,497],{"class":297},[287,1185,385],{"class":326},[287,1187,1188],{"class":297},"\"diff_vs_last_month.xlsx\"",[287,1190,385],{"class":326},[287,1192,1193],{"class":297},"\"$B2\u003C>'Last month'!$B2\"",[287,1195,385],{"class":326},[287,1197,1198],{"class":388},"cell_range",[287,1200,392],{"class":322},[287,1202,1203],{"class":297},"\"B2:B8\"",[287,1205,398],{"class":326},[10,1207,1208,1209,1212],{},"Only B3 (100 against last month's 99) and B7 (50.004 against 50) are highlighted. The comparison relies on both sheets listing items in the same order; if they might not, look the previous value up by key instead — ",[14,1210,1211],{},"$B2\u003C>INDEX('Last month'!$B:$B,MATCH($A2,'Last month'!$A:$A,0))"," — so a reordered sheet does not produce a wall of false differences. Current Excel versions accept references to other sheets in conditional formatting formulas; versions older than Excel 2010 did not, and needed a defined name as a go-between.",[25,1214,34,1219,34,1222,34,1225,34,1228,34,1234,34,1242,34,1246,34,1250,34,1256,34,1262,34,1266,34,1271,34,1275,34,1280,34,1284,34,1289,34,1293,34,1300,34,1305,34,1309,34,1313,34,1318,34,1321,34,1326,34,1329,34,1336,34,1339],{"viewBox":1215,"role":28,"ariaLabelledBy":1216,"xmlns":32,"style":601},"4 0 752 233",[1217,1218],"dif-position-t","dif-position-d",[36,1220,1221],{"id":1217},"Comparing by position versus by key",[40,1223,1224],{"id":1218},"Two panels. Comparing row by row with $B2 against the other sheet's $B2 is simple and fast but produces false differences when rows are sorted or inserted differently. Comparing by key with INDEX and MATCH on the item column survives reordering and inserted rows, at the cost of a longer formula and a lookup per cell.",[44,1226],{"x":170,"y":47,"width":610,"height":1227,"fill":50},"233",[44,1229],{"x":614,"y":615,"width":1230,"height":1231,"rx":1232,"fill":622,"stroke":623,"style":1233},"351.0","201.0","14","stroke-width:2px",[261,1235,1237],{"transform":1236},"translate(182.5,29.0) scale(1.083)",[666,1238],{"d":1239,"fill":669,"stroke":1240,"style":1241},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","var(--brand-strong,#4338ca)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[52,1243,1245],{"x":1244,"y":657,"style":56},"195.5","By position: $B2 \u003C> 'Last month'!$B2",[289,1247],{"x1":1248,"y1":100,"x2":1249,"y2":100,"stroke":623,"style":66},"36.0","355.0",[52,1251,1255],{"x":1252,"y":1253,"style":1254},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","short formula",[261,1257,1259],{"transform":1258},"translate(38.5,99.5) scale(0.625)",[666,1260],{"d":706,"fill":669,"stroke":707,"style":1261},"stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[52,1263,1265],{"x":1252,"y":1264,"style":1254},"137.0","fast",[261,1267,1269],{"transform":1268},"translate(38.5,125.5) scale(0.625)",[666,1270],{"d":706,"fill":669,"stroke":707,"style":1261},[52,1272,1274],{"x":1252,"y":1273,"style":1254},"163.0","breaks if rows are reordered",[261,1276,1278],{"transform":1277},"translate(38.5,151.5) scale(0.625)",[666,1279],{"d":668,"fill":669,"stroke":149,"style":1261},[52,1281,1283],{"x":1252,"y":1282,"style":1254},"189.0","inserted rows shift everything",[261,1285,1287],{"transform":1286},"translate(38.5,177.5) scale(0.625)",[666,1288],{"d":668,"fill":669,"stroke":149,"style":1261},[44,1290],{"x":1291,"y":615,"width":1230,"height":1231,"rx":1232,"fill":1292,"stroke":707,"style":1233},"389.0","#d9f4f1",[261,1294,1296],{"transform":1295},"translate(551.5,29.0) scale(1.083)",[666,1297],{"d":1298,"fill":669,"stroke":1299,"style":1241},"M8 11a4 4 0 1 0 .01 0z M11 13l9-9 M17 7l2 2 M15 9l2 2","var(--teal-ink,#0b6157)",[52,1301,1304],{"x":1302,"y":657,"style":1303},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","By key with INDEX \u002F MATCH",[289,1306],{"x1":1307,"y1":100,"x2":1308,"y2":100,"stroke":707,"style":66},"405.0","724.0",[52,1310,1312],{"x":1311,"y":1253,"style":1254},"429.0","survives sorting",[261,1314,1316],{"transform":1315},"translate(407.5,99.5) scale(0.625)",[666,1317],{"d":706,"fill":669,"stroke":707,"style":1261},[52,1319,1320],{"x":1311,"y":1264,"style":1254},"survives inserted rows",[261,1322,1324],{"transform":1323},"translate(407.5,125.5) scale(0.625)",[666,1325],{"d":706,"fill":669,"stroke":707,"style":1261},[52,1327,1328],{"x":1311,"y":1273,"style":1254},"longer formula",[261,1330,1332],{"transform":1331},"translate(407.5,151.5) scale(0.625)",[666,1333],{"d":1334,"fill":669,"stroke":1335,"style":1261},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold,#b4740a)",[52,1337,1338],{"x":1311,"y":1282,"style":1254},"a lookup per cell",[261,1340,1342],{"transform":1341},"translate(407.5,177.5) scale(0.625)",[666,1343],{"d":1334,"fill":669,"stroke":1335,"style":1261},[273,1345,1347],{"id":1346},"step-6-list-the-same-differences-in-pandas","Step 6 — List the same differences in pandas",[10,1349,1350],{},"Highlighting helps the person fixing the data; the job that produced the workbook should also know how many rows disagree. The same rules translate directly into pandas, with the same decisions made explicit:",[278,1352,1354],{"className":313,"code":1353,"language":315,"meta":283,"style":283},"import numpy as np\nimport pandas as pd\n\ndf = pd.read_excel(\"compare.xlsx\", sheet_name=\"Compare\")\na, b = df[\"system_a\"], df[\"system_b\"]\nnum = pd.to_numeric(a, errors=\"coerce\").notna() & pd.to_numeric(b, errors=\"coerce\").notna()\ndiff_num = (pd.to_numeric(a, errors=\"coerce\") - pd.to_numeric(b, errors=\"coerce\")).abs() > 0.005\ndiff_txt = (a.astype(\"string\").str.strip().str.casefold().fillna(\"\")\n            != b.astype(\"string\").str.strip().str.casefold().fillna(\"\"))\ndf[\"mismatch\"] = np.where(num, diff_num, diff_txt)\nprint(df.loc[df[\"mismatch\"], \"item\"].tolist())\n",[14,1355,1356,1369,1381,1385,1408,1429,1464,1503,1523,1539,1555],{"__ignoreMap":283},[287,1357,1358,1360,1363,1366],{"class":289,"line":290},[287,1359,330],{"class":322},[287,1361,1362],{"class":326}," numpy ",[287,1364,1365],{"class":322},"as",[287,1367,1368],{"class":326}," np\n",[287,1370,1371,1373,1376,1378],{"class":289,"line":336},[287,1372,330],{"class":322},[287,1374,1375],{"class":326}," pandas ",[287,1377,1365],{"class":322},[287,1379,1380],{"class":326}," pd\n",[287,1382,1383],{"class":289,"line":349},[287,1384,366],{"emptyLinePlaceholder":365},[287,1386,1387,1390,1392,1395,1397,1399,1402,1404,1406],{"class":289,"line":362},[287,1388,1389],{"class":326},"df ",[287,1391,392],{"class":322},[287,1393,1394],{"class":326}," pd.read_excel(",[287,1396,497],{"class":297},[287,1398,385],{"class":326},[287,1400,1401],{"class":388},"sheet_name",[287,1403,392],{"class":322},[287,1405,429],{"class":297},[287,1407,398],{"class":326},[287,1409,1410,1413,1415,1418,1421,1424,1427],{"class":289,"line":369},[287,1411,1412],{"class":326},"a, b ",[287,1414,392],{"class":322},[287,1416,1417],{"class":326}," df[",[287,1419,1420],{"class":297},"\"system_a\"",[287,1422,1423],{"class":326},"], df[",[287,1425,1426],{"class":297},"\"system_b\"",[287,1428,1042],{"class":326},[287,1430,1431,1433,1435,1438,1441,1443,1446,1449,1452,1455,1457,1459,1461],{"class":289,"line":401},[287,1432,1047],{"class":326},[287,1434,392],{"class":322},[287,1436,1437],{"class":326}," pd.to_numeric(a, ",[287,1439,1440],{"class":388},"errors",[287,1442,392],{"class":322},[287,1444,1445],{"class":297},"\"coerce\"",[287,1447,1448],{"class":326},").notna() ",[287,1450,1451],{"class":322},"&",[287,1453,1454],{"class":326}," pd.to_numeric(b, ",[287,1456,1440],{"class":388},[287,1458,392],{"class":322},[287,1460,1445],{"class":297},[287,1462,1463],{"class":326},").notna()\n",[287,1465,1466,1469,1471,1474,1476,1478,1480,1483,1486,1488,1490,1492,1494,1497,1500],{"class":289,"line":406},[287,1467,1468],{"class":326},"diff_num ",[287,1470,392],{"class":322},[287,1472,1473],{"class":326}," (pd.to_numeric(a, ",[287,1475,1440],{"class":388},[287,1477,392],{"class":322},[287,1479,1445],{"class":297},[287,1481,1482],{"class":326},") ",[287,1484,1485],{"class":322},"-",[287,1487,1454],{"class":326},[287,1489,1440],{"class":388},[287,1491,392],{"class":322},[287,1493,1445],{"class":297},[287,1495,1496],{"class":326},")).abs() ",[287,1498,1499],{"class":322},">",[287,1501,1502],{"class":372}," 0.005\n",[287,1504,1505,1508,1510,1513,1516,1519,1521],{"class":289,"line":435},[287,1506,1507],{"class":326},"diff_txt ",[287,1509,392],{"class":322},[287,1511,1512],{"class":326}," (a.astype(",[287,1514,1515],{"class":297},"\"string\"",[287,1517,1518],{"class":326},").str.strip().str.casefold().fillna(",[287,1520,524],{"class":297},[287,1522,398],{"class":326},[287,1524,1525,1528,1531,1533,1535,1537],{"class":289,"line":446},[287,1526,1527],{"class":322},"            !=",[287,1529,1530],{"class":326}," b.astype(",[287,1532,1515],{"class":297},[287,1534,1518],{"class":326},[287,1536,524],{"class":297},[287,1538,467],{"class":326},[287,1540,1541,1544,1547,1550,1552],{"class":289,"line":470},[287,1542,1543],{"class":326},"df[",[287,1545,1546],{"class":297},"\"mismatch\"",[287,1548,1549],{"class":326},"] ",[287,1551,392],{"class":322},[287,1553,1554],{"class":326}," np.where(num, diff_num, diff_txt)\n",[287,1556,1558,1561,1564,1566,1568,1571],{"class":289,"line":1557},11,[287,1559,1560],{"class":372},"print",[287,1562,1563],{"class":326},"(df.loc[df[",[287,1565,1546],{"class":297},[287,1567,1144],{"class":326},[287,1569,1570],{"class":297},"\"item\"",[287,1572,1573],{"class":326},"].tolist())\n",[10,1575,1576,1577,1580,1581,1585],{},"This version uses the tolerance for numbers and trimmed, case-folded text for everything else, so it flags b and c — the real difference and the blank-versus-zero — and nothing else. The ",[14,1578,1579],{},"casefold()"," is not optional: pandas compares strings case-sensitively, unlike the spreadsheet, and without it row d would be reported by the job while the workbook shows it as matching. Write the count to the job log and fail the run above a threshold; ",[19,1582,1584],{"href":1583},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcompare-two-excel-files-for-differences-with-python\u002F","Compare Two Excel Files for Differences with Python"," builds a full cell-by-cell diff for whole workbooks.",[273,1587,1589],{"id":1588},"common-pitfalls","Common pitfalls",[1591,1592,1593,1608,1617,1623,1629],"ul",{},[1594,1595,1596,1600,1601,1604,1605,1607],"li",{},[1597,1598,1599],"strong",{},"Unanchored columns."," ",[14,1602,1603],{},"B2\u003C>C2"," without ",[14,1606,483],{}," compares the wrong pair in the second and third columns of a row rule.",[1594,1609,1610,1616],{},[1597,1611,1612,1613,1615],{},"Assuming ",[14,1614,16],{}," is strict."," It ignores case and treats blank as zero.",[1594,1618,1619,1622],{},[1597,1620,1621],{},"Exact comparison of computed decimals."," Use a tolerance.",[1594,1624,1625,1628],{},[1597,1626,1627],{},"Comparing sorted and unsorted sheets by position."," Look values up by key.",[1594,1630,1631,1634],{},[1597,1632,1633],{},"Different rules in the workbook and in the job."," If the conditional format ignores case and the pandas check does not, the job reports mismatches that readers cannot see. Keep the two definitions identical.",[273,1636,1638],{"id":1637},"performance-notes","Performance notes",[10,1640,1641,1642,1645],{},"Positional comparisons are trivial for the spreadsheet to evaluate. Key-based rules with ",[14,1643,1644],{},"MATCH"," over whole columns are evaluated per cell and can slow large sheets noticeably; on tens of thousands of rows, compute the mismatch flag in pandas, write it as a column, and highlight on the flag instead.",[273,1647,1649],{"id":1648},"conclusion","Conclusion",[10,1651,1652,1653,1656,1657,1659,1660,1663,1664,1666],{},"A rule such as ",[14,1654,1655],{},"$B2\u003C>$C2"," on the row range highlights disagreements, but it ignores case and treats blanks as zero. Use ",[14,1658,534],{}," when case matters, add ",[14,1661,1662],{},"ISBLANK"," tests when missing and zero must differ, compare numbers with a tolerance, ",[14,1665,959],{}," text, and compare with another sheet by position or — safer — by key. Mirror the chosen rule in pandas so the job can count the mismatches it is highlighting.",[273,1668,1670],{"id":1669},"frequently-asked-questions","Frequently asked questions",[10,1672,1673,1676],{},[1597,1674,1675],{},"Is the \u003C> comparison case-sensitive?","\nNo. In a spreadsheet formula, \"North\"\u003C>\"north\" is FALSE, so a plain \u003C> rule treats them as equal. Use NOT(EXACT($B2,$C2)) when case matters.",[10,1678,1679,1682],{},[1597,1680,1681],{},"Why is a blank cell not flagged against a zero?","\nSpreadsheet comparisons treat an empty cell as equal to 0 and to an empty string. If blank and zero must count as different — a missing value versus a real zero — test ISBLANK on both sides as well.",[10,1684,1685,1688],{},[1597,1686,1687],{},"How do I ignore tiny rounding differences?","\nCompare numbers with a tolerance: ABS($B2-$C2)>0.005 flags only differences larger than half a cent. Wrap it in an IF with ISNUMBER so text columns still use a plain comparison.",[10,1690,1691,1694],{},[1597,1692,1693],{},"Can a conditional format compare with another sheet?","\nYes. Current versions of Excel accept references to other sheets in conditional formatting formulas, such as $B2\u003C>'Last month'!$B2. Very old versions (before 2010) required a defined name instead.",[273,1696,1698],{"id":1697},"related","Related",[1591,1700,1701,1707,1714,1721],{},[1594,1702,1703,1704,1706],{},"Up one level: ",[19,1705,22],{"href":21}," — every rule type.",[1594,1708,1709,1713],{},[19,1710,1712],{"href":1711},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Freconcile-excel-totals-with-control-totals\u002F","Reconcile Excel Totals Against Control Totals with Python"," — reconciling at the total level.",[1594,1715,1716,1720],{},[19,1717,1719],{"href":1718},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Ffind-rows-in-one-excel-file-missing-from-another\u002F","Find Rows in One Excel File Missing from Another"," — the rows that have no partner at all.",[1594,1722,1723,1727],{},[19,1724,1726],{"href":1725},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-duplicate-values-in-excel-with-openpyxl\u002F","Highlight Duplicate Values in Excel with openpyxl"," — another rule for data checks.",[1729,1730,1731],"style",{},"html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}",{"title":283,"searchDepth":336,"depth":336,"links":1733},[1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744,1745,1746],{"id":275,"depth":336,"text":276},{"id":476,"depth":336,"text":477},{"id":528,"depth":336,"text":529},{"id":907,"depth":336,"text":908},{"id":952,"depth":336,"text":953},{"id":999,"depth":336,"text":1000},{"id":1168,"depth":336,"text":1169},{"id":1346,"depth":336,"text":1347},{"id":1588,"depth":336,"text":1589},{"id":1637,"depth":336,"text":1638},{"id":1648,"depth":336,"text":1649},{"id":1669,"depth":336,"text":1670},{"id":1697,"depth":336,"text":1698},"2026-09-25","Mark every row where system A and system B disagree, or where this month's value differs from last month's sheet. Write the comparison as a conditional format with openpyxl, and decide deliberately how case, blanks versus zero, trailing spaces and rounding differences should count.","md",[1751,1753,1755,1757],{"q":1675,"a":1752},"No. In a spreadsheet formula, \"North\"\u003C>\"north\" is FALSE, so a plain \u003C> rule treats them as equal. Use NOT(EXACT($B2,$C2)) when case matters.",{"q":1681,"a":1754},"Spreadsheet comparisons treat an empty cell as equal to 0 and to an empty string. If blank and zero must count as different — a missing value versus a real zero — test ISBLANK on both sides as well.",{"q":1687,"a":1756},"Compare numbers with a tolerance: ABS($B2-$C2)>0.005 flags only differences larger than half a cent. Wrap it in an IF with ISNUMBER so text columns still use a plain comparison.",{"q":1693,"a":1758},"Yes. Current versions of Excel accept references to other sheets in conditional formatting formulas, such as $B2\u003C>'Last month'!$B2. Very old versions (before 2010) required a defined name instead.","compare two columns and highlight differences with conditional formatting",null,{"breadcrumb":1762},[1763,1766,1769],{"name":1764,"item":1765},"Home","\u002F",{"name":1767,"item":1768},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":22,"item":21},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-differences-between-two-columns-with-openpyxl",{"title":1772,"description":1773},"Highlight Mismatches Between Two Excel Columns with openpyxl","Conditional formatting that highlights differences between two columns or two sheets with openpyxl: \u003C> versus EXACT, blank versus zero, tolerance for rounding, ignoring spaces, and a pandas list of the same mismatches.","highlight-differences-between-two-columns-with-openpyxl","advanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-differences-between-two-columns-with-openpyxl\u002Findex",[1777,1778,1779],"conditional-formatting","joins-and-lookups","openpyxl","how-to","AImtfvja7eWZ3nzZORrA_C2Cu4LCTEd7gwArm09yCsE",[1783,1787],{"title":1784,"path":1785,"stem":1786,"children":-1},"Highlight Cells with Formula Errors Using Conditional Formatting","\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-cells-with-formula-errors-using-conditional-formatting","advanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-cells-with-formula-errors-using-conditional-formatting\u002Findex",{"title":1726,"path":1788,"stem":1789,"children":-1},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-duplicate-values-in-excel-with-openpyxl","advanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-duplicate-values-in-excel-with-openpyxl\u002Findex",[1791,1793],{"path":1792,"title":22},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl",{"path":1794,"title":1767},"\u002Fadvanced-data-transformation-and-cleaning",[],[1797,1800,1803,1806,1809,1812],{"path":1798,"title":1799,"kind":1780},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fapply-conditional-formatting-with-xlsxwriter","Apply Conditional Formatting with xlsxwriter",{"path":1801,"title":1802,"kind":1780},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-data-bars-and-colour-scales-with-openpyxl","Add Data Bars and Colour Scales with openpyxl",{"path":1804,"title":1805,"kind":1780},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-icon-sets-with-openpyxl","Add Icon Sets to Excel Cells with openpyxl",{"path":1807,"title":1808,"kind":1780},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fapply-a-colour-scale-per-row-or-per-column-with-openpyxl","Apply a Colour Scale per Row or per Column with openpyxl",{"path":1810,"title":1811,"kind":1780},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fapply-conditional-formatting-to-an-excel-table-that-grows","Apply Conditional Formatting to an Excel Table That Grows",{"path":1813,"title":1814,"kind":1780},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fcontrol-conditional-formatting-rule-order-and-stop-if-true","Control Conditional Formatting Rule Order and Stop If True",1790972078253]