
本文介绍如何在Pandas中为每位客户准确提取其访问记录中首个和最后一个非“Direct”渠道(若全为“Direct”则回退取“Direct”),并按时间顺序正确处理,适用于归因分析与用户路径建模。
本文介绍如何在pandas中为每位客户准确提取其访问记录中首个和最后一个**非“direct”渠道**(若全为“direct”则回退取“direct”),并按时间顺序正确处理,适用于归因分析与用户路径建模。
在用户行为分析中,识别客户“首次接触渠道”(First Touch)和“最终转化前渠道”(Last Non-Direct Touch)至关重要——但需排除干扰性的“Direct”访问(如手动输入网址、书签访问等),除非所有记录均为 Direct。本教程提供一种高效、可复用的 Pandas 实现方案,无需显式 for 循环,完全基于向量化操作与分组聚合,兼顾性能与逻辑严谨性。
✅ 核心步骤解析
日期标准化与排序:确保时间顺序正确是前提。将 Date 列转为 datetime 类型,并按时间升序排列(sort_values(by="Date")),否则 first()/last() 将失去业务意义。
-
智能过滤逻辑设计:
- 若某客户所有渠道均为 "Direct" → 保留全部记录(用于 fallback);
- 否则 → 仅保留 "Channel" != "Direct" 的记录(跳过所有 Direct)。
这一逻辑通过 groupby().transform() 配合布尔条件实现,避免逐行循环,高效且可读。
分组聚合与结果合并:
对筛选后的数据按 Customer ID 分组,分别调用 .first()(取最早非-Direct 记录的 Channel)和 .last()(取最晚非-Direct 记录的 Channel),再通过 pd.merge() 关联输出。
? 完整可运行代码
import pandas as pd
# 示例数据(请替换为您的实际 DataFrame)
df = pd.DataFrame({
"Customer ID": [1, 1, 1, 2, 2, 2, 3, 3, 3, 3],
"Date": ["1/1/24", "1/2/24", "1/3/24", "1/5/24", "1/6/24", "1/7/24", "1/8/24", "1/9/24", "1/10/24", "1/11/24"],
"Channel": ["Email", "Search", "Direct", "Direct", "Paid", "Email", "Direct", "Direct", "Direct", "Direct"]
})
# 步骤 1:日期转换与排序(关键!)
df["Date"] = pd.to_datetime(df["Date"], format="%m/%d/%y")
df = df.sort_values(by=["Customer ID", "Date"]).reset_index(drop=True)
# 步骤 2:构建分组级布尔掩码
# condition_i: 每个客户是否「全部为 Direct」
condition_i = df.groupby("Customer ID")["Channel"].transform(lambda x: (x == "Direct").all())
# condition_ii: 每个客户存在非 Direct 记录时,标记其非 Direct 行(注意:此处需重写以确保逻辑正确)
mask_non_direct = df["Channel"] != "Direct"
condition_ii = df.groupby("Customer ID")[mask_non_direct].transform("any") & mask_non_direct
# 步骤 3:统一筛选(满足「全 Direct」或「非 Direct 行」)
filtered = df[condition_i | condition_ii]
# 步骤 4:分组聚合
first_df = (filtered
.groupby("Customer ID", as_index=False)
.first()
.rename(columns={"Channel": "First Channel"}))
last_df = (filtered
.groupby("Customer ID", as_index=False)
.last()
.rename(columns={"Channel": "Last Channel"}))
# 步骤 5:合并结果
result = pd.merge(first_df[["Customer ID", "First Channel"]],
last_df[["Customer ID", "Last Channel"]],
on="Customer ID", how="inner")
print(result)✅ 输出:
Customer ID First Channel Last Channel 0 1 Email Search 1 2 Paid Email 2 3 Direct Direct
⚠️ 注意事项与最佳实践
- 日期格式务必匹配:示例中使用 format="%m/%d/%y",若您的日期为 "YYYY-MM-DD" 或 "DD/MM/YYYY",请同步调整 pd.to_datetime() 的 format 参数,或省略 format 让 Pandas 自动推断(但自动推断可能出错,建议显式指定)。
- 空值处理:若 Channel 列含 NaN,需提前用 df.dropna(subset=["Channel"]) 或 df["Channel"].fillna("Unknown") 处理,避免影响 == "Direct" 判断。
- 性能优化:对百万级数据,transform + 布尔索引比 apply + 自定义函数快 5–10 倍;如需进一步加速,可考虑 polars 替代方案。
- 扩展性提示:该逻辑可轻松泛化至其他排除渠道(如 "Internal"、"Referral"),只需修改字符串比较条件即可。
掌握此方法,您不仅能精准提取首末有效触点,更建立起一套可复用于多场景的渠道归因预处理范式——让数据真正驱动增长决策。

















