はじめに:スプレッドシート自動化の重要性

ExcelとCSVファイルは、ビジネス環境で最も広く使用されているデータ形式です。毎日膨大なデータがスプレッドシート形式で生成、処理、報告されています。このような繰り返し作業をPythonで自動化すれば、時間を節約し、人為的ミスを減らすことができます。

今回の第5編では、Pythonのcsvモジュール、pandas、openpyxlライブラリを活用してCSVとExcelファイルを効率的に処理する方法を学びます。基本的なファイルの読み書きからセルスタイリング、チャート作成、大容量ファイル処理まで、実務ですぐに活用できる技術を取り上げます。

1. CSVファイル処理の基礎(csvモジュール)

Pythonの組み込みcsvモジュールは、追加インストールなしでCSVファイルを処理できます。

1.1 CSVファイルの読み込み

import csv

# 基本的なCSV読み込み
with open('data.csv', 'r', encoding='utf-8') as file:
    reader = csv.reader(file)
    for row in reader:
        print(row)  # 各行はリストとして返される

# 辞書として読み込み(ヘッダーベース)
with open('data.csv', 'r', encoding='utf-8') as file:
    reader = csv.DictReader(file)
    for row in reader:
        print(row['名前'], row['年齢'])  # 列名でアクセス

# 区切り文字の指定
with open('data.tsv', 'r', encoding='utf-8') as file:
    reader = csv.reader(file, delimiter='\t')  # タブ区切り
    for row in reader:
        print(row)

1.2 CSVファイルの書き込み

import csv

# 基本的なCSV書き込み
data = [
    ['名前', '年齢', '都市'],
    ['田中太郎', 25, '東京'],
    ['山田花子', 30, '大阪'],
    ['佐藤次郎', 28, '名古屋']
]

with open('output.csv', 'w', encoding='utf-8-sig', newline='') as file:
    writer = csv.writer(file)
    writer.writerows(data)  # 複数行を一度に書き込み

# 辞書として書き込み
data_dict = [
    {'名前': '田中太郎', '年齢': 25, '都市': '東京'},
    {'名前': '山田花子', '年齢': 30, '都市': '大阪'},
    {'名前': '佐藤次郎', '年齢': 28, '都市': '名古屋'}
]

with open('output_dict.csv', 'w', encoding='utf-8-sig', newline='') as file:
    fieldnames = ['名前', '年齢', '都市']
    writer = csv.DictWriter(file, fieldnames=fieldnames)
    writer.writeheader()  # ヘッダーを書き込み
    writer.writerows(data_dict)
Tip: WindowsでExcelでCSVファイルを開く際に日本語が文字化けするのを防ぐには、encoding='utf-8-sig'を使用してください。BOM(Byte Order Mark)が追加され、Excelで正しく認識されます。

2. pandasでCSVを扱う

pandasはデータ分析に最適化されたライブラリで、CSVファイル処理に非常に強力な機能を提供します。

2.1 pandasのインストールと基本使用法

# インストール
pip install pandas
import pandas as pd

# CSV読み込み
df = pd.read_csv('data.csv', encoding='utf-8')

# 基本情報の確認
print(df.head())        # 最初の5行
print(df.info())        # データ型と欠損値情報
print(df.describe())    # 統計サマリー

# 特定の列を選択
print(df['名前'])           # 単一列
print(df[['名前', '年齢']])  # 複数列

# 条件フィルタリング
adults = df[df['年齢'] >= 20]
tokyo_people = df[df['都市'] == '東京']

2.2 データの前処理と加工

import pandas as pd

df = pd.read_csv('sales_data.csv')

# 欠損値の処理
df.fillna(0, inplace=True)              # 0で埋める
df.dropna(subset=['必須列'], inplace=True)  # 特定列の欠損値行を削除

# 新しい列を追加
df['合計'] = df['単価'] * df['数量']
df['割引価格'] = df['合計'] * 0.9

# 列名の変更
df.rename(columns={'old_name': 'new_name'}, inplace=True)

# データのソート
df.sort_values(by='合計', ascending=False, inplace=True)

# グループ別集計
summary = df.groupby('カテゴリ').agg({
    '合計': 'sum',
    '数量': 'mean',
    '注文番号': 'count'
}).rename(columns={'注文番号': '注文数'})

# CSVとして保存
df.to_csv('processed_data.csv', index=False, encoding='utf-8-sig')

2.3 複数のCSVファイルの結合

import pandas as pd
import glob

# 同じ形式の複数CSVファイルを結合
csv_files = glob.glob('data/*.csv')
df_list = [pd.read_csv(f) for f in csv_files]
combined_df = pd.concat(df_list, ignore_index=True)

# 2つのDataFrameを結合(JOIN)
df1 = pd.read_csv('customers.csv')
df2 = pd.read_csv('orders.csv')

# 内部結合(両方にあるもののみ)
merged = pd.merge(df1, df2, on='顧客ID', how='inner')

# 外部結合(すべてのデータを含む)
merged_all = pd.merge(df1, df2, on='顧客ID', how='outer')

3. openpyxlでExcelファイルの作成/編集

openpyxlはExcelファイル(.xlsx)の作成と編集に特化したライブラリです。

3.1 インストールと基本使用法

# インストール
pip install openpyxl
from openpyxl import Workbook, load_workbook

# 新規ワークブックの作成
wb = Workbook()
ws = wb.active
ws.title = "売上データ"

# データ入力
ws['A1'] = '製品名'
ws['B1'] = '数量'
ws['C1'] = '単価'

# 複数行を追加
data = [
    ['ノートPC', 10, 120000],
    ['マウス', 50, 2500],
    ['キーボード', 30, 4500]
]
for row in data:
    ws.append(row)

# 保存
wb.save('sales.xlsx')

# 既存ファイルを開く
wb = load_workbook('sales.xlsx')
ws = wb.active

# セル値の読み取り
print(ws['A1'].value)
print(ws.cell(row=2, column=1).value)

# 範囲の読み取り
for row in ws.iter_rows(min_row=2, max_row=4, min_col=1, max_col=3):
    for cell in row:
        print(cell.value, end=' ')
    print()

3.2 セルスタイリング(フォント、色、罫線)

from openpyxl import Workbook
from openpyxl.styles import Font, Fill, PatternFill, Border, Side, Alignment

wb = Workbook()
ws = wb.active

# データ入力
headers = ['製品名', '数量', '単価', '合計']
for col, header in enumerate(headers, 1):
    ws.cell(row=1, column=col, value=header)

# フォントスタイル
bold_font = Font(bold=True, size=12, color='FFFFFF')
normal_font = Font(size=11)

# 背景色
header_fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
alt_fill = PatternFill(start_color='D9E2F3', end_color='D9E2F3', fill_type='solid')

# 罫線
thin_border = Border(
    left=Side(style='thin'),
    right=Side(style='thin'),
    top=Side(style='thin'),
    bottom=Side(style='thin')
)

# 配置
center_align = Alignment(horizontal='center', vertical='center')
right_align = Alignment(horizontal='right')

# ヘッダーにスタイルを適用
for col in range(1, 5):
    cell = ws.cell(row=1, column=col)
    cell.font = bold_font
    cell.fill = header_fill
    cell.border = thin_border
    cell.alignment = center_align

# データ行のスタイル
data = [
    ['ノートPC', 10, 120000],
    ['マウス', 50, 2500],
    ['キーボード', 30, 4500]
]

for row_idx, row_data in enumerate(data, 2):
    for col_idx, value in enumerate(row_data, 1):
        cell = ws.cell(row=row_idx, column=col_idx, value=value)
        cell.border = thin_border
        if row_idx % 2 == 0:
            cell.fill = alt_fill

# 列幅の調整
ws.column_dimensions['A'].width = 15
ws.column_dimensions['B'].width = 10
ws.column_dimensions['C'].width = 15
ws.column_dimensions['D'].width = 15

# 行の高さの調整
ws.row_dimensions[1].height = 25

wb.save('styled_sales.xlsx')

3.3 数式の挿入

from openpyxl import Workbook

wb = Workbook()
ws = wb.active

# データ入力
ws['A1'] = '数量'
ws['B1'] = '単価'
ws['C1'] = '合計'

data = [
    [10, 120000],
    [50, 2500],
    [30, 4500]
]

for row_idx, (qty, price) in enumerate(data, 2):
    ws.cell(row=row_idx, column=1, value=qty)
    ws.cell(row=row_idx, column=2, value=price)
    # 数式を挿入(合計 = 数量 * 単価)
    ws.cell(row=row_idx, column=3, value=f'=A{row_idx}*B{row_idx}')

# 合計数式
last_row = len(data) + 1
ws.cell(row=last_row + 1, column=1, value='合計')
ws.cell(row=last_row + 1, column=3, value=f'=SUM(C2:C{last_row})')

# 条件付き数式
ws['D1'] = 'ランク'
for row_idx in range(2, last_row + 1):
    ws.cell(row=row_idx, column=4,
            value=f'=IF(C{row_idx}>=10000000,"A",IF(C{row_idx}>=1000000,"B","C"))')

wb.save('formulas.xlsx')

4. チャートの作成

openpyxlを使用すると、Excelで様々なチャートをプログラム的に作成できます。

from openpyxl import Workbook
from openpyxl.chart import BarChart, LineChart, PieChart, Reference

wb = Workbook()
ws = wb.active

# データ入力
data = [
    ['月', '売上', '費用', '利益'],
    ['1月', 500000, 300000, 200000],
    ['2月', 600000, 350000, 250000],
    ['3月', 550000, 320000, 230000],
    ['4月', 700000, 400000, 300000],
    ['5月', 800000, 450000, 350000],
    ['6月', 750000, 420000, 330000]
]

for row in data:
    ws.append(row)

# 棒グラフ
bar_chart = BarChart()
bar_chart.type = "col"  # 縦棒
bar_chart.style = 10
bar_chart.title = "月別売上状況"
bar_chart.y_axis.title = "金額(円)"
bar_chart.x_axis.title = "月"

# データ範囲の指定
data_ref = Reference(ws, min_col=2, min_row=1, max_col=4, max_row=7)
categories = Reference(ws, min_col=1, min_row=2, max_row=7)

bar_chart.add_data(data_ref, titles_from_data=True)
bar_chart.set_categories(categories)
bar_chart.shape = 4  # チャートスタイル

ws.add_chart(bar_chart, "F2")  # チャートの位置

# 折れ線グラフ
line_chart = LineChart()
line_chart.title = "月別推移"
line_chart.style = 13
line_chart.y_axis.title = "金額"
line_chart.x_axis.title = "月"

line_chart.add_data(data_ref, titles_from_data=True)
line_chart.set_categories(categories)

ws.add_chart(line_chart, "F18")

# 円グラフ(6月のデータのみ)
pie_chart = PieChart()
pie_chart.title = "6月費用構成"

labels = Reference(ws, min_col=2, max_col=4, min_row=1)
pie_data = Reference(ws, min_col=2, max_col=4, min_row=7)

pie_chart.add_data(pie_data, titles_from_data=False)
pie_chart.set_categories(labels)

ws.add_chart(pie_chart, "N2")

wb.save('charts.xlsx')

5. 複数シートの操作

from openpyxl import Workbook, load_workbook

# 新規ワークブックに複数シートを作成
wb = Workbook()

# 最初のシート(デフォルトシート)
ws1 = wb.active
ws1.title = "サマリー"

# 追加シートの作成
ws2 = wb.create_sheet("第1四半期")
ws3 = wb.create_sheet("第2四半期")
ws4 = wb.create_sheet("第3四半期", 0)  # 先頭に挿入

# シート別にデータを入力
quarters = {
    '第1四半期': [['1月', 100], ['2月', 120], ['3月', 110]],
    '第2四半期': [['4月', 130], ['5月', 140], ['6月', 135]],
    '第3四半期': [['7月', 150], ['8月', 160], ['9月', 155]]
}

for sheet_name, data in quarters.items():
    ws = wb[sheet_name]
    ws.append(['月', '売上'])
    for row in data:
        ws.append(row)

# サマリーシートに各四半期の合計を参照
summary = wb['サマリー']
summary.append(['四半期', '総売上'])
summary.append(['第1四半期', '=SUM(\'第1四半期\'!B2:B4)'])
summary.append(['第2四半期', '=SUM(\'第2四半期\'!B2:B4)'])
summary.append(['第3四半期', '=SUM(\'第3四半期\'!B2:B4)'])

# シートの順序を変更
wb._sheets = [wb['サマリー'], wb['第1四半期'], wb['第2四半期'], wb['第3四半期']]

# シートのコピー
source = wb['第1四半期']
target = wb.copy_worksheet(source)
target.title = "第1四半期_バックアップ"

# シートの削除
# del wb['第1四半期_バックアップ']

wb.save('multi_sheets.xlsx')

# 既存ファイルのすべてのシートを巡回
wb = load_workbook('multi_sheets.xlsx')
for sheet_name in wb.sheetnames:
    ws = wb[sheet_name]
    print(f"シート: {sheet_name}")
    for row in ws.iter_rows(values_only=True):
        print(row)

6. Excelファイルの結合

import pandas as pd
from openpyxl import load_workbook
import glob

# pandasを使用したExcelファイルの結合
def merge_excel_files(file_pattern, output_file):
    """複数のExcelファイルを1つに結合"""
    excel_files = glob.glob(file_pattern)
    df_list = []

    for file in excel_files:
        df = pd.read_excel(file)
        df['元ファイル'] = file  # 出典を表示
        df_list.append(df)

    combined_df = pd.concat(df_list, ignore_index=True)
    combined_df.to_excel(output_file, index=False)
    print(f"結合完了: {len(excel_files)}ファイル -> {output_file}")

merge_excel_files('reports/*.xlsx', 'combined_report.xlsx')

# 複数シートを1つのシートに結合
def merge_sheets(input_file, output_file):
    """1つのExcelファイル内のすべてのシートを結合"""
    xl = pd.ExcelFile(input_file)
    df_list = []

    for sheet_name in xl.sheet_names:
        df = pd.read_excel(xl, sheet_name=sheet_name)
        df['シート名'] = sheet_name
        df_list.append(df)

    combined_df = pd.concat(df_list, ignore_index=True)
    combined_df.to_excel(output_file, index=False)

merge_sheets('multi_sheets.xlsx', 'merged_single_sheet.xlsx')

# 条件付き結合(特定の列を基準に)
def conditional_merge(files, key_column, output_file):
    """特定のキー列を基準に複数ファイルを結合"""
    base_df = pd.read_excel(files[0])

    for file in files[1:]:
        df = pd.read_excel(file)
        base_df = pd.merge(base_df, df, on=key_column, how='outer')

    base_df.to_excel(output_file, index=False)

conditional_merge(['customers.xlsx', 'orders.xlsx', 'payments.xlsx'],
                  '顧客ID', 'customer_full_data.xlsx')

7. 大容量ファイルの処理(チャンク処理)

数十万行以上の大容量ファイルを処理する際は、メモリ効率を考慮する必要があります。

import pandas as pd
from openpyxl import load_workbook
from openpyxl.utils.dataframe import dataframe_to_rows

# チャンク単位で大容量CSVを処理
def process_large_csv(input_file, output_file, chunk_size=10000):
    """大容量CSVをチャンク単位で処理"""
    processed_chunks = []

    for chunk in pd.read_csv(input_file, chunksize=chunk_size):
        # 各チャンクの処理
        chunk['処理済み'] = True
        chunk['合計'] = chunk['数量'] * chunk['単価']
        processed_chunks.append(chunk)
        print(f"処理された行: {len(chunk)}")

    # 結果を保存
    result_df = pd.concat(processed_chunks, ignore_index=True)
    result_df.to_csv(output_file, index=False)

# チャンクごとにフィルタリングして保存(メモリ節約)
def filter_large_csv(input_file, output_file, condition_func, chunk_size=10000):
    """大容量CSVから条件に合う行のみを抽出"""
    first_chunk = True

    for chunk in pd.read_csv(input_file, chunksize=chunk_size):
        filtered = chunk[condition_func(chunk)]

        if first_chunk:
            filtered.to_csv(output_file, index=False, mode='w')
            first_chunk = False
        else:
            filtered.to_csv(output_file, index=False, mode='a', header=False)

# 使用例:合計が100万円以上の行のみを抽出
filter_large_csv(
    'large_sales.csv',
    'high_value_sales.csv',
    lambda df: df['合計'] >= 1000000
)

# 大容量Excelファイルの読み込み(read_onlyモード)
def read_large_excel(file_path):
    """大容量Excelファイルをメモリ効率的に読み込み"""
    wb = load_workbook(file_path, read_only=True)
    ws = wb.active

    for row in ws.iter_rows(values_only=True):
        yield row  # ジェネレータで1行ずつ返す

    wb.close()

# 大容量Excelファイルの書き込み(write_onlyモード)
from openpyxl import Workbook

def write_large_excel(data_generator, output_file):
    """大容量データをExcelに効率的に書き込み"""
    wb = Workbook(write_only=True)
    ws = wb.create_sheet()

    for row in data_generator:
        ws.append(row)

    wb.save(output_file)

8. 実践:月次レポート自動生成

これまで学んだ内容を総合して、実務で使用できる月次販売レポート自動生成システムを作成してみましょう。

import pandas as pd
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Border, Side, Alignment
from openpyxl.chart import BarChart, LineChart, PieChart, Reference
from openpyxl.utils.dataframe import dataframe_to_rows
from datetime import datetime
import os

class MonthlyReportGenerator:
    """月次販売レポート自動生成クラス"""

    def __init__(self, data_file, output_dir='reports'):
        self.data_file = data_file
        self.output_dir = output_dir
        self.df = None
        self.wb = None

        # 出力ディレクトリの作成
        os.makedirs(output_dir, exist_ok=True)

        # スタイル定義
        self.header_font = Font(bold=True, size=11, color='FFFFFF')
        self.header_fill = PatternFill(start_color='2F5496', end_color='2F5496', fill_type='solid')
        self.border = Border(
            left=Side(style='thin'),
            right=Side(style='thin'),
            top=Side(style='thin'),
            bottom=Side(style='thin')
        )
        self.center_align = Alignment(horizontal='center', vertical='center')
        self.number_format = '#,##0'
        self.currency_format = '#,##0"円"'

    def load_data(self):
        """データのロード"""
        self.df = pd.read_csv(self.data_file, parse_dates=['注文日'])
        self.df['月'] = self.df['注文日'].dt.to_period('M')
        print(f"データロード完了: {len(self.df)}行")

    def create_summary_sheet(self, ws):
        """サマリーシートの作成"""
        ws.title = "サマリー"

        # タイトル
        ws['A1'] = '月次販売レポート'
        ws['A1'].font = Font(bold=True, size=16)
        ws.merge_cells('A1:E1')

        # 作成日時
        ws['A2'] = f'作成日時: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}'
        ws['A2'].font = Font(italic=True, size=10)

        # 月別サマリーデータ
        monthly_summary = self.df.groupby('月').agg({
            '注文番号': 'count',
            '数量': 'sum',
            '合計': 'sum'
        }).reset_index()
        monthly_summary.columns = ['月', '注文件数', '販売数量', '総売上']

        # テーブル開始位置
        start_row = 4
        headers = ['月', '注文件数', '販売数量', '総売上']

        # ヘッダー作成
        for col, header in enumerate(headers, 1):
            cell = ws.cell(row=start_row, column=col, value=header)
            cell.font = self.header_font
            cell.fill = self.header_fill
            cell.border = self.border
            cell.alignment = self.center_align

        # データ作成
        for row_idx, row in monthly_summary.iterrows():
            ws.cell(row=start_row + row_idx + 1, column=1, value=str(row['月']))
            ws.cell(row=start_row + row_idx + 1, column=2, value=row['注文件数'])
            ws.cell(row=start_row + row_idx + 1, column=3, value=row['販売数量'])
            cell = ws.cell(row=start_row + row_idx + 1, column=4, value=row['総売上'])
            cell.number_format = self.currency_format

            for col in range(1, 5):
                ws.cell(row=start_row + row_idx + 1, column=col).border = self.border

        # 合計行
        total_row = start_row + len(monthly_summary) + 1
        ws.cell(row=total_row, column=1, value='合計').font = Font(bold=True)
        ws.cell(row=total_row, column=2, value=f'=SUM(B{start_row+1}:B{total_row-1})')
        ws.cell(row=total_row, column=3, value=f'=SUM(C{start_row+1}:C{total_row-1})')
        ws.cell(row=total_row, column=4, value=f'=SUM(D{start_row+1}:D{total_row-1})')
        ws.cell(row=total_row, column=4).number_format = self.currency_format

        for col in range(1, 5):
            ws.cell(row=total_row, column=col).border = self.border

        # 列幅の調整
        ws.column_dimensions['A'].width = 12
        ws.column_dimensions['B'].width = 12
        ws.column_dimensions['C'].width = 12
        ws.column_dimensions['D'].width = 18

        # チャートの追加
        chart = BarChart()
        chart.type = "col"
        chart.style = 10
        chart.title = "月別売上状況"
        chart.y_axis.title = "売上(円)"

        data = Reference(ws, min_col=4, min_row=start_row,
                        max_row=start_row + len(monthly_summary))
        cats = Reference(ws, min_col=1, min_row=start_row + 1,
                        max_row=start_row + len(monthly_summary))

        chart.add_data(data, titles_from_data=True)
        chart.set_categories(cats)
        chart.shape = 4

        ws.add_chart(chart, "F4")

        return monthly_summary

    def generate_report(self):
        """レポート生成メイン関数"""
        print("レポート生成開始...")

        # データロード
        self.load_data()

        # ワークブック作成
        self.wb = Workbook()

        # シート作成
        ws_summary = self.wb.active
        self.create_summary_sheet(ws_summary)

        # ファイル保存
        today = datetime.now().strftime('%Y%m')
        output_file = os.path.join(self.output_dir, f'月次レポート_{today}.xlsx')
        self.wb.save(output_file)

        print(f"レポート生成完了: {output_file}")
        return output_file

# 使用例
if __name__ == "__main__":
    generator = MonthlyReportGenerator('sample_sales_data.csv')
    generator.generate_report()

まとめ

今回の編では、Pythonを活用したExcelとCSVファイル自動化の核心技術を学びました。

  • csvモジュール:シンプルなCSVファイルの読み書きに適しています
  • pandas:データ分析と前処理に強力な機能を提供します
  • openpyxl:Excelファイルの作成、スタイリング、チャート作成に特化しています

実務ではこの3つを状況に応じて組み合わせて使用します。単純なデータ処理はpandasで、最終的なレポートはopenpyxlでスタイリングする方式が一般的です。

次回の第6編では、メールと通知の自動化を扱います。今回作成したレポートを自動でメールで送信したり、Slack/Telegramで通知を送る方法を学ぶ予定です。