1억이 무조건 이상한 거래일까? 데이터의 패턴으로 탐지하기
회사 A에겐 1억이 평범한 거래지만, 회사 B에겐 1년 치 매출일 수 있습니다.
단순한 조건문(IF)을 넘어 평균, 표준편차, Z-score를 활용해 "이 거래처의 평소 거래패턴 치고는 통계적으로 이상한 거래"를 파이썬이 수학적으로 색출해냅니다.
왜 Rule-based를 넘어 통계 분석이 필요한가?
13단계에서는 IF 금액 > 1억 THEN "고액 거래 검토" 와 같이 사람이 규칙을 세웠습니다. 하지만 수만 개의 거래처마다 평소 거래 규모가 다르기 때문에 일괄적인 규칙으로는 진짜 이상 거래를 놓치거나 정상 거래를 오탐지하게 됩니다.
(평균, 중앙값, 분산)
(표준편차 구하기)
(이상치 점수화)
(관심/검토/우선검토)
파이썬을 사용하면 각 거래처의 평소 패턴과 데이터 분포(Distribution)가 스스로 기준을 만들어냅니다.
Z-score: 평균으로부터 얼마나 벗어났는가?
Z-score(표준점수)는 특정 거래 금액이 해당 그룹의 평균에서 표준편차의 몇 배만큼 떨어져 있는지를 나타내는 통계 기법입니다.
Z = (거래금액 - 평균) / 표준편차
mean = df["금액"].mean() # 평균 std = df["금액"].std() # 표준편차 # Z-score 계산 df["Z_score"] = (df["금액"] - mean) / std # 보통 Z-score가 절대값 3 이상이면 '통계적 이상치'로 간주함 df["Z_이상치"] = df["Z_score"].abs() >= 3
Z-score가 0이면 평균과 똑같다는 뜻입니다. +3 이면 평균보다 어마어마하게 높은 금액, -3 이면 비정상적으로 낮은 금액임을 의미합니다. (통계학에서 Z>3 인 경우는 0.3% 확률로 발생합니다)
극단값에 강한 IQR (사분위수 범위)
Z-score는 100억 짜리 오류 데이터가 하나만 섞여 있어도 "평균" 자체를 망가뜨려 탐지를 방해합니다. 이때는 중앙값(Median)을 활용하는 IQR 방식이 강력합니다.
# 상위 25% 지점(Q1)과 75% 지점(Q3) 구하기 Q1 = df["금액"].quantile(0.25) Q3 = df["금액"].quantile(0.75) # IQR (사분위 범위) 계산 IQR = Q3 - Q1 # 통계적 허용 범위(하한, 상한) 설정 (Tukey 방식) lower = Q1 - 1.5 * IQR upper = Q3 + 1.5 * IQR # 허용 범위를 벗어나면 이상치! df["IQR_이상치"] = (df["금액"] < lower) | (df["금액"] > upper)
핵심: 거래처별 평소 패턴에 맞춘 Z-score
전체 회사의 통계가 아니라, 각 거래처별 그룹(groupby)으로 Z-score를 계산해야 진짜 실무에 쓸 수 있습니다.
# 거래처별로 묶어(groupby) 각각의 평균과 표준편차를 구한 뒤 원래 행 위치에 넣음(transform) df["거래처평균"] = df.groupby("거래처")["금액"].transform("mean") df["거래처표준편차"] = df.groupby("거래처")["금액"].transform("std") # 이 거래처의 평소 패턴 대비, 이 거래는 얼마나 튀는가? df["거래처_Z"] = (df["금액"] - df["거래처평균"]) / df["거래처표준편차"] # 거래처 평소 패턴 기준 Z-score 3 이상을 이상치로 판단! df["거래처_이상치"] = df["거래처_Z"].abs() >= 3
이렇게 하면 A회사에겐 1억이 평범해도(Z=0.5), B회사에겐 1억이 초고액(Z=6.5) 거래로 완벽하게 핀포인트 분류됩니다.
위험 등급을 위한 복합 점수(Scoring) 시스템
단순 규칙 기반(13단계)에 통계 기반(14단계)을 얹어, 각 거래 행위의 "수상함"을 점수로 매깁니다. 점수가 높을수록 회계사가 우선 검토해야 합니다.
df["이상점수"] = 0 # 가산점 룰셋 적용 df.loc[ df["금액"] < 0, "이상점수" ] += 1 # 음수 거래 df.loc[ df["중복의심"] == True, "이상점수" ] += 2 # 중복 기장 의심 df.loc[ df["거래처_이상치"] == True, "이상점수" ] += 2 # 통계적 패턴 위반 # 최종 등급 매기기 def risk_level(score): if score >= 4: return "우선검토" elif score >= 2: return "검토" elif score == 1: return "관심" return "정상" df["위험등급"] = df["이상점수"].apply(risk_level)
14단계 최종 실무 스크립트
13단계의 규칙을 보완하는 완전한 통계적 이상 탐지 시스템입니다.
import pandas as pd def main(): # 1. 엑셀 로드 및 클리닝 df = pd.read_excel("transactions_raw.xlsx") df["공급가액"] = pd.to_numeric(df["공급가액"].astype(str).str.replace(",", ""), errors="coerce") df["중복의심"] = df.duplicated(subset=["거래일자", "거래처", "공급가액"], keep=False) # 2. 거래처별 패턴에 맞춘 Z-Score 도출 df["거래처평균"] = df.groupby("거래처")["공급가액"].transform("mean") df["거래처표준편차"] = df.groupby("거래처")["공급가액"].transform("std").fillna(0) # 분모가 0(표준편차가 0)인 경우를 대비한 안전 계산 df["거래처_Z"] = 0 valid_mask = df["거래처표준편차"] > 0 df.loc[valid_mask, "거래처_Z"] = (df["공급가액"] - df["거래처평균"]) / df["거래처표준편차"] df["통계적_이상치"] = df["거래처_Z"].abs() >= 3 # 3. 위험 점수(Scoring) df["점수"] = 0 df.loc[df["공급가액"] < 0, "점수"] += 1 df.loc[df["중복의심"], "점수"] += 2 df.loc[df["통계적_이상치"], "점수"] += 3 # Z-Score 위반시 가중치 부여 def set_grade(s): if s >= 4: return "우선검토" elif s >= 2: return "일반검토" return "정상" df["최종위험등급"] = df["점수"].apply(set_grade) # 4. 검토 대상만 추출하여 저장 target = df[df["최종위험등급"] != "정상"].sort_values("점수", ascending=False) with pd.ExcelWriter("통계기반_이상거래분석.xlsx") as w: target.to_excel(w, sheet_name="🚨위험우선순위", index=False) df.to_excel(w, sheet_name="전체데이터", index=False) if __name__ == "__main__": main()
지금까지는 파이썬이 이상한 거래를 색출해주었습니다. 15단계부터는 찾아낸 데이터를 LLM (챗GPT API)에 전달하여, "이 거래가 왜 이상한지 자연어로 설명하는 기능"을 배웁니다.
"ABC상사의 이번 2억 원 매출은 해당 거래처 평균(1,000만 원) 대비 약 20배 높은 비정상적 고액 거래입니다. 세금계산서 중복 발행 여부 확인이 요망됩니다." 와 같은 AI 회계 검토 보고서 자동 작성 시스템을 구현합니다!