シンプルな自動化スクリプトを書き始めた:監視システムから出力された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()を使えば、そのエラーは最初のステップで捕捉され、どのカラムが不足しているかを明示したメッセージとともに表示される。本番環境では、このようなフェイルファストなアプローチが、他のどんなパターンよりもデバッグ時間を節約できる。
