[{"data":1,"prerenderedAt":2008},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-top-and-bottom-values-with-openpyxl":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-top-and-bottom-values-with-openpyxl":1974,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-top-and-bottom-values-with-openpyxl":1983,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-top-and-bottom-values-with-openpyxl":1988,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-top-and-bottom-values-with-openpyxl":1989},{"id":4,"title":5,"body":6,"dateModified":1939,"datePublished":1939,"description":1940,"extension":1941,"faq":1942,"keyword":1951,"kind":1952,"meta":1953,"navigation":379,"path":1962,"seo":1963,"slug":1966,"stem":1967,"topics":1968,"type":1972,"__hash__":1973},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-top-and-bottom-values-with-openpyxl\u002Findex.md","Highlight Top and Bottom Values in Excel with openpyxl",{"type":7,"value":8,"toc":1925},"minimark",[9,24,275,280,306,312,484,494,498,513,676,679,682,717,720,724,731,737,784,787,940,944,959,1063,1066,1070,1080,1161,1176,1180,1187,1277,1295,1457,1461,1467,1761,1764,1768,1815,1819,1829,1833,1860,1864,1870,1876,1882,1888,1892,1921],[10,11,12,13,17,18,23],"p",{},"\"Who are our top three reps?\" and \"which products are in the bottom 10%?\" are questions a report should answer at a glance. Sorting answers them once; conditional formatting answers them permanently, because the spreadsheet re-evaluates the rule whenever the numbers change. openpyxl can write every variant — top or bottom N, top or bottom percent, above or below average — plus the one Excel's menu does not offer: the top value ",[14,15,16],"em",{},"within each group",". 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",", writes each rule and checks that it marks the cells it should.",[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,131,34,133,34,136,34,138,34,141,34,144,34,148,34,150,34,152,34,154,34,157,34,162,34,166,34,169,34,173,34,175,34,177,34,179,34,182,34,184,34,186,34,189,34,193,34,195,34,198,34,200,34,203,34,205,34,208,34,211,34,215,34,217,34,219,34,221,34,224,34,228,34,232,34,235,34,239,34,241,34,243,34,245,34,248,34,250,34,253,34,256,34,260,34,262,34,265,34,267,34,270,34,272],"svg",{"viewBox":27,"role":28,"ariaLabelledBy":29,"xmlns":32,"style":33},"207 0 345 312","img",[30,31],"top-result-t","top-result-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:345px;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},"Top two and bottom one sales, highlighted",[40,41,42],"desc",{"id":31},"A sales sheet with seven reps. The top two values rule marks both cells holding 980, because the two highest values are tied. The bottom one rule marks the single lowest value, 75. The other values are unformatted.",[44,45],"rect",{"x":46,"y":47,"width":48,"height":49,"fill":50},"207","0","345","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","sales.xlsx — top 2 in green, bottom 1 in pink",[44,59],{"x":60,"y":61,"width":62,"height":63,"rx":47,"fill":64,"stroke":65,"style":66},"278.0","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},"310.0","63.6",[52,72,76],{"x":73,"y":74,"style":75},"341.8","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},"373.6","52.0",[52,82,84],{"x":83,"y":74,"style":75},"399.6","B",[44,86],{"x":87,"y":61,"width":88,"height":63,"rx":47,"fill":64,"stroke":65,"style":66},"425.6","56.4",[52,90,92],{"x":91,"y":74,"style":75},"453.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},"294.0","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","region",[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},"rep",[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},"sales",[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,130],{"x":73,"y":123,"style":129},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","North",[44,132],{"x":79,"y":120,"width":80,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,134,135],{"x":83,"y":123,"style":129},"Ada",[44,137],{"x":87,"y":120,"width":88,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,139,140],{"x":91,"y":123,"style":129},"410",[44,142],{"x":60,"y":143,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},"128.0",[52,145,147],{"x":99,"y":146,"style":75},"147.0","3",[44,149],{"x":69,"y":143,"width":70,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,151,130],{"x":73,"y":146,"style":129},[44,153],{"x":79,"y":143,"width":80,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,155,156],{"x":83,"y":146,"style":129},"Ben",[44,158],{"x":87,"y":143,"width":88,"height":96,"rx":47,"fill":159,"stroke":160,"style":161},"#d9f4f1","var(--teal,#0f9488)","stroke-width:1.5px",[52,163,165],{"x":91,"y":146,"style":164},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","980",[44,167],{"x":60,"y":168,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},"156.0",[52,170,172],{"x":99,"y":171,"style":75},"175.0","4",[44,174],{"x":69,"y":168,"width":70,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,176,130],{"x":73,"y":171,"style":129},[44,178],{"x":79,"y":168,"width":80,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,180,181],{"x":83,"y":171,"style":129},"Cleo",[44,183],{"x":87,"y":168,"width":88,"height":96,"rx":47,"fill":159,"stroke":160,"style":161},[52,185,165],{"x":91,"y":171,"style":164},[44,187],{"x":60,"y":188,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},"184.0",[52,190,192],{"x":99,"y":191,"style":75},"203.0","5",[44,194],{"x":69,"y":188,"width":70,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,196,197],{"x":73,"y":191,"style":129},"South",[44,199],{"x":79,"y":188,"width":80,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,201,202],{"x":83,"y":191,"style":129},"Dev",[44,204],{"x":87,"y":188,"width":88,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,206,207],{"x":91,"y":191,"style":129},"660",[44,209],{"x":60,"y":210,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},"212.0",[52,212,214],{"x":99,"y":213,"style":75},"231.0","6",[44,216],{"x":69,"y":210,"width":70,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,218,197],{"x":73,"y":213,"style":129},[44,220],{"x":79,"y":210,"width":80,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,222,223],{"x":83,"y":213,"style":129},"Eve",[44,225],{"x":87,"y":210,"width":88,"height":96,"rx":47,"fill":226,"stroke":227,"style":161},"#fee8f2","var(--accent,#f43f8f)",[52,229,231],{"x":91,"y":213,"style":230},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","75",[44,233],{"x":60,"y":234,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},"240.0",[52,236,238],{"x":99,"y":237,"style":75},"259.0","7",[44,240],{"x":69,"y":234,"width":70,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,242,197],{"x":73,"y":237,"style":129},[44,244],{"x":79,"y":234,"width":80,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,246,247],{"x":83,"y":237,"style":129},"Finn",[44,249],{"x":87,"y":234,"width":88,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,251,252],{"x":91,"y":237,"style":129},"300",[44,254],{"x":60,"y":255,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},"268.0",[52,257,259],{"x":99,"y":258,"style":75},"287.0","8",[44,261],{"x":69,"y":255,"width":70,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,263,264],{"x":73,"y":258,"style":129},"East",[44,266],{"x":79,"y":255,"width":80,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,268,269],{"x":83,"y":258,"style":129},"Gus",[44,271],{"x":87,"y":255,"width":88,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,273,274],{"x":91,"y":258,"style":129},"500",[276,277,279],"h2",{"id":278},"prerequisites","Prerequisites",[281,282,287],"pre",{"className":283,"code":284,"language":285,"meta":286,"style":286},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl\n","bash","",[288,289,290],"code",{"__ignoreMap":286},[291,292,295,299,303],"span",{"class":293,"line":294},"line",1,[291,296,298],{"class":297},"sMTad","pip",[291,300,302],{"class":301},"srMev"," install",[291,304,305],{"class":301}," openpyxl\n",[10,307,308,311],{},[288,309,310],{},"sales.xlsx"," has seven reps across three regions, with two reps tied on 980.",[281,313,317],{"className":314,"code":315,"language":316,"meta":286,"style":286},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import load_workbook\nfrom openpyxl.formatting.rule import Rule, FormulaRule\nfrom openpyxl.styles import PatternFill, Font\nfrom openpyxl.styles.differential import DifferentialStyle\n\nGREEN = DifferentialStyle(fill=PatternFill(\"solid\", bgColor=\"C6EFCE\"), font=Font(color=\"006100\"))\nPINK = DifferentialStyle(fill=PatternFill(\"solid\", bgColor=\"FFC7CE\"), font=Font(color=\"9C0006\"))\n","python",[288,318,319,335,348,361,374,381,440],{"__ignoreMap":286},[291,320,321,325,329,332],{"class":293,"line":294},[291,322,324],{"class":323},"s-kum","from",[291,326,328],{"class":327},"skGVy"," openpyxl ",[291,330,331],{"class":323},"import",[291,333,334],{"class":327}," load_workbook\n",[291,336,338,340,343,345],{"class":293,"line":337},2,[291,339,324],{"class":323},[291,341,342],{"class":327}," openpyxl.formatting.rule ",[291,344,331],{"class":323},[291,346,347],{"class":327}," Rule, FormulaRule\n",[291,349,351,353,356,358],{"class":293,"line":350},3,[291,352,324],{"class":323},[291,354,355],{"class":327}," openpyxl.styles ",[291,357,331],{"class":323},[291,359,360],{"class":327}," PatternFill, Font\n",[291,362,364,366,369,371],{"class":293,"line":363},4,[291,365,324],{"class":323},[291,367,368],{"class":327}," openpyxl.styles.differential ",[291,370,331],{"class":323},[291,372,373],{"class":327}," DifferentialStyle\n",[291,375,377],{"class":293,"line":376},5,[291,378,380],{"emptyLinePlaceholder":379},true,"\n",[291,382,384,388,391,394,398,401,404,407,410,413,415,418,421,424,426,429,432,434,437],{"class":293,"line":383},6,[291,385,387],{"class":386},"sP0c6","GREEN",[291,389,390],{"class":323}," =",[291,392,393],{"class":327}," DifferentialStyle(",[291,395,397],{"class":396},"sa561","fill",[291,399,400],{"class":323},"=",[291,402,403],{"class":327},"PatternFill(",[291,405,406],{"class":301},"\"solid\"",[291,408,409],{"class":327},", ",[291,411,412],{"class":396},"bgColor",[291,414,400],{"class":323},[291,416,417],{"class":301},"\"C6EFCE\"",[291,419,420],{"class":327},"), ",[291,422,423],{"class":396},"font",[291,425,400],{"class":323},[291,427,428],{"class":327},"Font(",[291,430,431],{"class":396},"color",[291,433,400],{"class":323},[291,435,436],{"class":301},"\"006100\"",[291,438,439],{"class":327},"))\n",[291,441,443,446,448,450,452,454,456,458,460,462,464,467,469,471,473,475,477,479,482],{"class":293,"line":442},7,[291,444,445],{"class":386},"PINK",[291,447,390],{"class":323},[291,449,393],{"class":327},[291,451,397],{"class":396},[291,453,400],{"class":323},[291,455,403],{"class":327},[291,457,406],{"class":301},[291,459,409],{"class":327},[291,461,412],{"class":396},[291,463,400],{"class":323},[291,465,466],{"class":301},"\"FFC7CE\"",[291,468,420],{"class":327},[291,470,423],{"class":396},[291,472,400],{"class":323},[291,474,428],{"class":327},[291,476,431],{"class":396},[291,478,400],{"class":323},[291,480,481],{"class":301},"\"9C0006\"",[291,483,439],{"class":327},[10,485,486,487,490,491,493],{},"The two styles are Excel's own \"Good\" and \"Bad\" colours. Conditional formats take a ",[288,488,489],{},"DifferentialStyle"," — only the parts you set are applied over the cell's normal formatting — and for solid fills the colour goes in ",[288,492,412],{},".",[276,495,497],{"id":496},"step-1-highlight-the-top-and-bottom-n-values","Step 1 — Highlight the top and bottom N values",[10,499,500,501,504,505,508,509,512],{},"The rule type is called ",[288,502,503],{},"top10"," whatever N is. ",[288,506,507],{},"rank"," sets N, and ",[288,510,511],{},"bottom=True"," flips it to the lowest values:",[281,514,516],{"className":314,"code":515,"language":316,"meta":286,"style":286},"wb = load_workbook(\"sales.xlsx\")\nws = wb[\"Sales\"]\nws.conditional_formatting.add(\"C2:C8\", Rule(type=\"top10\", rank=2, dxf=GREEN))\nws.conditional_formatting.add(\"C2:C8\", Rule(type=\"top10\", rank=1, bottom=True, dxf=PINK))\nwb.save(\"top_bottom.xlsx\")\nprint([(str(cf.sqref), [r.type for r in cf.rules]) for cf in ws.conditional_formatting])\n",[288,517,518,534,550,588,630,640],{"__ignoreMap":286},[291,519,520,523,525,528,531],{"class":293,"line":294},[291,521,522],{"class":327},"wb ",[291,524,400],{"class":323},[291,526,527],{"class":327}," load_workbook(",[291,529,530],{"class":301},"\"sales.xlsx\"",[291,532,533],{"class":327},")\n",[291,535,536,539,541,544,547],{"class":293,"line":337},[291,537,538],{"class":327},"ws ",[291,540,400],{"class":323},[291,542,543],{"class":327}," wb[",[291,545,546],{"class":301},"\"Sales\"",[291,548,549],{"class":327},"]\n",[291,551,552,555,558,561,564,566,569,571,573,575,577,579,582,584,586],{"class":293,"line":350},[291,553,554],{"class":327},"ws.conditional_formatting.add(",[291,556,557],{"class":301},"\"C2:C8\"",[291,559,560],{"class":327},", Rule(",[291,562,563],{"class":396},"type",[291,565,400],{"class":323},[291,567,568],{"class":301},"\"top10\"",[291,570,409],{"class":327},[291,572,507],{"class":396},[291,574,400],{"class":323},[291,576,124],{"class":386},[291,578,409],{"class":327},[291,580,581],{"class":396},"dxf",[291,583,400],{"class":323},[291,585,387],{"class":386},[291,587,439],{"class":327},[291,589,590,592,594,596,598,600,602,604,606,608,610,612,615,617,620,622,624,626,628],{"class":293,"line":363},[291,591,554],{"class":327},[291,593,557],{"class":301},[291,595,560],{"class":327},[291,597,563],{"class":396},[291,599,400],{"class":323},[291,601,568],{"class":301},[291,603,409],{"class":327},[291,605,507],{"class":396},[291,607,400],{"class":323},[291,609,101],{"class":386},[291,611,409],{"class":327},[291,613,614],{"class":396},"bottom",[291,616,400],{"class":323},[291,618,619],{"class":386},"True",[291,621,409],{"class":327},[291,623,581],{"class":396},[291,625,400],{"class":323},[291,627,445],{"class":386},[291,629,439],{"class":327},[291,631,632,635,638],{"class":293,"line":376},[291,633,634],{"class":327},"wb.save(",[291,636,637],{"class":301},"\"top_bottom.xlsx\"",[291,639,533],{"class":327},[291,641,642,645,648,651,654,657,660,663,666,668,671,673],{"class":293,"line":383},[291,643,644],{"class":386},"print",[291,646,647],{"class":327},"([(",[291,649,650],{"class":386},"str",[291,652,653],{"class":327},"(cf.sqref), [r.type ",[291,655,656],{"class":323},"for",[291,658,659],{"class":327}," r ",[291,661,662],{"class":323},"in",[291,664,665],{"class":327}," cf.rules]) ",[291,667,656],{"class":323},[291,669,670],{"class":327}," cf ",[291,672,662],{"class":323},[291,674,675],{"class":327}," ws.conditional_formatting])\n",[10,677,678],{},"The rules are stored against the range, not copied into cells, so they apply to whatever values C2:C8 hold when the workbook is opened. Extend the range when the data grows — a rule on C2:C8 ignores a new row 9 — or apply it to a generous range such as C2:C1000; empty cells are never highlighted by a top rule.",[10,680,681],{},"When a cell matches more than one rule — 980 is both a top value and, say, above a 500 threshold — the rule with the higher priority wins. openpyxl numbers rules in the order you add them, starting at priority 1, and priority 1 is applied first:",[281,683,685],{"className":314,"code":684,"language":316,"meta":286,"style":286},"print([[r.priority for r in cf.rules] for cf in ws.conditional_formatting])\n# [[1, 2]] — the top rule outranks the bottom rule\n",[288,686,687,711],{"__ignoreMap":286},[291,688,689,691,694,696,698,700,703,705,707,709],{"class":293,"line":294},[291,690,644],{"class":386},[291,692,693],{"class":327},"([[r.priority ",[291,695,656],{"class":323},[291,697,659],{"class":327},[291,699,662],{"class":323},[291,701,702],{"class":327}," cf.rules] ",[291,704,656],{"class":323},[291,706,670],{"class":327},[291,708,662],{"class":323},[291,710,675],{"class":327},[291,712,713],{"class":293,"line":337},[291,714,716],{"class":715},"s-wDw","# [[1, 2]] — the top rule outranks the bottom rule\n",[10,718,719],{},"Add the rule you want to dominate first. Rendering a test file with a green top rule added before a pink \"greater than 500\" rule shows 980 in green and 660, which matches only the second rule, in pink.",[276,721,723],{"id":722},"step-2-understand-ties-and-percentages","Step 2 — Understand ties and percentages",[10,725,726,727,730],{},"With two reps on 980, \"top 2\" marks both of them and no one else, because both cells hold one of the two highest ",[14,728,729],{},"values",". A rule for the top three would mark three cells here, not four — 980, 980 and 660 — but if two reps shared third place it would mark four. That is the behaviour most readers expect from a league table. When exactly N rows must be marked, rank in Python and write a flag, or break ties in the data.",[10,732,733,736],{},[288,734,735],{},"percent=True"," turns the rank into a percentage of the cells in the range. A top 20% rule over ten values marks two of them:",[281,738,740],{"className":314,"code":739,"language":316,"meta":286,"style":286},"ws.conditional_formatting.add(\"C2:C8\", Rule(type=\"top10\", rank=20, percent=True, dxf=GREEN))\n",[288,741,742],{"__ignoreMap":286},[291,743,744,746,748,750,752,754,756,758,760,762,765,767,770,772,774,776,778,780,782],{"class":293,"line":294},[291,745,554],{"class":327},[291,747,557],{"class":301},[291,749,560],{"class":327},[291,751,563],{"class":396},[291,753,400],{"class":323},[291,755,568],{"class":301},[291,757,409],{"class":327},[291,759,507],{"class":396},[291,761,400],{"class":323},[291,763,764],{"class":386},"20",[291,766,409],{"class":327},[291,768,769],{"class":396},"percent",[291,771,400],{"class":323},[291,773,619],{"class":386},[291,775,409],{"class":327},[291,777,581],{"class":396},[291,779,400],{"class":323},[291,781,387],{"class":386},[291,783,439],{"class":327},[10,785,786],{},"Percent rules are the better choice for ranges whose size changes from run to run — \"the top fifth of products\" stays meaningful whether the report has 50 rows or 500.",[25,788,34,794,34,797,34,800,34,804,34,810,34,817,34,822,34,825,34,829,34,833,34,838,34,840,34,845,34,847,34,850,34,853,34,857,34,859,34,863,34,865,34,868,34,871,34,875,34,877,34,881,34,883,34,886,34,889,34,893,34,895,34,899,34,901,34,904,34,907,34,911,34,913,34,917,34,919,34,922,34,925,34,929,34,931,34,935,34,937],{"viewBox":789,"role":28,"ariaLabelledBy":790,"xmlns":32,"style":793},"4 0 752 276",[791,792],"top-rules-t","top-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,795,796],{"id":791},"The ranking rules and what they mark",[40,798,799],{"id":792},"A table of rule settings. type top10 with rank 3 marks cells holding the three highest values, including ties. Adding bottom True marks the lowest values. percent True with rank 20 marks the top 20 percent of cells. type aboveAverage marks values above the mean, and aboveAverage False marks values below it. A COUNTIFS formula rule marks the top value within each group.",[44,801],{"x":172,"y":47,"width":802,"height":803,"fill":50},"752","276",[44,805],{"x":806,"y":807,"width":808,"height":809,"rx":47,"fill":50,"stroke":65,"style":66},"20.0","16.0","125.3","40.0",[44,811],{"x":812,"y":807,"width":813,"height":809,"rx":47,"fill":814,"stroke":815,"style":816},"145.3","297.3","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[52,818,821],{"x":99,"y":819,"style":820},"41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","openpyxl rule",[44,823],{"x":824,"y":807,"width":813,"height":809,"rx":47,"fill":814,"stroke":815,"style":816},"442.7",[52,826,828],{"x":827,"y":819,"style":820},"591.3","Marks",[44,830],{"x":806,"y":831,"width":808,"height":832,"rx":47,"fill":64,"stroke":65,"style":66},"56.0","34.0",[52,834,837],{"x":62,"y":835,"style":836},"78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","Top 3",[44,839],{"x":812,"y":831,"width":813,"height":832,"rx":47,"fill":50,"stroke":65,"style":66},[52,841,844],{"x":99,"y":842,"style":843},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Rule(type=\"top10\", rank=3)",[44,846],{"x":824,"y":831,"width":813,"height":832,"rx":47,"fill":50,"stroke":65,"style":66},[52,848,849],{"x":827,"y":842,"style":843},"three highest values, ties included",[44,851],{"x":806,"y":852,"width":808,"height":832,"rx":47,"fill":64,"stroke":65,"style":66},"90.0",[52,854,856],{"x":62,"y":855,"style":836},"112.0","Bottom 3",[44,858],{"x":812,"y":852,"width":813,"height":832,"rx":47,"fill":50,"stroke":65,"style":66},[52,860,862],{"x":99,"y":861,"style":843},"111.0","… rank=3, bottom=True",[44,864],{"x":824,"y":852,"width":813,"height":832,"rx":47,"fill":50,"stroke":65,"style":66},[52,866,867],{"x":827,"y":861,"style":843},"three lowest values",[44,869],{"x":806,"y":870,"width":808,"height":832,"rx":47,"fill":64,"stroke":65,"style":66},"124.0",[52,872,874],{"x":62,"y":873,"style":836},"146.0","Top 20%",[44,876],{"x":812,"y":870,"width":813,"height":832,"rx":47,"fill":50,"stroke":65,"style":66},[52,878,880],{"x":99,"y":879,"style":843},"145.0","… rank=20, percent=True",[44,882],{"x":824,"y":870,"width":813,"height":832,"rx":47,"fill":50,"stroke":65,"style":66},[52,884,885],{"x":827,"y":879,"style":843},"top fifth of the cells",[44,887],{"x":806,"y":888,"width":808,"height":832,"rx":47,"fill":64,"stroke":65,"style":66},"158.0",[52,890,892],{"x":62,"y":891,"style":836},"180.0","Above average",[44,894],{"x":812,"y":888,"width":813,"height":832,"rx":47,"fill":50,"stroke":65,"style":66},[52,896,898],{"x":99,"y":897,"style":843},"179.0","Rule(type=\"aboveAverage\")",[44,900],{"x":824,"y":888,"width":813,"height":832,"rx":47,"fill":50,"stroke":65,"style":66},[52,902,903],{"x":827,"y":897,"style":843},"values above the mean",[44,905],{"x":806,"y":906,"width":808,"height":832,"rx":47,"fill":64,"stroke":65,"style":66},"192.0",[52,908,910],{"x":62,"y":909,"style":836},"214.0","Below average",[44,912],{"x":812,"y":906,"width":813,"height":832,"rx":47,"fill":50,"stroke":65,"style":66},[52,914,916],{"x":99,"y":915,"style":843},"213.0","… aboveAverage=False",[44,918],{"x":824,"y":906,"width":813,"height":832,"rx":47,"fill":50,"stroke":65,"style":66},[52,920,921],{"x":827,"y":915,"style":843},"values below the mean",[44,923],{"x":806,"y":924,"width":808,"height":832,"rx":47,"fill":64,"stroke":65,"style":66},"226.0",[52,926,928],{"x":62,"y":927,"style":836},"248.0","Top per group",[44,930],{"x":812,"y":924,"width":813,"height":832,"rx":47,"fill":50,"stroke":65,"style":66},[52,932,934],{"x":99,"y":933,"style":843},"247.0","FormulaRule with COUNTIFS",[44,936],{"x":824,"y":924,"width":813,"height":832,"rx":47,"fill":50,"stroke":65,"style":66},[52,938,939],{"x":827,"y":933,"style":843},"highest in each region",[276,941,943],{"id":942},"step-3-highlight-values-above-or-below-the-average","Step 3 — Highlight values above or below the average",[10,945,946,947,950,951,954,955,958],{},"The ",[288,948,949],{},"aboveAverage"," rule compares each cell with the mean of the range. ",[288,952,953],{},"aboveAverage=False"," gives the below-average version; ",[288,956,957],{},"equalAverage=True"," includes values exactly at the mean:",[281,960,962],{"className":314,"code":961,"language":316,"meta":286,"style":286},"wb = load_workbook(\"sales.xlsx\")\nws = wb[\"Sales\"]\nws.conditional_formatting.add(\"C2:C8\", Rule(type=\"aboveAverage\", aboveAverage=True, dxf=GREEN))\nws.conditional_formatting.add(\"C2:C8\", Rule(type=\"aboveAverage\", aboveAverage=False, dxf=PINK))\nwb.save(\"average.xlsx\")\n",[288,963,964,976,988,1021,1054],{"__ignoreMap":286},[291,965,966,968,970,972,974],{"class":293,"line":294},[291,967,522],{"class":327},[291,969,400],{"class":323},[291,971,527],{"class":327},[291,973,530],{"class":301},[291,975,533],{"class":327},[291,977,978,980,982,984,986],{"class":293,"line":337},[291,979,538],{"class":327},[291,981,400],{"class":323},[291,983,543],{"class":327},[291,985,546],{"class":301},[291,987,549],{"class":327},[291,989,990,992,994,996,998,1000,1003,1005,1007,1009,1011,1013,1015,1017,1019],{"class":293,"line":350},[291,991,554],{"class":327},[291,993,557],{"class":301},[291,995,560],{"class":327},[291,997,563],{"class":396},[291,999,400],{"class":323},[291,1001,1002],{"class":301},"\"aboveAverage\"",[291,1004,409],{"class":327},[291,1006,949],{"class":396},[291,1008,400],{"class":323},[291,1010,619],{"class":386},[291,1012,409],{"class":327},[291,1014,581],{"class":396},[291,1016,400],{"class":323},[291,1018,387],{"class":386},[291,1020,439],{"class":327},[291,1022,1023,1025,1027,1029,1031,1033,1035,1037,1039,1041,1044,1046,1048,1050,1052],{"class":293,"line":363},[291,1024,554],{"class":327},[291,1026,557],{"class":301},[291,1028,560],{"class":327},[291,1030,563],{"class":396},[291,1032,400],{"class":323},[291,1034,1002],{"class":301},[291,1036,409],{"class":327},[291,1038,949],{"class":396},[291,1040,400],{"class":323},[291,1042,1043],{"class":386},"False",[291,1045,409],{"class":327},[291,1047,581],{"class":396},[291,1049,400],{"class":323},[291,1051,445],{"class":386},[291,1053,439],{"class":327},[291,1055,1056,1058,1061],{"class":293,"line":376},[291,1057,634],{"class":327},[291,1059,1060],{"class":301},"\"average.xlsx\"",[291,1062,533],{"class":327},[10,1064,1065],{},"Average rules suit sheets where \"better or worse than typical\" matters more than rank — a column of delivery times, say. The mean is recalculated from the range every time, so outliers pull it; for skewed data a top or bottom percent rule is often more informative.",[276,1067,1069],{"id":1068},"step-4-highlight-whole-rows","Step 4 — Highlight whole rows",[10,1071,1072,1073,1076,1077,1079],{},"Marking the entire row of a top performer reads better than colouring one number. The built-in rules only test the cell they sit on, so use a formula rule over the full row range. The formula is written for the top-left cell of the range, and the ",[288,1074,1075],{},"$"," before ",[288,1078,92],{}," keeps every cell in a row looking at that row's sales:",[281,1081,1083],{"className":314,"code":1082,"language":316,"meta":286,"style":286},"wb = load_workbook(\"sales.xlsx\")\nws = wb[\"Sales\"]\nws.conditional_formatting.add(\"A2:C8\", FormulaRule(formula=[\"$C2>=LARGE($C$2:$C$8,3)\"], fill=PatternFill(\"solid\", bgColor=\"C6EFCE\")))\nwb.save(\"top_rows.xlsx\")\n",[288,1084,1085,1097,1109,1152],{"__ignoreMap":286},[291,1086,1087,1089,1091,1093,1095],{"class":293,"line":294},[291,1088,522],{"class":327},[291,1090,400],{"class":323},[291,1092,527],{"class":327},[291,1094,530],{"class":301},[291,1096,533],{"class":327},[291,1098,1099,1101,1103,1105,1107],{"class":293,"line":337},[291,1100,538],{"class":327},[291,1102,400],{"class":323},[291,1104,543],{"class":327},[291,1106,546],{"class":301},[291,1108,549],{"class":327},[291,1110,1111,1113,1116,1119,1122,1124,1127,1130,1133,1135,1137,1139,1141,1143,1145,1147,1149],{"class":293,"line":350},[291,1112,554],{"class":327},[291,1114,1115],{"class":301},"\"A2:C8\"",[291,1117,1118],{"class":327},", FormulaRule(",[291,1120,1121],{"class":396},"formula",[291,1123,400],{"class":323},[291,1125,1126],{"class":327},"[",[291,1128,1129],{"class":301},"\"$C2>=LARGE($C$2:$C$8,3)\"",[291,1131,1132],{"class":327},"], ",[291,1134,397],{"class":396},[291,1136,400],{"class":323},[291,1138,403],{"class":327},[291,1140,406],{"class":301},[291,1142,409],{"class":327},[291,1144,412],{"class":396},[291,1146,400],{"class":323},[291,1148,417],{"class":301},[291,1150,1151],{"class":327},")))\n",[291,1153,1154,1156,1159],{"class":293,"line":363},[291,1155,634],{"class":327},[291,1157,1158],{"class":301},"\"top_rows.xlsx\"",[291,1160,533],{"class":327},[10,1162,1163,1166,1167,1170,1171,1175],{},[288,1164,1165],{},"LARGE(range, 3)"," is the third-highest value, so the rule marks every row at or above it — the same tie behaviour as the built-in rule. Swap in ",[288,1168,1169],{},"SMALL"," for the bottom rows. ",[19,1172,1174],{"href":1173},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-an-entire-row-based-on-a-cell-value-with-openpyxl\u002F","Highlight an Entire Row Based on a Cell Value with openpyxl"," explains the anchoring rules for row formulas in more depth.",[276,1177,1179],{"id":1178},"step-5-highlight-the-top-value-in-each-group","Step 5 — Highlight the top value in each group",[10,1181,1182,1183,1186],{},"A league table per region — the best rep in North, in South, in East — is not something the built-in rules can do, because they rank the whole range. A ",[288,1184,1185],{},"COUNTIFS"," formula can: a row is the top of its region when no other row in the same region has higher sales:",[281,1188,1190],{"className":314,"code":1189,"language":316,"meta":286,"style":286},"wb = load_workbook(\"sales.xlsx\")\nws = wb[\"Sales\"]\nrule = FormulaRule(formula=['COUNTIFS($A$2:$A$8,$A2,$C$2:$C$8,\">\"&$C2)\u003C1'],\n                   fill=PatternFill(\"solid\", bgColor=\"C6EFCE\"))\nws.conditional_formatting.add(\"A2:C8\", rule)\nwb.save(\"top_per_region.xlsx\")\n",[288,1191,1192,1204,1216,1238,1259,1268],{"__ignoreMap":286},[291,1193,1194,1196,1198,1200,1202],{"class":293,"line":294},[291,1195,522],{"class":327},[291,1197,400],{"class":323},[291,1199,527],{"class":327},[291,1201,530],{"class":301},[291,1203,533],{"class":327},[291,1205,1206,1208,1210,1212,1214],{"class":293,"line":337},[291,1207,538],{"class":327},[291,1209,400],{"class":323},[291,1211,543],{"class":327},[291,1213,546],{"class":301},[291,1215,549],{"class":327},[291,1217,1218,1221,1223,1226,1228,1230,1232,1235],{"class":293,"line":350},[291,1219,1220],{"class":327},"rule ",[291,1222,400],{"class":323},[291,1224,1225],{"class":327}," FormulaRule(",[291,1227,1121],{"class":396},[291,1229,400],{"class":323},[291,1231,1126],{"class":327},[291,1233,1234],{"class":301},"'COUNTIFS($A$2:$A$8,$A2,$C$2:$C$8,\">\"&$C2)\u003C1'",[291,1236,1237],{"class":327},"],\n",[291,1239,1240,1243,1245,1247,1249,1251,1253,1255,1257],{"class":293,"line":363},[291,1241,1242],{"class":396},"                   fill",[291,1244,400],{"class":323},[291,1246,403],{"class":327},[291,1248,406],{"class":301},[291,1250,409],{"class":327},[291,1252,412],{"class":396},[291,1254,400],{"class":323},[291,1256,417],{"class":301},[291,1258,439],{"class":327},[291,1260,1261,1263,1265],{"class":293,"line":376},[291,1262,554],{"class":327},[291,1264,1115],{"class":301},[291,1266,1267],{"class":327},", rule)\n",[291,1269,1270,1272,1275],{"class":293,"line":383},[291,1271,634],{"class":327},[291,1273,1274],{"class":301},"\"top_per_region.xlsx\"",[291,1276,533],{"class":327},[10,1278,1279,1280,1283,1284,1287,1288,1283,1291,1294],{},"Change ",[288,1281,1282],{},"\u003C1"," to ",[288,1285,1286],{},"\u003C2"," for the top two per region, and flip ",[288,1289,1290],{},"\">\"",[288,1292,1293],{},"\"\u003C\""," for the bottom. The formula counts, for each row, how many rows in the same region beat it; ties again count as equal, so Ben and Cleo are both marked as North's top.",[25,1296,34,1302,34,1305,34,1308,34,1312,34,1315,34,1317,34,1319,34,1321,34,1323,34,1325,34,1327,34,1329,34,1331,34,1333,34,1335,34,1337,34,1339,34,1341,34,1343,34,1345,34,1347,34,1349,34,1351,34,1353,34,1355,34,1357,34,1359,34,1361,34,1363,34,1365,34,1367,34,1369,34,1371,34,1373,34,1375,34,1377,34,1379,34,1381,34,1383,34,1385,34,1387,34,1389,34,1391,34,1393,34,1395,34,1397,34,1399,34,1401,34,1403,34,1405,34,1407,34,1409,34,1411,34,1413,34,1415,34,1417,34,1419,34,1421,34,1423,34,1425,34,1427,34,1429,34,1431,34,1433,34,1435,34,1437,34,1439,34,1441,34,1443,34,1445,34,1447,34,1449,34,1451,34,1453,34,1455],{"viewBox":1297,"role":28,"ariaLabelledBy":1298,"xmlns":32,"style":1301},"201 0 358 312",[1299,1300],"top-group-t","top-group-d","width:100%;max-width:358px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[36,1303,1304],{"id":1299},"The top rep in each region, marked by a COUNTIFS rule",[40,1306,1307],{"id":1300},"The sales sheet with whole rows highlighted where no other rep in the same region has higher sales: both North reps on 980, Dev with 660 in South, and Gus with 500 in East.",[44,1309],{"x":1310,"y":47,"width":1311,"height":49,"fill":50},"201","358",[52,1313,1314],{"x":54,"y":55,"style":56},"=COUNTIFS(region, $A2, sales, \">\"&$C2) \u003C 1",[44,1316],{"x":60,"y":61,"width":62,"height":63,"rx":47,"fill":64,"stroke":65,"style":66},[44,1318],{"x":69,"y":61,"width":70,"height":63,"rx":47,"fill":64,"stroke":65,"style":66},[52,1320,76],{"x":73,"y":74,"style":75},[44,1322],{"x":79,"y":61,"width":80,"height":63,"rx":47,"fill":64,"stroke":65,"style":66},[52,1324,84],{"x":83,"y":74,"style":75},[44,1326],{"x":87,"y":61,"width":88,"height":63,"rx":47,"fill":64,"stroke":65,"style":66},[52,1328,92],{"x":91,"y":74,"style":75},[44,1330],{"x":60,"y":95,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},[52,1332,101],{"x":99,"y":100,"style":75},[44,1334],{"x":69,"y":95,"width":70,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,1336,107],{"x":73,"y":100,"style":106},[44,1338],{"x":79,"y":95,"width":80,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,1340,112],{"x":83,"y":100,"style":106},[44,1342],{"x":87,"y":95,"width":88,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,1344,117],{"x":91,"y":100,"style":106},[44,1346],{"x":60,"y":120,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},[52,1348,124],{"x":99,"y":123,"style":75},[44,1350],{"x":69,"y":120,"width":70,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,1352,130],{"x":73,"y":123,"style":129},[44,1354],{"x":79,"y":120,"width":80,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,1356,135],{"x":83,"y":123,"style":129},[44,1358],{"x":87,"y":120,"width":88,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,1360,140],{"x":91,"y":123,"style":129},[44,1362],{"x":60,"y":143,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},[52,1364,147],{"x":99,"y":146,"style":75},[44,1366],{"x":69,"y":143,"width":70,"height":96,"rx":47,"fill":159,"stroke":160,"style":161},[52,1368,130],{"x":73,"y":146,"style":164},[44,1370],{"x":79,"y":143,"width":80,"height":96,"rx":47,"fill":159,"stroke":160,"style":161},[52,1372,156],{"x":83,"y":146,"style":164},[44,1374],{"x":87,"y":143,"width":88,"height":96,"rx":47,"fill":159,"stroke":160,"style":161},[52,1376,165],{"x":91,"y":146,"style":164},[44,1378],{"x":60,"y":168,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},[52,1380,172],{"x":99,"y":171,"style":75},[44,1382],{"x":69,"y":168,"width":70,"height":96,"rx":47,"fill":159,"stroke":160,"style":161},[52,1384,130],{"x":73,"y":171,"style":164},[44,1386],{"x":79,"y":168,"width":80,"height":96,"rx":47,"fill":159,"stroke":160,"style":161},[52,1388,181],{"x":83,"y":171,"style":164},[44,1390],{"x":87,"y":168,"width":88,"height":96,"rx":47,"fill":159,"stroke":160,"style":161},[52,1392,165],{"x":91,"y":171,"style":164},[44,1394],{"x":60,"y":188,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},[52,1396,192],{"x":99,"y":191,"style":75},[44,1398],{"x":69,"y":188,"width":70,"height":96,"rx":47,"fill":159,"stroke":160,"style":161},[52,1400,197],{"x":73,"y":191,"style":164},[44,1402],{"x":79,"y":188,"width":80,"height":96,"rx":47,"fill":159,"stroke":160,"style":161},[52,1404,202],{"x":83,"y":191,"style":164},[44,1406],{"x":87,"y":188,"width":88,"height":96,"rx":47,"fill":159,"stroke":160,"style":161},[52,1408,207],{"x":91,"y":191,"style":164},[44,1410],{"x":60,"y":210,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},[52,1412,214],{"x":99,"y":213,"style":75},[44,1414],{"x":69,"y":210,"width":70,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,1416,197],{"x":73,"y":213,"style":129},[44,1418],{"x":79,"y":210,"width":80,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,1420,223],{"x":83,"y":213,"style":129},[44,1422],{"x":87,"y":210,"width":88,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,1424,231],{"x":91,"y":213,"style":129},[44,1426],{"x":60,"y":234,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},[52,1428,238],{"x":99,"y":237,"style":75},[44,1430],{"x":69,"y":234,"width":70,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,1432,197],{"x":73,"y":237,"style":129},[44,1434],{"x":79,"y":234,"width":80,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,1436,247],{"x":83,"y":237,"style":129},[44,1438],{"x":87,"y":234,"width":88,"height":96,"rx":47,"fill":50,"stroke":65,"style":66},[52,1440,252],{"x":91,"y":237,"style":129},[44,1442],{"x":60,"y":255,"width":62,"height":96,"rx":47,"fill":64,"stroke":65,"style":66},[52,1444,259],{"x":99,"y":258,"style":75},[44,1446],{"x":69,"y":255,"width":70,"height":96,"rx":47,"fill":159,"stroke":160,"style":161},[52,1448,264],{"x":73,"y":258,"style":164},[44,1450],{"x":79,"y":255,"width":80,"height":96,"rx":47,"fill":159,"stroke":160,"style":161},[52,1452,269],{"x":83,"y":258,"style":164},[44,1454],{"x":87,"y":255,"width":88,"height":96,"rx":47,"fill":159,"stroke":160,"style":161},[52,1456,274],{"x":91,"y":258,"style":164},[276,1458,1460],{"id":1459},"step-6-check-the-highlighting-without-excel","Step 6 — Check the highlighting without Excel",[10,1462,1463,1464,1466],{},"A rule that is syntactically fine but logically wrong — a missing ",[288,1465,1075],{},", an off-by-one range — produces a file that opens without complaint and highlights the wrong cells. On a server without Excel, LibreOffice can evaluate the rules for you: its HTML export writes each cell's resulting background colour, so a few lines of Python can list which cells ended up highlighted:",[281,1468,1470],{"className":314,"code":1469,"language":316,"meta":286,"style":286},"import re, subprocess, html, pathlib\n\ndef highlighted(path, colour=\"#C6EFCE\"):\n    subprocess.run([\"soffice\", \"--headless\", \"--convert-to\", \"html\", \"--outdir\", \"render\", path],\n                   check=True, capture_output=True, timeout=120)\n    page = pathlib.Path(\"render\", pathlib.Path(path).stem + \".html\").read_text(encoding=\"utf-8\", errors=\"ignore\")\n    cells = re.findall(r'\u003Ctd[^>]*bgcolor=\"' + colour + r'\"[^>]*>(.*?)\u003C\u002Ftd>', page, re.S)\n    return [html.unescape(re.sub(r\"\u003C[^>]+>\", \"\", c)).strip() for c in cells]\n\nprint(highlighted(\"top_bottom.xlsx\"))\nprint(highlighted(\"top_per_region.xlsx\"))\n",[288,1471,1472,1479,1483,1503,1539,1569,1613,1688,1733,1738,1750],{"__ignoreMap":286},[291,1473,1474,1476],{"class":293,"line":294},[291,1475,331],{"class":323},[291,1477,1478],{"class":327}," re, subprocess, html, pathlib\n",[291,1480,1481],{"class":293,"line":337},[291,1482,380],{"emptyLinePlaceholder":379},[291,1484,1485,1488,1492,1495,1497,1500],{"class":293,"line":350},[291,1486,1487],{"class":323},"def",[291,1489,1491],{"class":1490},"s_Opv"," highlighted",[291,1493,1494],{"class":327},"(path, colour",[291,1496,400],{"class":323},[291,1498,1499],{"class":301},"\"#C6EFCE\"",[291,1501,1502],{"class":327},"):\n",[291,1504,1505,1508,1511,1513,1516,1518,1521,1523,1526,1528,1531,1533,1536],{"class":293,"line":363},[291,1506,1507],{"class":327},"    subprocess.run([",[291,1509,1510],{"class":301},"\"soffice\"",[291,1512,409],{"class":327},[291,1514,1515],{"class":301},"\"--headless\"",[291,1517,409],{"class":327},[291,1519,1520],{"class":301},"\"--convert-to\"",[291,1522,409],{"class":327},[291,1524,1525],{"class":301},"\"html\"",[291,1527,409],{"class":327},[291,1529,1530],{"class":301},"\"--outdir\"",[291,1532,409],{"class":327},[291,1534,1535],{"class":301},"\"render\"",[291,1537,1538],{"class":327},", path],\n",[291,1540,1541,1544,1546,1548,1550,1553,1555,1557,1559,1562,1564,1567],{"class":293,"line":376},[291,1542,1543],{"class":396},"                   check",[291,1545,400],{"class":323},[291,1547,619],{"class":386},[291,1549,409],{"class":327},[291,1551,1552],{"class":396},"capture_output",[291,1554,400],{"class":323},[291,1556,619],{"class":386},[291,1558,409],{"class":327},[291,1560,1561],{"class":396},"timeout",[291,1563,400],{"class":323},[291,1565,1566],{"class":386},"120",[291,1568,533],{"class":327},[291,1570,1571,1574,1576,1579,1581,1584,1587,1590,1593,1596,1598,1601,1603,1606,1608,1611],{"class":293,"line":383},[291,1572,1573],{"class":327},"    page ",[291,1575,400],{"class":323},[291,1577,1578],{"class":327}," pathlib.Path(",[291,1580,1535],{"class":301},[291,1582,1583],{"class":327},", pathlib.Path(path).stem ",[291,1585,1586],{"class":323},"+",[291,1588,1589],{"class":301}," \".html\"",[291,1591,1592],{"class":327},").read_text(",[291,1594,1595],{"class":396},"encoding",[291,1597,400],{"class":323},[291,1599,1600],{"class":301},"\"utf-8\"",[291,1602,409],{"class":327},[291,1604,1605],{"class":396},"errors",[291,1607,400],{"class":323},[291,1609,1610],{"class":301},"\"ignore\"",[291,1612,533],{"class":327},[291,1614,1615,1618,1620,1623,1626,1629,1631,1634,1638,1641,1644,1647,1650,1653,1655,1658,1661,1663,1665,1667,1669,1671,1673,1676,1679,1682,1685],{"class":293,"line":442},[291,1616,1617],{"class":327},"    cells ",[291,1619,400],{"class":323},[291,1621,1622],{"class":327}," re.findall(",[291,1624,1625],{"class":323},"r",[291,1627,1628],{"class":301},"'\u003Ctd",[291,1630,1126],{"class":386},[291,1632,1633],{"class":323},"^",[291,1635,1637],{"class":1636},"sSjpA",">",[291,1639,1640],{"class":386},"]",[291,1642,1643],{"class":323},"*",[291,1645,1646],{"class":301},"bgcolor=\"'",[291,1648,1649],{"class":323}," +",[291,1651,1652],{"class":327}," colour ",[291,1654,1586],{"class":323},[291,1656,1657],{"class":323}," r",[291,1659,1660],{"class":301},"'\"",[291,1662,1126],{"class":386},[291,1664,1633],{"class":323},[291,1666,1637],{"class":1636},[291,1668,1640],{"class":386},[291,1670,1643],{"class":323},[291,1672,1637],{"class":301},[291,1674,1675],{"class":386},"(.",[291,1677,1678],{"class":323},"*?",[291,1680,1681],{"class":386},")",[291,1683,1684],{"class":301},"\u003C\u002Ftd>'",[291,1686,1687],{"class":327},", page, re.S)\n",[291,1689,1691,1694,1697,1699,1702,1704,1706,1708,1710,1712,1715,1717,1720,1723,1725,1728,1730],{"class":293,"line":1690},8,[291,1692,1693],{"class":323},"    return",[291,1695,1696],{"class":327}," [html.unescape(re.sub(",[291,1698,1625],{"class":323},[291,1700,1701],{"class":301},"\"\u003C",[291,1703,1126],{"class":386},[291,1705,1633],{"class":323},[291,1707,1637],{"class":1636},[291,1709,1640],{"class":386},[291,1711,1586],{"class":323},[291,1713,1714],{"class":301},">\"",[291,1716,409],{"class":327},[291,1718,1719],{"class":301},"\"\"",[291,1721,1722],{"class":327},", c)).strip() ",[291,1724,656],{"class":323},[291,1726,1727],{"class":327}," c ",[291,1729,662],{"class":323},[291,1731,1732],{"class":327}," cells]\n",[291,1734,1736],{"class":293,"line":1735},9,[291,1737,380],{"emptyLinePlaceholder":379},[291,1739,1741,1743,1746,1748],{"class":293,"line":1740},10,[291,1742,644],{"class":386},[291,1744,1745],{"class":327},"(highlighted(",[291,1747,637],{"class":301},[291,1749,439],{"class":327},[291,1751,1753,1755,1757,1759],{"class":293,"line":1752},11,[291,1754,644],{"class":386},[291,1756,1745],{"class":327},[291,1758,1274],{"class":301},[291,1760,439],{"class":327},[10,1762,1763],{},"The first file marks both 980 cells; the second marks the rows of Ben, Cleo, Dev and Gus — exactly the per-region winners. Turn the expected lists into assertions in a test and a broken rule fails the build instead of reaching readers.",[276,1765,1767],{"id":1766},"common-pitfalls","Common pitfalls",[1769,1770,1771,1779,1792,1798,1809],"ul",{},[1772,1773,1774,1778],"li",{},[1775,1776,1777],"strong",{},"A range that stops short of the data."," New rows outside the rule's range are never highlighted.",[1772,1780,1781,1787,1788,1791],{},[1775,1782,1783,1784,1786],{},"Missing ",[288,1785,1075],{}," in row formulas."," ",[288,1789,1790],{},"=C2>=…"," without the dollar shifts the tested column across the row.",[1772,1793,1794,1797],{},[1775,1795,1796],{},"Expecting exactly N cells."," Ties mark every tied cell.",[1772,1799,1800,1806,1807,493],{},[1775,1801,1802,1803,493],{},"Solid fills in ",[288,1804,1805],{},"fgColor"," For conditional formats, put the colour in ",[288,1808,412],{},[1772,1810,1811,1814],{},[1775,1812,1813],{},"Too many colours."," One highlight for \"good\" and one for \"bad\" is readable; five rules on one column are not.",[276,1816,1818],{"id":1817},"performance-notes","Performance notes",[10,1820,1821,1822,1824,1825,1828],{},"Rules are stored once per range and cost nothing at write time. ",[288,1823,1185],{}," rules are evaluated per cell by the spreadsheet, which is fine for thousands of rows; on sheets of hundreds of thousands, a flag column computed in pandas and a simple rule on it (",[288,1826,1827],{},"=$D2=\"top\"",") recalculates faster.",[276,1830,1832],{"id":1831},"conclusion","Conclusion",[10,1834,1835,1838,1839,409,1841,1843,1844,1846,1847,1849,1850,1853,1854,1856,1857,1859],{},[288,1836,1837],{},"Rule(type=\"top10\")"," with ",[288,1840,507],{},[288,1842,614],{}," and ",[288,1845,769],{}," covers top and bottom N and percentages, ties included; ",[288,1848,898],{}," compares with the mean; a ",[288,1851,1852],{},"LARGE","-based formula rule colours whole rows; and a ",[288,1855,1185],{}," formula rule finds the top value in each group. Anchor columns with ",[288,1858,1075],{},", extend ranges to cover growing data, and verify the result by rendering the workbook with LibreOffice before it is sent.",[276,1861,1863],{"id":1862},"frequently-asked-questions","Frequently asked questions",[10,1865,1866,1869],{},[1775,1867,1868],{},"Why does a top 3 rule highlight four cells?","\nTies. The rule highlights every cell whose value is among the top three values, so if two reps share the third-highest figure, both are marked. That is usually what readers expect; rank the data in Python if exactly N rows must be marked.",[10,1871,1872,1875],{},[1775,1873,1874],{},"Can I highlight the top value in each region?","\nNot with the built-in top10 rule, which ranks the whole range. Use a formula rule with COUNTIFS that counts how many rows in the same region have higher sales, and highlight the rows where that count is zero, as shown in Step 5.",[10,1877,1878,1881],{},[1775,1879,1880],{},"How do I highlight the whole row instead of one cell?","\nApply a formula rule to the full row range with the column locked, for example =$C2>=LARGE($C$2:$C$8,3) on A2:C8. The dollar sign before C makes every cell in a row test the same sales value.",[10,1883,1884,1887],{},[1775,1885,1886],{},"Does the highlighting update when the numbers change?","\nYes. Conditional formatting is evaluated by the spreadsheet whenever values change, so a rule written once keeps marking the current top values after edits or refreshes.",[276,1889,1891],{"id":1890},"related","Related",[1769,1893,1894,1900,1907,1914],{},[1772,1895,1896,1897,1899],{},"Up one level: ",[19,1898,22],{"href":21}," — every rule type.",[1772,1901,1902,1906],{},[19,1903,1905],{"href":1904},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-cells-above-a-threshold-with-openpyxl\u002F","Highlight Cells Above a Threshold with openpyxl"," — fixed cut-offs instead of ranks.",[1772,1908,1909,1913],{},[19,1910,1912],{"href":1911},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fget-the-top-n-rows-per-group-in-pandas\u002F","Get the Top N Rows per Group in pandas"," — computing the same answer in Python.",[1772,1915,1916,1920],{},[19,1917,1919],{"href":1918},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frank-and-percentile-formulas-in-pandas\u002F","RANK and PERCENTILE Formulas in pandas"," — ranking without formatting.",[1922,1923,1924],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .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":286,"searchDepth":337,"depth":337,"links":1926},[1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938],{"id":278,"depth":337,"text":279},{"id":496,"depth":337,"text":497},{"id":722,"depth":337,"text":723},{"id":942,"depth":337,"text":943},{"id":1068,"depth":337,"text":1069},{"id":1178,"depth":337,"text":1179},{"id":1459,"depth":337,"text":1460},{"id":1766,"depth":337,"text":1767},{"id":1817,"depth":337,"text":1818},{"id":1831,"depth":337,"text":1832},{"id":1862,"depth":337,"text":1863},{"id":1890,"depth":337,"text":1891},"2026-09-25","Mark the top three sales, the bottom 10%, everything above average, or the best rep in each region with conditional formatting written from Python. Covers the top10 and aboveAverage rules, how ties behave, highlighting whole rows, top-N per group with COUNTIFS, and checking the result without Excel.","md",[1943,1945,1947,1949],{"q":1868,"a":1944},"Ties. The rule highlights every cell whose value is among the top three values, so if two reps share the third-highest figure, both are marked. That is usually what readers expect; rank the data in Python if exactly N rows must be marked.",{"q":1874,"a":1946},"Not with the built-in top10 rule, which ranks the whole range. Use a formula rule with COUNTIFS that counts how many rows in the same region have higher sales, and highlight the rows where that count is zero, as shown in Step 5.",{"q":1880,"a":1948},"Apply a formula rule to the full row range with the column locked, for example =$C2>=LARGE($C$2:$C$8,3) on A2:C8. The dollar sign before C makes every cell in a row test the same sales value.",{"q":1886,"a":1950},"Yes. Conditional formatting is evaluated by the spreadsheet whenever values change, so a rule written once keeps marking the current top values after edits or refreshes.","highlight top 10 values with openpyxl conditional formatting",null,{"breadcrumb":1954},[1955,1958,1961],{"name":1956,"item":1957},"Home","\u002F",{"name":1959,"item":1960},"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-top-and-bottom-values-with-openpyxl",{"title":1964,"description":1965},"openpyxl Top 10 \u002F Bottom N Conditional Formatting (and Per Group)","Highlight top N, bottom N, top percent and above-average values with openpyxl conditional formatting rules, handle ties, colour whole rows, highlight the top value per group with COUNTIFS, and verify with LibreOffice.","highlight-top-and-bottom-values-with-openpyxl","advanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-top-and-bottom-values-with-openpyxl\u002Findex",[1969,1970,1971],"conditional-formatting","openpyxl","report-layouts","how-to","6DdCVHTYWu_dyMpyBb0Wf2YqbowhmT6JROB3coi0Ha0",[1975,1979],{"title":1976,"path":1977,"stem":1978,"children":-1},"Highlight Overdue Dates in Excel with openpyxl","\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-overdue-dates-with-openpyxl","advanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-overdue-dates-with-openpyxl\u002Findex",{"title":1980,"path":1981,"stem":1982,"children":-1},"Highlight Values Outside a Tolerance of Target with openpyxl","\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-values-outside-a-tolerance-of-target-with-openpyxl","advanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-values-outside-a-tolerance-of-target-with-openpyxl\u002Findex",[1984,1986],{"path":1985,"title":22},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl",{"path":1987,"title":1959},"\u002Fadvanced-data-transformation-and-cleaning",[],[1990,1993,1996,1999,2002,2005],{"path":1991,"title":1992,"kind":1972},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fapply-conditional-formatting-with-xlsxwriter","Apply Conditional Formatting with xlsxwriter",{"path":1994,"title":1995,"kind":1972},"\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":1997,"title":1998,"kind":1972},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-icon-sets-with-openpyxl","Add Icon Sets to Excel Cells with openpyxl",{"path":2000,"title":2001,"kind":1972},"\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":2003,"title":2004,"kind":1972},"\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":2006,"title":2007,"kind":1972},"\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",1790972078332]