|
@@ -38,6 +38,8 @@ for ch in channel_stats['channel']:
|
|
|
uv = ch_df['点击uv'].sum()
|
|
uv = ch_df['点击uv'].sum()
|
|
|
channel_stats.loc[channel_stats['channel'] == ch, '进入推荐率'] = (ch_df['进入推荐率'] * ch_df['点击uv']).sum() / uv
|
|
channel_stats.loc[channel_stats['channel'] == ch, '进入推荐率'] = (ch_df['进入推荐率'] * ch_df['点击uv']).sum() / uv
|
|
|
channel_stats.loc[channel_stats['channel'] == ch, '再分享回流率'] = (ch_df['再分享回流率'] * ch_df['点击uv']).sum() / uv
|
|
channel_stats.loc[channel_stats['channel'] == ch, '再分享回流率'] = (ch_df['再分享回流率'] * ch_df['点击uv']).sum() / uv
|
|
|
|
|
+ channel_stats.loc[channel_stats['channel'] == ch, '原视频回流率'] = (ch_df['原视频再分享回流率'] * ch_df['点击uv']).sum() / uv
|
|
|
|
|
+ channel_stats.loc[channel_stats['channel'] == ch, '推荐回流率'] = (ch_df['推荐再分享回流率'] * ch_df['点击uv']).sum() / uv
|
|
|
|
|
|
|
|
channel_stats = channel_stats.sort_values('点击uv', ascending=False)
|
|
channel_stats = channel_stats.sort_values('点击uv', ascending=False)
|
|
|
|
|
|
|
@@ -60,40 +62,50 @@ daily = df.groupby(['dt', 'channel']).apply(
|
|
|
}), include_groups=False
|
|
}), include_groups=False
|
|
|
).reset_index()
|
|
).reset_index()
|
|
|
|
|
|
|
|
|
|
+# ============================================================
|
|
|
|
|
+# 统一渐变颜色函数
|
|
|
|
|
+# ============================================================
|
|
|
|
|
+
|
|
|
|
|
+def get_gradient_color(val, max_val, min_val=0):
|
|
|
|
|
+ """
|
|
|
|
|
+ 统一的渐变颜色:白色(min) -> 绿色(max)
|
|
|
|
|
+ 所有矩阵使用相同的渐变逻辑,统一黑色字体
|
|
|
|
|
+ """
|
|
|
|
|
+ if val is None or pd.isna(val) or val <= min_val:
|
|
|
|
|
+ return "background: #f8f9fa"
|
|
|
|
|
+ ratio = min((val - min_val) / (max_val - min_val), 1.0)
|
|
|
|
|
+ # 白色 (255,255,255) -> 绿色 (40,167,69)
|
|
|
|
|
+ r = int(255 - ratio * 215)
|
|
|
|
|
+ g = int(255 - ratio * 88)
|
|
|
|
|
+ b = int(255 - ratio * 186)
|
|
|
|
|
+ return f"background: rgb({r},{g},{b})"
|
|
|
|
|
+
|
|
|
# ============================================================
|
|
# ============================================================
|
|
|
# 准备图表数据
|
|
# 准备图表数据
|
|
|
# ============================================================
|
|
# ============================================================
|
|
|
|
|
|
|
|
# 主要渠道(UV > 10000)
|
|
# 主要渠道(UV > 10000)
|
|
|
-# 选择 UV > 10000 的所有渠道
|
|
|
|
|
main_channels = channel_stats[channel_stats['点击uv'] > 10000]['channel'].tolist()
|
|
main_channels = channel_stats[channel_stats['点击uv'] > 10000]['channel'].tolist()
|
|
|
valid_categories = pivot_uv[pivot_uv.sum(axis=1) >= 1000].index.tolist()
|
|
valid_categories = pivot_uv[pivot_uv.sum(axis=1) >= 1000].index.tolist()
|
|
|
heatmap_cols = [c for c in main_channels if c in pivot_ror.columns]
|
|
heatmap_cols = [c for c in main_channels if c in pivot_ror.columns]
|
|
|
|
|
|
|
|
-# 1. 渠道表格行
|
|
|
|
|
|
|
+# 1. 渠道表格行(使用统一渐变)
|
|
|
channel_rows = []
|
|
channel_rows = []
|
|
|
for _, row in channel_stats.iterrows():
|
|
for _, row in channel_stats.iterrows():
|
|
|
- bar_width = min(row['再分享回流率'] * 200, 100)
|
|
|
|
|
|
|
+ rec_style = get_gradient_color(row['进入推荐率'], 1.0, 0.6) # 进入推荐率 0.6~1.0
|
|
|
|
|
+ ror_style = get_gradient_color(row['再分享回流率'], 0.8) # 回流率 0~0.8
|
|
|
|
|
+ orig_style = get_gradient_color(row['原视频回流率'], 0.3) # 原视频 0~0.3
|
|
|
|
|
+ rec_ror_style = get_gradient_color(row['推荐回流率'], 0.5) # 推荐回流 0~0.5
|
|
|
channel_rows.append(
|
|
channel_rows.append(
|
|
|
f"<tr><td>{row['channel']}</td>"
|
|
f"<tr><td>{row['channel']}</td>"
|
|
|
- f"<td>{int(row['点击uv']):,}</td>"
|
|
|
|
|
- f"<td>{row['再分享回流率']:.4f}</td>"
|
|
|
|
|
- f"<td><div style='background:#007bff;height:20px;width:{bar_width}%'></div></td></tr>"
|
|
|
|
|
|
|
+ f"<td style='text-align:right'>{int(row['点击uv']):,}</td>"
|
|
|
|
|
+ f"<td style='{rec_style}; text-align:center'>{row['进入推荐率']:.4f}</td>"
|
|
|
|
|
+ f"<td style='{ror_style}; text-align:center'>{row['再分享回流率']:.4f}</td>"
|
|
|
|
|
+ f"<td style='{orig_style}; text-align:center'>{row['原视频回流率']:.4f}</td>"
|
|
|
|
|
+ f"<td style='{rec_ror_style}; text-align:center'>{row['推荐回流率']:.4f}</td></tr>"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
-# 2. 回流率热力图 - 根据数值渐变颜色
|
|
|
|
|
-def get_ror_color(val, max_val=0.5):
|
|
|
|
|
- """回流率颜色:白色(0) -> 绿色(max)"""
|
|
|
|
|
- if val is None or val <= 0:
|
|
|
|
|
- return "background: #f8f9fa"
|
|
|
|
|
- ratio = min(val / max_val, 1.0)
|
|
|
|
|
- # 白色到绿色渐变
|
|
|
|
|
- r = int(255 - ratio * 215) # 255 -> 40
|
|
|
|
|
- g = int(255 - ratio * 88) # 255 -> 167
|
|
|
|
|
- b = int(255 - ratio * 186) # 255 -> 69
|
|
|
|
|
- text_color = "white" if ratio > 0.5 else "black"
|
|
|
|
|
- return f"background: rgb({r},{g},{b}); color: {text_color}"
|
|
|
|
|
-
|
|
|
|
|
|
|
+# 2. 回流率热力图(使用统一渐变)
|
|
|
ror_header = "<tr><th>品类</th>" + "".join([f"<th>{c[:10]}</th>" for c in heatmap_cols]) + "</tr>"
|
|
ror_header = "<tr><th>品类</th>" + "".join([f"<th>{c[:10]}</th>" for c in heatmap_cols]) + "</tr>"
|
|
|
ror_rows = []
|
|
ror_rows = []
|
|
|
for cat in valid_categories:
|
|
for cat in valid_categories:
|
|
@@ -101,25 +113,13 @@ for cat in valid_categories:
|
|
|
for ch in heatmap_cols:
|
|
for ch in heatmap_cols:
|
|
|
if ch in pivot_ror.columns and pd.notna(pivot_ror.loc[cat, ch]):
|
|
if ch in pivot_ror.columns and pd.notna(pivot_ror.loc[cat, ch]):
|
|
|
val = pivot_ror.loc[cat, ch]
|
|
val = pivot_ror.loc[cat, ch]
|
|
|
- style = get_ror_color(val)
|
|
|
|
|
|
|
+ style = get_gradient_color(val, 0.8) # 回流率 max=0.8
|
|
|
cells.append(f'<td style="{style}">{val:.4f}</td>')
|
|
cells.append(f'<td style="{style}">{val:.4f}</td>')
|
|
|
else:
|
|
else:
|
|
|
cells.append("<td>-</td>")
|
|
cells.append("<td>-</td>")
|
|
|
ror_rows.append("<tr>" + "".join(cells) + "</tr>")
|
|
ror_rows.append("<tr>" + "".join(cells) + "</tr>")
|
|
|
|
|
|
|
|
-# 3. UV分布热力图 - 根据数值渐变颜色
|
|
|
|
|
-def get_uv_color(val, max_val=100000):
|
|
|
|
|
- """UV颜色:白色(0) -> 蓝色(max)"""
|
|
|
|
|
- if val is None or val <= 0:
|
|
|
|
|
- return "background: #f8f9fa"
|
|
|
|
|
- ratio = min(val / max_val, 1.0)
|
|
|
|
|
- # 白色到蓝色渐变
|
|
|
|
|
- r = int(255 - ratio * 201) # 255 -> 54
|
|
|
|
|
- g = int(255 - ratio * 93) # 255 -> 162
|
|
|
|
|
- b = int(255 - ratio * 20) # 255 -> 235
|
|
|
|
|
- text_color = "white" if ratio > 0.5 else "black"
|
|
|
|
|
- return f"background: rgb({r},{g},{b}); color: {text_color}"
|
|
|
|
|
-
|
|
|
|
|
|
|
+# 3. UV分布热力图(使用统一渐变)
|
|
|
uv_header = "<tr><th>品类</th>" + "".join([f"<th>{c[:10]}</th>" for c in heatmap_cols]) + "</tr>"
|
|
uv_header = "<tr><th>品类</th>" + "".join([f"<th>{c[:10]}</th>" for c in heatmap_cols]) + "</tr>"
|
|
|
uv_rows = []
|
|
uv_rows = []
|
|
|
for cat in valid_categories:
|
|
for cat in valid_categories:
|
|
@@ -128,7 +128,7 @@ for cat in valid_categories:
|
|
|
if ch in pivot_uv.columns:
|
|
if ch in pivot_uv.columns:
|
|
|
val = pivot_uv.loc[cat, ch]
|
|
val = pivot_uv.loc[cat, ch]
|
|
|
if val > 0:
|
|
if val > 0:
|
|
|
- style = get_uv_color(val)
|
|
|
|
|
|
|
+ style = get_gradient_color(val, 100000) # UV max=10万
|
|
|
cells.append(f'<td style="{style}">{int(val):,}</td>')
|
|
cells.append(f'<td style="{style}">{int(val):,}</td>')
|
|
|
else:
|
|
else:
|
|
|
cells.append("<td>-</td>")
|
|
cells.append("<td>-</td>")
|
|
@@ -136,33 +136,12 @@ for cat in valid_categories:
|
|
|
cells.append("<td>-</td>")
|
|
cells.append("<td>-</td>")
|
|
|
uv_rows.append("<tr>" + "".join(cells) + "</tr>")
|
|
uv_rows.append("<tr>" + "".join(cells) + "</tr>")
|
|
|
|
|
|
|
|
-# 4. 进入推荐率热力图(按品类×渠道)
|
|
|
|
|
|
|
+# 4. 进入推荐率热力图(使用统一渐变)
|
|
|
pivot_recommend = df.groupby(['merge一级品类', 'channel']).apply(
|
|
pivot_recommend = df.groupby(['merge一级品类', 'channel']).apply(
|
|
|
lambda x: (x['进入推荐率'] * x['点击uv']).sum() / x['点击uv'].sum() if x['点击uv'].sum() > 0 else 0,
|
|
lambda x: (x['进入推荐率'] * x['点击uv']).sum() / x['点击uv'].sum() if x['点击uv'].sum() > 0 else 0,
|
|
|
include_groups=False
|
|
include_groups=False
|
|
|
).unstack()
|
|
).unstack()
|
|
|
|
|
|
|
|
-def get_recommend_color(val):
|
|
|
|
|
- """推荐率颜色:红色(低) -> 黄色(中) -> 绿色(高)"""
|
|
|
|
|
- if val is None:
|
|
|
|
|
- return "background: #f8f9fa"
|
|
|
|
|
- if val >= 0.8:
|
|
|
|
|
- ratio = min((val - 0.8) / 0.2, 1.0)
|
|
|
|
|
- r, g, b = 40, 167, 69 # 绿色
|
|
|
|
|
- elif val >= 0.7:
|
|
|
|
|
- ratio = (val - 0.7) / 0.1
|
|
|
|
|
- r = int(255 - ratio * 215)
|
|
|
|
|
- g = int(193 - ratio * 26)
|
|
|
|
|
- b = int(7 + ratio * 62)
|
|
|
|
|
- else:
|
|
|
|
|
- ratio = val / 0.7
|
|
|
|
|
- r, g, b = 220, 53, 69 # 红色基础
|
|
|
|
|
- r = int(255 - ratio * 35)
|
|
|
|
|
- g = int(255 - ratio * 62)
|
|
|
|
|
- b = int(255 - ratio * 248)
|
|
|
|
|
- text_color = "white" if val >= 0.75 or val < 0.65 else "black"
|
|
|
|
|
- return f"background: rgb({r},{g},{b}); color: {text_color}"
|
|
|
|
|
-
|
|
|
|
|
recommend_rows = []
|
|
recommend_rows = []
|
|
|
for cat in valid_categories:
|
|
for cat in valid_categories:
|
|
|
if cat not in pivot_recommend.index:
|
|
if cat not in pivot_recommend.index:
|
|
@@ -171,7 +150,7 @@ for cat in valid_categories:
|
|
|
for ch in heatmap_cols:
|
|
for ch in heatmap_cols:
|
|
|
if ch in pivot_recommend.columns and pd.notna(pivot_recommend.loc[cat, ch]):
|
|
if ch in pivot_recommend.columns and pd.notna(pivot_recommend.loc[cat, ch]):
|
|
|
val = pivot_recommend.loc[cat, ch]
|
|
val = pivot_recommend.loc[cat, ch]
|
|
|
- style = get_recommend_color(val)
|
|
|
|
|
|
|
+ style = get_gradient_color(val, 1.0, 0.6) # 推荐率 0.6~1.0
|
|
|
cells.append(f'<td style="{style}">{val:.4f}</td>')
|
|
cells.append(f'<td style="{style}">{val:.4f}</td>')
|
|
|
else:
|
|
else:
|
|
|
cells.append("<td>-</td>")
|
|
cells.append("<td>-</td>")
|
|
@@ -199,7 +178,7 @@ valid_cat2_labels = cat2_stats.head(20)['cat2_label'].tolist()
|
|
|
pivot_cat2_ror = channel_cat2.pivot_table(index='cat2_label', columns='channel', values='回流率')
|
|
pivot_cat2_ror = channel_cat2.pivot_table(index='cat2_label', columns='channel', values='回流率')
|
|
|
pivot_cat2_uv = channel_cat2.pivot_table(index='cat2_label', columns='channel', values='点击uv', fill_value=0)
|
|
pivot_cat2_uv = channel_cat2.pivot_table(index='cat2_label', columns='channel', values='点击uv', fill_value=0)
|
|
|
|
|
|
|
|
-# 二级品类回流率热力图
|
|
|
|
|
|
|
+# 二级品类回流率热力图(使用统一渐变)
|
|
|
cat2_ror_header = "<tr><th>二级品类</th>" + "".join([f"<th>{c[:10]}</th>" for c in heatmap_cols]) + "</tr>"
|
|
cat2_ror_header = "<tr><th>二级品类</th>" + "".join([f"<th>{c[:10]}</th>" for c in heatmap_cols]) + "</tr>"
|
|
|
cat2_ror_rows = []
|
|
cat2_ror_rows = []
|
|
|
for cat2 in valid_cat2_labels:
|
|
for cat2 in valid_cat2_labels:
|
|
@@ -209,13 +188,13 @@ for cat2 in valid_cat2_labels:
|
|
|
for ch in heatmap_cols:
|
|
for ch in heatmap_cols:
|
|
|
if ch in pivot_cat2_ror.columns and pd.notna(pivot_cat2_ror.loc[cat2, ch]):
|
|
if ch in pivot_cat2_ror.columns and pd.notna(pivot_cat2_ror.loc[cat2, ch]):
|
|
|
val = pivot_cat2_ror.loc[cat2, ch]
|
|
val = pivot_cat2_ror.loc[cat2, ch]
|
|
|
- style = get_ror_color(val)
|
|
|
|
|
|
|
+ style = get_gradient_color(val, 0.8) # 回流率 max=0.8
|
|
|
cells.append(f'<td style="{style}">{val:.4f}</td>')
|
|
cells.append(f'<td style="{style}">{val:.4f}</td>')
|
|
|
else:
|
|
else:
|
|
|
cells.append("<td>-</td>")
|
|
cells.append("<td>-</td>")
|
|
|
cat2_ror_rows.append("<tr>" + "".join(cells) + "</tr>")
|
|
cat2_ror_rows.append("<tr>" + "".join(cells) + "</tr>")
|
|
|
|
|
|
|
|
-# 二级品类UV热力图
|
|
|
|
|
|
|
+# 二级品类UV热力图(使用统一渐变)
|
|
|
cat2_uv_rows = []
|
|
cat2_uv_rows = []
|
|
|
for cat2 in valid_cat2_labels:
|
|
for cat2 in valid_cat2_labels:
|
|
|
if cat2 not in pivot_cat2_uv.index:
|
|
if cat2 not in pivot_cat2_uv.index:
|
|
@@ -225,7 +204,7 @@ for cat2 in valid_cat2_labels:
|
|
|
if ch in pivot_cat2_uv.columns:
|
|
if ch in pivot_cat2_uv.columns:
|
|
|
val = pivot_cat2_uv.loc[cat2, ch]
|
|
val = pivot_cat2_uv.loc[cat2, ch]
|
|
|
if val > 0:
|
|
if val > 0:
|
|
|
- style = get_uv_color(val)
|
|
|
|
|
|
|
+ style = get_gradient_color(val, 100000) # UV max=10万
|
|
|
cells.append(f'<td style="{style}">{int(val):,}</td>')
|
|
cells.append(f'<td style="{style}">{int(val):,}</td>')
|
|
|
else:
|
|
else:
|
|
|
cells.append("<td>-</td>")
|
|
cells.append("<td>-</td>")
|
|
@@ -233,40 +212,6 @@ for cat2 in valid_cat2_labels:
|
|
|
cells.append("<td>-</td>")
|
|
cells.append("<td>-</td>")
|
|
|
cat2_uv_rows.append("<tr>" + "".join(cells) + "</tr>")
|
|
cat2_uv_rows.append("<tr>" + "".join(cells) + "</tr>")
|
|
|
|
|
|
|
|
-# 6. 趋势数据
|
|
|
|
|
-top_channels = channel_stats.head(6)['channel'].tolist()
|
|
|
|
|
-trend_data = {}
|
|
|
|
|
-for ch in top_channels:
|
|
|
|
|
- ch_daily = daily[daily['channel'] == ch].sort_values('dt')
|
|
|
|
|
- trend_data[ch] = {
|
|
|
|
|
- 'dates': [str(d) for d in ch_daily['dt'].tolist()],
|
|
|
|
|
- 'values': [round(x * 100, 1) for x in ch_daily['回流率'].tolist()]
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-# 7. 散点图数据
|
|
|
|
|
-scatter_data = []
|
|
|
|
|
-for _, row in channel_category.iterrows():
|
|
|
|
|
- if row['点击uv'] >= 100:
|
|
|
|
|
- scatter_data.append({
|
|
|
|
|
- 'x': int(row['点击uv']),
|
|
|
|
|
- 'y': round(row['回流率'], 4),
|
|
|
|
|
- 'channel': row['channel'][:12],
|
|
|
|
|
- 'category': str(row['merge一级品类'])[:10] if pd.notna(row['merge一级品类']) else ''
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
-# 8. 气泡图数据(渠道:占比 vs 回流率)
|
|
|
|
|
-total_uv_all = df['点击uv'].sum()
|
|
|
|
|
-bubble_data = []
|
|
|
|
|
-for _, row in channel_stats.iterrows():
|
|
|
|
|
- share = row['点击uv'] / total_uv_all
|
|
|
|
|
- bubble_data.append({
|
|
|
|
|
- 'x': round(share, 4), # UV占比
|
|
|
|
|
- 'y': round(row['再分享回流率'], 4), # 回流率
|
|
|
|
|
- 'r': max(5, min(50, row['点击uv'] / 20000)), # 气泡大小
|
|
|
|
|
- 'uv': int(row['点击uv']),
|
|
|
|
|
- 'channel': row['channel'][:15]
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
# ============================================================
|
|
# ============================================================
|
|
|
# 生成 HTML
|
|
# 生成 HTML
|
|
|
# ============================================================
|
|
# ============================================================
|
|
@@ -282,11 +227,21 @@ html_content = f"""<!DOCTYPE html>
|
|
|
<head>
|
|
<head>
|
|
|
<meta charset="utf-8">
|
|
<meta charset="utf-8">
|
|
|
<title>渠道效果分析报告</title>
|
|
<title>渠道效果分析报告</title>
|
|
|
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
|
|
|
<style>
|
|
<style>
|
|
|
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
|
- margin: 20px; background: #f5f5f5; }}
|
|
|
|
|
- .container {{ max-width: 1400px; margin: 0 auto; }}
|
|
|
|
|
|
|
+ margin: 0; background: #f5f5f5; }}
|
|
|
|
|
+ .sidebar {{ position: fixed; left: 0; top: 0; width: 220px; height: 100vh;
|
|
|
|
|
+ background: #2c3e50; color: white; overflow-y: auto; padding: 20px 0;
|
|
|
|
|
+ box-shadow: 2px 0 5px rgba(0,0,0,0.1); }}
|
|
|
|
|
+ .sidebar h2 {{ color: white; font-size: 16px; margin: 0 15px 15px; padding-bottom: 10px;
|
|
|
|
|
+ border-bottom: 1px solid #3d566e; }}
|
|
|
|
|
+ .sidebar ul {{ list-style: none; margin: 0; padding: 0; }}
|
|
|
|
|
+ .sidebar li {{ margin: 0; }}
|
|
|
|
|
+ .sidebar a {{ display: block; padding: 8px 15px; color: #bdc3c7; text-decoration: none;
|
|
|
|
|
+ font-size: 13px; transition: all 0.2s; }}
|
|
|
|
|
+ .sidebar a:hover {{ background: #34495e; color: white; }}
|
|
|
|
|
+ .sidebar .sub a {{ padding-left: 30px; font-size: 12px; }}
|
|
|
|
|
+ .container {{ margin-left: 240px; padding: 20px; max-width: 1400px; }}
|
|
|
h1 {{ color: #333; border-bottom: 2px solid #007bff; padding-bottom: 10px; }}
|
|
h1 {{ color: #333; border-bottom: 2px solid #007bff; padding-bottom: 10px; }}
|
|
|
h2 {{ color: #444; margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; }}
|
|
h2 {{ color: #444; margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; }}
|
|
|
h3 {{ color: #555; margin-top: 20px; }}
|
|
h3 {{ color: #555; margin-top: 20px; }}
|
|
@@ -309,16 +264,23 @@ html_content = f"""<!DOCTYPE html>
|
|
|
canvas {{ max-height: 400px; }}
|
|
canvas {{ max-height: 400px; }}
|
|
|
.matrix-section {{ margin-bottom: 40px; }}
|
|
.matrix-section {{ margin-bottom: 40px; }}
|
|
|
.legend {{ font-size: 12px; margin: 10px 0; color: #666; }}
|
|
.legend {{ font-size: 12px; margin: 10px 0; color: #666; }}
|
|
|
- .toc {{ background: white; padding: 20px; margin: 20px 0; border-radius: 8px; }}
|
|
|
|
|
- .toc h2 {{ margin-top: 0; border: none; padding: 0; }}
|
|
|
|
|
- .toc ul {{ margin: 0; padding-left: 20px; }}
|
|
|
|
|
- .toc li {{ margin: 5px 0; }}
|
|
|
|
|
- .toc a {{ color: #007bff; text-decoration: none; }}
|
|
|
|
|
- .toc a:hover {{ text-decoration: underline; }}
|
|
|
|
|
- .toc .sub {{ padding-left: 20px; }}
|
|
|
|
|
</style>
|
|
</style>
|
|
|
</head>
|
|
</head>
|
|
|
<body>
|
|
<body>
|
|
|
|
|
+ <nav class="sidebar">
|
|
|
|
|
+ <h2>目录</h2>
|
|
|
|
|
+ <ul>
|
|
|
|
|
+ <li><a href="#sec1">一、渠道整体表现</a></li>
|
|
|
|
|
+ <li><a href="#sec2">二、一级品类分析</a></li>
|
|
|
|
|
+ <li class="sub"><a href="#sec2-1">2.1 回流率矩阵</a></li>
|
|
|
|
|
+ <li class="sub"><a href="#sec2-2">2.2 点击UV矩阵</a></li>
|
|
|
|
|
+ <li class="sub"><a href="#sec2-3">2.3 进入推荐率矩阵</a></li>
|
|
|
|
|
+ <li><a href="#sec3">三、二级品类分析</a></li>
|
|
|
|
|
+ <li class="sub"><a href="#sec3-1">3.1 回流率矩阵</a></li>
|
|
|
|
|
+ <li class="sub"><a href="#sec3-2">3.2 点击UV矩阵</a></li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ </nav>
|
|
|
|
|
+
|
|
|
<div class="container">
|
|
<div class="container">
|
|
|
<h1>渠道效果分析报告</h1>
|
|
<h1>渠道效果分析报告</h1>
|
|
|
<p>数据范围: {date_range}</p>
|
|
<p>数据范围: {date_range}</p>
|
|
@@ -342,37 +304,11 @@ html_content = f"""<!DOCTYPE html>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div class="toc">
|
|
|
|
|
- <h2>目录</h2>
|
|
|
|
|
- <ul>
|
|
|
|
|
- <li><a href="#sec1">一、渠道整体表现</a></li>
|
|
|
|
|
- <li><a href="#sec2">二、一级品类分析</a>
|
|
|
|
|
- <ul class="sub">
|
|
|
|
|
- <li><a href="#sec2-1">2.1 回流率矩阵</a></li>
|
|
|
|
|
- <li><a href="#sec2-2">2.2 点击UV矩阵</a></li>
|
|
|
|
|
- <li><a href="#sec2-3">2.3 进入推荐率矩阵</a></li>
|
|
|
|
|
- </ul>
|
|
|
|
|
- </li>
|
|
|
|
|
- <li><a href="#sec3">三、二级品类分析</a>
|
|
|
|
|
- <ul class="sub">
|
|
|
|
|
- <li><a href="#sec3-1">3.1 回流率矩阵</a></li>
|
|
|
|
|
- <li><a href="#sec3-2">3.2 点击UV矩阵</a></li>
|
|
|
|
|
- </ul>
|
|
|
|
|
- </li>
|
|
|
|
|
- <li><a href="#sec4">四、趋势与分布</a>
|
|
|
|
|
- <ul class="sub">
|
|
|
|
|
- <li><a href="#sec4-1">4.1 渠道占比 vs 回流率</a></li>
|
|
|
|
|
- <li><a href="#sec4-2">4.2 每日回流率趋势</a></li>
|
|
|
|
|
- <li><a href="#sec4-3">4.3 UV vs 回流率散点</a></li>
|
|
|
|
|
- </ul>
|
|
|
|
|
- </li>
|
|
|
|
|
- </ul>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
<h2 id="sec1">一、渠道整体表现</h2>
|
|
<h2 id="sec1">一、渠道整体表现</h2>
|
|
|
<div class="chart-container">
|
|
<div class="chart-container">
|
|
|
|
|
+ <div class="legend">颜色越深=数值越高(统一白→绿渐变)</div>
|
|
|
<table>
|
|
<table>
|
|
|
- <tr><th>渠道</th><th>点击UV</th><th>回流率</th><th>可视化</th></tr>
|
|
|
|
|
|
|
+ <tr><th>渠道</th><th>点击UV</th><th>进入推荐率</th><th>回流率</th><th>原视频回流</th><th>推荐回流</th></tr>
|
|
|
{"".join(channel_rows)}
|
|
{"".join(channel_rows)}
|
|
|
</table>
|
|
</table>
|
|
|
</div>
|
|
</div>
|
|
@@ -381,7 +317,7 @@ html_content = f"""<!DOCTYPE html>
|
|
|
|
|
|
|
|
<h3 id="sec2-1">2.1 回流率矩阵</h3>
|
|
<h3 id="sec2-1">2.1 回流率矩阵</h3>
|
|
|
<div class="chart-container heatmap matrix-section">
|
|
<div class="chart-container heatmap matrix-section">
|
|
|
- <div class="legend">颜色越深=回流率越高(白→绿渐变,max=0.50)</div>
|
|
|
|
|
|
|
+ <div class="legend">颜色越深=回流率越高(白→绿渐变,max=0.80)</div>
|
|
|
<table>
|
|
<table>
|
|
|
{ror_header}
|
|
{ror_header}
|
|
|
{"".join(ror_rows)}
|
|
{"".join(ror_rows)}
|
|
@@ -390,7 +326,7 @@ html_content = f"""<!DOCTYPE html>
|
|
|
|
|
|
|
|
<h3 id="sec2-2">2.2 点击UV矩阵</h3>
|
|
<h3 id="sec2-2">2.2 点击UV矩阵</h3>
|
|
|
<div class="chart-container heatmap matrix-section">
|
|
<div class="chart-container heatmap matrix-section">
|
|
|
- <div class="legend">颜色越深=UV越高(白→蓝渐变,max=10万)</div>
|
|
|
|
|
|
|
+ <div class="legend">颜色越深=UV越高(白→绿渐变,max=10万)</div>
|
|
|
<table>
|
|
<table>
|
|
|
{uv_header}
|
|
{uv_header}
|
|
|
{"".join(uv_rows)}
|
|
{"".join(uv_rows)}
|
|
@@ -399,7 +335,7 @@ html_content = f"""<!DOCTYPE html>
|
|
|
|
|
|
|
|
<h3 id="sec2-3">2.3 进入推荐率矩阵</h3>
|
|
<h3 id="sec2-3">2.3 进入推荐率矩阵</h3>
|
|
|
<div class="chart-container heatmap matrix-section">
|
|
<div class="chart-container heatmap matrix-section">
|
|
|
- <div class="legend">颜色:红(<0.70) → 黄(0.70-0.80) → 绿(>0.80)</div>
|
|
|
|
|
|
|
+ <div class="legend">颜色越深=推荐率越高(白→绿渐变,min=0.60,max=1.00)</div>
|
|
|
<table>
|
|
<table>
|
|
|
{ror_header}
|
|
{ror_header}
|
|
|
{"".join(recommend_rows)}
|
|
{"".join(recommend_rows)}
|
|
@@ -410,7 +346,7 @@ html_content = f"""<!DOCTYPE html>
|
|
|
|
|
|
|
|
<h3 id="sec3-1">3.1 回流率矩阵</h3>
|
|
<h3 id="sec3-1">3.1 回流率矩阵</h3>
|
|
|
<div class="chart-container heatmap matrix-section">
|
|
<div class="chart-container heatmap matrix-section">
|
|
|
- <div class="legend">颜色越深=回流率越高(白→绿渐变,max=0.50)</div>
|
|
|
|
|
|
|
+ <div class="legend">颜色越深=回流率越高(白→绿渐变,max=0.80)</div>
|
|
|
<table>
|
|
<table>
|
|
|
{cat2_ror_header}
|
|
{cat2_ror_header}
|
|
|
{"".join(cat2_ror_rows)}
|
|
{"".join(cat2_ror_rows)}
|
|
@@ -419,120 +355,14 @@ html_content = f"""<!DOCTYPE html>
|
|
|
|
|
|
|
|
<h3 id="sec3-2">3.2 点击UV矩阵</h3>
|
|
<h3 id="sec3-2">3.2 点击UV矩阵</h3>
|
|
|
<div class="chart-container heatmap matrix-section">
|
|
<div class="chart-container heatmap matrix-section">
|
|
|
- <div class="legend">颜色越深=UV越高(白→蓝渐变,max=10万)</div>
|
|
|
|
|
|
|
+ <div class="legend">颜色越深=UV越高(白→绿渐变,max=10万)</div>
|
|
|
<table>
|
|
<table>
|
|
|
{cat2_ror_header}
|
|
{cat2_ror_header}
|
|
|
{"".join(cat2_uv_rows)}
|
|
{"".join(cat2_uv_rows)}
|
|
|
</table>
|
|
</table>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <h2 id="sec4">四、趋势与分布</h2>
|
|
|
|
|
-
|
|
|
|
|
- <h3 id="sec4-1">4.1 渠道占比 vs 回流率(气泡图)</h3>
|
|
|
|
|
- <div class="chart-container">
|
|
|
|
|
- <canvas id="bubbleChart"></canvas>
|
|
|
|
|
- <p style="font-size:12px;color:#666;margin-top:10px;">X轴=UV占比,Y轴=回流率,气泡大小=UV量级</p>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <h3 id="sec4-2">4.2 每日回流率趋势</h3>
|
|
|
|
|
- <div class="chart-container">
|
|
|
|
|
- <canvas id="trendChart"></canvas>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <h3 id="sec4-3">4.3 UV vs 回流率 散点分布</h3>
|
|
|
|
|
- <div class="chart-container">
|
|
|
|
|
- <canvas id="scatterChart"></canvas>
|
|
|
|
|
- </div>
|
|
|
|
|
</div>
|
|
</div>
|
|
|
-
|
|
|
|
|
- <script>
|
|
|
|
|
- const colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf'];
|
|
|
|
|
-
|
|
|
|
|
- // 气泡图
|
|
|
|
|
- const bubbleCtx = document.getElementById('bubbleChart').getContext('2d');
|
|
|
|
|
- const bubbleData = {json.dumps(bubble_data, ensure_ascii=False)};
|
|
|
|
|
- new Chart(bubbleCtx, {{
|
|
|
|
|
- type: 'bubble',
|
|
|
|
|
- data: {{
|
|
|
|
|
- datasets: bubbleData.map((d, i) => ({{
|
|
|
|
|
- label: d.channel,
|
|
|
|
|
- data: [{{ x: d.x, y: d.y, r: d.r }}],
|
|
|
|
|
- backgroundColor: colors[i % colors.length] + '80'
|
|
|
|
|
- }}))
|
|
|
|
|
- }},
|
|
|
|
|
- options: {{
|
|
|
|
|
- responsive: true,
|
|
|
|
|
- scales: {{
|
|
|
|
|
- x: {{ title: {{ display: true, text: 'UV占比' }} }},
|
|
|
|
|
- y: {{ title: {{ display: true, text: '回流率' }} }}
|
|
|
|
|
- }},
|
|
|
|
|
- plugins: {{
|
|
|
|
|
- tooltip: {{
|
|
|
|
|
- callbacks: {{
|
|
|
|
|
- label: (ctx) => {{
|
|
|
|
|
- const d = bubbleData[ctx.datasetIndex];
|
|
|
|
|
- return d.channel + ': 占比=' + (d.x * 100).toFixed(1) + '%, 回流率=' + d.y.toFixed(4) + ', UV=' + d.uv.toLocaleString();
|
|
|
|
|
- }}
|
|
|
|
|
- }}
|
|
|
|
|
- }}
|
|
|
|
|
- }}
|
|
|
|
|
- }}
|
|
|
|
|
- }});
|
|
|
|
|
-
|
|
|
|
|
- // 趋势图
|
|
|
|
|
- const trendCtx = document.getElementById('trendChart').getContext('2d');
|
|
|
|
|
- const trendData = {json.dumps(trend_data, ensure_ascii=False)};
|
|
|
|
|
- const datasets = Object.keys(trendData).map((ch, i) => ({{
|
|
|
|
|
- label: ch.substring(0, 15),
|
|
|
|
|
- data: trendData[ch].values,
|
|
|
|
|
- borderColor: colors[i % colors.length],
|
|
|
|
|
- tension: 0.1,
|
|
|
|
|
- fill: false
|
|
|
|
|
- }}));
|
|
|
|
|
- new Chart(trendCtx, {{
|
|
|
|
|
- type: 'line',
|
|
|
|
|
- data: {{
|
|
|
|
|
- labels: trendData[Object.keys(trendData)[0]].dates,
|
|
|
|
|
- datasets: datasets
|
|
|
|
|
- }},
|
|
|
|
|
- options: {{
|
|
|
|
|
- responsive: true,
|
|
|
|
|
- plugins: {{ legend: {{ position: 'top' }} }},
|
|
|
|
|
- scales: {{ y: {{ title: {{ display: true, text: '回流率' }} }} }}
|
|
|
|
|
- }}
|
|
|
|
|
- }});
|
|
|
|
|
-
|
|
|
|
|
- // 散点图
|
|
|
|
|
- const scatterCtx = document.getElementById('scatterChart').getContext('2d');
|
|
|
|
|
- const scatterData = {json.dumps(scatter_data, ensure_ascii=False)};
|
|
|
|
|
- new Chart(scatterCtx, {{
|
|
|
|
|
- type: 'scatter',
|
|
|
|
|
- data: {{
|
|
|
|
|
- datasets: [{{
|
|
|
|
|
- label: '渠道×品类',
|
|
|
|
|
- data: scatterData.map(d => ({{ x: d.x, y: d.y }})),
|
|
|
|
|
- backgroundColor: 'rgba(54, 162, 235, 0.5)'
|
|
|
|
|
- }}]
|
|
|
|
|
- }},
|
|
|
|
|
- options: {{
|
|
|
|
|
- responsive: true,
|
|
|
|
|
- scales: {{
|
|
|
|
|
- x: {{ type: 'logarithmic', title: {{ display: true, text: '点击UV' }} }},
|
|
|
|
|
- y: {{ title: {{ display: true, text: '回流率' }} }}
|
|
|
|
|
- }},
|
|
|
|
|
- plugins: {{
|
|
|
|
|
- tooltip: {{
|
|
|
|
|
- callbacks: {{
|
|
|
|
|
- label: (ctx) => {{
|
|
|
|
|
- const d = scatterData[ctx.dataIndex];
|
|
|
|
|
- return d.channel + ' - ' + d.category + ': UV=' + d.x + ', 回流率=' + d.y;
|
|
|
|
|
- }}
|
|
|
|
|
- }}
|
|
|
|
|
- }}
|
|
|
|
|
- }}
|
|
|
|
|
- }}
|
|
|
|
|
- }});
|
|
|
|
|
- </script>
|
|
|
|
|
</body>
|
|
</body>
|
|
|
</html>
|
|
</html>
|
|
|
"""
|
|
"""
|