들어가며

은행 거래내역에는 우리의 소비 패턴, 수입 구조, 그리고 재정 건강에 대한 수많은 인사이트가 숨어 있습니다. 이 튜토리얼에서는 Bank API로 거래내역을 가져온 뒤, Python과 Pandas를 사용하여 데이터를 분석하고 시각화하는 방법을 알아봅니다.

이 튜토리얼에서 배우는 것
  • Bank API에서 거래내역 가져오기
  • Pandas DataFrame으로 데이터 정제
  • 카테고리별 지출 분석
  • 월별/주별 지출 추이 분석
  • 이상 거래 탐지
  • Matplotlib/Seaborn으로 시각화

사전 요구사항

  • Python 3.9 이상
  • Bank API Key (무료 가입: 가입하기)
  • 기본적인 Python 및 Pandas 지식

1. 환경 설정

필요한 패키지 설치

# 가상환경 생성 (권장)
python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate

# 패키지 설치
pip install pandas numpy matplotlib seaborn requests python-dotenv

프로젝트 구조

transaction-analysis/
├── .env                 # API 키 (Git 제외)
├── bank_api.py          # API 클라이언트
├── analysis.py          # 분석 스크립트
├── visualize.py         # 시각화 스크립트
└── requirements.txt

환경 변수 설정

# .env
BANK_API_KEY=pk_live_your_api_key_here
BANK_API_SECRET=sk_live_your_secret_key_here
BANK_API_URL=https://api.bankapi.co.kr

2. Bank API 클라이언트

bank_api.py 파일을 생성합니다:

# bank_api.py
import os
import requests
from datetime import datetime, timedelta
from typing import Optional
from dotenv import load_dotenv

load_dotenv()

class BankApiClient:
    """Bank API 클라이언트"""

    def __init__(self):
        self.api_key = os.getenv('BANK_API_KEY')
        self.secret_key = os.getenv('BANK_API_SECRET')
        self.base_url = os.getenv('BANK_API_URL', 'https://api.bankapi.co.kr')

        if not self.api_key or not self.secret_key:
            raise ValueError("BANK_API_KEY와 BANK_API_SECRET 환경변수를 설정하세요.")

    def _get_headers(self) -> dict:
        return {
            'Authorization': f'Bearer {self.api_key}:{self.secret_key}',
            'Content-Type': 'application/json'
        }

    def get_transactions(
        self,
        bank_code: str,
        account_number: str,
        account_password: str,
        start_date: str,
        end_date: str
    ) -> dict:
        """
        거래내역을 조회합니다.

        Args:
            bank_code: 은행 코드 (예: 'NH')
            account_number: 계좌번호
            account_password: 계좌 비밀번호
            start_date: 시작일 (YYYY-MM-DD)
            end_date: 종료일 (YYYY-MM-DD)

        Returns:
            거래내역 딕셔너리
        """
        url = f"{self.base_url}/v1/transactions"

        payload = {
            'bankCode': bank_code,
            'accountNumber': account_number,
            'accountPassword': account_password,
            'startDate': start_date,
            'endDate': end_date
        }

        response = requests.post(url, json=payload, headers=self._get_headers())
        response.raise_for_status()

        return response.json()

    def get_transactions_for_period(
        self,
        bank_code: str,
        account_number: str,
        account_password: str,
        days: int = 30
    ) -> dict:
        """최근 N일간의 거래내역을 조회합니다."""
        end_date = datetime.now()
        start_date = end_date - timedelta(days=days)

        return self.get_transactions(
            bank_code=bank_code,
            account_number=account_number,
            account_password=account_password,
            start_date=start_date.strftime('%Y-%m-%d'),
            end_date=end_date.strftime('%Y-%m-%d')
        )


# 사용 예시
if __name__ == '__main__':
    client = BankApiClient()

    # 최근 30일 거래내역 조회
    result = client.get_transactions_for_period(
        bank_code='NH',
        account_number='123-456-789012',
        account_password='1234',
        days=30
    )

    if result.get('success'):
        transactions = result['data']['transactions']
        print(f"총 {len(transactions)}건의 거래내역을 조회했습니다.")
    else:
        print(f"오류: {result.get('error', {}).get('message')}")

3. 데이터 정제 및 전처리

analysis.py 파일을 생성합니다:

# analysis.py
import pandas as pd
import numpy as np
from datetime import datetime
from typing import List, Dict, Optional

def transactions_to_dataframe(transactions: List[Dict]) -> pd.DataFrame:
    """
    거래내역 리스트를 Pandas DataFrame으로 변환합니다.
    """
    df = pd.DataFrame(transactions)

    # 날짜/시간 컬럼 처리
    df['datetime'] = pd.to_datetime(df['date'] + ' ' + df['time'])
    df['date'] = pd.to_datetime(df['date'])

    # 금액 컬럼 처리 (이미 숫자인 경우 생략)
    df['amount'] = pd.to_numeric(df['amount'], errors='coerce')
    df['balance'] = pd.to_numeric(df['balance'], errors='coerce')

    # 유용한 컬럼 추가
    df['year'] = df['date'].dt.year
    df['month'] = df['date'].dt.month
    df['day'] = df['date'].dt.day
    df['weekday'] = df['date'].dt.day_name()
    df['weekday_num'] = df['date'].dt.weekday  # 0=Monday
    df['week'] = df['date'].dt.isocalendar().week

    # 입출금 구분
    df['is_expense'] = df['amount'] < 0
    df['abs_amount'] = df['amount'].abs()

    return df


def categorize_transactions(df: pd.DataFrame) -> pd.DataFrame:
    """
    거래 설명을 기반으로 카테고리를 자동 분류합니다.
    """
    # 카테고리 규칙 정의
    category_rules = {
        '식비': ['배달의민족', '요기요', '쿠팡이츠', '스타벅스', '이디야',
                '맥도날드', 'GS25', 'CU', '세븐일레븐', '롯데리아', '버거킹',
                '식당', '카페', '치킨', '피자', '음식점'],
        '교통': ['카카오택시', '우버', '타다', '대중교통', '지하철', '버스',
                '주유소', 'SK에너지', 'GS칼텍스', '현대오일뱅크', '주차'],
        '쇼핑': ['쿠팡', '네이버페이', '카카오페이', '11번가', 'G마켓',
                '옥션', 'SSG', '롯데온', '무신사', '올리브영'],
        '통신': ['SKT', 'KT', 'LG U+', '통신료', '인터넷'],
        '주거': ['월세', '관리비', '전기료', '가스비', '수도료', '아파트'],
        '보험/금융': ['보험료', '적금', '펀드', '증권', '대출이자'],
        '의료': ['병원', '약국', '의원', '치과', '안과', '정형외과'],
        '문화/여가': ['넷플릭스', '유튜브', '멜론', '스포티파이', 'CGV',
                    '메가박스', '롯데시네마', '헬스장', '수영장'],
        '교육': ['학원', '인강', '클래스101', '패스트캠퍼스', '학교'],
        '급여': ['급여', '월급', '상여금', '보너스'],
        '이체': ['이체', '송금'],
    }

    def get_category(description: str) -> str:
        description = str(description).lower()
        for category, keywords in category_rules.items():
            for keyword in keywords:
                if keyword.lower() in description:
                    return category
        return '기타'

    df['category'] = df['description'].apply(get_category)

    return df


def get_expense_summary(df: pd.DataFrame) -> pd.DataFrame:
    """카테고리별 지출 요약을 반환합니다."""
    expenses = df[df['is_expense']].copy()

    summary = expenses.groupby('category').agg({
        'abs_amount': ['sum', 'mean', 'count'],
        'description': lambda x: ', '.join(x.head(3).unique())  # 상위 3개 예시
    }).round(0)

    summary.columns = ['총액', '평균', '건수', '예시']
    summary = summary.sort_values('총액', ascending=False)

    return summary


def get_monthly_summary(df: pd.DataFrame) -> pd.DataFrame:
    """월별 수입/지출 요약을 반환합니다."""
    monthly = df.groupby(['year', 'month']).agg({
        'amount': [
            ('수입', lambda x: x[x > 0].sum()),
            ('지출', lambda x: x[x < 0].abs().sum()),
        ],
        'balance': ('잔액', 'last')
    }).round(0)

    monthly.columns = ['수입', '지출', '잔액']
    monthly['순수익'] = monthly['수입'] - monthly['지출']
    monthly['저축률'] = (monthly['순수익'] / monthly['수입'] * 100).round(1)

    return monthly


def get_weekday_pattern(df: pd.DataFrame) -> pd.DataFrame:
    """요일별 지출 패턴을 분석합니다."""
    expenses = df[df['is_expense']].copy()

    weekday_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
                     'Friday', 'Saturday', 'Sunday']

    pattern = expenses.groupby('weekday').agg({
        'abs_amount': ['sum', 'mean', 'count']
    }).round(0)

    pattern.columns = ['총지출', '평균지출', '건수']
    pattern = pattern.reindex(weekday_order)

    return pattern


def detect_anomalies(df: pd.DataFrame, threshold: float = 2.0) -> pd.DataFrame:
    """
    이상 거래를 탐지합니다.
    평균에서 threshold * 표준편차 이상 벗어난 거래를 이상치로 판단합니다.
    """
    expenses = df[df['is_expense']].copy()

    mean_amount = expenses['abs_amount'].mean()
    std_amount = expenses['abs_amount'].std()

    upper_bound = mean_amount + (threshold * std_amount)

    anomalies = expenses[expenses['abs_amount'] > upper_bound].copy()
    anomalies['z_score'] = (anomalies['abs_amount'] - mean_amount) / std_amount

    return anomalies[['date', 'description', 'abs_amount', 'category', 'z_score']].sort_values(
        'abs_amount', ascending=False
    )


def get_recurring_transactions(df: pd.DataFrame, min_occurrences: int = 2) -> pd.DataFrame:
    """
    정기 결제로 추정되는 거래를 찾습니다.
    동일한 금액으로 반복되는 거래를 탐지합니다.
    """
    expenses = df[df['is_expense']].copy()

    # 설명과 금액이 동일한 거래 그룹화
    recurring = expenses.groupby(['description', 'abs_amount']).agg({
        'date': ['count', 'min', 'max']
    })

    recurring.columns = ['횟수', '첫결제', '마지막결제']
    recurring = recurring[recurring['횟수'] >= min_occurrences]
    recurring = recurring.reset_index()
    recurring = recurring.sort_values('횟수', ascending=False)

    return recurring


# 메인 실행
if __name__ == '__main__':
    from bank_api import BankApiClient

    # API에서 데이터 가져오기
    client = BankApiClient()
    result = client.get_transactions_for_period(
        bank_code='NH',
        account_number='123-456-789012',
        account_password='1234',
        days=90  # 3개월
    )

    if not result.get('success'):
        print(f"API 오류: {result.get('error')}")
        exit(1)

    # DataFrame 변환 및 전처리
    transactions = result['data']['transactions']
    df = transactions_to_dataframe(transactions)
    df = categorize_transactions(df)

    print(f"\n총 {len(df)}건의 거래내역을 분석합니다.\n")

    # 카테고리별 지출 요약
    print("=" * 50)
    print("📊 카테고리별 지출 분석")
    print("=" * 50)
    expense_summary = get_expense_summary(df)
    print(expense_summary.to_string())

    # 월별 요약
    print("\n" + "=" * 50)
    print("📅 월별 수입/지출 요약")
    print("=" * 50)
    monthly = get_monthly_summary(df)
    print(monthly.to_string())

    # 요일별 패턴
    print("\n" + "=" * 50)
    print("📆 요일별 지출 패턴")
    print("=" * 50)
    weekday = get_weekday_pattern(df)
    print(weekday.to_string())

    # 이상 거래
    print("\n" + "=" * 50)
    print("⚠️ 이상 거래 탐지 (평균의 2배 이상)")
    print("=" * 50)
    anomalies = detect_anomalies(df)
    if len(anomalies) > 0:
        print(anomalies.head(10).to_string())
    else:
        print("이상 거래가 발견되지 않았습니다.")

    # 정기 결제
    print("\n" + "=" * 50)
    print("🔄 정기 결제 추정 항목")
    print("=" * 50)
    recurring = get_recurring_transactions(df)
    if len(recurring) > 0:
        print(recurring.head(10).to_string())
    else:
        print("정기 결제로 추정되는 항목이 없습니다.")

4. 데이터 시각화

visualize.py 파일을 생성합니다:

# visualize.py
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import seaborn as sns
from datetime import datetime
from typing import Optional

# 한글 폰트 설정 (macOS)
plt.rcParams['font.family'] = 'AppleGothic'
# Windows: plt.rcParams['font.family'] = 'Malgun Gothic'
plt.rcParams['axes.unicode_minus'] = False

# Seaborn 스타일 설정
sns.set_style("whitegrid")
sns.set_palette("husl")


def plot_category_pie(df: pd.DataFrame, save_path: Optional[str] = None):
    """카테고리별 지출 비율 파이 차트"""
    expenses = df[df['is_expense']].copy()
    category_totals = expenses.groupby('category')['abs_amount'].sum()
    category_totals = category_totals.sort_values(ascending=False)

    # 상위 7개 + 기타로 그룹화
    if len(category_totals) > 8:
        top_categories = category_totals.head(7)
        others = category_totals[7:].sum()
        category_totals = pd.concat([top_categories, pd.Series({'기타(합계)': others})])

    fig, ax = plt.subplots(figsize=(10, 8))

    colors = sns.color_palette("husl", len(category_totals))
    wedges, texts, autotexts = ax.pie(
        category_totals,
        labels=category_totals.index,
        autopct='%1.1f%%',
        colors=colors,
        startangle=90,
        explode=[0.02] * len(category_totals)
    )

    plt.setp(autotexts, size=10, weight='bold')
    ax.set_title('카테고리별 지출 비율', fontsize=16, fontweight='bold', pad=20)

    # 범례에 금액 표시
    legend_labels = [f'{cat}: {amt:,.0f}원' for cat, amt in category_totals.items()]
    ax.legend(wedges, legend_labels, title='카테고리', loc='center left',
              bbox_to_anchor=(1, 0, 0.5, 1))

    plt.tight_layout()

    if save_path:
        plt.savefig(save_path, dpi=150, bbox_inches='tight')
        print(f"차트 저장: {save_path}")
    else:
        plt.show()


def plot_monthly_trend(df: pd.DataFrame, save_path: Optional[str] = None):
    """월별 수입/지출 추이 차트"""
    df['year_month'] = df['date'].dt.to_period('M')

    monthly = df.groupby('year_month').agg({
        'amount': [
            ('income', lambda x: x[x > 0].sum()),
            ('expense', lambda x: x[x < 0].abs().sum())
        ]
    })
    monthly.columns = ['수입', '지출']
    monthly['순수익'] = monthly['수입'] - monthly['지출']
    monthly = monthly.reset_index()
    monthly['year_month'] = monthly['year_month'].astype(str)

    fig, ax = plt.subplots(figsize=(12, 6))

    x = np.arange(len(monthly))
    width = 0.35

    bars1 = ax.bar(x - width/2, monthly['수입'], width, label='수입', color='#4CAF50')
    bars2 = ax.bar(x + width/2, monthly['지출'], width, label='지출', color='#F44336')

    # 순수익 라인
    ax2 = ax.twinx()
    line = ax2.plot(x, monthly['순수익'], 'o-', color='#2196F3',
                    linewidth=2, markersize=8, label='순수익')
    ax2.axhline(y=0, color='gray', linestyle='--', alpha=0.5)

    ax.set_xlabel('월', fontsize=12)
    ax.set_ylabel('금액 (원)', fontsize=12)
    ax2.set_ylabel('순수익 (원)', fontsize=12, color='#2196F3')

    ax.set_xticks(x)
    ax.set_xticklabels(monthly['year_month'], rotation=45)
    ax.set_title('월별 수입/지출 추이', fontsize=16, fontweight='bold')

    # 금액 포맷팅
    ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'{x/10000:,.0f}만'))
    ax2.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'{x/10000:,.0f}만'))

    # 범례 통합
    lines1, labels1 = ax.get_legend_handles_labels()
    lines2, labels2 = ax2.get_legend_handles_labels()
    ax.legend(lines1 + lines2, labels1 + labels2, loc='upper left')

    plt.tight_layout()

    if save_path:
        plt.savefig(save_path, dpi=150, bbox_inches='tight')
    else:
        plt.show()


def plot_weekday_heatmap(df: pd.DataFrame, save_path: Optional[str] = None):
    """요일별/시간대별 지출 히트맵"""
    expenses = df[df['is_expense']].copy()
    expenses['hour'] = expenses['datetime'].dt.hour
    expenses['weekday'] = expenses['datetime'].dt.day_name()

    weekday_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
                     'Friday', 'Saturday', 'Sunday']
    korean_weekdays = ['월', '화', '수', '목', '금', '토', '일']

    # 피벗 테이블 생성
    pivot = expenses.pivot_table(
        values='abs_amount',
        index='weekday',
        columns='hour',
        aggfunc='sum',
        fill_value=0
    )
    pivot = pivot.reindex(weekday_order)

    fig, ax = plt.subplots(figsize=(14, 6))

    sns.heatmap(
        pivot / 10000,  # 만원 단위
        cmap='YlOrRd',
        annot=True,
        fmt='.0f',
        linewidths=0.5,
        ax=ax,
        cbar_kws={'label': '지출 (만원)'}
    )

    ax.set_yticklabels(korean_weekdays, rotation=0)
    ax.set_xlabel('시간대', fontsize=12)
    ax.set_ylabel('요일', fontsize=12)
    ax.set_title('요일/시간대별 지출 패턴', fontsize=16, fontweight='bold')

    plt.tight_layout()

    if save_path:
        plt.savefig(save_path, dpi=150, bbox_inches='tight')
    else:
        plt.show()


def plot_balance_trend(df: pd.DataFrame, save_path: Optional[str] = None):
    """잔액 변화 추이 차트"""
    daily = df.groupby('date').agg({
        'balance': 'last',
        'amount': [
            ('income', lambda x: x[x > 0].sum()),
            ('expense', lambda x: x[x < 0].abs().sum())
        ]
    })
    daily.columns = ['잔액', '수입', '지출']
    daily = daily.reset_index()

    fig, ax = plt.subplots(figsize=(14, 6))

    # 잔액 라인
    ax.fill_between(daily['date'], daily['잔액'], alpha=0.3, color='#2196F3')
    ax.plot(daily['date'], daily['잔액'], linewidth=2, color='#2196F3', label='잔액')

    # 최고/최저 잔액 표시
    max_balance = daily['잔액'].max()
    min_balance = daily['잔액'].min()
    max_date = daily.loc[daily['잔액'].idxmax(), 'date']
    min_date = daily.loc[daily['잔액'].idxmin(), 'date']

    ax.scatter([max_date], [max_balance], color='green', s=100, zorder=5)
    ax.scatter([min_date], [min_balance], color='red', s=100, zorder=5)

    ax.annotate(f'최고: {max_balance:,.0f}원',
                xy=(max_date, max_balance),
                xytext=(10, 10), textcoords='offset points',
                fontsize=10, color='green')
    ax.annotate(f'최저: {min_balance:,.0f}원',
                xy=(min_date, min_balance),
                xytext=(10, -15), textcoords='offset points',
                fontsize=10, color='red')

    ax.set_xlabel('날짜', fontsize=12)
    ax.set_ylabel('잔액 (원)', fontsize=12)
    ax.set_title('일별 잔액 변화 추이', fontsize=16, fontweight='bold')
    ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'{x/10000:,.0f}만'))

    plt.xticks(rotation=45)
    plt.tight_layout()

    if save_path:
        plt.savefig(save_path, dpi=150, bbox_inches='tight')
    else:
        plt.show()


def create_dashboard(df: pd.DataFrame, output_dir: str = './charts'):
    """모든 차트를 생성하고 저장합니다."""
    import os
    os.makedirs(output_dir, exist_ok=True)

    print("📊 대시보드 생성 중...")

    print("  - 카테고리별 지출 비율...")
    plot_category_pie(df, f'{output_dir}/category_pie.png')

    print("  - 월별 수입/지출 추이...")
    plot_monthly_trend(df, f'{output_dir}/monthly_trend.png')

    print("  - 요일/시간대별 히트맵...")
    plot_weekday_heatmap(df, f'{output_dir}/weekday_heatmap.png')

    print("  - 잔액 변화 추이...")
    plot_balance_trend(df, f'{output_dir}/balance_trend.png')

    print(f"\n✅ 모든 차트가 {output_dir}/ 폴더에 저장되었습니다.")


# 메인 실행
if __name__ == '__main__':
    from bank_api import BankApiClient
    from analysis import transactions_to_dataframe, categorize_transactions

    # 데이터 가져오기
    client = BankApiClient()
    result = client.get_transactions_for_period(
        bank_code='NH',
        account_number='123-456-789012',
        account_password='1234',
        days=90
    )

    if result.get('success'):
        df = transactions_to_dataframe(result['data']['transactions'])
        df = categorize_transactions(df)
        create_dashboard(df)
    else:
        print(f"API 오류: {result.get('error')}")

5. Jupyter Notebook으로 대화형 분석

Jupyter Notebook을 사용하면 더 인터랙티브하게 데이터를 탐색할 수 있습니다.

# Jupyter 설치
pip install jupyter

# 노트북 실행
jupyter notebook

노트북 셀에서 사용할 수 있는 유용한 분석 코드:

# 셀 1: 데이터 로드 및 기본 정보
import pandas as pd
from bank_api import BankApiClient
from analysis import transactions_to_dataframe, categorize_transactions

client = BankApiClient()
result = client.get_transactions_for_period('NH', '123-456-789012', '1234', 90)

df = transactions_to_dataframe(result['data']['transactions'])
df = categorize_transactions(df)

# 기본 정보
print(f"총 거래 건수: {len(df)}")
print(f"기간: {df['date'].min()} ~ {df['date'].max()}")
print(f"총 수입: {df[df['amount'] > 0]['amount'].sum():,.0f}원")
print(f"총 지출: {df[df['amount'] < 0]['amount'].abs().sum():,.0f}원")
# 셀 2: 가장 많이 지출한 곳 TOP 10
expenses = df[df['is_expense']].copy()
top_merchants = expenses.groupby('description')['abs_amount'].agg(['sum', 'count'])
top_merchants.columns = ['총액', '횟수']
top_merchants = top_merchants.sort_values('총액', ascending=False)
top_merchants.head(10)
# 셀 3: 특정 카테고리 상세 분석
category = '식비'  # 분석할 카테고리
cat_df = df[(df['category'] == category) & (df['is_expense'])]

print(f"\n📍 {category} 상세 분석")
print(f"총 지출: {cat_df['abs_amount'].sum():,.0f}원")
print(f"평균 지출: {cat_df['abs_amount'].mean():,.0f}원")
print(f"거래 건수: {len(cat_df)}건")
print(f"\n자주 가는 곳:")
print(cat_df['description'].value_counts().head(5))
# 셀 4: 지출 증가/감소 추이 확인
weekly = df[df['is_expense']].groupby(df['date'].dt.to_period('W'))['abs_amount'].sum()
weekly_pct_change = weekly.pct_change() * 100

print("주별 지출 변화율:")
for period, change in weekly_pct_change.items():
    emoji = "📈" if change > 0 else "📉"
    print(f"  {period}: {emoji} {change:+.1f}%")

6. 실전 활용 예시

📱

카카오톡 알림 봇

이상 거래가 감지되면 카카오톡으로 알림을 보내는 봇을 만들 수 있습니다.

📊

월간 리포트 자동화

매월 1일에 지난 달 지출 리포트를 PDF로 생성하고 이메일로 발송합니다.

🎯

예산 목표 추적

카테고리별 월 예산을 설정하고 현재 사용량을 추적합니다.

📈

지출 예측

과거 데이터를 기반으로 다음 달 예상 지출을 예측합니다.

예산 목표 추적 예시

# 예산 목표 설정 및 추적
MONTHLY_BUDGET = {
    '식비': 500000,
    '교통': 150000,
    '쇼핑': 200000,
    '문화/여가': 100000,
}

def check_budget(df: pd.DataFrame, year: int, month: int):
    """이번 달 예산 사용 현황을 확인합니다."""
    month_df = df[(df['year'] == year) & (df['month'] == month) & (df['is_expense'])]

    print(f"\n📊 {year}년 {month}월 예산 현황\n")
    print("-" * 50)

    for category, budget in MONTHLY_BUDGET.items():
        spent = month_df[month_df['category'] == category]['abs_amount'].sum()
        remaining = budget - spent
        usage_pct = (spent / budget) * 100

        if usage_pct >= 100:
            status = "🔴 초과"
        elif usage_pct >= 80:
            status = "🟡 주의"
        else:
            status = "🟢 양호"

        print(f"{category:10s} | {status} | {spent:>10,.0f}원 / {budget:>10,.0f}원 ({usage_pct:5.1f}%)")

    print("-" * 50)
    total_spent = month_df['abs_amount'].sum()
    total_budget = sum(MONTHLY_BUDGET.values())
    print(f"{'총합':10s} |      | {total_spent:>10,.0f}원 / {total_budget:>10,.0f}원")


# 현재 달 예산 체크
from datetime import datetime
now = datetime.now()
check_budget(df, now.year, now.month)

출력 예시:

📊 2026년 1월 예산 현황

--------------------------------------------------
식비       | 🟡 주의 |    421,500원 /    500,000원 (84.3%)
교통       | 🟢 양호 |     87,000원 /    150,000원 (58.0%)
쇼핑       | 🔴 초과 |    256,800원 /    200,000원 (128.4%)
문화/여가  | 🟢 양호 |     45,000원 /    100,000원 (45.0%)
--------------------------------------------------
총합       |      |    810,300원 /    950,000원

마무리

이 튜토리얼에서는 Python과 Pandas를 사용하여 은행 거래내역을 분석하는 방법을 알아보았습니다. 데이터 전처리부터 카테고리 분류, 시각화, 그리고 실전 활용 예시까지 다루었습니다.

핵심 포인트 요약

  • Pandas DataFrame으로 거래내역을 효율적으로 다룰 수 있습니다
  • 키워드 기반 규칙으로 거래를 자동 분류할 수 있습니다
  • 통계적 방법으로 이상 거래를 탐지할 수 있습니다
  • Matplotlib/Seaborn으로 인사이트를 시각화할 수 있습니다
  • 분석 결과를 예산 관리, 알림, 리포트에 활용할 수 있습니다

데이터 분석은 시작에 불과합니다. 이 코드를 기반으로 자신만의 개인 금융 대시보드를 만들어보세요!

지금 바로 분석을 시작하세요

무료 플랜으로 가입하고 거래내역을 분석해보세요. 월 10,000건까지 무료!

무료로 시작하기 API 문서 보기