[{"data":1,"prerenderedAt":2727},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter":2719},{"id":4,"title":5,"body":6,"dateModified":2689,"datePublished":2689,"description":2690,"extension":2691,"faq":2692,"meta":2703,"navigation":190,"path":2711,"seo":2712,"slug":2715,"stem":2716,"type":2717,"__hash__":2718},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Findex.md","Building Excel Reports with xlsxwriter",{"type":7,"value":8,"toc":2675},"minimark",[9,13,22,143,148,164,577,588,592,599,660,673,680,684,687,933,957,961,968,1143,1156,1225,1229,1236,1353,1361,1365,1368,1588,1603,1612,1616,1619,1990,2001,2009,2126,2130,2133,2382,2396,2400,2403,2419,2454,2461,2483,2486,2489,2493,2555,2559,2565,2574,2594,2603,2609,2613,2671],[10,11,12],"p",{},"openpyxl is the general-purpose library for Excel work in Python: it reads, it writes, and it edits files that already exist. xlsxwriter does exactly one of those things — it writes new files — and in exchange it is faster at it, has a far richer formatting and charting API, and can produce workbooks of any size in flat memory.",[10,14,15,16,21],{},"For report generation, which is nearly always \"build a new file from a query or a DataFrame\", that trade is a good one. This topic within ",[17,18,20],"a",{"href":19},"\u002Fformatting-and-charting-excel-reports-with-python\u002F","Formatting and Charting Excel Reports with Python"," covers how xlsxwriter models a workbook, the format-object pattern that makes it fast, and the four jobs it does better than anything else: styled tables, charts, conditional formats and very large writes.",[23,24,32,33,32,37,32,41,32,48,32,58,32,65,32,74,32,79,32,85,32,90,32,94,32,99,32,106,32,112,32,117,32,121,32,125,32,129,32,134,32,137,32,140],"svg",{"viewBox":25,"role":26,"ariaLabelledBy":27,"xmlns":30,"style":31},"0 0 760 246","img",[28,29],"xw-choose-t","xw-choose-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  ",[34,35,36],"title",{"id":28},"Choosing between xlsxwriter and openpyxl",[38,39,40],"desc",{"id":29},"If the job creates a new workbook from data, xlsxwriter writes it faster with a richer formatting and chart API and a constant-memory mode. If the job must read a workbook, edit one in place, or fill in an existing template, only openpyxl can do it — xlsxwriter cannot open a file at all.",[42,43],"rect",{"x":44,"y":44,"width":45,"height":46,"fill":47},"0","760","246","#ffffff",[42,49],{"x":50,"y":51,"width":52,"height":53,"rx":54,"fill":55,"stroke":56,"style":57},"236","20","288","48","12","#ebebfd","var(--brand,#5b5cf0)","stroke-width:2px",[59,60,64],"text",{"x":61,"y":62,"style":63},"380","50","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Does the job need to OPEN a file?",[66,67],"line",{"x1":68,"y1":69,"x2":70,"y2":71,"stroke":72,"style":73},"300","70","180","106","var(--teal,#0f9488)","stroke-width:1.5px",[75,76],"polygon",{"points":77,"fill":78},"176,108 188,102 186,112","#0f766e",[59,80,84],{"x":81,"y":82,"style":83},"216","94","font-size:11.5px;font-weight:700;fill:var(--teal-ink,#0b6157)","no",[66,86],{"x1":87,"y1":69,"x2":88,"y2":71,"stroke":89,"style":73},"460","580","var(--gold,#b4740a)",[75,91],{"points":92,"fill":93},"584,108 572,102 574,112","#8a5808",[59,95,98],{"x":96,"y":82,"style":97},"536","font-size:11.5px;font-weight:700;fill:var(--gold-ink,#7a4e06)","yes",[42,100],{"x":51,"y":101,"width":102,"height":103,"rx":104,"fill":105,"stroke":72,"style":57},"114","330","112","14","#d9f4f1",[59,107,111],{"x":108,"y":109,"style":110},"185","142","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","xlsxwriter",[59,113,116],{"x":108,"y":114,"style":115},"168","font-size:11.5px;fill:var(--text,#172033);text-anchor:middle","new workbook from a DataFrame or query",[59,118,120],{"x":108,"y":119,"style":115},"190","richest chart and format API · fastest write",[59,122,124],{"x":108,"y":123,"style":115},"212","constant memory at any row count",[42,126],{"x":127,"y":101,"width":102,"height":103,"rx":104,"fill":128,"stroke":89,"style":57},"410","#fdefd8",[59,130,133],{"x":131,"y":109,"style":132},"575","font-size:13px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","openpyxl",[59,135,136],{"x":131,"y":114,"style":115},"read values, edit in place, fill a template",[59,138,139],{"x":131,"y":119,"style":115},"keeps existing styling when it loads",[59,141,142],{"x":131,"y":123,"style":115},"the only option for an existing file",[144,145,147],"h2",{"id":146},"the-model-workbook-formats-worksheets","The model: workbook, formats, worksheets",[10,149,150,151,155,156,159,160,163],{},"xlsxwriter has three objects and one rule. A ",[152,153,154],"code",{},"Workbook"," owns the file. ",[152,157,158],{},"Format"," objects are created on the workbook and reused across every cell that shares a look. ",[152,161,162],{},"Worksheet"," objects receive writes. The rule is that a cell's format is chosen at the moment it is written and cannot be changed afterwards — there is no going back to restyle.",[165,166,171],"pre",{"className":167,"code":168,"language":169,"meta":170,"style":170},"language-python shiki shiki-themes github-light github-dark-high-contrast","import xlsxwriter\n\nwb = xlsxwriter.Workbook(\"report.xlsx\")\nws = wb.add_worksheet(\"Summary\")\n\nheader = wb.add_format({\"bold\": True, \"font_color\": \"white\",\n                        \"bg_color\": \"#1F4E78\", \"align\": \"center\",\n                        \"border\": 1})\nmoney = wb.add_format({\"num_format\": '#,##0.00', \"border\": 1})\npct = wb.add_format({\"num_format\": \"0.0%\", \"border\": 1})\n\nws.write_row(\"A1\", [\"Region\", \"Amount\", \"Share\"], header)\n\nrows = [(\"South\", 274.75, 0.55), (\"North\", 150.25, 0.30), (\"East\", 75.0, 0.15)]\nfor r, (region, amount, share) in enumerate(rows, start=1):\n    ws.write_string(r, 0, region)\n    ws.write_number(r, 1, amount, money)\n    ws.write_number(r, 2, share, pct)\n\nws.set_column(\"A:A\", 16)\nws.set_column(\"B:C\", 14)\nws.freeze_panes(1, 0)\nwb.close()                      # nothing exists on disk until close()\n","python","",[152,172,173,185,192,211,227,232,267,290,304,333,360,365,393,398,456,485,496,507,518,523,539,553,567],{"__ignoreMap":170},[174,175,177,181],"span",{"class":66,"line":176},1,[174,178,180],{"class":179},"s-kum","import",[174,182,184],{"class":183},"skGVy"," xlsxwriter\n",[174,186,188],{"class":66,"line":187},2,[174,189,191],{"emptyLinePlaceholder":190},true,"\n",[174,193,195,198,201,204,208],{"class":66,"line":194},3,[174,196,197],{"class":183},"wb ",[174,199,200],{"class":179},"=",[174,202,203],{"class":183}," xlsxwriter.Workbook(",[174,205,207],{"class":206},"srMev","\"report.xlsx\"",[174,209,210],{"class":183},")\n",[174,212,214,217,219,222,225],{"class":66,"line":213},4,[174,215,216],{"class":183},"ws ",[174,218,200],{"class":179},[174,220,221],{"class":183}," wb.add_worksheet(",[174,223,224],{"class":206},"\"Summary\"",[174,226,210],{"class":183},[174,228,230],{"class":66,"line":229},5,[174,231,191],{"emptyLinePlaceholder":190},[174,233,235,238,240,243,246,249,253,256,259,261,264],{"class":66,"line":234},6,[174,236,237],{"class":183},"header ",[174,239,200],{"class":179},[174,241,242],{"class":183}," wb.add_format({",[174,244,245],{"class":206},"\"bold\"",[174,247,248],{"class":183},": ",[174,250,252],{"class":251},"sP0c6","True",[174,254,255],{"class":183},", ",[174,257,258],{"class":206},"\"font_color\"",[174,260,248],{"class":183},[174,262,263],{"class":206},"\"white\"",[174,265,266],{"class":183},",\n",[174,268,270,273,275,278,280,283,285,288],{"class":66,"line":269},7,[174,271,272],{"class":206},"                        \"bg_color\"",[174,274,248],{"class":183},[174,276,277],{"class":206},"\"#1F4E78\"",[174,279,255],{"class":183},[174,281,282],{"class":206},"\"align\"",[174,284,248],{"class":183},[174,286,287],{"class":206},"\"center\"",[174,289,266],{"class":183},[174,291,293,296,298,301],{"class":66,"line":292},8,[174,294,295],{"class":206},"                        \"border\"",[174,297,248],{"class":183},[174,299,300],{"class":251},"1",[174,302,303],{"class":183},"})\n",[174,305,307,310,312,314,317,319,322,324,327,329,331],{"class":66,"line":306},9,[174,308,309],{"class":183},"money ",[174,311,200],{"class":179},[174,313,242],{"class":183},[174,315,316],{"class":206},"\"num_format\"",[174,318,248],{"class":183},[174,320,321],{"class":206},"'#,##0.00'",[174,323,255],{"class":183},[174,325,326],{"class":206},"\"border\"",[174,328,248],{"class":183},[174,330,300],{"class":251},[174,332,303],{"class":183},[174,334,336,339,341,343,345,347,350,352,354,356,358],{"class":66,"line":335},10,[174,337,338],{"class":183},"pct ",[174,340,200],{"class":179},[174,342,242],{"class":183},[174,344,316],{"class":206},[174,346,248],{"class":183},[174,348,349],{"class":206},"\"0.0%\"",[174,351,255],{"class":183},[174,353,326],{"class":206},[174,355,248],{"class":183},[174,357,300],{"class":251},[174,359,303],{"class":183},[174,361,363],{"class":66,"line":362},11,[174,364,191],{"emptyLinePlaceholder":190},[174,366,368,371,374,377,380,382,385,387,390],{"class":66,"line":367},12,[174,369,370],{"class":183},"ws.write_row(",[174,372,373],{"class":206},"\"A1\"",[174,375,376],{"class":183},", [",[174,378,379],{"class":206},"\"Region\"",[174,381,255],{"class":183},[174,383,384],{"class":206},"\"Amount\"",[174,386,255],{"class":183},[174,388,389],{"class":206},"\"Share\"",[174,391,392],{"class":183},"], header)\n",[174,394,396],{"class":66,"line":395},13,[174,397,191],{"emptyLinePlaceholder":190},[174,399,401,404,406,409,412,414,417,419,422,425,428,430,433,435,438,440,443,445,448,450,453],{"class":66,"line":400},14,[174,402,403],{"class":183},"rows ",[174,405,200],{"class":179},[174,407,408],{"class":183}," [(",[174,410,411],{"class":206},"\"South\"",[174,413,255],{"class":183},[174,415,416],{"class":251},"274.75",[174,418,255],{"class":183},[174,420,421],{"class":251},"0.55",[174,423,424],{"class":183},"), (",[174,426,427],{"class":206},"\"North\"",[174,429,255],{"class":183},[174,431,432],{"class":251},"150.25",[174,434,255],{"class":183},[174,436,437],{"class":251},"0.30",[174,439,424],{"class":183},[174,441,442],{"class":206},"\"East\"",[174,444,255],{"class":183},[174,446,447],{"class":251},"75.0",[174,449,255],{"class":183},[174,451,452],{"class":251},"0.15",[174,454,455],{"class":183},")]\n",[174,457,459,462,465,468,471,474,478,480,482],{"class":66,"line":458},15,[174,460,461],{"class":179},"for",[174,463,464],{"class":183}," r, (region, amount, share) ",[174,466,467],{"class":179},"in",[174,469,470],{"class":251}," enumerate",[174,472,473],{"class":183},"(rows, ",[174,475,477],{"class":476},"sa561","start",[174,479,200],{"class":179},[174,481,300],{"class":251},[174,483,484],{"class":183},"):\n",[174,486,488,491,493],{"class":66,"line":487},16,[174,489,490],{"class":183},"    ws.write_string(r, ",[174,492,44],{"class":251},[174,494,495],{"class":183},", region)\n",[174,497,499,502,504],{"class":66,"line":498},17,[174,500,501],{"class":183},"    ws.write_number(r, ",[174,503,300],{"class":251},[174,505,506],{"class":183},", amount, money)\n",[174,508,510,512,515],{"class":66,"line":509},18,[174,511,501],{"class":183},[174,513,514],{"class":251},"2",[174,516,517],{"class":183},", share, pct)\n",[174,519,521],{"class":66,"line":520},19,[174,522,191],{"emptyLinePlaceholder":190},[174,524,526,529,532,534,537],{"class":66,"line":525},20,[174,527,528],{"class":183},"ws.set_column(",[174,530,531],{"class":206},"\"A:A\"",[174,533,255],{"class":183},[174,535,536],{"class":251},"16",[174,538,210],{"class":183},[174,540,542,544,547,549,551],{"class":66,"line":541},21,[174,543,528],{"class":183},[174,545,546],{"class":206},"\"B:C\"",[174,548,255],{"class":183},[174,550,104],{"class":251},[174,552,210],{"class":183},[174,554,556,559,561,563,565],{"class":66,"line":555},22,[174,557,558],{"class":183},"ws.freeze_panes(",[174,560,300],{"class":251},[174,562,255],{"class":183},[174,564,44],{"class":251},[174,566,210],{"class":183},[174,568,570,573],{"class":66,"line":569},23,[174,571,572],{"class":183},"wb.close()                      ",[174,574,576],{"class":575},"s-wDw","# nothing exists on disk until close()\n",[10,578,579,580,583,584,587],{},"Two habits come out of this immediately. Create each format once and reuse it — a format created inside the row loop produces thousands of near-identical style records, which bloats the file and slows the write. And remember that ",[152,581,582],{},"close()"," is what actually writes the file; a script that raises before it is reached leaves nothing behind, which is why the context-manager form (",[152,585,586],{},"with xlsxwriter.Workbook(path) as wb:",") is worth preferring.",[144,589,591],{"id":590},"format-columns-not-cells","Format columns, not cells",[10,593,594,595,598],{},"The single biggest speed and size difference between a naive xlsxwriter script and a good one is where the formatting is applied. ",[152,596,597],{},"set_column"," attaches a format to an entire column in one record, instead of stamping it onto every cell:",[165,600,602],{"className":167,"code":601,"language":169,"meta":170,"style":170},"ws.set_column(\"B:B\", 14, money)       # width AND default format for the column\nws.set_column(\"C:C\", 12, pct)\nws.set_column(\"D:D\", 18, wb.add_format({\"num_format\": \"yyyy-mm-dd\"}))\n",[152,603,604,621,635],{"__ignoreMap":170},[174,605,606,608,611,613,615,618],{"class":66,"line":176},[174,607,528],{"class":183},[174,609,610],{"class":206},"\"B:B\"",[174,612,255],{"class":183},[174,614,104],{"class":251},[174,616,617],{"class":183},", money)       ",[174,619,620],{"class":575},"# width AND default format for the column\n",[174,622,623,625,628,630,632],{"class":66,"line":187},[174,624,528],{"class":183},[174,626,627],{"class":206},"\"C:C\"",[174,629,255],{"class":183},[174,631,54],{"class":251},[174,633,634],{"class":183},", pct)\n",[174,636,637,639,642,644,647,650,652,654,657],{"class":66,"line":194},[174,638,528],{"class":183},[174,640,641],{"class":206},"\"D:D\"",[174,643,255],{"class":183},[174,645,646],{"class":251},"18",[174,648,649],{"class":183},", wb.add_format({",[174,651,316],{"class":206},[174,653,248],{"class":183},[174,655,656],{"class":206},"\"yyyy-mm-dd\"",[174,658,659],{"class":183},"}))\n",[10,661,662,663,667,668,672],{},"The column format applies to cells written without their own format. Anything written ",[664,665,666],"em",{},"with"," a format keeps that one, so the pattern is: column formats for the bulk of the data, explicit formats for the exceptions — a total row, a highlighted cell. The number-format codes themselves are the same ones described in ",[17,669,671],{"href":670},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002F","Applying Number and Date Formats in Excel",", because they are the file format's codes rather than any library's invention.",[10,674,675,679],{},[17,676,678],{"href":677},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-formatted-excel-report-with-xlsxwriter\u002F","Write a Formatted Excel Report with xlsxwriter"," builds a complete styled report on this pattern, including totals, banded rows and autofilters.",[144,681,683],{"id":682},"charts-are-first-class","Charts are first-class",[10,685,686],{},"Charts are where xlsxwriter's API is genuinely nicer than the alternative. A chart is created on the workbook, given series that point at ranges, and inserted into a sheet:",[165,688,690],{"className":167,"code":689,"language":169,"meta":170,"style":170},"chart = wb.add_chart({\"type\": \"column\"})\nchart.add_series({\n    \"name\":       \"Amount\",\n    \"categories\": [\"Summary\", 1, 0, 3, 0],      # sheet, first_row, first_col, last_row, last_col\n    \"values\":     [\"Summary\", 1, 1, 3, 1],\n    \"data_labels\": {\"value\": True, \"num_format\": '#,##0'},\n    \"fill\":       {\"color\": \"#5B5CF0\"},\n})\nchart.set_title({\"name\": \"Sales by region\"})\nchart.set_y_axis({\"major_gridlines\": {\"visible\": True}, \"num_format\": '#,##0'})\nchart.set_legend({\"none\": True})\nchart.set_size({\"width\": 640, \"height\": 360})\nws.insert_chart(\"E2\", chart)\n",[152,691,692,712,717,729,762,791,818,836,840,855,883,897,922],{"__ignoreMap":170},[174,693,694,697,699,702,705,707,710],{"class":66,"line":176},[174,695,696],{"class":183},"chart ",[174,698,200],{"class":179},[174,700,701],{"class":183}," wb.add_chart({",[174,703,704],{"class":206},"\"type\"",[174,706,248],{"class":183},[174,708,709],{"class":206},"\"column\"",[174,711,303],{"class":183},[174,713,714],{"class":66,"line":187},[174,715,716],{"class":183},"chart.add_series({\n",[174,718,719,722,725,727],{"class":66,"line":194},[174,720,721],{"class":206},"    \"name\"",[174,723,724],{"class":183},":       ",[174,726,384],{"class":206},[174,728,266],{"class":183},[174,730,731,734,737,739,741,743,745,747,749,752,754,756,759],{"class":66,"line":213},[174,732,733],{"class":206},"    \"categories\"",[174,735,736],{"class":183},": [",[174,738,224],{"class":206},[174,740,255],{"class":183},[174,742,300],{"class":251},[174,744,255],{"class":183},[174,746,44],{"class":251},[174,748,255],{"class":183},[174,750,751],{"class":251},"3",[174,753,255],{"class":183},[174,755,44],{"class":251},[174,757,758],{"class":183},"],      ",[174,760,761],{"class":575},"# sheet, first_row, first_col, last_row, last_col\n",[174,763,764,767,770,772,774,776,778,780,782,784,786,788],{"class":66,"line":229},[174,765,766],{"class":206},"    \"values\"",[174,768,769],{"class":183},":     [",[174,771,224],{"class":206},[174,773,255],{"class":183},[174,775,300],{"class":251},[174,777,255],{"class":183},[174,779,300],{"class":251},[174,781,255],{"class":183},[174,783,751],{"class":251},[174,785,255],{"class":183},[174,787,300],{"class":251},[174,789,790],{"class":183},"],\n",[174,792,793,796,799,802,804,806,808,810,812,815],{"class":66,"line":234},[174,794,795],{"class":206},"    \"data_labels\"",[174,797,798],{"class":183},": {",[174,800,801],{"class":206},"\"value\"",[174,803,248],{"class":183},[174,805,252],{"class":251},[174,807,255],{"class":183},[174,809,316],{"class":206},[174,811,248],{"class":183},[174,813,814],{"class":206},"'#,##0'",[174,816,817],{"class":183},"},\n",[174,819,820,823,826,829,831,834],{"class":66,"line":269},[174,821,822],{"class":206},"    \"fill\"",[174,824,825],{"class":183},":       {",[174,827,828],{"class":206},"\"color\"",[174,830,248],{"class":183},[174,832,833],{"class":206},"\"#5B5CF0\"",[174,835,817],{"class":183},[174,837,838],{"class":66,"line":292},[174,839,303],{"class":183},[174,841,842,845,848,850,853],{"class":66,"line":306},[174,843,844],{"class":183},"chart.set_title({",[174,846,847],{"class":206},"\"name\"",[174,849,248],{"class":183},[174,851,852],{"class":206},"\"Sales by region\"",[174,854,303],{"class":183},[174,856,857,860,863,865,868,870,872,875,877,879,881],{"class":66,"line":335},[174,858,859],{"class":183},"chart.set_y_axis({",[174,861,862],{"class":206},"\"major_gridlines\"",[174,864,798],{"class":183},[174,866,867],{"class":206},"\"visible\"",[174,869,248],{"class":183},[174,871,252],{"class":251},[174,873,874],{"class":183},"}, ",[174,876,316],{"class":206},[174,878,248],{"class":183},[174,880,814],{"class":206},[174,882,303],{"class":183},[174,884,885,888,891,893,895],{"class":66,"line":362},[174,886,887],{"class":183},"chart.set_legend({",[174,889,890],{"class":206},"\"none\"",[174,892,248],{"class":183},[174,894,252],{"class":251},[174,896,303],{"class":183},[174,898,899,902,905,907,910,912,915,917,920],{"class":66,"line":367},[174,900,901],{"class":183},"chart.set_size({",[174,903,904],{"class":206},"\"width\"",[174,906,248],{"class":183},[174,908,909],{"class":251},"640",[174,911,255],{"class":183},[174,913,914],{"class":206},"\"height\"",[174,916,248],{"class":183},[174,918,919],{"class":251},"360",[174,921,303],{"class":183},[174,923,924,927,930],{"class":66,"line":395},[174,925,926],{"class":183},"ws.insert_chart(",[174,928,929],{"class":206},"\"E2\"",[174,931,932],{"class":183},", chart)\n",[10,934,935,936,939,940,943,944,947,948,951,952,956],{},"The ",[152,937,938],{},"[sheet, first_row, first_col, last_row, last_col]"," form is worth using over the string form (",[152,941,942],{},"=Summary!$A$2:$A$4",") because it is computed rather than typed: build it from ",[152,945,946],{},"len(df)"," and the ranges stay correct when the data grows. Series ranges must point at cells that exist in the ",[664,949,950],{},"saved"," file — a chart whose range extends past the written rows renders as a gap in Excel rather than an error, which is the most common charting complaint. ",[17,953,955],{"href":954},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-a-chart-to-an-excel-file-with-xlsxwriter\u002F","Add a Chart to an Excel File with xlsxwriter"," covers combined chart types, secondary axes and sizing.",[144,958,960],{"id":959},"conditional-formats-and-the-sparkline-exception","Conditional formats and the sparkline exception",[10,962,963,964,967],{},"xlsxwriter writes conditional formatting rules with the same vocabulary Excel uses — cell rules, colour scales, data bars, icon sets — and it is the only Python library that can write ",[664,965,966],{},"sparklines",", the miniature in-cell charts, because openpyxl does not implement them:",[165,969,971],{"className":167,"code":970,"language":169,"meta":170,"style":170},"ws.conditional_format(\"B2:B100\", {\n    \"type\": \"3_color_scale\",\n    \"min_color\": \"#F8696B\", \"mid_color\": \"#FFEB84\", \"max_color\": \"#63BE7B\",\n})\nws.conditional_format(\"C2:C100\", {\n    \"type\": \"cell\", \"criteria\": \"\u003C\", \"value\": 0,\n    \"format\": wb.add_format({\"font_color\": \"#9C0006\", \"bg_color\": \"#FFC7CE\"}),\n})\nws.add_sparkline(\"D2\", {\"range\": \"Detail!B2:M2\", \"type\": \"line\",\n                        \"markers\": True})\n",[152,972,973,984,996,1028,1032,1041,1070,1098,1102,1132],{"__ignoreMap":170},[174,974,975,978,981],{"class":66,"line":176},[174,976,977],{"class":183},"ws.conditional_format(",[174,979,980],{"class":206},"\"B2:B100\"",[174,982,983],{"class":183},", {\n",[174,985,986,989,991,994],{"class":66,"line":187},[174,987,988],{"class":206},"    \"type\"",[174,990,248],{"class":183},[174,992,993],{"class":206},"\"3_color_scale\"",[174,995,266],{"class":183},[174,997,998,1001,1003,1006,1008,1011,1013,1016,1018,1021,1023,1026],{"class":66,"line":194},[174,999,1000],{"class":206},"    \"min_color\"",[174,1002,248],{"class":183},[174,1004,1005],{"class":206},"\"#F8696B\"",[174,1007,255],{"class":183},[174,1009,1010],{"class":206},"\"mid_color\"",[174,1012,248],{"class":183},[174,1014,1015],{"class":206},"\"#FFEB84\"",[174,1017,255],{"class":183},[174,1019,1020],{"class":206},"\"max_color\"",[174,1022,248],{"class":183},[174,1024,1025],{"class":206},"\"#63BE7B\"",[174,1027,266],{"class":183},[174,1029,1030],{"class":66,"line":213},[174,1031,303],{"class":183},[174,1033,1034,1036,1039],{"class":66,"line":229},[174,1035,977],{"class":183},[174,1037,1038],{"class":206},"\"C2:C100\"",[174,1040,983],{"class":183},[174,1042,1043,1045,1047,1050,1052,1055,1057,1060,1062,1064,1066,1068],{"class":66,"line":234},[174,1044,988],{"class":206},[174,1046,248],{"class":183},[174,1048,1049],{"class":206},"\"cell\"",[174,1051,255],{"class":183},[174,1053,1054],{"class":206},"\"criteria\"",[174,1056,248],{"class":183},[174,1058,1059],{"class":206},"\"\u003C\"",[174,1061,255],{"class":183},[174,1063,801],{"class":206},[174,1065,248],{"class":183},[174,1067,44],{"class":251},[174,1069,266],{"class":183},[174,1071,1072,1075,1078,1080,1082,1085,1087,1090,1092,1095],{"class":66,"line":269},[174,1073,1074],{"class":206},"    \"format\"",[174,1076,1077],{"class":183},": wb.add_format({",[174,1079,258],{"class":206},[174,1081,248],{"class":183},[174,1083,1084],{"class":206},"\"#9C0006\"",[174,1086,255],{"class":183},[174,1088,1089],{"class":206},"\"bg_color\"",[174,1091,248],{"class":183},[174,1093,1094],{"class":206},"\"#FFC7CE\"",[174,1096,1097],{"class":183},"}),\n",[174,1099,1100],{"class":66,"line":292},[174,1101,303],{"class":183},[174,1103,1104,1107,1110,1113,1116,1118,1121,1123,1125,1127,1130],{"class":66,"line":306},[174,1105,1106],{"class":183},"ws.add_sparkline(",[174,1108,1109],{"class":206},"\"D2\"",[174,1111,1112],{"class":183},", {",[174,1114,1115],{"class":206},"\"range\"",[174,1117,248],{"class":183},[174,1119,1120],{"class":206},"\"Detail!B2:M2\"",[174,1122,255],{"class":183},[174,1124,704],{"class":206},[174,1126,248],{"class":183},[174,1128,1129],{"class":206},"\"line\"",[174,1131,266],{"class":183},[174,1133,1134,1137,1139,1141],{"class":66,"line":335},[174,1135,1136],{"class":206},"                        \"markers\"",[174,1138,248],{"class":183},[174,1140,252],{"class":251},[174,1142,303],{"class":183},[10,1144,1145,1146,1150,1151,1155],{},"A conditional format is a rule stored in the file, not a computed colour: Excel re-evaluates it every time the values change, which is what makes it survive a reader editing a cell. The equivalent openpyxl API is covered in ",[17,1147,1149],{"href":1148},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002F","Applying Conditional Formatting with openpyxl",", and the xlsxwriter specifics — including why the range and the rule's own formula must agree on the anchor cell — are in ",[17,1152,1154],{"href":1153},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fapply-conditional-formatting-with-xlsxwriter\u002F","Apply Conditional Formatting with xlsxwriter",".",[23,1157,32,1163,32,1166,32,1169,32,1173,32,1177,32,1182,32,1186,32,1190,32,1194,32,1199,32,1203,32,1205,32,1210,32,1213,32,1216,32,1219,32,1222],{"viewBox":1158,"role":26,"ariaLabelledBy":1159,"xmlns":30,"style":1162},"0 0 740 242",[1160,1161],"xw-mem-t","xw-mem-d","width:100%;max-width:740px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[34,1164,1165],{"id":1160},"What constant_memory mode changes",[38,1167,1168],{"id":1161},"In the default mode every written cell is held in memory until close, so peak memory grows with the row count and any row can still be revisited. In constant memory mode each row is serialised as soon as the next row is started, so memory stays flat but rows must be written strictly in order and cannot be revisited.",[42,1170],{"x":44,"y":44,"width":1171,"height":1172,"fill":47},"740","242",[42,1174],{"x":536,"y":1175,"width":1176,"height":70,"rx":104,"fill":55,"stroke":56,"style":57},"34","344",[59,1178,1181],{"x":1179,"y":1180,"style":63},"188","62","default mode",[59,1183,1185],{"x":1179,"y":1184,"style":115},"92","every cell kept until close()",[59,1187,1189],{"x":1179,"y":1188,"style":115},"116","rows may be written in any order",[59,1191,1193],{"x":1179,"y":1192,"style":115},"140","a total row can be added at the end",[59,1195,1198],{"x":1179,"y":1196,"style":1197},"176","font-size:11px;fill:var(--muted,#5b6780);text-anchor:middle","memory grows with the data —",[59,1200,1202],{"x":1179,"y":1201,"style":1197},"194","fine to a few hundred thousand rows",[42,1204],{"x":61,"y":1175,"width":1176,"height":70,"rx":104,"fill":105,"stroke":72,"style":57},[59,1206,1209],{"x":1207,"y":1180,"style":1208},"552","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","constant_memory: True",[59,1211,1212],{"x":1207,"y":1184,"style":115},"each row flushed when the next begins",[59,1214,1215],{"x":1207,"y":1188,"style":115},"strict top-to-bottom, left-to-right order",[59,1217,1218],{"x":1207,"y":1192,"style":115},"no going back — plan the totals first",[59,1220,1221],{"x":1207,"y":1196,"style":1197},"memory stays flat at one row,",[59,1223,1224],{"x":1207,"y":1201,"style":1197},"whether the sheet has 10k rows or 1M",[144,1226,1228],{"id":1227},"constant-memory-for-very-large-sheets","Constant memory for very large sheets",[10,1230,1231,1232,1235],{},"Passing ",[152,1233,1234],{},"{\"constant_memory\": True}"," changes the write model: each row is serialised to a temporary file as soon as the next row starts, so peak memory stays flat regardless of how many rows you write.",[165,1237,1239],{"className":167,"code":1238,"language":169,"meta":170,"style":170},"wb = xlsxwriter.Workbook(\"large.xlsx\", {\"constant_memory\": True,\n                                        \"tmpdir\": \"\u002Fvar\u002Ftmp\"})\nws = wb.add_worksheet(\"Detail\")\nws.write_row(0, 0, [\"order_id\", \"region\", \"amount\"])\nfor i, row in enumerate(source_rows(), start=1):\n    ws.write_row(i, 0, row)\nwb.close()\n",[152,1240,1241,1263,1275,1288,1316,1338,1348],{"__ignoreMap":170},[174,1242,1243,1245,1247,1249,1252,1254,1257,1259,1261],{"class":66,"line":176},[174,1244,197],{"class":183},[174,1246,200],{"class":179},[174,1248,203],{"class":183},[174,1250,1251],{"class":206},"\"large.xlsx\"",[174,1253,1112],{"class":183},[174,1255,1256],{"class":206},"\"constant_memory\"",[174,1258,248],{"class":183},[174,1260,252],{"class":251},[174,1262,266],{"class":183},[174,1264,1265,1268,1270,1273],{"class":66,"line":187},[174,1266,1267],{"class":206},"                                        \"tmpdir\"",[174,1269,248],{"class":183},[174,1271,1272],{"class":206},"\"\u002Fvar\u002Ftmp\"",[174,1274,303],{"class":183},[174,1276,1277,1279,1281,1283,1286],{"class":66,"line":194},[174,1278,216],{"class":183},[174,1280,200],{"class":179},[174,1282,221],{"class":183},[174,1284,1285],{"class":206},"\"Detail\"",[174,1287,210],{"class":183},[174,1289,1290,1292,1294,1296,1298,1300,1303,1305,1308,1310,1313],{"class":66,"line":213},[174,1291,370],{"class":183},[174,1293,44],{"class":251},[174,1295,255],{"class":183},[174,1297,44],{"class":251},[174,1299,376],{"class":183},[174,1301,1302],{"class":206},"\"order_id\"",[174,1304,255],{"class":183},[174,1306,1307],{"class":206},"\"region\"",[174,1309,255],{"class":183},[174,1311,1312],{"class":206},"\"amount\"",[174,1314,1315],{"class":183},"])\n",[174,1317,1318,1320,1323,1325,1327,1330,1332,1334,1336],{"class":66,"line":229},[174,1319,461],{"class":179},[174,1321,1322],{"class":183}," i, row ",[174,1324,467],{"class":179},[174,1326,470],{"class":251},[174,1328,1329],{"class":183},"(source_rows(), ",[174,1331,477],{"class":476},[174,1333,200],{"class":179},[174,1335,300],{"class":251},[174,1337,484],{"class":183},[174,1339,1340,1343,1345],{"class":66,"line":234},[174,1341,1342],{"class":183},"    ws.write_row(i, ",[174,1344,44],{"class":251},[174,1346,1347],{"class":183},", row)\n",[174,1349,1350],{"class":66,"line":269},[174,1351,1352],{"class":183},"wb.close()\n",[10,1354,1355,1356,1360],{},"The cost is that a written row is gone. You cannot go back to add a total at the top, autofit a column after seeing the data, or write row 5 after row 6. Everything that depends on the whole dataset has to be known in advance or written to a second sheet afterwards — which is usually fine, because a summary sheet is a better place for totals than the top of a million-row detail sheet anyway. ",[17,1357,1359],{"href":1358},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-million-rows-to-excel-with-xlsxwriter-constant-memory\u002F","Write a Million Rows to Excel with xlsxwriter"," covers the row limit, the temporary-directory setting and where the remaining time actually goes.",[144,1362,1364],{"id":1363},"tables-panes-and-the-sheet-level-furniture","Tables, panes and the sheet-level furniture",[10,1366,1367],{},"A report is more than styled cells. The settings that make a sheet usable are all worksheet methods, and they are cheap enough that leaving them out is never a considered decision — just an omission:",[165,1369,1371],{"className":167,"code":1370,"language":169,"meta":170,"style":170},"ws.add_table(0, 0, len(rows), 3, {\n    \"name\": \"Regions\",\n    \"columns\": [{\"header\": \"Region\"}, {\"header\": \"Orders\", \"format\": count},\n                {\"header\": \"Amount\", \"format\": money},\n                {\"header\": \"Share\", \"format\": pct,\n                 \"total_function\": \"sum\"}],\n    \"style\": \"Table Style Light 9\",\n    \"total_row\": True,\n})\nws.freeze_panes(1, 0)                       # header stays put while scrolling\nws.set_landscape()\nws.fit_to_pages(1, 0)                       # one page wide when printed\nws.repeat_rows(0)                           # header repeats on every printed page\nws.hide_gridlines(2)                        # cleaner behind a styled table\nws.set_zoom(90)\n",[152,1372,1373,1396,1407,1440,1458,1475,1488,1500,1511,1515,1531,1536,1552,1565,1578],{"__ignoreMap":170},[174,1374,1375,1378,1380,1382,1384,1386,1389,1392,1394],{"class":66,"line":176},[174,1376,1377],{"class":183},"ws.add_table(",[174,1379,44],{"class":251},[174,1381,255],{"class":183},[174,1383,44],{"class":251},[174,1385,255],{"class":183},[174,1387,1388],{"class":251},"len",[174,1390,1391],{"class":183},"(rows), ",[174,1393,751],{"class":251},[174,1395,983],{"class":183},[174,1397,1398,1400,1402,1405],{"class":66,"line":187},[174,1399,721],{"class":206},[174,1401,248],{"class":183},[174,1403,1404],{"class":206},"\"Regions\"",[174,1406,266],{"class":183},[174,1408,1409,1412,1415,1418,1420,1422,1425,1427,1429,1432,1434,1437],{"class":66,"line":194},[174,1410,1411],{"class":206},"    \"columns\"",[174,1413,1414],{"class":183},": [{",[174,1416,1417],{"class":206},"\"header\"",[174,1419,248],{"class":183},[174,1421,379],{"class":206},[174,1423,1424],{"class":183},"}, {",[174,1426,1417],{"class":206},[174,1428,248],{"class":183},[174,1430,1431],{"class":206},"\"Orders\"",[174,1433,255],{"class":183},[174,1435,1436],{"class":206},"\"format\"",[174,1438,1439],{"class":183},": count},\n",[174,1441,1442,1445,1447,1449,1451,1453,1455],{"class":66,"line":213},[174,1443,1444],{"class":183},"                {",[174,1446,1417],{"class":206},[174,1448,248],{"class":183},[174,1450,384],{"class":206},[174,1452,255],{"class":183},[174,1454,1436],{"class":206},[174,1456,1457],{"class":183},": money},\n",[174,1459,1460,1462,1464,1466,1468,1470,1472],{"class":66,"line":229},[174,1461,1444],{"class":183},[174,1463,1417],{"class":206},[174,1465,248],{"class":183},[174,1467,389],{"class":206},[174,1469,255],{"class":183},[174,1471,1436],{"class":206},[174,1473,1474],{"class":183},": pct,\n",[174,1476,1477,1480,1482,1485],{"class":66,"line":234},[174,1478,1479],{"class":206},"                 \"total_function\"",[174,1481,248],{"class":183},[174,1483,1484],{"class":206},"\"sum\"",[174,1486,1487],{"class":183},"}],\n",[174,1489,1490,1493,1495,1498],{"class":66,"line":269},[174,1491,1492],{"class":206},"    \"style\"",[174,1494,248],{"class":183},[174,1496,1497],{"class":206},"\"Table Style Light 9\"",[174,1499,266],{"class":183},[174,1501,1502,1505,1507,1509],{"class":66,"line":292},[174,1503,1504],{"class":206},"    \"total_row\"",[174,1506,248],{"class":183},[174,1508,252],{"class":251},[174,1510,266],{"class":183},[174,1512,1513],{"class":66,"line":306},[174,1514,303],{"class":183},[174,1516,1517,1519,1521,1523,1525,1528],{"class":66,"line":335},[174,1518,558],{"class":183},[174,1520,300],{"class":251},[174,1522,255],{"class":183},[174,1524,44],{"class":251},[174,1526,1527],{"class":183},")                       ",[174,1529,1530],{"class":575},"# header stays put while scrolling\n",[174,1532,1533],{"class":66,"line":362},[174,1534,1535],{"class":183},"ws.set_landscape()\n",[174,1537,1538,1541,1543,1545,1547,1549],{"class":66,"line":367},[174,1539,1540],{"class":183},"ws.fit_to_pages(",[174,1542,300],{"class":251},[174,1544,255],{"class":183},[174,1546,44],{"class":251},[174,1548,1527],{"class":183},[174,1550,1551],{"class":575},"# one page wide when printed\n",[174,1553,1554,1557,1559,1562],{"class":66,"line":395},[174,1555,1556],{"class":183},"ws.repeat_rows(",[174,1558,44],{"class":251},[174,1560,1561],{"class":183},")                           ",[174,1563,1564],{"class":575},"# header repeats on every printed page\n",[174,1566,1567,1570,1572,1575],{"class":66,"line":400},[174,1568,1569],{"class":183},"ws.hide_gridlines(",[174,1571,514],{"class":251},[174,1573,1574],{"class":183},")                        ",[174,1576,1577],{"class":575},"# cleaner behind a styled table\n",[174,1579,1580,1583,1586],{"class":66,"line":458},[174,1581,1582],{"class":183},"ws.set_zoom(",[174,1584,1585],{"class":251},"90",[174,1587,210],{"class":183},[10,1589,1590,1591,1594,1595,1598,1599,1602],{},"An Excel ",[664,1592,1593],{},"table"," is worth preferring over a plain range plus ",[152,1596,1597],{},"autofilter"," wherever the data is a simple rectangle. It gives banded rows without a format per row, a filter, a structured name that formulas elsewhere can reference, and a total row Excel maintains itself. The catch is that ",[152,1600,1601],{},"add_table"," writes its own header cells, so the column headers must be declared in the call rather than written beforehand — writing them first and then adding the table leaves duplicated headings.",[10,1604,1605,1608,1609,1611],{},[152,1606,1607],{},"hide_gridlines(2)"," removes both the screen and print gridlines; ",[152,1610,300],{}," removes only the printed ones. On a sheet where every cell already has a border from its format, the gridlines underneath add visual noise for no information, and turning them off is the single change that most makes generated output look deliberate rather than dumped.",[144,1613,1615],{"id":1614},"repeating-the-same-report-for-many-groups","Repeating the same report for many groups",[10,1617,1618],{},"Because a workbook is cheap to create and nothing can be revisited, the natural shape for a multi-group report is a function that builds one sheet, called in a loop:",[165,1620,1622],{"className":167,"code":1621,"language":169,"meta":170,"style":170},"def add_region_sheet(wb, formats, region, frame):\n    ws = wb.add_worksheet(region[:31])         # sheet titles cap at 31 characters\n    ws.write_row(0, 0, [\"Date\", \"Product\", \"Amount\"], formats[\"header\"])\n    ws.set_column(\"A:A\", 14, formats[\"date\"])\n    ws.set_column(\"B:B\", 22)\n    ws.set_column(\"C:C\", 14, formats[\"money\"])\n    for r, row in enumerate(frame.itertuples(index=False), start=1):\n        ws.write_datetime(r, 0, row.order_date)\n        ws.write_string(r, 1, row.product)\n        ws.write_number(r, 2, float(row.amount))\n    ws.write(len(frame) + 1, 0, \"Total\", formats[\"total_text\"])\n    ws.write_formula(len(frame) + 1, 2,\n                     f\"=SUBTOTAL(109,C2:C{len(frame) + 1})\",\n                     formats[\"total_money\"], float(frame[\"amount\"].sum()))\n    ws.freeze_panes(1, 0)\n    return ws\n\n\nwith xlsxwriter.Workbook(\"regions.xlsx\") as wb:\n    formats = make_formats(wb)                 # created once, shared by every sheet\n    for region, frame in sorted(data.groupby(\"region\")):\n        add_region_sheet(wb, formats, region, frame)\n",[152,1623,1624,1636,1655,1687,1706,1719,1736,1770,1780,1790,1805,1837,1856,1884,1905,1918,1926,1930,1934,1952,1965,1985],{"__ignoreMap":170},[174,1625,1626,1629,1633],{"class":66,"line":176},[174,1627,1628],{"class":179},"def",[174,1630,1632],{"class":1631},"s_Opv"," add_region_sheet",[174,1634,1635],{"class":183},"(wb, formats, region, frame):\n",[174,1637,1638,1641,1643,1646,1649,1652],{"class":66,"line":187},[174,1639,1640],{"class":183},"    ws ",[174,1642,200],{"class":179},[174,1644,1645],{"class":183}," wb.add_worksheet(region[:",[174,1647,1648],{"class":251},"31",[174,1650,1651],{"class":183},"])         ",[174,1653,1654],{"class":575},"# sheet titles cap at 31 characters\n",[174,1656,1657,1660,1662,1664,1666,1668,1671,1673,1676,1678,1680,1683,1685],{"class":66,"line":194},[174,1658,1659],{"class":183},"    ws.write_row(",[174,1661,44],{"class":251},[174,1663,255],{"class":183},[174,1665,44],{"class":251},[174,1667,376],{"class":183},[174,1669,1670],{"class":206},"\"Date\"",[174,1672,255],{"class":183},[174,1674,1675],{"class":206},"\"Product\"",[174,1677,255],{"class":183},[174,1679,384],{"class":206},[174,1681,1682],{"class":183},"], formats[",[174,1684,1417],{"class":206},[174,1686,1315],{"class":183},[174,1688,1689,1692,1694,1696,1698,1701,1704],{"class":66,"line":213},[174,1690,1691],{"class":183},"    ws.set_column(",[174,1693,531],{"class":206},[174,1695,255],{"class":183},[174,1697,104],{"class":251},[174,1699,1700],{"class":183},", formats[",[174,1702,1703],{"class":206},"\"date\"",[174,1705,1315],{"class":183},[174,1707,1708,1710,1712,1714,1717],{"class":66,"line":229},[174,1709,1691],{"class":183},[174,1711,610],{"class":206},[174,1713,255],{"class":183},[174,1715,1716],{"class":251},"22",[174,1718,210],{"class":183},[174,1720,1721,1723,1725,1727,1729,1731,1734],{"class":66,"line":234},[174,1722,1691],{"class":183},[174,1724,627],{"class":206},[174,1726,255],{"class":183},[174,1728,104],{"class":251},[174,1730,1700],{"class":183},[174,1732,1733],{"class":206},"\"money\"",[174,1735,1315],{"class":183},[174,1737,1738,1741,1744,1746,1748,1751,1754,1756,1759,1762,1764,1766,1768],{"class":66,"line":269},[174,1739,1740],{"class":179},"    for",[174,1742,1743],{"class":183}," r, row ",[174,1745,467],{"class":179},[174,1747,470],{"class":251},[174,1749,1750],{"class":183},"(frame.itertuples(",[174,1752,1753],{"class":476},"index",[174,1755,200],{"class":179},[174,1757,1758],{"class":251},"False",[174,1760,1761],{"class":183},"), ",[174,1763,477],{"class":476},[174,1765,200],{"class":179},[174,1767,300],{"class":251},[174,1769,484],{"class":183},[174,1771,1772,1775,1777],{"class":66,"line":292},[174,1773,1774],{"class":183},"        ws.write_datetime(r, ",[174,1776,44],{"class":251},[174,1778,1779],{"class":183},", row.order_date)\n",[174,1781,1782,1785,1787],{"class":66,"line":306},[174,1783,1784],{"class":183},"        ws.write_string(r, ",[174,1786,300],{"class":251},[174,1788,1789],{"class":183},", row.product)\n",[174,1791,1792,1795,1797,1799,1802],{"class":66,"line":335},[174,1793,1794],{"class":183},"        ws.write_number(r, ",[174,1796,514],{"class":251},[174,1798,255],{"class":183},[174,1800,1801],{"class":251},"float",[174,1803,1804],{"class":183},"(row.amount))\n",[174,1806,1807,1810,1812,1815,1818,1821,1823,1825,1827,1830,1832,1835],{"class":66,"line":362},[174,1808,1809],{"class":183},"    ws.write(",[174,1811,1388],{"class":251},[174,1813,1814],{"class":183},"(frame) ",[174,1816,1817],{"class":179},"+",[174,1819,1820],{"class":251}," 1",[174,1822,255],{"class":183},[174,1824,44],{"class":251},[174,1826,255],{"class":183},[174,1828,1829],{"class":206},"\"Total\"",[174,1831,1700],{"class":183},[174,1833,1834],{"class":206},"\"total_text\"",[174,1836,1315],{"class":183},[174,1838,1839,1842,1844,1846,1848,1850,1852,1854],{"class":66,"line":367},[174,1840,1841],{"class":183},"    ws.write_formula(",[174,1843,1388],{"class":251},[174,1845,1814],{"class":183},[174,1847,1817],{"class":179},[174,1849,1820],{"class":251},[174,1851,255],{"class":183},[174,1853,514],{"class":251},[174,1855,266],{"class":183},[174,1857,1858,1861,1864,1868,1870,1872,1874,1876,1879,1882],{"class":66,"line":395},[174,1859,1860],{"class":179},"                     f",[174,1862,1863],{"class":206},"\"=SUBTOTAL(109,C2:C",[174,1865,1867],{"class":1866},"sSjpA","{",[174,1869,1388],{"class":251},[174,1871,1814],{"class":183},[174,1873,1817],{"class":179},[174,1875,1820],{"class":251},[174,1877,1878],{"class":1866},"}",[174,1880,1881],{"class":206},")\"",[174,1883,266],{"class":183},[174,1885,1886,1889,1892,1895,1897,1900,1902],{"class":66,"line":400},[174,1887,1888],{"class":183},"                     formats[",[174,1890,1891],{"class":206},"\"total_money\"",[174,1893,1894],{"class":183},"], ",[174,1896,1801],{"class":251},[174,1898,1899],{"class":183},"(frame[",[174,1901,1312],{"class":206},[174,1903,1904],{"class":183},"].sum()))\n",[174,1906,1907,1910,1912,1914,1916],{"class":66,"line":458},[174,1908,1909],{"class":183},"    ws.freeze_panes(",[174,1911,300],{"class":251},[174,1913,255],{"class":183},[174,1915,44],{"class":251},[174,1917,210],{"class":183},[174,1919,1920,1923],{"class":66,"line":487},[174,1921,1922],{"class":179},"    return",[174,1924,1925],{"class":183}," ws\n",[174,1927,1928],{"class":66,"line":498},[174,1929,191],{"emptyLinePlaceholder":190},[174,1931,1932],{"class":66,"line":509},[174,1933,191],{"emptyLinePlaceholder":190},[174,1935,1936,1938,1940,1943,1946,1949],{"class":66,"line":520},[174,1937,666],{"class":179},[174,1939,203],{"class":183},[174,1941,1942],{"class":206},"\"regions.xlsx\"",[174,1944,1945],{"class":183},") ",[174,1947,1948],{"class":179},"as",[174,1950,1951],{"class":183}," wb:\n",[174,1953,1954,1957,1959,1962],{"class":66,"line":525},[174,1955,1956],{"class":183},"    formats ",[174,1958,200],{"class":179},[174,1960,1961],{"class":183}," make_formats(wb)                 ",[174,1963,1964],{"class":575},"# created once, shared by every sheet\n",[174,1966,1967,1969,1972,1974,1977,1980,1982],{"class":66,"line":541},[174,1968,1740],{"class":179},[174,1970,1971],{"class":183}," region, frame ",[174,1973,467],{"class":179},[174,1975,1976],{"class":251}," sorted",[174,1978,1979],{"class":183},"(data.groupby(",[174,1981,1307],{"class":206},[174,1983,1984],{"class":183},")):\n",[174,1986,1987],{"class":66,"line":555},[174,1988,1989],{"class":183},"        add_region_sheet(wb, formats, region, frame)\n",[10,1991,1992,1993,1996,1997,2000],{},"The formats are created once on the workbook and passed in, which is the whole point: twenty sheets sharing a dozen format objects produce a small file, while twenty sheets each building their own produce hundreds of near-identical style records. Sheet titles are capped at 31 characters and cannot contain ",[152,1994,1995],{},"\\ \u002F ? * [ ] :",", so a name built from data needs sanitising before it reaches ",[152,1998,1999],{},"add_worksheet"," — Excel refuses to open a file that breaks either rule.",[10,2002,2003,2004,2008],{},"Whether the groups belong in one workbook or in one file each is a question about the reader rather than the code. Sheets suit someone who compares regions; separate files suit someone who owns one and should not receive the others. ",[17,2005,2007],{"href":2006},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-one-excel-report-per-region-in-a-loop\u002F","Generate One Excel Report per Region in a Loop"," covers the file-per-group version, including filenames that cannot collide.",[23,2010,32,2015,32,2018,32,2021,32,2023,32,2029,32,2035,32,2040,32,2044,32,2050,32,2054,32,2058,32,2061,32,2065,32,2068,32,2072,32,2075,32,2080,32,2086,32,2090,32,2093,32,2097,32,2100,32,2102,32,2106,32,2109,32,2114,32,2119,32,2122],{"viewBox":2011,"role":26,"ariaLabelledBy":2012,"xmlns":30,"style":1162},"0 0 740 236",[2013,2014],"xw-many-t","xw-many-d",[34,2016,2017],{"id":2013},"One set of formats, many sheets",[38,2019,2020],{"id":2014},"Format objects are created once on the workbook and shared by every sheet the loop adds, so twenty regional sheets carry a dozen style records between them. Creating formats inside the loop instead produces hundreds of near-identical records, a larger file and a slower write.",[42,2022],{"x":44,"y":44,"width":1171,"height":50,"fill":47},[59,2024,2028],{"x":2025,"y":2026,"style":2027},"370","30","font-size:12.5px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","Formats belong to the workbook, not to the sheet",[42,2030],{"x":2031,"y":62,"width":50,"height":2032,"rx":54,"fill":2033,"stroke":2034,"style":57},"252","52","#5b5cf0","var(--brand-strong,#4338ca)",[59,2036,2039],{"x":2025,"y":2037,"style":2038},"72","font-size:12px;font-weight:700;fill:#ffffff;text-anchor:middle","make_formats(wb)",[59,2041,2043],{"x":2025,"y":1184,"style":2042},"font-size:11px;fill:rgba(255,255,255,0.9);text-anchor:middle","header · money · date · total",[66,2045],{"x1":68,"y1":2046,"x2":2047,"y2":2048,"stroke":2049,"style":73},"104","150","134","var(--muted,#5b6780)",[75,2051],{"points":2052,"fill":2053},"146,135 158,129 156,139","#5b6780",[66,2055],{"x1":2056,"y1":2046,"x2":2057,"y2":2048,"stroke":2049,"style":73},"358","310",[75,2059],{"points":2060,"fill":2053},"306,136 318,130 316,140",[66,2062],{"x1":2063,"y1":2046,"x2":2064,"y2":2048,"stroke":2049,"style":73},"382","430",[75,2066],{"points":2067,"fill":2053},"434,136 422,130 424,140",[66,2069],{"x1":2070,"y1":2046,"x2":2071,"y2":2048,"stroke":2049,"style":73},"440","590",[75,2073],{"points":2074,"fill":2053},"594,135 582,129 584,139",[42,2076],{"x":2077,"y":1192,"width":2047,"height":2078,"rx":2079,"fill":105,"stroke":72,"style":57},"40","60","10",[59,2081,2085],{"x":2082,"y":2083,"style":2084},"115","166","font-size:11.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","North",[59,2087,2089],{"x":2082,"y":2088,"style":1197},"186","412 rows",[42,2091],{"x":2092,"y":1192,"width":2047,"height":2078,"rx":2079,"fill":105,"stroke":72,"style":57},"200",[59,2094,2096],{"x":2095,"y":2083,"style":2084},"275","South",[59,2098,2099],{"x":2095,"y":2088,"style":1197},"388 rows",[42,2101],{"x":919,"y":1192,"width":2047,"height":2078,"rx":2079,"fill":105,"stroke":72,"style":57},[59,2103,2105],{"x":2104,"y":2083,"style":2084},"435","East",[59,2107,2108],{"x":2104,"y":2088,"style":1197},"484 rows",[42,2110],{"x":2111,"y":1192,"width":70,"height":2078,"rx":2079,"fill":2112,"stroke":2113,"style":57},"520","#f0f2f5","var(--line,#cdd5e6)",[59,2115,2118],{"x":2116,"y":2083,"style":2117},"610","font-size:11.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","…seventeen more",[59,2120,2121],{"x":2116,"y":2088,"style":1197},"same dozen formats",[59,2123,2125],{"x":2025,"y":2124,"style":1197},"224","A format created inside the loop becomes a style record per sheet — the usual cause of a bloated file",[144,2127,2129],{"id":2128},"driving-it-from-pandas","Driving it from pandas",[10,2131,2132],{},"Most reports start as a DataFrame, and pandas will use xlsxwriter as its engine while still giving you the underlying objects:",[165,2134,2136],{"className":167,"code":2135,"language":169,"meta":170,"style":170},"import pandas as pd\n\nwith pd.ExcelWriter(\"report.xlsx\", engine=\"xlsxwriter\",\n                    datetime_format=\"yyyy-mm-dd\") as writer:\n    df.to_excel(writer, sheet_name=\"Detail\", index=False, startrow=1, header=False)\n\n    wb, ws = writer.book, writer.sheets[\"Detail\"]\n    header = wb.add_format({\"bold\": True, \"bg_color\": \"#1F4E78\",\n                            \"font_color\": \"white\", \"border\": 1})\n    for col, name in enumerate(df.columns):\n        ws.write(0, col, name, header)\n    ws.set_column(0, len(df.columns) - 1, 16)\n    ws.autofilter(0, 0, len(df), len(df.columns) - 1)\n    ws.freeze_panes(1, 0)\n",[152,2137,2138,2150,2154,2175,2191,2231,2235,2250,2275,2294,2308,2318,2342,2370],{"__ignoreMap":170},[174,2139,2140,2142,2145,2147],{"class":66,"line":176},[174,2141,180],{"class":179},[174,2143,2144],{"class":183}," pandas ",[174,2146,1948],{"class":179},[174,2148,2149],{"class":183}," pd\n",[174,2151,2152],{"class":66,"line":187},[174,2153,191],{"emptyLinePlaceholder":190},[174,2155,2156,2158,2161,2163,2165,2168,2170,2173],{"class":66,"line":194},[174,2157,666],{"class":179},[174,2159,2160],{"class":183}," pd.ExcelWriter(",[174,2162,207],{"class":206},[174,2164,255],{"class":183},[174,2166,2167],{"class":476},"engine",[174,2169,200],{"class":179},[174,2171,2172],{"class":206},"\"xlsxwriter\"",[174,2174,266],{"class":183},[174,2176,2177,2180,2182,2184,2186,2188],{"class":66,"line":213},[174,2178,2179],{"class":476},"                    datetime_format",[174,2181,200],{"class":179},[174,2183,656],{"class":206},[174,2185,1945],{"class":183},[174,2187,1948],{"class":179},[174,2189,2190],{"class":183}," writer:\n",[174,2192,2193,2196,2199,2201,2203,2205,2207,2209,2211,2213,2216,2218,2220,2222,2225,2227,2229],{"class":66,"line":229},[174,2194,2195],{"class":183},"    df.to_excel(writer, ",[174,2197,2198],{"class":476},"sheet_name",[174,2200,200],{"class":179},[174,2202,1285],{"class":206},[174,2204,255],{"class":183},[174,2206,1753],{"class":476},[174,2208,200],{"class":179},[174,2210,1758],{"class":251},[174,2212,255],{"class":183},[174,2214,2215],{"class":476},"startrow",[174,2217,200],{"class":179},[174,2219,300],{"class":251},[174,2221,255],{"class":183},[174,2223,2224],{"class":476},"header",[174,2226,200],{"class":179},[174,2228,1758],{"class":251},[174,2230,210],{"class":183},[174,2232,2233],{"class":66,"line":234},[174,2234,191],{"emptyLinePlaceholder":190},[174,2236,2237,2240,2242,2245,2247],{"class":66,"line":269},[174,2238,2239],{"class":183},"    wb, ws ",[174,2241,200],{"class":179},[174,2243,2244],{"class":183}," writer.book, writer.sheets[",[174,2246,1285],{"class":206},[174,2248,2249],{"class":183},"]\n",[174,2251,2252,2255,2257,2259,2261,2263,2265,2267,2269,2271,2273],{"class":66,"line":292},[174,2253,2254],{"class":183},"    header ",[174,2256,200],{"class":179},[174,2258,242],{"class":183},[174,2260,245],{"class":206},[174,2262,248],{"class":183},[174,2264,252],{"class":251},[174,2266,255],{"class":183},[174,2268,1089],{"class":206},[174,2270,248],{"class":183},[174,2272,277],{"class":206},[174,2274,266],{"class":183},[174,2276,2277,2280,2282,2284,2286,2288,2290,2292],{"class":66,"line":306},[174,2278,2279],{"class":206},"                            \"font_color\"",[174,2281,248],{"class":183},[174,2283,263],{"class":206},[174,2285,255],{"class":183},[174,2287,326],{"class":206},[174,2289,248],{"class":183},[174,2291,300],{"class":251},[174,2293,303],{"class":183},[174,2295,2296,2298,2301,2303,2305],{"class":66,"line":335},[174,2297,1740],{"class":179},[174,2299,2300],{"class":183}," col, name ",[174,2302,467],{"class":179},[174,2304,470],{"class":251},[174,2306,2307],{"class":183},"(df.columns):\n",[174,2309,2310,2313,2315],{"class":66,"line":362},[174,2311,2312],{"class":183},"        ws.write(",[174,2314,44],{"class":251},[174,2316,2317],{"class":183},", col, name, header)\n",[174,2319,2320,2322,2324,2326,2328,2331,2334,2336,2338,2340],{"class":66,"line":367},[174,2321,1691],{"class":183},[174,2323,44],{"class":251},[174,2325,255],{"class":183},[174,2327,1388],{"class":251},[174,2329,2330],{"class":183},"(df.columns) ",[174,2332,2333],{"class":179},"-",[174,2335,1820],{"class":251},[174,2337,255],{"class":183},[174,2339,536],{"class":251},[174,2341,210],{"class":183},[174,2343,2344,2347,2349,2351,2353,2355,2357,2360,2362,2364,2366,2368],{"class":66,"line":395},[174,2345,2346],{"class":183},"    ws.autofilter(",[174,2348,44],{"class":251},[174,2350,255],{"class":183},[174,2352,44],{"class":251},[174,2354,255],{"class":183},[174,2356,1388],{"class":251},[174,2358,2359],{"class":183},"(df), ",[174,2361,1388],{"class":251},[174,2363,2330],{"class":183},[174,2365,2333],{"class":179},[174,2367,1820],{"class":251},[174,2369,210],{"class":183},[174,2371,2372,2374,2376,2378,2380],{"class":66,"line":400},[174,2373,1909],{"class":183},[174,2375,300],{"class":251},[174,2377,255],{"class":183},[174,2379,44],{"class":251},[174,2381,210],{"class":183},[10,2383,2384,2385,2388,2389,2392,2393,1155],{},"Writing with ",[152,2386,2387],{},"header=False"," and ",[152,2390,2391],{},"startrow=1",", then writing the header row yourself, is the standard way around xlsxwriter's write-once rule: pandas' own header cells cannot be restyled afterwards, so you simply do not let pandas write them. Everything else — column widths, filters, panes, charts — is applied to the sheet object through ",[152,2394,2395],{},"writer.sheets",[144,2397,2399],{"id":2398},"what-the-engine-does-not-do","What the engine does not do",[10,2401,2402],{},"Two limits catch people out, and both are consequences of the write-only design rather than gaps anybody plans to fill.",[10,2404,2405,2406,2414,2415,2418],{},"The first is that xlsxwriter does not evaluate formulas. It writes the formula string into the cell and, optionally, a cached result you supply; Excel computes the real value when the file is opened. That matters whenever something downstream reads the workbook without Excel ever touching it — a pandas read, an ",[17,2407,2409,2410,2413],{"href":2408},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fread-formula-results-with-openpyxl-data-only\u002F","openpyxl pass in ",[152,2411,2412],{},"data_only"," mode",", a conversion to CSV. All of them see ",[152,2416,2417],{},"None"," where the total should be unless you passed the cached value:",[165,2420,2422],{"className":167,"code":2421,"language":169,"meta":170,"style":170},"ws.write_formula(\"D10\", \"=SUM(D2:D9)\", money, float(df[\"amount\"].sum()))\n#                                              ^ the cached result: cheap insurance\n",[152,2423,2424,2449],{"__ignoreMap":170},[174,2425,2426,2429,2432,2434,2437,2440,2442,2445,2447],{"class":66,"line":176},[174,2427,2428],{"class":183},"ws.write_formula(",[174,2430,2431],{"class":206},"\"D10\"",[174,2433,255],{"class":183},[174,2435,2436],{"class":206},"\"=SUM(D2:D9)\"",[174,2438,2439],{"class":183},", money, ",[174,2441,1801],{"class":251},[174,2443,2444],{"class":183},"(df[",[174,2446,1312],{"class":206},[174,2448,1904],{"class":183},[174,2450,2451],{"class":66,"line":187},[174,2452,2453],{"class":575},"#                                              ^ the cached result: cheap insurance\n",[10,2455,2456,2457,1155],{},"The second is that a workbook is written once and cannot be reopened. There is no \"add a sheet to yesterday's file\" and no \"update cell B4\" — the file is produced whole, every time. In a reporting pipeline that is usually a feature, because regenerating from the source is more reliable than patching a file whose provenance nobody can reconstruct. It becomes a genuine constraint in exactly one situation: a workbook that a person edits between runs and expects their edits to survive. That is a template, and templates belong to openpyxl, which can ",[17,2458,2460],{"href":2459},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fpopulate-excel-template-without-losing-formatting\u002F","fill one without losing its formatting",[10,2462,2463,2464,2388,2467,2470,2471,2474,2475,2477,2478,2482],{},"A third limit is worth naming because it looks like a bug: xlsxwriter writes ",[152,2465,2466],{},".xlsx",[152,2468,2469],{},".xlsm"," only. It cannot produce the legacy ",[152,2472,2473],{},".xls"," format, and it cannot read either. If a downstream system insists on ",[152,2476,2473],{},", the workbook has to go through Excel or LibreOffice for the conversion, which is the same dependency the ",[17,2479,2481],{"href":2480},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002F","PDF export"," needs.",[10,2484,2485],{},"Within those limits the engine is unusually predictable, which is its real advantage in a scheduled job. Nothing depends on what a previous run left behind, nothing is inherited from a template that somebody edited, and a given input produces a byte-identical workbook every time. When a report is wrong, the cause is in the code that built it — which is a much shorter list of suspects than the alternative.",[10,2487,2488],{},"That predictability extends to the output itself. Because nothing is inherited and every format is declared in the code, two people running the same script against the same data get workbooks that differ only in the timestamp you chose to write. For a report that is reviewed, signed off and archived, that reproducibility is worth more than any individual feature — it means a question about last quarter's figures can be answered by re-running the job rather than by trusting whatever file happens to be on the shared drive.",[144,2490,2492],{"id":2491},"key-takeaways","Key takeaways",[2494,2495,2496,2504,2510,2519,2532,2541,2547],"ul",{},[2497,2498,2499,2503],"li",{},[2500,2501,2502],"strong",{},"xlsxwriter writes; it never reads."," New workbooks only. Anything that opens, edits or fills in an existing file is openpyxl's job.",[2497,2505,2506,2509],{},[2500,2507,2508],{},"Create each format once and reuse it."," Formats built inside a loop bloat the file and slow the write.",[2497,2511,2512,2518],{},[2500,2513,2514,2515,2517],{},"Prefer ",[152,2516,597],{}," to per-cell formatting."," One record per column instead of one per cell, and it sets the width at the same time.",[2497,2520,2521,2524,2525,2388,2528,2531],{},[2500,2522,2523],{},"Build chart ranges from row counts."," The list form of ",[152,2526,2527],{},"categories",[152,2529,2530],{},"values"," stays correct as the data grows; a range past the written rows renders as a gap.",[2497,2533,2534,2540],{},[2500,2535,2536,2539],{},[152,2537,2538],{},"constant_memory"," buys flat memory at the price of order."," Rows must be written top to bottom and cannot be revisited — put totals on a summary sheet.",[2497,2542,2543,2546],{},[2500,2544,2545],{},"With pandas, write the data headerless and add your own header row."," It is the only way to control the header's format, because a written cell cannot be restyled.",[2497,2548,2549,2554],{},[2500,2550,2551,2552,1155],{},"Nothing exists until ",[152,2553,582],{}," Use the workbook as a context manager so a failure cannot leave an empty file.",[144,2556,2558],{"id":2557},"frequently-asked-questions","Frequently asked questions",[10,2560,2561,2564],{},[2500,2562,2563],{},"Can xlsxwriter open an existing workbook?","\nNo. It is strictly write-only — it creates new files and cannot read or modify one that already exists. If you need to edit a workbook in place, or fill in a template, that is openpyxl's job.",[10,2566,2567,2570,2571,2573],{},[2500,2568,2569],{},"Is xlsxwriter faster than openpyxl?","\nFor writing a new formatted workbook, yes, usually by a wide margin, and its ",[152,2572,2538],{}," mode keeps memory flat at any row count. For reading or editing it is not a comparison, because xlsxwriter cannot do either.",[10,2575,2576,2579,2580,2583,2584,2587,2588,2388,2591,2593],{},[2500,2577,2578],{},"How do I use xlsxwriter with pandas?","\nPass ",[152,2581,2582],{},"engine=\"xlsxwriter\""," to ",[152,2585,2586],{},"pd.ExcelWriter",", then reach the workbook and worksheet objects through ",[152,2589,2590],{},"writer.book",[152,2592,2395],{}," to apply formats, charts and panes.",[10,2595,2596,2599,2600,2602],{},[2500,2597,2598],{},"Why is my format ignored on a cell pandas already wrote?","\nxlsxwriter cannot restyle a cell that has been written. Apply column formats with ",[152,2601,597],{}," before or instead, or write the header row yourself with the format you want.",[10,2604,2605,2608],{},[2500,2606,2607],{},"Does a number format change the stored value?","\nNo. A format changes only how the number is displayed. The cell still holds the full-precision value and still sums correctly.",[144,2610,2612],{"id":2611},"related","Related",[2494,2614,2615,2624,2637,2647,2661],{},[2497,2616,2617,2620,2621,2623],{},[2500,2618,2619],{},"Parent:"," ",[17,2622,20],{"href":19}," — the report-polishing track this engine belongs to.",[2497,2625,2626,2620,2629,255,2631,255,2633,2388,2635,1155],{},[2500,2627,2628],{},"In this topic:",[17,2630,678],{"href":677},[17,2632,955],{"href":954},[17,2634,1154],{"href":1153},[17,2636,1359],{"href":1358},[2497,2638,2639,2620,2642,2646],{},[2500,2640,2641],{},"The comparison:",[17,2643,2645],{"href":2644},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fopenpyxl-vs-xlsxwriter-vs-pandas-excelwriter\u002F","openpyxl vs xlsxwriter vs pandas.ExcelWriter"," — choosing an engine for a specific job.",[2497,2648,2649,2620,2652,2388,2656,2660],{},[2500,2650,2651],{},"Sibling topics:",[17,2653,2655],{"href":2654},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002F","Styling Excel Cells with openpyxl",[17,2657,2659],{"href":2658},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002F","Creating Charts in Excel with openpyxl"," — the same jobs on the other engine.",[2497,2662,2663,2620,2666,2670],{},[2500,2664,2665],{},"At scale:",[17,2667,2669],{"href":2668},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002F","Working with Large Excel Files in Python"," — the reading side of very large workbooks.",[2672,2673,2674],"style",{},"html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}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 .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_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":170,"searchDepth":187,"depth":187,"links":2676},[2677,2678,2679,2680,2681,2682,2683,2684,2685,2686,2687,2688],{"id":146,"depth":187,"text":147},{"id":590,"depth":187,"text":591},{"id":682,"depth":187,"text":683},{"id":959,"depth":187,"text":960},{"id":1227,"depth":187,"text":1228},{"id":1363,"depth":187,"text":1364},{"id":1614,"depth":187,"text":1615},{"id":2128,"depth":187,"text":2129},{"id":2398,"depth":187,"text":2399},{"id":2491,"depth":187,"text":2492},{"id":2557,"depth":187,"text":2558},{"id":2611,"depth":187,"text":2612},"2026-08-10","The write-only engine that formats fastest: reusable format objects, column-level formatting, charts, conditional formats, panes and constant-memory mode — and exactly when openpyxl is still the right choice.","md",[2693,2695,2697,2699,2701],{"q":2563,"a":2694},"No. It is strictly write-only — it creates new files and cannot read or modify one that already exists. If you need to edit a workbook in place, or fill in a template, that is openpyxl's job.",{"q":2569,"a":2696},"For writing a new formatted workbook, yes, usually by a wide margin, and its constant_memory mode keeps memory flat at any row count. For reading or editing it is not a comparison, because xlsxwriter cannot do either.",{"q":2578,"a":2698},"Pass engine=\"xlsxwriter\" to pd.ExcelWriter, then reach the workbook and worksheet objects through writer.book and writer.sheets to apply formats, charts and panes.",{"q":2598,"a":2700},"xlsxwriter cannot restyle a cell that has been written. Apply column formats with set_column before or instead, or write the header row yourself with the format you want.",{"q":2607,"a":2702},"No. A format changes only how the number is displayed. The cell still holds the full-precision value and still sums correctly.",{"breadcrumb":2704},[2705,2708,2709],{"name":2706,"item":2707},"Home","\u002F",{"name":20,"item":19},{"name":5,"item":2710},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002F","\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter",{"title":2713,"description":2714},"Build Excel Reports with xlsxwriter in Python","Use xlsxwriter to generate formatted .xlsx reports from Python — format objects, set_column, charts, conditional formatting, freeze panes, constant memory mode and pandas integration.","building-excel-reports-with-xlsxwriter","formatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Findex","guide","iBv3UN0Wc0EZ1Z1ORnMPmGag1zBoKTDv-oixb_7U44o",[2720,2724],{"title":2721,"path":2722,"stem":2723,"children":-1},"Format Numbers as Percentages in Excel with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-numbers-as-percentages-in-excel-with-python","formatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-numbers-as-percentages-in-excel-with-python\u002Findex",{"title":955,"path":2725,"stem":2726,"children":-1},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-a-chart-to-an-excel-file-with-xlsxwriter","formatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-a-chart-to-an-excel-file-with-xlsxwriter\u002Findex",1786800026511]