ai_member_xiaoxi/scripts/charts_v4.py
2026-05-15 08:00:01 +08:00

88 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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图表已生成')