相关不是因果:光纤用户流失率高一倍,是光纤的锅吗

214次阅读
没有评论

混杂变量:合并差 22.9pp,按合同分层后是 22.4 / 10.0 / 5.3pp


先看一个场景

分析师发现:用光纤的用户流失率 41.89%,用 DSL 的只有 18.96%。

结论:”光纤体验有问题,得优化。” 或者更激进:”停售光纤。”

先别急。问一句:光纤用户和 DSL 用户,别的地方一样吗?


用真实数据看

数据:7,043 名电信客户。

网络类型 人数 流失率 平均月费
光纤 3,096 41.89% 91.5 元
DSL 2,421 18.96% 58.1 元

相关不是因果:光纤用户流失率高一倍,是光纤的锅吗

差距 22.9 个百分点,看着很大。

但光纤用户月费也高 33.4 元。 而我们已经知道(见文章:辛普森与异质性:一个数字背后,可能藏着三个完全不同的结论):月费越高,流失越多。

所以”光纤”可能只是”贵”的一个替身——不是光纤让人流失,是光纤用户恰好更贵。


分层验证

按合同类型分层,每层内部再比光纤 vs DSL:

相关不是因果:光纤用户流失率高一倍,是光纤的锅吗

分层 光纤流失 DSL 流失 差距
Month-to-month 54.6% 32.2% +22.4pp
One year 19.3% 9.3% +10.0pp
Two year 7.2% 1.9% +5.3pp
合并 41.89% 18.96% +22.9pp

相关不是因果:光纤用户流失率高一倍,是光纤的锅吗

合并看差 22.9pp,但在两年合约层只有 5.3pp——缩了近 4 倍。


这说明什么

不是说”光纤和流失无关”——分层后每层里光纤仍然更高(5.3 ~ 22.4pp)。

而是说:合并的 22.9pp 里,有一部分其实是”月费高”和”合同短”造成的,不该算在光纤头上。

如果真要下因果结论,得做实验:随机给一批人换光纤,看流失是否变化。观察数据只能证明相关。


看到相关性,先找第三个变量

相关不是因果:光纤用户流失率高一倍,是光纤的锅吗

  1. 发现 A 和 B 相关(光纤 ↔ 流失)

  2. 找可能同时影响两者的 C(月费、合同类型、入网时间、地区)

  3. 按 C 分层重算,看 A-B 关系是否还在

  4. 还在 → 相关性更可信(但仍不等于因果)

  5. 大幅缩小甚至反转 → 原来那个数是假的

第 5 种情况就是辛普森悖论(00E 那篇讲过),它比想象中常见。


怎么表述才严谨

说法 能不能说
“光纤用户流失率更高” ✅ 描述事实
“光纤用户更可能流失” ✅ 仍是描述
“光纤导致流失” ❌ 没有实验证据
“停掉光纤能降低流失” ❌ 混淆了相关与因果

改成这样最稳:”光纤用户流失率高 22.9pp,但其中一部分来自他们更高的月费和更短的合同。要判断是否光纤本身的问题,需要做 A/B 实验。”


附:可运行代码

数据集:IBM / UCI Telco Customer Churn 电信客户流失(7,043 条,公开数据)

import pandas as pd

df['churn'] = (df['Churn'] == 'Yes').astype(int)

# 1. 合并看
fo = df[df['InternetService'] == 'Fiber optic']
ds = df[df['InternetService'] == 'DSL']
print('光纤 %.1f%%  DSL %.1f%%  差 %+.1fpp'
      % (100*fo['churn'].mean(), 100*ds['churn'].mean(),
         100*(fo['churn'].mean() - ds['churn'].mean())))

# 2. 混杂变量:月费
print('月费:光纤 %.1f 元  DSL %.1f 元'
      % (fo['MonthlyCharges'].mean(), ds['MonthlyCharges'].mean()))

# 3. 按合同分层
for c in ['Month-to-month', 'One year', 'Two year']:
    s = df[df['Contract'] == c]
    a = s[s['InternetService'] == 'Fiber optic']
    b = s[s['InternetService'] == 'DSL']
    print('%-16s 光纤 %.1f%%  DSL %.1f%%  差 %+.1fpp'
          % (c, 100*a['churn'].mean(), 100*b['churn'].mean(),
             100*(a['churn'].mean() - b['churn'].mean())))

跑一遍你会看到:合并 +22.9pp,分层后 +22.4 / +10.0 / +5.3pp。

如果你也在做类似的东西,欢迎交流,我这边有完整实现,可以发你参考。


正文完
 3
评论(没有评论)