|
@@ -76,7 +76,7 @@ for _, row in channel_stats.iterrows():
|
|
|
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>{int(row['点击uv']):,}</td>"
|
|
|
- f"<td>{row['再分享回流率']:.1%}</td>"
|
|
|
|
|
|
|
+ f"<td>{row['再分享回流率']:.4f}</td>"
|
|
|
f"<td><div style='background:#007bff;height:20px;width:{bar_width}%'></div></td></tr>"
|
|
f"<td><div style='background:#007bff;height:20px;width:{bar_width}%'></div></td></tr>"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
@@ -84,9 +84,9 @@ for _, row in channel_stats.iterrows():
|
|
|
def get_cell_class(val):
|
|
def get_cell_class(val):
|
|
|
if val is None:
|
|
if val is None:
|
|
|
return ""
|
|
return ""
|
|
|
- if val > 30:
|
|
|
|
|
|
|
+ if val > 0.30:
|
|
|
return "high"
|
|
return "high"
|
|
|
- if val > 15:
|
|
|
|
|
|
|
+ if val > 0.15:
|
|
|
return "medium"
|
|
return "medium"
|
|
|
return "low"
|
|
return "low"
|
|
|
|
|
|
|
@@ -96,9 +96,9 @@ for cat in valid_categories:
|
|
|
cells = [f"<td>{str(cat)[:12]}</td>"]
|
|
cells = [f"<td>{str(cat)[:12]}</td>"]
|
|
|
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] * 100
|
|
|
|
|
|
|
+ val = pivot_ror.loc[cat, ch]
|
|
|
cls = get_cell_class(val)
|
|
cls = get_cell_class(val)
|
|
|
- cells.append(f'<td class="{cls}">{val:.1f}%</td>')
|
|
|
|
|
|
|
+ cells.append(f'<td class="{cls}">{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>")
|
|
@@ -149,7 +149,7 @@ for cat in valid_categories:
|
|
|
if ch in pivot_recommend.columns and cat in pivot_recommend.index and pd.notna(pivot_recommend.loc[cat, ch]):
|
|
if ch in pivot_recommend.columns and cat in pivot_recommend.index and pd.notna(pivot_recommend.loc[cat, ch]):
|
|
|
val = pivot_recommend.loc[cat, ch]
|
|
val = pivot_recommend.loc[cat, ch]
|
|
|
cls = get_recommend_class(val)
|
|
cls = get_recommend_class(val)
|
|
|
- cells.append(f'<td class="{cls}">{val:.1%}</td>')
|
|
|
|
|
|
|
+ cells.append(f'<td class="{cls}">{val:.4f}</td>')
|
|
|
else:
|
|
else:
|
|
|
cells.append("<td>-</td>")
|
|
cells.append("<td>-</td>")
|
|
|
recommend_rows.append("<tr>" + "".join(cells) + "</tr>")
|
|
recommend_rows.append("<tr>" + "".join(cells) + "</tr>")
|
|
@@ -185,9 +185,9 @@ for cat2 in valid_cat2_labels:
|
|
|
cells = [f"<td>{cat2[:15]}</td>"]
|
|
cells = [f"<td>{cat2[:15]}</td>"]
|
|
|
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] * 100
|
|
|
|
|
|
|
+ val = pivot_cat2_ror.loc[cat2, ch]
|
|
|
cls = get_cell_class(val)
|
|
cls = get_cell_class(val)
|
|
|
- cells.append(f'<td class="{cls}">{val:.1f}%</td>')
|
|
|
|
|
|
|
+ cells.append(f'<td class="{cls}">{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>")
|
|
@@ -220,17 +220,30 @@ for ch in top_channels:
|
|
|
'values': [round(x * 100, 1) for x in ch_daily['回流率'].tolist()]
|
|
'values': [round(x * 100, 1) for x in ch_daily['回流率'].tolist()]
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-# 6. 散点图数据
|
|
|
|
|
|
|
+# 7. 散点图数据
|
|
|
scatter_data = []
|
|
scatter_data = []
|
|
|
for _, row in channel_category.iterrows():
|
|
for _, row in channel_category.iterrows():
|
|
|
if row['点击uv'] >= 100:
|
|
if row['点击uv'] >= 100:
|
|
|
scatter_data.append({
|
|
scatter_data.append({
|
|
|
'x': int(row['点击uv']),
|
|
'x': int(row['点击uv']),
|
|
|
- 'y': round(row['回流率'] * 100, 1),
|
|
|
|
|
|
|
+ 'y': round(row['回流率'], 4),
|
|
|
'channel': row['channel'][:12],
|
|
'channel': row['channel'][:12],
|
|
|
'category': str(row['merge一级品类'])[:10] if pd.notna(row['merge一级品类']) else ''
|
|
'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
|
|
|
# ============================================================
|
|
# ============================================================
|
|
@@ -289,7 +302,7 @@ html_content = f"""<!DOCTYPE html>
|
|
|
<p>总点击UV</p>
|
|
<p>总点击UV</p>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="stat-card">
|
|
<div class="stat-card">
|
|
|
- <h3>{avg_ror:.1%}</h3>
|
|
|
|
|
|
|
+ <h3>{avg_ror:.4f}</h3>
|
|
|
<p>平均回流率</p>
|
|
<p>平均回流率</p>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="stat-card">
|
|
<div class="stat-card">
|
|
@@ -313,9 +326,9 @@ html_content = f"""<!DOCTYPE html>
|
|
|
<h2>2. 渠道×品类 回流率矩阵</h2>
|
|
<h2>2. 渠道×品类 回流率矩阵</h2>
|
|
|
<div class="chart-container heatmap matrix-section">
|
|
<div class="chart-container heatmap matrix-section">
|
|
|
<div class="legend">
|
|
<div class="legend">
|
|
|
- <span class="high">高 >30%</span>
|
|
|
|
|
- <span class="medium">中 15-30%</span>
|
|
|
|
|
- <span class="low">低 <15%</span>
|
|
|
|
|
|
|
+ <span class="high">高 >0.30</span>
|
|
|
|
|
+ <span class="medium">中 0.15-0.30</span>
|
|
|
|
|
+ <span class="low">低 <0.15</span>
|
|
|
</div>
|
|
</div>
|
|
|
<table>
|
|
<table>
|
|
|
{ror_header}
|
|
{ror_header}
|
|
@@ -339,9 +352,9 @@ html_content = f"""<!DOCTYPE html>
|
|
|
<h2>4. 渠道×品类 进入推荐率矩阵</h2>
|
|
<h2>4. 渠道×品类 进入推荐率矩阵</h2>
|
|
|
<div class="chart-container heatmap matrix-section">
|
|
<div class="chart-container heatmap matrix-section">
|
|
|
<div class="legend">
|
|
<div class="legend">
|
|
|
- <span class="high">高 >80%</span>
|
|
|
|
|
- <span class="medium">中 70-80%</span>
|
|
|
|
|
- <span class="low">低 <70%</span>
|
|
|
|
|
|
|
+ <span class="high">高 >0.80</span>
|
|
|
|
|
+ <span class="medium">中 0.70-0.80</span>
|
|
|
|
|
+ <span class="low">低 <0.70</span>
|
|
|
</div>
|
|
</div>
|
|
|
<table>
|
|
<table>
|
|
|
{ror_header}
|
|
{ror_header}
|
|
@@ -352,9 +365,9 @@ html_content = f"""<!DOCTYPE html>
|
|
|
<h2>5. 渠道×二级品类 回流率矩阵</h2>
|
|
<h2>5. 渠道×二级品类 回流率矩阵</h2>
|
|
|
<div class="chart-container heatmap matrix-section">
|
|
<div class="chart-container heatmap matrix-section">
|
|
|
<div class="legend">
|
|
<div class="legend">
|
|
|
- <span class="high">高 >30%</span>
|
|
|
|
|
- <span class="medium">中 15-30%</span>
|
|
|
|
|
- <span class="low">低 <15%</span>
|
|
|
|
|
|
|
+ <span class="high">高 >0.30</span>
|
|
|
|
|
+ <span class="medium">中 0.15-0.30</span>
|
|
|
|
|
+ <span class="low">低 <0.15</span>
|
|
|
</div>
|
|
</div>
|
|
|
<table>
|
|
<table>
|
|
|
{cat2_ror_header}
|
|
{cat2_ror_header}
|
|
@@ -375,22 +388,60 @@ html_content = f"""<!DOCTYPE html>
|
|
|
</table>
|
|
</table>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <h2>7. 每日回流率趋势</h2>
|
|
|
|
|
|
|
+ <h2>7. 渠道占比 vs 回流率(气泡图)</h2>
|
|
|
|
|
+ <div class="chart-container">
|
|
|
|
|
+ <canvas id="bubbleChart"></canvas>
|
|
|
|
|
+ <p style="font-size:12px;color:#666;margin-top:10px;">X轴=UV占比,Y轴=回流率,气泡大小=UV量级</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <h2>8. 每日回流率趋势</h2>
|
|
|
<div class="chart-container">
|
|
<div class="chart-container">
|
|
|
<canvas id="trendChart"></canvas>
|
|
<canvas id="trendChart"></canvas>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <h2>8. UV vs 回流率 散点分布</h2>
|
|
|
|
|
|
|
+ <h2>9. UV vs 回流率 散点分布</h2>
|
|
|
<div class="chart-container">
|
|
<div class="chart-container">
|
|
|
<canvas id="scatterChart"></canvas>
|
|
<canvas id="scatterChart"></canvas>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<script>
|
|
<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 trendCtx = document.getElementById('trendChart').getContext('2d');
|
|
|
const trendData = {json.dumps(trend_data, ensure_ascii=False)};
|
|
const trendData = {json.dumps(trend_data, ensure_ascii=False)};
|
|
|
- const colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b'];
|
|
|
|
|
const datasets = Object.keys(trendData).map((ch, i) => ({{
|
|
const datasets = Object.keys(trendData).map((ch, i) => ({{
|
|
|
label: ch.substring(0, 15),
|
|
label: ch.substring(0, 15),
|
|
|
data: trendData[ch].values,
|
|
data: trendData[ch].values,
|
|
@@ -407,7 +458,7 @@ html_content = f"""<!DOCTYPE html>
|
|
|
options: {{
|
|
options: {{
|
|
|
responsive: true,
|
|
responsive: true,
|
|
|
plugins: {{ legend: {{ position: 'top' }} }},
|
|
plugins: {{ legend: {{ position: 'top' }} }},
|
|
|
- scales: {{ y: {{ title: {{ display: true, text: '回流率(%)' }} }} }}
|
|
|
|
|
|
|
+ scales: {{ y: {{ title: {{ display: true, text: '回流率' }} }} }}
|
|
|
}}
|
|
}}
|
|
|
}});
|
|
}});
|
|
|
|
|
|
|
@@ -427,14 +478,14 @@ html_content = f"""<!DOCTYPE html>
|
|
|
responsive: true,
|
|
responsive: true,
|
|
|
scales: {{
|
|
scales: {{
|
|
|
x: {{ type: 'logarithmic', title: {{ display: true, text: '点击UV' }} }},
|
|
x: {{ type: 'logarithmic', title: {{ display: true, text: '点击UV' }} }},
|
|
|
- y: {{ title: {{ display: true, text: '回流率(%)' }} }}
|
|
|
|
|
|
|
+ y: {{ title: {{ display: true, text: '回流率' }} }}
|
|
|
}},
|
|
}},
|
|
|
plugins: {{
|
|
plugins: {{
|
|
|
tooltip: {{
|
|
tooltip: {{
|
|
|
callbacks: {{
|
|
callbacks: {{
|
|
|
label: (ctx) => {{
|
|
label: (ctx) => {{
|
|
|
const d = scatterData[ctx.dataIndex];
|
|
const d = scatterData[ctx.dataIndex];
|
|
|
- return d.channel + ' - ' + d.category + ': UV=' + d.x + ', 回流率=' + d.y + '%';
|
|
|
|
|
|
|
+ return d.channel + ' - ' + d.category + ': UV=' + d.x + ', 回流率=' + d.y;
|
|
|
}}
|
|
}}
|
|
|
}}
|
|
}}
|
|
|
}}
|
|
}}
|