[{"data":1,"prerenderedAt":2433},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Findent-cells-to-show-a-hierarchy-with-openpyxl":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Findent-cells-to-show-a-hierarchy-with-openpyxl":2399,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Findent-cells-to-show-a-hierarchy-with-openpyxl":2408,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Findent-cells-to-show-a-hierarchy-with-openpyxl":2413,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Findent-cells-to-show-a-hierarchy-with-openpyxl":2414},{"id":4,"title":5,"body":6,"dateModified":2364,"datePublished":2364,"description":2365,"extension":2366,"faq":2367,"keyword":2376,"kind":2377,"meta":2378,"navigation":367,"path":2387,"seo":2388,"slug":2391,"stem":2392,"topics":2393,"type":2397,"__hash__":2398},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Findent-cells-to-show-a-hierarchy-with-openpyxl\u002Findex.md","Indent Cells to Show a Hierarchy with openpyxl",{"type":7,"value":8,"toc":2351},"minimark",[9,19,279,284,312,318,322,325,725,734,737,991,994,998,1001,1170,1182,1186,1193,1599,1606,1801,1805,1817,1993,2009,2104,2108,2111,2214,2217,2221,2259,2263,2273,2277,2289,2293,2299,2305,2311,2317,2321,2348],[10,11,12,13,18],"p",{},"Hierarchies are everywhere in finance and operations reports: a chart of accounts that rolls up into cost groups, an organisation chart, a bill of materials, a product catalogue with categories and subcategories. Printed as a flat table they are hard to read; the convention readers expect is an outline — children indented under their parent, parents in bold with totals, and branches that can be folded away. Excel supports all of it natively, and openpyxl can set each piece. This page, part of ",[14,15,17],"a",{"href":16},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002F","Styling Excel Cells with openpyxl",", turns a parent–child table of expense accounts into an indented, rolled-up, collapsible report.",[20,21,29,30,29,34,29,38,29,46,29,55,29,59,29,66,29,69,29,73,29,77,29,81,29,85,29,90,29,92,29,96,29,98,29,101,29,103,29,106,29,109,29,113,29,115,29,119,29,121,29,123,29,126,29,129,29,133,29,135,29,138,29,140,29,142,29,144,29,147,29,150,29,154,29,156,29,159,29,161,29,163,29,165,29,168,29,171,29,175,29,177,29,180,29,182,29,184,29,186,29,189,29,192,29,195,29,198,29,200,29,203,29,205,29,207,29,209,29,211,29,213,29,215,29,217,29,219,29,221,29,224,29,226,29,228,29,230,29,233,29,235,29,237,29,239,29,242,29,248,29,252,29,260,29,265,29,273],"svg",{"viewBox":22,"role":23,"ariaLabelledBy":24,"xmlns":27,"style":28},"84 0 592 220","img",[25,26],"ind-shape-t","ind-shape-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:592px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[31,32,33],"title",{"id":25},"A parent–child table becomes an indented outline",[35,36,37],"desc",{"id":26},"Left, the accounts as a flat parent-child table: each row has a code, a parent code and a name. Right, the same rows as an outline: Operating expenses at the top, People, Premises and Technology indented one level under it, and the individual accounts such as Salaries and Rent indented a further level under their groups.",[39,40],"rect",{"x":41,"y":42,"width":43,"height":44,"fill":45},"84","0","592","220","#ffffff",[39,47],{"x":48,"y":49,"width":50,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"100.4","40.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[39,56],{"x":57,"y":49,"width":58,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"132.4","72.5",[60,61,65],"text",{"x":62,"y":63,"style":64},"168.6","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[39,67],{"x":68,"y":49,"width":58,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"204.8",[60,70,72],{"x":71,"y":63,"style":64},"241.1","B",[39,74],{"x":75,"y":49,"width":76,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"277.3","115.4",[60,78,80],{"x":79,"y":63,"style":64},"335.0","C",[39,82],{"x":48,"y":83,"width":50,"height":84,"rx":42,"fill":52,"stroke":53,"style":54},"64.0","28.0",[60,86,89],{"x":87,"y":88,"style":64},"116.4","83.0","1",[39,91],{"x":57,"y":83,"width":58,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,93,95],{"x":62,"y":88,"style":94},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Code",[39,97],{"x":68,"y":83,"width":58,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,99,100],{"x":71,"y":88,"style":94},"Parent",[39,102],{"x":75,"y":83,"width":76,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,104,105],{"x":79,"y":88,"style":94},"Name",[39,107],{"x":48,"y":108,"width":50,"height":84,"rx":42,"fill":52,"stroke":53,"style":54},"92.0",[60,110,112],{"x":87,"y":111,"style":64},"111.0","2",[39,114],{"x":57,"y":108,"width":58,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,116,118],{"x":62,"y":111,"style":117},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","OPEX",[39,120],{"x":68,"y":108,"width":58,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[39,122],{"x":75,"y":108,"width":76,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,124,125],{"x":79,"y":111,"style":117},"Operating exp.",[39,127],{"x":48,"y":128,"width":50,"height":84,"rx":42,"fill":52,"stroke":53,"style":54},"120.0",[60,130,132],{"x":87,"y":131,"style":64},"139.0","3",[39,134],{"x":57,"y":128,"width":58,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,136,137],{"x":62,"y":131,"style":117},"PEOPLE",[39,139],{"x":68,"y":128,"width":58,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,141,118],{"x":71,"y":131,"style":117},[39,143],{"x":75,"y":128,"width":76,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,145,146],{"x":79,"y":131,"style":117},"People",[39,148],{"x":48,"y":149,"width":50,"height":84,"rx":42,"fill":52,"stroke":53,"style":54},"148.0",[60,151,153],{"x":87,"y":152,"style":64},"167.0","4",[39,155],{"x":57,"y":149,"width":58,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,157,158],{"x":62,"y":152,"style":117},"6000",[39,160],{"x":68,"y":149,"width":58,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,162,137],{"x":71,"y":152,"style":117},[39,164],{"x":75,"y":149,"width":76,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,166,167],{"x":79,"y":152,"style":117},"Salaries",[39,169],{"x":48,"y":170,"width":50,"height":84,"rx":42,"fill":52,"stroke":53,"style":54},"176.0",[60,172,174],{"x":87,"y":173,"style":64},"195.0","5",[39,176],{"x":57,"y":170,"width":58,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,178,179],{"x":62,"y":173,"style":117},"PREM",[39,181],{"x":68,"y":170,"width":58,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,183,118],{"x":71,"y":173,"style":117},[39,185],{"x":75,"y":170,"width":76,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,187,188],{"x":79,"y":173,"style":117},"Premises",[39,190],{"x":191,"y":49,"width":50,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"512.2",[39,193],{"x":194,"y":49,"width":76,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"544.2",[60,196,65],{"x":197,"y":63,"style":64},"601.9",[39,199],{"x":191,"y":83,"width":50,"height":84,"rx":42,"fill":52,"stroke":53,"style":54},[60,201,89],{"x":202,"y":88,"style":64},"528.2",[39,204],{"x":194,"y":83,"width":76,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,206,105],{"x":197,"y":88,"style":94},[39,208],{"x":191,"y":108,"width":50,"height":84,"rx":42,"fill":52,"stroke":53,"style":54},[60,210,112],{"x":202,"y":111,"style":64},[39,212],{"x":194,"y":108,"width":76,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,214,125],{"x":197,"y":111,"style":117},[39,216],{"x":191,"y":128,"width":50,"height":84,"rx":42,"fill":52,"stroke":53,"style":54},[60,218,132],{"x":202,"y":131,"style":64},[39,220],{"x":194,"y":128,"width":76,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,222,223],{"x":197,"y":131,"style":117},"   People",[39,225],{"x":191,"y":149,"width":50,"height":84,"rx":42,"fill":52,"stroke":53,"style":54},[60,227,153],{"x":202,"y":152,"style":64},[39,229],{"x":194,"y":149,"width":76,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,231,232],{"x":197,"y":152,"style":117},"      Salaries",[39,234],{"x":191,"y":170,"width":50,"height":84,"rx":42,"fill":52,"stroke":53,"style":54},[60,236,174],{"x":202,"y":173,"style":64},[39,238],{"x":194,"y":170,"width":76,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,240,241],{"x":197,"y":173,"style":117},"   Premises",[60,243,247],{"x":244,"y":245,"style":246},"246.5","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","before",[60,249,251],{"x":250,"y":245,"style":246},"585.9","after",[253,254],"line",{"x1":255,"y1":256,"x2":257,"y2":256,"stroke":258,"style":259},"402.7","122.0","498.2","var(--brand,#5b5cf0)","stroke-width:2px",[261,262],"polygon",{"points":263,"fill":264},"502.2,122.0 493.2,127.0 493.2,117.0","#5b5cf0",[39,266],{"x":267,"y":268,"width":269,"height":270,"rx":271,"fill":45,"stroke":53,"style":272},"420.7","89.0","63.6","22.0","11","stroke-width:1.2px",[60,274,278],{"x":275,"y":276,"style":277},"452.5","104.0","font-size:11.5px;font-weight:400;fill:var(--brand-strong,#4338ca);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","indent",[280,281,283],"h2",{"id":282},"prerequisites","Prerequisites",[285,286,291],"pre",{"className":287,"code":288,"language":289,"meta":290,"style":290},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pandas\n","bash","",[292,293,294],"code",{"__ignoreMap":290},[295,296,298,302,306,309],"span",{"class":253,"line":297},1,[295,299,301],{"class":300},"sMTad","pip",[295,303,305],{"class":304},"srMev"," install",[295,307,308],{"class":304}," openpyxl",[295,310,311],{"class":304}," pandas\n",[10,313,314,317],{},[292,315,316],{},"accounts.xlsx"," lists thirteen rows: groups such as People and Technology, a subgroup (Software, under Technology), and the accounts that carry amounts. Each row names its parent; the top row, Operating expenses, has none.",[280,319,321],{"id":320},"step-1-work-out-each-rows-depth-and-order","Step 1 — Work out each row's depth and order",[10,323,324],{},"The depth of a row is the number of steps to the top of the tree. Follow parents recursively, caching results so each row is computed once. The display order is a depth-first walk: each parent followed immediately by its children, in the order the table lists them:",[285,326,330],{"className":327,"code":328,"language":329,"meta":290,"style":290},"language-python shiki shiki-themes github-light github-dark-high-contrast","from functools import lru_cache\nimport pandas as pd\n\nacc = pd.read_excel(\"accounts.xlsx\", dtype={\"Code\": str, \"Parent\": str})\nparent = dict(zip(acc[\"Code\"], acc[\"Parent\"]))\nchildren = {}\nfor code, par in zip(acc[\"Code\"], acc[\"Parent\"]):\n    children.setdefault(par if pd.notna(par) else None, []).append(code)\n\n@lru_cache(maxsize=None)\ndef depth(code):\n    p = parent[code]\n    return 0 if pd.isna(p) else depth(p) + 1\n\ndef walk(code=None):\n    for child in children.get(code, []):\n        yield child\n        yield from walk(child)\n\norder = list(walk())\nacc = acc.set_index(\"Code\").loc[order].reset_index()\nacc[\"Depth\"] = acc[\"Code\"].map(depth)\nprint(acc[[\"Code\", \"Name\", \"Depth\"]].to_string(index=False))\n","python",[292,331,332,348,362,369,418,448,459,485,506,511,531,543,554,580,585,603,617,626,635,640,654,669,691],{"__ignoreMap":290},[295,333,334,338,342,345],{"class":253,"line":297},[295,335,337],{"class":336},"s-kum","from",[295,339,341],{"class":340},"skGVy"," functools ",[295,343,344],{"class":336},"import",[295,346,347],{"class":340}," lru_cache\n",[295,349,351,353,356,359],{"class":253,"line":350},2,[295,352,344],{"class":336},[295,354,355],{"class":340}," pandas ",[295,357,358],{"class":336},"as",[295,360,361],{"class":340}," pd\n",[295,363,365],{"class":253,"line":364},3,[295,366,368],{"emptyLinePlaceholder":367},true,"\n",[295,370,372,375,378,381,384,387,391,393,396,399,402,406,408,411,413,415],{"class":253,"line":371},4,[295,373,374],{"class":340},"acc ",[295,376,377],{"class":336},"=",[295,379,380],{"class":340}," pd.read_excel(",[295,382,383],{"class":304},"\"accounts.xlsx\"",[295,385,386],{"class":340},", ",[295,388,390],{"class":389},"sa561","dtype",[295,392,377],{"class":336},[295,394,395],{"class":340},"{",[295,397,398],{"class":304},"\"Code\"",[295,400,401],{"class":340},": ",[295,403,405],{"class":404},"sP0c6","str",[295,407,386],{"class":340},[295,409,410],{"class":304},"\"Parent\"",[295,412,401],{"class":340},[295,414,405],{"class":404},[295,416,417],{"class":340},"})\n",[295,419,421,424,426,429,432,435,438,440,443,445],{"class":253,"line":420},5,[295,422,423],{"class":340},"parent ",[295,425,377],{"class":336},[295,427,428],{"class":404}," dict",[295,430,431],{"class":340},"(",[295,433,434],{"class":404},"zip",[295,436,437],{"class":340},"(acc[",[295,439,398],{"class":304},[295,441,442],{"class":340},"], acc[",[295,444,410],{"class":304},[295,446,447],{"class":340},"]))\n",[295,449,451,454,456],{"class":253,"line":450},6,[295,452,453],{"class":340},"children ",[295,455,377],{"class":336},[295,457,458],{"class":340}," {}\n",[295,460,462,465,468,471,474,476,478,480,482],{"class":253,"line":461},7,[295,463,464],{"class":336},"for",[295,466,467],{"class":340}," code, par ",[295,469,470],{"class":336},"in",[295,472,473],{"class":404}," zip",[295,475,437],{"class":340},[295,477,398],{"class":304},[295,479,442],{"class":340},[295,481,410],{"class":304},[295,483,484],{"class":340},"]):\n",[295,486,488,491,494,497,500,503],{"class":253,"line":487},8,[295,489,490],{"class":340},"    children.setdefault(par ",[295,492,493],{"class":336},"if",[295,495,496],{"class":340}," pd.notna(par) ",[295,498,499],{"class":336},"else",[295,501,502],{"class":404}," None",[295,504,505],{"class":340},", []).append(code)\n",[295,507,509],{"class":253,"line":508},9,[295,510,368],{"emptyLinePlaceholder":367},[295,512,514,518,520,523,525,528],{"class":253,"line":513},10,[295,515,517],{"class":516},"s_Opv","@lru_cache",[295,519,431],{"class":340},[295,521,522],{"class":389},"maxsize",[295,524,377],{"class":336},[295,526,527],{"class":404},"None",[295,529,530],{"class":340},")\n",[295,532,534,537,540],{"class":253,"line":533},11,[295,535,536],{"class":336},"def",[295,538,539],{"class":516}," depth",[295,541,542],{"class":340},"(code):\n",[295,544,546,549,551],{"class":253,"line":545},12,[295,547,548],{"class":340},"    p ",[295,550,377],{"class":336},[295,552,553],{"class":340}," parent[code]\n",[295,555,557,560,563,566,569,571,574,577],{"class":253,"line":556},13,[295,558,559],{"class":336},"    return",[295,561,562],{"class":404}," 0",[295,564,565],{"class":336}," if",[295,567,568],{"class":340}," pd.isna(p) ",[295,570,499],{"class":336},[295,572,573],{"class":340}," depth(p) ",[295,575,576],{"class":336},"+",[295,578,579],{"class":404}," 1\n",[295,581,583],{"class":253,"line":582},14,[295,584,368],{"emptyLinePlaceholder":367},[295,586,588,590,593,596,598,600],{"class":253,"line":587},15,[295,589,536],{"class":336},[295,591,592],{"class":516}," walk",[295,594,595],{"class":340},"(code",[295,597,377],{"class":336},[295,599,527],{"class":404},[295,601,602],{"class":340},"):\n",[295,604,606,609,612,614],{"class":253,"line":605},16,[295,607,608],{"class":336},"    for",[295,610,611],{"class":340}," child ",[295,613,470],{"class":336},[295,615,616],{"class":340}," children.get(code, []):\n",[295,618,620,623],{"class":253,"line":619},17,[295,621,622],{"class":336},"        yield",[295,624,625],{"class":340}," child\n",[295,627,629,632],{"class":253,"line":628},18,[295,630,631],{"class":336},"        yield from",[295,633,634],{"class":340}," walk(child)\n",[295,636,638],{"class":253,"line":637},19,[295,639,368],{"emptyLinePlaceholder":367},[295,641,643,646,648,651],{"class":253,"line":642},20,[295,644,645],{"class":340},"order ",[295,647,377],{"class":336},[295,649,650],{"class":404}," list",[295,652,653],{"class":340},"(walk())\n",[295,655,657,659,661,664,666],{"class":253,"line":656},21,[295,658,374],{"class":340},[295,660,377],{"class":336},[295,662,663],{"class":340}," acc.set_index(",[295,665,398],{"class":304},[295,667,668],{"class":340},").loc[order].reset_index()\n",[295,670,672,675,678,681,683,686,688],{"class":253,"line":671},22,[295,673,674],{"class":340},"acc[",[295,676,677],{"class":304},"\"Depth\"",[295,679,680],{"class":340},"] ",[295,682,377],{"class":336},[295,684,685],{"class":340}," acc[",[295,687,398],{"class":304},[295,689,690],{"class":340},"].map(depth)\n",[295,692,694,697,700,702,704,707,709,711,714,717,719,722],{"class":253,"line":693},23,[295,695,696],{"class":404},"print",[295,698,699],{"class":340},"(acc[[",[295,701,398],{"class":304},[295,703,386],{"class":340},[295,705,706],{"class":304},"\"Name\"",[295,708,386],{"class":340},[295,710,677],{"class":304},[295,712,713],{"class":340},"]].to_string(",[295,715,716],{"class":389},"index",[295,718,377],{"class":336},[295,720,721],{"class":404},"False",[295,723,724],{"class":340},"))\n",[10,726,727,728,730,731,733],{},"Reading the codes as text matters: account numbers such as ",[292,729,158],{}," and group codes such as ",[292,732,118],{}," share a column, and a mix of types would break the parent lookup. Hardware, listed after Software in the table, still appears under Technology after Software's two accounts — the walk keeps each branch together.",[10,735,736],{},"Hand-maintained hierarchies go wrong in two ways: a row names a parent that does not exist, or a chain of parents loops back on itself. Either one breaks the depth calculation, so check the table before building anything from it:",[285,738,740],{"className":327,"code":739,"language":329,"meta":290,"style":290},"def tree_problems(parent):\n    problems = []\n    for code, p in parent.items():\n        if pd.notna(p) and p not in parent:\n            problems.append(f\"{code}: parent {p} does not exist\")\n    for code in parent:\n        seen, p = {code}, parent[code]\n        while pd.notna(p) and p in parent:\n            if p in seen:\n                problems.append(f\"{code}: cycle through {p}\")\n                break\n            seen.add(p)\n            p = parent[p]\n    return problems\n\nprint(tree_problems(parent) or \"tree is valid\")\nprint(tree_problems({**parent, \"6400\": \"MARKETING\", \"OPEX\": \"SAAS\"})[:3])\n",[292,741,742,752,762,774,797,830,841,851,866,878,906,911,916,926,933,937,952],{"__ignoreMap":290},[295,743,744,746,749],{"class":253,"line":297},[295,745,536],{"class":336},[295,747,748],{"class":516}," tree_problems",[295,750,751],{"class":340},"(parent):\n",[295,753,754,757,759],{"class":253,"line":350},[295,755,756],{"class":340},"    problems ",[295,758,377],{"class":336},[295,760,761],{"class":340}," []\n",[295,763,764,766,769,771],{"class":253,"line":364},[295,765,608],{"class":336},[295,767,768],{"class":340}," code, p ",[295,770,470],{"class":336},[295,772,773],{"class":340}," parent.items():\n",[295,775,776,779,782,785,788,791,794],{"class":253,"line":371},[295,777,778],{"class":336},"        if",[295,780,781],{"class":340}," pd.notna(p) ",[295,783,784],{"class":336},"and",[295,786,787],{"class":340}," p ",[295,789,790],{"class":336},"not",[295,792,793],{"class":336}," in",[295,795,796],{"class":340}," parent:\n",[295,798,799,802,805,808,811,813,816,819,821,823,825,828],{"class":253,"line":420},[295,800,801],{"class":340},"            problems.append(",[295,803,804],{"class":336},"f",[295,806,807],{"class":304},"\"",[295,809,395],{"class":810},"sSjpA",[295,812,292],{"class":340},[295,814,815],{"class":810},"}",[295,817,818],{"class":304},": parent ",[295,820,395],{"class":810},[295,822,10],{"class":340},[295,824,815],{"class":810},[295,826,827],{"class":304}," does not exist\"",[295,829,530],{"class":340},[295,831,832,834,837,839],{"class":253,"line":450},[295,833,608],{"class":336},[295,835,836],{"class":340}," code ",[295,838,470],{"class":336},[295,840,796],{"class":340},[295,842,843,846,848],{"class":253,"line":461},[295,844,845],{"class":340},"        seen, p ",[295,847,377],{"class":336},[295,849,850],{"class":340}," {code}, parent[code]\n",[295,852,853,856,858,860,862,864],{"class":253,"line":487},[295,854,855],{"class":336},"        while",[295,857,781],{"class":340},[295,859,784],{"class":336},[295,861,787],{"class":340},[295,863,470],{"class":336},[295,865,796],{"class":340},[295,867,868,871,873,875],{"class":253,"line":508},[295,869,870],{"class":336},"            if",[295,872,787],{"class":340},[295,874,470],{"class":336},[295,876,877],{"class":340}," seen:\n",[295,879,880,883,885,887,889,891,893,896,898,900,902,904],{"class":253,"line":513},[295,881,882],{"class":340},"                problems.append(",[295,884,804],{"class":336},[295,886,807],{"class":304},[295,888,395],{"class":810},[295,890,292],{"class":340},[295,892,815],{"class":810},[295,894,895],{"class":304},": cycle through ",[295,897,395],{"class":810},[295,899,10],{"class":340},[295,901,815],{"class":810},[295,903,807],{"class":304},[295,905,530],{"class":340},[295,907,908],{"class":253,"line":533},[295,909,910],{"class":336},"                break\n",[295,912,913],{"class":253,"line":545},[295,914,915],{"class":340},"            seen.add(p)\n",[295,917,918,921,923],{"class":253,"line":556},[295,919,920],{"class":340},"            p ",[295,922,377],{"class":336},[295,924,925],{"class":340}," parent[p]\n",[295,927,928,930],{"class":253,"line":582},[295,929,559],{"class":336},[295,931,932],{"class":340}," problems\n",[295,934,935],{"class":253,"line":587},[295,936,368],{"emptyLinePlaceholder":367},[295,938,939,941,944,947,950],{"class":253,"line":605},[295,940,696],{"class":404},[295,942,943],{"class":340},"(tree_problems(parent) ",[295,945,946],{"class":336},"or",[295,948,949],{"class":304}," \"tree is valid\"",[295,951,530],{"class":340},[295,953,954,956,959,962,965,968,970,973,975,978,980,983,986,988],{"class":253,"line":619},[295,955,696],{"class":404},[295,957,958],{"class":340},"(tree_problems({",[295,960,961],{"class":336},"**",[295,963,964],{"class":340},"parent, ",[295,966,967],{"class":304},"\"6400\"",[295,969,401],{"class":340},[295,971,972],{"class":304},"\"MARKETING\"",[295,974,386],{"class":340},[295,976,977],{"class":304},"\"OPEX\"",[295,979,401],{"class":340},[295,981,982],{"class":304},"\"SAAS\"",[295,984,985],{"class":340},"})[:",[295,987,132],{"class":404},[295,989,990],{"class":340},"])\n",[10,992,993],{},"The real table is valid. The second call adds a row whose parent is missing and makes Operating expenses a child of Software, which creates a loop; both are reported instead of sending the depth function into endless recursion. Run the check whenever the hierarchy comes from a sheet people edit.",[280,995,997],{"id":996},"step-2-roll-the-amounts-up-the-tree","Step 2 — Roll the amounts up the tree",[10,999,1000],{},"Parent rows show the total of everything beneath them. Add each account's amount to every ancestor on its path:",[285,1002,1004],{"className":327,"code":1003,"language":329,"meta":290,"style":290},"totals = dict(zip(acc[\"Code\"], acc[\"Amount\"].fillna(0)))\nfor code, amount in zip(acc[\"Code\"], acc[\"Amount\"]):\n    if pd.isna(amount):\n        continue\n    p = parent[code]\n    while pd.notna(p):\n        totals[p] += amount\n        p = parent[p]\nacc[\"Total\"] = acc[\"Code\"].map(totals)\nacc[\"IsParent\"] = acc[\"Code\"].isin(children)\nprint(acc.loc[acc[\"IsParent\"], [\"Name\", \"Total\"]].to_string(index=False))\n",[292,1005,1006,1036,1057,1065,1070,1078,1086,1097,1106,1124,1142],{"__ignoreMap":290},[295,1007,1008,1011,1013,1015,1017,1019,1021,1023,1025,1028,1031,1033],{"class":253,"line":297},[295,1009,1010],{"class":340},"totals ",[295,1012,377],{"class":336},[295,1014,428],{"class":404},[295,1016,431],{"class":340},[295,1018,434],{"class":404},[295,1020,437],{"class":340},[295,1022,398],{"class":304},[295,1024,442],{"class":340},[295,1026,1027],{"class":304},"\"Amount\"",[295,1029,1030],{"class":340},"].fillna(",[295,1032,42],{"class":404},[295,1034,1035],{"class":340},")))\n",[295,1037,1038,1040,1043,1045,1047,1049,1051,1053,1055],{"class":253,"line":350},[295,1039,464],{"class":336},[295,1041,1042],{"class":340}," code, amount ",[295,1044,470],{"class":336},[295,1046,473],{"class":404},[295,1048,437],{"class":340},[295,1050,398],{"class":304},[295,1052,442],{"class":340},[295,1054,1027],{"class":304},[295,1056,484],{"class":340},[295,1058,1059,1062],{"class":253,"line":364},[295,1060,1061],{"class":336},"    if",[295,1063,1064],{"class":340}," pd.isna(amount):\n",[295,1066,1067],{"class":253,"line":371},[295,1068,1069],{"class":336},"        continue\n",[295,1071,1072,1074,1076],{"class":253,"line":420},[295,1073,548],{"class":340},[295,1075,377],{"class":336},[295,1077,553],{"class":340},[295,1079,1080,1083],{"class":253,"line":450},[295,1081,1082],{"class":336},"    while",[295,1084,1085],{"class":340}," pd.notna(p):\n",[295,1087,1088,1091,1094],{"class":253,"line":461},[295,1089,1090],{"class":340},"        totals[p] ",[295,1092,1093],{"class":336},"+=",[295,1095,1096],{"class":340}," amount\n",[295,1098,1099,1102,1104],{"class":253,"line":487},[295,1100,1101],{"class":340},"        p ",[295,1103,377],{"class":336},[295,1105,925],{"class":340},[295,1107,1108,1110,1113,1115,1117,1119,1121],{"class":253,"line":508},[295,1109,674],{"class":340},[295,1111,1112],{"class":304},"\"Total\"",[295,1114,680],{"class":340},[295,1116,377],{"class":336},[295,1118,685],{"class":340},[295,1120,398],{"class":304},[295,1122,1123],{"class":340},"].map(totals)\n",[295,1125,1126,1128,1131,1133,1135,1137,1139],{"class":253,"line":513},[295,1127,674],{"class":340},[295,1129,1130],{"class":304},"\"IsParent\"",[295,1132,680],{"class":340},[295,1134,377],{"class":336},[295,1136,685],{"class":340},[295,1138,398],{"class":304},[295,1140,1141],{"class":340},"].isin(children)\n",[295,1143,1144,1146,1149,1151,1154,1156,1158,1160,1162,1164,1166,1168],{"class":253,"line":533},[295,1145,696],{"class":404},[295,1147,1148],{"class":340},"(acc.loc[acc[",[295,1150,1130],{"class":304},[295,1152,1153],{"class":340},"], [",[295,1155,706],{"class":304},[295,1157,386],{"class":340},[295,1159,1112],{"class":304},[295,1161,713],{"class":340},[295,1163,716],{"class":389},[295,1165,377],{"class":336},[295,1167,721],{"class":404},[295,1169,724],{"class":340},[10,1171,1172,1173,1176,1177,1181],{},"People totals 133,300, Premises 41,150, Technology 16,120 — including its Software subgroup's 12,920 — and Operating expenses 190,570. Computing the totals in Python keeps them as values; to make them live, write ",[292,1174,1175],{},"SUM"," formulas over each parent's child rows instead, the way ",[14,1178,1180],{"href":1179},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-budget-vs-actual-variance-report\u002F","Build a Budget vs Actual Variance Report"," writes its subtotals.",[280,1183,1185],{"id":1184},"step-3-indent-bold-and-write","Step 3 — Indent, bold and write",[10,1187,1188,1189,1192],{},"Now write the rows, setting ",[292,1190,1191],{},"Alignment(indent=depth)"," on the name cell. Each indent level shifts the text right by about three characters' width, and the value itself stays clean — \"Rent\", not \"      Rent\". Parents get bold text and a top border to read as headings:",[285,1194,1196],{"className":327,"code":1195,"language":329,"meta":290,"style":290},"from openpyxl import Workbook\nfrom openpyxl.styles import Alignment, Border, Font, Side\n\nwb = Workbook(); ws = wb.active; ws.title = \"Expenses\"\nws.append([\"Account\", \"Code\", \"Amount\"])\nfor c in ws[1]:\n    c.font = Font(bold=True)\nmoney = \"#,##0\"\nbold = Font(bold=True)\ntop = Border(top=Side(style=\"thin\"))\nindents = {}\nfor r in acc.itertuples(index=False):\n    ws.append([r.Name, r.Code, r.Total])\n    row = ws.max_row\n    name = ws.cell(row=row, column=1)\n    name.alignment = indents.setdefault(r.Depth, Alignment(indent=r.Depth))\n    ws.cell(row=row, column=3).number_format = money\n    if r.IsParent:\n        for c in ws[row]:\n            c.font = bold\n            c.border = top\nws.column_dimensions[\"A\"].width = 28\nws.column_dimensions[\"C\"].width = 12\nprint([(ws.cell(row=i, column=1).value, ws.cell(row=i, column=1).alignment.indent) for i in range(2, 6)])\n",[292,1197,1198,1210,1222,1226,1246,1264,1281,1301,1311,1328,1356,1365,1385,1390,1400,1427,1444,1469,1476,1488,1498,1508,1524,1538],{"__ignoreMap":290},[295,1199,1200,1202,1205,1207],{"class":253,"line":297},[295,1201,337],{"class":336},[295,1203,1204],{"class":340}," openpyxl ",[295,1206,344],{"class":336},[295,1208,1209],{"class":340}," Workbook\n",[295,1211,1212,1214,1217,1219],{"class":253,"line":350},[295,1213,337],{"class":336},[295,1215,1216],{"class":340}," openpyxl.styles ",[295,1218,344],{"class":336},[295,1220,1221],{"class":340}," Alignment, Border, Font, Side\n",[295,1223,1224],{"class":253,"line":364},[295,1225,368],{"emptyLinePlaceholder":367},[295,1227,1228,1231,1233,1236,1238,1241,1243],{"class":253,"line":371},[295,1229,1230],{"class":340},"wb ",[295,1232,377],{"class":336},[295,1234,1235],{"class":340}," Workbook(); ws ",[295,1237,377],{"class":336},[295,1239,1240],{"class":340}," wb.active; ws.title ",[295,1242,377],{"class":336},[295,1244,1245],{"class":304}," \"Expenses\"\n",[295,1247,1248,1251,1254,1256,1258,1260,1262],{"class":253,"line":420},[295,1249,1250],{"class":340},"ws.append([",[295,1252,1253],{"class":304},"\"Account\"",[295,1255,386],{"class":340},[295,1257,398],{"class":304},[295,1259,386],{"class":340},[295,1261,1027],{"class":304},[295,1263,990],{"class":340},[295,1265,1266,1268,1271,1273,1276,1278],{"class":253,"line":450},[295,1267,464],{"class":336},[295,1269,1270],{"class":340}," c ",[295,1272,470],{"class":336},[295,1274,1275],{"class":340}," ws[",[295,1277,89],{"class":404},[295,1279,1280],{"class":340},"]:\n",[295,1282,1283,1286,1288,1291,1294,1296,1299],{"class":253,"line":461},[295,1284,1285],{"class":340},"    c.font ",[295,1287,377],{"class":336},[295,1289,1290],{"class":340}," Font(",[295,1292,1293],{"class":389},"bold",[295,1295,377],{"class":336},[295,1297,1298],{"class":404},"True",[295,1300,530],{"class":340},[295,1302,1303,1306,1308],{"class":253,"line":487},[295,1304,1305],{"class":340},"money ",[295,1307,377],{"class":336},[295,1309,1310],{"class":304}," \"#,##0\"\n",[295,1312,1313,1316,1318,1320,1322,1324,1326],{"class":253,"line":508},[295,1314,1315],{"class":340},"bold ",[295,1317,377],{"class":336},[295,1319,1290],{"class":340},[295,1321,1293],{"class":389},[295,1323,377],{"class":336},[295,1325,1298],{"class":404},[295,1327,530],{"class":340},[295,1329,1330,1333,1335,1338,1341,1343,1346,1349,1351,1354],{"class":253,"line":513},[295,1331,1332],{"class":340},"top ",[295,1334,377],{"class":336},[295,1336,1337],{"class":340}," Border(",[295,1339,1340],{"class":389},"top",[295,1342,377],{"class":336},[295,1344,1345],{"class":340},"Side(",[295,1347,1348],{"class":389},"style",[295,1350,377],{"class":336},[295,1352,1353],{"class":304},"\"thin\"",[295,1355,724],{"class":340},[295,1357,1358,1361,1363],{"class":253,"line":533},[295,1359,1360],{"class":340},"indents ",[295,1362,377],{"class":336},[295,1364,458],{"class":340},[295,1366,1367,1369,1372,1374,1377,1379,1381,1383],{"class":253,"line":545},[295,1368,464],{"class":336},[295,1370,1371],{"class":340}," r ",[295,1373,470],{"class":336},[295,1375,1376],{"class":340}," acc.itertuples(",[295,1378,716],{"class":389},[295,1380,377],{"class":336},[295,1382,721],{"class":404},[295,1384,602],{"class":340},[295,1386,1387],{"class":253,"line":556},[295,1388,1389],{"class":340},"    ws.append([r.Name, r.Code, r.Total])\n",[295,1391,1392,1395,1397],{"class":253,"line":582},[295,1393,1394],{"class":340},"    row ",[295,1396,377],{"class":336},[295,1398,1399],{"class":340}," ws.max_row\n",[295,1401,1402,1405,1407,1410,1413,1415,1418,1421,1423,1425],{"class":253,"line":587},[295,1403,1404],{"class":340},"    name ",[295,1406,377],{"class":336},[295,1408,1409],{"class":340}," ws.cell(",[295,1411,1412],{"class":389},"row",[295,1414,377],{"class":336},[295,1416,1417],{"class":340},"row, ",[295,1419,1420],{"class":389},"column",[295,1422,377],{"class":336},[295,1424,89],{"class":404},[295,1426,530],{"class":340},[295,1428,1429,1432,1434,1437,1439,1441],{"class":253,"line":605},[295,1430,1431],{"class":340},"    name.alignment ",[295,1433,377],{"class":336},[295,1435,1436],{"class":340}," indents.setdefault(r.Depth, Alignment(",[295,1438,278],{"class":389},[295,1440,377],{"class":336},[295,1442,1443],{"class":340},"r.Depth))\n",[295,1445,1446,1449,1451,1453,1455,1457,1459,1461,1464,1466],{"class":253,"line":619},[295,1447,1448],{"class":340},"    ws.cell(",[295,1450,1412],{"class":389},[295,1452,377],{"class":336},[295,1454,1417],{"class":340},[295,1456,1420],{"class":389},[295,1458,377],{"class":336},[295,1460,132],{"class":404},[295,1462,1463],{"class":340},").number_format ",[295,1465,377],{"class":336},[295,1467,1468],{"class":340}," money\n",[295,1470,1471,1473],{"class":253,"line":628},[295,1472,1061],{"class":336},[295,1474,1475],{"class":340}," r.IsParent:\n",[295,1477,1478,1481,1483,1485],{"class":253,"line":637},[295,1479,1480],{"class":336},"        for",[295,1482,1270],{"class":340},[295,1484,470],{"class":336},[295,1486,1487],{"class":340}," ws[row]:\n",[295,1489,1490,1493,1495],{"class":253,"line":642},[295,1491,1492],{"class":340},"            c.font ",[295,1494,377],{"class":336},[295,1496,1497],{"class":340}," bold\n",[295,1499,1500,1503,1505],{"class":253,"line":656},[295,1501,1502],{"class":340},"            c.border ",[295,1504,377],{"class":336},[295,1506,1507],{"class":340}," top\n",[295,1509,1510,1513,1516,1519,1521],{"class":253,"line":671},[295,1511,1512],{"class":340},"ws.column_dimensions[",[295,1514,1515],{"class":304},"\"A\"",[295,1517,1518],{"class":340},"].width ",[295,1520,377],{"class":336},[295,1522,1523],{"class":404}," 28\n",[295,1525,1526,1528,1531,1533,1535],{"class":253,"line":693},[295,1527,1512],{"class":340},[295,1529,1530],{"class":304},"\"C\"",[295,1532,1518],{"class":340},[295,1534,377],{"class":336},[295,1536,1537],{"class":404}," 12\n",[295,1539,1541,1543,1546,1548,1550,1553,1555,1557,1559,1562,1564,1566,1568,1570,1572,1574,1577,1579,1582,1584,1587,1589,1591,1593,1596],{"class":253,"line":1540},24,[295,1542,696],{"class":404},[295,1544,1545],{"class":340},"([(ws.cell(",[295,1547,1412],{"class":389},[295,1549,377],{"class":336},[295,1551,1552],{"class":340},"i, ",[295,1554,1420],{"class":389},[295,1556,377],{"class":336},[295,1558,89],{"class":404},[295,1560,1561],{"class":340},").value, ws.cell(",[295,1563,1412],{"class":389},[295,1565,377],{"class":336},[295,1567,1552],{"class":340},[295,1569,1420],{"class":389},[295,1571,377],{"class":336},[295,1573,89],{"class":404},[295,1575,1576],{"class":340},").alignment.indent) ",[295,1578,464],{"class":336},[295,1580,1581],{"class":340}," i ",[295,1583,470],{"class":336},[295,1585,1586],{"class":404}," range",[295,1588,431],{"class":340},[295,1590,112],{"class":404},[295,1592,386],{"class":340},[295,1594,1595],{"class":404},"6",[295,1597,1598],{"class":340},")])\n",[10,1600,1601,1602,1605],{},"One ",[292,1603,1604],{},"Alignment"," object per depth is reused for every row at that depth. The maximum indent Excel accepts is 250, far more than any readable hierarchy; beyond four or five levels, consider splitting the report instead.",[20,1607,29,1613,29,1616,29,1619,29,1624,29,1630,29,1634,29,1638,29,1642,29,1645,29,1648,29,1652,29,1655,29,1658,29,1662,29,1664,29,1667,29,1669,29,1671,29,1673,29,1676,29,1679,29,1682,29,1686,29,1690,29,1692,29,1694,29,1696,29,1699,29,1702,29,1705,29,1707,29,1709,29,1711,29,1713,29,1715,29,1718,29,1721,29,1724,29,1726,29,1728,29,1730,29,1732,29,1734,29,1737,29,1740,29,1743,29,1745,29,1748,29,1750,29,1753,29,1755,29,1758,29,1761,29,1764,29,1766,29,1769,29,1771,29,1774,29,1776,29,1779,29,1782,29,1786,29,1788,29,1791,29,1793,29,1796,29,1798],{"viewBox":1608,"role":23,"ariaLabelledBy":1609,"xmlns":27,"style":1612},"200 0 360 284",[1610,1611],"ind-sheet-t","ind-sheet-d","width:100%;max-width:360px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[31,1614,1615],{"id":1610},"The indented expense report",[35,1617,1618],{"id":1611},"The Expenses sheet. Operating expenses 190,570 at the top in bold. People 133,300 indented one level and bold, with Salaries 119,000, Pension 11,900 and Training 2,400 indented two levels. Premises 41,150 with Rent and Utilities under it. Technology 16,120 with Software 12,920 indented two levels and Licences and Hosting three levels, then Hardware 3,200 at two levels.",[39,1620],{"x":1621,"y":42,"width":1622,"height":1623,"fill":45},"200","360","284",[60,1625,1629],{"x":1626,"y":1627,"style":1628},"380.0","31.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Expenses — indented by depth",[39,1631],{"x":1632,"y":1633,"width":50,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"216.2","48.0",[39,1635],{"x":1636,"y":1633,"width":1637,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"248.2","148.2",[60,1639,65],{"x":1640,"y":1641,"style":64},"322.2","65.0",[39,1643],{"x":1644,"y":1633,"width":58,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"396.3",[60,1646,72],{"x":1647,"y":1641,"style":64},"432.6",[39,1649],{"x":1650,"y":1633,"width":1651,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"468.8","75.0",[60,1653,80],{"x":1654,"y":1641,"style":64},"506.3",[39,1656],{"x":1632,"y":1657,"width":50,"height":84,"rx":42,"fill":52,"stroke":53,"style":54},"72.0",[60,1659,89],{"x":1660,"y":1661,"style":64},"232.2","91.0",[39,1663],{"x":1636,"y":1657,"width":1637,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,1665,1666],{"x":1640,"y":1661,"style":94},"Account",[39,1668],{"x":1644,"y":1657,"width":58,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,1670,95],{"x":1647,"y":1661,"style":94},[39,1672],{"x":1650,"y":1657,"width":1651,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,1674,1675],{"x":1654,"y":1661,"style":94},"Amount",[39,1677],{"x":1632,"y":1678,"width":50,"height":84,"rx":42,"fill":52,"stroke":53,"style":54},"100.0",[60,1680,112],{"x":1660,"y":1681,"style":64},"119.0",[39,1683],{"x":1636,"y":1678,"width":1637,"height":84,"rx":42,"fill":1684,"stroke":258,"style":1685},"#f0f4ff","stroke-width:1.5px",[60,1687,1689],{"x":1640,"y":1681,"style":1688},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Operating expenses",[39,1691],{"x":1644,"y":1678,"width":58,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,1693,118],{"x":1647,"y":1681,"style":117},[39,1695],{"x":1650,"y":1678,"width":1651,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,1697,1698],{"x":1654,"y":1681,"style":117},"190,570",[39,1700],{"x":1632,"y":1701,"width":50,"height":84,"rx":42,"fill":52,"stroke":53,"style":54},"128.0",[60,1703,132],{"x":1660,"y":1704,"style":64},"147.0",[39,1706],{"x":1636,"y":1701,"width":1637,"height":84,"rx":42,"fill":1684,"stroke":258,"style":1685},[60,1708,223],{"x":1640,"y":1704,"style":1688},[39,1710],{"x":1644,"y":1701,"width":58,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,1712,137],{"x":1647,"y":1704,"style":117},[39,1714],{"x":1650,"y":1701,"width":1651,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,1716,1717],{"x":1654,"y":1704,"style":117},"133,300",[39,1719],{"x":1632,"y":1720,"width":50,"height":84,"rx":42,"fill":52,"stroke":53,"style":54},"156.0",[60,1722,153],{"x":1660,"y":1723,"style":64},"175.0",[39,1725],{"x":1636,"y":1720,"width":1637,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,1727,232],{"x":1640,"y":1723,"style":117},[39,1729],{"x":1644,"y":1720,"width":58,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,1731,158],{"x":1647,"y":1723,"style":117},[39,1733],{"x":1650,"y":1720,"width":1651,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,1735,1736],{"x":1654,"y":1723,"style":117},"119,000",[39,1738],{"x":1632,"y":1739,"width":50,"height":84,"rx":42,"fill":52,"stroke":53,"style":54},"184.0",[60,1741,174],{"x":1660,"y":1742,"style":64},"203.0",[39,1744],{"x":1636,"y":1739,"width":1637,"height":84,"rx":42,"fill":1684,"stroke":258,"style":1685},[60,1746,1747],{"x":1640,"y":1742,"style":1688},"   Technology",[39,1749],{"x":1644,"y":1739,"width":58,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,1751,1752],{"x":1647,"y":1742,"style":117},"TECH",[39,1754],{"x":1650,"y":1739,"width":1651,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,1756,1757],{"x":1654,"y":1742,"style":117},"16,120",[39,1759],{"x":1632,"y":1760,"width":50,"height":84,"rx":42,"fill":52,"stroke":53,"style":54},"212.0",[60,1762,1595],{"x":1660,"y":1763,"style":64},"231.0",[39,1765],{"x":1636,"y":1760,"width":1637,"height":84,"rx":42,"fill":1684,"stroke":258,"style":1685},[60,1767,1768],{"x":1640,"y":1763,"style":1688},"      Software",[39,1770],{"x":1644,"y":1760,"width":58,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,1772,1773],{"x":1647,"y":1763,"style":117},"SAAS",[39,1775],{"x":1650,"y":1760,"width":1651,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,1777,1778],{"x":1654,"y":1763,"style":117},"12,920",[39,1780],{"x":1632,"y":1781,"width":50,"height":84,"rx":42,"fill":52,"stroke":53,"style":54},"240.0",[60,1783,1785],{"x":1660,"y":1784,"style":64},"259.0","7",[39,1787],{"x":1636,"y":1781,"width":1637,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,1789,1790],{"x":1640,"y":1784,"style":117},"         Licences",[39,1792],{"x":1644,"y":1781,"width":58,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,1794,1795],{"x":1647,"y":1784,"style":117},"6300",[39,1797],{"x":1650,"y":1781,"width":1651,"height":84,"rx":42,"fill":45,"stroke":53,"style":54},[60,1799,1800],{"x":1654,"y":1784,"style":117},"8,820",[280,1802,1804],{"id":1803},"step-4-group-rows-so-branches-can-collapse","Step 4 — Group rows so branches can collapse",[10,1806,1807,1808,1811,1812,1816],{},"Excel's outline feature adds buttons in the left margin that fold a group of rows away. Give each row an ",[292,1809,1810],{},"outline_level"," equal to its depth, and tell Excel that summary rows sit ",[1813,1814,1815],"em",{},"above"," their detail — the default assumes totals below:",[285,1818,1820],{"className":327,"code":1819,"language":329,"meta":290,"style":290},"for i, d in enumerate(acc[\"Depth\"], start=2):\n    if d > 0:\n        ws.row_dimensions[i].outline_level = min(d, 7)\nws.sheet_properties.outlinePr.summaryBelow = False\nws.freeze_panes = \"B2\"\nwb.save(\"expenses_outline.xlsx\")\n\nfrom openpyxl import load_workbook\nbook = load_workbook(\"expenses_outline.xlsx\")\ncheck = book.active\nprint({check.cell(row=i, column=1).value: check.row_dimensions[i].outline_level for i in range(2, 9)})\n",[292,1821,1822,1850,1865,1882,1892,1902,1912,1916,1927,1941,1951],{"__ignoreMap":290},[295,1823,1824,1826,1829,1831,1834,1836,1838,1841,1844,1846,1848],{"class":253,"line":297},[295,1825,464],{"class":336},[295,1827,1828],{"class":340}," i, d ",[295,1830,470],{"class":336},[295,1832,1833],{"class":404}," enumerate",[295,1835,437],{"class":340},[295,1837,677],{"class":304},[295,1839,1840],{"class":340},"], ",[295,1842,1843],{"class":389},"start",[295,1845,377],{"class":336},[295,1847,112],{"class":404},[295,1849,602],{"class":340},[295,1851,1852,1854,1857,1860,1862],{"class":253,"line":350},[295,1853,1061],{"class":336},[295,1855,1856],{"class":340}," d ",[295,1858,1859],{"class":336},">",[295,1861,562],{"class":404},[295,1863,1864],{"class":340},":\n",[295,1866,1867,1870,1872,1875,1878,1880],{"class":253,"line":364},[295,1868,1869],{"class":340},"        ws.row_dimensions[i].outline_level ",[295,1871,377],{"class":336},[295,1873,1874],{"class":404}," min",[295,1876,1877],{"class":340},"(d, ",[295,1879,1785],{"class":404},[295,1881,530],{"class":340},[295,1883,1884,1887,1889],{"class":253,"line":371},[295,1885,1886],{"class":340},"ws.sheet_properties.outlinePr.summaryBelow ",[295,1888,377],{"class":336},[295,1890,1891],{"class":404}," False\n",[295,1893,1894,1897,1899],{"class":253,"line":420},[295,1895,1896],{"class":340},"ws.freeze_panes ",[295,1898,377],{"class":336},[295,1900,1901],{"class":304}," \"B2\"\n",[295,1903,1904,1907,1910],{"class":253,"line":450},[295,1905,1906],{"class":340},"wb.save(",[295,1908,1909],{"class":304},"\"expenses_outline.xlsx\"",[295,1911,530],{"class":340},[295,1913,1914],{"class":253,"line":461},[295,1915,368],{"emptyLinePlaceholder":367},[295,1917,1918,1920,1922,1924],{"class":253,"line":487},[295,1919,337],{"class":336},[295,1921,1204],{"class":340},[295,1923,344],{"class":336},[295,1925,1926],{"class":340}," load_workbook\n",[295,1928,1929,1932,1934,1937,1939],{"class":253,"line":508},[295,1930,1931],{"class":340},"book ",[295,1933,377],{"class":336},[295,1935,1936],{"class":340}," load_workbook(",[295,1938,1909],{"class":304},[295,1940,530],{"class":340},[295,1942,1943,1946,1948],{"class":253,"line":513},[295,1944,1945],{"class":340},"check ",[295,1947,377],{"class":336},[295,1949,1950],{"class":340}," book.active\n",[295,1952,1953,1955,1958,1960,1962,1964,1966,1968,1970,1973,1975,1977,1979,1981,1983,1985,1987,1990],{"class":253,"line":533},[295,1954,696],{"class":404},[295,1956,1957],{"class":340},"({check.cell(",[295,1959,1412],{"class":389},[295,1961,377],{"class":336},[295,1963,1552],{"class":340},[295,1965,1420],{"class":389},[295,1967,377],{"class":336},[295,1969,89],{"class":404},[295,1971,1972],{"class":340},").value: check.row_dimensions[i].outline_level ",[295,1974,464],{"class":336},[295,1976,1581],{"class":340},[295,1978,470],{"class":336},[295,1980,1586],{"class":404},[295,1982,431],{"class":340},[295,1984,112],{"class":404},[295,1986,386],{"class":340},[295,1988,1989],{"class":404},"9",[295,1991,1992],{"class":340},")})\n",[10,1994,1995,1996,1999,2000,2003,2004,2008],{},"Excel supports up to seven outline levels, hence the ",[292,1997,1998],{},"min",". With ",[292,2001,2002],{},"summaryBelow = False",", the collapse button for People sits on the People row itself, and clicking the level buttons at the top of the margin shows the report at one, two or three levels of detail. ",[14,2005,2007],{"href":2006},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fgroup-and-outline-rows-in-excel-with-openpyxl\u002F","Group and Outline Rows in Excel with openpyxl"," covers outlines in more depth.",[20,2010,29,2015,29,2018,29,2021,29,2024,29,2030,29,2041,29,2046,29,2051,29,2055,29,2058,29,2061,29,2068,29,2073,29,2077,29,2081,29,2084,29,2089,29,2095,29,2100],{"viewBox":2011,"role":23,"ariaLabelledBy":2012,"xmlns":27,"style":28},"84 0 592 383",[2013,2014],"ind-levels-t","ind-levels-d",[31,2016,2017],{"id":2013},"The outline levels readers can switch between",[35,2019,2020],{"id":2014},"Three views of the same sheet, chosen with the outline level buttons. Level 1 shows only Operating expenses. Level 2 adds the groups People, Premises and Technology with their totals. Level 3 and below show the individual accounts and the Software subgroup.",[39,2022],{"x":41,"y":42,"width":43,"height":2023,"fill":45},"383",[39,2025],{"x":1678,"y":2026,"width":2027,"height":2028,"rx":2029,"fill":52,"stroke":53,"style":259},"16.0","560.0","93.0","12",[2031,2032,2034],"g",{"transform":2033},"translate(368.0,29.0) scale(1.000)",[2035,2036],"path",{"d":2037,"fill":2038,"stroke":2039,"style":2040},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[60,2042,2045],{"x":1626,"y":2043,"style":2044},"75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Level 3+",[60,2047,2050],{"x":1626,"y":2048,"style":2049},"95.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","every account, e.g. Salaries, Rent",[253,2052],{"x1":1626,"y1":2053,"x2":1626,"y2":2054,"stroke":258,"style":259},"114.0","136.0",[261,2056],{"points":2057,"fill":264},"380.0,140.0 375.0,131.0 385.0,131.0",[39,2059],{"x":1678,"y":2060,"width":2027,"height":2028,"rx":2029,"fill":1684,"stroke":258,"style":259},"145.0",[2031,2062,2064],{"transform":2063},"translate(368.0,158.0) scale(1.000)",[2035,2065],{"d":2066,"fill":2038,"stroke":2067,"style":2040},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5","var(--brand-strong,#4338ca)",[60,2069,2072],{"x":1626,"y":2070,"style":2071},"204.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Level 2",[60,2074,2076],{"x":1626,"y":2075,"style":2049},"224.0","People, Premises, Technology",[253,2078],{"x1":1626,"y1":2079,"x2":1626,"y2":2080,"stroke":258,"style":259},"243.0","265.0",[261,2082],{"points":2083,"fill":264},"380.0,269.0 375.0,260.0 385.0,260.0",[39,2085],{"x":1678,"y":2086,"width":2027,"height":2028,"rx":2029,"fill":2087,"stroke":2088,"style":259},"274.0","#d9f4f1","var(--teal,#0f9488)",[2031,2090,2092],{"transform":2091},"translate(368.0,287.0) scale(1.000)",[2035,2093],{"d":2066,"fill":2038,"stroke":2094,"style":2040},"var(--teal-ink,#0b6157)",[60,2096,2099],{"x":1626,"y":2097,"style":2098},"333.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Level 1",[60,2101,2103],{"x":1626,"y":2102,"style":2049},"353.0","Operating expenses only",[280,2105,2107],{"id":2106},"step-5-start-with-branches-collapsed","Step 5 — Start with branches collapsed",[10,2109,2110],{},"For a summary-first report, open it with the detail folded away. Hide the rows below the level you want to show; Excel treats hidden rows inside an outline as collapsed, and the buttons expand them:",[285,2112,2114],{"className":327,"code":2113,"language":329,"meta":290,"style":290},"for i, d in enumerate(acc[\"Depth\"], start=2):\n    if d >= 2:\n        check.row_dimensions[i].hidden = True\nbook.save(\"expenses_collapsed.xlsx\")\nprint(\"hidden rows:\", [i for i in range(2, check.max_row + 1) if check.row_dimensions[i].hidden])\n",[292,2115,2116,2140,2154,2164,2174],{"__ignoreMap":290},[295,2117,2118,2120,2122,2124,2126,2128,2130,2132,2134,2136,2138],{"class":253,"line":297},[295,2119,464],{"class":336},[295,2121,1828],{"class":340},[295,2123,470],{"class":336},[295,2125,1833],{"class":404},[295,2127,437],{"class":340},[295,2129,677],{"class":304},[295,2131,1840],{"class":340},[295,2133,1843],{"class":389},[295,2135,377],{"class":336},[295,2137,112],{"class":404},[295,2139,602],{"class":340},[295,2141,2142,2144,2146,2149,2152],{"class":253,"line":350},[295,2143,1061],{"class":336},[295,2145,1856],{"class":340},[295,2147,2148],{"class":336},">=",[295,2150,2151],{"class":404}," 2",[295,2153,1864],{"class":340},[295,2155,2156,2159,2161],{"class":253,"line":364},[295,2157,2158],{"class":340},"        check.row_dimensions[i].hidden ",[295,2160,377],{"class":336},[295,2162,2163],{"class":404}," True\n",[295,2165,2166,2169,2172],{"class":253,"line":371},[295,2167,2168],{"class":340},"book.save(",[295,2170,2171],{"class":304},"\"expenses_collapsed.xlsx\"",[295,2173,530],{"class":340},[295,2175,2176,2178,2180,2183,2186,2188,2190,2192,2194,2196,2198,2201,2203,2206,2209,2211],{"class":253,"line":420},[295,2177,696],{"class":404},[295,2179,431],{"class":340},[295,2181,2182],{"class":304},"\"hidden rows:\"",[295,2184,2185],{"class":340},", [i ",[295,2187,464],{"class":336},[295,2189,1581],{"class":340},[295,2191,470],{"class":336},[295,2193,1586],{"class":404},[295,2195,431],{"class":340},[295,2197,112],{"class":404},[295,2199,2200],{"class":340},", check.max_row ",[295,2202,576],{"class":336},[295,2204,2205],{"class":404}," 1",[295,2207,2208],{"class":340},") ",[295,2210,493],{"class":336},[295,2212,2213],{"class":340}," check.row_dimensions[i].hidden])\n",[10,2215,2216],{},"Opened in Excel, the report shows Operating expenses and its three groups; clicking a group's button expands its accounts. Leave the rows visible when the file is mainly printed, since collapsed rows do not print, and when readers are likely to filter or sort, since hidden rows are easy to forget about.",[280,2218,2220],{"id":2219},"common-pitfalls","Common pitfalls",[2222,2223,2224,2232,2238,2247,2253],"ul",{},[2225,2226,2227,2231],"li",{},[2228,2229,2230],"strong",{},"Leading spaces instead of indent."," They change the value and break lookups and sorting.",[2225,2233,2234,2237],{},[2228,2235,2236],{},"Codes read as numbers."," Mixed numeric and text codes break parent lookups; read them as text.",[2225,2239,2240,2243,2244,2246],{},[2228,2241,2242],{},"Totals below by default."," Set ",[292,2245,2002],{}," when parents are above their children.",[2225,2248,2249,2252],{},[2228,2250,2251],{},"Too many levels."," Excel outlines stop at seven levels; very deep trees need a different layout.",[2225,2254,2255,2258],{},[2228,2256,2257],{},"Cycles in the parent column."," A row that is its own ancestor makes the depth function recurse forever; validate the tree first.",[280,2260,2262],{"id":2261},"performance-notes","Performance notes",[10,2264,2265,2266,2268,2269,2272],{},"The recursive depth function with a cache and the depth-first walk are linear in the number of rows; ten thousand accounts take milliseconds. Styling per row is the slowest step, which is why the example reuses one ",[292,2267,1604],{}," per depth and one ",[292,2270,2271],{},"Font",".",[280,2274,2276],{"id":2275},"conclusion","Conclusion",[10,2278,2279,2280,2282,2283,2285,2286,2288],{},"Compute each row's depth by following its parents, order rows with a depth-first walk so branches stay together, and roll amounts up to every ancestor. Write the names with ",[292,2281,1191],{}," rather than leading spaces, bold the parent rows, and set ",[292,2284,1810],{}," on each row with ",[292,2287,2002],{}," so branches collapse under their headings — optionally hiding the deeper levels for a summary-first view.",[280,2290,2292],{"id":2291},"frequently-asked-questions","Frequently asked questions",[10,2294,2295,2298],{},[2228,2296,2297],{},"How do I indent a cell with openpyxl?","\nSet the alignment's indent property: cell.alignment = Alignment(indent=2). Each indent level moves the text right by roughly the width of three characters. Use it instead of typing leading spaces, which break sorting, lookups and exports.",[10,2300,2301,2304],{},[2228,2302,2303],{},"How do I work out how deep each row is in a hierarchy?","\nFrom a parent–child table, follow each row's parent until you reach a row with no parent, counting the steps. A small recursive function with a cache does this for thousands of rows instantly.",[10,2306,2307,2310],{},[2228,2308,2309],{},"How do I make rows collapsible in Excel with Python?","\nGive each detail row an outline level by setting outline_level on its entry in ws.row_dimensions. Excel then shows grouping buttons in the margin. Set summaryBelow to False in the sheet's outline properties when parent rows sit above their children.",[10,2312,2313,2316],{},[2228,2314,2315],{},"Why not just put spaces in front of the names?","\nLeading spaces become part of the value. \"   Rent\" no longer matches \"Rent\" in a lookup, sorts before every letter, and appears with spaces in any export. The indent property changes only how the cell is displayed.",[280,2318,2320],{"id":2319},"related","Related",[2222,2322,2323,2329,2334,2341],{},[2225,2324,2325,2326,2328],{},"Up one level: ",[14,2327,17],{"href":16}," — alignment, fonts and borders.",[2225,2330,2331,2333],{},[14,2332,2007],{"href":2006}," — outlines in depth.",[2225,2335,2336,2340],{},[14,2337,2339],{"href":2338},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002F","Building Finance and Operations Reports"," — where charts of accounts come from.",[2225,2342,2343,2347],{},[14,2344,2346],{"href":2345},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-category-values-with-a-mapping-table\u002F","Standardise Category Values with a Mapping Table"," — mapping accounts to groups.",[1348,2349,2350],{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}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":290,"searchDepth":350,"depth":350,"links":2352},[2353,2354,2355,2356,2357,2358,2359,2360,2361,2362,2363],{"id":282,"depth":350,"text":283},{"id":320,"depth":350,"text":321},{"id":996,"depth":350,"text":997},{"id":1184,"depth":350,"text":1185},{"id":1803,"depth":350,"text":1804},{"id":2106,"depth":350,"text":2107},{"id":2219,"depth":350,"text":2220},{"id":2261,"depth":350,"text":2262},{"id":2275,"depth":350,"text":2276},{"id":2291,"depth":350,"text":2292},{"id":2319,"depth":350,"text":2320},"2026-09-25","Lay out a chart of accounts, an organisation or a product tree the way people read hierarchies: children indented under their parent, parents in bold with rolled-up totals, and rows grouped so each branch can be collapsed. Work out each row's depth from a parent–child table, set Alignment(indent=...), roll values up the tree and add outline levels with openpyxl.","md",[2368,2370,2372,2374],{"q":2297,"a":2369},"Set the alignment's indent property: cell.alignment = Alignment(indent=2). Each indent level moves the text right by roughly the width of three characters. Use it instead of typing leading spaces, which break sorting, lookups and exports.",{"q":2303,"a":2371},"From a parent–child table, follow each row's parent until you reach a row with no parent, counting the steps. A small recursive function with a cache does this for thousands of rows instantly.",{"q":2309,"a":2373},"Give each detail row an outline level by setting outline_level on its entry in ws.row_dimensions. Excel then shows grouping buttons in the margin. Set summaryBelow to False in the sheet's outline properties when parent rows sit above their children.",{"q":2315,"a":2375},"Leading spaces become part of the value. \"   Rent\" no longer matches \"Rent\" in a lookup, sorts before every letter, and appears with spaces in any export. The indent property changes only how the cell is displayed.","indent cells to show a hierarchy with openpyxl",null,{"breadcrumb":2379},[2380,2383,2386],{"name":2381,"item":2382},"Home","\u002F",{"name":2384,"item":2385},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":17,"item":16},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Findent-cells-to-show-a-hierarchy-with-openpyxl",{"title":2389,"description":2390},"Indent Cells and Group Rows into a Hierarchy with openpyxl","Show hierarchical data in Excel with openpyxl: depth from a parent-child table, Alignment(indent=n) instead of leading spaces, bold parent rows, rolled-up subtotals, outline_level row grouping, summary rows above detail and collapsed branches.","indent-cells-to-show-a-hierarchy-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Findent-cells-to-show-a-hierarchy-with-openpyxl\u002Findex",[2394,2395,2396],"cell-styling","report-layouts","openpyxl","how-to","v1m12A8bVp-Q9TvC-BAAO377yi4RSv2FCsORReRAU58",[2400,2404],{"title":2401,"path":2402,"stem":2403,"children":-1},"Hide Gridlines and Set the Zoom Level with openpyxl","\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fhide-gridlines-and-set-zoom-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fhide-gridlines-and-set-zoom-with-openpyxl\u002Findex",{"title":2405,"path":2406,"stem":2407,"children":-1},"Insert Page Breaks Between Groups with openpyxl","\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Finsert-page-breaks-between-groups-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Finsert-page-breaks-between-groups-with-openpyxl\u002Findex",[2409,2411],{"path":2410,"title":2384},"\u002Fformatting-and-charting-excel-reports-with-python",{"path":2412,"title":17},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl",[],[2415,2418,2421,2424,2427,2430],{"path":2416,"title":2417,"kind":2397},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-rich-text-in-a-cell-with-xlsxwriter","Write Rich Text with Mixed Formats in One Cell with xlsxwriter",{"path":2419,"title":2420,"kind":2397},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fcopy-cell-styles-with-openpyxl","Copy Cell Styles from One Cell to Another with openpyxl",{"path":2422,"title":2423,"kind":2397},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fauto-fit-column-widths-when-writing-with-pandas","Auto-Fit Column Widths When Writing with pandas",{"path":2425,"title":2426,"kind":2397},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fexport-a-styled-pandas-dataframe-to-excel","Export a Styled pandas DataFrame to Excel",{"path":2428,"title":2429,"kind":2397},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fgenerate-a-staff-rota-in-excel-with-python","Generate a Staff Rota in Excel with Python",{"path":2431,"title":2432,"kind":2397},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-a-table-of-contents-sheet-with-hyperlinks-in-excel","Add a Table of Contents Sheet with Hyperlinks in Excel",1790972117336]