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

Bokeh绘图中为每个datapoint分配唯一颜色失败求助

问题:散点图数据点无法按type_id分配唯一颜色,全部显示最后一种颜色

场景与问题

需要为每个数据点根据type_id分配唯一颜色,但运行代码后所有数据点仅显示最后一种颜色。

原始代码

for index, row in df.iterrows():
        print(row['type'],row['type_id'])    
###########################################################
    color_index=df['type_id'].max()
    
    for index, row in df.iterrows():
        color_index = row['type_id'] % len(Category20)
        # Extract the time values for this event type as a Pandas Series object
        time_series = df['time']
        
        # Convert each Pandas Timestamp object to a Python datetime object, then to a Unix timestamp (seconds since 1970-01-01)
        timestamps = [t.to_pydatetime().timestamp() for t in time_series]

        # Compute the number of seconds since midnight for each timestamp by taking the modulo with 86400 (the number of seconds in a day)
        seconds_since_midnight = [ts % 86400 for ts in timestamps]

        # Create a ColumnDataSource object with the data for this event type
        source_data = dict(x=seconds_since_midnight,y=[0]*len(seconds_since_midnight),desc=time_series.dt.strftime('%Y-%m-%d %H:%M:%S'))

        for col_name in df.columns:

            if col_name.startswith('details'):

                source_data[col_name] = df[col_name].tolist()
        
        source = ColumnDataSource(data=source_data)       
        # Add a scatter plot glyph to the figure using the data from this event type's ColumnDataSource object and assign it a color from the Category10 palette and increase its size to 10 pixels.
        p.scatter('x', 'y', source=source, legend_label=row['type'],color=Category20[20][row['type_id']], size=10)

    
    #legend_labels = [item.label['value'] for item in p.legend.items]

    # Define tooltips dictionary containing label-value pairs for each column starting with 'details'
    tooltips_dict = {'Time': '@desc'}

    limited_df = df[['details.start_url','details.username']]
    for col_name in limited_df.columns:
       split_col = col_name.rsplit('.', 1)
       new_col = split_col[1] if len(split_col) > 1 else col_name
       tooltips_dict[new_col] = f': @{{{col_name}}}'
            
    # Add hover tool that displays all columns starting with 'details' when hovering over their dots
    hover_tool = HoverTool(tooltips=[(label, value) for label,value in tooltips_dict.items()])

    # Add hover tool and wheel zoom tool to our plot
    p.add_tools(hover_tool)

    # Remove tick lines on y-axis 
    p.yaxis.minor_tick_line_color = None 
    p.yaxis.major_tick_line_color = None 

    p.xaxis.minor_tick_line_color = None 
    p.xaxis.major_tick_line_color = None 

    # Show plot in web browser

    p.y_range = Range1d(y_min - 0.5, y_max + 0.5)
    p.yaxis.major_label_text_font_size = '0pt'
    show(p, width=1000, height=1000)

运行前打印的type与type_id

session_created 1
leader_joined 2
control_gained 3
relocate_start 4
input_change 5
input_change 5
follower_joined 6
control_gained 3
control_gained 3
control_switch 7
host_change 8
control_gained 3
follower_joined 6
control_gained 3
click 9
input_change 5
input_change 5
session_end 10

现象

所有数据点仅显示最后一种颜色,效果图:
绘图效果

问题原因

  1. 重复绘制所有数据:循环遍历每行时,始终使用整个DataFrame的所有数据创建source_data,每次调用p.scatter都会把所有点重新画一遍,最后一次循环的颜色会覆盖之前所有点。
  2. 调色板调用错误:Category20[20]是无效写法,Category20本身就是长度为20的调色板数组,直接取索引即可。

修复方案

修改核心循环逻辑,按type_id分组处理,只绘制对应组的数据:

# 提前计算所有数据的时间转换结果,避免重复计算
df['seconds_since_midnight'] = df['time'].apply(lambda t: t.to_pydatetime().timestamp() % 86400)
df['desc'] = df['time'].dt.strftime('%Y-%m-%d %H:%M:%S')

# 按type_id分组,每组对应一种颜色
for type_id, group in df.groupby('type_id'):
    color_index = type_id % len(Category20)
    # 仅使用当前分组的数据构建数据源
    source_data = {
        'x': group['seconds_since_midnight'],
        'y': [0] * len(group),
        'desc': group['desc']
    }
    # 添加details列数据
    for col_name in df.columns:
        if col_name.startswith('details'):
            source_data[col_name] = group[col_name].tolist()
    
    source = ColumnDataSource(data=source_data)
    # 获取当前分组的事件类型名称
    type_label = group['type'].iloc[0]
    # 绘制当前分组的点,使用对应颜色
    p.scatter('x', 'y', source=source, legend_label=type_label, color=Category20[color_index], size=10)

# 以下保留原有的tooltip、轴样式设置代码
tooltips_dict = {'Time': '@desc'}
limited_df = df[['details.start_url','details.username']]
for col_name in limited_df.columns:
   split_col = col_name.rsplit('.', 1)
   new_col = split_col[1] if len(split_col) > 1 else col_name
   tooltips_dict[new_col] = f': @{{{col_name}}}'
        
hover_tool = HoverTool(tooltips=[(label, value) for label,value in tooltips_dict.items()])
p.add_tools(hover_tool)

p.yaxis.minor_tick_line_color = None 
p.yaxis.major_tick_line_color = None 
p.xaxis.minor_tick_line_color = None 
p.xaxis.major_tick_line_color = None 

p.y_range = Range1d(y_min - 0.5, y_max + 0.5)
p.yaxis.major_label_text_font_size = '0pt'
show(p, width=1000, height=1000)

修复要点

  • 提前计算时间转换结果,提升代码效率
  • 使用groupby('type_id')分组,避免重复绘制所有数据点
  • 修正Category20的调用方式,直接通过索引取对应颜色
  • 每个分组仅处理自身数据,确保不同type_id的点使用对应颜色绘制

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 13:37:04