Created
          October 8, 2025 14:50 
        
      - 
      
- 
        Save allieus/c82e974a807075940e99728e71ea2f27 to your computer and use it in GitHub Desktop. 
    ABC부트캠프 Python 강의 - Excel 대시보드 자동 생성기 (xlwings)
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | # ABC부트캠프 Python 강의 - Excel 대시보드 자동 생성기 | |
| # xlwings와 Claude Code를 활용한 실습 | |
| import xlwings as xw | |
| import pandas as pd | |
| from pathlib import Path | |
| # 설정: 대시보드 색상 테마 | |
| THEME_COLORS = { | |
| "header_bg": "#2E5984", # 진한 파란색 (헤더 배경) | |
| "header_text": "#FFFFFF", # 흰색 (헤더 텍스트) | |
| "title_bg": "#4472C4", # 밝은 파란색 (제목 배경) | |
| "chart_color1": "#5B9BD5", # 차트 색상 1 | |
| "chart_color2": "#ED7D31", # 차트 색상 2 | |
| "chart_color3": "#A5A5A5", # 차트 색상 3 | |
| "grid_line": "#D9D9D9" # 그리드 라인 | |
| } | |
| class ExcelDashboardGenerator: | |
| """Excel 대시보드 자동 생성 클래스""" | |
| def __init__(self, data_path): | |
| """ | |
| 초기화 | |
| Args: | |
| data_path: 엑셀 데이터 파일 경로 | |
| """ | |
| self.data_path = Path(data_path) | |
| self.app = None | |
| self.wb = None | |
| self.df = None | |
| def load_data(self): | |
| """데이터 로드""" | |
| print("📂 데이터 로드 중...") | |
| self.df = pd.read_excel(self.data_path) | |
| print(f"✅ {len(self.df)}개 행 로드 완료") | |
| return self.df | |
| def create_workbook(self, visible=True): | |
| """새 워크북 생성""" | |
| print("📝 Excel 워크북 생성 중...") | |
| self.app = xw.App(visible=visible) | |
| self.wb = self.app.books.add() | |
| print("✅ 워크북 생성 완료") | |
| def create_dashboard_template(self): | |
| """대시보드 템플릿 생성""" | |
| print("🎨 대시보드 템플릿 생성 중...") | |
| # 시트 이름 변경 | |
| dashboard_sheet = self.wb.sheets[0] | |
| dashboard_sheet.name = "대시보드" | |
| # 헤더 영역 (A1:H1) | |
| header_range = dashboard_sheet.range('A1:H1') | |
| header_range.merge() | |
| header_range.value = "🎮 글로벌 게임 판매 대시보드" | |
| header_range.color = THEME_COLORS["header_bg"] | |
| header_range.api.Font.Color = 0xFFFFFF # 흰색 텍스트 | |
| header_range.api.Font.Size = 20 | |
| header_range.api.Font.Bold = True | |
| header_range.api.HorizontalAlignment = -4108 # 가운데 정렬 | |
| dashboard_sheet.range('A1').row_height = 40 | |
| # 차트 제목 영역 설정 | |
| chart_titles = { | |
| 'A3:D3': '📊 장르별 판매량', | |
| 'E3:H3': '🏆 Top 10 배급사', | |
| 'A13:D13': '📅 연도별 판매 추이', | |
| 'E13:H13': '🎯 플랫폼별 분포' | |
| } | |
| for cell_range, title in chart_titles.items(): | |
| title_cell = dashboard_sheet.range(cell_range) | |
| title_cell.merge() | |
| title_cell.value = title | |
| title_cell.color = THEME_COLORS["title_bg"] | |
| title_cell.api.Font.Color = 0xFFFFFF | |
| title_cell.api.Font.Size = 14 | |
| title_cell.api.Font.Bold = True | |
| title_cell.api.HorizontalAlignment = -4108 | |
| print("✅ 템플릿 생성 완료") | |
| def add_data_sheet(self): | |
| """데이터 시트 추가""" | |
| print("📊 데이터 시트 추가 중...") | |
| # 새 시트 추가 | |
| data_sheet = self.wb.sheets.add("원본데이터", after=self.wb.sheets[-1]) | |
| # 데이터 붙여넣기 | |
| data_sheet.range('A1').value = self.df | |
| # 헤더 스타일링 | |
| header_range = data_sheet.range(f'A1:{chr(65+len(self.df.columns)-1)}1') | |
| header_range.color = THEME_COLORS["header_bg"] | |
| header_range.api.Font.Color = 0xFFFFFF | |
| header_range.api.Font.Bold = True | |
| # 열 너비 자동 조정 | |
| data_sheet.autofit('c') | |
| print("✅ 데이터 시트 추가 완료") | |
| def create_genre_chart(self): | |
| """장르별 판매량 차트 생성""" | |
| print("📈 장르별 판매량 차트 생성 중...") | |
| # 장르별 판매량 집계 | |
| genre_sales = self.df.groupby('장르')['판매량_백만'].sum().sort_values(ascending=False) | |
| # 피벗 시트 생성 | |
| pivot_sheet = self.wb.sheets.add("피벗_장르", after=self.wb.sheets[-1]) | |
| pivot_sheet.range('A1').value = "장르" | |
| pivot_sheet.range('B1').value = "판매량" | |
| pivot_sheet.range('A2').value = genre_sales.index.tolist() | |
| pivot_sheet.range('B2').value = [[v] for v in genre_sales.values.tolist()] | |
| # 차트 생성 | |
| dashboard_sheet = self.wb.sheets["대시보드"] | |
| chart = dashboard_sheet.charts.add(left=dashboard_sheet.range('A4').left, | |
| top=dashboard_sheet.range('A4').top, | |
| width=280, height=200) | |
| chart.set_source_data(pivot_sheet.range('A1:B' + str(len(genre_sales)+1))) | |
| chart.chart_type = 'column_clustered' # 세로 막대형 | |
| chart.api[1].HasTitle = False | |
| print("✅ 장르별 차트 완성") | |
| def create_publisher_chart(self): | |
| """Top 10 배급사 차트 생성""" | |
| print("📈 배급사 차트 생성 중...") | |
| # 배급사별 판매량 Top 10 | |
| publisher_sales = self.df.groupby('배급사')['판매량_백만'].sum().sort_values(ascending=False).head(10) | |
| # 피벗 시트 생성 | |
| pivot_sheet = self.wb.sheets.add("피벗_배급사", after=self.wb.sheets[-1]) | |
| pivot_sheet.range('A1').value = "배급사" | |
| pivot_sheet.range('B1').value = "판매량" | |
| pivot_sheet.range('A2').value = publisher_sales.index.tolist() | |
| pivot_sheet.range('B2').value = [[v] for v in publisher_sales.values.tolist()] | |
| # 차트 생성 | |
| dashboard_sheet = self.wb.sheets["대시보드"] | |
| chart = dashboard_sheet.charts.add(left=dashboard_sheet.range('E4').left, | |
| top=dashboard_sheet.range('E4').top, | |
| width=280, height=200) | |
| chart.set_source_data(pivot_sheet.range('A1:B11')) | |
| chart.chart_type = 'bar_clustered' # 가로 막대형 | |
| chart.api[1].HasTitle = False | |
| print("✅ 배급사 차트 완성") | |
| def create_year_chart(self): | |
| """연도별 판매 추이 차트 생성""" | |
| print("📈 연도별 추이 차트 생성 중...") | |
| # 연도별 판매량 집계 | |
| year_sales = self.df.groupby('발행년도')['판매량_백만'].sum().sort_index() | |
| # 피벗 시트 생성 | |
| pivot_sheet = self.wb.sheets.add("피벗_연도", after=self.wb.sheets[-1]) | |
| pivot_sheet.range('A1').value = "연도" | |
| pivot_sheet.range('B1').value = "판매량" | |
| pivot_sheet.range('A2').value = [[y] for y in year_sales.index.tolist()] | |
| pivot_sheet.range('B2').value = [[v] for v in year_sales.values.tolist()] | |
| # 차트 생성 | |
| dashboard_sheet = self.wb.sheets["대시보드"] | |
| chart = dashboard_sheet.charts.add(left=dashboard_sheet.range('A14').left, | |
| top=dashboard_sheet.range('A14').top, | |
| width=280, height=200) | |
| chart.set_source_data(pivot_sheet.range('A1:B' + str(len(year_sales)+1))) | |
| chart.chart_type = 'line' # 꺾은선형 | |
| chart.api[1].HasTitle = False | |
| print("✅ 연도별 차트 완성") | |
| def create_platform_chart(self): | |
| """플랫폼별 분포 차트 생성""" | |
| print("📈 플랫폼 차트 생성 중...") | |
| # 플랫폼별 게임 수 집계 | |
| platform_count = self.df['플랫폼'].value_counts().head(8) | |
| # 피벗 시트 생성 | |
| pivot_sheet = self.wb.sheets.add("피벗_플랫폼", after=self.wb.sheets[-1]) | |
| pivot_sheet.range('A1').value = "플랫폼" | |
| pivot_sheet.range('B1').value = "게임 수" | |
| pivot_sheet.range('A2').value = platform_count.index.tolist() | |
| pivot_sheet.range('B2').value = [[v] for v in platform_count.values.tolist()] | |
| # 차트 생성 | |
| dashboard_sheet = self.wb.sheets["대시보드"] | |
| chart = dashboard_sheet.charts.add(left=dashboard_sheet.range('E14').left, | |
| top=dashboard_sheet.range('E14').top, | |
| width=280, height=200) | |
| chart.set_source_data(pivot_sheet.range('A1:B' + str(len(platform_count)+1))) | |
| chart.chart_type = 'pie' # 원형 | |
| chart.api[1].HasTitle = False | |
| print("✅ 플랫폼 차트 완성") | |
| def save_dashboard(self, output_path): | |
| """대시보드 저장""" | |
| print(f"💾 대시보드 저장 중: {output_path}") | |
| self.wb.save(output_path) | |
| print("✅ 저장 완료!") | |
| def close(self): | |
| """워크북 닫기""" | |
| if self.wb: | |
| self.wb.close() | |
| if self.app: | |
| self.app.quit() | |
| def generate(self, output_path, visible=True): | |
| """ | |
| 대시보드 전체 생성 프로세스 | |
| Args: | |
| output_path: 출력 파일 경로 | |
| visible: Excel 창 표시 여부 | |
| """ | |
| try: | |
| # 1. 데이터 로드 | |
| self.load_data() | |
| # 2. 워크북 생성 | |
| self.create_workbook(visible=visible) | |
| # 3. 대시보드 템플릿 생성 | |
| self.create_dashboard_template() | |
| # 4. 데이터 시트 추가 | |
| self.add_data_sheet() | |
| # 5. 차트 생성 | |
| self.create_genre_chart() | |
| self.create_publisher_chart() | |
| self.create_year_chart() | |
| self.create_platform_chart() | |
| # 6. 저장 | |
| self.save_dashboard(output_path) | |
| print("\n🎉 대시보드 생성 완료!") | |
| print(f"📁 파일 위치: {output_path}") | |
| except Exception as e: | |
| print(f"❌ 오류 발생: {e}") | |
| raise | |
| finally: | |
| # Excel은 사용자가 직접 닫을 수 있도록 자동 종료하지 않음 | |
| pass | |
| # 메인 실행 | |
| if __name__ == "__main__": | |
| # 샘플 데이터 경로 | |
| data_path = "game_sales_data.xlsx" | |
| output_path = "게임판매_대시보드.xlsx" | |
| # 대시보드 생성 | |
| generator = ExcelDashboardGenerator(data_path) | |
| generator.generate(output_path, visible=True) | |
| print("\n💡 Tip: Excel 창을 직접 닫으시면 됩니다!") | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment