Generate PowerPoint Slides from Excel Data
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 .pptx files directly, and — importantly — it creates 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 Generating Excel Reports from Templates.
Prerequisites
pip install python-pptx pandas openpyxl
python-pptx is installed as python-pptx and imported as pptx. regional_sales.xlsx has six months of sales and targets, in thousands, for four regions — one row per region and month.
Step 1 — Summarise the data once
Compute everything the slides will say in pandas, before touching PowerPoint: the monthly series per region and the headline numbers.
import pandas as pd
sales = pd.read_excel("regional_sales.xlsx")
last = sales["Month"].max()
summary = (sales[sales["Month"] == last].set_index("Region")[["Sales", "Target"]]
.assign(Variance=lambda d: d["Sales"] - d["Target"],
Pct=lambda d: d["Sales"] / d["Target"] - 1))
first = sales[sales["Month"] == sales["Month"].min()].set_index("Region")["Sales"]
summary["SixMonthGrowth"] = summary["Sales"] / first - 1
print(last)
print(summary.round(3))
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.
Step 2 — A title slide from the default layouts
A new Presentation() comes with the standard Office layouts. Layout 0 is the title slide:
from pptx import Presentation
from pptx.util import Inches, Pt
prs = Presentation()
prs.slide_width, prs.slide_height = Inches(13.333), Inches(7.5) # 16:9
title_slide = prs.slides.add_slide(prs.slide_layouts[0])
title_slide.shapes.title.text = "Regional sales review"
title_slide.placeholders[1].text = f"Six months to {pd.Period(last).strftime('%B %Y')}"
print([layout.name for layout in prs.slide_layouts][:6])
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 — 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.
Step 3 — One slide per region with a native chart
For each region, add a slide with a headline, a column-and-line chart of sales against target, and the numbers. CategoryChartData holds the chart's data; PowerPoint stores it in an embedded workbook, which is why the chart remains editable:
from pptx.chart.data import CategoryChartData
from pptx.dml.color import RGBColor
from pptx.enum.chart import XL_CHART_TYPE, XL_LEGEND_POSITION
INDIGO, GREY = RGBColor(0x43, 0x38, 0xCA), RGBColor(0x94, 0xA3, 0xB8)
def layout(name):
for candidate in prs.slide_layouts:
if candidate.name == name:
return candidate
raise KeyError(f"no slide layout named {name!r}: {[l.name for l in prs.slide_layouts]}")
def headline(region, row):
direction = "above" if row.Variance >= 0 else "below"
return f"{region}: {row.Sales:,.0f}k in {pd.Period(last).strftime('%B')}, {abs(row.Pct):.0%} {direction} target"
for region, row in summary.iterrows():
slide = prs.slides.add_slide(layout("Title Only"))
title = slide.shapes.title
title.text = headline(region, row)
title.left, title.top, title.width, title.height = Inches(0.6), Inches(0.4), Inches(12.1), Inches(1)
title.text_frame.paragraphs[0].runs[0].font.size = Pt(30)
series = sales[sales["Region"] == region]
data = CategoryChartData()
data.categories = [pd.Period(m).strftime("%b") for m in series["Month"]]
data.add_series("Sales", series["Sales"].tolist())
data.add_series("Target", series["Target"].tolist())
frame = slide.shapes.add_chart(XL_CHART_TYPE.COLUMN_CLUSTERED, Inches(0.6), Inches(1.6),
Inches(8), Inches(5.2), data)
chart = frame.chart
chart.has_legend = True
chart.legend.position = XL_LEGEND_POSITION.BOTTOM
chart.legend.include_in_layout = False
chart.plots[0].series[0].format.fill.solid()
chart.plots[0].series[0].format.fill.fore_color.rgb = INDIGO
chart.plots[0].series[1].format.fill.solid()
chart.plots[0].series[1].format.fill.fore_color.rgb = GREY
chart.value_axis.has_major_gridlines = True
chart.value_axis.tick_labels.font.size = Pt(12)
chart.category_axis.tick_labels.font.size = Pt(12)
print(len(prs.slides), "slides so far")
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 name 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.
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.
Step 4 — Add a table of the figures
A small table beside the chart gives the exact numbers. Format them as text in Python — PowerPoint tables hold text, not numbers with formats:
from pptx.enum.text import PP_ALIGN
for slide, (region, row) in zip(list(prs.slides)[1:], summary.iterrows()):
shape = slide.shapes.add_table(4, 2, Inches(9), Inches(2.2), Inches(3.6), Inches(2))
table = shape.table
cells = [("Sales", f"{row.Sales:,.0f}k"), ("Target", f"{row.Target:,.0f}k"),
("Variance", f"{row.Variance:+,.0f}k"), ("6-month growth", f"{row.SixMonthGrowth:+.0%}")]
for r, (label, value) in enumerate(cells):
table.cell(r, 0).text = label
table.cell(r, 1).text = value
for c in (0, 1):
para = table.cell(r, c).text_frame.paragraphs[0]
para.runs[0].font.size = Pt(16)
if c == 1:
para.alignment = PP_ALIGN.RIGHT
prs.save("regional_review.pptx")
print("saved", len(prs.slides), "slides")
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 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.
Step 5 — Check the deck
Read the deck back to confirm its structure, then render it to PDF with LibreOffice to look at it:
check = Presentation("regional_review.pptx")
for i, slide in enumerate(check.slides):
kinds = sorted({s.shape_type for s in slide.shapes if s.shape_type is not None}, key=str)
print(i, slide.shapes.title.text if slide.shapes.title else "", [k.name for k in kinds])
soffice -env:UserInstallation=file://$PWD/lo_profile --headless --convert-to pdf regional_review.pptx > /dev/null 2>&1
python -c "from pypdf import PdfReader; print(len(PdfReader('regional_review.pdf').pages), 'pages rendered')"
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.
Performance notes
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 .pptx to PDF in a single soffice call, exactly as for workbooks.
Common pitfalls
- Pasting pictures instead of native charts. Use
add_chartwithCategoryChartDataso charts stay editable and sharp. - Layouts by index in a corporate template. Index 5 is "Title Only" in the default template, not necessarily in yours; find layouts by name.
- Numbers in tables. Table cells are text; format them in Python.
- Headline text typed by hand. Compute it from the same data as the chart so it cannot drift.
- 4:3 slides. The default presentation is 4:3; set 16:9 dimensions or use a 16:9 template.
Conclusion
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 CategoryChartData 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.
Frequently asked questions
How do I create PowerPoint slides from Excel data with Python? 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").
Can python-pptx create charts? 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.
How do I use my company's PowerPoint template? 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.
How do I check the deck without PowerPoint? Read it back with python-pptx to count slides and shapes, and convert it to PDF with headless LibreOffice to look at the pages.
Related
- Up one level: Generating Excel Reports from Templates — template-driven report generation.
- Generate Word Documents from Excel Rows — the same idea for Word.
- Build a Dashboard Sheet with Charts from Multiple Tabs — the workbook version of the deck.
- Build a Weekly Sales Report — the summaries these slides are built on.
More on report layouts & templates
- GuideBuilding Finance and Operations Reports in Excel with Python
- How-toAllocate Shared Costs Across Departments with Python
- How-toBuild a Bank Reconciliation Report with Python and Excel
- How-toBuild a Bill of Materials Cost Rollup
- How-toBuild a Break-Even Analysis in Excel with Python
- ComparisonBuild a Budget vs Actual Variance Report in Excel with Python