엑셀·문서 업무

pandas groupby로 월별 요약표를 만들고 원본 합계와 확인하기

주문 단위 CSV를 월별 주문 수, 수량, 금액으로 요약한 뒤 그룹화 결과를 원본 합계와 대조합니다. 작은 합성 데이터로 모든 예상 결과를 손으로 확인할 수 있습니다.

목차 보기

이런 분께 맞아요CSV에서 반복 가능한 월별 요약을 만들면서 집계 과정에서 레코드가 빠지지 않았는지 명시적으로 확인하려는 사람을 위한 안내입니다.

준비할 것
  • Python 3.12와 해당 버전을 실행하는 터미널 명령이 준비되어 있어야 합니다.
  • python -m pip install pandas 명령으로 pandas가 설치되어 있어야 합니다.
  • 스크립트가 outputs 아래에 새 폴더를 만들 수 있는 작업 폴더가 필요합니다.
  • 원본 CSV에는 order_id, order_date, units, amount 열이 있어야 합니다.

01월별 집계 항목 정의하기

이 예제에서는 달력 월별로 주문 수, 전체 units, 전체 amount의 세 가지 지표를 계산합니다. 합성 원본은 주문 하나당 한 행이므로 이 데이터에서는 행 개수와 주문 수가 같습니다.

핵심은 원본과의 대조입니다. groupby 이후 월별 값을 다시 모두 더해 원본 합계와 비교합니다. 중간에 행이 잘못 필터링되거나 중복되거나 누락되어도 최종 요약값은 그럴듯해 보일 수 있습니다.

02합성 CSV 만들기

아래 데이터는 이 글을 위해 작성한 합성 데이터입니다. monthly_orders.csv로 저장하세요. 2026년 1월, 2월, 3월에 걸친 주문 7개가 있습니다.

csv
order_id,order_date,team,units,amount
O001,2026-01-05,North,2,40
O002,2026-01-20,South,3,60
O003,2026-02-02,North,1,25
O004,2026-02-18,South,4,100
O005,2026-02-28,North,2,50
O006,2026-03-03,South,5,125
O007,2026-03-15,North,1,30

원본 합계는 손으로 확인할 수 있습니다. 주문은 7개입니다. units 합계는 18이며 계산식은 2 + 3 + 1 + 4 + 2 + 5 + 1입니다. amount 합계는 430이며 계산식은 40 + 60 + 25 + 100 + 50 + 125 + 30입니다.

지표원본 합계
주문7
Units18
Amount430

03예상 월별 결과를 손으로 계산하기

1월에는 O001과 O002, 2월에는 O003, O004, O005, 3월에는 O006과 O007이 있습니다. 각 월을 더하면 아래 결과가 나옵니다.

monthordersunitsamount
2026-0125100
2026-0237175
2026-0326155

월별 값을 다시 더하면 원본과 일치합니다. 주문은 2 + 3 + 2 = 7, units는 5 + 7 + 6 = 18, amount는 100 + 175 + 155 = 430입니다.

04pandas로 월별 요약 만들고 검증하기

다음 스크립트를 monthly_summary_pandas.py로 저장하세요. 필수 열을 확인하고 누락되거나 중복된 주문 ID를 거부하며, 날짜를 하나의 명시적 형식으로 파싱하고 숫자 열을 변환합니다. 월별 집계와 원본 합계 대조가 통과한 뒤에만 결과를 저장합니다.

python
from pathlib import Path
import pandas as pd

SOURCE = Path("monthly_orders.csv")
OUTPUT_DIR = Path("outputs") / "monthly_summary_result"
OUTPUT = OUTPUT_DIR / "monthly_summary.csv"
REQUIRED = ["order_id", "order_date", "units", "amount"]


def main() -> None:
    if not SOURCE.is_file():
        raise FileNotFoundError(f"Source CSV not found: {SOURCE}")
    if OUTPUT_DIR.exists():
        raise FileExistsError(f"Output folder already exists: {OUTPUT_DIR}")

    data = pd.read_csv(SOURCE, dtype={"order_id": "string"})

    missing = [name for name in REQUIRED if name not in data.columns]
    if missing:
        raise ValueError(f"Missing required columns: {missing}")
    if data.empty:
        raise ValueError("Source CSV contains no data rows.")
    if data[REQUIRED].isna().any().any():
        raise ValueError("A required field contains a missing value.")
    if data["order_id"].str.strip().eq("").any():
        raise ValueError("order_id contains a blank value.")
    if data["order_id"].duplicated().any():
        raise ValueError("Duplicate order_id found.")

    data["order_date"] = pd.to_datetime(
        data["order_date"], format="%Y-%m-%d", errors="raise"
    )
    data["units"] = pd.to_numeric(data["units"], errors="raise")
    data["amount"] = pd.to_numeric(data["amount"], errors="raise")
    data["month"] = data["order_date"].dt.to_period("M").astype(str)

    summary = (
        data.groupby("month", as_index=False, sort=True)
        .agg(
            orders=("order_id", "count"),
            units=("units", "sum"),
            amount=("amount", "sum"),
        )
    )

    source_totals = (len(data), data["units"].sum(), data["amount"].sum())
    summary_totals = (
        summary["orders"].sum(),
        summary["units"].sum(),
        summary["amount"].sum(),
    )
    if summary_totals != source_totals:
        raise RuntimeError("Monthly totals do not reconcile with the source.")

    OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT_DIR.mkdir()
    summary.to_csv(OUTPUT, index=False)

    check = pd.read_csv(OUTPUT)
    if check.to_dict("records") != summary.to_dict("records"):
        raise RuntimeError("Saved CSV does not match the calculated summary.")

    print(f"Source rows: {len(data)}.")
    print(f"Months: {len(summary)}.")
    print(f"Verified totals: units={source_totals[1]}, amount={source_totals[2]}.")
    print(f"Output: {OUTPUT.as_posix()}")


if __name__ == "__main__":
    main()
text
python monthly_summary_pandas.py

05예상 출력 확인하기

출력에는 월 순서로 정렬된 데이터 행 3개가 있어야 합니다. 예상 CSV 내용은 아래와 같습니다.

csv
month,orders,units,amount
2026-01,2,5,100
2026-02,3,7,175
2026-03,2,6,155

아래 예상 콘솔 출력은 합성 데이터와 코드를 기준으로 손으로 도출한 결과이며 실제 실행 로그가 아닙니다.

text
Source rows: 7.
Months: 3.
Verified totals: units=18, amount=430.
Output: outputs/monthly_summary_result/monthly_summary.csv

06결과를 대조하고 자주 발생하는 오류 확인하기

  1. 원본 행을 세어 7개인지 확인합니다.
  2. 월별 orders를 더합니다. 2 + 3 + 2 = 7입니다.
  3. 월별 units를 더합니다. 5 + 7 + 6 = 18입니다.
  4. 월별 amount를 더합니다. 100 + 175 + 155 = 430입니다.
  5. 출력 월이 정확히 2026-01, 2026-02, 2026-03인지 확인합니다.
  6. OUTPUT_DIR을 바꾸지 않고 다시 실행합니다. 기존 결과를 덮어쓰지 않고 FileExistsError로 중단되어야 합니다.
증상확인할 사항
pandas ModuleNotFoundError같은 Python 환경에 python -m pip install pandas 명령으로 pandas를 설치하세요.
필수 열 누락예상 스키마를 바꾸기 전에 CSV 헤더를 확인하세요.
날짜 파싱 오류모든 order_date가 YYYY-MM-DD 형식인지 확인하세요.
숫자 변환 오류units와 amount에 텍스트, 통화 기호, 구분자 등이 있는지 확인하세요.
Duplicate order_id found반복된 ID가 단순 중복인지 아니면 다른 데이터 구조를 의미하는지 확인하세요.
FileExistsError기존 결과를 검토하고 덮어쓰지 말고 새 출력 폴더를 사용하세요.

전체 합계가 맞는다고 모든 월이 정확하다는 뜻은 아닙니다. 서로 다른 월의 오류가 서로 상쇄될 수 있으므로 중요한 보고서는 일부 원본 행과 월별 소계도 추가로 확인하세요.

07적용 범위와 한계 이해하기

이 예제는 한 행이 주문 한 건이고 amount가 정수라고 가정합니다. 하나의 주문이 여러 품목 행으로 나뉘어 있다면 행 개수 대신 order_id의 고유 개수를 사용해야 할 수 있습니다. 실제 금액이 소수라면 일반 부동소수점 대신 최소 화폐 단위 정수나 Decimal 기반 처리가 필요할 수 있습니다.

월은 시간대 처리 없이 날짜에서 직접 계산합니다. 여러 시간대의 타임스탬프가 있다면 어느 업무 시간대를 기준으로 월을 정할지 먼저 결정해야 합니다. 환불, 취소, 필터, 누락 레코드도 명시적인 업무 규칙이 필요하며, 단순한 합계 일치만으로 그 규칙이 적절한지 판단할 수는 없습니다.

실행·검증 기록

2026-09-20 · 예제 수동 검토 · 대상: Python 3.12 · pandas 필요 · 실행하지 않음

  • 2026년 1월, 2월, 3월의 원본 레코드 7개를 수동으로 확인했습니다.
  • 원본 units 합계 18과 amount 합계 430을 수동 계산했습니다.
  • 1월은 주문 2개, units 5, amount 100; 2월은 주문 3개, units 7, amount 175; 3월은 주문 2개, units 6, amount 155로 수동 계산했습니다.
  • 월별 값을 다시 더해 주문 7개, units 18, amount 430으로 원본과 일치함을 확인했습니다.
  • 필수 열 검사, 중복 ID 검사, 명시적 날짜 파싱, 숫자 변환, groupby 집계, 합계 대조, 저장 파일 비교, 출력 충돌 방지 로직을 코드로 검토했습니다.
  • 예상 CSV와 콘솔 출력을 손으로 도출했습니다.
검증 한계
  • 이 응답의 작성자는 코드를 실행하지 않았으며, pandas 동작이나 파일 시스템 쓰기를 실제로 시험하지 않았습니다.
  • 소수 금액, 시간대, 환불, 취소, 품목 단위 주문 데이터, 매우 큰 파일은 시험하지 않았습니다.
  • 공식 문서 URL은 알려진 문서 위치를 사용했지만 실시간으로 접속해 확인하지 않았습니다.

사이트 전체 작성·검증 원칙

참고 출처

설명과 예제는 직접 작성했습니다. 관련 동작과 개념은 아래 공식 자료에서 확인할 수 있습니다.