You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Python中基于DataFrame动态更新条形图

动态更新DataFrame条形图的正确实现方案

问题背景

我基于DataFrame d绘制了条形图,需要通过遍历第二个DataFrame new_d的每一行来更新d的数据,进而动态刷新条形图。尝试用FuncAnimation实现但效果不符合预期,求可行的解决方法。

原始数据与代码

原始DataFrame d

Position    Operation   Side    Price   Size
1   9   0   1   0.7289  -19
2   8   0   1   0.729   -427
3   7   0   1   0.7291  -267
4   6   0   1   0.7292  -18
5   5   0   1   0.7293  -16
6   4   0   1   0.7294  -16
7   3   0   1   0.7295  -429
8   2   0   1   0.7296  -23
9   1   0   1   0.7297  -31
10  0   0   0   0.7299  41
11  1   0   0   0.73    9
12  2   0   0   0.7301  10
13  3   0   0   0.7302  18
14  4   0   0   0.7303  16
15  5   0   0   0.7304  18
16  6   0   0   0.7305  429
17  7   0   0   0.7306  16
18  8   0   0   0.7307  268
19  9   0   0   0.7308  18

初始绘图代码

import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib.ticker as ticker
import pandas as pd
from matplotlib.animation import FuncAnimation

f, ax = plt.subplots()
sns.set_color_codes('muted')

sns.barplot(data = d[d.Side==0], x = 'Size', y = 'Price', color = 'b', orient = 'h', native_scale=True)
sns.barplot(data = d[d.Side==1], x = 'Size', y = 'Price', color = 'r', orient = 'h', native_scale=True)
ax.yaxis.set_major_locator(ticker.MultipleLocator(.0001))
sns.despine()

待遍历的更新数据new_d

Position    Operation   Side    Price   Size
34  0   1   0   0.7299  39
35  1   1   0   0.73    9
36  3   1   0   0.7302  18
37  0   1   1   0.7298  -8
38  0   1   1   0.7298  -9
39  0   1   1   0.7298  -8
40  0   1   1   0.7298  -9
41  0   1   1   0.7298  -14
42  0   1   1   0.7298  -9
43  0   2   1   0.0 0
44  9   0   1   0.7288  -17
45  0   1   1   0.7297  -29
46  1   1   1   0.7296  -23
47  9   2   1   0.0 0
48  0   0   1   0.7298  -3
49  1   1   1   0.7297  -31
50  0   1   1   0.7298  -10
51  0   1   0   0.7299  41
52  0   1   1   0.7298  -4
53  0   2   1   0.0 0
54  9   0   1   0.7288  -17
55  9   2   0   0.0 0
56  0   0   0   0.7298  2
57  0   1   0   0.7298  4
58  1   1   0   0.7299  39
59  0   1   0   0.7298  5

尝试的FuncAnimation代码(存在问题)

def init():
    # f, ax = plt.subplots()
    sns.set_color_codes('muted')
   
    sns.barplot(data = d[d.Side==0], x = 'Size', y = 'Price', color = 'b', orient = 'h', native_scale=True)
    s = sns.barplot(data = d[d.Side==1], x = 'Size', y = 'Price', color = 'r', orient = 'h', native_scale=True)
    
    sns.despine()
    
    return s
    
    
def update_d(row):   
    
    if row.Operation == 1:
        d.loc[((d.Position==row.Position) & (d.Side==row.Side)), 'Size'] = row.Size
            
        
    elif row.Operation == 2:
        idx = d.loc[((d.Position==row.Position) & (d.Side==row.Side))].index
        d.drop(idx, inplace=True)
        
        
    elif row.Operation == 0:
        d = pd.concat([pd.DataFrame([[row.Time, row.Symbol, row.Position, row.Operation, row.Side, row.Price, row.Size]], columns=d.columns), d], ignore_index=True)
        d['Position'] = d.groupby('Side')['Price'].rank().astype('int').sub(1)        
        d.sort_values('Price', inplace=True)    

              
    sns.barplot(data = d[d.Side==0], x = 'Size', y = 'Price', color = 'b', orient = 'h', native_scale=True)
    s = sns.barplot(data = d[d.Side==1], x = 'Size', y = 'Price', color = 'r', orient = 'h', native_scale=True)
     
    return s
 

f, ax = plt.subplots()
ax.yaxis.set_major_locator(ticker.MultipleLocator(.0001))
    
ani = FuncAnimation(f, update_d, init_func=init, frames=new_d[20:].iterrows(), interval = 100)

plt.show()

问题分析

  1. 图层叠加:每次调用sns.barplot都会在原有图层上新增条形,导致画面混乱。
  2. DataFrame作用域:update_d函数中Operation=0分支重新赋值了d,但这是局部变量,没有修改全局的d。
  3. 动画更新逻辑错误:FuncAnimation需要更新现有绘图元素的属性,而非重新绘制整个图表;同时init和update函数返回的对象不符合要求,无法触发正确的重绘。

正确实现方案

核心思路是:初始化时保留条形图的patches对象,每次更新时先修改d的数据,再更新patches的宽度(水平条形图的长度对应x轴数值),最后调整轴范围。

import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib.ticker as ticker
import pandas as pd
from matplotlib.animation import FuncAnimation

# 先对原始数据d按Price排序,保证y轴顺序稳定
d.sort_values('Price', inplace=True)

# 初始化画布和轴
f, ax = plt.subplots()
sns.set_color_codes('muted')
ax.yaxis.set_major_locator(ticker.MultipleLocator(.0001))
sns.despine()

# 初始绘制两个条形图,保存patches对象
bar0 = sns.barplot(data=d[d.Side==0], x='Size', y='Price', color='b', orient='h', native_scale=True, ax=ax)
bar1 = sns.barplot(data=d[d.Side==1], x='Size', y='Price', color='r', orient='h', native_scale=True, ax=ax)

# 合并所有patches,方便统一更新
all_patches = list(bar0.patches) + list(bar1.patches)

def init():
    # 初始化时返回需要更新的元素
    return all_patches

def update(frame):
    # frame是new_d的一行,iterrows返回(index, row),取第二个元素
    _, row = frame
    
    global d  # 声明使用全局d
    
    # 执行数据更新逻辑
    if row.Operation == 1:
        # 更新现有行的Size
        mask = (d.Position == row.Position) & (d.Side == row.Side)
        d.loc[mask, 'Size'] = row.Size
    elif row.Operation == 2:
        # 删除指定行
        mask = (d.Position == row.Position) & (d.Side == row.Side)
        d.drop(d[mask].index, inplace=True)
    elif row.Operation == 0:
        # 新增行:匹配原始d的列结构
        new_row = pd.DataFrame({
            'Position': [row.Position],
            'Operation': [row.Operation],
            'Side': [row.Side],
            'Price': [row.Price],
            'Size': [row.Size]
        })
        d = pd.concat([new_row, d], ignore_index=True)
        # 重新计算Position:按Side分组,Price排序后排名减1
        d['Position'] = d.groupby('Side')['Price'].rank(ascending=False).astype(int).sub(1)
        d.sort_values('Price', inplace=True)
    
    # 遍历每个patch,更新宽度
    sorted_prices = d['Price'].unique()
    sorted_prices.sort()
    
    for patch in all_patches:
        # 获取条形中心对应的y轴刻度值(即Price)
        y_val = patch.get_y() + patch.get_height()/2
        tick_idx = int(round(y_val / ax.get_yticklabels()[0].get_position()[1]))
        price = ax.get_yticks()[tick_idx]
        # 获取对应Price的Size值
        size = d.loc[d['Price'] == price, 'Size'].values[0]
        # 更新条形宽度
        patch.set_width(size)
    
    # 调整x轴范围,确保所有条形完整显示
    min_size = d['Size'].min()
    max_size = d['Size'].max()
    ax.set_xlim(min_size * 1.1, max_size * 1.1)
    
    # 返回需要更新的元素
    return all_patches

# 创建动画,保持原代码的new_d切片范围
ani = FuncAnimation(f, update, init_func=init, frames=new_d[20:].iterrows(), interval=100, blit=True)

plt.show()

关键改进点

  1. 保留绘图元素引用:初始化时保存barplot返回的对象,获取其patches属性,后续直接更新这些patch的宽度,避免重复绘图。
  2. 修正DataFrame作用域:用global声明使用全局的d,确保数据修改生效。
  3. 轴范围自适应:每次更新后调整x轴范围,保证所有条形都能完整显示。
  4. 精准匹配数据与绘图元素:通过y轴刻度值关联DataFrame中的Price数据,确保每个条形都对应正确的Size值。

内容的提问来源于stack exchange,提问作者Chris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 20:25:55