79 lines
3.0 KiB
Python
79 lines
3.0 KiB
Python
import matplotlib
|
||
matplotlib.use('Agg')
|
||
import matplotlib.pyplot as plt
|
||
import numpy as np
|
||
|
||
plt.rcParams['font.family'] = 'sans-serif'
|
||
for f in ['WenQuanYi Micro Hei', 'Noto Sans CJK SC', 'SimHei', 'DejaVu Sans']:
|
||
try:
|
||
plt.rcParams['font.sans-serif'] = [f]
|
||
break
|
||
except:
|
||
continue
|
||
plt.rcParams['axes.unicode_minus'] = False
|
||
|
||
# Data from query
|
||
labels = ['0分钟\n(无数据)', '5-10', '10-15', '15-20', '20-25', '25-30',
|
||
'30-35', '35-40', '40-50', '50-60', '60-90', '90+']
|
||
counts = [14990, 8945, 33817, 21566, 11563, 5411, 2412, 1210, 957, 385, 313, 153]
|
||
pcts = [14.7, 8.8, 33.2, 21.2, 11.4, 5.3, 2.4, 1.2, 0.9, 0.4, 0.3, 0.2]
|
||
|
||
fig, ax = plt.subplots(figsize=(16, 7))
|
||
|
||
x = np.arange(len(labels))
|
||
colors = ['#E74C3C' if i == 0 else plt.cm.Blues(0.3 + 0.7*i/len(labels)) for i in range(len(labels))]
|
||
|
||
bars = ax.bar(x, counts, color=colors, edgecolor='white', linewidth=0.5)
|
||
|
||
# Labels
|
||
for bar, v, p in zip(bars, counts, pcts):
|
||
if v > 200:
|
||
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + max(counts)*0.015,
|
||
f'{v:,}\n({p}%)', ha='center', fontsize=9, color='#333', fontweight='bold')
|
||
|
||
ax.set_xticks(x)
|
||
ax.set_xticklabels(labels, fontsize=10)
|
||
ax.set_ylabel('完成次数', fontsize=13)
|
||
ax.set_title('最近3个月(2026.03-05)完成一个课时学习时长分布', fontsize=16, fontweight='bold')
|
||
ax.grid(axis='y', alpha=0.2, linestyle='--')
|
||
ax.set_ylim(0, max(counts) * 1.22)
|
||
|
||
# Summary box
|
||
stats_text = (
|
||
f'总完成: 101,724次\n'
|
||
f'平均: 14.8分钟\n'
|
||
f'中位数: 13.8分钟\n'
|
||
f'排除空数据平均: 17.4分钟\n'
|
||
f'无时长数据: 14,990次(14.7%)'
|
||
)
|
||
ax.text(0.98, 0.95, stats_text, transform=ax.transAxes, fontsize=11,
|
||
verticalalignment='top', horizontalalignment='right',
|
||
bbox=dict(boxstyle='round', facecolor='#FFFDE7', alpha=0.9, edgecolor='#CCC'),
|
||
fontfamily='monospace')
|
||
|
||
# Key insight annotation
|
||
ax.annotate('峰值: 10-15分钟\n占33.2%', xy=(2, 33817), xytext=(5, 28000),
|
||
arrowprops=dict(arrowstyle='->', color='#333', lw=1.5),
|
||
fontsize=11, color='#333', fontweight='bold',
|
||
bbox=dict(boxstyle='round,pad=0.3', facecolor='#FFF2CC', alpha=0.8))
|
||
|
||
# Distribution curve
|
||
from scipy import interpolate
|
||
nonzero_x = x[1:] # skip 0-min
|
||
nonzero_counts = np.array(counts[1:], dtype=float)
|
||
smooth_x = np.linspace(nonzero_x[0], nonzero_x[-1], 200)
|
||
try:
|
||
tck = interpolate.splrep(nonzero_x, nonzero_counts, s=5000)
|
||
smooth_y = interpolate.splev(smooth_x, tck)
|
||
ax.plot(smooth_x, smooth_y, '-', color='#C0392B', linewidth=2, alpha=0.6, label='平滑趋势')
|
||
ax.legend(fontsize=10)
|
||
except:
|
||
pass
|
||
|
||
fig.text(0.5, 0.01, '统计口径: 最近3个月 play_status=1 的课时完成记录, 时长=单次完成所有组件 interval_time 之和/60000(分钟)',
|
||
ha='center', fontsize=9, color='#888')
|
||
|
||
plt.tight_layout(rect=[0, 0.04, 1, 0.95])
|
||
plt.savefig('/root/.openclaw/workspace/output/single_duration_3m.png', dpi=150, bbox_inches='tight')
|
||
print('Saved.')
|