理财数据可视化开发助手

0 浏览
0 试用
0 购买
Nov 16, 2025更新

本提示词专为个人理财编程语言开发者设计,提供专业的数据可视化功能开发指导。通过系统化的任务拆解,帮助开发者快速构建图表生成和趋势分析模块,支持多种数据类型和图表形式,确保输出内容直观准确且具备良好的可扩展性。适用于月度收支分析、资产分布展示、消费趋势追踪等常见理财场景,有效提升开发效率和可视化效果。

可视化功能概述

本方案面向“近三个月的月度收支”数据,提供一套可扩展的堆叠面积图可视化模块,支持:

  • 收入(工资、副业)与支出(房租、餐饮、出行)的分项堆叠展示,收入为正向堆叠、支出为负向堆叠,直观体现收支结构。
  • 自动计算每月结余与结余率,结余以折线显示,便于识别盈余变化。
  • 标注异常波动(支持百分比变化阈值或z-score两种方法)与工资发放日(垂直虚线+标签)。
  • 配色与堆叠配置建议,保证信息清晰、对比鲜明、避免误导。
  • 提供Plotly交互式图表(默认)以及Matplotlib本地静态图表(可选)。

注意:所有图表仅在传入真实的用户数据时生成,示例中不包含虚构数据;请按示例接口传入您自己的CSV/DataFrame数据。


核心接口定义

from typing import List, Dict, Optional, Tuple, Union
import pandas as pd
import numpy as np

try:
    import plotly.graph_objects as go
except ImportError:
    go = None

def detect_anomalies(
    series: pd.Series,
    method: str = "pct_change",
    threshold: float = 0.25
) -> List[int]:
    """
    异常检测:
    - method='pct_change': 月环比变化绝对值 >= threshold(如0.25代表±25%)标记为异常
    - method='zscore': 计算z分数,|z| >= threshold(如1.5)标记为异常
    返回异常点的索引列表(相对series的顺序索引)
    """
    series = series.astype(float)
    if method == "pct_change":
        pct = series.pct_change()
        return [i for i, v in enumerate(pct) if i > 0 and pd.notnull(v) and abs(v) >= threshold]
    elif method == "zscore":
        mu = series.mean()
        sigma = series.std(ddof=1)
        if sigma == 0 or np.isnan(sigma):
            return []
        z = (series - mu) / sigma
        return [i for i, v in enumerate(z) if abs(v) >= threshold]
    else:
        raise ValueError("Unsupported anomaly method. Use 'pct_change' or 'zscore'.")


def monthly_cashflow_area(
    df: pd.DataFrame,
    income_cols: List[str],
    expense_cols: List[str],
    month_col: str = "month",
    salary_paydays: Optional[Dict[str, str]] = None,
    anomaly_on: str = "total",                  # 'total' 或 'surplus_rate'
    anomaly_method: str = "pct_change",         # 'pct_change' 或 'zscore'
    anomaly_threshold: float = 0.25,
    colors: Optional[Dict[str, str]] = None,
    library: str = "plotly",                    # 'plotly' 或 'matplotlib'
    title: Optional[str] = None,
    return_components: bool = False
) -> Union["go.Figure", "matplotlib.figure.Figure", Tuple[object, pd.DataFrame, Dict]]:
    """
    生成“近三个月月度收支”堆叠面积图,并计算结余与结余率、标注异常与工资发放日。

    参数:
    - df: 包含月份与各项收支的DataFrame。必须包含month_col、income_cols、expense_cols对应的列。
    - income_cols: 收入列名列表,例如 ['income_salary', 'income_side']。
    - expense_cols: 支出列名列表,例如 ['expense_rent', 'expense_dining', 'expense_transport']。
    - month_col: 月份列,建议为可解析为日期的字符串(如'2025-09-01')或datetime。
    - salary_paydays: 可选,字典 { 'YYYY-MM': 'YYYY-MM-DD' },用于在图中标记工资发放日。
    - anomaly_on: 异常检测维度,'total'在总收入与总支出上检测,'surplus_rate'在结余率上检测。
    - anomaly_method: 异常检测方法,'pct_change'(环比百分比变化)或 'zscore'(z分数)。
    - anomaly_threshold: 异常阈值,pct_change时如0.25=±25%,zscore时如1.5=|z|≥1.5。
    - colors: 可选,颜色映射字典。未提供时使用推荐配色。
    - library: 可视化库,'plotly'(默认,交互)或 'matplotlib'(静态)。
    - title: 图表标题。
    - return_components: True时返回(figure, metrics_df, diagnostics)。

    返回:
    - figure: Plotly或Matplotlib图对象。
    - (可选)metrics_df: 含 total_income、total_expense、surplus、surplus_rate 的DataFrame。
    - (可选)diagnostics: 包含异常索引与文本的字典。
    """
    # 校验列存在
    for col in [month_col] + income_cols + expense_cols:
        if col not in df.columns:
            raise ValueError(f"DataFrame缺少必要列: {col}")

    # 复制并预处理
    work = df.copy()
    # 规范月份为datetime,便于排序和轴显示
    work[month_col] = pd.to_datetime(work[month_col])
    work.sort_values(by=month_col, inplace=True)
    work.reset_index(drop=True, inplace=True)

    # 计算总计、结余与结余率
    work["total_income"] = work[income_cols].astype(float).sum(axis=1)
    work["total_expense"] = work[expense_cols].astype(float).sum(axis=1)
    work["surplus"] = work["total_income"] - work["total_expense"]
    work["surplus_rate"] = np.where(
        work["total_income"] > 0,
        work["surplus"] / work["total_income"],
        np.nan
    )

    # 异常检测
    if anomaly_on == "total":
        inc_anom_idx = detect_anomalies(work["total_income"], method=anomaly_method, threshold=anomaly_threshold)
        exp_anom_idx = detect_anomalies(work["total_expense"], method=anomaly_method, threshold=anomaly_threshold)
        anomalies = {"income": inc_anom_idx, "expense": exp_anom_idx}
    elif anomaly_on == "surplus_rate":
        sr_anom_idx = detect_anomalies(work["surplus_rate"], method=anomaly_method, threshold=anomaly_threshold)
        anomalies = {"surplus_rate": sr_anom_idx}
    else:
        raise ValueError("anomaly_on 必须为 'total' 或 'surplus_rate'")

    # 推荐配色(可覆盖)
    default_colors = {
        # 收入
        "income_salary": "#2ecc71",       # 绿 - 工资
        "income_side":   "#27ae60",       # 深绿 - 副业
        # 支出
        "expense_rent":      "#e74c3c",   # 红 - 房租
        "expense_dining":    "#f39c12",   # 橙 - 餐饮
        "expense_transport": "#e67e22",   # 橘 - 出行
        # 结余
        "surplus": "#34495e"              # 蓝灰 - 结余线
    }
    # 合并用户配色
    palette = {**default_colors, **(colors or {})}

    # 工资发放日解析为datetime
    payday_map = {}
    if salary_paydays:
        for k, v in salary_paydays.items():
            try:
                payday_map[pd.to_datetime(k).strftime("%Y-%m")] = pd.to_datetime(v)
            except Exception as e:
                raise ValueError(f"工资发放日解析失败: {k} -> {v}. {e}")

    # 构图
    if library == "plotly":
        if go is None:
            raise ImportError("未安装 plotly,请选择 library='matplotlib' 或安装 plotly")
        fig = go.Figure()

        x = work[month_col]

        # 收入堆叠(正向)
        for col in income_cols:
            fig.add_trace(go.Scatter(
                x=x, y=work[col], name=col,
                stackgroup="income", mode="lines",
                line={"width": 1, "color": palette.get(col, "#2ecc71")},
                fill="tonexty",
                hovertemplate=f"%{{x|%Y-%m}}<br>{col}: %{{y:,.2f}}<extra></extra>"
            ))

        # 支出堆叠(负向)
        for col in expense_cols:
            fig.add_trace(go.Scatter(
                x=x, y=-work[col], name=col,
                stackgroup="expense", mode="lines",
                line={"width": 1, "color": palette.get(col, "#e74c3c")},
                fill="tonexty",
                hovertemplate=f"%{{x|%Y-%m}}<br>{col}: -%{{y:,.2f}}<extra></extra>"
            ))

        # 结余线
        fig.add_trace(go.Scatter(
            x=x, y=work["surplus"], name="结余",
            mode="lines+markers",
            line={"width": 2, "color": palette.get("surplus", "#34495e")},
            hovertemplate="%{x|%Y-%m}<br>结余: %{y:,.2f}<br>结余率: %{customdata:.1%}<extra></extra>",
            customdata=work["surplus_rate"]
        ))

        # 工资发放日标注(垂直虚线 + 文本)
        if payday_map:
            xmin, xmax = x.min(), x.max()
            for m, d in payday_map.items():
                if d >= xmin and d <= xmax:
                    fig.add_vline(
                        x=d, line_width=1, line_dash="dot", line_color="#7f8c8d"
                    )
                    fig.add_annotation(
                        x=d, y=max(work["total_income"].max(), work["surplus"].max()),
                        text="工资日", showarrow=False, yshift=10,
                        font={"size": 10, "color": "#7f8c8d"}
                    )

        # 异常标注
        def _annotate(idx: int, text: str, color: str = "#c0392b"):
            fig.add_annotation(
                x=work.loc[idx, month_col],
                y=work.loc[idx, "surplus"],
                text=text,
                showarrow=True, arrowhead=2, ax=0, ay=-30,
                font={"size": 10, "color": color},
                bgcolor="rgba(255,255,255,0.7)"
            )

        if "income" in anomalies:
            for i in anomalies["income"]:
                _annotate(i, "收入异常")
        if "expense" in anomalies:
            for i in anomalies["expense"]:
                _annotate(i, "支出异常")
        if "surplus_rate" in anomalies:
            for i in anomalies["surplus_rate"]:
                _annotate(i, "结余率异常")

        fig.update_layout(
            title=title or "近三个月收支堆叠面积图(含结余与标注)",
            hovermode="x unified",
            legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
            margin=dict(l=40, r=20, t=60, b=40),
            template="plotly_white"
        )
        fig.update_yaxes(
            title="金额(收入为正,支出为负)",
            zeroline=True, zerolinewidth=1, zerolinecolor="#95a5a6"
        )
        fig.update_xaxes(
            dtick="M1", tickformat="%Y-%m", showgrid=True
        )

    elif library == "matplotlib":
        import matplotlib.pyplot as plt
        fig, ax = plt.subplots(figsize=(9, 5))
        x = work[month_col].dt.to_pydatetime()

        # 收入堆叠
        income_data = [work[col].values for col in income_cols]
        ax.stackplot(x, *income_data, labels=income_cols,
                     colors=[palette.get(c, "#2ecc71") for c in income_cols], alpha=0.85)

        # 支出堆叠(负值)
        expense_data = [(-work[col].values) for col in expense_cols]
        ax.stackplot(x, *expense_data, labels=expense_cols,
                     colors=[palette.get(c, "#e74c3c") for c in expense_cols], alpha=0.85)

        # 结余线
        ax.plot(x, work["surplus"], label="结余", color=palette.get("surplus", "#34495e"), linewidth=2, marker="o")

        # 工资日
        if payday_map:
            for m, d in payday_map.items():
                if d >= work[month_col].min() and d <= work[month_col].max():
                    ax.axvline(d, color="#7f8c8d", linestyle=":", linewidth=1)
                    ax.text(d, work["surplus"].max(), "工资日", color="#7f8c8d", fontsize=9, va="bottom")

        # 异常标注
        def _annotate(idx: int, text: str, color: str = "#c0392b"):
            ax.annotate(text, xy=(x[idx], work.loc[idx, "surplus"]),
                        xytext=(0, -25), textcoords="offset points",
                        arrowprops=dict(arrowstyle="->", color=color),
                        fontsize=9, color=color,
                        bbox=dict(boxstyle="round,pad=0.2", fc="white", alpha=0.7))

        if "income" in anomalies:
            for i in anomalies["income"]:
                _annotate(i, "收入异常")
        if "expense" in anomalies:
            for i in anomalies["expense"]:
                _annotate(i, "支出异常")
        if "surplus_rate" in anomalies:
            for i in anomalies["surplus_rate"]:
                _annotate(i, "结余率异常")

        ax.set_title(title or "近三个月收支堆叠面积图(含结余与标注)")
        ax.set_ylabel("金额(收入为正,支出为负)")
        ax.grid(True, axis="y", linestyle="--", alpha=0.3)
        ax.legend(loc="upper center", ncol=3, bbox_to_anchor=(0.5, 1.12))

    else:
        raise ValueError("library 必须为 'plotly' 或 'matplotlib'")

    diagnostics = {
        "anomalies": anomalies,
        "paydays": payday_map
    }
    metrics_df = work[[month_col, "total_income", "total_expense", "surplus", "surplus_rate"]].copy()

    if return_components:
        return fig, metrics_df, diagnostics
    return fig

使用示例

注意:为遵循“所有图表必须基于真实数据”的规则,以下示例只展示如何加载与调用。请用您真实的近三个月数据文件或DataFrame替换示例中的路径或变量。

示例一:基础用法(Plotly,CSV加载)

import pandas as pd

# 请准备真实数据CSV,列至少包含:
# month, income_salary, income_side, expense_rent, expense_dining, expense_transport
df = pd.read_csv("your_finance_3m.csv")

fig = monthly_cashflow_area(
    df=df,
    income_cols=["income_salary", "income_side"],
    expense_cols=["expense_rent", "expense_dining", "expense_transport"],
    month_col="month",
    library="plotly",
    title="近三个月收支堆叠面积图"
)
fig.show()

示例二:标注工资发放日 + 自定义异常检测与配色

import pandas as pd

df = pd.read_csv("your_finance_3m.csv")

custom_colors = {
    "income_salary": "#2ecc71",
    "income_side": "#1abc9c",
    "expense_rent": "#e74c3c",
    "expense_dining": "#f39c12",
    "expense_transport": "#d35400",
    "surplus": "#34495e"
}

# 将每个月映射到实际工资发放日(请填入真实日期)
salary_paydays = {
    "2025-08": "2025-08-25",
    "2025-09": "2025-09-25",
    "2025-10": "2025-10-25"
}

fig, metrics, diag = monthly_cashflow_area(
    df=df,
    income_cols=["income_salary", "income_side"],
    expense_cols=["expense_rent", "expense_dining", "expense_transport"],
    month_col="month",
    salary_paydays=salary_paydays,
    anomaly_on="total",
    anomaly_method="pct_change",
    anomaly_threshold=0.25,   # 环比变化超过±25%标记为异常
    colors=custom_colors,
    library="plotly",
    title="近三个月收支堆叠面积图(工资日与异常标注)",
    return_components=True
)

print("指标:")
print(metrics)       # 查看每月总收入/总支出/结余/结余率
print("诊断:", diag) # 查看异常索引、工资日字典
fig.show()

示例三:Matplotlib静态图(适合离线报告)

import pandas as pd

df = pd.read_csv("your_finance_3m.csv")

fig = monthly_cashflow_area(
    df=df,
    income_cols=["income_salary", "income_side"],
    expense_cols=["expense_rent", "expense_dining", "expense_transport"],
    month_col="month",
    library="matplotlib",
    title="近三个月收支堆叠面积图(静态)"
)

import matplotlib.pyplot as plt
plt.show()

图表效果描述

  • 视觉编码:
    • 收入为正向堆叠面积(绿色系),支出为负向堆叠面积(红橙系),中轴为零线。通过上下堆叠形成“收支对比”,可一眼看出各类别占比与总额差异。
    • 结余使用深蓝灰折线+标记点展示,放在同一坐标系,便于观察盈余随月变化。
  • 交互提示(Plotly):
    • 鼠标悬停显示月份、类别与金额,收入显示正值,支出显示带负号的数值(强调支出方向)。
    • 统一的hover模式(x unified)在同一竖直线同时显示各项。
  • 异常标注:
    • 默认在总收入与总支出上检测环比变化超过±25%的月份,并在结余线附近添加“收入异常/支出异常”提示。
    • 可切换为对“结余率”做z-score检测(如阈值1.5),适合数据稳定但存在异常波动的场景。
  • 工资发放日:
    • 在实际工资发放日期处绘制垂直虚线,并在顶部添加“工资日”标注,帮助关联当月现金流峰值与薪资入账时点。
  • 堆叠面积图配置建议:
    • Stack分组:收入与支出分别使用不同stackgroup,支出以负值显示,避免误导总额。
    • 透明度:面积图填充透明度建议在0.8左右,既突出又不过度遮挡。
    • 网格与零线:保留y轴零线与浅色网格,帮助识别正负区域与变化幅度。
    • 轴刻度:x轴按月份显示(%Y-%m),保证时间序可读;y轴单位统一(如元或千元),必要时添加千分位格式。

扩展建议

  • 趋势分析增强:
    • 引入三期移动平均(3M MA)或指数平滑,辅助识别收支趋势(不替代原值,仅作为辅线)。
    • 增加同比(YoY)与环比(MoM)标签,在hover中显示变化比例。
  • 异常识别个性化:
    • 分类别异常阈值(如房租一般稳定、餐饮和出行波动较大),支持为不同列设置差异化阈值。
    • 将异常划分为“高于预期/低于预期”,并用不同颜色的标注文本区分。
  • 指标完善:
    • 结余率边界处理(当月收入为0时显示为NaN并在hover中提示“收入为0,结余率不可计算”)。
    • 引入预算列(如budget_dining、budget_transport),在图中叠加预算线,辅助超支识别。
  • 配色与主题:
    • 支持深色主题(dark)与高对比主题(colorblind-friendly),提升可访问性。
  • 数据接口与验证:
    • 增加数据校验函数,检查负值、缺失值与异常极值,提供清洗建议但不篡改原始数据。
    • 支持多币种场景(自动换算到统一单位或分面显示)。

如需,我可以将以上模块封装为独立Python包,附带更详细的文档与单元测试模板,方便在您的个人理财应用中直接集成。

可视化功能概述

本方案面向“消费类别按周跟踪+月度环比+7日平滑”的多序列折线可视化需求,适配餐饮/购物/出行/数码等常见类别。核心特点:

  • 多序列折线:同一坐标系内展示多个消费类别的趋势(每日聚合 + 7日平滑),并可叠加每周总额标记。
  • 时间范围智能裁剪:自动截取数据中的近六个月(可手动设定起止日期)。
  • 趋势分析增强:
    • 7日移动平均,减少日内噪声,突出趋势。
    • 月环比计算与标注,突出月初月末波动点。
    • 周末高亮(阴影带),帮助识别周末消费模式。
    • 异常点自动检测与注释(IQR/标准差两种方法)。
  • 模块化接口:输入原始流水数据(日期、类别、金额),即可安全生成图表,无需人工拼接;函数返回图表对象与衍生数据,便于二次分析或导出。
  • 信息可视化最佳实践:清晰图例、合理配色、显式坐标轴与单位、注释不过度拥挤。

注意:出图严格基于用户提供的真实数据;示例仅展示调用方法,不包含虚构数据或图像。

核心接口定义

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from dataclasses import dataclass
from typing import List, Optional, Dict, Tuple

@dataclass
class TrendOutputs:
    fig: plt.Figure
    ax: plt.Axes
    daily: pd.DataFrame           # 每日各类别支出(原始聚合)
    daily_ma: pd.DataFrame        # 每日各类别7日移动平均
    weekly: pd.DataFrame          # 每周各类别支出
    monthly: pd.DataFrame         # 每月各类别支出
    monthly_mom: pd.Series        # 总支出月环比(近六个月范围内)
    anomalies: Dict[str, pd.DataFrame]  # 各类别异常点明细

def prepare_category_timeseries(
    df: pd.DataFrame,
    date_col: str = "date",
    category_col: str = "category",
    amount_col: str = "amount",
    categories: Optional[List[str]] = None,
    start: Optional[pd.Timestamp] = None,
    end: Optional[pd.Timestamp] = None,
    tz: Optional[str] = None
) -> Tuple[pd.DataFrame, pd.DatetimeIndex, List[str]]:
    """
    将原始流水标准化为每日x类别矩阵,自动裁剪近6个月范围。
    参数:
      - df: 包含日期、类别、金额的流水数据(真实数据,金额支出为正值)
      - date_col/category_col/amount_col: 列名
      - categories: 指定仅分析的类别列表(缺省为数据内出现的类别)
      - start/end: 时间范围;若缺省,默认 end=数据最大日期,start=end-6个月
      - tz: 可选时区名称,将日期转为该时区的日期(日粒度聚合不受时区细节影响)
    返回:
      - daily_df: 行为日期、列为类别的每日支出矩阵(缺失填0)
      - idx: 日索引
      - cats: 实际使用的类别列表(按传入或数据内出现顺序)
    """
    if df.empty:
        raise ValueError("输入数据为空。请提供真实交易流水。")
    data = df.copy()
    if date_col not in data.columns or category_col not in data.columns or amount_col not in data.columns:
        raise ValueError("缺失必要列,需包含: date, category, amount。")
    data[date_col] = pd.to_datetime(data[date_col], errors="coerce")
    data = data.dropna(subset=[date_col, category_col, amount_col])
    if tz:
        data[date_col] = data[date_col].dt.tz_localize(tz) if data[date_col].dt.tz is None else data[date_col].dt.tz_convert(tz)
    data[date_col] = data[date_col].dt.tz_localize(None)  # 移除时区以稳定重采样
    data[amount_col] = pd.to_numeric(data[amount_col], errors="coerce").fillna(0.0)

    # 仅保留指定类别
    if categories is not None:
        data = data[data[category_col].isin(categories)]
        cats = list(categories)
    else:
        cats = list(data[category_col].dropna().unique())

    if data.empty:
        raise ValueError("过滤类别后数据为空。请检查类别参数或原始数据。")

    # 自动时间范围:近6个月
    max_date = data[date_col].max().normalize()
    if end is None:
        end = max_date
    else:
        end = pd.to_datetime(end).normalize()
    if start is None:
        start = end - pd.DateOffset(months=6) + pd.Timedelta(days=1)  # 包含端点
    else:
        start = pd.to_datetime(start).normalize()

    # 过滤时间窗
    data = data[(data[date_col] >= start) & (data[date_col] <= end)]
    if data.empty:
        raise ValueError("选定时间范围内无数据。请调整起止日期或确认流水。")

    # 构造每日x类别矩阵
    g = data.groupby([data[date_col].dt.normalize(), category_col])[amount_col].sum()
    daily_df = g.unstack(fill_value=0.0)
    # 补齐日期
    idx = pd.date_range(start=daily_df.index.min(), end=daily_df.index.max(), freq="D")
    daily_df = daily_df.reindex(idx, fill_value=0.0)

    # 仅保留目标列,并保证顺序
    for c in cats:
        if c not in daily_df.columns:
            daily_df[c] = 0.0
    daily_df = daily_df[cats]
    return daily_df, idx, cats

def compute_aggregates(
    daily_df: pd.DataFrame,
    ma_window: int = 7,
    week_anchor: str = "W-SUN"  # 一周结束于周日
) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
    """
    计算7日移动平均、周聚合、月聚合。
    返回:
      - daily_ma: 7日移动平均
      - weekly: 周总额
      - monthly: 月总额
    """
    daily_ma = daily_df.rolling(window=ma_window, min_periods=1).mean()
    weekly = daily_df.resample(week_anchor).sum()
    monthly = daily_df.resample("MS").sum()  # 月初对齐的每月总额
    return daily_ma, weekly, monthly

def compute_monthly_mom(monthly: pd.DataFrame) -> pd.Series:
    """
    计算总支出的月环比(所有类别合计)。
    返回:
      - monthly_mom: 按月索引的环比百分比(Series)
    """
    total = monthly.sum(axis=1)
    mom = total.pct_change().replace([np.inf, -np.inf], np.nan)
    return mom

def detect_anomalies(
    daily_df: pd.DataFrame,
    method: str = "iqr",
    iqr_k: float = 1.5,
    z_sigma: float = 3.0,
    top_per_cat: int = 3
) -> Dict[str, pd.DataFrame]:
    """
    基于每日金额检测各类别异常点,并返回前top_per_cat个最显著点。
    - method='iqr': 值 > Q3 + k*IQR 或 < Q1 - k*IQR 判为异常(稳健)
    - method='zscore': 均值±z_sigma*std
    返回:
      {category: DataFrame(index=日期, columns=['value','score'])}
    """
    out = {}
    for c in daily_df.columns:
        s = daily_df[c]
        if method == "iqr":
            q1, q3 = s.quantile(0.25), s.quantile(0.75)
            iqr = q3 - q1
            upper = q3 + iqr_k * iqr
            lower = max(0.0, q1 - iqr_k * iqr)  # 支出不应为负,取0作为下限
            mask = (s > upper) | (s < lower)
            score = (s - q3) / (iqr + 1e-9)  # 简易强度分数(上侧为正)
        else:
            mu, sd = s.mean(), s.std(ddof=0)
            sd = sd if sd > 1e-9 else 1e-9
            upper = mu + z_sigma * sd
            lower = max(0.0, mu - z_sigma * sd)
            mask = (s > upper) | (s < lower)
            score = (s - mu) / sd
        cand = s[mask]
        if cand.empty:
            out[c] = pd.DataFrame(columns=["value", "score"])
            continue
        dfc = pd.DataFrame({"value": cand, "score": score.loc[cand.index]})
        # 选择绝对分数最大的前N个
        dfc = dfc.reindex(dfc["score"].abs().sort_values(ascending=False).index)[:top_per_cat]
        out[c] = dfc.sort_index()
    return out

def plot_category_trends(
    daily_df: pd.DataFrame,
    daily_ma: pd.DataFrame,
    weekly: pd.DataFrame,
    monthly_mom: pd.Series,
    categories: List[str],
    highlight_weekends: bool = True,
    show_weekly_markers: bool = True,
    show_month_boundary: bool = True,
    annotate_mom: bool = True,
    anomalies: Optional[Dict[str, pd.DataFrame]] = None,
    title: str = "消费类别趋势(近六个月)",
    currency: Optional[str] = None,
    figsize: Tuple[int, int] = (12, 7),
    output_path: Optional[str] = None,
    style: str = "seaborn-v0_8"
) -> Tuple[plt.Figure, plt.Axes]:
    """
    绘制多序列折线图(7日平滑),可叠加周标记、周末阴影、月环比与异常点注释。
    """
    plt.style.use(style)
    fig, ax = plt.subplots(figsize=figsize)

    # 颜色循环
    prop_cycle = plt.rcParams['axes.prop_cycle']
    colors = prop_cycle.by_key()['color']
    color_map = {c: colors[i % len(colors)] for i, c in enumerate(categories)}

    # 绘制平滑曲线
    for c in categories:
        ax.plot(daily_ma.index, daily_ma[c], label=c, color=color_map[c], linewidth=2)

    # 可选:每周总额标记(在周末或周末后一日放点)
    if show_weekly_markers and not weekly.empty:
        wpos = weekly.index
        for c in categories:
            ax.scatter(wpos, weekly[c], color=color_map[c], s=20, alpha=0.7, marker="o", edgecolor="white", linewidths=0.5, zorder=3)

    # 周末高亮
    if highlight_weekends:
        start = daily_ma.index.min().normalize()
        end = daily_ma.index.max().normalize()
        # 找到每个周六,阴影覆盖周六->下周一
        saturdays = pd.date_range(start, end, freq="W-SAT")
        for sat in saturdays:
            ax.axvspan(sat, sat + pd.Timedelta(days=2), color="#f0f0f0", alpha=0.5, lw=0)

    # 月边界与环比标注
    if show_month_boundary:
        months = pd.date_range(daily_ma.index.min().normalize(), daily_ma.index.max().normalize(), freq="MS")
        for m in months:
            ax.axvline(m, color="#cccccc", linestyle="--", linewidth=1, alpha=0.7)
        if annotate_mom and not monthly_mom.empty:
            for m, v in monthly_mom.dropna().items():
                # 仅在可见范围内标注
                if m >= daily_ma.index.min() and m <= daily_ma.index.max():
                    txt = f"MoM {v:+.1%}"
                    ax.annotate(txt, xy=(m, ax.get_ylim()[1]), xytext=(0, -18),
                                textcoords="offset points", ha="left", va="top",
                                fontsize=9, color="#555555", bbox=dict(boxstyle="round,pad=0.2", fc="white", ec="#bbbbbb", alpha=0.8))

    # 异常点注释
    if anomalies:
        for c, dfc in anomalies.items():
            if dfc is None or dfc.empty:
                continue
            for dt, row in dfc.iterrows():
                ax.scatter(dt, row["value"], color=color_map[c], s=36, marker="D", edgecolor="black", linewidths=0.5, zorder=4)
                ax.annotate(f"{c} 异常: {row['value']:.0f}", xy=(dt, row["value"]),
                            xytext=(6, -10), textcoords="offset points", fontsize=8,
                            bbox=dict(boxstyle="round,pad=0.2", fc="white", ec="#bbbbbb", alpha=0.9))

    # 轴与标题
    ax.set_title(title, fontsize=14, pad=10)
    ax.set_xlim(daily_ma.index.min(), daily_ma.index.max())
    ax.xaxis.set_major_locator(mdates.WeekdayLocator(byweekday=mdates.MO, interval=1))
    ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d"))
    plt.setp(ax.get_xticklabels(), rotation=30, ha="right")
    ylab = "支出金额"
    if currency:
        ylab += f"({currency})"
    ax.set_ylabel(ylab)
    ax.grid(True, axis="y", alpha=0.2)
    ax.legend(ncol=2, frameon=False)

    plt.tight_layout()
    if output_path:
        fig.savefig(output_path, dpi=150, bbox_inches="tight")
    return fig, ax

def create_category_trend_chart(
    df: pd.DataFrame,
    date_col: str = "date",
    category_col: str = "category",
    amount_col: str = "amount",
    categories: Optional[List[str]] = None,     # 例如 ["餐饮","购物","出行","数码"]
    start: Optional[str] = None,
    end: Optional[str] = None,
    tz: Optional[str] = None,
    # 分析参数
    ma_window: int = 7,
    anomaly_method: str = "iqr",
    anomaly_iqr_k: float = 1.5,
    anomaly_sigma: float = 3.0,
    max_annotations_per_cat: int = 3,
    # 显示参数
    highlight_weekends: bool = True,
    show_weekly_markers: bool = True,
    show_month_boundary: bool = True,
    annotate_mom: bool = True,
    currency: Optional[str] = None,
    figsize=(12,7),
    style: str = "seaborn-v0_8",
    output_path: Optional[str] = None
) -> TrendOutputs:
    """
    一站式生成“近六个月、按周跟踪、7日平滑、月环比、周末高亮、异常注释”的多序列折线图。
    返回 TrendOutputs,包含图表对象与聚合数据,方便二次处理或导出。
    """
    daily_df, idx, cats = prepare_category_timeseries(
        df=df,
        date_col=date_col,
        category_col=category_col,
        amount_col=amount_col,
        categories=categories,
        start=start,
        end=end,
        tz=tz
    )
    daily_ma, weekly, monthly = compute_aggregates(daily_df, ma_window=ma_window)
    monthly_mom = compute_monthly_mom(monthly)

    anomalies = detect_anomalies(
        daily_df=daily_df,
        method=anomaly_method,
        iqr_k=anomaly_iqr_k,
        z_sigma=anomaly_sigma,
        top_per_cat=max_annotations_per_cat
    )

    fig, ax = plot_category_trends(
        daily_df=daily_df,
        daily_ma=daily_ma,
        weekly=weekly,
        monthly_mom=monthly_mom,
        categories=cats,
        highlight_weekends=highlight_weekends,
        show_weekly_markers=show_weekly_markers,
        show_month_boundary=show_month_boundary,
        annotate_mom=annotate_mom,
        anomalies=anomalies,
        title="消费类别趋势(近六个月,周跟踪 + 7日平滑)",
        currency=currency,
        figsize=figsize,
        output_path=output_path,
        style=style
    )

    return TrendOutputs(
        fig=fig,
        ax=ax,
        daily=daily_df,
        daily_ma=daily_ma,
        weekly=weekly,
        monthly=monthly,
        monthly_mom=monthly_mom,
        anomalies=anomalies
    )

参数说明要点:

  • df:真实流水数据,至少包含 date(日期/时间)、category(类别)、amount(金额)列;支出请以正值表示。
  • categories:可指定仅分析的类别,例如 ["餐饮","购物","出行","数码"]。
  • start/end:可手动设定起止日期(YYYY-MM-DD);缺省自动取近6个月。
  • ma_window:移动平均窗口(默认7天)。
  • anomaly_method:异常检测方法,"iqr"(稳健,默认)或 "zscore"。
  • highlight_weekends:周末阴影高亮。
  • show_weekly_markers:叠加每周总额散点。
  • annotate_mom:月环比文字标注(以总支出计算)。
  • currency:货币单位字符串(如 "CNY"、"¥")。

使用示例

以下示例仅演示如何调用接口,不会生成任何基于虚构数据的图表。请替换为您自己的真实流水数据后再运行。

示例1:最简用法(默认近6个月、自动类别、周末高亮、7日平滑、异常点注释)

import pandas as pd

# 读取您自己的流水数据(需包含 date, category, amount 列)
df = pd.read_csv("transactions.csv")  # 例如:date=2025-06-01, category=餐饮, amount=58.0

out = create_category_trend_chart(
    df=df,
    date_col="date",
    category_col="category",
    amount_col="amount",
    currency="CNY"
)

# 可选:查看衍生数据
print(out.weekly.tail())
print(out.monthly_mom.dropna())

示例2:限定类别为餐饮/购物/出行/数码,设定时间范围,使用IQR异常检测并导出图片

selected = ["餐饮","购物","出行","数码"]
out = create_category_trend_chart(
    df=df,
    categories=selected,
    start="2025-05-01",
    end="2025-10-31",
    anomaly_method="iqr",
    anomaly_iqr_k=1.5,
    max_annotations_per_cat=2,
    output_path="category_trend.png",
    currency="¥"
)

示例3:关闭周末高亮,仅展示平滑曲线并保留月环比

out = create_category_trend_chart(
    df=df,
    categories=["餐饮","购物","出行","数码"],
    highlight_weekends=False,
    show_weekly_markers=False,
    annotate_mom=True,
    currency="CNY"
)

图表效果描述

  • 坐标与刻度:
    • X轴:按天显示,主刻度为每周一;每月初以虚线标记,便于感知月度边界。
    • Y轴:支出金额(可显示货币单位),网格线仅对Y轴开启,降低视觉干扰。
  • 多序列折线:
    • 每个消费类别一条平滑曲线(7日移动平均),线宽较粗以突出趋势,多类别使用一致可辨配色。
    • 可选在每周结束日(周日)叠加周总额小圆点,帮助快速感知“周消费强度”。
  • 周末高亮:
    • 周六到下周一的背景阴影统一浅灰,快速识别周末消费模式与峰谷。
  • 月度环比:
    • 每月初在图顶端标注“MoM +x.x%/−x.x%”,以所有类别总支出的环比为准;月初/月底波动在月边界附近更显著。
  • 异常点注释:
    • 每类取最多N个显著异常点(IQR或Z分数),以菱形点+文字标注金额,便于定位异常支出行为。
  • 视觉层级:
    • 主要信息:类别趋势曲线
    • 辅助信息:周标记、周末阴影、月边界
    • 提示信息:异常注释与MoM标注(轻边框白底,避免遮挡)

准确性与避免误导的设计:

  • 7日平滑基于每日真实聚合值计算,不改变原始金额,仅用于降噪;若需查看原始日值,可在plot函数中增加原始细线绘制(默认关闭以防拥挤)。
  • 周/月聚合严格由日数据重采样得到,时间范围明确标注,避免“跨区间比较”误差。
  • 异常检测阈值可配置,默认IQR法稳健对抗极端值,避免误将自然波动判为异常。

扩展建议

  • 细化维度与对比:
    • 新增“总支出”曲线(所有类别之和),并与各类别同图对比或放置于次坐标轴。
    • 新增“预算线/目标线”作为参考基线,支持超标提醒。
  • 交互与导出:
    • 可增加Plotly版本交互图(缩放、悬停明细、导出PNG),并保持同样的数据处理流程。
  • 更多分析能力:
    • 工作日/周末分组对比条形图,量化“周末效应”。
    • 类别间相关性热力图,识别同时上升/下降的类别组合。
    • 趋势分解(STL)识别季节性与长期趋势,适用于高频、长期数据。
  • 健壮性与国际化:
    • 自动货币单位缩放(千/万/十万),并在坐标轴标注单位缩写。
    • 字体配置与本地化(中文字体、负号显示、日期本地化)。

使用须知与数据规范:

  • 请确保仅使用真实流水数据进行出图,示例不包含任何虚构数据。
  • 金额列请以正值表示支出;如有收入,请在预处理时过滤或以单独类别管理,避免与支出混淆。
  • 时间戳包含时区时请指定 tz 参数以统一到同一时区后再按日聚合。

示例详情

解决的问题

把 AI 变成你的“理财数据可视化解决方案设计师”,围绕月度收支、资产分布、消费趋势等核心场景,快速产出清晰、准确且可扩展的图表方案与落地示例。通过一次输入(数据类型、图表类型、时间范围、分析维度),即可获得可直接应用于产品的模块化设计、场景化案例和效果说明,显著缩短开发周期,减少沟通成本与试错时间,提升用户体验与留存,并为后续功能扩展打下坚实基础。

适用用户

理财应用初创开发者

快速搭建收支趋势、资产分布与消费分析模块,缩短首版上线周期,形成可复用的可视化框架。

数据可视化工程师

基于模板配置生成稳定图表方案,敏捷定位异常数据并优化展示逻辑,提高交付质量与效率。

产品经理与增长负责人

无需深度编码即可规划可视化路线,验证用户理解路径,优化图表呈现以提升转化与留存。

特征总结

一键生成月度收支、资产分布、消费趋势图,快速上线理财可视化模块。
自动拆解开发任务与模块结构,给出清晰步骤,显著降低搭建难度。
提供可直接复制的示例与参数提示,帮助快速落地,节省研发时间成本。
智能识别收支趋势与异常波动,直观标注关键变化点,辅助决策。
支持多数据类型与多种图表形式,按需切换视角与维度,展示更全面。
以真实数据为基础规范呈现,减少误读与偏差,提升用户信任与留存。
模板化配置与参数化调用,轻松复用到不同理财场景,减少重复工作。
输出清晰的图表效果描述与优化建议,快速提升视觉表现与交互体验。
模块化架构可扩展,后续新增指标与图表无需大改动,维护更轻松。

如何使用购买的提示词模板

1. 直接在外部 Chat 应用中使用

将模板生成的提示词复制粘贴到您常用的 Chat 应用(如 ChatGPT、Claude 等),即可直接对话使用,无需额外开发。适合个人快速体验和轻量使用场景。

2. 发布为 API 接口调用

把提示词模板转化为 API,您的程序可任意修改模板参数,通过接口直接调用,轻松实现自动化与批量处理。适合开发者集成与业务系统嵌入。

3. 在 MCP Client 中配置使用

在 MCP client 中配置对应的 server 地址,让您的 AI 应用自动调用提示词模板。适合高级用户和团队协作,让提示词在不同 AI 工具间无缝衔接。

AI 提示词价格
¥20.00元
先用后买,用好了再付款,超安全!

您购买后可以获得什么

获得完整提示词模板
- 共 603 tokens
- 4 个可调节参数
{ 数据类型 } { 图表类型 } { 时间范围 } { 分析维度 }
获得社区贡献内容的使用权
- 精选社区优质案例,助您快速上手提示词
限时免费

不要错过!

免费获取高级提示词-优惠即将到期

17
:
23
小时
:
59
分钟
:
59