Pythonで選挙データを分析してみよう!①〜埼玉投票情報 選挙区情報 読み込み編〜
このシリーズでは、2025/07/21に行われた参院選挙結果を分析するためのプログラムを、一つずつ紐解いて解説していきます。
作成したコードは Github に公開しているので、興味があれば、見てみてください。
※コードは、予告なく、改変/削除されることがあります。
前回は比較的シンプルなCSVを扱いましたが、今回はより実践的で手ごわい、複数シートにまたがるExcel形式の開票結果データを綺麗に整形するプログラム cleanup_voting_info_by_electoral_district.py を見ていきたいと思います。
元データは「構造が複雑なExcelファイル」
まず、今回処理するファイルがどんなものか見てみましょう。このデータはExcelファイルで、複数のシートに分かれています。

プログラムで読み込むとこんな感じです。
令和07年07月20日執行,,,,,,,,,,,,,,,,,,,参投1表
参議院埼玉県選出議員選挙 投票調べ(国内+在外),,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,
結果,,,,,,,,,,,,,,,,,,,埼玉県選挙管理委員会
市区町村等,有権者数,,,投票者数,,,棄権者数,,,投票率(%),投票率(%),投票率(%),順位,,前回(R04.07)投票率(%),,,確定時刻,
,男,女,計,男,女,計,男,女,計,男,女,平均,,,男,女,平均,時,分
県 計,3034433.0,3092955.0,6127388.0,1751489.0,1733801.0,3485290.0,1282944.0,1359154.0,2642098.0,57.72,56.06,56.88,,,50.57,49.94,50.25,1.0,54.0
市 計,2832752.0,2889668.0,5722420.0,1634921.0,1620383.0,3255304.0,1197831.0,126928...ぱっと見ただけでも、以下のような多くの課題があることがわかります。
- ファイルの先頭に、日付やタイトルといった分析に不要な情報が複数行ある。
- ヘッダー(列名)が複数行にわたっており、どこからが本当のデータか分かりにくい。
- 「県 計」「市 計」のような集計行がデータの中に混ざっている。
- データが複数のシートに分割されている。
このような「人間には分かりやすいが、機械には優しくない」データを、どうやってプログラムで自動的に整形していくかが今回のテーマです。
いざ、データクレンジング!
この複雑なExcelファイルを、Pythonとpandasを使って、機械が読みやすい綺麗なデータに整形していくプロセスをコードと共に見ていきましょう。
Step 1: Excelファイルの全シートを読み込む
まず、Excelファイルを扱うところから始めます。
import pandas as pd
import os
# Excelファイルを指定
# ※実際のコードでは `current_dir` が定義されています
file_path = os.path.join(current_dir, "埼玉投票情報_選挙区.xls")
# Excelファイル内の全シート名を取得して、それぞれをデータフレームとして読み込む
excel_file = pd.ExcelFile(file_path, engine='xlrd')
target_sheets = excel_file.sheet_names
sheet_data = {}
for sheet_name in target_sheets:
df = pd.read_excel(file_path, sheet_name=sheet_name, engine='xlrd')
sheet_data[sheet_name] = dfpd.read_excel でシートを一つずつ読み込むこともできますが、今回は pd.ExcelFile を使っています。これを使うと、最初にファイル全体の情報を読み込み、sheet_names でシート名の一覧を取得できます。その後、ループ処理で各シートを効率的に読み込んで、辞書 sheet_data に格納していきます。
Step 2: このスクリプトの肝!不要な行を「動的に」見つけて削除
ここが今回のスクリプトで最も重要なポイントです。データの先頭に不要な行が何行あるか、ファイルによってまちまちですよね。そこで、「この行は不要な行だ」と判定する関数を作って、動的に削除します。
def is_header_or_unwanted_row(row_data):
"""
行がヘッダーや不要な行かどうかを判定する関数
"""
row_str = ' '.join([str(val) for val in row_data if pd.notna(val)])
# タイトル行やヘッダー行に含まれがちなキーワードをリストアップ
title_patterns = ['参議院議員選挙', '選挙管理委員会', '執行']
header_patterns = ['候補者名', '市区町村', '得票数', '投票率']
unit_patterns = ['(票)', '計', 'Unnamed']
# いずれかのキーワードが含まれていればTrueを返す
for pattern in title_patterns + header_patterns + unit_patterns:
if pattern in row_str:
return True
return Falseデータフレームの各行に関数を適用し、不要な行を除外する
mask = []
for index, row in df.iterrows():
is_unwanted = is_header_or_unwanted_row(row.values)
mask.append(not is_unwanted)
df = df[mask].reset_index(drop=True)is_header_or_unwanted_row 関数がその主役です。この関数は、行のデータを文字列として連結し、あらかじめ定義しておいたキーワード(‘選挙’, ‘計’ など)が含まれているかをチェックします。含まれていれば「不要な行」と判定します。 この関数をデータフレームの全行に適用することで、ヘッダーやタイトル、集計行などを一網打尽に削除できるわけです。行数を決め打ちするより、はるかに柔軟で堅牢な方法ですね。
Step 3: 不要な列の掃除と列名の整理
行が綺麗になったら、次は列の番です。
# 空の列を削除
df = df.dropna(axis=1, how='all')
# Unnamed:で始まる空の列を削除
unnamed_cols = [col for col in df.columns if 'Unnamed:' in str(col)]
for col in unnamed_cols:
if df[col].isna().all():
df = df.drop(col, axis=1)
# 列名を分かりやすい日本語に付け替える
column_mapping = {
df.columns[0]: "市区町村名",
df.columns[1]: "男性有権者数",
df.columns[2]: "女性有権者数",
# ... 以下続く
}
df = df.rename(columns=column_mapping)まず dropna(axis=1, how='all') で、全ての値が空の列を削除します。さらに、pandasが自動でつけてしまう Unnamed: から始まる名前の列も、中身が空なら削除します。
最後に、クレンジング後のデータは列の順番が一定になっているはずなので、df.columns[0] のようにインデックスで列を指定して、市区町村名 のような分かりやすい名前に変更します。これで、誰が見ても分かりやすいデータになります。
整形後のデータがこちら
これらの複雑な処理を経ることで、あの読みにくかったExcelデータは、以下のような機械にとって非常に扱いやすい形式に生まれ変わりました。
,市区町村名,男性有権者数,女性有権者数,有権者数計,男性投票者数,女性投票者数,投票者数計,...
0,さいたま市西区,52,53,105,28,29,57,...
1,さいたま市北区,35,36,71,20,20,40,...
2,さいたま市大宮区,45,47,92,26,27,53,...
...(数値はダミーです)
各市区町村ごとの有権者数や投票者数といった情報が、1行にまとまった綺麗なデータになりました。ここまでくれば、あとは自由に分析を進めるだけです。
まとめ
今回は、複数シートを持つ複雑なExcelファイルを、プログラムで自動的に整形する方法を解説しました。
特に、キーワードを使って不要な行を動的に判定するアプローチは、様々なフォーマットの非構造化データを扱う際に非常に有効なテクニックです。
一見すると面倒なデータクレンジングですが、こうして一度スクリプトを組んでしまえば、同じ形式のデータが来ても一瞬で処理が完了します。地道な作業こそ、自動化の価値が最も輝く瞬間かもしれませんね。





