88 lines
4.4 KiB
Python
88 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
||
"""图表 v4: L1只看L1课程, L2只看L2课程"""
|
||
import json, os
|
||
from datetime import date, timedelta
|
||
import matplotlib
|
||
matplotlib.use('Agg')
|
||
import matplotlib.pyplot as plt
|
||
import matplotlib.dates as mdates
|
||
import matplotlib.font_manager as fm
|
||
import numpy as np
|
||
|
||
fm.fontManager.addfont('/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc')
|
||
plt.rcParams['font.family'] = fm.FontProperties(fname='/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc').get_name()
|
||
plt.rcParams['axes.unicode_minus'] = False
|
||
|
||
with open('/root/.openclaw/workspace/output/course_data_v4.json') as f:
|
||
data = json.load(f)
|
||
results = data['results']
|
||
|
||
out = '/root/.openclaw/workspace/output'
|
||
|
||
configs = {
|
||
'L1': {'prefix': 'L1', 'color': '#4A90D9', 'light': '#A8CFF1', 'label': 'L1'},
|
||
'L2': {'prefix': 'L2', 'color': '#E85D47', 'light': '#F4A9A0', 'label': 'L2'},
|
||
}
|
||
|
||
for key, cfg in configs.items():
|
||
pfx = cfg['prefix']; color = cfg['color']; light = cfg['light']; label = cfg['label']
|
||
first = next(i for i, r in enumerate(results) if r[f'{pfx}_paid'] > 0)
|
||
data_sub = results[first:]
|
||
|
||
dates = [date.fromisoformat(r['ws']) for r in data_sub]
|
||
xs = [d + timedelta(days=3) for d in dates]
|
||
paid = [r[f'{pfx}_paid'] for r in data_sub]
|
||
cons_users = [r[f'{pfx}_cons_users'] for r in data_sub]
|
||
no_cons = [r[f'{pfx}_no_cons'] for r in data_sub]
|
||
avg_all = [r[f'{pfx}_avg_all'] for r in data_sub]
|
||
avg_cons = [r[f'{pfx}_avg_cons'] for r in data_sub]
|
||
|
||
# 图1: 堆叠柱状
|
||
fig, ax = plt.subplots(figsize=(18, 8))
|
||
x_idx = np.arange(len(xs))
|
||
ax.bar(x_idx, cons_users, 0.65, color=light, label='有课消用户', zorder=3)
|
||
ax.bar(x_idx, no_cons, 0.65, bottom=cons_users, color='#D0D0D0', label='无课消用户', zorder=3)
|
||
step = max(1, len(data_sub)//10)
|
||
for i in range(0, len(data_sub), step):
|
||
ax.annotate(str(paid[i]), (i, paid[i]), textcoords='offset points', xytext=(0, 5),
|
||
fontsize=7.5, ha='center', color='#333333', fontweight='bold')
|
||
ax.set_xticks(x_idx[::step])
|
||
ax.set_xticklabels([dates[i].strftime('%m/%d') for i in range(0, len(data_sub), step)], fontsize=8.5, rotation=45)
|
||
ax.set_ylabel('用户数', fontsize=13)
|
||
ax.set_title(f'{label}付费用户周课消分布(只看{label}课程,剔除U0)', fontsize=16, fontweight='bold')
|
||
ax.legend(fontsize=12, loc='upper left')
|
||
ax.grid(axis='y', alpha=0.3, zorder=0)
|
||
ax.set_xlim(-0.5, len(x_idx)-0.5)
|
||
no_rate = no_cons[-1]/paid[-1]*100 if paid[-1] else 0
|
||
ax.text(0.97, 0.95, f'付费{paid[-1]}人 | 无课消率{no_rate:.0f}%',
|
||
transform=ax.transAxes, fontsize=11, ha='right', va='top', color='#666666', fontstyle='italic')
|
||
plt.tight_layout()
|
||
plt.savefig(f'{out}/{pfx}_users_stack_v4.png', dpi=150, bbox_inches='tight', facecolor='white')
|
||
plt.close()
|
||
print(f' ✅ {pfx}_users_stack_v4.png')
|
||
|
||
# 图2: 折线
|
||
fig, ax = plt.subplots(figsize=(18, 8))
|
||
ax.plot(xs, avg_all, 'o-', color='#999999', linewidth=2.2, markersize=5, label='人均课消(全部付费用户)', markerfacecolor='white')
|
||
ax.plot(xs, avg_cons, 's-', color=color, linewidth=2.8, markersize=5, label='人均课消(有课消用户)', markerfacecolor='white')
|
||
ax.fill_between(xs, avg_all, avg_cons, alpha=0.08, color=color)
|
||
for i in range(0, len(data_sub), max(1, len(data_sub)//8)):
|
||
ax.annotate(f'{avg_all[i]:.1f}', (xs[i], avg_all[i]), textcoords='offset points',
|
||
xytext=(0,-15), fontsize=7.5, color='#999999', ha='center')
|
||
ax.annotate(f'{avg_cons[i]:.1f}', (xs[i], avg_cons[i]), textcoords='offset points',
|
||
xytext=(0,7), fontsize=7.5, color=color, ha='center', fontweight='bold')
|
||
ax.xaxis.set_major_formatter(mdates.DateFormatter('%m/%d'))
|
||
ax.xaxis.set_major_locator(mdates.MonthLocator())
|
||
plt.setp(ax.xaxis.get_majorticklabels(), rotation=45, fontsize=9)
|
||
ax.set_ylabel('课消数(节/周)', fontsize=13)
|
||
ax.set_title(f'{label}付费用户周人均课消趋势(只看{label}课程,剔除U0)', fontsize=16, fontweight='bold')
|
||
ax.legend(fontsize=12, loc='upper left')
|
||
ax.grid(True, alpha=0.3)
|
||
ax.set_xlim(date(2025,8,30), date(2026,5,12))
|
||
plt.tight_layout()
|
||
plt.savefig(f'{out}/{pfx}_avg_trend_v4.png', dpi=150, bbox_inches='tight', facecolor='white')
|
||
plt.close()
|
||
print(f' ✅ {pfx}_avg_trend_v4.png')
|
||
|
||
print('\n✅ 4张v4图表已生成')
|