快速实验篇(B03) 用户行为漏斗与转化率

小肥柴的Hadoop之旅 快速实验篇(B03) 用户行为漏斗与转化率

电商用户行为分析实战 · B 组 · 第三阶段

数据源:b_group.dwd_user_behavior(B02 产出)

技术栈:Hive on MR


目录

  • 前言
  • [0. 实验全景](#0. 实验全景)
  • [1. Why:为什么做 B03](#1. Why:为什么做 B03)
  • [2. 前置:B02 产出的统一入口](#2. 前置:B02 产出的统一入口)
  • [3. 脚本 00:环境检查与基线](#3. 脚本 00:环境检查与基线)
  • [4. 脚本 01:整体漏斗](#4. 脚本 01:整体漏斗)
  • [5. 脚本 02a:Session 行为标志](#5. 脚本 02a:Session 行为标志)
  • [6. 诊断插曲:session 跨天与跨用户](#6. 诊断插曲:session 跨天与跨用户)
  • [7. 脚本 02b:Session 有序漏斗(修订版)](#7. 脚本 02b:Session 有序漏斗(修订版))
  • [8. 脚本 03:品类漏斗](#8. 脚本 03:品类漏斗)
  • [9. 脚本 04:统一验证](#9. 脚本 04:统一验证)
  • [10. 三个核心发现(专题)](#10. 三个核心发现(专题))
  • [11. 对 B04 的影响](#11. 对 B04 的影响)
  • [12. 工程收获](#12. 工程收获)
  • [13. 方法收获](#13. 方法收获)
  • [14. 遗留问题](#14. 遗留问题)
  • [15. 结语](#15. 结语)
  • [附录 A:质量报告](#附录 A:质量报告)
  • [附录 B:执行环境](#附录 B:执行环境)

前言

B03 是 B 组第三个项目,也是第一个业务分析项目。

B01 完成了"数据能不能用"的审计,B02 完成了"把 DWD 升级成生产级资产"的工程化。到了 B03,问题第一次变成:这些数据能回答什么业务问题?

原设计里,B03 的问题是"搜索→点击→下单→支付的漏斗流失在哪?"。但执行下来发现,这个问题本身预设了一个错误的漏斗模型 :实际数据里,搜索和点击没有稳定的顺序关系,支付和下单也不总在同一会话内配对。B03 的最有价值产出,不是"漏斗转化率是多少",而是"原漏斗模型不成立,正确的漏斗模型是什么"。

这是 B 组实验第三次"推翻假设":B01 推翻了四条数据假设,B02 修正了 DWD 结构,B03 推翻了业务漏斗模型假设。


0. 实验全景

text 复制代码
b_group.dwd_user_behavior(B02 产出)
   │  180570 行,11 个 dt 分区
   │  排除 07-27:166770 行
   │
   │  ① 整体漏斗(脚本 01)
   ▼
ads_funnel_overall         4 行(search/click/order/pay)
   │
   │  ② Session 行为标志(脚本 02a)
   ▼
ads_funnel_session         8744 行(每 session 一行,15 种 combo)
   │
   │  ③ Session 有序漏斗(脚本 02b)
   ▼
ads_funnel_session_ordered 8744 行(每 session 一行,含四个 *_seq)
   │
   │  ④ 品类漏斗(脚本 03)
   ▼
ads_funnel_category        20 行(每品类一行)

   ↓
脚本 04 统一验证:16 项全部通过
  • 小结 :B02 说"这是你的资产",B03 说"用这份资产能算出什么业务结论,以及哪些结论不成立"。

1. Why:为什么做 B03

1.1 如果不做这一步,后面哪些项目会受到影响?

B04(热门品类 Top10)会受影响:它只能按点击量排序,可能推荐"高点击低转化"的品类。没有 B03 的品类转化率,B04 的排序就是瞎猜;而且 B03 还发现品类分布极端均匀,这一发现直接影响 B04 的结论是否成立。

B06(页面单跳转化率)会受影响:它需要知道"点击→下单"是不是一个可用的漏斗环节,才能设计页面路径。如果 B03 没跑,B06 可能按"搜索→点击→下单"设计,结果大部分路径为空。

B08(搜索词分析)会受影响 :它需要知道搜索在漏斗中的位置。原以为搜索是入口,B03 实测发现不是,B08 的分析框架要调整。

B13(ADS 主题宽表)会受影响:宽表缺核心业务字段------整体转化率、环节流失率、品类转化率。

B15(总结与工程复盘)会受影响:复盘时缺一条"业务漏斗模型被推翻"的关键认知,整个 B 组的故事线不完整。

1.2 这一步具体做什么?

用 Hive 计算四张表:

表 粒度 核心问题 行数
ads_funnel_overall 全站环节 各环节 PV/UV/Session 量级 4
ads_funnel_session Session 行为组合分布 8744
ads_funnel_session_ordered Session 有序漏斗 8744
ads_funnel_category 品类 品类点击→下单→支付 20

1.3 做完怎么验证?

验证项 方法 期望
PV 守恒 整体漏斗 PV = 基线 37115/111310/10887/7458
Session 守恒 各环节 session 数与 combos 一致 7426/8513/4977/4111
品类守恒 漏斗汇总 = 子表行数 order 32487, pay 22436
跨表一致 02a 与 02b 行数相同 8744
组合守恒 combo 之和 = session 总数 8744

1.4 与下游什么关系?

下游 本步给了什么 不用会怎样
B04 品类转化率 + "品类无区分度"结论 会出无意义的 Top10
B05 Session 转化标签 不知道哪些 session 高价值
B06 "点击→下单"是稳定环节 可能设计错误的页面路径
B08 搜索不是漏斗入口 分析框架错位
B13 漏斗指标字段 ADS 宽表缺核心指标
B14 漏斗图数据 无法画转化漏斗
B15 统一漏斗口径 + 业务认知 复盘无统一口径、缺故事线

2. 前置:B02 产出的统一入口

2.1 表结构

B03 使用的核心表:

sql 复制代码
b_group.dwd_user_behavior (
  user_id, session_id, page_id, action_time, ts, city_id,
  behavior_type, behavior_combo,
  search_keyword, click_category_id, click_product_id,
  order_category_ids, order_product_ids,
  pay_category_ids, pay_product_ids,
  is_*_field_used, is_valid_behavior, is_multi_behavior,
  is_unclassified, is_true_missing,
  quality_flags, raw_line_id, seq_in_session,
  dt  -- 分区
)

关键字段:

  • behavior_type:search / click / order / pay,四类互斥
  • ts:timestamp,用于时间差
  • seq_in_session:session 内按 ts, raw_line_id 双键排序的序号

四张子表:dwd_order_category、dwd_order_product、dwd_pay_category、dwd_pay_product。

2.2 基线记录

执行前记录(写入 output/b03_baseline.txt):

text 复制代码
main_total_all,180570
main_total_excl_0727,166770
behavior_search,37115
behavior_click,111310
behavior_order,10887
behavior_pay,7458

2.3 07-27 处理策略

B01 已确认 07-27 数据偏低 15~20%,疑截断。B03 采用:

  • 主分析排除 07-27 :dt BETWEEN '2019-07-17' AND '2019-07-26'
  • 报告中所有数字均基于 10 天数据

2.4 全局配置的两条警告

执行前发现 hive-site.xml 里两条全局配置:

xml 复制代码
<property>
    <name>hive.exec.mode.local.auto</name>
    <value>true</value>       <!-- B02 OOM 元凶 -->
</property>
<property>
    <name>hive.auto.convert.join</name>
    <value>false</value>      <!-- MAPJOIN 不自动触发 -->
</property>

影响一 :local.auto=true 会让输入文件数 ≤ 4 的查询走本地模式,用 Hive CLI 256MB 堆跑 MR,一碰多值 explode 或窗口就 OOM。

决策:B03 所有写表脚本在开头显式覆盖:

sql 复制代码
SET hive.exec.mode.local.auto=false;
SET mapreduce.task.io.sort.mb=64;

不改全局配置,避免影响其他项目。

2.5 Metastore 全局固定

执行前确认 hive-site.xml:

xml 复制代码
<property>
    <name>javax.jdo.option.ConnectionURL</name>
    <value>jdbc:derby:;databaseName=/home/hadoop/hive_metastore/metastore_db;create=true</value>
</property>

Metastore 被固定在 /home/hadoop/hive_metastore/metastore_db,与当前工作目录无关。B02 手册里反复强调的 Derby 目录漂移问题在本环境不存在 。后续 B03--B15 只需 USE b_group;,不需要每项目独立 metastore。

工作目录 /home/hadoop/bXX 仍然保留,用于脚本、日志、输出的组织。


3. 脚本 00:环境检查与基线

3.1 目的

确认 B02 表可用,记录基线,确认 07-27 排除后的行数。

3.2 脚本

sql 复制代码
-- B03-00:环境检查与基线
USE b_group;

SELECT 'main_total_all' AS metric, CAST(COUNT(*) AS STRING) AS value
FROM dwd_user_behavior;

SELECT 'main_total_excl_0727' AS metric, CAST(COUNT(*) AS STRING) AS value
FROM dwd_user_behavior
WHERE dt BETWEEN '2019-07-17' AND '2019-07-26';

SELECT CONCAT('behavior_', behavior_type) AS metric, CAST(COUNT(*) AS STRING) AS value
FROM dwd_user_behavior
WHERE dt BETWEEN '2019-07-17' AND '2019-07-26'
GROUP BY behavior_type;

3.3 执行结果

指标 期望 实测
main_total_all 180570 180570 ✅
main_total_excl_0727 166770 166770 ✅
behavior_search 37115 37115 ✅
behavior_click 111310 111310 ✅
behavior_order 10887 10887 ✅
behavior_pay 7458 7458 ✅

3.4 观察

三次查询都被 Hive 自动拒绝本地模式 (日志原话:Cannot run job locally: Number of Input Files (= 10/11) is larger than hive.exec.mode.local.auto.input.files.max(= 4))。说明当前分区粒度下,每个 dt 分区一个文件,10~11 个文件刚好超过阈值。

但如果某查询只命中 3 个分区(≤4 文件),它就会偷偷切进本地模式 ------所以写表脚本仍要显式 SET hive.exec.mode.local.auto=false; 防御。


4. 脚本 01:整体漏斗

4.1 设计

  • 4 行,每行一个环节(search / click / order / pay)
  • 三个口径:PV、UV、Session 数
  • 转化率不写表,查询时用窗口函数算(避免 CTAS 嵌窗口的兼容问题)
  • 排除 07-27

4.2 脚本

sql 复制代码
USE b_group;

SET hive.exec.mode.local.auto=false;
SET mapreduce.task.io.sort.mb=64;
SET hive.exec.compress.intermediate=true;
SET hive.exec.compress.output=true;
SET hive.auto.convert.join=true;

DROP TABLE IF EXISTS ads_funnel_overall;

CREATE TABLE ads_funnel_overall (
  stage        string,
  stage_order  int,
  pv           bigint,
  uv           bigint,
  session_cnt  bigint
)
STORED AS ORC
TBLPROPERTIES ("orc.compress"="SNAPPY");

INSERT OVERWRITE TABLE ads_funnel_overall
SELECT 'search' AS stage, 1 AS stage_order,
       COUNT(*) AS pv, COUNT(DISTINCT user_id) AS uv,
       COUNT(DISTINCT session_id) AS session_cnt
FROM dwd_user_behavior
WHERE dt BETWEEN '2019-07-17' AND '2019-07-26' AND behavior_type = 'search'
UNION ALL
SELECT 'click', 2, COUNT(*), COUNT(DISTINCT user_id), COUNT(DISTINCT session_id)
FROM dwd_user_behavior
WHERE dt BETWEEN '2019-07-17' AND '2019-07-26' AND behavior_type = 'click'
UNION ALL
SELECT 'order', 3, COUNT(*), COUNT(DISTINCT user_id), COUNT(DISTINCT session_id)
FROM dwd_user_behavior
WHERE dt BETWEEN '2019-07-17' AND '2019-07-26' AND behavior_type = 'order'
UNION ALL
SELECT 'pay', 4, COUNT(*), COUNT(DISTINCT user_id), COUNT(DISTINCT session_id)
FROM dwd_user_behavior
WHERE dt BETWEEN '2019-07-17' AND '2019-07-26' AND behavior_type = 'pay';

4.3 执行结果

执行耗时 203 秒(5 个 MR Stage)。

stage stage_order pv uv session_cnt
search 1 37115 100 7426
click 2 111310 100 8513
order 3 10887 100 4977
pay 4 7458 100 4111

4.4 初步观察

观察 1:UV 口径完全退化 。100 用户全样本,每个人都触达过每一类行为。后续所有分析必须用 PV 或 Session 口径,不能依赖 UV。

观察 2:click session (8513) > search session (7426)。很多点击不是从搜索来的。搜索→点击不是严格父子关系。

观察 3:真正的漏斗是"点击→下单→支付":

  • Session 层点击→下单:4977 / 8513 = 58.5%
  • Session 层下单→支付:4111 / 4977 = 82.6%
  • PV 层点击→下单:10887 / 111310 = 9.78%

PV 和 Session 口径差异巨大,因为 PV 口径下点击基数远大于 order。两套口径都要在报告中说明。


5. 脚本 02a:Session 行为标志

5.1 设计

每个 session 一行,含四个布尔标志(has_search / has_click / has_order / has_pay),加上 total_events 和 day_cnt。

5.2 脚本

sql 复制代码
USE b_group;

SET hive.exec.mode.local.auto=false;
SET mapreduce.task.io.sort.mb=64;

DROP TABLE IF EXISTS ads_funnel_session;

CREATE TABLE ads_funnel_session
STORED AS ORC
TBLPROPERTIES ("orc.compress"="SNAPPY")
AS
SELECT
  session_id,
  MAX(CASE WHEN behavior_type = 'search' THEN 1 ELSE 0 END) AS has_search,
  MAX(CASE WHEN behavior_type = 'click'  THEN 1 ELSE 0 END) AS has_click,
  MAX(CASE WHEN behavior_type = 'order'  THEN 1 ELSE 0 END) AS has_order,
  MAX(CASE WHEN behavior_type = 'pay'    THEN 1 ELSE 0 END) AS has_pay,
  COUNT(*) AS total_events,
  COUNT(DISTINCT dt) AS day_cnt
FROM dwd_user_behavior
WHERE dt BETWEEN '2019-07-17' AND '2019-07-26'
GROUP BY session_id;

5.3 组合分布结果

Session 总数 8744。15 种行为组合:

combo 含义 数量 占比
1111 搜索+点击+下单+支付 2882 33.0%
1100 搜索+点击 1711 19.6%
1110 搜索+点击+下单 1709 19.5%
1101 搜索+点击+支付(无下单) 955 10.9%
0100 仅点击 783 9.0%
0110 点击+下单 241 2.8%
0101 点击+支付(无下单) 152 1.7%
1000 仅搜索 132 1.5%
0111 点击+下单+支付 80 0.9%
0010 仅下单 38 0.4%
0001 仅支付 21 0.2%
1010 搜索+下单 19 0.2%
1001 搜索+支付 13 0.1%
1011 搜索+下单+支付 5 0.06%
0011 下单+支付 3 0.03%

守恒核对:

  • 含 search:2882+1711+1709+955+132+19+13+5 = 7426 ✅
  • 含 click:2882+1711+1709+955+783+241+152+80 = 8513 ✅
  • 含 order:2882+1709+241+80+38+19+5+3 = 4977 ✅
  • 含 pay:2882+955+152+80+21+13+5+3 = 4111 ✅

5.4 三个反直觉观察

观察 1 :全链路 session(1111)占比 33%,是最大类。原以为主流是"仅点击"或"仅搜索"。这说明 session 很长(平均 19 事件),或 session_id 在多天间被复用。

观察 2 :1101 + 0101 + 0001 = 1128 session 出现"支付但无下单"。逻辑上支付必须紧跟下单,这暗示 session 跨天或 session_id 复用。

观察 3 :纯搜索 session(1000)只有 132 个,纯点击 session(0100)只有 783 个。绝大多数 session 都包含"搜索 + 点击"组合。


6. 诊断插曲:session 跨天与跨用户

组合分布里的三个反直觉观察,必须查清楚才能继续。

6.1 诊断 1:session 跨天分布

sql 复制代码
SELECT day_cnt, COUNT(*) AS session_cnt
FROM ads_funnel_session GROUP BY day_cnt ORDER BY day_cnt;

结果:

text 复制代码
1       8736
2       8

结论:99.9% 的 session 单天,8 个跨天 session 属异常值但占比极小。

6.2 诊断 2:抽查 1101 的 session 明细

sql 复制代码
SELECT session_id, dt, behavior_type, action_time, seq_in_session
FROM dwd_user_behavior
WHERE session_id IN (
  SELECT session_id FROM ads_funnel_session
  WHERE has_search=1 AND has_click=1 AND has_order=0 AND has_pay=1 LIMIT 3
)
ORDER BY session_id, seq_in_session LIMIT 60;

样本 1(16 事件,04:36~04:38):

text 复制代码
seq=1  pay    04:36:28   ← 支付在 session 最开头
seq=2  search 04:36:37
seq=3  click  04:36:45
...

样本 2(3 事件):

text 复制代码
seq=1  click  20:48:47
seq=2  search 20:48:51
seq=3  pay    20:48:59   ← 点完就支付,没有 order

样本 3(13 事件,含 2 次 pay):

text 复制代码
seq=7  pay    19:06:24
seq=9  pay    19:06:36   ← 两次支付,都没 order

业务解释 :pay 事件独立出现,最合理的解释是支付历史订单------用户前一天/上一个 session 下过单,今天进来直接支付;或订单行为未在同一 session 记录。

6.3 诊断 3:session 是否跨用户

sql 复制代码
SELECT COUNT(*) AS sessions_multi_user
FROM (
  SELECT session_id FROM dwd_user_behavior
  WHERE dt BETWEEN '2019-07-17' AND '2019-07-26'
  GROUP BY session_id HAVING COUNT(DISTINCT user_id) > 1
) t;

结果:0。

结论:session_id 不跨用户,数据健康。

6.4 对 02b 的决定性影响

原设计 02b 想算"严格有序全链路 session 数"。现在看:

  • 33% 的 session 是 1111,但其中大部分 pay 不在 order 之后
  • 严格有序漏斗会严重低估,甚至可能接近 0

决策:02b 改为"两段有序 + 全链路宽松",分三个两段指标 + 两个全链路指标:

指标 定义
search_before_click 同 session 内 search_seq < click_seq
click_before_order 同 session 内 click_seq < order_seq
order_before_pay 同 session 内 order_seq < pay_seq
full_strict 上述三个全部满足
full_loose 同 session 内含全部四类(无论顺序)

7. 脚本 02b:Session 有序漏斗(修订版)

7.1 脚本

sql 复制代码
USE b_group;

SET hive.exec.mode.local.auto=false;
SET mapreduce.task.io.sort.mb=64;

DROP TABLE IF EXISTS ads_funnel_session_ordered;

CREATE TABLE ads_funnel_session_ordered
STORED AS ORC
TBLPROPERTIES ("orc.compress"="SNAPPY")
AS
SELECT
  session_id,
  MIN(CASE WHEN behavior_type = 'search' THEN seq_in_session END) AS search_seq,
  MIN(CASE WHEN behavior_type = 'click'  THEN seq_in_session END) AS click_seq,
  MIN(CASE WHEN behavior_type = 'order'  THEN seq_in_session END) AS order_seq,
  MIN(CASE WHEN behavior_type = 'pay'    THEN seq_in_session END) AS pay_seq
FROM dwd_user_behavior
WHERE dt BETWEEN '2019-07-17' AND '2019-07-26'
GROUP BY session_id;

7.2 五指标汇总

指标 值 分母 比率
search_sessions 7426 --- ---
search_before_click 1922 search∩click = 7257 26.5%
click_before_order 4242 click∩order = 4939 85.9%
order_before_pay 1582 order∩pay = 2970 53.3%
full_strict 260 full_loose = 2882 9.0%
full_loose 2882 --- ---

7.3 三个重磅发现

发现 A:搜索→点击的顺序在大多数 session 里是反的。

  • 同时有 search 和 click 的 session:7257
  • 其中先搜后点(search_seq < click_seq):仅 1922(26.5%)
  • 剩下 5335(73.5%)是"先点击,后搜索"

这不是数据错误,是埋点事实:用户可能在浏览页/推荐流直接点击,再回到搜索框搜索。搜索不是可用的漏斗起点,真正的漏斗起点是点击。

发现 B:点击→下单是唯一稳健的有序漏斗。

  • 85.9% 的 click∩order session 里 click_seq < order_seq
  • 这与业务直觉一致,也是唯一"经得起顺序检验"的环节

发现 C:下单→支付只有 53.3% 顺序正确。

  • 近一半的 session 里支付先于下单
  • 印证了"支付历史订单"的真实业务模式

7.4 修订后的漏斗主结论

原来的"搜索→点击→下单→支付"漏斗模型,实际数据上应改为:

text 复制代码
入口          点击 PV 111310 / click session 8513
              ↓  顺序可验证:85.9%
下单          order PV 10887 / order session 4977
              ↓  顺序可验证:53.3%
支付          pay PV 7458 / pay session 4111

搜索          独立分析:search PV 37115 / 与点击顺序无关

搜索不是漏斗入口,而是独立的"用户主动意图"信号,后续 B08(搜索词分析)要用这个定位。


8. 脚本 03:品类漏斗

8.1 设计

  • 点击品类来自 dwd_user_behavior.click_category_id(单值)
  • 下单品类来自 dwd_order_category(explode 后)
  • 支付品类来自 dwd_pay_category(explode 后)
  • 三表 FULL OUTER JOIN,保证没有点击但有下单的品类也出现
  • 不含搜索------B03 已确认搜索不是漏斗环节

8.2 脚本

sql 复制代码
USE b_group;

SET hive.exec.mode.local.auto=false;
SET mapreduce.task.io.sort.mb=64;

DROP TABLE IF EXISTS ads_funnel_category;

CREATE TABLE ads_funnel_category (
  category_id bigint,
  click_cnt   bigint,
  order_cnt   bigint,
  pay_cnt     bigint
)
STORED AS ORC
TBLPROPERTIES ("orc.compress"="SNAPPY");

INSERT OVERWRITE TABLE ads_funnel_category
SELECT
  COALESCE(c.category_id, o.category_id, p.category_id) AS category_id,
  COALESCE(c.click_cnt, 0) AS click_cnt,
  COALESCE(o.order_cnt, 0) AS order_cnt,
  COALESCE(p.pay_cnt, 0)   AS pay_cnt
FROM (
  SELECT click_category_id AS category_id, COUNT(*) AS click_cnt
  FROM dwd_user_behavior
  WHERE dt BETWEEN '2019-07-17' AND '2019-07-26'
    AND behavior_type = 'click' AND click_category_id != -1
  GROUP BY click_category_id
) c
FULL OUTER JOIN (
  SELECT category_id, COUNT(*) AS order_cnt
  FROM dwd_order_category
  WHERE dt BETWEEN '2019-07-17' AND '2019-07-26'
  GROUP BY category_id
) o ON c.category_id = o.category_id
FULL OUTER JOIN (
  SELECT category_id, COUNT(*) AS pay_cnt
  FROM dwd_pay_category
  WHERE dt BETWEEN '2019-07-17' AND '2019-07-26'
  GROUP BY category_id
) p ON COALESCE(c.category_id, o.category_id) = p.category_id;

8.3 结果

20 个品类,按 click 降序:

品类 click order pay click→order order→pay
2 5687 1628 1120 28.6% 68.8%
11 5652 1634 1115 28.9% 68.2%
15 5652 1532 1165 27.1% 76.0%
17 5640 1621 1132 28.7% 69.8%
20 5628 1632 1141 29.0% 69.9%
12 5609 1593 1131 28.4% 71.0%
7 5600 1656 1162 29.6% 70.2%
9 5595 1600 1153 28.6% 72.1%
19 5577 1598 1072 28.7% 67.1%
5 5572 1681 1053 30.2% 62.6%
13 5564 1647 1087 29.6% 66.0%
14 5558 1657 1087 29.8% 65.6%
18 5540 1626 1119 29.3% 68.8%
10 5531 1619 1089 29.3% 67.3%
4 5522 1630 1170 29.5% 71.8%
8 5510 1610 1144 29.2% 71.1%
16 5493 1656 1143 30.1% 69.0%
3 5482 1606 1118 29.3% 69.6%
1 5460 1628 1120 29.8% 68.8%
6 5438 1633 1115 30.0% 68.3%

8.4 守恒验证

指标 值 对照
category_click_sum 111310 ✅ 与基线一致
order_cat_rows 32487 ---
funnel_order_sum 32487 ✅ 相等
pay_cat_rows 22436 ---
funnel_pay_sum 22436 ✅ 相等

8.5 关键观察:品类数据太均匀了

20 个品类的 click 全部在 5438~5687 之间(波动 ±2.3%),转化率全部在 27~30% / 68~76% 之间。

真实电商数据应是长尾分布 ,头部品类 click 可能是尾部品类的 10~100 倍,转化率也有 3~5 倍差异。这里的均匀度极端反常。

这不是清洗问题,是数据集生成方式的问题 。B01 只审计了行级质量,没审计维度分布,B03 补上了这一课。


9. 脚本 04:统一验证

9.1 脚本

sql 复制代码
-- 16 项验证
USE b_group;

SELECT 'V1_overall_rows' AS metric, CAST(COUNT(*) AS STRING) AS value FROM ads_funnel_overall;
SELECT 'V2_overall_pv_sum', CAST(SUM(pv) AS STRING) FROM ads_funnel_overall;
SELECT 'V3_search_sessions', CAST(session_cnt AS STRING) FROM ads_funnel_overall WHERE stage = 'search';
SELECT 'V4_click_sessions', CAST(session_cnt AS STRING) FROM ads_funnel_overall WHERE stage = 'click';
SELECT 'V5_order_sessions', CAST(session_cnt AS STRING) FROM ads_funnel_overall WHERE stage = 'order';
SELECT 'V6_pay_sessions', CAST(session_cnt AS STRING) FROM ads_funnel_overall WHERE stage = 'pay';
SELECT 'V7_session_rows', CAST(COUNT(*) AS STRING) FROM ads_funnel_session;
SELECT 'V8_session_no_behavior', CAST(COUNT(*) AS STRING) FROM ads_funnel_session
  WHERE has_search + has_click + has_order + has_pay = 0;
SELECT 'V9_session_multi_day', CAST(COUNT(*) AS STRING) FROM ads_funnel_session WHERE day_cnt > 1;
SELECT 'V10_ordered_rows', CAST(COUNT(*) AS STRING) FROM ads_funnel_session_ordered;
SELECT 'V11_full_loose', CAST(COUNT(*) AS STRING) FROM ads_funnel_session_ordered
  WHERE search_seq IS NOT NULL AND click_seq IS NOT NULL
    AND order_seq IS NOT NULL AND pay_seq IS NOT NULL;
SELECT 'V12_full_strict', CAST(COUNT(*) AS STRING) FROM ads_funnel_session_ordered
  WHERE search_seq IS NOT NULL AND click_seq IS NOT NULL
    AND order_seq IS NOT NULL AND pay_seq IS NOT NULL
    AND search_seq < click_seq AND click_seq < order_seq AND order_seq < pay_seq;
SELECT 'V13_category_rows', CAST(COUNT(*) AS STRING) FROM ads_funnel_category;
SELECT 'V14_category_click_sum', CAST(SUM(click_cnt) AS STRING) FROM ads_funnel_category;
SELECT 'V15_category_order_sum', CAST(SUM(order_cnt) AS STRING) FROM ads_funnel_category;
SELECT 'V16_category_pay_sum', CAST(SUM(pay_cnt) AS STRING) FROM ads_funnel_category;

9.2 结果(16 项全部通过)

指标 期望 实测
V1_overall_rows 4 4 ✅
V2_overall_pv_sum 166770 166770 ✅
V3_search_sessions 7426 7426 ✅
V4_click_sessions 8513 8513 ✅
V5_order_sessions 4977 4977 ✅
V6_pay_sessions 4111 4111 ✅
V7_session_rows 8744 8744 ✅
V8_session_no_behavior 0 0 ✅
V9_session_multi_day 8 8 ✅
V10_ordered_rows 8744 8744 ✅
V11_full_loose 2882 2882 ✅
V12_full_strict 260 260 ✅
V13_category_rows 20 20 ✅
V14_category_click_sum 111310 111310 ✅
V15_category_order_sum 32487 32487 ✅
V16_category_pay_sum 22436 22436 ✅

10. 三个核心发现(专题)

这一章把 B03 最有价值的三条发现单独拎出来,它们都是推翻了预设的发现。

10.1 发现 1:搜索不是漏斗入口

预设:搜索 → 点击 → 下单 → 支付,搜索是漏斗第一层。

实测:

  • 同时有 search 和 click 的 session:7257
  • 先搜后点(search_seq < click_seq):1922(26.5%)
  • 先点后搜:5335(73.5%)

结论 :搜索与点击没有稳定的顺序关系。搜索不是"漏斗起点",而是独立的"用户主动意图"信号。

影响:

  • B04:品类排序不能假设"搜索→点击"
  • B06:页面路径不含搜索
  • B08:搜索分析要独立定位,不挂在漏斗下

10.2 发现 2:支付无下单是真实业务

预设:支付必须紧跟下单。

实测:

  • 1101(搜索+点击+支付,无下单):955 session
  • 0101(点击+支付,无下单):152 session
  • 0001(仅支付):21 session
  • 合计 1128 session 里"支付时没有下单"

明细显示 pay 事件独立出现,最合理解释是支付历史订单------用户前一天/上一个 session 下过单,今天进来直接支付。

结论 :order_before_pay 只有 53.3% ,严格全链路(full_strict)只有 260 ,远小于宽松全链路 2882。

影响:

  • B07:会话分析要区分"新订单支付"和"历史订单支付"
  • B12:异常流量识别要考虑"纯支付 session"是正常业务
  • B14:漏斗图要标注两种支付模式

10.3 发现 3:品类维度没有业务区分度

预设:品类应该有明显头部和长尾。

实测:

  • 20 个品类,click 全部在 5438~5687 之间(波动 ±2.3%)
  • 转化率全部在 27~30% / 68~76% 之间

真实电商应呈长尾分布,这里是极端均匀。

结论 :这不是清洗问题,是数据集生成方式的问题 。B01 只审计了行级质量,没审计维度分布。B03 补上了这一课。

影响:

  • B04:Top10 与 Bottom10 只差 4%,热门品类 Top10 的分析价值有限
  • B09:城市×品类偏好分析也会受均匀度影响
  • B10:品类共现分析要重新评估

11. 对 B04 的影响

B04 原设计是"热门品类 Top10 与加权排名"。B03 发现品类分布均匀后,B04 需要调整。

11.1 原设计的问题

  • 若按点击量排 Top10:Top1(品类 2,5687)和 Bottom1(品类 6,5438)只差 4.6%
  • 若按加权(点击×20% + 下单×30% + 支付×50%):由于三个维度的品类排序都不一致,加权结果会在 20 个品类间轻微抖动
  • 结论:排名差异不具业务意义

11.2 三个方案

方案 描述 优点 缺点
A 保留 Top10,报告中明确标注"品类分布均匀" 简单 结论无意义
B 改为品类转化率排行(click→order / order→pay) 至少区分高/低转化品类 转化率差异也仅 3%
C 改为品类共现(把 B10 部分提前) 至少看品类间关系 与原 B04 主题偏离

11.3 推荐方案

推荐 A + B 组合:

  1. 保留 Top10(原设计),但报告中标注"品类分布均匀,排名差异不具业务意义"
  2. 增加"品类转化率排行"作为补充
  3. 在 B04 报告里明确写出这条数据约束,与 B01 的"07-27 疑截断"、"100 用户"并列

决策权留给 B04 执行时。B03 的任务是把发现记录下来。


12. 工程收获

12.1 全局配置的两条坑

  • hive.exec.mode.local.auto=true 会让小输入查询走本地模式,用 256MB 堆跑 MR,容易 OOM
  • 修正:每个写表脚本开头 SET hive.exec.mode.local.auto=false; SET mapreduce.task.io.sort.mb=64;
  • hive.auto.convert.join=false 意味着 MAPJOIN 不自动触发,需要显式 SET 或 /*+ MAPJOIN */

12.2 交互式 SQL 与脚本文件的取舍

  • 单行 SELECT:交互式可靠
  • 多行 SQL:必须写 .hql 文件 + hive -f
  • 短查询:hive -e "..." 单行可靠
  • 长查询:写成 .hql 文件,避免 PS2 续行卡死

12.3 输出日志的双刃剑

  • 2>/dev/null 屏蔽噪声,但连错误信息也屏蔽
  • 推荐:2>&1 | grep -vE 'SLF4J|Logging initialized|Hive Session|^$'
  • 或保留全部日志,从日志尾部看结果

12.4 先 SHOW TABLES 再验证

建表脚本执行后,先 SHOW TABLES LIKE 'xxx' 确认表存在,再跑验证 SELECT 。这一步别省------否则 SELECT 报错被 2>/dev/null 吞掉,看到的是"空输出"而不是"错误"。

12.5 ORC + Snappy + 分区表

  • 主表 dwd_user_behavior:分区 ORC,11 个 dt 分区
  • B03 的四张表:非分区 ORC,Snappy
  • 决策原因:B03 的结果表很小(4 行 / 8744 行 / 20 行),分区收益有限

12.6 UNION ALL 多分支的 Stage 数

ads_funnel_overall 的 4 个 UNION ALL 分支,Hive 编译成 5 个 MR Stage(4 个分支各 1 个 + 1 个汇总)。耗时 203 秒。如果拆成 4 个 INSERT INTO 单跑,总耗时相近但更可控。


13. 方法收获

13.1 假设驱动:B03 推翻了"漏斗模型"假设

  • B01 推翻 4 条数据假设
  • B02 修正 DWD 结构
  • B03 推翻"搜索→点击→下单→支付"漏斗模型

每一条推翻,都对应一次设计调整。这些"推翻"本身是最有价值的产出。

13.2 组合分布是 Session 分析的第一手材料

MAX(CASE WHEN ...) 生成四个布尔标志,再 GROUP BY 四个标志,得到 15 种组合分布。这一步比计算任何转化率都重要------它直接暴露了数据的行为模式。

13.3 顺序分析必须显式验证

  • 原设计假设"搜索→点击→下单→支付"顺序天然成立
  • 实测发现:search→click 只有 26.5% 顺序正确,order→pay 只有 53.3%
  • "顺序"不是数据的天然属性,是需要验证的假设

13.4 维度分布审计不能漏

B01 审计了行级质量(null、空串、格式),没审计维度分布。B03 发现品类分布极端均匀,这才补齐了"维度分布"这一课。

后续项目:B04(品类)、B09(城市)、B08(搜索词)都要先看分布,再下结论。

13.5 口径说明必须显式写出

  • 整体漏斗有 PV / UV / Session 三种口径
  • UV 口径在本数据完全退化(都是 100)
  • 品类漏斗的点击是"行为次数",下单/支付是"品类出现次数"
  • 不写清口径的数字,都是误导

14. 遗留问题

14.1 8 个跨天 session

  • day_cnt=2 的 session 有 8 个
  • 占比 0.09%,暂不处理
  • 未验证:这 8 个 session 是否与"支付无下单"相关

14.2 3 组完全重复(沿用 B01)

  • 同一用户、同一秒、同一页面、同一搜索词
  • 占比 0.0017%,暂不处理

14.3 07-27 疑截断(沿用 B01)

  • 数据量偏低 15~20%
  • B03 全部排除 07-27
  • 未做含 07-27 的对照版本

14.4 100 用户样本(沿用 B01)

  • 100 用户全样本,UV 口径完全退化
  • 所有用户级分析(B11 RFM)都要谨慎

14.5 品类均匀(B03 新发现)

  • 20 个品类分布极端均匀
  • 未验证:是数据集生成方式问题,还是清洗过程引入
  • 影响 B04、B09、B10 的分析价值

14.6 支付无下单(B03 新发现)

  • 1128 session 出现"支付无下单"
  • 最合理解释是"支付历史订单"
  • 未验证:能否通过用户跨 session 行为佐证

15. 结语

B03 是 B 组第一个业务分析项目。它完成了"用 B02 的资产回答业务问题"的任务,但更重要的是:它推翻了预设的漏斗模型。

原来的漏斗模型是"搜索→点击→下单→支付"。实测发现:

  • 搜索不是漏斗入口(73.5% 的 session 先点后搜)
  • 下单→支付不总在同一 session 内(只有 53.3% 顺序正确)
  • 品类维度没有业务区分度(波动 ±2.3%)

这三条发现,才是 B03 对后续 B04--B15 的真正贡献 :不是"一份漏斗报告",而是"一套已被验证的业务认知"。

B01 建立了"数据认知 "(11 天数据、四类互斥、文件顺序≠时间顺序),B02 建立了"结构认知 "(分区、ts、seq_in_session),B03 建立了"业务认知"(搜索不是漏斗入口、支付无下单是真实业务、品类均匀)。

这三层认知,是 B04--B15 的共同底座。


附录 A:质量报告

output/b03_quality_report.csv:

text 复制代码
metric,value
overall_rows,4
overall_pv_sum,166770
search_pv,37115
click_pv,111310
order_pv,10887
pay_pv,7458
search_sessions,7426
click_sessions,8513
order_sessions,4977
pay_sessions,4111
session_rows,8744
session_no_behavior,0
session_multi_day,8
session_combo_1111,2882
session_combo_1100,1711
session_combo_1110,1709
session_combo_1101,955
session_combo_0100,783
session_combo_0110,241
session_combo_0101,152
session_combo_1000,132
search_before_click,1922
click_before_order,4242
order_before_pay,1582
full_loose,2882
full_strict,260
category_rows,20
category_click_sum,111310
category_order_sum,32487
category_pay_sum,22436
dt_range_excl_0727,2019-07-17_to_2019-07-26
dt_0727_anomaly,suspected_truncation
data_quality_finding,category_distribution_suspiciously_uniform
data_quality_finding,search_not_funnel_entry
data_quality_finding,pay_without_order_is_real_business

附录 B:执行环境

项 版本
Hadoop 3.3.6
Hive 3.1.3 (on MR)
集群 1 master + 3 worker,每节点 2GB
Java 8
工作目录 /home/hadoop/b03
Metastore 全局固定 /home/hadoop/hive_metastore/metastore_db
Warehouse /user/hive/warehouse

本文是 B03 阶段完整复盘,也是 B04--B15 的业务底座说明。

相关推荐
径硕科技JINGdigital2 小时前
出海企业想要借助统一平台接入国际主流基础模型,可选哪些云上生成式 AI 平台?
大数据·人工智能
Elastic 中国社区官方博客2 小时前
使用 Jev 作为 search reranker:基准测试与实现方法
大数据·运维·elasticsearch·全文检索
卷毛迷你猪2 小时前
快速实验篇(B01)用户行为日志画像与 DWD 标准化
大数据·hive·hadoop
四季豆333 小时前
中翰软件调整战略定位:以数据与知识“双治理”搭建企业AI落地桥梁
大数据
T06205143 小时前
【2026年更新】全国自然保护区矢量数据,列表+边界+功能区
大数据
ha_lydms3 小时前
MaxCompute中JSON函数
大数据·数据库·阿里云·json·dataworks·maxcompute·odps
H0311169854 小时前
移动应用数据分析平台信息整理:月狐数据、七麦数据、蝉大师
大数据·人工智能
小宋10214 小时前
Jev 不是另一个聊天模型:Choice、Score、Boolean 与概率决策完整实战
大数据·人工智能
数字化顾问4 小时前
(136页PPT)BCGIDG资本中国某省市场Geneva项目商业尽职调查项目报告(附下载方式)
大数据·人工智能