PythonのpandasでCSV・Excelファイルを処理する:アプローチの比較と実践的な実装ガイド

Python tutorial - IT technology blog
Python tutorial - IT technology blog

シンプルな自動化スクリプトを書き始めた:監視システムから出力されたCSVファイルを読み込み、CPU使用率が80%を超えるサーバーをフィルタリングして、アラートを送信するものだ。最初は約50行で、標準のcsvモジュールを使って動作していた。その後、要件が徐々に増えていった——財務チームからのExcelファイルも処理する必要が出て、複数のソースからデータをマージし、月次レポートをエクスポートしなければならなくなった。スクリプトが500行に膨れ上がっても、まだ素のcsvを使い続けていた時、ようやく自分が間違った道を進んでいたと気づいた。

リファクタリングに丸3日かかった。それ以来、惰性でライブラリを選ぶことはやめた。この記事では実践的な問いに直接答えていく——「pandasとは何か」ではなく、いつ何を使うべきか、そして正しい使い方についてだ。

3つの主要アプローチを比較する

Pythonにはこの問題に対する主要なライブラリが3つある。それぞれ異なる場面に適している:

方法1:標準ライブラリのcsvモジュール

import csv

with open('servers.csv', newline='', encoding='utf-8') as f:
    reader = csv.DictReader(f)
    for row in reader:
        if float(row['cpu_usage']) > 80:
            print(row['hostname'])

追加インストール不要で、あらゆる環境で動作する。ただし、各行は純粋なPythonの辞書(dict)なので——10,000行のCPU平均を計算したければ、自分でループを書かなければならない。

方法2:pandasのread_csv / read_excel

import pandas as pd

df = pd.read_csv('servers.csv')
high_cpu = df[df['cpu_usage'] > 80]
print(high_cpu[['hostname', 'cpu_usage']])

同じ結果を6行ではなく3行で実現でき、そのままgroupやmerge、aggregateも使える。これは表形式データに関するほとんどのタスクで自分が使うアプローチだ。

方法3:openpyxl / xlrd を直接使用(Excelのみ)

from openpyxl import load_workbook

wb = load_workbook('report.xlsx')
ws = wb.active
for row in ws.iter_rows(min_row=2, values_only=True):
    hostname, cpu = row[0], row[1]
    print(hostname, cpu)

Excelのフォーマット操作——セルの色付け、セルの結合、グラフの追加——が必要な場合は、openpyxlが必須だ。pandasはExcelを読み込む際にスタイルを保持しない。

メリット・デメリットの分析

評価項目 csvモジュール pandas openpyxl
インストール 不要 pip install pandas pip install openpyxl
大規模CSV処理(100万行以上) 優秀(ストリーミング) 対応可(チャンク処理が必要) 対象外
計算・集計 自前で実装が必要 非常に強力 弱い
Excelフォーマットの保持 不可 不可
複数ファイルのマージ 手動 pd.concat / merge 手動
メモリ使用量 少ない 多め 中程度

pandasについて覚えておくべきこと:ファイル全体をRAMに読み込む。100MBのCSVファイルは実際には約300〜500MBを消費する——ディスク上の元のサイズの3〜5倍だ。200MB未満なら心配不要。GBクラスのファイルにはチャンク処理を使うか、csvモジュールに戻って1行ずつストリーミングする必要がある。

どのアプローチを選ぶべきか?

判断を4つのケースにまとめた:

  • 行単位で読み込み、シンプルな処理が必要で、ファイルが非常に大きい可能性があるcsvモジュールを使う
  • フィルタリング、計算、マージ、レポートのエクスポートが必要——500MB未満のファイルpandasを使う
  • Excelの読み書きとスタイル、色、グラフの保持が必要openpyxlを使う
  • 計算と美しいExcelエクスポートの両方が必要 → pandasでデータ処理、openpyxlでスタイリングを担当させる

あの2000行のスクリプトを振り返ると:pandasで最初から書き直せば400〜500行程度で済む。データが3列以上あり、何らかの計算が必要な場合は、後でリファクタリングするよりも最初からpandasを使う方がはるかにコストが低い。

pandasを使った実装ガイド

インストール

pip install pandas openpyxl xlrd
# openpyxl: .xlsx に必要
# xlrd: .xls(旧形式)に必要

データの読み込みと確認

import pandas as pd

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

# Excelを読み込む(特定のシート)
df_excel = pd.read_excel('report.xlsx', sheet_name='January')

# クイックチェック
print(df.shape)        # (行数, 列数)
print(df.dtypes)       # 各列のデータ型
print(df.head(5))      # 先頭5行
print(df.isnull().sum()) # 各列のnull値をカウント

データのフィルタリングと処理

# 行をフィルタリング
high_cpu = df[df['cpu_usage'] > 80]

# 複数条件でフィルタリング
alert_servers = df[(df['cpu_usage'] > 80) & (df['status'] == 'running')]

# 特定の列を選択
result = df[['hostname', 'cpu_usage', 'memory_usage']]

# 計算列を追加
df['cpu_category'] = pd.cut(
    df['cpu_usage'],
    bins=[0, 50, 80, 100],
    labels=['normal', 'warning', 'critical']
)

# グループ化と集計
summary = df.groupby('datacenter').agg({
    'cpu_usage': ['mean', 'max'],
    'hostname': 'count'
}).round(2)

データクレンジング——見落とされがちなステップ

# 重要な列にnull値がある行を削除
df = df.dropna(subset=['hostname', 'cpu_usage'])

# nullをデフォルト値で置換
df['memory_usage'] = df['memory_usage'].fillna(0)

# データ型を正規化
df['cpu_usage'] = pd.to_numeric(df['cpu_usage'], errors='coerce')
df['date'] = pd.to_datetime(df['date'], format='%Y-%m-%d')

# 重複行を削除
df = df.drop_duplicates(subset=['hostname'])

複数ファイルのマージ

import glob

# ディレクトリ内の全CSVをマージ
all_files = glob.glob('/data/logs/*.csv')
df_all = pd.concat([pd.read_csv(f) for f in all_files], ignore_index=True)

# キーで2つのDataFrameをJOIN(SQL JOINと同様)
df_merged = pd.merge(
    df_servers,
    df_owners,
    on='hostname',
    how='left'  # left, right, inner, outer
)

CSVとExcelへのエクスポート

# CSVに書き込む
df.to_csv('output.csv', index=False, encoding='utf-8-sig')  # utf-8-sig: Excelで日本語を正しく読み込むため

# Excelに書き込む——複数シート
with pd.ExcelWriter('report.xlsx', engine='openpyxl') as writer:
    df_alert.to_excel(writer, sheet_name='Critical', index=False)
    summary.to_excel(writer, sheet_name='Summary', index=False)
    df_all.to_excel(writer, sheet_name='All Servers', index=False)

大容量ファイルの処理:チャンキング

GBクラスのログファイルはRAMに全部読み込めない。pandasはチャンク単位の読み込みをサポートしている:

chunk_size = 50000  # 1回あたり5万行
results = []

for chunk in pd.read_csv('large_log.csv', chunksize=chunk_size):
    # 各チャンクを処理
    filtered = chunk[chunk['level'] == 'ERROR']
    results.append(filtered)

# 結果をまとめる
df_errors = pd.concat(results, ignore_index=True)

この方法はメモリを安定させる——2GBをRAMに読み込む代わりに、一度に約200MBで済む。

自動化でよく使うパターン

あの2000行のスクリプトをリファクタリングした後、最もよく再利用する2つのヘルパーを抽出した:

import pandas as pd
from pathlib import Path
from datetime import datetime

def load_csv_safe(filepath: str, required_cols: list) -> pd.DataFrame:
    """CSVを読み込み、必須カラムをバリデートする。"""
    df = pd.read_csv(filepath)
    missing = set(required_cols) - set(df.columns)
    if missing:
        raise ValueError(f"Missing columns: {missing}")
    return df

def export_timestamped(df: pd.DataFrame, prefix: str, output_dir: str = ".") -> str:
    """ファイル名にタイムスタンプを付けてCSVにエクスポートする。"""
    ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    filename = f"{prefix}_{ts}.csv"
    filepath = Path(output_dir) / filename
    df.to_csv(filepath, index=False, encoding='utf-8-sig')
    return str(filepath)

# 使用例
df = load_csv_safe('servers.csv', ['hostname', 'cpu_usage'])
output = export_timestamped(df[df['cpu_usage'] > 80], 'high_cpu_alert')
print(f"Exported: {output}")

かつてスクリプトがfloat(row['cpu_usage'])の行で静かにクラッシュした——誰かが誤ったカラムヘッダーをCSVに貼り付けたからだ。load_csv_safe()を使えば、そのエラーは最初のステップで捕捉され、どのカラムが不足しているかを明示したメッセージとともに表示される。本番環境では、このようなフェイルファストなアプローチが、他のどんなパターンよりもデバッグ時間を節約できる。

Share: