Turn work logs into a monthly summary table and chart
Validate dates and work hours in a CSV, then save monthly record counts and total hours as a CSV and a bar chart.
Find rows with exact matches in all four columns and collect them in a new Excel file with their original row numbers. Review each duplicate group, including its first occurrence.
This translation was generated by AI. Check the code, units, and numbers against the original. Native-speaker review has not yet been completed for each language. 한국어
Who this is forOffice staff checking for duplicate entries after combining Excel data
In this example, rows are duplicates only when all four values match: order_id, customer, item, and quantity. Matching order numbers alone do not make rows duplicates. Spaces and letter case in strings are not changed automatically.
Before deleting anything, collect every row with matching contents in the Duplicates sheet, including the first occurrence. Rows that appear only once go into the Unique sheet so you can review all 8 input rows without omissions.
The sheet in orders.xlsx must be named Orders, and its first row must have the column names and order shown below. Adding columns also causes a header error. Rather than replacing the example with a real work file straight away, create a practice file by copying just the four required columns.
| Column | Purpose | Example |
|---|---|---|
| order_id | Order number as text | O-1001 |
| customer | Customer name as text | Alpha |
| item | Item as text | Sensor |
| quantity | Quantity as a positive integer | 2 |
Completely empty rows are skipped. The script stops if only some values are missing or a row contains a formula. Order numbers, customer names, and items must be non-empty strings. Numeric order numbers are not converted automatically; prepare them as text in the input file.
python --version
python -m pip install -r requirements.txt
python example.pyInstallation requires an internet connection. BASE in the code sets the input and output paths relative to example.py, not the current terminal folder.
The code opens the original with load_workbook in read-only mode and uses Counter to count how often each row occurs. It tracks the original Excel row numbers with enumerate and start=2. Results are saved to a new Workbook, so the original is not saved again.
"""Find repeated business rows. Keep the source workbook unchanged."""
from collections import Counter
from pathlib import Path
from openpyxl import Workbook, load_workbook
from openpyxl.styles import Font, PatternFill
BASE = Path(__file__).resolve().parent
HEADERS = ("order_id", "customer", "item", "quantity")
def main():
destination = BASE / "outputs"
if destination.exists():
raise ValueError("outputs already exists; rename it before running again.")
source = load_workbook(BASE / "orders.xlsx", read_only=True, data_only=False)
try:
if "Orders" not in source.sheetnames:
raise ValueError("Orders sheet is missing.")
worksheet = source["Orders"]
rows = list(worksheet.iter_rows(values_only=True))
if not rows or tuple(rows[0]) != HEADERS:
raise ValueError(f"Expected headers: {HEADERS}")
records = []
for row_number, row in enumerate(rows[1:], start=2):
if all(value is None for value in row):
continue
if any(value is None for value in row):
raise ValueError(f"Row {row_number}: missing value.")
if any(isinstance(value, str) and value.startswith("=") for value in row):
raise ValueError(f"Row {row_number}: formulas are not supported.")
if not all(isinstance(value, str) and value.strip() for value in row[:3]):
raise ValueError(f"Row {row_number}: first three fields must be text.")
if type(row[3]) is not int or row[3] <= 0:
raise ValueError(f"Row {row_number}: quantity must be a positive integer.")
records.append((row_number, tuple(row)))
if not records:
raise ValueError("No data rows were found.")
finally:
source.close()
counts = Counter(row for _, row in records)
repeated = [(number, row) for number, row in records if counts[row] > 1]
unique = [(number, row) for number, row in records if counts[row] == 1]
book = Workbook()
book.remove(book.active)
for name, selected in (("Duplicates", repeated), ("Unique", unique)):
sheet = book.create_sheet(name)
sheet.append(("source_row", *HEADERS, "occurrences"))
for number, row in selected:
sheet.append((number, *row, counts[row]))
sheet.freeze_panes = "A2"
sheet.auto_filter.ref = sheet.dimensions
sheet.sheet_view.showGridLines = False
for cell in sheet[1]:
cell.font = Font(name="Arial", bold=True, color="FFFFFF")
cell.fill = PatternFill("solid", fgColor="243B53")
for column, width in zip("ABCDEF", (14, 16, 18, 20, 14, 16)):
sheet.column_dimensions[column].width = width
destination.mkdir(exist_ok=False)
book.save(destination / "duplicate_review.xlsx")
book.close()
groups = sum(count > 1 for count in counts.values())
print(f"Input rows: {len(records)}; duplicate groups: {groups}")
print(f"Duplicate rows: {len(repeated)}; unique rows: {len(unique)}")
print("Created outputs/duplicate_review.xlsx (source unchanged).")
if __name__ == "__main__":
try:
main()
except (OSError, ValueError) as error:
raise SystemExit(f"Stopped: {error}") from error
The script creates two sheets, Duplicates and Unique, in outputs/duplicate_review.xlsx. source_row is the Excel row number in the original, and occurrences is the number of times the same contents appear across the full input.
| Item | Result for the included data |
|---|---|
| Input data rows | 8 |
| Duplicate groups | 2 |
| Duplicates rows | 4 (original rows 2, 3, 5, 8) |
| Unique rows | 4 (original rows 4, 6, 7, 9) |
The 4 rows in Duplicates do not mean that 4 rows should be removed. If you keep one row from each group, there are 2 extra entries. Decide which to keep only after checking the originals.
| Message or symptom | What to check |
|---|---|
| Orders sheet is missing | Set the original sheet name to Orders. |
| Expected headers | Check column names, their order, and any extra columns. |
| missing value / formulas are not supported | Check the reported original row and provide values rather than formulas. |
| quantity must be a positive integer | Enter a quantity that is an integer of 1 or more. |
| outputs already exists | Rename the previous result folder to keep it. |
| ModuleNotFoundError | Install the packages in requirements.txt using the same python you use to run the example. |
Windows 11 (10.0.26200), CPython 3.12.14 (64-bit). openpyxl 3.1.5, Matplotlib 3.10.8. The libraries used by each example are pinned in requirements.txt.
Includes code, input data, and instructions. Extract the ZIP and read README.txt first.
Download example ZIPExample code, filenames, and input keys remain unchanged. Refer to the commands and checking steps in the translated article as well.
Practice materials created for this site · Keep your originals separately before running.
The explanations and examples were written for this site. See the official sources below for the related behavior and concepts.