
Python 3: Deep Dive (Part 2 - Iterators, Generators): プロジェクト③(セクション7/14)
CSVファイルを1行ずつ読み込む遅延イテレータを作成し、メモリ使用量を最小限に抑えながらデータを処理する方法を示しています。
named_tupleとジェネレータを組み合わせて各チケットデータを適切なデータ型に変換し、defaultdictを使用して車種ごとの違反件数を効率的にカウントする実装を提供しています。
大規模データセット処理における実践的なPythonのベストプラクティスとして、エラー処理、データ検証、コード分割の手法を解説しています。
このブログ記事では、「Python 3:Deep Dive(パート2 - イテレータ、ジェネレータ)」コースのセクション7から実践的なプロジェクトを掘り下げていきます。NYC駐車チケットデータを処理し、車種別の違反件数を計算するための遅延イテレータを作成するために、Pythonジェネレータの使用方法を探ります。このプロジェクトは、大規模なデータセットを効率的に処理する際のジェネレータの力を示しています。
はじめに
大規模なデータセットを扱う場合、メモリ使用量を最小限に抑え、パフォーマンスを向上させるための効率的なデータ処理技術が必要です。Pythonジェネレータは、遅延評価を通じてそのような作業を処理する強力な方法を提供し、データセット全体をメモリにロードすることなく、一度に1項目ずつデータを処理します。
このプロジェクトでは、NYC駐車チケットデータセットを処理し、車種別の違反件数を計算するための遅延イテレータを作成します。組み込みのPythonモジュールと標準ライブラリツールを使用して目標を達成することに焦点を当てます。
背景情報
使用するデータセットは`nyc_parking_tickets_extract.csv`という名前のCSVファイルで、NYC駐車チケット記録の抽出データが含まれています。各行は以下のようなフィールドを持つ駐車チケットを表しています:
召喚番号
ナンバープレートID
登録州
プレートタイプ
発行日
違反コード
車体タイプ
車種
違反の説明
最初の行には列ヘッダーが含まれており、続く行にはチケットデータが含まれています。フィールドはカンマで区切られ、各行は改行文字`\n`で終わります。
サンプルデータ:
Summons Number,Plate ID,Registration State,Plate Type,Issue Date,Violation Code,Vehicle Body Type,Vehicle Make,Violation Description
4006478550,VAD7274,VA,PAS,10/5/2016,5,4D,BMW,BUS LANE VIOLATION
4006462396,22834JK,NY,COM,9/30/2016,5,VAN,CHEVR,BUS LANE VIOLATION
4007117810,21791MG,NY,COM,4/10/2017,5,VAN,DODGE,BUS LANE VIOLATION
...
私たちの目標は、ジェネレータを使用してこのデータを効率的に処理することです。
プロジェクトの目標
目標1:遅延イテレータの作成
CSVファイルを読み込み、各行を名前付きタプルとして生成する遅延イテレータを開発する
名前付きタプルの各フィールドが適切なデータ型(整数、日付、文字列など)を持つようにする
Pythonの組み込みモジュールと標準ライブラリ関数のみを使用する
ファイル全体をメモリにロードせず、1行ずつ処理する
目標2:車種別違反件数の計算
目標1で作成した遅延イテレータを使用して、各車種の駐車違反件数を計算する
結果を車種をキーとし、違反件数を値とする辞書に保存する
効率的なデータ構造や関数を使用してカウント処理を最適化する
目標1:遅延イテレータの作成
データの理解
データを処理する前に、構造を理解し、各フィールドに適切なデータ型を決定する必要があります。
フィールド分析:
召喚番号:整数
ナンバープレートID:文字列
登録州:文字列
プレートタイプ:文字列
発行日:日付
違反コード:整数
車体タイプ:文字列
車種:文字列
違反の説明:文字列
データの解析
列ヘッダーを読み込み、名前付きタプルで使用するために準備することから始めます。スペースをアンダースコアに置き換え、小文字に変換してヘッダーをクリーンアップします。
import csv
from collections import namedtuple
from datetime import datetime
# 列ヘッダーを読み込みクリーンアップする
with open('nyc_parking_tickets_extract.csv') as f:
reader = csv.reader(f)
headers = next(reader)
headers = [header.strip().replace(" ", "_").lower() for header in headers]
次に、文字列を適切なデータ型に変換し、潜在的なエラーや欠損値を処理する解析関数を定義します。
def parse_int(value, default=None):
try:
return int(value)
except ValueError:
return default
def parse_date(value, default=None):
date_format = '%m/%d/%Y'
try:
return datetime.strptime(value, date_format).date()
except ValueError:
return default
def parse_string(value, default=None):
cleaned = value.strip()
return cleaned if cleaned else default
遅延イテレータの実装
ファイルを1行ずつ読み込み、各行を解析し、必要なフィールドがすべて有効な場合に名前付きタプルを生成するジェネレータ関数を作成します。
def parking_tickets(filename):
Ticket = namedtuple('Ticket', headers)
with open(filename) as f:
reader = csv.reader(f)
next(reader) # ヘッダー行をスキップ
for row in reader:
parsed_row = [
parse_int(row[0]), # 召喚番号
parse_string(row[1]), # ナンバープレートID
parse_string(row[2], default=''), # 登録州
parse_string(row[3], default=''), # プレートタイプ
parse_date(row[4]), # 発行日
parse_int(row[5]), # 違反コード
parse_string(row[6], default=''), # 車体タイプ
parse_string(row[7]), # 車種
parse_string(row[8], default='') # 違反の説明
]
# 必要なフィールドがすべて存在するか確認
if all(field is not None for field in parsed_row):
yield Ticket(*parsed_row)
説明:
ジェネレータ関数:`parking_tickets`はファイルを遅延的に読み込みます。
名前付きタプル:`Ticket`は適切なフィールド名で各行を保存します。
解析:各フィールドは定義した関数を使用して解析されます。
検証:必要なフィールドが欠損しているか無効な行はスキップされます。
使用例:
filename = 'nyc_parking_tickets_extract.csv'
tickets = parking_tickets(filename)
for ticket in tickets:
print(ticket)
これにより、ファイル全体をメモリにロードすることなく、1行ずつ処理しながら各チケットを名前付きタプルとして出力します。
目標2:車種別違反件数のカウント
辞書の使用
遅延イテレータで生成されたチケットを反復処理し、各車種の出現回数をカウントします。
def count_violations_by_make(filename):
tickets = parking_tickets(filename)
violations = {}
for ticket in tickets:
make = ticket.vehicle_make
if make in violations:
violations[make] += 1
else:
violations[make] = 1
return violations
説明:
辞書:`violations`は車種ごとの違反件数を保存します。
カウント:各チケットに対して、対応する車種のカウントを増やします。
`defaultdict`による最適化
`collections`モジュールの`defaultdict`を使用してカウント処理を簡略化できます。これにより、キーを自動的にデフォルト値で初期化します。
from collections import defaultdict
def count_violations_by_make(filename):
tickets = parking_tickets(filename)
violations = defaultdict(int)
for ticket in tickets:
violations[ticket.vehicle_make] += 1
# 結果を件数の降順でソート
sorted_violations = dict(sorted(violations.items(), key=lambda item: item[1], reverse=True))
return sorted_violations
説明:
`defaultdict(int)`:存在しないキーを自動的に`0`で初期化します。
カウント:キーの存在確認なしに直接カウントを増やすことができます。
ソート:読みやすさのために、辞書を件数の降順でソートします。
使用例:
filename = 'nyc_parking_tickets_extract.csv'
violations_by_make = count_violations_by_make(filename)
for make, count in violations_by_make.items():
print(f"{make}: {count}")
サンプル出力:
TOYOT: 36
HONDA: 35
FORD: 34
NISSA: 29
CHEVR: 23
DODGE: 22
... (その他の車種)
結論
Pythonジェネレータを活用することで、潜在的に大きなCSVファイルを処理するためのメモリ効率の良い遅延イテレータを作成しました。このアプローチにより、メモリ使用量を最小限に抑えながら、1回に1レコードずつ処理することができます。`csv`、`collections`、`datetime`などの組み込みモジュールを使用して、データを適切な型に解析し、車種ごとの駐車違反件数を計算しました。
このプロジェクトは、特に大規模なデータセットやデータストリームを扱う際の、実世界におけるジェネレータの実用性を示しています。また、タスクを小さな関数に分割し、データ検証を効果的に処理するなどのクリーンコードプラクティスの重要性も強調しています。
参考文献
注意:提供されているコード例はデモンストレーション目的で簡略化されています。本番環境では、追加のエラー処理、データ検証、ログ記録が必要になる場合があります。