[{"data":1,"prerenderedAt":2417},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-powerpoint-slides-from-excel-data":3,"surround:\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-powerpoint-slides-from-excel-data":2381,"ancestors:\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-powerpoint-slides-from-excel-data":2390,"children:\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-powerpoint-slides-from-excel-data":2395,"related:\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-powerpoint-slides-from-excel-data":2396},{"id":4,"title":5,"body":6,"dateModified":2348,"datePublished":2348,"description":2349,"extension":2350,"faq":2351,"keyword":2360,"kind":2361,"meta":2362,"navigation":299,"path":2370,"seo":2371,"slug":2374,"stem":2375,"topics":2376,"type":2379,"__hash__":2380},"docs\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-powerpoint-slides-from-excel-data\u002Findex.md","Generate PowerPoint Slides from Excel Data",{"type":7,"value":8,"toc":2335},"minimark",[9,29,34,65,80,264,268,271,525,528,532,539,696,703,707,714,1464,1470,1473,1539,1543,1546,1901,1911,1915,1918,2034,2084,2087,2201,2205,2214,2218,2260,2264,2270,2274,2280,2286,2292,2298,2302,2331],[10,11,12,13,17,18,22,23,28],"p",{},"The monthly review deck is often the most manual part of reporting: the numbers are already in a workbook, but someone spends an afternoon pasting charts into slides, retyping headline figures and fixing fonts. python-pptx writes ",[14,15,16],"code",{},".pptx"," files directly, and — importantly — it creates ",[19,20,21],"em",{},"native"," PowerPoint charts from data, not pictures of charts, so the deck's charts can be restyled, resized and even edited in PowerPoint afterwards. Generating the deck from the same pandas summaries that feed the workbook means the slides and the spreadsheet can never disagree. This page is part of ",[24,25,27],"a",{"href":26},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002F","Generating Excel Reports from Templates",".",[30,31,33],"h2",{"id":32},"prerequisites","Prerequisites",[35,36,41],"pre",{"className":37,"code":38,"language":39,"meta":40,"style":40},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install python-pptx pandas openpyxl\n","bash","",[14,42,43],{"__ignoreMap":40},[44,45,48,52,56,59,62],"span",{"class":46,"line":47},"line",1,[44,49,51],{"class":50},"sMTad","pip",[44,53,55],{"class":54},"srMev"," install",[44,57,58],{"class":54}," python-pptx",[44,60,61],{"class":54}," pandas",[44,63,64],{"class":54}," openpyxl\n",[10,66,67,68,71,72,75,76,79],{},"python-pptx is installed as ",[14,69,70],{},"python-pptx"," and imported as ",[14,73,74],{},"pptx",". ",[14,77,78],{},"regional_sales.xlsx"," has six months of sales and targets, in thousands, for four regions — one row per region and month.",[81,82,90,91,90,95,90,99,90,107,90,117,90,128,90,135,90,140,90,144,90,150,90,155,90,159,90,166,90,172,90,176,90,180,90,183,90,186,90,192,90,196,90,199,90,202,90,206,90,209,90,214,90,221,90,226,90,229,90,233,90,236,90,241,90,248,90,254,90,257,90,260],"svg",{"viewBox":83,"role":84,"ariaLabelledBy":85,"xmlns":88,"style":89},"4 0 752 160","img",[86,87],"pp-flow-t","pp-flow-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[92,93,94],"title",{"id":86},"From the workbook to the deck",[96,97,98],"desc",{"id":87},"The sales sheet is summarised with pandas, a title slide is added, then for each region a slide with a headline, a native chart and a table, and finally the deck is rendered to PDF for checking.",[100,101],"rect",{"x":102,"y":103,"width":104,"height":105,"fill":106},"4","0","752","160","#ffffff",[100,108],{"x":109,"y":110,"width":111,"height":112,"rx":113,"fill":114,"stroke":115,"style":116},"20.0","16.0","115.2","128.0","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[118,119,121],"g",{"transform":120},"translate(65.6,37.8) scale(1.000)",[122,123],"path",{"d":124,"fill":125,"stroke":126,"style":127},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[129,130,134],"text",{"x":131,"y":132,"style":133},"77.6","84.2","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Summarise",[129,136,139],{"x":131,"y":137,"style":138},"103.8","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","pandas per",[129,141,143],{"x":131,"y":142,"style":138},"121.2","region",[46,145],{"x1":146,"y1":147,"x2":148,"y2":147,"stroke":149,"style":116},"140.2","80.0","162.2","var(--brand,#5b5cf0)",[151,152],"polygon",{"points":153,"fill":154},"166.2,80.0 157.2,85.0 157.2,75.0","#5b5cf0",[100,156],{"x":157,"y":110,"width":111,"height":112,"rx":113,"fill":158,"stroke":149,"style":116},"171.2","#f0f4ff",[118,160,162],{"transform":161},"translate(216.8,46.5) scale(1.000)",[122,163],{"d":164,"fill":125,"stroke":165,"style":127},"M6 3h9l4 4v14H6z M15 3v4h4","var(--brand-strong,#4338ca)",[129,167,171],{"x":168,"y":169,"style":170},"228.8","93.0","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Title slide",[129,173,175],{"x":168,"y":174,"style":138},"112.5","period and date",[46,177],{"x1":178,"y1":147,"x2":179,"y2":147,"stroke":149,"style":116},"291.4","313.4",[151,181],{"points":182,"fill":154},"317.4,80.0 308.4,85.0 308.4,75.0",[100,184],{"x":185,"y":110,"width":111,"height":112,"rx":113,"fill":158,"stroke":149,"style":116},"322.4",[118,187,189],{"transform":188},"translate(368.0,37.8) scale(1.000)",[122,190],{"d":191,"fill":125,"stroke":165,"style":127},"M4 20h16 M7 17v-5 M12 17V7 M17 17v-8",[129,193,195],{"x":194,"y":132,"style":170},"380.0","Region slides",[129,197,198],{"x":194,"y":137,"style":138},"headline, chart,",[129,200,201],{"x":194,"y":142,"style":138},"table",[46,203],{"x1":204,"y1":147,"x2":205,"y2":147,"stroke":149,"style":116},"442.6","464.6",[151,207],{"points":208,"fill":154},"468.6,80.0 459.6,85.0 459.6,75.0",[100,210],{"x":211,"y":110,"width":111,"height":112,"rx":113,"fill":212,"stroke":213,"style":116},"473.6","#d9f4f1","var(--teal,#0f9488)",[118,215,217],{"transform":216},"translate(519.2,46.5) scale(1.000)",[122,218],{"d":219,"fill":125,"stroke":220,"style":127},"M12 2.5l8.5 4.7v9.6L12 21.5l-8.5-4.7V7.2z M3.5 7.2l8.5 4.7 8.5-4.7 M12 11.9v9.6","var(--teal-ink,#0b6157)",[129,222,225],{"x":223,"y":169,"style":224},"531.2","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Save .pptx",[129,227,228],{"x":223,"y":174,"style":138},"native charts",[46,230],{"x1":231,"y1":147,"x2":232,"y2":147,"stroke":149,"style":116},"593.8","615.8",[151,234],{"points":235,"fill":154},"619.8,80.0 610.8,85.0 610.8,75.0",[100,237],{"x":238,"y":110,"width":111,"height":112,"rx":113,"fill":239,"stroke":240,"style":116},"624.8","#fdefd8","var(--gold,#b4740a)",[118,242,244],{"transform":243},"translate(670.4,29.0) scale(1.000)",[122,245],{"d":246,"fill":125,"stroke":247,"style":127},"M5 12.5l4.5 4.5L19 7","var(--gold-ink,#7a4e06)",[129,249,253],{"x":250,"y":251,"style":252},"682.4","75.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Render",[129,255,256],{"x":250,"y":169,"style":252},"check",[129,258,259],{"x":250,"y":174,"style":138},"PDF via",[129,261,263],{"x":250,"y":262,"style":138},"130.0","LibreOffice",[30,265,267],{"id":266},"step-1-summarise-the-data-once","Step 1 — Summarise the data once",[10,269,270],{},"Compute everything the slides will say in pandas, before touching PowerPoint: the monthly series per region and the headline numbers.",[35,272,276],{"className":273,"code":274,"language":275,"meta":40,"style":40},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nsales = pd.read_excel(\"regional_sales.xlsx\")\nlast = sales[\"Month\"].max()\nsummary = (sales[sales[\"Month\"] == last].set_index(\"Region\")[[\"Sales\", \"Target\"]]\n           .assign(Variance=lambda d: d[\"Sales\"] - d[\"Target\"],\n                   Pct=lambda d: d[\"Sales\"] \u002F d[\"Target\"] - 1))\nfirst = sales[sales[\"Month\"] == sales[\"Month\"].min()].set_index(\"Region\")[\"Sales\"]\nsummary[\"SixMonthGrowth\"] = summary[\"Sales\"] \u002F first - 1\nprint(last)\nprint(summary.round(3))\n","python",[14,277,278,294,301,319,336,376,407,439,473,503,512],{"__ignoreMap":40},[44,279,280,284,288,291],{"class":46,"line":47},[44,281,283],{"class":282},"s-kum","import",[44,285,287],{"class":286},"skGVy"," pandas ",[44,289,290],{"class":282},"as",[44,292,293],{"class":286}," pd\n",[44,295,297],{"class":46,"line":296},2,[44,298,300],{"emptyLinePlaceholder":299},true,"\n",[44,302,304,307,310,313,316],{"class":46,"line":303},3,[44,305,306],{"class":286},"sales ",[44,308,309],{"class":282},"=",[44,311,312],{"class":286}," pd.read_excel(",[44,314,315],{"class":54},"\"regional_sales.xlsx\"",[44,317,318],{"class":286},")\n",[44,320,322,325,327,330,333],{"class":46,"line":321},4,[44,323,324],{"class":286},"last ",[44,326,309],{"class":282},[44,328,329],{"class":286}," sales[",[44,331,332],{"class":54},"\"Month\"",[44,334,335],{"class":286},"].max()\n",[44,337,339,342,344,347,349,352,355,358,361,364,367,370,373],{"class":46,"line":338},5,[44,340,341],{"class":286},"summary ",[44,343,309],{"class":282},[44,345,346],{"class":286}," (sales[sales[",[44,348,332],{"class":54},[44,350,351],{"class":286},"] ",[44,353,354],{"class":282},"==",[44,356,357],{"class":286}," last].set_index(",[44,359,360],{"class":54},"\"Region\"",[44,362,363],{"class":286},")[[",[44,365,366],{"class":54},"\"Sales\"",[44,368,369],{"class":286},", ",[44,371,372],{"class":54},"\"Target\"",[44,374,375],{"class":286},"]]\n",[44,377,379,382,386,389,392,394,396,399,402,404],{"class":46,"line":378},6,[44,380,381],{"class":286},"           .assign(",[44,383,385],{"class":384},"sa561","Variance",[44,387,388],{"class":282},"=lambda",[44,390,391],{"class":286}," d: d[",[44,393,366],{"class":54},[44,395,351],{"class":286},[44,397,398],{"class":282},"-",[44,400,401],{"class":286}," d[",[44,403,372],{"class":54},[44,405,406],{"class":286},"],\n",[44,408,410,413,415,417,419,421,424,426,428,430,432,436],{"class":46,"line":409},7,[44,411,412],{"class":384},"                   Pct",[44,414,388],{"class":282},[44,416,391],{"class":286},[44,418,366],{"class":54},[44,420,351],{"class":286},[44,422,423],{"class":282},"\u002F",[44,425,401],{"class":286},[44,427,372],{"class":54},[44,429,351],{"class":286},[44,431,398],{"class":282},[44,433,435],{"class":434},"sP0c6"," 1",[44,437,438],{"class":286},"))\n",[44,440,442,445,447,450,452,454,456,458,460,463,465,468,470],{"class":46,"line":441},8,[44,443,444],{"class":286},"first ",[44,446,309],{"class":282},[44,448,449],{"class":286}," sales[sales[",[44,451,332],{"class":54},[44,453,351],{"class":286},[44,455,354],{"class":282},[44,457,329],{"class":286},[44,459,332],{"class":54},[44,461,462],{"class":286},"].min()].set_index(",[44,464,360],{"class":54},[44,466,467],{"class":286},")[",[44,469,366],{"class":54},[44,471,472],{"class":286},"]\n",[44,474,476,479,482,484,486,489,491,493,495,498,500],{"class":46,"line":475},9,[44,477,478],{"class":286},"summary[",[44,480,481],{"class":54},"\"SixMonthGrowth\"",[44,483,351],{"class":286},[44,485,309],{"class":282},[44,487,488],{"class":286}," summary[",[44,490,366],{"class":54},[44,492,351],{"class":286},[44,494,423],{"class":282},[44,496,497],{"class":286}," first ",[44,499,398],{"class":282},[44,501,502],{"class":434}," 1\n",[44,504,506,509],{"class":46,"line":505},10,[44,507,508],{"class":434},"print",[44,510,511],{"class":286},"(last)\n",[44,513,515,517,520,523],{"class":46,"line":514},11,[44,516,508],{"class":434},[44,518,519],{"class":286},"(summary.round(",[44,521,522],{"class":434},"3",[44,524,438],{"class":286},[10,526,527],{},"East is the standout, 21% over target in September; West is 18% under and the only region smaller than six months ago. Keeping the numbers in a DataFrame means the same summary can be written to the workbook, used for the slide headlines, and tested — the deck becomes a view of data that was calculated once.",[30,529,531],{"id":530},"step-2-a-title-slide-from-the-default-layouts","Step 2 — A title slide from the default layouts",[10,533,534,535,538],{},"A new ",[14,536,537],{},"Presentation()"," comes with the standard Office layouts. Layout 0 is the title slide:",[35,540,542],{"className":273,"code":541,"language":275,"meta":40,"style":40},"from pptx import Presentation\nfrom pptx.util import Inches, Pt\n\nprs = Presentation()\nprs.slide_width, prs.slide_height = Inches(13.333), Inches(7.5)       # 16:9\ntitle_slide = prs.slides.add_slide(prs.slide_layouts[0])\ntitle_slide.shapes.title.text = \"Regional sales review\"\ntitle_slide.placeholders[1].text = f\"Six months to {pd.Period(last).strftime('%B %Y')}\"\nprint([layout.name for layout in prs.slide_layouts][:6])\n",[14,543,544,557,569,573,583,609,624,634,672],{"__ignoreMap":40},[44,545,546,549,552,554],{"class":46,"line":47},[44,547,548],{"class":282},"from",[44,550,551],{"class":286}," pptx ",[44,553,283],{"class":282},[44,555,556],{"class":286}," Presentation\n",[44,558,559,561,564,566],{"class":46,"line":296},[44,560,548],{"class":282},[44,562,563],{"class":286}," pptx.util ",[44,565,283],{"class":282},[44,567,568],{"class":286}," Inches, Pt\n",[44,570,571],{"class":46,"line":303},[44,572,300],{"emptyLinePlaceholder":299},[44,574,575,578,580],{"class":46,"line":321},[44,576,577],{"class":286},"prs ",[44,579,309],{"class":282},[44,581,582],{"class":286}," Presentation()\n",[44,584,585,588,590,593,596,599,602,605],{"class":46,"line":338},[44,586,587],{"class":286},"prs.slide_width, prs.slide_height ",[44,589,309],{"class":282},[44,591,592],{"class":286}," Inches(",[44,594,595],{"class":434},"13.333",[44,597,598],{"class":286},"), Inches(",[44,600,601],{"class":434},"7.5",[44,603,604],{"class":286},")       ",[44,606,608],{"class":607},"s-wDw","# 16:9\n",[44,610,611,614,616,619,621],{"class":46,"line":378},[44,612,613],{"class":286},"title_slide ",[44,615,309],{"class":282},[44,617,618],{"class":286}," prs.slides.add_slide(prs.slide_layouts[",[44,620,103],{"class":434},[44,622,623],{"class":286},"])\n",[44,625,626,629,631],{"class":46,"line":409},[44,627,628],{"class":286},"title_slide.shapes.title.text ",[44,630,309],{"class":282},[44,632,633],{"class":54}," \"Regional sales review\"\n",[44,635,636,639,642,645,647,650,653,657,660,663,666,669],{"class":46,"line":441},[44,637,638],{"class":286},"title_slide.placeholders[",[44,640,641],{"class":434},"1",[44,643,644],{"class":286},"].text ",[44,646,309],{"class":282},[44,648,649],{"class":282}," f",[44,651,652],{"class":54},"\"Six months to ",[44,654,656],{"class":655},"sSjpA","{",[44,658,659],{"class":286},"pd.Period(last).strftime(",[44,661,662],{"class":54},"'%B %Y'",[44,664,665],{"class":286},")",[44,667,668],{"class":655},"}",[44,670,671],{"class":54},"\"\n",[44,673,674,676,679,682,685,688,691,694],{"class":46,"line":475},[44,675,508],{"class":434},[44,677,678],{"class":286},"([layout.name ",[44,680,681],{"class":282},"for",[44,683,684],{"class":286}," layout ",[44,686,687],{"class":282},"in",[44,689,690],{"class":286}," prs.slide_layouts][:",[44,692,693],{"class":434},"6",[44,695,623],{"class":286},[10,697,698,699,702],{},"The default template is 4:3; setting the width and height to 13.333 × 7.5 inches makes it 16:9. For a real company deck, open the corporate template instead — ",[14,700,701],{},"Presentation(\"company_template.pptx\")"," — and use its layouts: fonts, colours and logos then come from the template, and the script only supplies content. The layout names printed above are the ones to look for in a corporate template too.",[30,704,706],{"id":705},"step-3-one-slide-per-region-with-a-native-chart","Step 3 — One slide per region with a native chart",[10,708,709,710,713],{},"For each region, add a slide with a headline, a column-and-line chart of sales against target, and the numbers. ",[14,711,712],{},"CategoryChartData"," holds the chart's data; PowerPoint stores it in an embedded workbook, which is why the chart remains editable:",[35,715,717],{"className":273,"code":716,"language":275,"meta":40,"style":40},"from pptx.chart.data import CategoryChartData\nfrom pptx.dml.color import RGBColor\nfrom pptx.enum.chart import XL_CHART_TYPE, XL_LEGEND_POSITION\n\nINDIGO, GREY = RGBColor(0x43, 0x38, 0xCA), RGBColor(0x94, 0xA3, 0xB8)\n\ndef layout(name):\n    for candidate in prs.slide_layouts:\n        if candidate.name == name:\n            return candidate\n    raise KeyError(f\"no slide layout named {name!r}: {[l.name for l in prs.slide_layouts]}\")\n\ndef headline(region, row):\n    direction = \"above\" if row.Variance >= 0 else \"below\"\n    return f\"{region}: {row.Sales:,.0f}k in {pd.Period(last).strftime('%B')}, {abs(row.Pct):.0%} {direction} target\"\n\nfor region, row in summary.iterrows():\n    slide = prs.slides.add_slide(layout(\"Title Only\"))\n    title = slide.shapes.title\n    title.text = headline(region, row)\n    title.left, title.top, title.width, title.height = Inches(0.6), Inches(0.4), Inches(12.1), Inches(1)\n    title.text_frame.paragraphs[0].runs[0].font.size = Pt(30)\n    series = sales[sales[\"Region\"] == region]\n    data = CategoryChartData()\n    data.categories = [pd.Period(m).strftime(\"%b\") for m in series[\"Month\"]]\n    data.add_series(\"Sales\", series[\"Sales\"].tolist())\n    data.add_series(\"Target\", series[\"Target\"].tolist())\n    frame = slide.shapes.add_chart(XL_CHART_TYPE.COLUMN_CLUSTERED, Inches(0.6), Inches(1.6),\n                                   Inches(8), Inches(5.2), data)\n    chart = frame.chart\n    chart.has_legend = True\n    chart.legend.position = XL_LEGEND_POSITION.BOTTOM\n    chart.legend.include_in_layout = False\n    chart.plots[0].series[0].format.fill.solid()\n    chart.plots[0].series[0].format.fill.fore_color.rgb = INDIGO\n    chart.plots[0].series[1].format.fill.solid()\n    chart.plots[0].series[1].format.fill.fore_color.rgb = GREY\n    chart.value_axis.has_major_gridlines = True\n    chart.value_axis.tick_labels.font.size = Pt(12)\n    chart.category_axis.tick_labels.font.size = Pt(12)\nprint(len(prs.slides), \"slides so far\")\n",[14,718,719,731,743,760,764,824,828,840,853,866,874,926,931,942,971,1039,1044,1057,1073,1084,1095,1124,1150,1169,1180,1215,1231,1244,1276,1293,1304,1315,1331,1342,1358,1377,1390,1408,1418,1432,1446],{"__ignoreMap":40},[44,720,721,723,726,728],{"class":46,"line":47},[44,722,548],{"class":282},[44,724,725],{"class":286}," pptx.chart.data ",[44,727,283],{"class":282},[44,729,730],{"class":286}," CategoryChartData\n",[44,732,733,735,738,740],{"class":46,"line":296},[44,734,548],{"class":282},[44,736,737],{"class":286}," pptx.dml.color ",[44,739,283],{"class":282},[44,741,742],{"class":286}," RGBColor\n",[44,744,745,747,750,752,755,757],{"class":46,"line":303},[44,746,548],{"class":282},[44,748,749],{"class":286}," pptx.enum.chart ",[44,751,283],{"class":282},[44,753,754],{"class":434}," XL_CHART_TYPE",[44,756,369],{"class":286},[44,758,759],{"class":434},"XL_LEGEND_POSITION\n",[44,761,762],{"class":46,"line":321},[44,763,300],{"emptyLinePlaceholder":299},[44,765,766,769,771,774,777,780,783,786,788,790,793,795,797,800,803,805,808,810,812,815,817,819,822],{"class":46,"line":338},[44,767,768],{"class":434},"INDIGO",[44,770,369],{"class":286},[44,772,773],{"class":434},"GREY",[44,775,776],{"class":282}," =",[44,778,779],{"class":286}," RGBColor(",[44,781,782],{"class":282},"0x",[44,784,785],{"class":434},"43",[44,787,369],{"class":286},[44,789,782],{"class":282},[44,791,792],{"class":434},"38",[44,794,369],{"class":286},[44,796,782],{"class":282},[44,798,799],{"class":434},"CA",[44,801,802],{"class":286},"), RGBColor(",[44,804,782],{"class":282},[44,806,807],{"class":434},"94",[44,809,369],{"class":286},[44,811,782],{"class":282},[44,813,814],{"class":434},"A3",[44,816,369],{"class":286},[44,818,782],{"class":282},[44,820,821],{"class":434},"B8",[44,823,318],{"class":286},[44,825,826],{"class":46,"line":378},[44,827,300],{"emptyLinePlaceholder":299},[44,829,830,833,837],{"class":46,"line":409},[44,831,832],{"class":282},"def",[44,834,836],{"class":835},"s_Opv"," layout",[44,838,839],{"class":286},"(name):\n",[44,841,842,845,848,850],{"class":46,"line":441},[44,843,844],{"class":282},"    for",[44,846,847],{"class":286}," candidate ",[44,849,687],{"class":282},[44,851,852],{"class":286}," prs.slide_layouts:\n",[44,854,855,858,861,863],{"class":46,"line":475},[44,856,857],{"class":282},"        if",[44,859,860],{"class":286}," candidate.name ",[44,862,354],{"class":282},[44,864,865],{"class":286}," name:\n",[44,867,868,871],{"class":46,"line":505},[44,869,870],{"class":282},"            return",[44,872,873],{"class":286}," candidate\n",[44,875,876,879,882,885,888,891,893,896,899,901,904,906,909,911,914,916,919,921,924],{"class":46,"line":514},[44,877,878],{"class":282},"    raise",[44,880,881],{"class":434}," KeyError",[44,883,884],{"class":286},"(",[44,886,887],{"class":282},"f",[44,889,890],{"class":54},"\"no slide layout named ",[44,892,656],{"class":655},[44,894,895],{"class":286},"name",[44,897,898],{"class":282},"!r",[44,900,668],{"class":655},[44,902,903],{"class":54},": ",[44,905,656],{"class":655},[44,907,908],{"class":286},"[l.name ",[44,910,681],{"class":282},[44,912,913],{"class":286}," l ",[44,915,687],{"class":282},[44,917,918],{"class":286}," prs.slide_layouts]",[44,920,668],{"class":655},[44,922,923],{"class":54},"\"",[44,925,318],{"class":286},[44,927,929],{"class":46,"line":928},12,[44,930,300],{"emptyLinePlaceholder":299},[44,932,934,936,939],{"class":46,"line":933},13,[44,935,832],{"class":282},[44,937,938],{"class":835}," headline",[44,940,941],{"class":286},"(region, row):\n",[44,943,945,948,950,953,956,959,962,965,968],{"class":46,"line":944},14,[44,946,947],{"class":286},"    direction ",[44,949,309],{"class":282},[44,951,952],{"class":54}," \"above\"",[44,954,955],{"class":282}," if",[44,957,958],{"class":286}," row.Variance ",[44,960,961],{"class":282},">=",[44,963,964],{"class":434}," 0",[44,966,967],{"class":282}," else",[44,969,970],{"class":54}," \"below\"\n",[44,972,974,977,979,981,983,985,987,989,991,994,997,999,1002,1004,1006,1009,1011,1013,1015,1017,1020,1023,1026,1028,1031,1034,1036],{"class":46,"line":973},15,[44,975,976],{"class":282},"    return",[44,978,649],{"class":282},[44,980,923],{"class":54},[44,982,656],{"class":655},[44,984,143],{"class":286},[44,986,668],{"class":655},[44,988,903],{"class":54},[44,990,656],{"class":655},[44,992,993],{"class":286},"row.Sales",[44,995,996],{"class":282},":,.0f",[44,998,668],{"class":655},[44,1000,1001],{"class":54},"k in ",[44,1003,656],{"class":655},[44,1005,659],{"class":286},[44,1007,1008],{"class":54},"'%B'",[44,1010,665],{"class":286},[44,1012,668],{"class":655},[44,1014,369],{"class":54},[44,1016,656],{"class":655},[44,1018,1019],{"class":434},"abs",[44,1021,1022],{"class":286},"(row.Pct)",[44,1024,1025],{"class":282},":.0%",[44,1027,668],{"class":655},[44,1029,1030],{"class":655}," {",[44,1032,1033],{"class":286},"direction",[44,1035,668],{"class":655},[44,1037,1038],{"class":54}," target\"\n",[44,1040,1042],{"class":46,"line":1041},16,[44,1043,300],{"emptyLinePlaceholder":299},[44,1045,1047,1049,1052,1054],{"class":46,"line":1046},17,[44,1048,681],{"class":282},[44,1050,1051],{"class":286}," region, row ",[44,1053,687],{"class":282},[44,1055,1056],{"class":286}," summary.iterrows():\n",[44,1058,1060,1063,1065,1068,1071],{"class":46,"line":1059},18,[44,1061,1062],{"class":286},"    slide ",[44,1064,309],{"class":282},[44,1066,1067],{"class":286}," prs.slides.add_slide(layout(",[44,1069,1070],{"class":54},"\"Title Only\"",[44,1072,438],{"class":286},[44,1074,1076,1079,1081],{"class":46,"line":1075},19,[44,1077,1078],{"class":286},"    title ",[44,1080,309],{"class":282},[44,1082,1083],{"class":286}," slide.shapes.title\n",[44,1085,1087,1090,1092],{"class":46,"line":1086},20,[44,1088,1089],{"class":286},"    title.text ",[44,1091,309],{"class":282},[44,1093,1094],{"class":286}," headline(region, row)\n",[44,1096,1098,1101,1103,1105,1108,1110,1113,1115,1118,1120,1122],{"class":46,"line":1097},21,[44,1099,1100],{"class":286},"    title.left, title.top, title.width, title.height ",[44,1102,309],{"class":282},[44,1104,592],{"class":286},[44,1106,1107],{"class":434},"0.6",[44,1109,598],{"class":286},[44,1111,1112],{"class":434},"0.4",[44,1114,598],{"class":286},[44,1116,1117],{"class":434},"12.1",[44,1119,598],{"class":286},[44,1121,641],{"class":434},[44,1123,318],{"class":286},[44,1125,1127,1130,1132,1135,1137,1140,1142,1145,1148],{"class":46,"line":1126},22,[44,1128,1129],{"class":286},"    title.text_frame.paragraphs[",[44,1131,103],{"class":434},[44,1133,1134],{"class":286},"].runs[",[44,1136,103],{"class":434},[44,1138,1139],{"class":286},"].font.size ",[44,1141,309],{"class":282},[44,1143,1144],{"class":286}," Pt(",[44,1146,1147],{"class":434},"30",[44,1149,318],{"class":286},[44,1151,1153,1156,1158,1160,1162,1164,1166],{"class":46,"line":1152},23,[44,1154,1155],{"class":286},"    series ",[44,1157,309],{"class":282},[44,1159,449],{"class":286},[44,1161,360],{"class":54},[44,1163,351],{"class":286},[44,1165,354],{"class":282},[44,1167,1168],{"class":286}," region]\n",[44,1170,1172,1175,1177],{"class":46,"line":1171},24,[44,1173,1174],{"class":286},"    data ",[44,1176,309],{"class":282},[44,1178,1179],{"class":286}," CategoryChartData()\n",[44,1181,1183,1186,1188,1191,1193,1196,1198,1201,1203,1206,1208,1211,1213],{"class":46,"line":1182},25,[44,1184,1185],{"class":286},"    data.categories ",[44,1187,309],{"class":282},[44,1189,1190],{"class":286}," [pd.Period(m).strftime(",[44,1192,923],{"class":54},[44,1194,1195],{"class":655},"%b",[44,1197,923],{"class":54},[44,1199,1200],{"class":286},") ",[44,1202,681],{"class":282},[44,1204,1205],{"class":286}," m ",[44,1207,687],{"class":282},[44,1209,1210],{"class":286}," series[",[44,1212,332],{"class":54},[44,1214,375],{"class":286},[44,1216,1218,1221,1223,1226,1228],{"class":46,"line":1217},26,[44,1219,1220],{"class":286},"    data.add_series(",[44,1222,366],{"class":54},[44,1224,1225],{"class":286},", series[",[44,1227,366],{"class":54},[44,1229,1230],{"class":286},"].tolist())\n",[44,1232,1234,1236,1238,1240,1242],{"class":46,"line":1233},27,[44,1235,1220],{"class":286},[44,1237,372],{"class":54},[44,1239,1225],{"class":286},[44,1241,372],{"class":54},[44,1243,1230],{"class":286},[44,1245,1247,1250,1252,1255,1258,1260,1263,1266,1268,1270,1273],{"class":46,"line":1246},28,[44,1248,1249],{"class":286},"    frame ",[44,1251,309],{"class":282},[44,1253,1254],{"class":286}," slide.shapes.add_chart(",[44,1256,1257],{"class":434},"XL_CHART_TYPE",[44,1259,28],{"class":286},[44,1261,1262],{"class":434},"COLUMN_CLUSTERED",[44,1264,1265],{"class":286},", Inches(",[44,1267,1107],{"class":434},[44,1269,598],{"class":286},[44,1271,1272],{"class":434},"1.6",[44,1274,1275],{"class":286},"),\n",[44,1277,1279,1282,1285,1287,1290],{"class":46,"line":1278},29,[44,1280,1281],{"class":286},"                                   Inches(",[44,1283,1284],{"class":434},"8",[44,1286,598],{"class":286},[44,1288,1289],{"class":434},"5.2",[44,1291,1292],{"class":286},"), data)\n",[44,1294,1296,1299,1301],{"class":46,"line":1295},30,[44,1297,1298],{"class":286},"    chart ",[44,1300,309],{"class":282},[44,1302,1303],{"class":286}," frame.chart\n",[44,1305,1307,1310,1312],{"class":46,"line":1306},31,[44,1308,1309],{"class":286},"    chart.has_legend ",[44,1311,309],{"class":282},[44,1313,1314],{"class":434}," True\n",[44,1316,1318,1321,1323,1326,1328],{"class":46,"line":1317},32,[44,1319,1320],{"class":286},"    chart.legend.position ",[44,1322,309],{"class":282},[44,1324,1325],{"class":434}," XL_LEGEND_POSITION",[44,1327,28],{"class":286},[44,1329,1330],{"class":434},"BOTTOM\n",[44,1332,1334,1337,1339],{"class":46,"line":1333},33,[44,1335,1336],{"class":286},"    chart.legend.include_in_layout ",[44,1338,309],{"class":282},[44,1340,1341],{"class":434}," False\n",[44,1343,1345,1348,1350,1353,1355],{"class":46,"line":1344},34,[44,1346,1347],{"class":286},"    chart.plots[",[44,1349,103],{"class":434},[44,1351,1352],{"class":286},"].series[",[44,1354,103],{"class":434},[44,1356,1357],{"class":286},"].format.fill.solid()\n",[44,1359,1361,1363,1365,1367,1369,1372,1374],{"class":46,"line":1360},35,[44,1362,1347],{"class":286},[44,1364,103],{"class":434},[44,1366,1352],{"class":286},[44,1368,103],{"class":434},[44,1370,1371],{"class":286},"].format.fill.fore_color.rgb ",[44,1373,309],{"class":282},[44,1375,1376],{"class":434}," INDIGO\n",[44,1378,1380,1382,1384,1386,1388],{"class":46,"line":1379},36,[44,1381,1347],{"class":286},[44,1383,103],{"class":434},[44,1385,1352],{"class":286},[44,1387,641],{"class":434},[44,1389,1357],{"class":286},[44,1391,1393,1395,1397,1399,1401,1403,1405],{"class":46,"line":1392},37,[44,1394,1347],{"class":286},[44,1396,103],{"class":434},[44,1398,1352],{"class":286},[44,1400,641],{"class":434},[44,1402,1371],{"class":286},[44,1404,309],{"class":282},[44,1406,1407],{"class":434}," GREY\n",[44,1409,1411,1414,1416],{"class":46,"line":1410},38,[44,1412,1413],{"class":286},"    chart.value_axis.has_major_gridlines ",[44,1415,309],{"class":282},[44,1417,1314],{"class":434},[44,1419,1421,1424,1426,1428,1430],{"class":46,"line":1420},39,[44,1422,1423],{"class":286},"    chart.value_axis.tick_labels.font.size ",[44,1425,309],{"class":282},[44,1427,1144],{"class":286},[44,1429,113],{"class":434},[44,1431,318],{"class":286},[44,1433,1435,1438,1440,1442,1444],{"class":46,"line":1434},40,[44,1436,1437],{"class":286},"    chart.category_axis.tick_labels.font.size ",[44,1439,309],{"class":282},[44,1441,1144],{"class":286},[44,1443,113],{"class":434},[44,1445,318],{"class":286},[44,1447,1449,1451,1453,1456,1459,1462],{"class":46,"line":1448},41,[44,1450,508],{"class":434},[44,1452,884],{"class":286},[44,1454,1455],{"class":434},"len",[44,1457,1458],{"class":286},"(prs.slides), ",[44,1460,1461],{"class":54},"\"slides so far\"",[44,1463,318],{"class":286},[10,1465,1466,1467,1469],{},"The headline is computed from the data, so it is always true: \"West: 259k in September, 18% below target\" is written by the script from the same row the chart shows. The \"Title Only\" layout gives a title placeholder and an empty body, which suits a chart and a table side by side. It is looked up by ",[19,1468,895],{}," rather than by position, because corporate templates order their layouts differently — index 5 is \"Title Only\" in the default template and could be anything in yours — and a missing name fails with a list of the names that do exist.",[10,1471,1472],{},"The title is repositioned on every slide for a reason worth knowing: the default template's layouts are drawn for 4:3 slides, and changing the slide size to 16:9 does not move their placeholders. Left alone, the headline sits in a 4:3-wide box in the middle of a wide slide and wraps onto two lines. A proper 16:9 template avoids this; with the default one, set the title's position and width yourself.",[81,1474,90,1480,90,1483,90,1486,90,1491,90,1495,90,1500,90,1503,90,1507,90,1510,90,1515,90,1519,90,1523,90,1526,90,1531,90,1535],{"viewBox":1475,"role":84,"ariaLabelledBy":1476,"xmlns":88,"style":1479},"54 0 652 331",[1477,1478],"pp-slide-t","pp-slide-d","width:100%;max-width:652px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[92,1481,1482],{"id":1477},"What each region slide contains",[96,1484,1485],{"id":1478},"Three layers of one slide. A headline computed from the data across the top, a native column chart of sales against target, and a small table of the last month's figures.",[100,1487],{"x":1488,"y":103,"width":1489,"height":1490,"fill":106},"54","652","331",[100,1492],{"x":1493,"y":110,"width":1494,"height":169,"rx":113,"fill":158,"stroke":149,"style":116},"70.0","620.0",[118,1496,1498],{"transform":1497},"translate(368.0,29.0) scale(1.000)",[122,1499],{"d":164,"fill":125,"stroke":165,"style":127},[129,1501,1502],{"x":194,"y":251,"style":170},"Headline — West: 259k in September, 18% below target",[129,1504,1506],{"x":194,"y":1505,"style":138},"95.0","computed from the same row as the chart",[100,1508],{"x":1493,"y":1509,"width":1494,"height":169,"rx":113,"fill":212,"stroke":213,"style":116},"119.0",[118,1511,1513],{"transform":1512},"translate(368.0,132.0) scale(1.000)",[122,1514],{"d":191,"fill":125,"stroke":220,"style":127},[129,1516,1518],{"x":194,"y":1517,"style":224},"178.5","Native column chart",[129,1520,1522],{"x":194,"y":1521,"style":138},"198.0","Sales vs Target, April to September",[100,1524],{"x":1493,"y":1525,"width":1494,"height":169,"rx":113,"fill":114,"stroke":115,"style":116},"222.0",[118,1527,1529],{"transform":1528},"translate(368.0,235.0) scale(1.000)",[122,1530],{"d":124,"fill":125,"stroke":126,"style":127},[129,1532,1534],{"x":194,"y":1533,"style":133},"281.5","Table",[129,1536,1538],{"x":194,"y":1537,"style":138},"301.0","Sales 259k · Target 315k · Variance −56k · 6-month growth −14%",[30,1540,1542],{"id":1541},"step-4-add-a-table-of-the-figures","Step 4 — Add a table of the figures",[10,1544,1545],{},"A small table beside the chart gives the exact numbers. Format them as text in Python — PowerPoint tables hold text, not numbers with formats:",[35,1547,1549],{"className":273,"code":1548,"language":275,"meta":40,"style":40},"from pptx.enum.text import PP_ALIGN\n\nfor slide, (region, row) in zip(list(prs.slides)[1:], summary.iterrows()):\n    shape = slide.shapes.add_table(4, 2, Inches(9), Inches(2.2), Inches(3.6), Inches(2))\n    table = shape.table\n    cells = [(\"Sales\", f\"{row.Sales:,.0f}k\"), (\"Target\", f\"{row.Target:,.0f}k\"),\n             (\"Variance\", f\"{row.Variance:+,.0f}k\"), (\"6-month growth\", f\"{row.SixMonthGrowth:+.0%}\")]\n    for r, (label, value) in enumerate(cells):\n        table.cell(r, 0).text = label\n        table.cell(r, 1).text = value\n        for c in (0, 1):\n            para = table.cell(r, c).text_frame.paragraphs[0]\n            para.runs[0].font.size = Pt(16)\n            if c == 1:\n                para.alignment = PP_ALIGN.RIGHT\nprs.save(\"regional_review.pptx\")\nprint(\"saved\", len(prs.slides), \"slides\")\n",[14,1550,1551,1563,1567,1592,1630,1640,1693,1745,1760,1775,1788,1810,1824,1842,1856,1871,1881],{"__ignoreMap":40},[44,1552,1553,1555,1558,1560],{"class":46,"line":47},[44,1554,548],{"class":282},[44,1556,1557],{"class":286}," pptx.enum.text ",[44,1559,283],{"class":282},[44,1561,1562],{"class":434}," PP_ALIGN\n",[44,1564,1565],{"class":46,"line":296},[44,1566,300],{"emptyLinePlaceholder":299},[44,1568,1569,1571,1574,1576,1579,1581,1584,1587,1589],{"class":46,"line":303},[44,1570,681],{"class":282},[44,1572,1573],{"class":286}," slide, (region, row) ",[44,1575,687],{"class":282},[44,1577,1578],{"class":434}," zip",[44,1580,884],{"class":286},[44,1582,1583],{"class":434},"list",[44,1585,1586],{"class":286},"(prs.slides)[",[44,1588,641],{"class":434},[44,1590,1591],{"class":286},":], summary.iterrows()):\n",[44,1593,1594,1597,1599,1602,1604,1606,1609,1611,1614,1616,1619,1621,1624,1626,1628],{"class":46,"line":321},[44,1595,1596],{"class":286},"    shape ",[44,1598,309],{"class":282},[44,1600,1601],{"class":286}," slide.shapes.add_table(",[44,1603,102],{"class":434},[44,1605,369],{"class":286},[44,1607,1608],{"class":434},"2",[44,1610,1265],{"class":286},[44,1612,1613],{"class":434},"9",[44,1615,598],{"class":286},[44,1617,1618],{"class":434},"2.2",[44,1620,598],{"class":286},[44,1622,1623],{"class":434},"3.6",[44,1625,598],{"class":286},[44,1627,1608],{"class":434},[44,1629,438],{"class":286},[44,1631,1632,1635,1637],{"class":46,"line":338},[44,1633,1634],{"class":286},"    table ",[44,1636,309],{"class":282},[44,1638,1639],{"class":286}," shape.table\n",[44,1641,1642,1645,1647,1650,1652,1654,1656,1658,1660,1662,1664,1666,1669,1672,1674,1676,1678,1680,1682,1685,1687,1689,1691],{"class":46,"line":378},[44,1643,1644],{"class":286},"    cells ",[44,1646,309],{"class":282},[44,1648,1649],{"class":286}," [(",[44,1651,366],{"class":54},[44,1653,369],{"class":286},[44,1655,887],{"class":282},[44,1657,923],{"class":54},[44,1659,656],{"class":655},[44,1661,993],{"class":286},[44,1663,996],{"class":282},[44,1665,668],{"class":655},[44,1667,1668],{"class":54},"k\"",[44,1670,1671],{"class":286},"), (",[44,1673,372],{"class":54},[44,1675,369],{"class":286},[44,1677,887],{"class":282},[44,1679,923],{"class":54},[44,1681,656],{"class":655},[44,1683,1684],{"class":286},"row.Target",[44,1686,996],{"class":282},[44,1688,668],{"class":655},[44,1690,1668],{"class":54},[44,1692,1275],{"class":286},[44,1694,1695,1698,1701,1703,1705,1707,1709,1712,1715,1717,1719,1721,1724,1726,1728,1730,1732,1735,1738,1740,1742],{"class":46,"line":409},[44,1696,1697],{"class":286},"             (",[44,1699,1700],{"class":54},"\"Variance\"",[44,1702,369],{"class":286},[44,1704,887],{"class":282},[44,1706,923],{"class":54},[44,1708,656],{"class":655},[44,1710,1711],{"class":286},"row.Variance",[44,1713,1714],{"class":282},":+,.0f",[44,1716,668],{"class":655},[44,1718,1668],{"class":54},[44,1720,1671],{"class":286},[44,1722,1723],{"class":54},"\"6-month growth\"",[44,1725,369],{"class":286},[44,1727,887],{"class":282},[44,1729,923],{"class":54},[44,1731,656],{"class":655},[44,1733,1734],{"class":286},"row.SixMonthGrowth",[44,1736,1737],{"class":282},":+.0%",[44,1739,668],{"class":655},[44,1741,923],{"class":54},[44,1743,1744],{"class":286},")]\n",[44,1746,1747,1749,1752,1754,1757],{"class":46,"line":441},[44,1748,844],{"class":282},[44,1750,1751],{"class":286}," r, (label, value) ",[44,1753,687],{"class":282},[44,1755,1756],{"class":434}," enumerate",[44,1758,1759],{"class":286},"(cells):\n",[44,1761,1762,1765,1767,1770,1772],{"class":46,"line":475},[44,1763,1764],{"class":286},"        table.cell(r, ",[44,1766,103],{"class":434},[44,1768,1769],{"class":286},").text ",[44,1771,309],{"class":282},[44,1773,1774],{"class":286}," label\n",[44,1776,1777,1779,1781,1783,1785],{"class":46,"line":505},[44,1778,1764],{"class":286},[44,1780,641],{"class":434},[44,1782,1769],{"class":286},[44,1784,309],{"class":282},[44,1786,1787],{"class":286}," value\n",[44,1789,1790,1793,1796,1798,1801,1803,1805,1807],{"class":46,"line":514},[44,1791,1792],{"class":282},"        for",[44,1794,1795],{"class":286}," c ",[44,1797,687],{"class":282},[44,1799,1800],{"class":286}," (",[44,1802,103],{"class":434},[44,1804,369],{"class":286},[44,1806,641],{"class":434},[44,1808,1809],{"class":286},"):\n",[44,1811,1812,1815,1817,1820,1822],{"class":46,"line":928},[44,1813,1814],{"class":286},"            para ",[44,1816,309],{"class":282},[44,1818,1819],{"class":286}," table.cell(r, c).text_frame.paragraphs[",[44,1821,103],{"class":434},[44,1823,472],{"class":286},[44,1825,1826,1829,1831,1833,1835,1837,1840],{"class":46,"line":933},[44,1827,1828],{"class":286},"            para.runs[",[44,1830,103],{"class":434},[44,1832,1139],{"class":286},[44,1834,309],{"class":282},[44,1836,1144],{"class":286},[44,1838,1839],{"class":434},"16",[44,1841,318],{"class":286},[44,1843,1844,1847,1849,1851,1853],{"class":46,"line":944},[44,1845,1846],{"class":282},"            if",[44,1848,1795],{"class":286},[44,1850,354],{"class":282},[44,1852,435],{"class":434},[44,1854,1855],{"class":286},":\n",[44,1857,1858,1861,1863,1866,1868],{"class":46,"line":973},[44,1859,1860],{"class":286},"                para.alignment ",[44,1862,309],{"class":282},[44,1864,1865],{"class":434}," PP_ALIGN",[44,1867,28],{"class":286},[44,1869,1870],{"class":434},"RIGHT\n",[44,1872,1873,1876,1879],{"class":46,"line":1041},[44,1874,1875],{"class":286},"prs.save(",[44,1877,1878],{"class":54},"\"regional_review.pptx\"",[44,1880,318],{"class":286},[44,1882,1883,1885,1887,1890,1892,1894,1896,1899],{"class":46,"line":1046},[44,1884,508],{"class":434},[44,1886,884],{"class":286},[44,1888,1889],{"class":54},"\"saved\"",[44,1891,369],{"class":286},[44,1893,1455],{"class":434},[44,1895,1458],{"class":286},[44,1897,1898],{"class":54},"\"slides\"",[44,1900,318],{"class":286},[10,1902,1903,1906,1907,1910],{},[14,1904,1905],{},"f\"{row.Variance:+,.0f}k\""," puts an explicit sign on the variance, so a positive number reads as \"+12k\" rather than just \"12k\". The first table row gets the default table style's header formatting — white on blue in the render — which is why the Sales figure stands out; with a corporate template, the table picks up that template's table style instead. If the first row should not look like a header, set ",[14,1908,1909],{},"shape.table.first_row = False",". Font sizes are set per run, because a PowerPoint table cell's text is a paragraph made of runs, like any other text box.",[30,1912,1914],{"id":1913},"step-5-check-the-deck","Step 5 — Check the deck",[10,1916,1917],{},"Read the deck back to confirm its structure, then render it to PDF with LibreOffice to look at it:",[35,1919,1921],{"className":273,"code":1920,"language":275,"meta":40,"style":40},"check = Presentation(\"regional_review.pptx\")\nfor i, slide in enumerate(check.slides):\n    kinds = sorted({s.shape_type for s in slide.shapes if s.shape_type is not None}, key=str)\n    print(i, slide.shapes.title.text if slide.shapes.title else \"\", [k.name for k in kinds])\n",[14,1922,1923,1937,1951,2002],{"__ignoreMap":40},[44,1924,1925,1928,1930,1933,1935],{"class":46,"line":47},[44,1926,1927],{"class":286},"check ",[44,1929,309],{"class":282},[44,1931,1932],{"class":286}," Presentation(",[44,1934,1878],{"class":54},[44,1936,318],{"class":286},[44,1938,1939,1941,1944,1946,1948],{"class":46,"line":296},[44,1940,681],{"class":282},[44,1942,1943],{"class":286}," i, slide ",[44,1945,687],{"class":282},[44,1947,1756],{"class":434},[44,1949,1950],{"class":286},"(check.slides):\n",[44,1952,1953,1956,1958,1961,1964,1966,1969,1971,1974,1977,1980,1983,1986,1989,1992,1995,1997,2000],{"class":46,"line":303},[44,1954,1955],{"class":286},"    kinds ",[44,1957,309],{"class":282},[44,1959,1960],{"class":434}," sorted",[44,1962,1963],{"class":286},"({s.shape_type ",[44,1965,681],{"class":282},[44,1967,1968],{"class":286}," s ",[44,1970,687],{"class":282},[44,1972,1973],{"class":286}," slide.shapes ",[44,1975,1976],{"class":282},"if",[44,1978,1979],{"class":286}," s.shape_type ",[44,1981,1982],{"class":282},"is",[44,1984,1985],{"class":282}," not",[44,1987,1988],{"class":434}," None",[44,1990,1991],{"class":286},"}, ",[44,1993,1994],{"class":384},"key",[44,1996,309],{"class":282},[44,1998,1999],{"class":434},"str",[44,2001,318],{"class":286},[44,2003,2004,2007,2010,2012,2015,2018,2021,2024,2026,2029,2031],{"class":46,"line":321},[44,2005,2006],{"class":434},"    print",[44,2008,2009],{"class":286},"(i, slide.shapes.title.text ",[44,2011,1976],{"class":282},[44,2013,2014],{"class":286}," slide.shapes.title ",[44,2016,2017],{"class":282},"else",[44,2019,2020],{"class":54}," \"\"",[44,2022,2023],{"class":286},", [k.name ",[44,2025,681],{"class":282},[44,2027,2028],{"class":286}," k ",[44,2030,687],{"class":282},[44,2032,2033],{"class":286}," kinds])\n",[35,2035,2037],{"className":37,"code":2036,"language":39,"meta":40,"style":40},"soffice -env:UserInstallation=file:\u002F\u002F$PWD\u002Flo_profile --headless --convert-to pdf regional_review.pptx > \u002Fdev\u002Fnull 2>&1\npython -c \"from pypdf import PdfReader; print(len(PdfReader('regional_review.pdf').pages), 'pages rendered')\"\n",[14,2038,2039,2074],{"__ignoreMap":40},[44,2040,2041,2044,2047,2050,2053,2056,2059,2062,2065,2068,2071],{"class":46,"line":47},[44,2042,2043],{"class":50},"soffice",[44,2045,2046],{"class":434}," -env:UserInstallation=file:\u002F\u002F",[44,2048,2049],{"class":286},"$PWD",[44,2051,2052],{"class":434},"\u002Flo_profile",[44,2054,2055],{"class":434}," --headless",[44,2057,2058],{"class":434}," --convert-to",[44,2060,2061],{"class":54}," pdf",[44,2063,2064],{"class":54}," regional_review.pptx",[44,2066,2067],{"class":282}," >",[44,2069,2070],{"class":54}," \u002Fdev\u002Fnull",[44,2072,2073],{"class":282}," 2>&1\n",[44,2075,2076,2078,2081],{"class":46,"line":296},[44,2077,275],{"class":50},[44,2079,2080],{"class":434}," -c",[44,2082,2083],{"class":54}," \"from pypdf import PdfReader; print(len(PdfReader('regional_review.pdf').pages), 'pages rendered')\"\n",[10,2085,2086],{},"The deck has five slides — a title and four regions — each region slide carrying a chart and a table, and LibreOffice renders all five. The PDF is also a convenient format to email alongside the editable deck, for readers who only need to look at it.",[81,2088,90,2093,90,2096,90,2099,90,2102,90,2107,90,2113,90,2119,90,2125,90,2131,90,2137,90,2141,90,2146,90,2150,90,2155,90,2158,90,2164,90,2169,90,2173,90,2177,90,2184,90,2187,90,2192,90,2195],{"viewBox":2089,"role":84,"ariaLabelledBy":2090,"xmlns":88,"style":89},"4 0 752 207",[2091,2092],"pp-native-t","pp-native-d",[92,2094,2095],{"id":2091},"Native charts or pasted pictures",[96,2097,2098],{"id":2092},"Two panels. A native python-pptx chart stores its data in the deck, stays sharp at any size and can be restyled and edited in PowerPoint. A pasted image of a chart is fixed, blurs when enlarged and cannot be edited.",[100,2100],{"x":102,"y":103,"width":104,"height":2101,"fill":106},"207",[100,2103],{"x":109,"y":110,"width":2104,"height":2105,"rx":2106,"fill":212,"stroke":213,"style":116},"351.0","175.0","14",[118,2108,2110],{"transform":2109},"translate(182.5,29.0) scale(1.083)",[122,2111],{"d":191,"fill":125,"stroke":220,"style":2112},"stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[129,2114,2118],{"x":2115,"y":2116,"style":2117},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Native chart (python-pptx)",[46,2120],{"x1":2121,"y1":2122,"x2":2123,"y2":2122,"stroke":213,"style":2124},"36.0","91.0","355.0","stroke-width:1px",[129,2126,2130],{"x":2127,"y":2128,"style":2129},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","data embedded, editable",[118,2132,2134],{"transform":2133},"translate(38.5,99.5) scale(0.625)",[122,2135],{"d":246,"fill":125,"stroke":213,"style":2136},"stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[129,2138,2140],{"x":2127,"y":2139,"style":2129},"137.0","takes the template's styling",[118,2142,2144],{"transform":2143},"translate(38.5,125.5) scale(0.625)",[122,2145],{"d":246,"fill":125,"stroke":213,"style":2136},[129,2147,2149],{"x":2127,"y":2148,"style":2129},"163.0","sharp at any size",[118,2151,2153],{"transform":2152},"translate(38.5,151.5) scale(0.625)",[122,2154],{"d":246,"fill":125,"stroke":213,"style":2136},[100,2156],{"x":2157,"y":110,"width":2104,"height":2105,"rx":2106,"fill":239,"stroke":240,"style":116},"389.0",[118,2159,2161],{"transform":2160},"translate(551.5,29.0) scale(1.083)",[122,2162],{"d":2163,"fill":125,"stroke":247,"style":2112},"M4 5h16v14H4z M4 16l5-5 4 4 3-3 4 4 M15 9h.01",[129,2165,2168],{"x":2166,"y":2116,"style":2167},"564.5","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Picture of a chart",[46,2170],{"x1":2171,"y1":2122,"x2":2172,"y2":2122,"stroke":240,"style":2124},"405.0","724.0",[129,2174,2176],{"x":2175,"y":2128,"style":2129},"429.0","fixed pixels",[118,2178,2180],{"transform":2179},"translate(407.5,99.5) scale(0.625)",[122,2181],{"d":2182,"fill":125,"stroke":2183,"style":2136},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)",[129,2185,2186],{"x":2175,"y":2139,"style":2129},"restyling means regenerating",[118,2188,2190],{"transform":2189},"translate(407.5,125.5) scale(0.625)",[122,2191],{"d":2182,"fill":125,"stroke":2183,"style":2136},[129,2193,2194],{"x":2175,"y":2148,"style":2129},"fine for matplotlib-only visuals",[118,2196,2198],{"transform":2197},"translate(407.5,151.5) scale(0.625)",[122,2199],{"d":2200,"fill":125,"stroke":240,"style":2136},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4",[30,2202,2204],{"id":2203},"performance-notes","Performance notes",[10,2206,2207,2208,2210,2211,2213],{},"python-pptx writes a deck of dozens of slides with native charts in well under a second; each chart embeds a small workbook with its data, so a slide with a chart adds tens of kilobytes rather than the megabytes a high-resolution picture would. The slow part of the check in Step 5 is LibreOffice's start-up. When decks are produced per region or per client, build them all in one run and convert every ",[14,2209,16],{}," to PDF in a single ",[14,2212,2043],{}," call, exactly as for workbooks.",[30,2215,2217],{"id":2216},"common-pitfalls","Common pitfalls",[2219,2220,2221,2236,2242,2248,2254],"ul",{},[2222,2223,2224,2228,2229,2232,2233,2235],"li",{},[2225,2226,2227],"strong",{},"Pasting pictures instead of native charts."," Use ",[14,2230,2231],{},"add_chart"," with ",[14,2234,712],{}," so charts stay editable and sharp.",[2222,2237,2238,2241],{},[2225,2239,2240],{},"Layouts by index in a corporate template."," Index 5 is \"Title Only\" in the default template, not necessarily in yours; find layouts by name.",[2222,2243,2244,2247],{},[2225,2245,2246],{},"Numbers in tables."," Table cells are text; format them in Python.",[2222,2249,2250,2253],{},[2225,2251,2252],{},"Headline text typed by hand."," Compute it from the same data as the chart so it cannot drift.",[2222,2255,2256,2259],{},[2225,2257,2258],{},"4:3 slides."," The default presentation is 4:3; set 16:9 dimensions or use a 16:9 template.",[30,2261,2263],{"id":2262},"conclusion","Conclusion",[10,2265,2266,2267,2269],{},"Summarise the workbook's data with pandas, then build the deck with python-pptx: a title slide from the template's layouts, one slide per region with a headline computed from the data, a native chart from ",[14,2268,712],{}," coloured to the house style, and a table of formatted figures. Save it, read it back to check its structure, and render it to PDF with LibreOffice before it goes out.",[30,2271,2273],{"id":2272},"frequently-asked-questions","Frequently asked questions",[10,2275,2276,2279],{},[2225,2277,2278],{},"How do I create PowerPoint slides from Excel data with Python?","\nRead the data with pandas, then use python-pptx: Presentation() or your template, add_slide with a layout, and add titles, charts and tables to each slide before saving with prs.save(\"deck.pptx\").",[10,2281,2282,2285],{},[2225,2283,2284],{},"Can python-pptx create charts?","\nYes. slide.shapes.add_chart with a CategoryChartData object creates a native PowerPoint chart — column, bar, line, pie and others — whose data is embedded in the file, so it can be edited in PowerPoint.",[10,2287,2288,2291],{},[2225,2289,2290],{},"How do I use my company's PowerPoint template?","\nOpen it with Presentation(\"template.pptx\") and add slides using its layouts, found by name in prs.slide_layouts. Fonts, colours and logos come from the template.",[10,2293,2294,2297],{},[2225,2295,2296],{},"How do I check the deck without PowerPoint?","\nRead it back with python-pptx to count slides and shapes, and convert it to PDF with headless LibreOffice to look at the pages.",[30,2299,2301],{"id":2300},"related","Related",[2219,2303,2304,2310,2317,2324],{},[2222,2305,2306,2307,2309],{},"Up one level: ",[24,2308,27],{"href":26}," — template-driven report generation.",[2222,2311,2312,2316],{},[24,2313,2315],{"href":2314},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-word-documents-from-excel-rows\u002F","Generate Word Documents from Excel Rows"," — the same idea for Word.",[2222,2318,2319,2323],{},[24,2320,2322],{"href":2321},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-dashboard-sheet-with-charts-from-multiple-tabs\u002F","Build a Dashboard Sheet with Charts from Multiple Tabs"," — the workbook version of the deck.",[2222,2325,2326,2330],{},[24,2327,2329],{"href":2328},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-weekly-sales-report\u002F","Build a Weekly Sales Report"," — the summaries these slides are built on.",[2332,2333,2334],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .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-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}",{"title":40,"searchDepth":296,"depth":296,"links":2336},[2337,2338,2339,2340,2341,2342,2343,2344,2345,2346,2347],{"id":32,"depth":296,"text":33},{"id":266,"depth":296,"text":267},{"id":530,"depth":296,"text":531},{"id":705,"depth":296,"text":706},{"id":1541,"depth":296,"text":1542},{"id":1913,"depth":296,"text":1914},{"id":2203,"depth":296,"text":2204},{"id":2216,"depth":296,"text":2217},{"id":2262,"depth":296,"text":2263},{"id":2272,"depth":296,"text":2273},{"id":2300,"depth":296,"text":2301},"2026-09-29","Build the monthly deck from the spreadsheet instead of by copy and paste: python-pptx slides with a title per region, native editable PowerPoint charts from pandas summaries, a formatted table, a KPI headline computed from the data, a company template's layouts, and a check that renders the deck to PDF.","md",[2352,2354,2356,2358],{"q":2278,"a":2353},"Read the data with pandas, then use python-pptx: Presentation() or your template, add_slide with a layout, and add titles, charts and tables to each slide before saving with prs.save(\"deck.pptx\").",{"q":2284,"a":2355},"Yes. slide.shapes.add_chart with a CategoryChartData object creates a native PowerPoint chart — column, bar, line, pie and others — whose data is embedded in the file, so it can be edited in PowerPoint.",{"q":2290,"a":2357},"Open it with Presentation(\"template.pptx\") and add slides using its layouts, found by name in prs.slide_layouts. Fonts, colours and logos come from the template.",{"q":2296,"a":2359},"Read it back with python-pptx to count slides and shapes, and convert it to PDF with headless LibreOffice to look at the pages.","generate powerpoint slides from excel data",null,{"breadcrumb":2363},[2364,2366,2369],{"name":2365,"item":423},"Home",{"name":2367,"item":2368},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":27,"item":26},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-powerpoint-slides-from-excel-data",{"title":2372,"description":2373},"Create PowerPoint Slides from Excel with Python (python-pptx)","Generate a PowerPoint deck from Excel data with python-pptx: one slide per region, native column and line charts via CategoryChartData, tables with number formats, headline text from pandas, template layouts and a LibreOffice render check.","generate-powerpoint-slides-from-excel-data","automating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-powerpoint-slides-from-excel-data\u002Findex",[2377,2378],"report-layouts","charts-and-images","how-to","e-WsilzummLzqB7B0toTI1dZmgDW6kFaTpFgnBdTP7I",[2382,2386],{"title":2383,"path":2384,"stem":2385,"children":-1},"Generate One PDF per Excel Row","\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-one-pdf-per-excel-row","automating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-one-pdf-per-excel-row\u002Findex",{"title":2387,"path":2388,"stem":2389,"children":-1},"Generate Word Documents from Excel Rows with Python","\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-word-documents-from-excel-rows","automating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-word-documents-from-excel-rows\u002Findex",[2391,2393],{"path":2392,"title":27},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates",{"path":2394,"title":2367},"\u002Fautomating-reporting-workflows",[],[2397,2401,2404,2407,2410,2413],{"path":2398,"title":2399,"kind":2400},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports","Building Finance and Operations Reports in Excel with Python","guide",{"path":2402,"title":2403,"kind":2379},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fallocate-shared-costs-across-departments","Allocate Shared Costs Across Departments with Python",{"path":2405,"title":2406,"kind":2379},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-bank-reconciliation-report","Build a Bank Reconciliation Report with Python and Excel",{"path":2408,"title":2409,"kind":2379},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-bill-of-materials-cost-rollup","Build a Bill of Materials Cost Rollup",{"path":2411,"title":2412,"kind":2379},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-break-even-analysis-in-excel-with-python","Build a Break-Even Analysis in Excel with Python",{"path":2414,"title":2415,"kind":2416},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-budget-vs-actual-variance-report","Build a Budget vs Actual Variance Report in Excel with Python","compare",1790972138129]