[{"data":1,"prerenderedAt":3920},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python":3912},{"id":4,"title":5,"body":6,"dateModified":3885,"datePublished":3885,"description":3886,"extension":3887,"faq":3888,"meta":3897,"navigation":212,"path":3905,"seo":3906,"slug":3908,"stem":3909,"type":3910,"__hash__":3911},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Findex.md","Creating Excel Tables and Autofilters with Python",{"type":7,"value":8,"toc":3864},"minimark",[9,19,146,151,180,184,506,510,516,665,673,680,684,691,935,953,956,974,1052,1056,1062,1312,1315,1319,1322,1429,1435,1439,1446,1621,1636,1731,1735,1738,1856,1875,1879,1882,2060,2071,2074,2078,2085,2344,2351,2359,2363,2366,2582,2592,2604,2608,2611,2632,2732,2735,2739,2742,3331,3334,3338,3464,3468,3553,3557,3560,3697,3710,3714,3758,3762,3768,3777,3786,3795,3799,3802,3809,3812,3835,3838,3860],[10,11,12,13,18],"p",{},"A generated report is usually read, not just looked at: someone wants their region, this quarter, the rows over a threshold. Two features make that possible without anyone touching the data — an autofilter for the simple case, and a real Excel table for everything else. This guide, part of ",[14,15,17],"a",{"href":16},"\u002Fformatting-and-charting-excel-reports-with-python\u002F","Formatting and Charting Excel Reports with Python",", covers both, plus the sorting, grouping and protection that go with them.",[20,21,29,30,29,34,29,38,29,45,29,55,29,62,29,69,29,75,29,79,29,83,29,86,29,91,29,94,29,97,29,102,29,107,29,112,29,117,29,121,29,124,29,127,29,130,29,133,29,135,29,138,29,140,29,143],"svg",{"viewBox":22,"role":23,"ariaLabelledBy":24,"xmlns":27,"style":28},"0 0 760 260","img",[25,26],"tb-two-t","tb-two-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:760px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[31,32,33],"title",{"id":25},"An autofilter and a table compared",[35,36,37],"desc",{"id":26},"An autofilter adds dropdown arrows to the header row of a range and nothing else. A table adds the same filter plus a name, banded row formatting, structured references and a range Excel maintains as rows are added.",[39,40],"rect",{"x":41,"y":41,"width":42,"height":43,"fill":44},"0","760","260","#ffffff",[39,46],{"x":47,"y":48,"width":49,"height":50,"rx":51,"fill":52,"stroke":53,"style":54},"20","26","350","210","14","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[56,57,61],"text",{"x":58,"y":59,"style":60},"195","52","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","ws.auto_filter",[39,63],{"x":64,"y":65,"width":66,"height":67,"fill":68},"44","66","302","28","#5b5cf0",[56,70,74],{"x":71,"y":72,"style":73},"60","85","font-size:11px;font-weight:700;fill:#ffffff","Region",[76,77],"polygon",{"points":78,"fill":44},"150,74 164,74 157,84",[56,80,82],{"x":81,"y":72,"style":73},"200","Revenue",[76,84],{"points":85,"fill":44},"300,74 314,74 307,84",[39,87],{"x":64,"y":88,"width":66,"height":89,"fill":44,"stroke":90},"94","24","var(--line,#cdd5e6)",[39,92],{"x":64,"y":93,"width":66,"height":89,"fill":44,"stroke":90},"118",[39,95],{"x":64,"y":96,"width":66,"height":89,"fill":44,"stroke":90},"142",[56,98,101],{"x":58,"y":99,"style":100},"190","font-size:11.5px;fill:var(--text,#172033);text-anchor:middle","dropdown arrows, nothing else",[56,103,106],{"x":58,"y":104,"style":105},"212","font-size:11.5px;fill:var(--muted,#5b6780);text-anchor:middle","one line, tolerant of any layout",[39,108],{"x":109,"y":48,"width":49,"height":50,"rx":51,"fill":110,"stroke":111,"style":54},"390","#d9f4f1","var(--teal,#0f9488)",[56,113,116],{"x":114,"y":59,"style":115},"565","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Table(\"Orders\", ref=…)",[39,118],{"x":119,"y":65,"width":66,"height":67,"fill":120},"414","#0f766e",[56,122,74],{"x":123,"y":72,"style":73},"430",[76,125],{"points":126,"fill":44},"520,74 534,74 527,84",[56,128,82],{"x":129,"y":72,"style":73},"570",[76,131],{"points":132,"fill":44},"670,74 684,74 677,84",[39,134],{"x":119,"y":88,"width":66,"height":89,"fill":44,"stroke":90},[39,136],{"x":119,"y":93,"width":66,"height":89,"fill":137,"stroke":90},"#e7f4f3",[39,139],{"x":119,"y":96,"width":66,"height":89,"fill":44,"stroke":90},[56,141,142],{"x":114,"y":99,"style":100},"filter + banding + a name",[56,144,145],{"x":114,"y":104,"style":105},"Orders[Revenue] works in formulas",[147,148,150],"h2",{"id":149},"install-the-dependencies","Install the dependencies",[152,153,158],"pre",{"className":154,"code":155,"language":156,"meta":157,"style":157},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[159,160,161],"code",{"__ignoreMap":157},[162,163,166,170,174,177],"span",{"class":164,"line":165},"line",1,[162,167,169],{"class":168},"sMTad","pip",[162,171,173],{"class":172},"srMev"," install",[162,175,176],{"class":172}," pandas",[162,178,179],{"class":172}," openpyxl\n",[147,181,183],{"id":182},"build-a-sheet-worth-filtering","Build a sheet worth filtering",[152,185,189],{"className":186,"code":187,"language":188,"meta":157,"style":157},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nrows = [\n    (\"2026-01-14\", \"North\", \"A-100\", 4, 19.99),\n    (\"2026-01-15\", \"South\", \"B-200\", 2, 49.50),\n    (\"2026-01-19\", \"West\", \"C-300\", 7, 12.25),\n    (\"2026-02-02\", \"North\", \"A-100\", 5, 19.99),\n    (\"2026-02-11\", \"Central\", \"D-400\", 3, 8.75),\n]\ndf = pd.DataFrame(rows, columns=[\"Order_Date\", \"Region\", \"SKU\", \"Quantity\", \"Unit_Price\"])\ndf[\"Revenue\"] = (df[\"Quantity\"] * df[\"Unit_Price\"]).round(2)\ndf.to_excel(\"orders.xlsx\", index=False, sheet_name=\"Orders\")\nprint(df)\n","python",[159,190,191,207,214,226,260,290,320,347,377,383,429,466,497],{"__ignoreMap":157},[162,192,193,197,201,204],{"class":164,"line":165},[162,194,196],{"class":195},"s-kum","import",[162,198,200],{"class":199},"skGVy"," pandas ",[162,202,203],{"class":195},"as",[162,205,206],{"class":199}," pd\n",[162,208,210],{"class":164,"line":209},2,[162,211,213],{"emptyLinePlaceholder":212},true,"\n",[162,215,217,220,223],{"class":164,"line":216},3,[162,218,219],{"class":199},"rows ",[162,221,222],{"class":195},"=",[162,224,225],{"class":199}," [\n",[162,227,229,232,235,238,241,243,246,248,252,254,257],{"class":164,"line":228},4,[162,230,231],{"class":199},"    (",[162,233,234],{"class":172},"\"2026-01-14\"",[162,236,237],{"class":199},", ",[162,239,240],{"class":172},"\"North\"",[162,242,237],{"class":199},[162,244,245],{"class":172},"\"A-100\"",[162,247,237],{"class":199},[162,249,251],{"class":250},"sP0c6","4",[162,253,237],{"class":199},[162,255,256],{"class":250},"19.99",[162,258,259],{"class":199},"),\n",[162,261,263,265,268,270,273,275,278,280,283,285,288],{"class":164,"line":262},5,[162,264,231],{"class":199},[162,266,267],{"class":172},"\"2026-01-15\"",[162,269,237],{"class":199},[162,271,272],{"class":172},"\"South\"",[162,274,237],{"class":199},[162,276,277],{"class":172},"\"B-200\"",[162,279,237],{"class":199},[162,281,282],{"class":250},"2",[162,284,237],{"class":199},[162,286,287],{"class":250},"49.50",[162,289,259],{"class":199},[162,291,293,295,298,300,303,305,308,310,313,315,318],{"class":164,"line":292},6,[162,294,231],{"class":199},[162,296,297],{"class":172},"\"2026-01-19\"",[162,299,237],{"class":199},[162,301,302],{"class":172},"\"West\"",[162,304,237],{"class":199},[162,306,307],{"class":172},"\"C-300\"",[162,309,237],{"class":199},[162,311,312],{"class":250},"7",[162,314,237],{"class":199},[162,316,317],{"class":250},"12.25",[162,319,259],{"class":199},[162,321,323,325,328,330,332,334,336,338,341,343,345],{"class":164,"line":322},7,[162,324,231],{"class":199},[162,326,327],{"class":172},"\"2026-02-02\"",[162,329,237],{"class":199},[162,331,240],{"class":172},[162,333,237],{"class":199},[162,335,245],{"class":172},[162,337,237],{"class":199},[162,339,340],{"class":250},"5",[162,342,237],{"class":199},[162,344,256],{"class":250},[162,346,259],{"class":199},[162,348,350,352,355,357,360,362,365,367,370,372,375],{"class":164,"line":349},8,[162,351,231],{"class":199},[162,353,354],{"class":172},"\"2026-02-11\"",[162,356,237],{"class":199},[162,358,359],{"class":172},"\"Central\"",[162,361,237],{"class":199},[162,363,364],{"class":172},"\"D-400\"",[162,366,237],{"class":199},[162,368,369],{"class":250},"3",[162,371,237],{"class":199},[162,373,374],{"class":250},"8.75",[162,376,259],{"class":199},[162,378,380],{"class":164,"line":379},9,[162,381,382],{"class":199},"]\n",[162,384,386,389,391,394,398,400,403,406,408,411,413,416,418,421,423,426],{"class":164,"line":385},10,[162,387,388],{"class":199},"df ",[162,390,222],{"class":195},[162,392,393],{"class":199}," pd.DataFrame(rows, ",[162,395,397],{"class":396},"sa561","columns",[162,399,222],{"class":195},[162,401,402],{"class":199},"[",[162,404,405],{"class":172},"\"Order_Date\"",[162,407,237],{"class":199},[162,409,410],{"class":172},"\"Region\"",[162,412,237],{"class":199},[162,414,415],{"class":172},"\"SKU\"",[162,417,237],{"class":199},[162,419,420],{"class":172},"\"Quantity\"",[162,422,237],{"class":199},[162,424,425],{"class":172},"\"Unit_Price\"",[162,427,428],{"class":199},"])\n",[162,430,432,435,438,441,443,446,448,450,453,456,458,461,463],{"class":164,"line":431},11,[162,433,434],{"class":199},"df[",[162,436,437],{"class":172},"\"Revenue\"",[162,439,440],{"class":199},"] ",[162,442,222],{"class":195},[162,444,445],{"class":199}," (df[",[162,447,420],{"class":172},[162,449,440],{"class":199},[162,451,452],{"class":195},"*",[162,454,455],{"class":199}," df[",[162,457,425],{"class":172},[162,459,460],{"class":199},"]).round(",[162,462,282],{"class":250},[162,464,465],{"class":199},")\n",[162,467,469,472,475,477,480,482,485,487,490,492,495],{"class":164,"line":468},12,[162,470,471],{"class":199},"df.to_excel(",[162,473,474],{"class":172},"\"orders.xlsx\"",[162,476,237],{"class":199},[162,478,479],{"class":396},"index",[162,481,222],{"class":195},[162,483,484],{"class":250},"False",[162,486,237],{"class":199},[162,488,489],{"class":396},"sheet_name",[162,491,222],{"class":195},[162,493,494],{"class":172},"\"Orders\"",[162,496,465],{"class":199},[162,498,500,503],{"class":164,"line":499},13,[162,501,502],{"class":250},"print",[162,504,505],{"class":199},"(df)\n",[147,507,509],{"id":508},"the-one-line-autofilter","The one-line autofilter",[10,511,512,515],{},[159,513,514],{},"ws.auto_filter.ref"," is the whole feature. Point it at the header plus the data and Excel adds the dropdowns:",[152,517,519],{"className":186,"code":518,"language":188,"meta":157,"style":157},"from openpyxl import load_workbook\nfrom openpyxl.utils import get_column_letter\n\nwb = load_workbook(\"orders.xlsx\")\nws = wb[\"Orders\"]\n\nlast_col = get_column_letter(ws.max_column)\nws.auto_filter.ref = f\"A1:{last_col}{ws.max_row}\"\nws.freeze_panes = \"A2\"                       # keep the header visible while scrolling\n\nwb.save(\"orders_filtered.xlsx\")\nprint(\"filter on\", ws.auto_filter.ref)\n",[159,520,521,534,546,550,564,578,582,592,624,638,642,652],{"__ignoreMap":157},[162,522,523,526,529,531],{"class":164,"line":165},[162,524,525],{"class":195},"from",[162,527,528],{"class":199}," openpyxl ",[162,530,196],{"class":195},[162,532,533],{"class":199}," load_workbook\n",[162,535,536,538,541,543],{"class":164,"line":209},[162,537,525],{"class":195},[162,539,540],{"class":199}," openpyxl.utils ",[162,542,196],{"class":195},[162,544,545],{"class":199}," get_column_letter\n",[162,547,548],{"class":164,"line":216},[162,549,213],{"emptyLinePlaceholder":212},[162,551,552,555,557,560,562],{"class":164,"line":228},[162,553,554],{"class":199},"wb ",[162,556,222],{"class":195},[162,558,559],{"class":199}," load_workbook(",[162,561,474],{"class":172},[162,563,465],{"class":199},[162,565,566,569,571,574,576],{"class":164,"line":262},[162,567,568],{"class":199},"ws ",[162,570,222],{"class":195},[162,572,573],{"class":199}," wb[",[162,575,494],{"class":172},[162,577,382],{"class":199},[162,579,580],{"class":164,"line":292},[162,581,213],{"emptyLinePlaceholder":212},[162,583,584,587,589],{"class":164,"line":322},[162,585,586],{"class":199},"last_col ",[162,588,222],{"class":195},[162,590,591],{"class":199}," get_column_letter(ws.max_column)\n",[162,593,594,597,599,602,605,609,612,615,618,621],{"class":164,"line":349},[162,595,596],{"class":199},"ws.auto_filter.ref ",[162,598,222],{"class":195},[162,600,601],{"class":195}," f",[162,603,604],{"class":172},"\"A1:",[162,606,608],{"class":607},"sSjpA","{",[162,610,611],{"class":199},"last_col",[162,613,614],{"class":607},"}{",[162,616,617],{"class":199},"ws.max_row",[162,619,620],{"class":607},"}",[162,622,623],{"class":172},"\"\n",[162,625,626,629,631,634],{"class":164,"line":379},[162,627,628],{"class":199},"ws.freeze_panes ",[162,630,222],{"class":195},[162,632,633],{"class":172}," \"A2\"",[162,635,637],{"class":636},"s-wDw","                       # keep the header visible while scrolling\n",[162,639,640],{"class":164,"line":385},[162,641,213],{"emptyLinePlaceholder":212},[162,643,644,647,650],{"class":164,"line":431},[162,645,646],{"class":199},"wb.save(",[162,648,649],{"class":172},"\"orders_filtered.xlsx\"",[162,651,465],{"class":199},[162,653,654,656,659,662],{"class":164,"line":468},[162,655,502],{"class":250},[162,657,658],{"class":199},"(",[162,660,661],{"class":172},"\"filter on\"",[162,663,664],{"class":199},", ws.auto_filter.ref)\n",[10,666,667,668,672],{},"Freezing the header is not optional in practice. A filter on a sheet where the header scrolls away leaves the reader looking at anonymous columns, and the two features are always worth applying together — see ",[14,669,671],{"href":670},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Ffreeze-header-row-in-excel-with-openpyxl\u002F","freezing the header row"," for the variations.",[10,674,675,676,679],{},"The range must include the header row, because that is where the dropdowns are drawn. Ending it short of the last data row is the usual bug: rows below the range are not filtered, so a reader who filters to ",[159,677,678],{},"North"," still sees stray rows from other regions at the bottom.",[147,681,683],{"id":682},"a-real-excel-table","A real Excel table",[10,685,686,687,690],{},"A ",[159,688,689],{},"Table"," object gives the filter plus banding, a name and structured references. It is stricter about its range, and that strictness is what lets Excel maintain it:",[152,692,694],{"className":186,"code":693,"language":188,"meta":157,"style":157},"from openpyxl import load_workbook\nfrom openpyxl.worksheet.table import Table, TableStyleInfo\nfrom openpyxl.utils import get_column_letter\n\nwb = load_workbook(\"orders.xlsx\")\nws = wb[\"Orders\"]\n\nref = f\"A1:{get_column_letter(ws.max_column)}{ws.max_row}\"\ntable = Table(displayName=\"Orders\", ref=ref)\ntable.tableStyleInfo = TableStyleInfo(\n    name=\"TableStyleMedium9\",\n    showRowStripes=True,\n    showColumnStripes=False,\n    showFirstColumn=False,\n    showLastColumn=False,\n)\nws.add_table(table)\nws.freeze_panes = \"A2\"\n\nwb.save(\"orders_table.xlsx\")\nprint(\"tables:\", list(ws.tables))\n",[159,695,696,706,718,728,732,744,756,760,784,811,821,834,846,857,869,881,886,892,902,907,917],{"__ignoreMap":157},[162,697,698,700,702,704],{"class":164,"line":165},[162,699,525],{"class":195},[162,701,528],{"class":199},[162,703,196],{"class":195},[162,705,533],{"class":199},[162,707,708,710,713,715],{"class":164,"line":209},[162,709,525],{"class":195},[162,711,712],{"class":199}," openpyxl.worksheet.table ",[162,714,196],{"class":195},[162,716,717],{"class":199}," Table, TableStyleInfo\n",[162,719,720,722,724,726],{"class":164,"line":216},[162,721,525],{"class":195},[162,723,540],{"class":199},[162,725,196],{"class":195},[162,727,545],{"class":199},[162,729,730],{"class":164,"line":228},[162,731,213],{"emptyLinePlaceholder":212},[162,733,734,736,738,740,742],{"class":164,"line":262},[162,735,554],{"class":199},[162,737,222],{"class":195},[162,739,559],{"class":199},[162,741,474],{"class":172},[162,743,465],{"class":199},[162,745,746,748,750,752,754],{"class":164,"line":292},[162,747,568],{"class":199},[162,749,222],{"class":195},[162,751,573],{"class":199},[162,753,494],{"class":172},[162,755,382],{"class":199},[162,757,758],{"class":164,"line":322},[162,759,213],{"emptyLinePlaceholder":212},[162,761,762,765,767,769,771,773,776,778,780,782],{"class":164,"line":349},[162,763,764],{"class":199},"ref ",[162,766,222],{"class":195},[162,768,601],{"class":195},[162,770,604],{"class":172},[162,772,608],{"class":607},[162,774,775],{"class":199},"get_column_letter(ws.max_column)",[162,777,614],{"class":607},[162,779,617],{"class":199},[162,781,620],{"class":607},[162,783,623],{"class":172},[162,785,786,789,791,794,797,799,801,803,806,808],{"class":164,"line":379},[162,787,788],{"class":199},"table ",[162,790,222],{"class":195},[162,792,793],{"class":199}," Table(",[162,795,796],{"class":396},"displayName",[162,798,222],{"class":195},[162,800,494],{"class":172},[162,802,237],{"class":199},[162,804,805],{"class":396},"ref",[162,807,222],{"class":195},[162,809,810],{"class":199},"ref)\n",[162,812,813,816,818],{"class":164,"line":385},[162,814,815],{"class":199},"table.tableStyleInfo ",[162,817,222],{"class":195},[162,819,820],{"class":199}," TableStyleInfo(\n",[162,822,823,826,828,831],{"class":164,"line":431},[162,824,825],{"class":396},"    name",[162,827,222],{"class":195},[162,829,830],{"class":172},"\"TableStyleMedium9\"",[162,832,833],{"class":199},",\n",[162,835,836,839,841,844],{"class":164,"line":468},[162,837,838],{"class":396},"    showRowStripes",[162,840,222],{"class":195},[162,842,843],{"class":250},"True",[162,845,833],{"class":199},[162,847,848,851,853,855],{"class":164,"line":499},[162,849,850],{"class":396},"    showColumnStripes",[162,852,222],{"class":195},[162,854,484],{"class":250},[162,856,833],{"class":199},[162,858,860,863,865,867],{"class":164,"line":859},14,[162,861,862],{"class":396},"    showFirstColumn",[162,864,222],{"class":195},[162,866,484],{"class":250},[162,868,833],{"class":199},[162,870,872,875,877,879],{"class":164,"line":871},15,[162,873,874],{"class":396},"    showLastColumn",[162,876,222],{"class":195},[162,878,484],{"class":250},[162,880,833],{"class":199},[162,882,884],{"class":164,"line":883},16,[162,885,465],{"class":199},[162,887,889],{"class":164,"line":888},17,[162,890,891],{"class":199},"ws.add_table(table)\n",[162,893,895,897,899],{"class":164,"line":894},18,[162,896,628],{"class":199},[162,898,222],{"class":195},[162,900,901],{"class":172}," \"A2\"\n",[162,903,905],{"class":164,"line":904},19,[162,906,213],{"emptyLinePlaceholder":212},[162,908,910,912,915],{"class":164,"line":909},20,[162,911,646],{"class":199},[162,913,914],{"class":172},"\"orders_table.xlsx\"",[162,916,465],{"class":199},[162,918,920,922,924,927,929,932],{"class":164,"line":919},21,[162,921,502],{"class":250},[162,923,658],{"class":199},[162,925,926],{"class":172},"\"tables:\"",[162,928,237],{"class":199},[162,930,931],{"class":250},"list",[162,933,934],{"class":199},"(ws.tables))\n",[10,936,937,939,940,944,945,948,949,952],{},[159,938,796],{}," follows the same rules as a ",[14,941,943],{"href":942},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fcreate-named-range-in-excel-with-openpyxl\u002F","defined name",": no spaces, not starting with a digit, unique within the workbook. The name is what makes ",[159,946,947],{},"=SUM(Orders[Revenue])"," work, which is far more robust than ",[159,950,951],{},"=SUM(F2:F6)"," when rows are added.",[10,954,955],{},"Three rules prevent the \"Excel found unreadable content\" dialog that follows a badly built table:",[957,958,959,966,969],"ul",{},[960,961,962,963,965],"li",{},"every header cell in the first row of ",[159,964,805],{}," must be a non-empty string",[960,967,968],{},"header names must be unique, including after Excel's own case-insensitive comparison",[960,970,971,973],{},[159,972,805],{}," must cover exactly the header plus the data, with no trailing blank rows",[20,975,29,980,29,983,29,986,29,989,29,991,29,995,29,998,29,1001,29,1005,29,1008,29,1011,29,1014,29,1018,29,1021,29,1025,29,1029,29,1032,29,1034,29,1038,29,1041,29,1043,29,1046,29,1049],{"viewBox":976,"role":23,"ariaLabelledBy":977,"xmlns":27,"style":28},"0 0 760 236",[978,979],"tb-rules-t","tb-rules-d",[31,981,982],{"id":978},"Table definitions Excel accepts and rejects",[35,984,985],{"id":979},"A valid table has unique non-empty text headers and a range covering exactly the header and data rows. Invalid ones have a blank header, two identically named columns, or a range extending past the data into empty rows.",[39,987],{"x":41,"y":41,"width":42,"height":988,"fill":44},"236",[39,990],{"x":47,"y":48,"width":49,"height":99,"rx":51,"fill":110,"stroke":111,"style":54},[56,992,994],{"x":58,"y":59,"style":993},"font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","accepted",[39,996],{"x":64,"y":997,"width":66,"height":48,"fill":120},"68",[56,999,74],{"x":71,"y":1000,"style":73},"86",[56,1002,1004],{"x":1003,"y":1000,"style":73},"180","Quantity",[56,1006,82],{"x":1007,"y":1000,"style":73},"270",[39,1009],{"x":64,"y":88,"width":66,"height":1010,"fill":44,"stroke":90},"22",[39,1012],{"x":64,"y":1013,"width":66,"height":1010,"fill":137,"stroke":90},"116",[56,1015,1017],{"x":58,"y":1016,"style":100},"166","unique, non-empty headers",[56,1019,1020],{"x":58,"y":99,"style":100},"ref = A1:C3, exactly the data",[39,1022],{"x":109,"y":48,"width":49,"height":99,"rx":51,"fill":1023,"stroke":1024,"style":54},"#fce9e9","var(--danger,#dc2626)",[56,1026,1028],{"x":114,"y":59,"style":1027},"font-size:13px;font-weight:700;fill:var(--danger,#dc2626);text-anchor:middle","rejected — file is \"repaired\"",[39,1030],{"x":119,"y":997,"width":66,"height":48,"fill":1031},"#d81b73",[56,1033,74],{"x":123,"y":1000,"style":73},[56,1035,1037],{"x":1036,"y":1000,"style":73},"550","(blank)",[56,1039,74],{"x":1040,"y":1000,"style":73},"640",[39,1042],{"x":119,"y":88,"width":66,"height":1010,"fill":44,"stroke":90},[39,1044],{"x":119,"y":1013,"width":66,"height":1010,"fill":44,"stroke":90,"strokeDashArray":1045},[251,369],[56,1047,1048],{"x":114,"y":1016,"style":100},"blank header · duplicate name",[56,1050,1051],{"x":114,"y":99,"style":100},"ref runs past the last data row",[147,1053,1055],{"id":1054},"sort-before-you-write","Sort before you write",[10,1057,1058,1061],{},[159,1059,1060],{},"auto_filter.add_sort_condition"," records a sort state for Excel's interface — it does not reorder anything in the file. If the order matters, sort the data before writing it:",[152,1063,1065],{"className":186,"code":1064,"language":188,"meta":157,"style":157},"import pandas as pd\nfrom openpyxl import load_workbook\nfrom openpyxl.utils import get_column_letter\n\ndf = pd.read_excel(\"orders.xlsx\", sheet_name=\"Orders\")\nordered = df.sort_values([\"Region\", \"Revenue\"], ascending=[True, False])\n\nwith pd.ExcelWriter(\"orders_sorted.xlsx\", engine=\"openpyxl\") as writer:\n    ordered.to_excel(writer, sheet_name=\"Orders\", index=False)\n\nwb = load_workbook(\"orders_sorted.xlsx\")\nws = wb[\"Orders\"]\nref = f\"A1:{get_column_letter(ws.max_column)}{ws.max_row}\"\nws.auto_filter.ref = ref\nws.auto_filter.add_sort_condition(f\"F2:F{ws.max_row}\", descending=True)   # UI state only\nwb.save(\"orders_sorted.xlsx\")\n",[159,1066,1067,1077,1087,1097,1101,1122,1156,1160,1189,1210,1214,1226,1238,1260,1269,1304],{"__ignoreMap":157},[162,1068,1069,1071,1073,1075],{"class":164,"line":165},[162,1070,196],{"class":195},[162,1072,200],{"class":199},[162,1074,203],{"class":195},[162,1076,206],{"class":199},[162,1078,1079,1081,1083,1085],{"class":164,"line":209},[162,1080,525],{"class":195},[162,1082,528],{"class":199},[162,1084,196],{"class":195},[162,1086,533],{"class":199},[162,1088,1089,1091,1093,1095],{"class":164,"line":216},[162,1090,525],{"class":195},[162,1092,540],{"class":199},[162,1094,196],{"class":195},[162,1096,545],{"class":199},[162,1098,1099],{"class":164,"line":228},[162,1100,213],{"emptyLinePlaceholder":212},[162,1102,1103,1105,1107,1110,1112,1114,1116,1118,1120],{"class":164,"line":262},[162,1104,388],{"class":199},[162,1106,222],{"class":195},[162,1108,1109],{"class":199}," pd.read_excel(",[162,1111,474],{"class":172},[162,1113,237],{"class":199},[162,1115,489],{"class":396},[162,1117,222],{"class":195},[162,1119,494],{"class":172},[162,1121,465],{"class":199},[162,1123,1124,1127,1129,1132,1134,1136,1138,1141,1144,1146,1148,1150,1152,1154],{"class":164,"line":292},[162,1125,1126],{"class":199},"ordered ",[162,1128,222],{"class":195},[162,1130,1131],{"class":199}," df.sort_values([",[162,1133,410],{"class":172},[162,1135,237],{"class":199},[162,1137,437],{"class":172},[162,1139,1140],{"class":199},"], ",[162,1142,1143],{"class":396},"ascending",[162,1145,222],{"class":195},[162,1147,402],{"class":199},[162,1149,843],{"class":250},[162,1151,237],{"class":199},[162,1153,484],{"class":250},[162,1155,428],{"class":199},[162,1157,1158],{"class":164,"line":322},[162,1159,213],{"emptyLinePlaceholder":212},[162,1161,1162,1165,1168,1171,1173,1176,1178,1181,1184,1186],{"class":164,"line":349},[162,1163,1164],{"class":195},"with",[162,1166,1167],{"class":199}," pd.ExcelWriter(",[162,1169,1170],{"class":172},"\"orders_sorted.xlsx\"",[162,1172,237],{"class":199},[162,1174,1175],{"class":396},"engine",[162,1177,222],{"class":195},[162,1179,1180],{"class":172},"\"openpyxl\"",[162,1182,1183],{"class":199},") ",[162,1185,203],{"class":195},[162,1187,1188],{"class":199}," writer:\n",[162,1190,1191,1194,1196,1198,1200,1202,1204,1206,1208],{"class":164,"line":379},[162,1192,1193],{"class":199},"    ordered.to_excel(writer, ",[162,1195,489],{"class":396},[162,1197,222],{"class":195},[162,1199,494],{"class":172},[162,1201,237],{"class":199},[162,1203,479],{"class":396},[162,1205,222],{"class":195},[162,1207,484],{"class":250},[162,1209,465],{"class":199},[162,1211,1212],{"class":164,"line":385},[162,1213,213],{"emptyLinePlaceholder":212},[162,1215,1216,1218,1220,1222,1224],{"class":164,"line":431},[162,1217,554],{"class":199},[162,1219,222],{"class":195},[162,1221,559],{"class":199},[162,1223,1170],{"class":172},[162,1225,465],{"class":199},[162,1227,1228,1230,1232,1234,1236],{"class":164,"line":468},[162,1229,568],{"class":199},[162,1231,222],{"class":195},[162,1233,573],{"class":199},[162,1235,494],{"class":172},[162,1237,382],{"class":199},[162,1239,1240,1242,1244,1246,1248,1250,1252,1254,1256,1258],{"class":164,"line":499},[162,1241,764],{"class":199},[162,1243,222],{"class":195},[162,1245,601],{"class":195},[162,1247,604],{"class":172},[162,1249,608],{"class":607},[162,1251,775],{"class":199},[162,1253,614],{"class":607},[162,1255,617],{"class":199},[162,1257,620],{"class":607},[162,1259,623],{"class":172},[162,1261,1262,1264,1266],{"class":164,"line":859},[162,1263,596],{"class":199},[162,1265,222],{"class":195},[162,1267,1268],{"class":199}," ref\n",[162,1270,1271,1274,1277,1280,1282,1284,1286,1289,1291,1294,1296,1298,1301],{"class":164,"line":871},[162,1272,1273],{"class":199},"ws.auto_filter.add_sort_condition(",[162,1275,1276],{"class":195},"f",[162,1278,1279],{"class":172},"\"F2:F",[162,1281,608],{"class":607},[162,1283,617],{"class":199},[162,1285,620],{"class":607},[162,1287,1288],{"class":172},"\"",[162,1290,237],{"class":199},[162,1292,1293],{"class":396},"descending",[162,1295,222],{"class":195},[162,1297,843],{"class":250},[162,1299,1300],{"class":199},")   ",[162,1302,1303],{"class":636},"# UI state only\n",[162,1305,1306,1308,1310],{"class":164,"line":883},[162,1307,646],{"class":199},[162,1309,1170],{"class":172},[162,1311,465],{"class":199},[10,1313,1314],{},"The sort condition is still worth setting, because it tells Excel to show the sort indicator on the column a reader expects — but the rows are in the order pandas put them, and that is the order anyone reading the raw file will see.",[147,1316,1318],{"id":1317},"group-rows-so-a-long-sheet-collapses","Group rows so a long sheet collapses",[10,1320,1321],{},"Outline groups turn a thousand-row detail sheet into a summary a reader can expand selectively:",[152,1323,1325],{"className":186,"code":1324,"language":188,"meta":157,"style":157},"from openpyxl import load_workbook\n\nwb = load_workbook(\"orders_sorted.xlsx\")\nws = wb[\"Orders\"]\n\n# Collapse the January block (rows 2-4 in this example)\nws.row_dimensions.group(2, 4, outline_level=1, hidden=True)\nws.sheet_properties.outlinePr.summaryBelow = True\n\nwb.save(\"orders_grouped.xlsx\")\n",[159,1326,1327,1337,1341,1353,1365,1369,1374,1406,1416,1420],{"__ignoreMap":157},[162,1328,1329,1331,1333,1335],{"class":164,"line":165},[162,1330,525],{"class":195},[162,1332,528],{"class":199},[162,1334,196],{"class":195},[162,1336,533],{"class":199},[162,1338,1339],{"class":164,"line":209},[162,1340,213],{"emptyLinePlaceholder":212},[162,1342,1343,1345,1347,1349,1351],{"class":164,"line":216},[162,1344,554],{"class":199},[162,1346,222],{"class":195},[162,1348,559],{"class":199},[162,1350,1170],{"class":172},[162,1352,465],{"class":199},[162,1354,1355,1357,1359,1361,1363],{"class":164,"line":228},[162,1356,568],{"class":199},[162,1358,222],{"class":195},[162,1360,573],{"class":199},[162,1362,494],{"class":172},[162,1364,382],{"class":199},[162,1366,1367],{"class":164,"line":262},[162,1368,213],{"emptyLinePlaceholder":212},[162,1370,1371],{"class":164,"line":292},[162,1372,1373],{"class":636},"# Collapse the January block (rows 2-4 in this example)\n",[162,1375,1376,1379,1381,1383,1385,1387,1390,1392,1395,1397,1400,1402,1404],{"class":164,"line":322},[162,1377,1378],{"class":199},"ws.row_dimensions.group(",[162,1380,282],{"class":250},[162,1382,237],{"class":199},[162,1384,251],{"class":250},[162,1386,237],{"class":199},[162,1388,1389],{"class":396},"outline_level",[162,1391,222],{"class":195},[162,1393,1394],{"class":250},"1",[162,1396,237],{"class":199},[162,1398,1399],{"class":396},"hidden",[162,1401,222],{"class":195},[162,1403,843],{"class":250},[162,1405,465],{"class":199},[162,1407,1408,1411,1413],{"class":164,"line":349},[162,1409,1410],{"class":199},"ws.sheet_properties.outlinePr.summaryBelow ",[162,1412,222],{"class":195},[162,1414,1415],{"class":250}," True\n",[162,1417,1418],{"class":164,"line":379},[162,1419,213],{"emptyLinePlaceholder":212},[162,1421,1422,1424,1427],{"class":164,"line":385},[162,1423,646],{"class":199},[162,1425,1426],{"class":172},"\"orders_grouped.xlsx\"",[162,1428,465],{"class":199},[10,1430,1431,1434],{},[159,1432,1433],{},"summaryBelow"," tells Excel where the group's summary row sits, which decides which way the little plus and minus buttons point. Groups nest up to seven levels, and a report that groups by month and then by region reads far better than the same data as one long list.",[147,1436,1438],{"id":1437},"keep-a-total-visible-while-filtering","Keep a total visible while filtering",[10,1440,1441,1442,1445],{},"A total inside the filtered range disappears the moment a reader filters. Put it outside the range and make it follow the visible rows with ",[159,1443,1444],{},"SUBTOTAL",":",[152,1447,1449],{"className":186,"code":1448,"language":188,"meta":157,"style":157},"from openpyxl import load_workbook\n\nwb = load_workbook(\"orders_table.xlsx\")\nws = wb[\"Orders\"]\n\nlast_data = ws.max_row\ntotal_row = last_data + 2                      # one blank row between data and total\nws[f\"E{total_row}\"] = \"Visible total\"\nws[f\"F{total_row}\"] = f\"=SUBTOTAL(109,F2:F{last_data})\"\nws[f\"F{total_row}\"].number_format = \"#,##0.00\"\n\nwb.save(\"orders_table_total.xlsx\")\n",[159,1450,1451,1461,1465,1477,1489,1493,1503,1522,1548,1584,1608,1612],{"__ignoreMap":157},[162,1452,1453,1455,1457,1459],{"class":164,"line":165},[162,1454,525],{"class":195},[162,1456,528],{"class":199},[162,1458,196],{"class":195},[162,1460,533],{"class":199},[162,1462,1463],{"class":164,"line":209},[162,1464,213],{"emptyLinePlaceholder":212},[162,1466,1467,1469,1471,1473,1475],{"class":164,"line":216},[162,1468,554],{"class":199},[162,1470,222],{"class":195},[162,1472,559],{"class":199},[162,1474,914],{"class":172},[162,1476,465],{"class":199},[162,1478,1479,1481,1483,1485,1487],{"class":164,"line":228},[162,1480,568],{"class":199},[162,1482,222],{"class":195},[162,1484,573],{"class":199},[162,1486,494],{"class":172},[162,1488,382],{"class":199},[162,1490,1491],{"class":164,"line":262},[162,1492,213],{"emptyLinePlaceholder":212},[162,1494,1495,1498,1500],{"class":164,"line":292},[162,1496,1497],{"class":199},"last_data ",[162,1499,222],{"class":195},[162,1501,1502],{"class":199}," ws.max_row\n",[162,1504,1505,1508,1510,1513,1516,1519],{"class":164,"line":322},[162,1506,1507],{"class":199},"total_row ",[162,1509,222],{"class":195},[162,1511,1512],{"class":199}," last_data ",[162,1514,1515],{"class":195},"+",[162,1517,1518],{"class":250}," 2",[162,1520,1521],{"class":636},"                      # one blank row between data and total\n",[162,1523,1524,1527,1529,1532,1534,1537,1539,1541,1543,1545],{"class":164,"line":349},[162,1525,1526],{"class":199},"ws[",[162,1528,1276],{"class":195},[162,1530,1531],{"class":172},"\"E",[162,1533,608],{"class":607},[162,1535,1536],{"class":199},"total_row",[162,1538,620],{"class":607},[162,1540,1288],{"class":172},[162,1542,440],{"class":199},[162,1544,222],{"class":195},[162,1546,1547],{"class":172}," \"Visible total\"\n",[162,1549,1550,1552,1554,1557,1559,1561,1563,1565,1567,1569,1571,1574,1576,1579,1581],{"class":164,"line":379},[162,1551,1526],{"class":199},[162,1553,1276],{"class":195},[162,1555,1556],{"class":172},"\"F",[162,1558,608],{"class":607},[162,1560,1536],{"class":199},[162,1562,620],{"class":607},[162,1564,1288],{"class":172},[162,1566,440],{"class":199},[162,1568,222],{"class":195},[162,1570,601],{"class":195},[162,1572,1573],{"class":172},"\"=SUBTOTAL(109,F2:F",[162,1575,608],{"class":607},[162,1577,1578],{"class":199},"last_data",[162,1580,620],{"class":607},[162,1582,1583],{"class":172},")\"\n",[162,1585,1586,1588,1590,1592,1594,1596,1598,1600,1603,1605],{"class":164,"line":385},[162,1587,1526],{"class":199},[162,1589,1276],{"class":195},[162,1591,1556],{"class":172},[162,1593,608],{"class":607},[162,1595,1536],{"class":199},[162,1597,620],{"class":607},[162,1599,1288],{"class":172},[162,1601,1602],{"class":199},"].number_format ",[162,1604,222],{"class":195},[162,1606,1607],{"class":172}," \"#,##0.00\"\n",[162,1609,1610],{"class":164,"line":431},[162,1611,213],{"emptyLinePlaceholder":212},[162,1613,1614,1616,1619],{"class":164,"line":468},[162,1615,646],{"class":199},[162,1617,1618],{"class":172},"\"orders_table_total.xlsx\"",[162,1620,465],{"class":199},[10,1622,1623,1626,1627,1630,1631,1635],{},[159,1624,1625],{},"SUBTOTAL(109, …)"," ignores rows the filter has hidden, so the number always matches what the reader can see — which is what they will assume regardless of what you wrote. A plain ",[159,1628,1629],{},"SUM"," there is the classic source of \"the total does not match the rows\" complaints. The ",[14,1632,1634],{"href":1633},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fadd-sum-and-average-formulas-with-python\u002F","SUM and AVERAGE"," guide covers the function numbers in more detail.",[20,1637,29,1642,29,1645,29,1648,29,1651,29,1656,29,1659,29,1663,29,1666,29,1669,29,1671,29,1673,29,1678,29,1681,29,1686,29,1689,29,1693,29,1696,29,1699,29,1702,29,1704,29,1706,29,1710,29,1715,29,1718,29,1723,29,1726],{"viewBox":1638,"role":23,"ariaLabelledBy":1639,"xmlns":27,"style":28},"0 0 760 220",[1640,1641],"tb-total-t","tb-total-d",[31,1643,1644],{"id":1640},"Where a total row belongs relative to the filter range",[35,1646,1647],{"id":1641},"A total placed inside the filtered range is hidden or filtered away. A total placed below a blank row sits outside the range, stays visible, and uses SUBTOTAL so it reflects only the rows the filter shows.",[39,1649],{"x":41,"y":41,"width":42,"height":1650,"fill":44},"220",[56,1652,1655],{"x":99,"y":1653,"style":1654},"30","font-size:12px;font-weight:700;fill:var(--danger,#dc2626);text-anchor:middle","inside the range",[39,1657],{"x":1653,"y":64,"width":1658,"height":48,"fill":1031},"320",[56,1660,74],{"x":1661,"y":1662,"style":73},"46","62",[56,1664,82],{"x":1665,"y":1662,"style":73},"250",[39,1667],{"x":1653,"y":1668,"width":1658,"height":89,"fill":44,"stroke":90},"70",[39,1670],{"x":1653,"y":88,"width":1658,"height":89,"fill":44,"stroke":90},[39,1672],{"x":1653,"y":93,"width":1658,"height":48,"fill":1023,"stroke":1024},[56,1674,1677],{"x":1661,"y":1675,"style":1676},"136","font-size:11px;font-weight:700;fill:var(--danger,#dc2626)","Total",[56,1679,1680],{"x":1665,"y":1675,"style":1676},"=SUM(...)",[56,1682,1685],{"x":99,"y":1683,"style":1684},"170","font-size:11px;fill:var(--danger,#dc2626);text-anchor:middle","filtered away with the data,",[56,1687,1688],{"x":99,"y":99,"style":1684},"or counts hidden rows",[56,1690,1692],{"x":129,"y":1653,"style":1691},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","outside, with SUBTOTAL",[39,1694],{"x":1695,"y":64,"width":1658,"height":48,"fill":120},"410",[56,1697,74],{"x":1698,"y":1662,"style":73},"426",[56,1700,82],{"x":1701,"y":1662,"style":73},"630",[39,1703],{"x":1695,"y":1668,"width":1658,"height":89,"fill":44,"stroke":90},[39,1705],{"x":1695,"y":88,"width":1658,"height":89,"fill":44,"stroke":90},[39,1707],{"x":1695,"y":93,"width":1658,"height":1708,"fill":1709},"16","#f0f2f5",[56,1711,1714],{"x":129,"y":1712,"style":1713},"130","font-size:10px;font-style:italic;fill:var(--muted,#5b6780);text-anchor:middle","blank spacer row",[39,1716],{"x":1695,"y":1717,"width":1658,"height":48,"fill":110,"stroke":111,"style":54},"134",[56,1719,1722],{"x":1698,"y":1720,"style":1721},"152","font-size:11px;font-weight:700;fill:var(--teal,#0f9488)","Visible total",[56,1724,1725],{"x":1701,"y":1720,"style":1721},"SUBTOTAL(109)",[56,1727,1730],{"x":129,"y":1728,"style":1729},"186","font-size:11px;fill:var(--teal,#0f9488);text-anchor:middle","always visible, always matches the rows shown",[147,1732,1734],{"id":1733},"let-readers-filter-without-letting-them-edit","Let readers filter without letting them edit",[10,1736,1737],{},"Filtering and sorting can be permitted on a protected sheet, which is the combination most delivered reports want:",[152,1739,1741],{"className":186,"code":1740,"language":188,"meta":157,"style":157},"from openpyxl import load_workbook\n\nwb = load_workbook(\"orders_table_total.xlsx\")\nws = wb[\"Orders\"]\n\nws.protection.sheet = True\nws.protection.autoFilter = False      # False = the action is ALLOWED\nws.protection.sort = False\nws.protection.formatCells = True      # True = blocked\nws.protection.password = \"report\"     # deterrent, not security\n\nwb.save(\"orders_protected.xlsx\")\n",[159,1742,1743,1753,1757,1769,1781,1785,1794,1807,1817,1830,1843,1847],{"__ignoreMap":157},[162,1744,1745,1747,1749,1751],{"class":164,"line":165},[162,1746,525],{"class":195},[162,1748,528],{"class":199},[162,1750,196],{"class":195},[162,1752,533],{"class":199},[162,1754,1755],{"class":164,"line":209},[162,1756,213],{"emptyLinePlaceholder":212},[162,1758,1759,1761,1763,1765,1767],{"class":164,"line":216},[162,1760,554],{"class":199},[162,1762,222],{"class":195},[162,1764,559],{"class":199},[162,1766,1618],{"class":172},[162,1768,465],{"class":199},[162,1770,1771,1773,1775,1777,1779],{"class":164,"line":228},[162,1772,568],{"class":199},[162,1774,222],{"class":195},[162,1776,573],{"class":199},[162,1778,494],{"class":172},[162,1780,382],{"class":199},[162,1782,1783],{"class":164,"line":262},[162,1784,213],{"emptyLinePlaceholder":212},[162,1786,1787,1790,1792],{"class":164,"line":292},[162,1788,1789],{"class":199},"ws.protection.sheet ",[162,1791,222],{"class":195},[162,1793,1415],{"class":250},[162,1795,1796,1799,1801,1804],{"class":164,"line":322},[162,1797,1798],{"class":199},"ws.protection.autoFilter ",[162,1800,222],{"class":195},[162,1802,1803],{"class":250}," False",[162,1805,1806],{"class":636},"      # False = the action is ALLOWED\n",[162,1808,1809,1812,1814],{"class":164,"line":349},[162,1810,1811],{"class":199},"ws.protection.sort ",[162,1813,222],{"class":195},[162,1815,1816],{"class":250}," False\n",[162,1818,1819,1822,1824,1827],{"class":164,"line":379},[162,1820,1821],{"class":199},"ws.protection.formatCells ",[162,1823,222],{"class":195},[162,1825,1826],{"class":250}," True",[162,1828,1829],{"class":636},"      # True = blocked\n",[162,1831,1832,1835,1837,1840],{"class":164,"line":385},[162,1833,1834],{"class":199},"ws.protection.password ",[162,1836,222],{"class":195},[162,1838,1839],{"class":172}," \"report\"",[162,1841,1842],{"class":636},"     # deterrent, not security\n",[162,1844,1845],{"class":164,"line":431},[162,1846,213],{"emptyLinePlaceholder":212},[162,1848,1849,1851,1854],{"class":164,"line":468},[162,1850,646],{"class":199},[162,1852,1853],{"class":172},"\"orders_protected.xlsx\"",[162,1855,465],{"class":199},[10,1857,1858,1859,1862,1863,1865,1866,1869,1870,1874],{},"The inverted booleans are a genuine trap: on ",[159,1860,1861],{},"SheetProtection",", a ",[159,1864,484],{}," means \"this action is not protected against\", so ",[159,1867,1868],{},"autoFilter = False"," is what ",[1871,1872,1873],"em",{},"permits"," filtering. Worksheet passwords are trivially removable and should be treated as a way to prevent accidents, never as access control — for that, control who receives the file.",[147,1876,1878],{"id":1877},"filters-on-several-sheets-at-once","Filters on several sheets at once",[10,1880,1881],{},"A workbook with a tab per region needs the same treatment on each, and the range differs per sheet. Deriving it from the sheet rather than hardcoding it is what makes the loop safe:",[152,1883,1885],{"className":186,"code":1884,"language":188,"meta":157,"style":157},"from openpyxl import load_workbook\nfrom openpyxl.utils import get_column_letter\n\nwb = load_workbook(\"regional.xlsx\")\n\nfor ws in wb.worksheets:\n    if ws.max_row \u003C 2:                        # header only, or empty — skip it\n        continue\n    ws.auto_filter.ref = f\"A1:{get_column_letter(ws.max_column)}{ws.max_row}\"\n    ws.freeze_panes = \"A2\"\n    ws.sheet_view.zoomScale = 100\n\nwb.save(\"regional_filtered.xlsx\")\nprint([f\"{ws.title}: {ws.auto_filter.ref}\" for ws in wb.worksheets])\n",[159,1886,1887,1897,1907,1911,1924,1928,1942,1961,1966,1989,1998,2008,2012,2021],{"__ignoreMap":157},[162,1888,1889,1891,1893,1895],{"class":164,"line":165},[162,1890,525],{"class":195},[162,1892,528],{"class":199},[162,1894,196],{"class":195},[162,1896,533],{"class":199},[162,1898,1899,1901,1903,1905],{"class":164,"line":209},[162,1900,525],{"class":195},[162,1902,540],{"class":199},[162,1904,196],{"class":195},[162,1906,545],{"class":199},[162,1908,1909],{"class":164,"line":216},[162,1910,213],{"emptyLinePlaceholder":212},[162,1912,1913,1915,1917,1919,1922],{"class":164,"line":228},[162,1914,554],{"class":199},[162,1916,222],{"class":195},[162,1918,559],{"class":199},[162,1920,1921],{"class":172},"\"regional.xlsx\"",[162,1923,465],{"class":199},[162,1925,1926],{"class":164,"line":262},[162,1927,213],{"emptyLinePlaceholder":212},[162,1929,1930,1933,1936,1939],{"class":164,"line":292},[162,1931,1932],{"class":195},"for",[162,1934,1935],{"class":199}," ws ",[162,1937,1938],{"class":195},"in",[162,1940,1941],{"class":199}," wb.worksheets:\n",[162,1943,1944,1947,1950,1953,1955,1958],{"class":164,"line":322},[162,1945,1946],{"class":195},"    if",[162,1948,1949],{"class":199}," ws.max_row ",[162,1951,1952],{"class":195},"\u003C",[162,1954,1518],{"class":250},[162,1956,1957],{"class":199},":                        ",[162,1959,1960],{"class":636},"# header only, or empty — skip it\n",[162,1962,1963],{"class":164,"line":349},[162,1964,1965],{"class":195},"        continue\n",[162,1967,1968,1971,1973,1975,1977,1979,1981,1983,1985,1987],{"class":164,"line":379},[162,1969,1970],{"class":199},"    ws.auto_filter.ref ",[162,1972,222],{"class":195},[162,1974,601],{"class":195},[162,1976,604],{"class":172},[162,1978,608],{"class":607},[162,1980,775],{"class":199},[162,1982,614],{"class":607},[162,1984,617],{"class":199},[162,1986,620],{"class":607},[162,1988,623],{"class":172},[162,1990,1991,1994,1996],{"class":164,"line":385},[162,1992,1993],{"class":199},"    ws.freeze_panes ",[162,1995,222],{"class":195},[162,1997,901],{"class":172},[162,1999,2000,2003,2005],{"class":164,"line":431},[162,2001,2002],{"class":199},"    ws.sheet_view.zoomScale ",[162,2004,222],{"class":195},[162,2006,2007],{"class":250}," 100\n",[162,2009,2010],{"class":164,"line":468},[162,2011,213],{"emptyLinePlaceholder":212},[162,2013,2014,2016,2019],{"class":164,"line":499},[162,2015,646],{"class":199},[162,2017,2018],{"class":172},"\"regional_filtered.xlsx\"",[162,2020,465],{"class":199},[162,2022,2023,2025,2028,2030,2032,2034,2037,2039,2042,2044,2046,2048,2050,2053,2055,2057],{"class":164,"line":859},[162,2024,502],{"class":250},[162,2026,2027],{"class":199},"([",[162,2029,1276],{"class":195},[162,2031,1288],{"class":172},[162,2033,608],{"class":607},[162,2035,2036],{"class":199},"ws.title",[162,2038,620],{"class":607},[162,2040,2041],{"class":172},": ",[162,2043,608],{"class":607},[162,2045,514],{"class":199},[162,2047,620],{"class":607},[162,2049,1288],{"class":172},[162,2051,2052],{"class":195}," for",[162,2054,1935],{"class":199},[162,2056,1938],{"class":195},[162,2058,2059],{"class":199}," wb.worksheets])\n",[10,2061,2062,2063,2066,2067,2070],{},"The ",[159,2064,2065],{},"max_row \u003C 2"," guard matters because an empty sheet still reports a row count of one, and setting a filter over ",[159,2068,2069],{},"A1:F1"," gives the reader dropdowns on a header with nothing beneath — which looks like the data failed to load. Skipping such sheets, or better still not creating them, is the honest option.",[10,2072,2073],{},"Setting the zoom explicitly is a small courtesy on a wide table: a workbook saved by someone at 70% opens at 70% for everyone, and a reader who has to fix the zoom before reading the report starts out mildly annoyed.",[147,2075,2077],{"id":2076},"apply-it-after-pandas-has-written-the-sheet","Apply it after pandas has written the sheet",[10,2079,2080,2081,2084],{},"Order of operations catches almost everyone once. ",[159,2082,2083],{},"DataFrame.to_excel"," replaces the target sheet wholesale, so a filter, table or freeze applied before the write is discarded without a warning:",[152,2086,2088],{"className":186,"code":2087,"language":188,"meta":157,"style":157},"import pandas as pd\nfrom openpyxl import load_workbook\nfrom openpyxl.utils import get_column_letter\nfrom openpyxl.worksheet.table import Table, TableStyleInfo\n\ndef write_filtered(df, path, sheet=\"Orders\", table_name=\"Orders\"):\n    with pd.ExcelWriter(path, engine=\"openpyxl\") as writer:\n        df.to_excel(writer, sheet_name=sheet, index=False)      # 1. data first\n\n    wb = load_workbook(path)                                     # 2. then decoration\n    ws = wb[sheet]\n    ref = f\"A1:{get_column_letter(ws.max_column)}{ws.max_row}\"\n    table = Table(displayName=table_name, ref=ref)\n    table.tableStyleInfo = TableStyleInfo(name=\"TableStyleMedium9\", showRowStripes=True)\n    ws.add_table(table)\n    ws.freeze_panes = \"A2\"\n    wb.save(path)\n    return path\n\nwrite_filtered(df, \"orders_final.xlsx\")\n",[159,2089,2090,2100,2110,2120,2130,2134,2160,2180,2204,2208,2221,2231,2254,2276,2304,2309,2317,2322,2330,2334],{"__ignoreMap":157},[162,2091,2092,2094,2096,2098],{"class":164,"line":165},[162,2093,196],{"class":195},[162,2095,200],{"class":199},[162,2097,203],{"class":195},[162,2099,206],{"class":199},[162,2101,2102,2104,2106,2108],{"class":164,"line":209},[162,2103,525],{"class":195},[162,2105,528],{"class":199},[162,2107,196],{"class":195},[162,2109,533],{"class":199},[162,2111,2112,2114,2116,2118],{"class":164,"line":216},[162,2113,525],{"class":195},[162,2115,540],{"class":199},[162,2117,196],{"class":195},[162,2119,545],{"class":199},[162,2121,2122,2124,2126,2128],{"class":164,"line":228},[162,2123,525],{"class":195},[162,2125,712],{"class":199},[162,2127,196],{"class":195},[162,2129,717],{"class":199},[162,2131,2132],{"class":164,"line":262},[162,2133,213],{"emptyLinePlaceholder":212},[162,2135,2136,2139,2143,2146,2148,2150,2153,2155,2157],{"class":164,"line":292},[162,2137,2138],{"class":195},"def",[162,2140,2142],{"class":2141},"s_Opv"," write_filtered",[162,2144,2145],{"class":199},"(df, path, sheet",[162,2147,222],{"class":195},[162,2149,494],{"class":172},[162,2151,2152],{"class":199},", table_name",[162,2154,222],{"class":195},[162,2156,494],{"class":172},[162,2158,2159],{"class":199},"):\n",[162,2161,2162,2165,2168,2170,2172,2174,2176,2178],{"class":164,"line":322},[162,2163,2164],{"class":195},"    with",[162,2166,2167],{"class":199}," pd.ExcelWriter(path, ",[162,2169,1175],{"class":396},[162,2171,222],{"class":195},[162,2173,1180],{"class":172},[162,2175,1183],{"class":199},[162,2177,203],{"class":195},[162,2179,1188],{"class":199},[162,2181,2182,2185,2187,2189,2192,2194,2196,2198,2201],{"class":164,"line":349},[162,2183,2184],{"class":199},"        df.to_excel(writer, ",[162,2186,489],{"class":396},[162,2188,222],{"class":195},[162,2190,2191],{"class":199},"sheet, ",[162,2193,479],{"class":396},[162,2195,222],{"class":195},[162,2197,484],{"class":250},[162,2199,2200],{"class":199},")      ",[162,2202,2203],{"class":636},"# 1. data first\n",[162,2205,2206],{"class":164,"line":379},[162,2207,213],{"emptyLinePlaceholder":212},[162,2209,2210,2213,2215,2218],{"class":164,"line":385},[162,2211,2212],{"class":199},"    wb ",[162,2214,222],{"class":195},[162,2216,2217],{"class":199}," load_workbook(path)                                     ",[162,2219,2220],{"class":636},"# 2. then decoration\n",[162,2222,2223,2226,2228],{"class":164,"line":431},[162,2224,2225],{"class":199},"    ws ",[162,2227,222],{"class":195},[162,2229,2230],{"class":199}," wb[sheet]\n",[162,2232,2233,2236,2238,2240,2242,2244,2246,2248,2250,2252],{"class":164,"line":468},[162,2234,2235],{"class":199},"    ref ",[162,2237,222],{"class":195},[162,2239,601],{"class":195},[162,2241,604],{"class":172},[162,2243,608],{"class":607},[162,2245,775],{"class":199},[162,2247,614],{"class":607},[162,2249,617],{"class":199},[162,2251,620],{"class":607},[162,2253,623],{"class":172},[162,2255,2256,2259,2261,2263,2265,2267,2270,2272,2274],{"class":164,"line":499},[162,2257,2258],{"class":199},"    table ",[162,2260,222],{"class":195},[162,2262,793],{"class":199},[162,2264,796],{"class":396},[162,2266,222],{"class":195},[162,2268,2269],{"class":199},"table_name, ",[162,2271,805],{"class":396},[162,2273,222],{"class":195},[162,2275,810],{"class":199},[162,2277,2278,2281,2283,2286,2289,2291,2293,2295,2298,2300,2302],{"class":164,"line":859},[162,2279,2280],{"class":199},"    table.tableStyleInfo ",[162,2282,222],{"class":195},[162,2284,2285],{"class":199}," TableStyleInfo(",[162,2287,2288],{"class":396},"name",[162,2290,222],{"class":195},[162,2292,830],{"class":172},[162,2294,237],{"class":199},[162,2296,2297],{"class":396},"showRowStripes",[162,2299,222],{"class":195},[162,2301,843],{"class":250},[162,2303,465],{"class":199},[162,2305,2306],{"class":164,"line":871},[162,2307,2308],{"class":199},"    ws.add_table(table)\n",[162,2310,2311,2313,2315],{"class":164,"line":883},[162,2312,1993],{"class":199},[162,2314,222],{"class":195},[162,2316,901],{"class":172},[162,2318,2319],{"class":164,"line":888},[162,2320,2321],{"class":199},"    wb.save(path)\n",[162,2323,2324,2327],{"class":164,"line":894},[162,2325,2326],{"class":195},"    return",[162,2328,2329],{"class":199}," path\n",[162,2331,2332],{"class":164,"line":904},[162,2333,213],{"emptyLinePlaceholder":212},[162,2335,2336,2339,2342],{"class":164,"line":909},[162,2337,2338],{"class":199},"write_filtered(df, ",[162,2340,2341],{"class":172},"\"orders_final.xlsx\"",[162,2343,465],{"class":199},[10,2345,2346,2347,2350],{},"Two passes, and the second one owns everything a reader sees. It is worth writing this as a single function rather than as two steps in a script, because the moment they are separated someone will insert a ",[159,2348,2349],{},"to_excel"," between them and quietly lose the table.",[10,2352,2353,2354,2358],{},"The same ordering applies to ",[14,2355,2357],{"href":2356},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002F","conditional formatting",", column widths and cell styles: data first, presentation second, save once.",[147,2360,2362],{"id":2361},"size-the-columns-so-filtered-data-stays-readable","Size the columns so filtered data stays readable",[10,2364,2365],{},"A filter is only useful if the reader can see what they filtered to. Column widths are the cheapest fix, and they can be derived from the content rather than guessed:",[152,2367,2369],{"className":186,"code":2368,"language":188,"meta":157,"style":157},"from openpyxl import load_workbook\nfrom openpyxl.utils import get_column_letter\n\ndef autosize(ws, min_width=9, max_width=42, padding=3):\n    for column_cells in ws.columns:\n        letter = get_column_letter(column_cells[0].column)\n        longest = max(\n            (len(str(cell.value)) for cell in column_cells if cell.value is not None),\n            default=0,\n        )\n        ws.column_dimensions[letter].width = min(max(longest + padding, min_width), max_width)\n\nwb = load_workbook(\"orders_final.xlsx\")\nautosize(wb[\"Orders\"])\nwb.save(\"orders_final.xlsx\")\n",[159,2370,2371,2381,2391,2395,2427,2440,2455,2468,2510,2521,2526,2549,2553,2565,2574],{"__ignoreMap":157},[162,2372,2373,2375,2377,2379],{"class":164,"line":165},[162,2374,525],{"class":195},[162,2376,528],{"class":199},[162,2378,196],{"class":195},[162,2380,533],{"class":199},[162,2382,2383,2385,2387,2389],{"class":164,"line":209},[162,2384,525],{"class":195},[162,2386,540],{"class":199},[162,2388,196],{"class":195},[162,2390,545],{"class":199},[162,2392,2393],{"class":164,"line":216},[162,2394,213],{"emptyLinePlaceholder":212},[162,2396,2397,2399,2402,2405,2407,2410,2413,2415,2418,2421,2423,2425],{"class":164,"line":228},[162,2398,2138],{"class":195},[162,2400,2401],{"class":2141}," autosize",[162,2403,2404],{"class":199},"(ws, min_width",[162,2406,222],{"class":195},[162,2408,2409],{"class":250},"9",[162,2411,2412],{"class":199},", max_width",[162,2414,222],{"class":195},[162,2416,2417],{"class":250},"42",[162,2419,2420],{"class":199},", padding",[162,2422,222],{"class":195},[162,2424,369],{"class":250},[162,2426,2159],{"class":199},[162,2428,2429,2432,2435,2437],{"class":164,"line":262},[162,2430,2431],{"class":195},"    for",[162,2433,2434],{"class":199}," column_cells ",[162,2436,1938],{"class":195},[162,2438,2439],{"class":199}," ws.columns:\n",[162,2441,2442,2445,2447,2450,2452],{"class":164,"line":292},[162,2443,2444],{"class":199},"        letter ",[162,2446,222],{"class":195},[162,2448,2449],{"class":199}," get_column_letter(column_cells[",[162,2451,41],{"class":250},[162,2453,2454],{"class":199},"].column)\n",[162,2456,2457,2460,2462,2465],{"class":164,"line":322},[162,2458,2459],{"class":199},"        longest ",[162,2461,222],{"class":195},[162,2463,2464],{"class":250}," max",[162,2466,2467],{"class":199},"(\n",[162,2469,2470,2473,2476,2478,2481,2484,2486,2489,2491,2493,2496,2499,2502,2505,2508],{"class":164,"line":349},[162,2471,2472],{"class":199},"            (",[162,2474,2475],{"class":250},"len",[162,2477,658],{"class":199},[162,2479,2480],{"class":250},"str",[162,2482,2483],{"class":199},"(cell.value)) ",[162,2485,1932],{"class":195},[162,2487,2488],{"class":199}," cell ",[162,2490,1938],{"class":195},[162,2492,2434],{"class":199},[162,2494,2495],{"class":195},"if",[162,2497,2498],{"class":199}," cell.value ",[162,2500,2501],{"class":195},"is",[162,2503,2504],{"class":195}," not",[162,2506,2507],{"class":250}," None",[162,2509,259],{"class":199},[162,2511,2512,2515,2517,2519],{"class":164,"line":379},[162,2513,2514],{"class":396},"            default",[162,2516,222],{"class":195},[162,2518,41],{"class":250},[162,2520,833],{"class":199},[162,2522,2523],{"class":164,"line":385},[162,2524,2525],{"class":199},"        )\n",[162,2527,2528,2531,2533,2536,2538,2541,2544,2546],{"class":164,"line":431},[162,2529,2530],{"class":199},"        ws.column_dimensions[letter].width ",[162,2532,222],{"class":195},[162,2534,2535],{"class":250}," min",[162,2537,658],{"class":199},[162,2539,2540],{"class":250},"max",[162,2542,2543],{"class":199},"(longest ",[162,2545,1515],{"class":195},[162,2547,2548],{"class":199}," padding, min_width), max_width)\n",[162,2550,2551],{"class":164,"line":468},[162,2552,213],{"emptyLinePlaceholder":212},[162,2554,2555,2557,2559,2561,2563],{"class":164,"line":499},[162,2556,554],{"class":199},[162,2558,222],{"class":195},[162,2560,559],{"class":199},[162,2562,2341],{"class":172},[162,2564,465],{"class":199},[162,2566,2567,2570,2572],{"class":164,"line":859},[162,2568,2569],{"class":199},"autosize(wb[",[162,2571,494],{"class":172},[162,2573,428],{"class":199},[162,2575,2576,2578,2580],{"class":164,"line":871},[162,2577,646],{"class":199},[162,2579,2341],{"class":172},[162,2581,465],{"class":199},[10,2583,2062,2584,2587,2588,2591],{},[159,2585,2586],{},"padding"," allowance is for the filter arrow, which overlaps the last few characters of a header when the column is sized exactly to its content — a header reading ",[159,2589,2590],{},"Unit_Pri▾"," is a small thing that makes a generated report look unfinished. Capping the width keeps one long free-text column from pushing everything else off the screen.",[10,2593,2594,2595,2598,2599,2603],{},"Number formats deserve the same attention on a filtered sheet, because a reader comparing filtered subsets is reading columns of figures rather than individual cells. Applying ",[159,2596,2597],{},"#,##0.00"," to money and a date format to dates is covered in ",[14,2600,2602],{"href":2601},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002F","applying number and date formats",", and it matters more here than anywhere else in a report.",[147,2605,2607],{"id":2606},"what-openpyxl-cannot-do","What openpyxl cannot do",[10,2609,2610],{},"Three things readers ask for are outside the library's reach, and knowing that saves an afternoon:",[957,2612,2613,2620,2626],{},[960,2614,2615,2619],{},[2616,2617,2618],"strong",{},"Applying a filter."," openpyxl records that a filter exists and which criteria are set, but it does not hide rows. A file written with filter criteria opens showing everything until the reader re-applies the filter, and reading such a file back gives you every row regardless.",[960,2621,2622,2625],{},[2616,2623,2624],{},"Slicers."," The button-style filter controls introduced with tables have no openpyxl API. A workbook that already contains slicers keeps them through a round trip in recent versions, but you cannot create one.",[960,2627,2628,2631],{},[2616,2629,2630],{},"Sorting a range."," There is no sort operation on a worksheet. Ordering comes from whatever you wrote, which is why sorting belongs in pandas.",[152,2633,2635],{"className":186,"code":2634,"language":188,"meta":157,"style":157},"from openpyxl import load_workbook\n\nwb = load_workbook(\"orders_filtered.xlsx\")\nws = wb[\"Orders\"]\n\n# Recording criteria: Excel shows the filter as set, but rows are NOT hidden here\nws.auto_filter.add_filter_column(1, [\"North\", \"West\"])\nprint(ws.auto_filter.ref, [f.vals for f in ws.auto_filter.filterColumn])\n\nwb.save(\"orders_criteria.xlsx\")\n",[159,2636,2637,2647,2651,2663,2675,2679,2684,2702,2719,2723],{"__ignoreMap":157},[162,2638,2639,2641,2643,2645],{"class":164,"line":165},[162,2640,525],{"class":195},[162,2642,528],{"class":199},[162,2644,196],{"class":195},[162,2646,533],{"class":199},[162,2648,2649],{"class":164,"line":209},[162,2650,213],{"emptyLinePlaceholder":212},[162,2652,2653,2655,2657,2659,2661],{"class":164,"line":216},[162,2654,554],{"class":199},[162,2656,222],{"class":195},[162,2658,559],{"class":199},[162,2660,649],{"class":172},[162,2662,465],{"class":199},[162,2664,2665,2667,2669,2671,2673],{"class":164,"line":228},[162,2666,568],{"class":199},[162,2668,222],{"class":195},[162,2670,573],{"class":199},[162,2672,494],{"class":172},[162,2674,382],{"class":199},[162,2676,2677],{"class":164,"line":262},[162,2678,213],{"emptyLinePlaceholder":212},[162,2680,2681],{"class":164,"line":292},[162,2682,2683],{"class":636},"# Recording criteria: Excel shows the filter as set, but rows are NOT hidden here\n",[162,2685,2686,2689,2691,2694,2696,2698,2700],{"class":164,"line":322},[162,2687,2688],{"class":199},"ws.auto_filter.add_filter_column(",[162,2690,1394],{"class":250},[162,2692,2693],{"class":199},", [",[162,2695,240],{"class":172},[162,2697,237],{"class":199},[162,2699,302],{"class":172},[162,2701,428],{"class":199},[162,2703,2704,2706,2709,2711,2714,2716],{"class":164,"line":349},[162,2705,502],{"class":250},[162,2707,2708],{"class":199},"(ws.auto_filter.ref, [f.vals ",[162,2710,1932],{"class":195},[162,2712,2713],{"class":199}," f ",[162,2715,1938],{"class":195},[162,2717,2718],{"class":199}," ws.auto_filter.filterColumn])\n",[162,2720,2721],{"class":164,"line":379},[162,2722,213],{"emptyLinePlaceholder":212},[162,2724,2725,2727,2730],{"class":164,"line":385},[162,2726,646],{"class":199},[162,2728,2729],{"class":172},"\"orders_criteria.xlsx\"",[162,2731,465],{"class":199},[10,2733,2734],{},"If a recipient must receive only their own rows, filter the data in pandas and write a file per recipient. That is both simpler and safer than shipping one workbook with a filter set, where a single click reveals everything to everyone.",[147,2736,2738],{"id":2737},"a-complete-report","A complete report",[10,2740,2741],{},"Putting the pieces in the right order gives a small, reusable function:",[152,2743,2745],{"className":186,"code":2744,"language":188,"meta":157,"style":157},"import pandas as pd\nfrom openpyxl import load_workbook\nfrom openpyxl.utils import get_column_letter\nfrom openpyxl.worksheet.table import Table, TableStyleInfo\n\ndef publish(df, path, sheet=\"Orders\", sort_by=(\"Region\", \"Revenue\"), money=(\"Revenue\",)):\n    ordered = df.sort_values(list(sort_by), ascending=[True, False])\n\n    with pd.ExcelWriter(path, engine=\"openpyxl\") as writer:\n        ordered.to_excel(writer, sheet_name=sheet, index=False)\n\n    wb = load_workbook(path)\n    ws = wb[sheet]\n    last_row, last_col = ws.max_row, get_column_letter(ws.max_column)\n\n    table = Table(displayName=sheet, ref=f\"A1:{last_col}{last_row}\")\n    table.tableStyleInfo = TableStyleInfo(name=\"TableStyleMedium9\", showRowStripes=True)\n    ws.add_table(table)\n    ws.freeze_panes = \"A2\"\n    autosize(ws)\n\n    headers = {cell.value: cell.column_letter for cell in ws[1]}\n    for name in money:\n        letter = headers.get(name)\n        if letter:\n            for row in range(2, last_row + 1):\n                ws[f\"{letter}{row}\"].number_format = \"#,##0.00\"\n            total_row = last_row + 2\n            ws[f\"{get_column_letter(ws.max_column - 1)}{total_row}\"] = \"Visible total\"\n            ws[f\"{letter}{total_row}\"] = f\"=SUBTOTAL(109,{letter}2:{letter}{last_row})\"\n            ws[f\"{letter}{total_row}\"].number_format = \"#,##0.00\"\n\n    ws.protection.sheet = True\n    ws.protection.autoFilter = False        # readers may filter\n    ws.protection.sort = False              # and sort\n    wb.save(path)\n    return path\n\npublish(df, \"orders_published.xlsx\")\n",[159,2746,2747,2757,2767,2777,2787,2791,2829,2858,2862,2880,2899,2903,2912,2920,2930,2934,2971,2995,2999,3007,3012,3016,3041,3054,3064,3073,3101,3131,3147,3184,3235,3262,3267,3277,3290,3303,3308,3315,3320],{"__ignoreMap":157},[162,2748,2749,2751,2753,2755],{"class":164,"line":165},[162,2750,196],{"class":195},[162,2752,200],{"class":199},[162,2754,203],{"class":195},[162,2756,206],{"class":199},[162,2758,2759,2761,2763,2765],{"class":164,"line":209},[162,2760,525],{"class":195},[162,2762,528],{"class":199},[162,2764,196],{"class":195},[162,2766,533],{"class":199},[162,2768,2769,2771,2773,2775],{"class":164,"line":216},[162,2770,525],{"class":195},[162,2772,540],{"class":199},[162,2774,196],{"class":195},[162,2776,545],{"class":199},[162,2778,2779,2781,2783,2785],{"class":164,"line":228},[162,2780,525],{"class":195},[162,2782,712],{"class":199},[162,2784,196],{"class":195},[162,2786,717],{"class":199},[162,2788,2789],{"class":164,"line":262},[162,2790,213],{"emptyLinePlaceholder":212},[162,2792,2793,2795,2798,2800,2802,2804,2807,2809,2811,2813,2815,2817,2820,2822,2824,2826],{"class":164,"line":292},[162,2794,2138],{"class":195},[162,2796,2797],{"class":2141}," publish",[162,2799,2145],{"class":199},[162,2801,222],{"class":195},[162,2803,494],{"class":172},[162,2805,2806],{"class":199},", sort_by",[162,2808,222],{"class":195},[162,2810,658],{"class":199},[162,2812,410],{"class":172},[162,2814,237],{"class":199},[162,2816,437],{"class":172},[162,2818,2819],{"class":199},"), money",[162,2821,222],{"class":195},[162,2823,658],{"class":199},[162,2825,437],{"class":172},[162,2827,2828],{"class":199},",)):\n",[162,2830,2831,2834,2836,2839,2841,2844,2846,2848,2850,2852,2854,2856],{"class":164,"line":322},[162,2832,2833],{"class":199},"    ordered ",[162,2835,222],{"class":195},[162,2837,2838],{"class":199}," df.sort_values(",[162,2840,931],{"class":250},[162,2842,2843],{"class":199},"(sort_by), ",[162,2845,1143],{"class":396},[162,2847,222],{"class":195},[162,2849,402],{"class":199},[162,2851,843],{"class":250},[162,2853,237],{"class":199},[162,2855,484],{"class":250},[162,2857,428],{"class":199},[162,2859,2860],{"class":164,"line":349},[162,2861,213],{"emptyLinePlaceholder":212},[162,2863,2864,2866,2868,2870,2872,2874,2876,2878],{"class":164,"line":379},[162,2865,2164],{"class":195},[162,2867,2167],{"class":199},[162,2869,1175],{"class":396},[162,2871,222],{"class":195},[162,2873,1180],{"class":172},[162,2875,1183],{"class":199},[162,2877,203],{"class":195},[162,2879,1188],{"class":199},[162,2881,2882,2885,2887,2889,2891,2893,2895,2897],{"class":164,"line":385},[162,2883,2884],{"class":199},"        ordered.to_excel(writer, ",[162,2886,489],{"class":396},[162,2888,222],{"class":195},[162,2890,2191],{"class":199},[162,2892,479],{"class":396},[162,2894,222],{"class":195},[162,2896,484],{"class":250},[162,2898,465],{"class":199},[162,2900,2901],{"class":164,"line":431},[162,2902,213],{"emptyLinePlaceholder":212},[162,2904,2905,2907,2909],{"class":164,"line":468},[162,2906,2212],{"class":199},[162,2908,222],{"class":195},[162,2910,2911],{"class":199}," load_workbook(path)\n",[162,2913,2914,2916,2918],{"class":164,"line":499},[162,2915,2225],{"class":199},[162,2917,222],{"class":195},[162,2919,2230],{"class":199},[162,2921,2922,2925,2927],{"class":164,"line":859},[162,2923,2924],{"class":199},"    last_row, last_col ",[162,2926,222],{"class":195},[162,2928,2929],{"class":199}," ws.max_row, get_column_letter(ws.max_column)\n",[162,2931,2932],{"class":164,"line":871},[162,2933,213],{"emptyLinePlaceholder":212},[162,2935,2936,2938,2940,2942,2944,2946,2948,2950,2952,2954,2956,2958,2960,2962,2965,2967,2969],{"class":164,"line":883},[162,2937,2258],{"class":199},[162,2939,222],{"class":195},[162,2941,793],{"class":199},[162,2943,796],{"class":396},[162,2945,222],{"class":195},[162,2947,2191],{"class":199},[162,2949,805],{"class":396},[162,2951,222],{"class":195},[162,2953,1276],{"class":195},[162,2955,604],{"class":172},[162,2957,608],{"class":607},[162,2959,611],{"class":199},[162,2961,614],{"class":607},[162,2963,2964],{"class":199},"last_row",[162,2966,620],{"class":607},[162,2968,1288],{"class":172},[162,2970,465],{"class":199},[162,2972,2973,2975,2977,2979,2981,2983,2985,2987,2989,2991,2993],{"class":164,"line":888},[162,2974,2280],{"class":199},[162,2976,222],{"class":195},[162,2978,2285],{"class":199},[162,2980,2288],{"class":396},[162,2982,222],{"class":195},[162,2984,830],{"class":172},[162,2986,237],{"class":199},[162,2988,2297],{"class":396},[162,2990,222],{"class":195},[162,2992,843],{"class":250},[162,2994,465],{"class":199},[162,2996,2997],{"class":164,"line":894},[162,2998,2308],{"class":199},[162,3000,3001,3003,3005],{"class":164,"line":904},[162,3002,1993],{"class":199},[162,3004,222],{"class":195},[162,3006,901],{"class":172},[162,3008,3009],{"class":164,"line":909},[162,3010,3011],{"class":199},"    autosize(ws)\n",[162,3013,3014],{"class":164,"line":919},[162,3015,213],{"emptyLinePlaceholder":212},[162,3017,3019,3022,3024,3027,3029,3031,3033,3036,3038],{"class":164,"line":3018},22,[162,3020,3021],{"class":199},"    headers ",[162,3023,222],{"class":195},[162,3025,3026],{"class":199}," {cell.value: cell.column_letter ",[162,3028,1932],{"class":195},[162,3030,2488],{"class":199},[162,3032,1938],{"class":195},[162,3034,3035],{"class":199}," ws[",[162,3037,1394],{"class":250},[162,3039,3040],{"class":199},"]}\n",[162,3042,3044,3046,3049,3051],{"class":164,"line":3043},23,[162,3045,2431],{"class":195},[162,3047,3048],{"class":199}," name ",[162,3050,1938],{"class":195},[162,3052,3053],{"class":199}," money:\n",[162,3055,3057,3059,3061],{"class":164,"line":3056},24,[162,3058,2444],{"class":199},[162,3060,222],{"class":195},[162,3062,3063],{"class":199}," headers.get(name)\n",[162,3065,3067,3070],{"class":164,"line":3066},25,[162,3068,3069],{"class":195},"        if",[162,3071,3072],{"class":199}," letter:\n",[162,3074,3076,3079,3082,3084,3087,3089,3091,3094,3096,3099],{"class":164,"line":3075},26,[162,3077,3078],{"class":195},"            for",[162,3080,3081],{"class":199}," row ",[162,3083,1938],{"class":195},[162,3085,3086],{"class":250}," range",[162,3088,658],{"class":199},[162,3090,282],{"class":250},[162,3092,3093],{"class":199},", last_row ",[162,3095,1515],{"class":195},[162,3097,3098],{"class":250}," 1",[162,3100,2159],{"class":199},[162,3102,3104,3107,3109,3111,3113,3116,3118,3121,3123,3125,3127,3129],{"class":164,"line":3103},27,[162,3105,3106],{"class":199},"                ws[",[162,3108,1276],{"class":195},[162,3110,1288],{"class":172},[162,3112,608],{"class":607},[162,3114,3115],{"class":199},"letter",[162,3117,614],{"class":607},[162,3119,3120],{"class":199},"row",[162,3122,620],{"class":607},[162,3124,1288],{"class":172},[162,3126,1602],{"class":199},[162,3128,222],{"class":195},[162,3130,1607],{"class":172},[162,3132,3134,3137,3139,3142,3144],{"class":164,"line":3133},28,[162,3135,3136],{"class":199},"            total_row ",[162,3138,222],{"class":195},[162,3140,3141],{"class":199}," last_row ",[162,3143,1515],{"class":195},[162,3145,3146],{"class":250}," 2\n",[162,3148,3150,3153,3155,3157,3159,3162,3165,3167,3170,3172,3174,3176,3178,3180,3182],{"class":164,"line":3149},29,[162,3151,3152],{"class":199},"            ws[",[162,3154,1276],{"class":195},[162,3156,1288],{"class":172},[162,3158,608],{"class":607},[162,3160,3161],{"class":199},"get_column_letter(ws.max_column ",[162,3163,3164],{"class":195},"-",[162,3166,3098],{"class":250},[162,3168,3169],{"class":199},")",[162,3171,614],{"class":607},[162,3173,1536],{"class":199},[162,3175,620],{"class":607},[162,3177,1288],{"class":172},[162,3179,440],{"class":199},[162,3181,222],{"class":195},[162,3183,1547],{"class":172},[162,3185,3187,3189,3191,3193,3195,3197,3199,3201,3203,3205,3207,3209,3211,3214,3216,3218,3220,3223,3225,3227,3229,3231,3233],{"class":164,"line":3186},30,[162,3188,3152],{"class":199},[162,3190,1276],{"class":195},[162,3192,1288],{"class":172},[162,3194,608],{"class":607},[162,3196,3115],{"class":199},[162,3198,614],{"class":607},[162,3200,1536],{"class":199},[162,3202,620],{"class":607},[162,3204,1288],{"class":172},[162,3206,440],{"class":199},[162,3208,222],{"class":195},[162,3210,601],{"class":195},[162,3212,3213],{"class":172},"\"=SUBTOTAL(109,",[162,3215,608],{"class":607},[162,3217,3115],{"class":199},[162,3219,620],{"class":607},[162,3221,3222],{"class":172},"2:",[162,3224,608],{"class":607},[162,3226,3115],{"class":199},[162,3228,614],{"class":607},[162,3230,2964],{"class":199},[162,3232,620],{"class":607},[162,3234,1583],{"class":172},[162,3236,3238,3240,3242,3244,3246,3248,3250,3252,3254,3256,3258,3260],{"class":164,"line":3237},31,[162,3239,3152],{"class":199},[162,3241,1276],{"class":195},[162,3243,1288],{"class":172},[162,3245,608],{"class":607},[162,3247,3115],{"class":199},[162,3249,614],{"class":607},[162,3251,1536],{"class":199},[162,3253,620],{"class":607},[162,3255,1288],{"class":172},[162,3257,1602],{"class":199},[162,3259,222],{"class":195},[162,3261,1607],{"class":172},[162,3263,3265],{"class":164,"line":3264},32,[162,3266,213],{"emptyLinePlaceholder":212},[162,3268,3270,3273,3275],{"class":164,"line":3269},33,[162,3271,3272],{"class":199},"    ws.protection.sheet ",[162,3274,222],{"class":195},[162,3276,1415],{"class":250},[162,3278,3280,3283,3285,3287],{"class":164,"line":3279},34,[162,3281,3282],{"class":199},"    ws.protection.autoFilter ",[162,3284,222],{"class":195},[162,3286,1803],{"class":250},[162,3288,3289],{"class":636},"        # readers may filter\n",[162,3291,3293,3296,3298,3300],{"class":164,"line":3292},35,[162,3294,3295],{"class":199},"    ws.protection.sort ",[162,3297,222],{"class":195},[162,3299,1803],{"class":250},[162,3301,3302],{"class":636},"              # and sort\n",[162,3304,3306],{"class":164,"line":3305},36,[162,3307,2321],{"class":199},[162,3309,3311,3313],{"class":164,"line":3310},37,[162,3312,2326],{"class":195},[162,3314,2329],{"class":199},[162,3316,3318],{"class":164,"line":3317},38,[162,3319,213],{"emptyLinePlaceholder":212},[162,3321,3323,3326,3329],{"class":164,"line":3322},39,[162,3324,3325],{"class":199},"publish(df, ",[162,3327,3328],{"class":172},"\"orders_published.xlsx\"",[162,3330,465],{"class":199},[10,3332,3333],{},"Sorted rows, a named table with banding, a frozen header, sized columns, formatted money, a total that follows the filter, and a sheet nobody can accidentally edit — in one function, in the order that survives. Everything the reader needs to explore the data is there, and nothing they do can change the numbers.",[147,3335,3337],{"id":3336},"common-errors-and-fixes","Common errors and fixes",[3339,3340,3341,3357],"table",{},[3342,3343,3344],"thead",{},[3345,3346,3347,3351,3354],"tr",{},[3348,3349,3350],"th",{},"Symptom",[3348,3352,3353],{},"Cause",[3348,3355,3356],{},"Fix",[3358,3359,3360,3375,3391,3406,3421,3436,3447],"tbody",{},[3345,3361,3362,3366,3372],{},[3363,3364,3365],"td",{},"Filter or table missing from the output",[3363,3367,3368,3369,3371],{},"Applied before ",[159,3370,2349],{}," rewrote the sheet",[3363,3373,3374],{},"Write the data first, decorate afterwards",[3345,3376,3377,3380,3385],{},[3363,3378,3379],{},"\"Excel found unreadable content\"",[3363,3381,3382,3383],{},"Blank or duplicate table headers, or a bad ",[159,3384,805],{},[3363,3386,3387,3388,3390],{},"Unique non-empty headers; ",[159,3389,805],{}," exactly covers the data",[3345,3392,3393,3398,3403],{},[3363,3394,3395],{},[159,3396,3397],{},"ValueError: Table name is already in use",[3363,3399,3400,3401],{},"Two tables share a ",[159,3402,796],{},[3363,3404,3405],{},"Names must be unique across the workbook",[3345,3407,3408,3411,3416],{},[3363,3409,3410],{},"Rows below the range escape the filter",[3363,3412,3413,3415],{},[159,3414,805],{}," ended short of the last row",[3363,3417,3418,3419],{},"Build the range from ",[159,3420,617],{},[3345,3422,3423,3426,3431],{},[3363,3424,3425],{},"Total does not match the filtered rows",[3363,3427,3428,3430],{},[159,3429,1629],{}," inside or over the filtered range",[3363,3432,3433,3435],{},[159,3434,1625],{}," outside the range",[3345,3437,3438,3441,3444],{},[3363,3439,3440],{},"Filter criteria written but nothing is hidden",[3363,3442,3443],{},"openpyxl does not apply filters",[3363,3445,3446],{},"Filter in pandas and write per-recipient files",[3345,3448,3449,3452,3458],{},[3363,3450,3451],{},"Readers cannot filter a protected sheet",[3363,3453,3454,3457],{},[159,3455,3456],{},"autoFilter"," left at its protected default",[3363,3459,3460,3461],{},"Set ",[159,3462,3463],{},"ws.protection.autoFilter = False",[147,3465,3467],{"id":3466},"which-feature-for-which-job","Which feature for which job",[3339,3469,3470,3480],{},[3342,3471,3472],{},[3345,3473,3474,3477],{},[3348,3475,3476],{},"Need",[3348,3478,3479],{},"Use",[3358,3481,3482,3491,3504,3516,3524,3534,3543],{},[3345,3483,3484,3487],{},[3363,3485,3486],{},"Dropdown filters on a simple export",[3363,3488,3489],{},[159,3490,514],{},[3345,3492,3493,3496],{},[3363,3494,3495],{},"Banded rows and a named range for formulas",[3363,3497,3498,3500,3501],{},[159,3499,689],{}," with ",[159,3502,3503],{},"TableStyleInfo",[3345,3505,3506,3512],{},[3363,3507,3508,3509],{},"Structured references like ",[159,3510,3511],{},"Orders[Revenue]",[3363,3513,3514],{},[159,3515,689],{},[3345,3517,3518,3521],{},[3363,3519,3520],{},"Rows in a meaningful order",[3363,3522,3523],{},"Sort in pandas before writing",[3345,3525,3526,3529],{},[3363,3527,3528],{},"A long sheet readers can collapse",[3363,3530,3531],{},[159,3532,3533],{},"row_dimensions.group",[3345,3535,3536,3539],{},[3363,3537,3538],{},"A total that tracks the filter",[3363,3540,3541,3435],{},[159,3542,1625],{},[3345,3544,3545,3548],{},[3363,3546,3547],{},"Readers who may filter but not edit",[3363,3549,3550,3551],{},"Sheet protection with ",[159,3552,1868],{},[147,3554,3556],{"id":3555},"read-the-filters-an-existing-workbook-already-has","Read the filters an existing workbook already has",[10,3558,3559],{},"Before adding anything to a file someone else built, look at what is there. A template often carries a filter or a table whose range stopped matching the data several months ago:",[152,3561,3563],{"className":186,"code":3562,"language":188,"meta":157,"style":157},"from openpyxl import load_workbook\n\nwb = load_workbook(\"regional_filtered.xlsx\")\nfor ws in wb.worksheets:\n    ref = ws.auto_filter.ref\n    tables = {name: table.ref for name, table in ws.tables.items()}\n    print(f\"{ws.title:12} rows={ws.max_row:\u003C6} filter={ref or '—':12} tables={tables or '—'}\")\n",[159,3564,3565,3575,3579,3591,3601,3610,3630],{"__ignoreMap":157},[162,3566,3567,3569,3571,3573],{"class":164,"line":165},[162,3568,525],{"class":195},[162,3570,528],{"class":199},[162,3572,196],{"class":195},[162,3574,533],{"class":199},[162,3576,3577],{"class":164,"line":209},[162,3578,213],{"emptyLinePlaceholder":212},[162,3580,3581,3583,3585,3587,3589],{"class":164,"line":216},[162,3582,554],{"class":199},[162,3584,222],{"class":195},[162,3586,559],{"class":199},[162,3588,2018],{"class":172},[162,3590,465],{"class":199},[162,3592,3593,3595,3597,3599],{"class":164,"line":228},[162,3594,1932],{"class":195},[162,3596,1935],{"class":199},[162,3598,1938],{"class":195},[162,3600,1941],{"class":199},[162,3602,3603,3605,3607],{"class":164,"line":262},[162,3604,2235],{"class":199},[162,3606,222],{"class":195},[162,3608,3609],{"class":199}," ws.auto_filter.ref\n",[162,3611,3612,3615,3617,3620,3622,3625,3627],{"class":164,"line":292},[162,3613,3614],{"class":199},"    tables ",[162,3616,222],{"class":195},[162,3618,3619],{"class":199}," {name: table.ref ",[162,3621,1932],{"class":195},[162,3623,3624],{"class":199}," name, table ",[162,3626,1938],{"class":195},[162,3628,3629],{"class":199}," ws.tables.items()}\n",[162,3631,3632,3635,3637,3639,3641,3643,3645,3648,3650,3653,3655,3657,3660,3662,3665,3667,3669,3672,3675,3677,3679,3682,3684,3687,3689,3691,3693,3695],{"class":164,"line":322},[162,3633,3634],{"class":250},"    print",[162,3636,658],{"class":199},[162,3638,1276],{"class":195},[162,3640,1288],{"class":172},[162,3642,608],{"class":607},[162,3644,2036],{"class":199},[162,3646,3647],{"class":195},":12",[162,3649,620],{"class":607},[162,3651,3652],{"class":172}," rows=",[162,3654,608],{"class":607},[162,3656,617],{"class":199},[162,3658,3659],{"class":195},":\u003C6",[162,3661,620],{"class":607},[162,3663,3664],{"class":172}," filter=",[162,3666,608],{"class":607},[162,3668,764],{"class":199},[162,3670,3671],{"class":195},"or",[162,3673,3674],{"class":172}," '—'",[162,3676,3647],{"class":195},[162,3678,620],{"class":607},[162,3680,3681],{"class":172}," tables=",[162,3683,608],{"class":607},[162,3685,3686],{"class":199},"tables ",[162,3688,3671],{"class":195},[162,3690,3674],{"class":172},[162,3692,620],{"class":607},[162,3694,1288],{"class":172},[162,3696,465],{"class":199},[10,3698,3699,3700,3703,3704,3706,3707,3709],{},"The output makes a stale range obvious: a filter reading ",[159,3701,3702],{},"A1:F200"," on a sheet with 4,000 rows means 3,800 rows have been outside it since whenever the template was made. Refreshing both the filter and any table ",[159,3705,805],{}," from ",[159,3708,617],{}," on every run is a one-line fix that keeps a delivered workbook honest as the data grows.",[147,3711,3713],{"id":3712},"key-takeaways","Key takeaways",[957,3715,3716,3721,3724,3729,3735,3741,3747],{},[960,3717,3718,3720],{},[159,3719,514],{}," is one line and must span the header row plus every data row.",[960,3722,3723],{},"Freeze the header whenever you add a filter, or the columns lose their names as soon as someone scrolls.",[960,3725,686,3726,3728],{},[159,3727,689],{}," adds banding, a name and structured references, and requires unique, non-empty headers with a range that matches the data exactly.",[960,3730,3731,3734],{},[159,3732,3733],{},"add_sort_condition"," sets Excel's sort indicator only — sort the rows in pandas if the order matters.",[960,3736,3737,3738,3740],{},"Outline groups make long detail sheets navigable, and ",[159,3739,1433],{}," decides which way the buttons point.",[960,3742,3743,3744,3746],{},"Keep totals outside the filtered range and use ",[159,3745,1625],{}," so they follow what the reader sees.",[960,3748,3749,3750,237,3752,3754,3755,3757],{},"On ",[159,3751,1861],{},[159,3753,484],{}," means allowed — set ",[159,3756,1868],{}," to let readers filter a protected sheet.",[147,3759,3761],{"id":3760},"frequently-asked-questions","Frequently asked questions",[10,3763,3764,3767],{},[2616,3765,3766],{},"What is the difference between an autofilter and a table?","\nAn autofilter adds dropdown arrows to a header row. A table is a named object with its own range, banded formatting, structured references and a filter built in — a superset, at the cost of a stricter range.",[10,3769,3770,3773,3774,3776],{},[2616,3771,3772],{},"Why does Excel say my file needs repairing after adding a table?","\nAlmost always a duplicate or blank header, a table name that clashes, or a ",[159,3775,805],{}," that does not match the data. Table headers must be unique, non-empty strings matching the cells in the first row of the range.",[10,3778,3779,3782,3783,3785],{},[2616,3780,3781],{},"Does openpyxl actually sort the rows when I filter?","\nNo. ",[159,3784,1060],{}," records the filter's state for Excel's interface; it does not reorder anything. Sort the data in pandas before writing if the order matters.",[10,3787,3788,3791,3792,3794],{},[2616,3789,3790],{},"Can I keep a total row visible while filtering?","\nYes — put it outside the table or filter range and use ",[159,3793,1444],{}," so it follows the visible rows.",[147,3796,3798],{"id":3797},"related","Related",[10,3800,3801],{},"Up to the parent guide:",[957,3803,3804],{},[960,3805,3806,3808],{},[14,3807,17],{"href":16}," — the wider presentation track.",[10,3810,3811],{},"Go deeper here:",[957,3813,3814,3821,3828],{},[960,3815,3816,3820],{},[14,3817,3819],{"href":3818},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fadd-an-autofilter-to-an-excel-sheet-with-openpyxl\u002F","Add an Autofilter to an Excel Sheet with openpyxl"," — ranges, multiple sheets and the common mistakes.",[960,3822,3823,3827],{},[14,3824,3826],{"href":3825},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fcreate-an-excel-table-with-python\u002F","Create an Excel Table with Python"," — styles, names, structured references and repair errors.",[960,3829,3830,3834],{},[14,3831,3833],{"href":3832},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fsort-excel-rows-with-python-before-writing\u002F","Sort Excel Rows with Python Before Writing"," — the order readers actually get.",[10,3836,3837],{},"Related areas:",[957,3839,3840,3847,3853],{},[960,3841,3842,3846],{},[14,3843,3845],{"href":3844},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002F","Styling Excel Cells with openpyxl"," — fonts, fills and borders around the table.",[960,3848,3849,3852],{},[14,3850,3851],{"href":670},"Freeze the Header Row in Excel with openpyxl"," — the companion to every filter.",[960,3854,3855,3859],{},[14,3856,3858],{"href":3857},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002F","Building Multi-Sheet Excel Dashboards"," — where filtered detail sheets sit in a report.",[3861,3862,3863],"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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}",{"title":157,"searchDepth":209,"depth":209,"links":3865},[3866,3867,3868,3869,3870,3871,3872,3873,3874,3875,3876,3877,3878,3879,3880,3881,3882,3883,3884],{"id":149,"depth":209,"text":150},{"id":182,"depth":209,"text":183},{"id":508,"depth":209,"text":509},{"id":682,"depth":209,"text":683},{"id":1054,"depth":209,"text":1055},{"id":1317,"depth":209,"text":1318},{"id":1437,"depth":209,"text":1438},{"id":1733,"depth":209,"text":1734},{"id":1877,"depth":209,"text":1878},{"id":2076,"depth":209,"text":2077},{"id":2361,"depth":209,"text":2362},{"id":2606,"depth":209,"text":2607},{"id":2737,"depth":209,"text":2738},{"id":3336,"depth":209,"text":3337},{"id":3466,"depth":209,"text":3467},{"id":3555,"depth":209,"text":3556},{"id":3712,"depth":209,"text":3713},{"id":3760,"depth":209,"text":3761},{"id":3797,"depth":209,"text":3798},"2026-08-01","Give readers a sheet they can slice — autofilters, real Excel tables with banded styles and structured references, sorted output, grouped rows and protected cells, all from openpyxl.","md",[3889,3891,3893,3895],{"q":3766,"a":3890},"An autofilter adds dropdown arrows to a header row. A table is a named object with its own range, banded formatting, structured references and a filter built in — a superset, at the cost of a stricter range.",{"q":3772,"a":3892},"Almost always a duplicate or blank header, a table name that clashes, or a ref that does not match the data. Table headers must be unique, non-empty strings matching the cells in the first row of the range.",{"q":3781,"a":3894},"No. auto_filter.add_sort_condition records the filter's state for Excel's interface; it does not reorder anything. Sort the data in pandas before writing if the order matters.",{"q":3790,"a":3896},"Yes — put it outside the table or filter range and use SUBTOTAL so it follows the visible rows.",{"breadcrumb":3898},[3899,3902,3903],{"name":3900,"item":3901},"Home","\u002F",{"name":17,"item":16},{"name":5,"item":3904},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002F","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python",{"title":5,"description":3907},"Add auto_filter, Table objects with TableStyleInfo, freeze panes, row grouping and worksheet protection to generated reports so readers can filter and explore safely.","creating-excel-tables-and-autofilters-with-python","formatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Findex","guide","RhpslqtMfgRLw5qHng5D4uc6fJ9_7qQgRkJ9y5OswqI",[3913,3917],{"title":3914,"path":3915,"stem":3916,"children":-1},"Create a Bar Chart in Excel with openpyxl","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-bar-chart-in-excel-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-bar-chart-in-excel-with-openpyxl\u002Findex",{"title":3819,"path":3918,"stem":3919,"children":-1},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fadd-an-autofilter-to-an-excel-sheet-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fadd-an-autofilter-to-an-excel-sheet-with-openpyxl\u002Findex",1785584462252]