Просмотр исходного кода

渠道效果分析:统一渐变颜色,完善渠道整体表现展示

- 统一所有矩阵使用白→绿渐变,统一黑色字体
- 渠道整体表现增加列:进入推荐率、原视频回流、推荐回流
- 移除废弃的chart.js和图表数据代码
- 更新legend说明

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
yangxiaohui 2 месяцев назад
Родитель
Сommit
204a66f0fa
1 измененных файлов с 74 добавлено и 244 удалено
  1. 74 244
      tasks/渠道效果分析/visualize.py

+ 74 - 244
tasks/渠道效果分析/visualize.py

@@ -38,6 +38,8 @@ for ch in channel_stats['channel']:
     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 = channel_stats.sort_values('点击uv', ascending=False)
 
@@ -60,40 +62,50 @@ daily = df.groupby(['dt', 'channel']).apply(
     }), include_groups=False
 ).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 的所有渠道
 main_channels = channel_stats[channel_stats['点击uv'] > 10000]['channel'].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]
 
-# 1. 渠道表格行
+# 1. 渠道表格行(使用统一渐变)
 channel_rows = []
 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(
         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_rows = []
 for cat in valid_categories:
@@ -101,25 +113,13 @@ for cat in valid_categories:
     for ch in heatmap_cols:
         if ch in pivot_ror.columns and pd.notna(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>')
         else:
             cells.append("<td>-</td>")
     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_rows = []
 for cat in valid_categories:
@@ -128,7 +128,7 @@ for cat in valid_categories:
         if ch in pivot_uv.columns:
             val = pivot_uv.loc[cat, ch]
             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>')
             else:
                 cells.append("<td>-</td>")
@@ -136,33 +136,12 @@ for cat in valid_categories:
             cells.append("<td>-</td>")
     uv_rows.append("<tr>" + "".join(cells) + "</tr>")
 
-# 4. 进入推荐率热力图(按品类×渠道
+# 4. 进入推荐率热力图(使用统一渐变
 pivot_recommend = df.groupby(['merge一级品类', 'channel']).apply(
     lambda x: (x['进入推荐率'] * x['点击uv']).sum() / x['点击uv'].sum() if x['点击uv'].sum() > 0 else 0,
     include_groups=False
 ).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 = []
 for cat in valid_categories:
     if cat not in pivot_recommend.index:
@@ -171,7 +150,7 @@ for cat in valid_categories:
     for ch in heatmap_cols:
         if ch in pivot_recommend.columns and pd.notna(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>')
         else:
             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_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_rows = []
 for cat2 in valid_cat2_labels:
@@ -209,13 +188,13 @@ for cat2 in valid_cat2_labels:
     for ch in heatmap_cols:
         if ch in pivot_cat2_ror.columns and pd.notna(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>')
         else:
             cells.append("<td>-</td>")
     cat2_ror_rows.append("<tr>" + "".join(cells) + "</tr>")
 
-# 二级品类UV热力图
+# 二级品类UV热力图(使用统一渐变)
 cat2_uv_rows = []
 for cat2 in valid_cat2_labels:
     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:
             val = pivot_cat2_uv.loc[cat2, ch]
             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>')
             else:
                 cells.append("<td>-</td>")
@@ -233,40 +212,6 @@ for cat2 in valid_cat2_labels:
             cells.append("<td>-</td>")
     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
 # ============================================================
@@ -282,11 +227,21 @@ html_content = f"""<!DOCTYPE html>
 <head>
     <meta charset="utf-8">
     <title>渠道效果分析报告</title>
-    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
     <style>
         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; }}
         h2 {{ color: #444; margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; }}
         h3 {{ color: #555; margin-top: 20px; }}
@@ -309,16 +264,23 @@ html_content = f"""<!DOCTYPE html>
         canvas {{ max-height: 400px; }}
         .matrix-section {{ margin-bottom: 40px; }}
         .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>
 </head>
 <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">
         <h1>渠道效果分析报告</h1>
         <p>数据范围: {date_range}</p>
@@ -342,37 +304,11 @@ html_content = f"""<!DOCTYPE html>
             </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>
         <div class="chart-container">
+            <div class="legend">颜色越深=数值越高(统一白→绿渐变)</div>
             <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)}
             </table>
         </div>
@@ -381,7 +317,7 @@ html_content = f"""<!DOCTYPE html>
 
         <h3 id="sec2-1">2.1 回流率矩阵</h3>
         <div class="chart-container heatmap matrix-section">
-            <div class="legend">颜色越深=回流率越高(白→绿渐变,max=0.50)</div>
+            <div class="legend">颜色越深=回流率越高(白→绿渐变,max=0.80)</div>
             <table>
                 {ror_header}
                 {"".join(ror_rows)}
@@ -390,7 +326,7 @@ html_content = f"""<!DOCTYPE html>
 
         <h3 id="sec2-2">2.2 点击UV矩阵</h3>
         <div class="chart-container heatmap matrix-section">
-            <div class="legend">颜色越深=UV越高(白→渐变,max=10万)</div>
+            <div class="legend">颜色越深=UV越高(白→绿渐变,max=10万)</div>
             <table>
                 {uv_header}
                 {"".join(uv_rows)}
@@ -399,7 +335,7 @@ html_content = f"""<!DOCTYPE html>
 
         <h3 id="sec2-3">2.3 进入推荐率矩阵</h3>
         <div class="chart-container heatmap matrix-section">
-            <div class="legend">颜色:红(&lt;0.70) → 黄(0.70-0.80) → 绿(&gt;0.80)</div>
+            <div class="legend">颜色越深=推荐率越高(白→绿渐变,min=0.60,max=1.00)</div>
             <table>
                 {ror_header}
                 {"".join(recommend_rows)}
@@ -410,7 +346,7 @@ html_content = f"""<!DOCTYPE html>
 
         <h3 id="sec3-1">3.1 回流率矩阵</h3>
         <div class="chart-container heatmap matrix-section">
-            <div class="legend">颜色越深=回流率越高(白→绿渐变,max=0.50)</div>
+            <div class="legend">颜色越深=回流率越高(白→绿渐变,max=0.80)</div>
             <table>
                 {cat2_ror_header}
                 {"".join(cat2_ror_rows)}
@@ -419,120 +355,14 @@ html_content = f"""<!DOCTYPE html>
 
         <h3 id="sec3-2">3.2 点击UV矩阵</h3>
         <div class="chart-container heatmap matrix-section">
-            <div class="legend">颜色越深=UV越高(白→渐变,max=10万)</div>
+            <div class="legend">颜色越深=UV越高(白→绿渐变,max=10万)</div>
             <table>
                 {cat2_ror_header}
                 {"".join(cat2_uv_rows)}
             </table>
         </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>
-
-    <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>
 </html>
 """