Excel & IT Info

아이엑셀러 닷컴, 엑셀러TV

Python

엑셀 데이터와 파이썬을 사용하여 프레젠테이션 자동화하기

권현욱(엑셀러) 2025. 1. 25. 15:37
반응형

들어가기 전에

인공지능(AI) 분야에서 엄청난 발전이 있었지만 여전히 전통적인 방식으로 처리해야 하는 것이 있습니다. 프레젠테이션을 준비하든 보고서를 작성하든, 매일 또는 매주 반복해야 하는 일상적인 일이 여전히 있습니다. 파이썬을 사용하여 이러한 작업을 자동화하는 한 가지 방법을 소개합니다.

권현욱(엑셀러) | 아이엑셀러 닷컴 대표 · Microsoft Excel MVP · Excel 솔루션 프로바이더 · 작가

이미지: 아이엑셀러 닷컴


이 글은 아래 기사 내용을 토대로 작성되었습니다만, 필자의 개인 의견이나 추가 자료들이 다수 포함되어 있습니다.

  • 원문: How to Automate PowerPoint Presentations Using Excel Data and Python
  • URL: https://medium.com/@esersaygin/how-to-automate-powerpoint-presentations-using-excel-data-and-python-2c6fae75fd87

준비 사항

구현에 들어가기 전에 필요한 파일에 대해 말씀드리겠습니다. 필요한 파일은 두 가지입니다. Excel 파일과 파워포인트 템플릿입니다. Excel 파일에는 파워포인트 프레젠테이션에서 사용할 구성과 데이터가 포함되어 있습니다. 파워포인트 템플릿에는 업데이트가 필요한 도형과 차트가 포함되어 있습니다.

Excel 및 PowerPoint 템플릿

먼저, PowerPoint 템플릿을 만들어 보겠습니다. 이 템플릿을 만들 때 가장 중요하게 고려해야 할 것은 도형의 이름입니다. 아래 스크린샷과 같이 '선택 창'에서 도형 이름을 확인할 수 있습니다. 도형 이름이 중요한 이유는 무엇일까요? 이 이름을 사용하여 도형 또는 차트를 업데이트할 것이기 때문입니다. 앞으로 진행하면서 더 명확하게 이해하실 수 있을 것입니다.

 

이미지: medium

 

“선택 창"으로 돌아가 보겠습니다. 메뉴에서 “선택 창”을 선택하면 화면 오른쪽에 개체 목록이 표시됩니다. 개체 이름을 더블클릭하고 변경할 수 있습니다. 이러한 개체 이름은 파일에 고유합니다. 즉, 다른 페이지에서 같은 이름을 가진 개체는 실제로는 같은 개체입니다.

 

이미지: medium

 

아래에서 이 프로젝트의 파워포인트 템플릿을 확인할 수 있습니다.

 

이미지: medium

 

이 글에서는 파이썬을 사용하여 차트의 모든 구성을 제어하지 않습니다만, 그렇다고 할 수 없다는 뜻은 아닙니다. 간단하게 설명하기 위해 이 글에서는 기능을 제한했습니다. 템플릿을 준비할 때 차트의 추세선과 범례를 추가하거나 꺾은선형 차트의 선에 부드러움을 추가하는 등의 작업을 수동으로 처리할 수 있습니다.

 

이제 Excel 파일로 이동합니다. 아래에서 파이썬 코드 스니펫의 Excel 시트 이름을 볼 수 있습니다. 이 이름은 동일해야 합니다. 이를 위해 Excel 파일에 '차트'와 '도형'이라는 이름의 두 개의 시트를 별도로 만듭니다.

이 시트에 테이블을 만들어야 합니다. 먼저 '도형' 시트를 보세요. 아래에서 파이썬으로 제어하고자 하는 기능을 볼 수 있습니다. 이것은 전적으로 여러분에게 달려 있습니다. 더 많은 기능을 추가할 수 있지만 현재 기능을 제거할 때는 주의하세요. 또한 코드가 아직 완벽하게 유연하지 않으므로 코드를 업데이트하는 것을 잊지 마세요.

 

이미지: medium

 

열에 대해 설명하겠습니다.

  • shape_name: 행의 이름입니다. 쓸모는 없지만 코드 때문에 비워두지 마세요. 아직 테스트하지 않았기 때문에 오류가 발생할 수 있습니다.
  • object_name: 시트에서 가장 중요한 부분입니다. 객체의 이름과 동일해야 합니다.
  • data_sheet: 데이터가 있어야 하는 시트입니다. 방금 “KPIs”라는 이름의 시트를 만들었습니다.
  • cell: data_sheet의 데이터 셀입니다.
  • font_size: 글꼴 크기
  • bold: 굵기
  • color: 16진수 코드
  • 정렬: 오른쪽, 왼쪽 또는 가운데
  • text_case: 대문자 또는 소문자
  • add_percentage: 참 또는 거짓
  • decimal_places: 소수점 이하 자릿수 값

 

차트 시트를 살펴보겠습니다.

 

이미지: medium

 

도형 시트와 동일하므로 모든 열에 대해 설명하지는 않겠습니다. 하지만 이 페이지의 열 중 몇 가지를 언급하고 싶습니다.

  • column_1, column_2: 예시에는 2개의 열만 사용했지만 더 많은 열을 사용할 수 있습니다(코드를 업데이트하는 것을 잊지 마세요). 예를 들어 꺾은선형 차트에 두 열을 모두 사용하려면 위의 예와 같이 열 이름을 언급하기만 하면 됩니다.


마지막으로 필자가 사용한 데이터에 대해 언급하고 싶습니다. 이 데이터는 2004년 이후 전 세계 및 터키를 대상으로 한 Google 트렌드의 '게임' 키워드에 대한 '시간 경과에 따른 관심도' 데이터입니다.

 

이미지: medium

 

모든 파일이 준비되었습니다. 코딩을 시작하겠습니다.

필수 라이브러리

아래에서 필요한 모든 라이브러리를 찾을 수 있습니다.

# Core libraries
import logging
import os
import openpyxl

# Presentation
from pptx import Presentation
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE
from pptx.util import Pt
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN

 

'로깅(logging)' 라이브러리를 포함시켰습니다. 이것은 매우 유용한 라이브러리입니다. 아래에서 이에 대한 설명을 확인할 수 있습니다.

로깅은 일부 소프트웨어가 실행될 때 발생하는 이벤트를 추적하는 수단입니다. 소프트웨어 개발자는 특정 이벤트가 발생했음을 나타내기 위해 코드에 로깅 호출을 추가합니다. 이벤트는 가변 데이터(즉, 이벤트가 발생할 때마다 잠재적으로 다른 데이터)를 선택적으로 포함할 수 있는 설명 메시지로 설명됩니다. 또한 이벤트에는 개발자가 이벤트에 부여하는 중요도가 있으며, 중요도는 레벨 또는 심각도라고도 할 수 있습니다.

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

 

프로젝트의 논리는 아주 간단합니다. Excel 표를 기반으로 PowerPoint 도형을 업데이트합니다. 다음은 도형 업데이트를 위해 필자가 만든 클래스입니다. 이 클래스에는 도형을 업데이트하기 위한 구성(글꼴, 색상, 정렬 등)이 포함되어 있습니다.

class Shape:
    def __init__(self, config):
        self.name = config['shape_name']
        self.object_name = config['object_name']
        self.data_sheet = config['data_sheet']
        self.cell = config['cell']
        self.font_size = config['font_size']
        self.font_type = config['font_type']
        self.bold = config['bold']
        self.color = config['color'].lstrip('#')  # Remove '#' if present
        self.alignment = config['alignment']
        self.text_case = config['text_case'].lower()
        self.add_percentage = config['add_percentage']
        self.decimal_places = config['decimal_places']  # New property for decimal places

    def update(self, shape, value):
        # Convert value to float if possible
        try:
            float_value = float(value)
            is_numeric = True
        except ValueError:
            float_value = value
            is_numeric = False

        # Format the value
        if is_numeric:
            if self.decimal_places is not None:
                # Format with specified decimal places
                text = f"{float_value:.{self.decimal_places}f}"
            else:
                # Use the original value if no decimal places specified
                text = str(value)
        else:
            text = str(value)

        # Apply text case
        if self.text_case == 'uppercase':
            text = text.upper()
        elif self.text_case == 'lowercase':
            text = text.lower()
        elif self.text_case == 'titlecase':
            text = text.title()

        # Add percentage sign if needed
        if self.add_percentage and is_numeric:
            text = f"{text}%"

        shape.text_frame.text = text
        paragraph = shape.text_frame.paragraphs[0]
        paragraph.font.size = Pt(self.font_size)
        paragraph.font.name = self.font_type
        paragraph.font.bold = self.bold
        paragraph.font.color.rgb = RGBColor.from_string(self.color)
        paragraph.alignment = getattr(PP_ALIGN, self.alignment.upper())

 

업데이트 함수는 지정된 값에 따라 도형을 적절한 형식으로 업데이트합니다. 이 함수에는 값이 숫자인지 여부를 확인하고, 값을 텍스트로 변환하는 등 이전에 사용했던 몇 가지 기능도 있습니다. 이러한 기능은 원하는 대로 변경할 수 있습니다.

Chart 클래스를 만듭니다. 이 클래스를 사용하면 모양과 마찬가지로 차트를 업데이트하게 됩니다.

class Chart:
    def __init__(self, config):
        self.name = config['chart_name']
        self.object_name = config['object_name']
        self.data_sheet = config['data_sheet']
        self.data_range = config['data_range']
        self.columns = [config[f'column_{i}'] for i in range(1, 3) if config[f'column_{i}'] and config[f'column_{i}'] != '-']
        self.chart_type = config['chart_type']
        self.colors = [config[f'color_{i}'] for i in range(1, 3) if config[f'color_{i}'] and config[f'color_{i}'] != '-']

    def update(self, chart, data):
        chart_data = CategoryChartData()
        categories = [row[0] for row in data[1:]]
        chart_data.categories = categories

        for i, col_name in enumerate(self.columns):
            col_index = data[0].index(col_name)
            series_values = [row[col_index] for row in data[1:]]
            chart_data.add_series(col_name, series_values)

        chart.replace_data(chart_data)

        # Update line colors
        if self.chart_type.lower() == 'line' and self.colors:
            for i, series in enumerate(chart.series):
                if i < len(self.colors):
                    color = RGBColor.from_string(self.colors[i].lstrip('#'))
                    series.format.line.color.rgb = color

 

다시 말하지만, 이것은 간단한 예시이므로 자신의 필요에 맞게 조정하는 방법을 찾아야 합니다. 또한 아직 충분히 유연하지 않다는 것을 잊지 마세요.

보고 클래스를 생성할 준비가 되었습니다. 아래에서 리포팅 시스템의 클래스를 볼 수 있습니다.

class ReportingSystem:
    def __init__(self, ppt_template, excel_file):
        self.ppt_template = ppt_template
        self.excel_file = excel_file
        self.charts = []
        self.shapes = []

    def load_configuration(self):
        wb = openpyxl.load_workbook(self.excel_file, data_only=True)
        
        charts_sheet = wb['Charts']
        for row in charts_sheet.iter_rows(min_row=2, values_only=True):
            if row[0]:
                chart_config = {
                    'chart_name': row[0],
                    'object_name': row[1],
                    'data_sheet': row[2],
                    'data_range': row[3],
                    'column_1': row[4],
                    'column_2': row[5],
                    'chart_type': row[6],
                    'color_1': row[7] if len(row) > 10 else None,
                    'color_2': row[8] if len(row) > 11 else None,
                }
                self.charts.append(Chart(chart_config))

        shapes_sheet = wb['Shapes']
        for row in shapes_sheet.iter_rows(min_row=2, values_only=True):
            if row[0]:
                shape_config = {
                    'shape_name': row[0],
                    'object_name': row[1],
                    'data_sheet': row[2],
                    'cell': row[3],
                    'font_size': int(row[4]),
                    'font_type': row[5],
                    'bold': row[6],
                    'color': row[7],
                    'alignment': row[8],
                    'text_case': row[9] if len(row) > 9 else 'default',
                    'add_percentage': row[10] if len(row) > 10 else False,
                    'decimal_places': int(row[11]) if len(row) > 11 and row[11] is not None else None 
                }
                self.shapes.append(Shape(shape_config))

        wb.close()
        logging.info(f"Loaded {len(self.charts)} charts and {len(self.shapes)} shapes from configuration")

 

이제 파워포인트 템플릿을 업데이트할 수 있습니다. 위에서 주의해야 할 가장 중요한 것은 Excel 시트 이름입니다. 이 이름은 코드에서와 동일해야 합니다. 그렇지 않으면 오류가 발생합니다.

 

이미지: medium

 

Excel 파일을 업로드하기 위해 openpyxl 라이브러리를 사용했습니다. 또한 값을 읽기 위해 “data_only=True”를 사용했습니다. 여기에 False를 사용하면 출력에 수식(셀에 수식이 포함된 경우)이 표시됩니다.

 

이미지: medium

 

코드의 마지막 부분은 실행하기 전에 템플릿과 데이터 파일을 확인합니다.

# path
template_path = r"/report_template.pptx"
data_path = r"/reporting_data.xlsx"
output_path = r"/output.pptx"

logging.info("Started report generation")

# check for the files
if not os.path.exists(template_path):
    raise FileNotFoundError(f"Template file is missing: {template_path}")

if not os.path.exists(data_path):
    raise FileNotFoundError(f"Data file is missing: {data_path}")

try:
    reporting_system = ReportingSystem(template_path, data_path)
    reporting_system.load_configuration()
    reporting_system.generate_report(output_path)
except Exception as e:
    logging.error(f"An error occurred while generating the report: {str(e)}")
    raise

 

출력 결과는 다음과 같습니다.

 

이미지: medium

 

클릭 한 번이면 됩니다. 정말 빠르죠.

 

마치며

Python을 통해 Powerpoint와 Excel을 연결하여 자동화를 만드는 방법에 대해 소개했습니다. 시간은 우리가 가진 가장 소중한 자산입니다. "반지의 제왕"에서 간달프가 한 말처럼, "우리가 해야 할 일은 우리에게 주어진 시간을 어떻게 사용할지 결정"하는 것뿐입니다.