[{"data":1,"prerenderedAt":1575},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-summary-sheet-to-excel-report-python":3,"surround:\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-summary-sheet-to-excel-report-python":1568},{"id":4,"title":5,"body":6,"description":1559,"extension":1560,"meta":1561,"navigation":106,"path":1562,"seo":1563,"stem":1566,"__hash__":1567},"docs\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-summary-sheet-to-excel-report-python\u002Findex.md","Add a Summary Sheet to an Excel Report with Python",{"type":7,"value":8,"toc":1547},"minimark",[9,19,24,27,56,59,63,78,470,473,477,493,818,824,828,831,1237,1244,1248,1251,1338,1345,1349,1452,1456,1459,1463,1469,1484,1496,1505,1509,1518,1522,1543],[10,11,12,13,18],"p",{},"A report that opens on raw rows makes the reader do the work. A Summary sheet does it for them: the totals, the key ratios, and a small breakdown table on the front page, with the detail behind it. This page builds that Summary tab step by step — compute the KPIs with pandas, write the Summary first so it lands leftmost, style the block, and set it as the active tab. It is part of ",[14,15,17],"a",{"href":16},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002F","Building Multi-Sheet Excel Dashboards",".",[20,21,23],"h2",{"id":22},"prerequisites","Prerequisites",[10,25,26],{},"You need pandas for the aggregation and openpyxl as the engine and styling layer:",[28,29,34],"pre",{"className":30,"code":31,"language":32,"meta":33,"style":33},"language-bash shiki shiki-themes github-light github-dark","pip install pandas openpyxl\n","bash","",[35,36,37],"code",{"__ignoreMap":33},[38,39,42,46,50,53],"span",{"class":40,"line":41},"line",1,[38,43,45],{"class":44},"sScJk","pip",[38,47,49],{"class":48},"sZZnC"," install",[38,51,52],{"class":48}," pandas",[38,54,55],{"class":48}," openpyxl\n",[10,57,58],{},"The rest of the page uses a single sample sales DataFrame, built inline in each snippet so every block runs on its own.",[20,60,62],{"id":61},"step-1-compute-the-kpis-with-pandas","Step 1: compute the KPIs with pandas",[10,64,65,66,69,70,73,74,77],{},"Derive the headline numbers from your data, not by hand. Totals come from ",[35,67,68],{},"sum()","; a breakdown comes from ",[35,71,72],{},"groupby",". Keep the breakdown as a flat DataFrame with ",[35,75,76],{},"as_index=False"," so it writes cleanly:",[28,79,83],{"className":80,"code":81,"language":82,"meta":33,"style":33},"language-python shiki shiki-themes github-light github-dark","import pandas as pd\n\nsales = pd.DataFrame({\n    \"region\": [\"North\", \"South\", \"North\", \"West\", \"South\", \"West\"],\n    \"product\": [\"Widget\", \"Gadget\", \"Widget\", \"Gizmo\", \"Gadget\", \"Widget\"],\n    \"revenue\": [12500, 9800, 14200, 7600, 11100, 8300],\n    \"units\":   [125, 98, 142, 76, 111, 83],\n})\n\ntotal_rev = sales[\"revenue\"].sum()\ntotal_units = sales[\"units\"].sum()\navg_price = round(total_rev \u002F total_units, 2)\ntop_region = sales.groupby(\"region\")[\"revenue\"].sum().idxmax()\n\nkpis = pd.DataFrame({\n    \"Metric\": [\"Total Revenue\", \"Total Units\", \"Avg Unit Price\", \"Top Region\"],\n    \"Value\":  [total_rev, total_units, avg_price, top_region],\n})\nby_region = sales.groupby(\"region\", as_index=False)[\"revenue\"].sum()\n\nprint(kpis)\nprint(by_region)\n","python",[35,84,85,101,108,120,158,193,232,271,277,282,299,314,340,362,367,377,405,414,419,448,453,462],{"__ignoreMap":33},[38,86,87,91,95,98],{"class":40,"line":41},[38,88,90],{"class":89},"szBVR","import",[38,92,94],{"class":93},"sVt8B"," pandas ",[38,96,97],{"class":89},"as",[38,99,100],{"class":93}," pd\n",[38,102,104],{"class":40,"line":103},2,[38,105,107],{"emptyLinePlaceholder":106},true,"\n",[38,109,111,114,117],{"class":40,"line":110},3,[38,112,113],{"class":93},"sales ",[38,115,116],{"class":89},"=",[38,118,119],{"class":93}," pd.DataFrame({\n",[38,121,123,126,129,132,135,138,140,142,144,147,149,151,153,155],{"class":40,"line":122},4,[38,124,125],{"class":48},"    \"region\"",[38,127,128],{"class":93},": [",[38,130,131],{"class":48},"\"North\"",[38,133,134],{"class":93},", ",[38,136,137],{"class":48},"\"South\"",[38,139,134],{"class":93},[38,141,131],{"class":48},[38,143,134],{"class":93},[38,145,146],{"class":48},"\"West\"",[38,148,134],{"class":93},[38,150,137],{"class":48},[38,152,134],{"class":93},[38,154,146],{"class":48},[38,156,157],{"class":93},"],\n",[38,159,161,164,166,169,171,174,176,178,180,183,185,187,189,191],{"class":40,"line":160},5,[38,162,163],{"class":48},"    \"product\"",[38,165,128],{"class":93},[38,167,168],{"class":48},"\"Widget\"",[38,170,134],{"class":93},[38,172,173],{"class":48},"\"Gadget\"",[38,175,134],{"class":93},[38,177,168],{"class":48},[38,179,134],{"class":93},[38,181,182],{"class":48},"\"Gizmo\"",[38,184,134],{"class":93},[38,186,173],{"class":48},[38,188,134],{"class":93},[38,190,168],{"class":48},[38,192,157],{"class":93},[38,194,196,199,201,205,207,210,212,215,217,220,222,225,227,230],{"class":40,"line":195},6,[38,197,198],{"class":48},"    \"revenue\"",[38,200,128],{"class":93},[38,202,204],{"class":203},"sj4cs","12500",[38,206,134],{"class":93},[38,208,209],{"class":203},"9800",[38,211,134],{"class":93},[38,213,214],{"class":203},"14200",[38,216,134],{"class":93},[38,218,219],{"class":203},"7600",[38,221,134],{"class":93},[38,223,224],{"class":203},"11100",[38,226,134],{"class":93},[38,228,229],{"class":203},"8300",[38,231,157],{"class":93},[38,233,235,238,241,244,246,249,251,254,256,259,261,264,266,269],{"class":40,"line":234},7,[38,236,237],{"class":48},"    \"units\"",[38,239,240],{"class":93},":   [",[38,242,243],{"class":203},"125",[38,245,134],{"class":93},[38,247,248],{"class":203},"98",[38,250,134],{"class":93},[38,252,253],{"class":203},"142",[38,255,134],{"class":93},[38,257,258],{"class":203},"76",[38,260,134],{"class":93},[38,262,263],{"class":203},"111",[38,265,134],{"class":93},[38,267,268],{"class":203},"83",[38,270,157],{"class":93},[38,272,274],{"class":40,"line":273},8,[38,275,276],{"class":93},"})\n",[38,278,280],{"class":40,"line":279},9,[38,281,107],{"emptyLinePlaceholder":106},[38,283,285,288,290,293,296],{"class":40,"line":284},10,[38,286,287],{"class":93},"total_rev ",[38,289,116],{"class":89},[38,291,292],{"class":93}," sales[",[38,294,295],{"class":48},"\"revenue\"",[38,297,298],{"class":93},"].sum()\n",[38,300,302,305,307,309,312],{"class":40,"line":301},11,[38,303,304],{"class":93},"total_units ",[38,306,116],{"class":89},[38,308,292],{"class":93},[38,310,311],{"class":48},"\"units\"",[38,313,298],{"class":93},[38,315,317,320,322,325,328,331,334,337],{"class":40,"line":316},12,[38,318,319],{"class":93},"avg_price ",[38,321,116],{"class":89},[38,323,324],{"class":203}," round",[38,326,327],{"class":93},"(total_rev ",[38,329,330],{"class":89},"\u002F",[38,332,333],{"class":93}," total_units, ",[38,335,336],{"class":203},"2",[38,338,339],{"class":93},")\n",[38,341,343,346,348,351,354,357,359],{"class":40,"line":342},13,[38,344,345],{"class":93},"top_region ",[38,347,116],{"class":89},[38,349,350],{"class":93}," sales.groupby(",[38,352,353],{"class":48},"\"region\"",[38,355,356],{"class":93},")[",[38,358,295],{"class":48},[38,360,361],{"class":93},"].sum().idxmax()\n",[38,363,365],{"class":40,"line":364},14,[38,366,107],{"emptyLinePlaceholder":106},[38,368,370,373,375],{"class":40,"line":369},15,[38,371,372],{"class":93},"kpis ",[38,374,116],{"class":89},[38,376,119],{"class":93},[38,378,380,383,385,388,390,393,395,398,400,403],{"class":40,"line":379},16,[38,381,382],{"class":48},"    \"Metric\"",[38,384,128],{"class":93},[38,386,387],{"class":48},"\"Total Revenue\"",[38,389,134],{"class":93},[38,391,392],{"class":48},"\"Total Units\"",[38,394,134],{"class":93},[38,396,397],{"class":48},"\"Avg Unit Price\"",[38,399,134],{"class":93},[38,401,402],{"class":48},"\"Top Region\"",[38,404,157],{"class":93},[38,406,408,411],{"class":40,"line":407},17,[38,409,410],{"class":48},"    \"Value\"",[38,412,413],{"class":93},":  [total_rev, total_units, avg_price, top_region],\n",[38,415,417],{"class":40,"line":416},18,[38,418,276],{"class":93},[38,420,422,425,427,429,431,433,437,439,442,444,446],{"class":40,"line":421},19,[38,423,424],{"class":93},"by_region ",[38,426,116],{"class":89},[38,428,350],{"class":93},[38,430,353],{"class":48},[38,432,134],{"class":93},[38,434,436],{"class":435},"s4XuR","as_index",[38,438,116],{"class":89},[38,440,441],{"class":203},"False",[38,443,356],{"class":93},[38,445,295],{"class":48},[38,447,298],{"class":93},[38,449,451],{"class":40,"line":450},20,[38,452,107],{"emptyLinePlaceholder":106},[38,454,456,459],{"class":40,"line":455},21,[38,457,458],{"class":203},"print",[38,460,461],{"class":93},"(kpis)\n",[38,463,465,467],{"class":40,"line":464},22,[38,466,458],{"class":203},[38,468,469],{"class":93},"(by_region)\n",[10,471,472],{},"You now have two things for the front page: a KPI table and a small per-region total table.",[20,474,476],{"id":475},"step-2-write-the-summary-first-then-the-detail","Step 2: write the Summary first, then the detail",[10,478,479,480,483,484,488,489,492],{},"Sheet order follows the order of your ",[35,481,482],{},"to_excel()"," calls, so write the ",[485,486,487],"strong",{},"Summary"," sheet first to make it the leftmost tab. Put the KPI block at the top and the breakdown table a few rows below it, on the same sheet, using ",[35,490,491],{},"startrow",". Then write the detail sheet:",[28,494,496],{"className":80,"code":495,"language":82,"meta":33,"style":33},"import pandas as pd\n\nsales = pd.DataFrame({\n    \"region\": [\"North\", \"South\", \"North\", \"West\"],\n    \"revenue\": [12500, 9800, 14200, 7600],\n    \"units\":   [125, 98, 142, 76],\n})\nkpis = pd.DataFrame({\n    \"Metric\": [\"Total Revenue\", \"Total Units\"],\n    \"Value\":  [sales[\"revenue\"].sum(), sales[\"units\"].sum()],\n})\nby_region = sales.groupby(\"region\", as_index=False)[\"revenue\"].sum()\n\nwith pd.ExcelWriter(\"report.xlsx\", engine=\"openpyxl\") as writer:\n    kpis.to_excel(writer, sheet_name=\"Summary\", index=False, startrow=1)\n    # place the breakdown table below the KPI block\n    start = len(kpis) + 4\n    by_region.to_excel(writer, sheet_name=\"Summary\",\n                       index=False, startrow=start)\n    sales.to_excel(writer, sheet_name=\"Detail\", index=False)\n\nprint(\"Summary tab written first, Detail behind it\")\n",[35,497,498,508,512,520,542,564,586,590,598,612,629,633,657,661,690,723,729,748,762,780,802,806],{"__ignoreMap":33},[38,499,500,502,504,506],{"class":40,"line":41},[38,501,90],{"class":89},[38,503,94],{"class":93},[38,505,97],{"class":89},[38,507,100],{"class":93},[38,509,510],{"class":40,"line":103},[38,511,107],{"emptyLinePlaceholder":106},[38,513,514,516,518],{"class":40,"line":110},[38,515,113],{"class":93},[38,517,116],{"class":89},[38,519,119],{"class":93},[38,521,522,524,526,528,530,532,534,536,538,540],{"class":40,"line":122},[38,523,125],{"class":48},[38,525,128],{"class":93},[38,527,131],{"class":48},[38,529,134],{"class":93},[38,531,137],{"class":48},[38,533,134],{"class":93},[38,535,131],{"class":48},[38,537,134],{"class":93},[38,539,146],{"class":48},[38,541,157],{"class":93},[38,543,544,546,548,550,552,554,556,558,560,562],{"class":40,"line":160},[38,545,198],{"class":48},[38,547,128],{"class":93},[38,549,204],{"class":203},[38,551,134],{"class":93},[38,553,209],{"class":203},[38,555,134],{"class":93},[38,557,214],{"class":203},[38,559,134],{"class":93},[38,561,219],{"class":203},[38,563,157],{"class":93},[38,565,566,568,570,572,574,576,578,580,582,584],{"class":40,"line":195},[38,567,237],{"class":48},[38,569,240],{"class":93},[38,571,243],{"class":203},[38,573,134],{"class":93},[38,575,248],{"class":203},[38,577,134],{"class":93},[38,579,253],{"class":203},[38,581,134],{"class":93},[38,583,258],{"class":203},[38,585,157],{"class":93},[38,587,588],{"class":40,"line":234},[38,589,276],{"class":93},[38,591,592,594,596],{"class":40,"line":273},[38,593,372],{"class":93},[38,595,116],{"class":89},[38,597,119],{"class":93},[38,599,600,602,604,606,608,610],{"class":40,"line":279},[38,601,382],{"class":48},[38,603,128],{"class":93},[38,605,387],{"class":48},[38,607,134],{"class":93},[38,609,392],{"class":48},[38,611,157],{"class":93},[38,613,614,616,619,621,624,626],{"class":40,"line":284},[38,615,410],{"class":48},[38,617,618],{"class":93},":  [sales[",[38,620,295],{"class":48},[38,622,623],{"class":93},"].sum(), sales[",[38,625,311],{"class":48},[38,627,628],{"class":93},"].sum()],\n",[38,630,631],{"class":40,"line":301},[38,632,276],{"class":93},[38,634,635,637,639,641,643,645,647,649,651,653,655],{"class":40,"line":316},[38,636,424],{"class":93},[38,638,116],{"class":89},[38,640,350],{"class":93},[38,642,353],{"class":48},[38,644,134],{"class":93},[38,646,436],{"class":435},[38,648,116],{"class":89},[38,650,441],{"class":203},[38,652,356],{"class":93},[38,654,295],{"class":48},[38,656,298],{"class":93},[38,658,659],{"class":40,"line":342},[38,660,107],{"emptyLinePlaceholder":106},[38,662,663,666,669,672,674,677,679,682,685,687],{"class":40,"line":364},[38,664,665],{"class":89},"with",[38,667,668],{"class":93}," pd.ExcelWriter(",[38,670,671],{"class":48},"\"report.xlsx\"",[38,673,134],{"class":93},[38,675,676],{"class":435},"engine",[38,678,116],{"class":89},[38,680,681],{"class":48},"\"openpyxl\"",[38,683,684],{"class":93},") ",[38,686,97],{"class":89},[38,688,689],{"class":93}," writer:\n",[38,691,692,695,698,700,703,705,708,710,712,714,716,718,721],{"class":40,"line":369},[38,693,694],{"class":93},"    kpis.to_excel(writer, ",[38,696,697],{"class":435},"sheet_name",[38,699,116],{"class":89},[38,701,702],{"class":48},"\"Summary\"",[38,704,134],{"class":93},[38,706,707],{"class":435},"index",[38,709,116],{"class":89},[38,711,441],{"class":203},[38,713,134],{"class":93},[38,715,491],{"class":435},[38,717,116],{"class":89},[38,719,720],{"class":203},"1",[38,722,339],{"class":93},[38,724,725],{"class":40,"line":379},[38,726,728],{"class":727},"sJ8bj","    # place the breakdown table below the KPI block\n",[38,730,731,734,736,739,742,745],{"class":40,"line":407},[38,732,733],{"class":93},"    start ",[38,735,116],{"class":89},[38,737,738],{"class":203}," len",[38,740,741],{"class":93},"(kpis) ",[38,743,744],{"class":89},"+",[38,746,747],{"class":203}," 4\n",[38,749,750,753,755,757,759],{"class":40,"line":416},[38,751,752],{"class":93},"    by_region.to_excel(writer, ",[38,754,697],{"class":435},[38,756,116],{"class":89},[38,758,702],{"class":48},[38,760,761],{"class":93},",\n",[38,763,764,767,769,771,773,775,777],{"class":40,"line":421},[38,765,766],{"class":435},"                       index",[38,768,116],{"class":89},[38,770,441],{"class":203},[38,772,134],{"class":93},[38,774,491],{"class":435},[38,776,116],{"class":89},[38,778,779],{"class":93},"start)\n",[38,781,782,785,787,789,792,794,796,798,800],{"class":40,"line":450},[38,783,784],{"class":93},"    sales.to_excel(writer, ",[38,786,697],{"class":435},[38,788,116],{"class":89},[38,790,791],{"class":48},"\"Detail\"",[38,793,134],{"class":93},[38,795,707],{"class":435},[38,797,116],{"class":89},[38,799,441],{"class":203},[38,801,339],{"class":93},[38,803,804],{"class":40,"line":455},[38,805,107],{"emptyLinePlaceholder":106},[38,807,808,810,813,816],{"class":40,"line":464},[38,809,458],{"class":203},[38,811,812],{"class":93},"(",[38,814,815],{"class":48},"\"Summary tab written first, Detail behind it\"",[38,817,339],{"class":93},[10,819,820,823],{},[35,821,822],{},"startrow=1"," leaves row 1 free for a title. The breakdown starts a few rows under the KPI block so the two tables read as distinct sections.",[20,825,827],{"id":826},"step-3-style-the-kpi-block","Step 3: style the KPI block",[10,829,830],{},"A Summary earns its place by being readable at a glance. Reopen the workbook and apply a title, bold headers, a fill behind the KPI labels, and a number format on the values. openpyxl styles are set cell by cell on the loaded worksheet:",[28,832,834],{"className":80,"code":833,"language":82,"meta":33,"style":33},"from openpyxl import load_workbook\nfrom openpyxl.styles import Font, PatternFill, Alignment\n\nwb = load_workbook(\"report.xlsx\")\nws = wb[\"Summary\"]\n\n# Title in the reserved top row\nws[\"A1\"] = \"Sales Summary\"\nws[\"A1\"].font = Font(size=14, bold=True)\n\n# Bold the KPI header row (row 2, since startrow=1 shifted it down)\nheader_fill = PatternFill(\"solid\", fgColor=\"DDEBF7\")\nfor cell in ws[2]:\n    if cell.value is not None:\n        cell.font = Font(bold=True)\n        cell.fill = header_fill\n        cell.alignment = Alignment(horizontal=\"left\")\n\n# Currency format on the Value column for the KPI rows\nfor row in ws.iter_rows(min_row=3, max_row=4, min_col=2, max_col=2):\n    for cell in row:\n        if isinstance(cell.value, (int, float)):\n            cell.number_format = \"#,##0\"\n\nws.column_dimensions[\"A\"].width = 18\nws.column_dimensions[\"B\"].width = 16\nwb.save(\"report.xlsx\")\nprint(\"Styled the Summary KPI block\")\n",[35,835,836,849,861,865,879,894,898,903,919,953,957,962,987,1006,1026,1043,1053,1073,1077,1082,1133,1145,1167,1178,1183,1200,1215,1225],{"__ignoreMap":33},[38,837,838,841,844,846],{"class":40,"line":41},[38,839,840],{"class":89},"from",[38,842,843],{"class":93}," openpyxl ",[38,845,90],{"class":89},[38,847,848],{"class":93}," load_workbook\n",[38,850,851,853,856,858],{"class":40,"line":103},[38,852,840],{"class":89},[38,854,855],{"class":93}," openpyxl.styles ",[38,857,90],{"class":89},[38,859,860],{"class":93}," Font, PatternFill, Alignment\n",[38,862,863],{"class":40,"line":110},[38,864,107],{"emptyLinePlaceholder":106},[38,866,867,870,872,875,877],{"class":40,"line":122},[38,868,869],{"class":93},"wb ",[38,871,116],{"class":89},[38,873,874],{"class":93}," load_workbook(",[38,876,671],{"class":48},[38,878,339],{"class":93},[38,880,881,884,886,889,891],{"class":40,"line":160},[38,882,883],{"class":93},"ws ",[38,885,116],{"class":89},[38,887,888],{"class":93}," wb[",[38,890,702],{"class":48},[38,892,893],{"class":93},"]\n",[38,895,896],{"class":40,"line":195},[38,897,107],{"emptyLinePlaceholder":106},[38,899,900],{"class":40,"line":234},[38,901,902],{"class":727},"# Title in the reserved top row\n",[38,904,905,908,911,914,916],{"class":40,"line":273},[38,906,907],{"class":93},"ws[",[38,909,910],{"class":48},"\"A1\"",[38,912,913],{"class":93},"] ",[38,915,116],{"class":89},[38,917,918],{"class":48}," \"Sales Summary\"\n",[38,920,921,923,925,928,930,933,936,938,941,943,946,948,951],{"class":40,"line":279},[38,922,907],{"class":93},[38,924,910],{"class":48},[38,926,927],{"class":93},"].font ",[38,929,116],{"class":89},[38,931,932],{"class":93}," Font(",[38,934,935],{"class":435},"size",[38,937,116],{"class":89},[38,939,940],{"class":203},"14",[38,942,134],{"class":93},[38,944,945],{"class":435},"bold",[38,947,116],{"class":89},[38,949,950],{"class":203},"True",[38,952,339],{"class":93},[38,954,955],{"class":40,"line":284},[38,956,107],{"emptyLinePlaceholder":106},[38,958,959],{"class":40,"line":301},[38,960,961],{"class":727},"# Bold the KPI header row (row 2, since startrow=1 shifted it down)\n",[38,963,964,967,969,972,975,977,980,982,985],{"class":40,"line":316},[38,965,966],{"class":93},"header_fill ",[38,968,116],{"class":89},[38,970,971],{"class":93}," PatternFill(",[38,973,974],{"class":48},"\"solid\"",[38,976,134],{"class":93},[38,978,979],{"class":435},"fgColor",[38,981,116],{"class":89},[38,983,984],{"class":48},"\"DDEBF7\"",[38,986,339],{"class":93},[38,988,989,992,995,998,1001,1003],{"class":40,"line":342},[38,990,991],{"class":89},"for",[38,993,994],{"class":93}," cell ",[38,996,997],{"class":89},"in",[38,999,1000],{"class":93}," ws[",[38,1002,336],{"class":203},[38,1004,1005],{"class":93},"]:\n",[38,1007,1008,1011,1014,1017,1020,1023],{"class":40,"line":364},[38,1009,1010],{"class":89},"    if",[38,1012,1013],{"class":93}," cell.value ",[38,1015,1016],{"class":89},"is",[38,1018,1019],{"class":89}," not",[38,1021,1022],{"class":203}," None",[38,1024,1025],{"class":93},":\n",[38,1027,1028,1031,1033,1035,1037,1039,1041],{"class":40,"line":369},[38,1029,1030],{"class":93},"        cell.font ",[38,1032,116],{"class":89},[38,1034,932],{"class":93},[38,1036,945],{"class":435},[38,1038,116],{"class":89},[38,1040,950],{"class":203},[38,1042,339],{"class":93},[38,1044,1045,1048,1050],{"class":40,"line":379},[38,1046,1047],{"class":93},"        cell.fill ",[38,1049,116],{"class":89},[38,1051,1052],{"class":93}," header_fill\n",[38,1054,1055,1058,1060,1063,1066,1068,1071],{"class":40,"line":407},[38,1056,1057],{"class":93},"        cell.alignment ",[38,1059,116],{"class":89},[38,1061,1062],{"class":93}," Alignment(",[38,1064,1065],{"class":435},"horizontal",[38,1067,116],{"class":89},[38,1069,1070],{"class":48},"\"left\"",[38,1072,339],{"class":93},[38,1074,1075],{"class":40,"line":416},[38,1076,107],{"emptyLinePlaceholder":106},[38,1078,1079],{"class":40,"line":421},[38,1080,1081],{"class":727},"# Currency format on the Value column for the KPI rows\n",[38,1083,1084,1086,1089,1091,1094,1097,1099,1102,1104,1107,1109,1112,1114,1117,1119,1121,1123,1126,1128,1130],{"class":40,"line":450},[38,1085,991],{"class":89},[38,1087,1088],{"class":93}," row ",[38,1090,997],{"class":89},[38,1092,1093],{"class":93}," ws.iter_rows(",[38,1095,1096],{"class":435},"min_row",[38,1098,116],{"class":89},[38,1100,1101],{"class":203},"3",[38,1103,134],{"class":93},[38,1105,1106],{"class":435},"max_row",[38,1108,116],{"class":89},[38,1110,1111],{"class":203},"4",[38,1113,134],{"class":93},[38,1115,1116],{"class":435},"min_col",[38,1118,116],{"class":89},[38,1120,336],{"class":203},[38,1122,134],{"class":93},[38,1124,1125],{"class":435},"max_col",[38,1127,116],{"class":89},[38,1129,336],{"class":203},[38,1131,1132],{"class":93},"):\n",[38,1134,1135,1138,1140,1142],{"class":40,"line":455},[38,1136,1137],{"class":89},"    for",[38,1139,994],{"class":93},[38,1141,997],{"class":89},[38,1143,1144],{"class":93}," row:\n",[38,1146,1147,1150,1153,1156,1159,1161,1164],{"class":40,"line":464},[38,1148,1149],{"class":89},"        if",[38,1151,1152],{"class":203}," isinstance",[38,1154,1155],{"class":93},"(cell.value, (",[38,1157,1158],{"class":203},"int",[38,1160,134],{"class":93},[38,1162,1163],{"class":203},"float",[38,1165,1166],{"class":93},")):\n",[38,1168,1170,1173,1175],{"class":40,"line":1169},23,[38,1171,1172],{"class":93},"            cell.number_format ",[38,1174,116],{"class":89},[38,1176,1177],{"class":48}," \"#,##0\"\n",[38,1179,1181],{"class":40,"line":1180},24,[38,1182,107],{"emptyLinePlaceholder":106},[38,1184,1186,1189,1192,1195,1197],{"class":40,"line":1185},25,[38,1187,1188],{"class":93},"ws.column_dimensions[",[38,1190,1191],{"class":48},"\"A\"",[38,1193,1194],{"class":93},"].width ",[38,1196,116],{"class":89},[38,1198,1199],{"class":203}," 18\n",[38,1201,1203,1205,1208,1210,1212],{"class":40,"line":1202},26,[38,1204,1188],{"class":93},[38,1206,1207],{"class":48},"\"B\"",[38,1209,1194],{"class":93},[38,1211,116],{"class":89},[38,1213,1214],{"class":203}," 16\n",[38,1216,1218,1221,1223],{"class":40,"line":1217},27,[38,1219,1220],{"class":93},"wb.save(",[38,1222,671],{"class":48},[38,1224,339],{"class":93},[38,1226,1228,1230,1232,1235],{"class":40,"line":1227},28,[38,1229,458],{"class":203},[38,1231,812],{"class":93},[38,1233,1234],{"class":48},"\"Styled the Summary KPI block\"",[38,1236,339],{"class":93},[10,1238,1239,1240,18],{},"For the full styling vocabulary — borders, conditional fills, themed colors — see ",[14,1241,1243],{"href":1242},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002F","Styling Excel Cells with openpyxl",[20,1245,1247],{"id":1246},"step-4-make-the-summary-the-active-frozen-front-page","Step 4: make the Summary the active, frozen front page",[10,1249,1250],{},"Even though you wrote it first, set it explicitly as the active tab so Excel opens on it, and freeze the top rows so the title and KPIs stay put while scrolling:",[28,1252,1254],{"className":80,"code":1253,"language":82,"meta":33,"style":33},"from openpyxl import load_workbook\n\nwb = load_workbook(\"report.xlsx\")\nwb.active = wb.sheetnames.index(\"Summary\")  # open here on launch\nwb[\"Summary\"].freeze_panes = \"A3\"           # pin title + KPI header\nwb.save(\"report.xlsx\")\nprint(\"Active tab:\", wb.active.title)\n",[35,1255,1256,1266,1270,1282,1300,1318,1326],{"__ignoreMap":33},[38,1257,1258,1260,1262,1264],{"class":40,"line":41},[38,1259,840],{"class":89},[38,1261,843],{"class":93},[38,1263,90],{"class":89},[38,1265,848],{"class":93},[38,1267,1268],{"class":40,"line":103},[38,1269,107],{"emptyLinePlaceholder":106},[38,1271,1272,1274,1276,1278,1280],{"class":40,"line":110},[38,1273,869],{"class":93},[38,1275,116],{"class":89},[38,1277,874],{"class":93},[38,1279,671],{"class":48},[38,1281,339],{"class":93},[38,1283,1284,1287,1289,1292,1294,1297],{"class":40,"line":122},[38,1285,1286],{"class":93},"wb.active ",[38,1288,116],{"class":89},[38,1290,1291],{"class":93}," wb.sheetnames.index(",[38,1293,702],{"class":48},[38,1295,1296],{"class":93},")  ",[38,1298,1299],{"class":727},"# open here on launch\n",[38,1301,1302,1305,1307,1310,1312,1315],{"class":40,"line":160},[38,1303,1304],{"class":93},"wb[",[38,1306,702],{"class":48},[38,1308,1309],{"class":93},"].freeze_panes ",[38,1311,116],{"class":89},[38,1313,1314],{"class":48}," \"A3\"",[38,1316,1317],{"class":727},"           # pin title + KPI header\n",[38,1319,1320,1322,1324],{"class":40,"line":195},[38,1321,1220],{"class":93},[38,1323,671],{"class":48},[38,1325,339],{"class":93},[38,1327,1328,1330,1332,1335],{"class":40,"line":234},[38,1329,458],{"class":203},[38,1331,812],{"class":93},[38,1333,1334],{"class":48},"\"Active tab:\"",[38,1336,1337],{"class":93},", wb.active.title)\n",[10,1339,1340,1341,1344],{},"Setting ",[35,1342,1343],{},"wb.active"," by index is the reliable way to control which tab the reader sees first, independent of sheet order.",[20,1346,1348],{"id":1347},"common-pitfalls","Common pitfalls",[1350,1351,1352,1368],"table",{},[1353,1354,1355],"thead",{},[1356,1357,1358,1362,1365],"tr",{},[1359,1360,1361],"th",{},"Symptom",[1359,1363,1364],{},"Cause",[1359,1366,1367],{},"Fix",[1369,1370,1371,1390,1401,1419,1436],"tbody",{},[1356,1372,1373,1377,1380],{},[1374,1375,1376],"td",{},"Summary ends up as the rightmost tab",[1374,1378,1379],{},"It was written after the detail sheets",[1374,1381,1382,1383,1385,1386,1389],{},"Make the Summary the first ",[35,1384,482],{}," call, or reorder ",[35,1387,1388],{},"wb._sheets"," afterward.",[1356,1391,1392,1395,1398],{},[1374,1393,1394],{},"KPIs disagree with the detail",[1374,1396,1397],{},"The Summary was hard-coded or computed from stale data",[1374,1399,1400],{},"Always derive KPIs from the same DataFrame the detail sheet is written from, in the same run.",[1356,1402,1403,1406,1409],{},[1374,1404,1405],{},"Number formats lost after reopening",[1374,1407,1408],{},"Styling applied before pandas overwrote the cells",[1374,1410,1411,1412,1414,1415,1418],{},"Style after all ",[35,1413,482],{}," writes — reopen with ",[35,1416,1417],{},"load_workbook()"," and style last.",[1356,1420,1421,1424,1429],{},[1374,1422,1423],{},"Report opens on the wrong tab",[1374,1425,1426,1428],{},[35,1427,1343],{}," never set",[1374,1430,1431,1432,1435],{},"Set ",[35,1433,1434],{},"wb.active = wb.sheetnames.index(\"Summary\")"," before saving.",[1356,1437,1438,1441,1446],{},[1374,1439,1440],{},"Styles target the wrong row",[1374,1442,1443,1445],{},[35,1444,491],{}," shifted the table down and the style code didn't account for it",[1374,1447,1448,1449,1451],{},"Track the offset: with ",[35,1450,822],{},", the header is row 2 and data starts at row 3.",[20,1453,1455],{"id":1454},"scale-note","Scale note",[10,1457,1458],{},"Recompute KPIs from the in-memory DataFrame rather than reading the detail sheet back from disk — reading back is slower and risks parsing formatted strings as text. For very large detail sets, aggregate once into the small tables the Summary needs and write the heavy detail separately; the Summary stays cheap to build no matter how big the underlying data grows.",[20,1460,1462],{"id":1461},"frequently-asked-questions","Frequently asked questions",[10,1464,1465,1468],{},[485,1466,1467],{},"Should I compute KPIs from the DataFrame or read them back from the Excel detail sheet?","\nCompute from the DataFrame in the same run. Reading the sheet back is slower and can misread formatted cells. Keeping a single source DataFrame guarantees the Summary and detail agree.",[10,1470,1471,1474,1475,1477,1478,1480,1481,1483],{},[485,1472,1473],{},"How do I make the Summary the first tab?","\nWrite it with the first ",[35,1476,482],{}," call so it lands leftmost. If it was written later, sort ",[35,1479,1388],{}," so ",[35,1482,702],{}," comes first before saving.",[10,1485,1486,1489,1490,1492,1493,1495],{},[485,1487,1488],{},"Why did my cell styles vanish?","\nYou styled before pandas wrote the data, so the ",[35,1491,482],{}," call overwrote the styled cells. Always reopen the finished file with ",[35,1494,1417],{}," and apply styles as the last step.",[10,1497,1498,1501,1502,1504],{},[485,1499,1500],{},"The Summary opens on the wrong tab — why?","\nSheet order and the active tab are separate. Set ",[35,1503,1343],{}," to the Summary's index explicitly; Excel opens on whatever was active when the file was saved.",[20,1506,1508],{"id":1507},"conclusion","Conclusion",[10,1510,1511,1512,1514,1515,1517],{},"A good Summary sheet is computed, not typed: pandas ",[35,1513,68],{}," and ",[35,1516,72],{}," produce the KPIs and the breakdown, you write the Summary first so it sits leftmost, you style the block after the data is written, and you set it as the active, frozen front page. Drive everything from one source DataFrame in a single run and the front page can never drift out of sync with the detail behind it.",[20,1519,1521],{"id":1520},"where-to-go-next","Where to go next",[1523,1524,1525,1531,1538],"ul",{},[1526,1527,1528,1530],"li",{},[14,1529,17],{"href":16}," — add a chart and cross-sheet navigation to this Summary.",[1526,1532,1533,1537],{},[14,1534,1536],{"href":1535},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fwrite-multiple-dataframes-to-one-excel-file\u002F","Write Multiple DataFrames to One Excel File"," — the writer mechanics behind the multi-sheet write above.",[1526,1539,1540,1542],{},[14,1541,1243],{"href":1242}," — fonts, fills, borders, and number formats in depth.",[1544,1545,1546],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":33,"searchDepth":103,"depth":103,"links":1548},[1549,1550,1551,1552,1553,1554,1555,1556,1557,1558],{"id":22,"depth":103,"text":23},{"id":61,"depth":103,"text":62},{"id":475,"depth":103,"text":476},{"id":826,"depth":103,"text":827},{"id":1246,"depth":103,"text":1247},{"id":1347,"depth":103,"text":1348},{"id":1454,"depth":103,"text":1455},{"id":1461,"depth":103,"text":1462},{"id":1507,"depth":103,"text":1508},{"id":1520,"depth":103,"text":1521},"Compute KPIs with pandas groupby, write a styled Summary sheet first, then detail sheets, and make the Summary the active leftmost tab — all with openpyxl.","md",{},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-summary-sheet-to-excel-report-python",{"title":1564,"description":1565},"Add a Summary Sheet to an Excel Report","Build a front-page Summary tab in Python: aggregate KPIs with pandas groupby, write it before the detail sheets, style the block, and set it as the active first tab.","automating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-summary-sheet-to-excel-report-python\u002Findex","TFZzAZfi62FATXwgzA7MIbcS8gjWfdHji8NIiSk_7KA",[1569,1572],{"title":17,"path":1570,"stem":1571,"children":-1},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards","automating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Findex",{"title":1536,"path":1573,"stem":1574,"children":-1},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fwrite-multiple-dataframes-to-one-excel-file","automating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fwrite-multiple-dataframes-to-one-excel-file\u002Findex",1781773160969]