Matplotlib使用bottom和height参数绘制时间轴条形图时出现数据显示异常的问题
Matplotlib使用bottom和height参数绘制时间轴条形图时出现数据显示异常的问题
嘿,我来帮你分析下这个问题!你遇到的条形图显示异常的情况,核心原因其实很简单——同一天的多个活动被挤在了x轴的同一个位置,导致它们的条形重叠在一起,视觉上看起来像是高度莫名累加,超过了24小时的上限。
问题根源拆解
你现在的代码把所有活动的x值都设为对应的日期,当同一天有多个活动时,matplotlib会把这些条形全部画在同一个x坐标点上。比如某天有两个活动:一个从2点到5点,另一个从19点到20点45分,这两个条形会完全重叠在一起,视觉上就会变成从2点延伸到20点45分的长条形,自然就突破了24小时的范围。你检查values["days"]就能发现,里面有大量重复的日期,这些重复日期对应的条形都挤在了同一个位置,最终导致了显示异常。
解决方案一:给同一天的活动添加x轴偏移
我们可以给同一天的每个活动分配一个小的x轴偏移量,让它们在日期位置上并排显示,避免重叠。修改后的代码如下:
import os, uuid, matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import pandas as pd import datetime import matplotlib.ticker import matplotlib.dates import numpy as np from collections import defaultdict dataset = pd.read_csv('input_df_3a3333a0-fd5d-4630-8707-5fc23cb0b326.csv') plt.figure(figsize=(5.55555555555556,4.16666666666667), dpi=72) # 数据预处理部分和你原来的一致 test_date_time = dataset.to_numpy().tolist() test_date_time = list(filter(lambda x: type(x[0]) is not float, test_date_time)) test_date_time = [(datetime.datetime.fromisoformat(x[0]), datetime.datetime.fromisoformat(x[1])) for x in test_date_time] test_date_time = sorted(test_date_time, key=lambda x:x[0]) # 按日期分组,整理每日的活动数据 daily_activities = defaultdict(list) for start, end in test_date_time: day = start.date() start_hour = start.hour + start.minute/60 + start.second/3600 duration = (end - start).total_seconds()/3600 daily_activities[day].append((start_hour, duration)) # 准备绘图数据,添加偏移量 x_values = [] bottom_values = [] height_values = [] bar_width = 0.2 # 单个条形的宽度,可根据需求调整 for day, activities in daily_activities.items(): num_activities = len(activities) # 计算偏移量,让同一天的条形居中排列 offsets = np.linspace(-bar_width*(num_activities-1)/2, bar_width*(num_activities-1)/2, num_activities) # 将日期转换为matplotlib可识别的数值格式 day_num = matplotlib.dates.date2num(day) for i, (start_hour, duration) in enumerate(activities): x_values.append(day_num + offsets[i]) bottom_values.append(start_hour) height_values.append(duration) # 绘制条形图 plt.bar(x=x_values, height=height_values, bottom=bottom_values, width=bar_width) # 配置x轴为日期格式,优化显示 plt.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%Y-%m-%d')) plt.gca().xaxis.set_major_locator(matplotlib.dates.DayLocator()) plt.gcf().autofmt_xdate() # 旋转日期标签,避免重叠 # 配置y轴范围和标签 plt.ylim(0, 24) plt.ylabel('Time (Hours)') plt.xlabel('Date') plt.show()
解决方案二:使用broken_barh绘制更适配的时间轴图
其实你想要的效果更接近甘特图,用broken_barh(水平分段条形图)来实现会更直观,代码示例如下:
import os, uuid, matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import pandas as pd import datetime import matplotlib.ticker import matplotlib.dates import numpy as np dataset = pd.read_csv('input_df_3a3333a0-fd5d-4630-8707-5fc23cb0b326.csv') plt.figure(figsize=(8,6), dpi=72) # 数据预处理 test_date_time = dataset.to_numpy().tolist() test_date_time = list(filter(lambda x: type(x[0]) is not float, test_date_time)) test_date_time = [(datetime.datetime.fromisoformat(x[0]), datetime.datetime.fromisoformat(x[1])) for x in test_date_time] test_date_time = sorted(test_date_time, key=lambda x:x[0]) # 给每个日期分配一个y轴位置,方便区分不同日期的活动 days = sorted(list({start.date() for start, end in test_date_time})) day_to_y = {day: i+1 for i, day in enumerate(days)} # 绘制每个活动的分段条形 for start, end in test_date_time: day_y = day_to_y[start.date()] # 计算活动的起始时间(当天的秒数)和持续时长 start_seconds = (start - datetime.datetime.combine(start.date(), datetime.time(0,0,0))).total_seconds() duration_seconds = (end - start).total_seconds() plt.broken_barh([(start_seconds, duration_seconds)], (day_y - 0.4, 0.8), facecolors='blue') # 配置y轴为日期标签 plt.yticks([i+1 for i in range(len(days))], [day.strftime('%Y-%m-%d') for day in days]) # 配置x轴为时分格式 plt.xlabel('Time of Day') plt.xlim(0, 24*3600) plt.gca().xaxis.set_major_formatter(plt.FuncFormatter(lambda x, pos: f'{int(x//3600)}:{int((x%3600)//60):02d}')) plt.gca().xaxis.set_major_locator(matplotlib.ticker.MultipleLocator(3600)) plt.tight_layout() plt.show()
备注:内容来源于stack exchange,提问作者Loufylouf
相关产品推荐
相关产品推荐

