こんにちは、Tech Samuraiです!
今回の記事は、一本のPythonスクリプトが、試行錯誤を経て、より賢く、より使いやすいツールへと「進化」していく物語です。
物語は、「手元にあるCSVファイルから、特定の列だけを抽出して、新しいCSVファイルとして保存したい」という、非常にシンプルな一つの要望から始まりました。この目的を達成するため、私はPythonの力を借りることにしました。
第一章:二つの道の提示
目の前には、二つの道が示されました。
- 道1:標準ライブラリ「csv」
Pythonが生まれながらに持つ、堅実で信頼性の高い道。追加の準備は不要で、Pythonの基本的な知識があれば誰でも歩むことができます。 - 道2:強力なライブラリ「pandas」
データ分析の世界で絶大な支持を得る、強力なライブラリ。まるで魔法のようにデータを操り、複雑な処理も驚くほど短いコードで実現できます。
第二章:選択と決断 – なぜPandasか
二つの道を比較検討した結果、その優位性は明白でした。
csv
モジュールは確かに確実ですが、処理が少し冗長になりがちです。一方、pandas
は、
- コードの簡潔さ: データの読み込み、列の抽出、ファイルへの保存が、それぞれほぼ1行で完結する。
- 可読性の高さ: 何をしているかが直感的に理解しやすい。
- 将来の拡張性: 今回は列抽出だけですが、将来的にデータの並べ替えや集計など、より高度な処理をしたくなった場合にも簡単対応できる。
「これは明らかにpandas
の方がスッキリしていて、未来的だ!」
私は迷わずpandas
の道を選択しました。コードはより洗練され、目的はスマートに達成されたのです。
第三章:新たなる課題 – ハードコーディングの壁
しかし、物語はここで終わりませんでした。完成したスクリプトには、一つの見過ごせない問題が潜んでいたのです。
# スクリプトの初期段階
input_csv_file = 'source_data.csv'
output_csv_file = 'extracted_data.csv'
target_headers = ['氏名', '年齢', '部署']
これらの設定が、スクリプトの中に直接書き込まれていました(ハードコーディング)。もし、別のCSVファイルでこのスクリプトを使いたくなったら?もし、抽出したい列を変更したくなったら?そのたびにコードを直接編集しなければなりません。これは非常に手間がかかるし、誤ってコードの他の部分を壊してしまう危険性もはらんでいました。
第四章:賢者の選択 – 設定ファイルの導入
この課題を解決するため、私はプログラミングの重要な原則に立ち返りました。
「プログラムのロジック(どう動くか)」と「設定(何を使うか)」を分離するのです。
解決策として「設定ファイル」を導入しました。
config.ini
の作成:
ユーザーが編集するための、シンプルな設定ファイルを用意しました。Pythonのコードを触ることなく、入力ファイル名、出力ファイル名、抽出したい列を自由に変更できます。[SETTINGS] input_csv_file = csv_column_extractor/source_data.csv output_csv_file = csv_column_extractor/extracted_data_pandas.csv target_headers = 氏名,年齢,部署
configparser
の活用:
Pythonスクリプト側では、標準ライブラリconfigparser
を使い、このconfig.ini
の内容を読み込むように改良しました。
これにより、私のスクリプトは最終形態へと進化しました。もはや、ユーザーはPythonのコードを一行も触る必要はありません。config.ini
という名の設計図を書き換えるだけで、あらゆるCSVファイルに対応できる、柔軟で再利用性の高いツールが誕生したのです。
後日談:新たなる敵 – WindowsとExcelの「文字化け」
完成したと思ったツールでしたが、Windows環境のユーザーから「出力されたCSVをExcelで開くと、日本語が文字化けする」という報告が。これは、Python開発者(特にMacやLinuxユーザー)がよく遭遇する、根深い問題です。
原因:
- Python (Pandas) の出力: 私のスクリプトは、国際標準であるUTF-8という文字コードでCSVファイルを出力していました。これは正しい挙動です。
- Windows版Excelの挙動: 一方、Windows版のExcelは、CSVファイルをダブルクリックで開く際、古い日本語標準であるShift_JISという文字コードだと想定して開こうとします。
UTF-8で書かれた手紙を、Shift_JISのルールで無理やり読もうとするため、文字化けが発生していたのです。
対策:
解決策は、Excelに「このファイルはUTF-8で書かれていますよ」と正しく教えてあげることです。そのための最も簡単な方法は、BOM (Byte Order Mark) 付きのUTF-8でファイルを出力することです。Pandasでは、encoding
に `utf-8-sig` を指定するだけで、このBOM付きUTF-8ファイルを簡単に作成できます。
完成したツールとコード(改良版)
こうして、単なる目的達成のためのコードは、クロスプラットフォームの問題も解決した、誰でも安全かつ簡単に使える「賢いスクリプト」へと昇華しました。
この開発の物語を経て完成したツールの全コードは、以下のGitHubリポジトリで公開しています。
- GitHubリポジトリ: csv-tool
# 最終的なスクリプト (extract_csv.py)
import pandas as pd
import configparser
import sys
def load_config(filename='csv_column_extractor/config.ini'):
"""設定ファイル(config.ini)を読み込む"""
config = configparser.ConfigParser()
if not config.read(filename, encoding='utf-8'):
print(f"エラー: 設定ファイル '{filename}' が見つからないか、空です。")
sys.exit(1)
settings = config['SETTINGS']
headers_str = settings.get('target_headers', '')
headers_list = [header.strip() for header in headers_str.split(',') if header.strip()]
return {
'input': settings.get('input_csv_file', ''),
'output': settings.get('output_csv_file', ''),
'headers': headers_list
}
def extract_columns_with_pandas(input_filename, output_filename, headers_to_keep):
"""
pandasを使用して、CSVファイルから指定された列を抽出します。
"""
if not input_filename or not output_filename or not headers_to_keep:
print("エラー: 設定ファイルの値が不足しています。")
return
try:
df = pd.read_csv(input_filename, encoding='utf-8')
existing_headers = [h for h in headers_to_keep if h in df.columns]
missing = set(headers_to_keep) - set(existing_headers)
if missing:
print(f"⚠️ 警告: 次のヘッダーはCSVファイルに存在しませんでした: {', '.join(missing)}")
if not existing_headers:
print("エラー: 抽出対象のヘッダーがCSVファイルに一つも存在しません。")
return
df_extracted = df[existing_headers]
# ▼▼▼ ここを修正しました ▼▼▼
# Excelで日本語が文字化けしないように、BOM付きUTF-8(utf-8-sig)で出力
df_extracted.to_csv(output_filename, index=False, encoding='utf-8-sig')
# ▲▲▲ 変更ここまで ▲▲▲
print(f"✅ 処理が完了しました。'{output_filename}' に結果を保存しました。")
except FileNotFoundError:
print(f"エラー: 入力ファイル '{input_filename}' が見つかりません。")
except Exception as e:
print(f"予期せぬエラーが発生しました: {e}")
if __name__ == '__main__':
config = load_config()
extract_columns_with_pandas(
input_filename=config['input'],
output_filename=config['output'],
headers_to_keep=config['headers']
)
コメント