×
¥
查看详情
🔥 会员专享 文生文 数据可视化

理财数据可视化开发助手

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

🎯 可自定义参数(4个)

数据类型
需要进行分析和可视化的理财数据类型
图表类型
期望生成的数据可视化图表类型
时间范围
数据分析覆盖的时间范围
分析维度
数据分析的主要维度和关注点

🎨 效果示例

可视化功能概述

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

  • 收入(工资、副业)与支出(房租、餐饮、出行)的分项堆叠展示,收入为正向堆叠、支出为负向堆叠,直观体现收支结构。
  • 自动计算每月结余与结余率,结余以折线显示,便于识别盈余变化。
  • 标注异常波动(支持百分比变化阈值或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 参数以统一到同一时区后再按日聚合。

示例详情

📖 如何使用

30秒出活:复制 → 粘贴 → 搞定
与其花几十分钟和AI聊天、试错,不如直接复制这些经过千人验证的模板,修改几个 {{变量}} 就能立刻获得专业级输出。省下来的时间,足够你轻松享受两杯咖啡!
加载中...
💬 不会填参数?让 AI 反过来问你
不确定变量该填什么?一键转为对话模式,AI 会像资深顾问一样逐步引导你,问几个问题就能自动生成完美匹配你需求的定制结果。零门槛,开口就行。
转为对话模式
🚀 告别复制粘贴,Chat 里直接调用
无需切换,输入 / 唤醒 8000+ 专家级提示词。 插件将全站提示词库深度集成于 Chat 输入框。基于当前对话语境,系统智能推荐最契合的 Prompt 并自动完成参数化,让海量资源触手可及,从此彻底告别"手动搬运"。
即将推出
🔌 接口一调,提示词自己会进化
手动跑一次还行,跑一百次呢?通过 API 接口动态注入变量,接入批量评价引擎,让程序自动迭代出更高质量的提示词方案。Prompt 会自己进化,你只管收结果。
发布 API
🤖 一键变成你的专属 Agent 应用
不想每次都配参数?把这条提示词直接发布成独立 Agent,内嵌图片生成、参数优化等工具,分享链接就能用。给团队或客户一个"开箱即用"的完整方案。
创建 Agent

✅ 特性总结

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

🎯 解决的问题

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

🕒 版本历史

当前版本
v2.1 2024-01-15
优化输出结构,增强情节连贯性
  • ✨ 新增章节节奏控制参数
  • 🔧 优化人物关系描述逻辑
  • 📝 改进主题深化引导语
  • 🎯 增强情节转折点设计
v2.0 2023-12-20
重构提示词架构,提升生成质量
  • 🚀 全新的提示词结构设计
  • 📊 增加输出格式化选项
  • 💡 优化角色塑造引导
v1.5 2023-11-10
修复已知问题,提升稳定性
  • 🐛 修复长文本处理bug
  • ⚡ 提升响应速度
v1.0 2023-10-01
首次发布
  • 🎉 初始版本上线
COMING SOON
版本历史追踪,即将启航
记录每一次提示词的进化与升级,敬请期待。

💬 用户评价

4.8
⭐⭐⭐⭐⭐
基于 28 条评价
5星
85%
4星
12%
3星
3%
👤
电商运营 - 张先生
⭐⭐⭐⭐⭐ 2025-01-15
双十一用这个提示词生成了20多张海报,效果非常好!点击率提升了35%,节省了大量设计时间。参数调整很灵活,能快速适配不同节日。
效果好 节省时间
👤
品牌设计师 - 李女士
⭐⭐⭐⭐⭐ 2025-01-10
作为设计师,这个提示词帮我快速生成创意方向,大大提升了工作效率。生成的海报氛围感很强,稍作调整就能直接使用。
创意好 专业
COMING SOON
用户评价与反馈系统,即将上线
倾听真实反馈,在这里留下您的使用心得,敬请期待。
加载中...
📋
提示词复制
在当前页面填写参数后直接复制: