Excelの重複行を見つけ、確認用ファイルに分ける
四列が完全に一致する行を探し、元の行番号とともに新しいExcelファイルへまとめます。最初に出現した行も含めて、重複グループを確認します。
CSVの日付と作業時間を検証し、月別の記録数と合計時間をCSVと棒グラフに保存します。
この翻訳はAIで作成しました。コード、単位、数値は原文と併せて確認してください。各言語のネイティブ話者による校閲は、まだ完了していません。 한국어
対象読者繰り返し発生する月次の業務集計を、簡単なPythonコードで処理したい事務担当者
業務記録の一行を一つの作業記録と見なし、dateが属する月にhoursを加算します。record_countは行数であり、人数や勤務日数ではありません。hoursは十進表記の時間です。1.50は1時間30分であり、1時間50分ではありません。
同梱データは、2026年1~3月の架空の業務記録9件です。作業時間を測定したり成果を評価したりするツールではなく、入力済みの数値を月別に合計する練習です。
| 列 | 形式 | 確認基準 |
|---|---|---|
| record_id | R001のようなテキスト | ファイル内で一意であること |
| date | 2026-01-06 | YYYY-MM-DD形式の実在する日付 |
| task | 자료 정리 | 空の値は不可 |
| hours | 2.50 | 0以上の有限の数値で、小数点以下は二桁まで |
タイトル行、合計行、カンマを含む数値は入れません。IDの重複が見つかると、二重集計を防ぐため停止します。作業内容が同じでも、IDが異なれば別の記録として合算します。
python --version
python -m pip install -r requirements.txt
python example.pyインストールにはインターネット接続が必要です。コードのBASEは、現在のターミナル位置ではなくexample.pyの位置を基準に入力と結果のパスを決めます。
date.fromisoformatで日付を検証し、YYYY-MM形式の月キーを作ります。Decimalで時間を合計し、表には小数点以下二桁まで記録します。通常の浮動小数点数に変換するのはグラフを描くときだけです。Aggモードを使うため、グラフのウィンドウは開かず、PNGを保存します。
"""Summarize fictional work-log records by calendar month."""
import csv
import math
from collections import defaultdict
from datetime import date
from decimal import Decimal, InvalidOperation
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
BASE = Path(__file__).resolve().parent
HEADERS = ["record_id", "date", "task", "hours"]
def main():
destination = BASE / "outputs"
if destination.exists():
raise ValueError("outputs already exists; rename it before running again.")
totals = defaultdict(lambda: {"records": 0, "hours": Decimal("0")})
seen = set()
with (BASE / "work_log.csv").open(encoding="utf-8-sig", newline="") as file:
reader = csv.DictReader(file)
if reader.fieldnames != HEADERS:
raise ValueError(f"Expected headers: {HEADERS}")
for line, row in enumerate(reader, start=2):
if None in row or any(value is None or not value.strip() for value in row.values()):
raise ValueError(f"Row {line}: missing or extra field.")
if row["record_id"] in seen:
raise ValueError(f"Row {line}: duplicate record_id.")
seen.add(row["record_id"])
parsed_date = date.fromisoformat(row["date"])
if parsed_date.isoformat() != row["date"]:
raise ValueError(f"Row {line}: use YYYY-MM-DD dates.")
try:
hours = Decimal(row["hours"])
except InvalidOperation as error:
raise ValueError(f"Row {line}: hours must be a number.") from error
if not hours.is_finite() or hours < 0 or hours.as_tuple().exponent < -2:
raise ValueError(f"Row {line}: hours must be finite, nonnegative, with at most 2 decimals.")
month = parsed_date.strftime("%Y-%m")
totals[month]["records"] += 1
totals[month]["hours"] += hours
if not totals:
raise ValueError("No work records were found.")
months = sorted(totals)
figure, axis = plt.subplots(figsize=(8, 4.6), layout="constrained")
values = [float(totals[month]["hours"]) for month in months]
if not all(math.isfinite(value) and math.isfinite(value * 1.2) for value in values):
plt.close(figure)
raise ValueError("Monthly total is too large to plot as a finite number.")
bars = axis.bar(months, values, color="#2B6578", width=0.55)
axis.bar_label(bars, labels=[f"{value:.2f}" for value in values], padding=4)
axis.set(title="Monthly work hours | Fictional data", xlabel="Calendar month", ylabel="Hours")
axis.set_ylim(0, max(values) * 1.2 if max(values) > 0 else 1)
axis.spines[["top", "right"]].set_visible(False)
axis.set_axisbelow(True)
axis.grid(axis="y", alpha=0.2)
destination.mkdir(exist_ok=False)
with (destination / "monthly_summary.csv").open("x", encoding="utf-8-sig", newline="") as file:
writer = csv.writer(file)
writer.writerow(["month", "record_count", "total_hours"])
for month in months:
writer.writerow([month, totals[month]["records"], f"{totals[month]['hours']:.2f}"])
figure.savefig(destination / "monthly_hours.png", dpi=160)
plt.close(figure)
total_hours = sum((values["hours"] for values in totals.values()), Decimal("0"))
print(f"Records: {len(seen)}; months: {len(months)}; hours: {total_hours:.2f}")
print("Created outputs/monthly_summary.csv and outputs/monthly_hours.png.")
if __name__ == "__main__":
try:
main()
except (OSError, ValueError, csv.Error) as error:
raise SystemExit(f"Stopped: {error}") from error
outputs/monthly_summary.csvとoutputs/monthly_hours.pngが作られます。グラフは入力のある月だけを、年・月の順に表示します。軸を英語にしたのは、韓国語フォントを別途インストールせずに実行するためです。
| month | record_count | total_hours |
|---|---|---|
| 2026-01 | 3 | 6.50 |
| 2026-02 | 3 | 7.50 |
| 2026-03 | 3 | 9.50 |
| 確認用の総合計 | 9 | 23.50 |

| 問題 | 修正方法 |
|---|---|
| duplicate record_id | 同じ記録を二回入力していないか確認します。 |
| day is out of range / Invalid isoformat | 2026-02-30のような存在しない日付や、日付の形式を確認します。 |
| hours must be a number | 時間には2.5のような数値だけを入力します。時間の単位や桁区切りのカンマは除いてください。 |
| hours must be finite… | 負の値、NaN、Infinity、小数点以下三桁以上の値がないか確認します。 |
| 韓国語の文字が正しく表示されない | 元のCSVをUTF-8で保存し直します。 |
| outputs already exists | 前回の結果を保管してから、結果フォルダーの名前を変更します。 |
Windows 11 (10.0.26200)、CPython 3.12.14(64ビット)。openpyxl 3.1.5、Matplotlib 3.10.8。各例で使用するライブラリのバージョンはrequirements.txtで固定しています。
コード、入力データ、実行手順が含まれています。展開して、まずREADME.txtを読んでください。
サンプルZIPをダウンロードサンプルコード、ファイル名、入力キーは原文のままです。翻訳本文のコマンドと確認手順も併せて参照してください。
独自に作成した練習用資料 · 元のファイルを別に保管してから実行してください。
説明と例は独自に作成しました。関連する動作や概念は、以下の公式資料で確認できます。