数学重学 - 07 数据素养

这是 数学重学路线图 阶段一的子页面

数据素养:别被数据骗了

在大数据时代,每天有无数图表、报告、新闻标题试图用数据说服你

但数据可以被操纵——不是篡改数字,而是用呈现方式误导你的判断

这一章教你识破数据骗术,做一个有数据素养的人

一、图表骗术识别

骗术1:Y轴不从0开始

这是最常见的图表欺骗手法

对比一下:

诚实的图(Y轴从0开始)

1
2
3
4
5
6
7
8
9
10
11
销售额(万)
100| ██
80| ██ ██
60| ██ ██ ██
40| ██ ██ ██ ██
20|██ ██ ██ ██ ██
0|██ ██ ██ ██ ██
+----+----+----+----+----
1月 2月 3月 4月 5月

看起来:稳步增长,正常趋势

骗人的图(Y轴从80开始)

1
2
3
4
5
6
7
8
9
10
11
销售额(万)
100| ██████
95| ██████
90| ██████
85| ██████
80|██████
+----+----+----+----+----
1月 2月 3月 4月 5月

看起来:暴涨!业绩翻了好几倍!
实际上:从80涨到100,只涨了25%

什么时候Y轴可以不从0开始?

当数据变化范围很小但变化本身很重要时(如体温36.5→38.5)

但必须明确标注,不能故意误导

骗术2:截断柱状图

把柱状图中间截断(用波浪线),让差异看起来巨大

A产品销量 1000,B产品销量 1050

完整柱状图:几乎一样高

截断后只显示 990-1060 的范围:B看起来是A的好几倍

骗术3:3D饼图视觉欺骗

3D饼图因为透视效果,前面的扇区看起来比实际大

永远不要用3D饼图——这是数据可视化的基本原则

用普通饼图或者更好的——条形图

1
2
3
4
5
6
3D饼图的问题:

正面看:A=30%, B=30%, C=40%
3D旋转后:放在前面的C看起来占了一半以上

结论:展示占比时,用水平条形图最清晰

骗术4:双Y轴误导

左Y轴是”收入(万)”0-100,右Y轴是”满意度(%)”90-100

两条线看起来趋势一致→暗示”收入增长带来满意度提升”

实际上:调整右Y轴的范围,可以让任何两条线看起来相关

双Y轴图本身不是错,但很容易被滥用来暗示因果

骗术5:面积/体积误导

用图标大小表示数据,但人眼对面积的感知是非线性的

A=100,B=200,B是A的2倍

如果用圆的直径表示:B的圆直径是A的2倍→面积是4倍→视觉感觉B是A的4倍

正确做法:用面积(而非直径)来映射数据

Python 代码:好图 vs 骗图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
matplotlib.rcParams['axes.unicode_minus'] = False

# 数据:某公司季度收入
quarters = ['Q1', 'Q2', 'Q3', 'Q4']
revenue = [980, 1010, 1030, 1055]

fig, axes = plt.subplots(1, 2, figsize=(12, 5))

# === 骗人的图 ===
axes[0].bar(quarters, revenue, color='#e74c3c', width=0.5)
axes[0].set_ylim(960, 1070) # Y轴不从0开始!
axes[0].set_title('骗人的图:看起来暴涨', fontsize=14, fontweight='bold')
axes[0].set_ylabel('收入(万)')
for i, v in enumerate(revenue):
axes[0].text(i, v + 2, str(v), ha='center', fontweight='bold')

# === 诚实的图 ===
axes[1].bar(quarters, revenue, color='#2ecc71', width=0.5)
axes[1].set_ylim(0, 1200) # Y轴从0开始
axes[1].set_title('诚实的图:其实很平稳', fontsize=14, fontweight='bold')
axes[1].set_ylabel('收入(万)')
for i, v in enumerate(revenue):
axes[1].text(i, v + 20, str(v), ha='center', fontweight='bold')

plt.tight_layout()
plt.savefig('honest_vs_deceptive_chart.png', dpi=150)
plt.show()
print("同样的数据,不同的Y轴范围,完全不同的观感")

二、相关不等于因果

经典案例

冰淇淋销量和溺水人数正相关

冰淇淋卖得越多,溺水的人越多

难道冰淇淋导致溺水?当然不是

共同因子:天气热→人们买冰淇淋 + 人们去游泳→溺水增多

尼古拉斯·凯奇电影数量和泳池溺亡数正相关

相关系数 r = 0.87(非常强的正相关)

纯粹的巧合——这叫伪相关(spurious correlation)

鹳鸟数量和新生儿数量正相关(欧洲古老的迷信)

共同因子:农村地区鹳鸟多,农村生育率也高

如何判断因果?

相关性只是因果性的必要条件,不是充分条件

判断因果需要:

时间顺序:原因在前,结果在后

排除混杂变量:有没有第三个因素同时影响两者

合理机制:在理论上说得通

随机对照实验:金标准——随机分组,控制变量

口诀:**”相关只是线索,因果需要证据”**

工作场景

“部署了新版本后错误率下降了” → 是新版本的功劳吗?

可能同时流量也下降了(周末效应)

可能运维同时做了其他调整

需要A/B测试或者回滚验证

“用了新安全规则后攻击减少了” → 规则有效吗?

可能攻击者换了目标

可能是季节性波动

需要对照组

三、辛普森悖论

什么是辛普森悖论?

分组看数据,A比B好;合并看数据,B比A好

听起来不可能?看这个例子:

经典例子:两家医院的手术成功率

轻症手术

A医院:90/100 = 90%

B医院:800/1000 = 80%

A医院更好

重症手术

A医院:300/900 = 33.3%

B医院:50/100 = 50%

不对,B医院更好

合并数据

A医院:(90+300)/(100+900) = 390/1000 = 39%

B医院:(800+50)/(1000+100) = 850/1100 = 77.3%

B医院远远好于A

分组看A在轻症上赢了,但合并后B大幅领先

为什么会这样?

样本量不均匀

A医院接了900例重症、100例轻症(重症多,拉低了总成功率)

B医院接了100例重症、1000例轻症(轻症多,拉高了总成功率)

A医院可能是更好的医院(专门接疑难杂症),但合并数据反而不好看

怎么防止被辛普森悖论骗?

看分组数据,不要只看合并数据

关注样本量分布,子组大小是否均匀

识别隐藏变量(这里是病情严重程度)

Python 代码:辛普森悖论模拟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import pandas as pd

# 构造辛普森悖论数据
data = {
'医院': ['A', 'A', 'B', 'B'],
'病情': ['轻症', '重症', '轻症', '重症'],
'成功': [90, 300, 800, 50],
'总数': [100, 900, 1000, 100],
}
df = pd.DataFrame(data)
df['成功率'] = df['成功'] / df['总数'] * 100

print("=== 分组数据 ===")
for _, row in df.iterrows():
print(f"{row['医院']}医院 {row['病情']}: "
f"{row['成功']}/{row['总数']} = {row['成功率']:.1f}%")

print("\n=== 合并数据 ===")
for hospital in ['A', 'B']:
subset = df[df['医院'] == hospital]
total_success = subset['成功'].sum()
total_cases = subset['总数'].sum()
rate = total_success / total_cases * 100
print(f"{hospital}医院总体: {total_success}/{total_cases} = {rate:.1f}%")

print("\n=== 悖论解释 ===")
print("A医院分组都不差,但因为接了大量重症(900例),")
print("合并后被重症的低成功率拉低了整体数据")
print("B医院虽然重症成功率只有50%,但重症只有100例,")
print("1000例轻症的80%成功率撑起了整体数据")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 可视化辛普森悖论
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
matplotlib.rcParams['axes.unicode_minus'] = False

fig, axes = plt.subplots(1, 3, figsize=(15, 5))

# 图1:轻症对比
axes[0].bar(['A医院', 'B医院'], [90, 80], color=['#3498db', '#e74c3c'])
axes[0].set_title('轻症成功率 (%)')
axes[0].set_ylim(0, 100)
axes[0].text(0, 92, '90%', ha='center', fontsize=14, fontweight='bold')
axes[0].text(1, 82, '80%', ha='center', fontsize=14, fontweight='bold')
axes[0].annotate('A赢', xy=(0.5, 85), fontsize=12, ha='center', color='blue')

# 图2:重症对比
axes[1].bar(['A医院', 'B医院'], [33.3, 50], color=['#3498db', '#e74c3c'])
axes[1].set_title('重症成功率 (%)')
axes[1].set_ylim(0, 100)
axes[1].text(0, 35.3, '33%', ha='center', fontsize=14, fontweight='bold')
axes[1].text(1, 52, '50%', ha='center', fontsize=14, fontweight='bold')
axes[1].annotate('B赢', xy=(0.5, 45), fontsize=12, ha='center', color='red')

# 图3:合并对比
axes[2].bar(['A医院', 'B医院'], [39, 77.3], color=['#3498db', '#e74c3c'])
axes[2].set_title('合并成功率 (%) — 悖论!')
axes[2].set_ylim(0, 100)
axes[2].text(0, 41, '39%', ha='center', fontsize=14, fontweight='bold')
axes[2].text(1, 79.3, '77%', ha='center', fontsize=14, fontweight='bold')
axes[2].annotate('B大赢?', xy=(0.5, 60), fontsize=12, ha='center', color='red')

plt.suptitle("辛普森悖论:分组A赢,合并B赢", fontsize=16, fontweight='bold')
plt.tight_layout()
plt.savefig('simpson_paradox.png', dpi=150)
plt.show()

四、幸存者偏差

直觉理解

你只看到了”活下来的”,没看到”死掉的”

于是得出错误的结论

经典故事:二战飞机装甲

二战时,盟军统计返航飞机的弹孔分布

机翼和机身中弹最多 → 直觉:加强机翼和机身的装甲

统计学家亚伯拉罕·沃尔德说:应该加强驾驶舱和引擎的装甲

为什么?

返航的飞机机翼中弹多→说明机翼中弹还能飞回来

驾驶舱和引擎中弹的飞机→没回来(坠毁了)

你看到的数据有偏——只包含”幸存者”

生活中的幸存者偏差

创业成功学

“大学辍学也能成功!看看比尔·盖茨、扎克伯格!”

你没看到的:无数辍学后找不到工作的人

幸存者偏差让你高估了辍学创业的成功率

老房子比新房子质量好?

“以前的房子住了50年还好好的”

质量差的老房子早就塌了或拆了,你看不到了

留下来的都是好的→产生”以前质量好”的错觉

读书无用论

“我认识的有钱人很多没读过大学”

统计数据:学历和收入正相关

你的社交圈不是随机样本

安全领域的幸存者偏差

只分析被捕获的攻击

WAF拦截了1000次SQL注入→”我们的防御很好”

问题:绕过WAF的攻击你根本不知道

被捕获的是”幸存样本”,真正危险的是没被捕获的

只分析已知漏洞

CVE库里的漏洞 = 被发现的

0day漏洞 = 没被发现的 = 更危险的

安全防御不能只基于已知威胁

渗透测试报告

“渗透测试没发现高危漏洞” ≠ “系统没有高危漏洞”

只是在有限时间和方法内没找到而已

五、其他统计陷阱

选择性报告(Cherry Picking)

只展示有利的数据,隐藏不利的

“我们的产品用户满意度95%”

样本是怎么选的?主动填写问卷的用户本身就偏满意

不满意的用户早就卸载了

防范:问”原始数据呢?””样本怎么选的?””有没有被排除的数据?”

基数谬误

“我们的系统可用性从99.9%提升到99.99%”

听起来只提升了0.09个百分点?

换个角度:

99.9%:全年停机 8.76小时

99.99%:全年停机 52.6分钟

停机时间减少了约90%

越接近100%,每提升一点点都越难

99% → 99.9%:故障时间减少90%(3天→8.7小时)

99.9% → 99.99%:故障时间再减少90%(8.7小时→52分钟)

99.99% → 99.999%:故障时间再减少90%(52分钟→5.2分钟)

这就是”几个9”的工程代价——每多一个9,成本可能翻10倍

均值回归

现象:极端表现之后,通常会向平均值靠拢

例子:

运动员打破纪录后,下一场通常表现”退步”——不是退步,是回归正常

学生考试考得特别差→家长骂→下次考好了→”骂有用”→其实是均值回归

某个月服务器故障率飙升→加班排查→下个月恢复正常→”排查有效”→可能本来就会恢复

防范:对极端值保持冷静,多看几个周期的趋势

伯克森悖论

在特定人群中观察到的相关性,在总体中不存在

例:”为什么帅的人都渣?”

你的约会对象 = 帅 OR 性格好(至少有一项才会吸引你)

帅+性格好:你会遇到

帅+性格差:你也会遇到(因为帅)

不帅+性格好:你也会遇到(因为性格好)

不帅+性格差:你不会遇到

在你的样本中,帅和性格好呈负相关——但总体中可能没有这种相关

德州神枪手谬误

先打枪再画靶——围绕结果编故事

“我们分析了100个指标,发现X和Y高度相关!”

100个指标里,纯靠随机就可能出现几对”相关”

这叫多重比较问题

防范:预先设定假设,用 Bonferroni 校正等方法调整显著性阈值

六、如何正确看数据

检查清单

看到任何数据/图表/报告时,问自己这些问题:

1. 样本量多大?

10个人的调查 vs 10000个人的调查,可信度天差地别

小样本容易出现极端值

2. 数据来源是什么?

谁收集的?有没有利益冲突?

烟草公司资助的”吸烟无害”研究,你信吗?

3. 有没有对照组?

“吃了这个药的人80%好了” → 不吃药的人呢?也许自愈率就是80%

没有对照组的实验基本没有说服力

4. 置信区间是多少?

“A方案转化率5.2%,B方案5.5%”→ B更好?

如果置信区间重叠,差异可能没有统计显著性

看到百分比,一定要问”误差范围是多少”

5. 图表有没有动手脚?

Y轴从哪里开始?

比例尺是否均匀?

是不是3D图?

6. 是相关还是因果?

两个事情一起变化 ≠ 一个导致另一个

有没有被遗漏的第三变量?

数据素养口诀

来源可靠吗?谁说的,有什么动机

样本够大吗?小样本 = 高噪声

有对照组吗?没有对照 = 没有说服力

是因果还是相关?相关只是线索

图表诚实吗?Y轴、比例、维度都检查

有没有幸存者偏差?你看到的是全部吗

七、工作中的数据素养

安全领域

“本月0天被入侵” → 真的安全还是没检测到?

“漏洞扫描发现0个高危” → 扫描器覆盖率如何?

安全的数据往往有观察者效应:你只能衡量你能检测到的

大数据/后端

A/B测试时:

样本量够不够?跑了多久?

P值是多少?(P < 0.05 通常被认为显著)

是不是看了太多指标导致多重比较问题?

性能监控:

P99延迟比平均延迟重要得多

平均值可能掩盖长尾问题

例:平均延迟50ms,但P99是2000ms——有1%的用户等了2秒

产品分析

“DAU增长了20%” → 是真增长还是刷量/作弊?

“留存率提升5%” → 定义变了吗?口径一致吗?

“用户评分4.8” → 刷好评?强制弹窗引导好评?

八、Python 综合实践

模拟幸存者偏差

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import random

def simulate_survivor_bias(n_startups=10000):
"""模拟创业幸存者偏差"""
random.seed(42)

startups = []
for i in range(n_startups):
talent = random.gauss(50, 15) # 能力值,正态分布
luck = random.gauss(50, 15) # 运气值,正态分布
# 成功 = 能力 × 0.4 + 运气 × 0.6(运气成分更大)
success_score = talent * 0.4 + luck * 0.6
startups.append({
'talent': talent,
'luck': luck,
'success': success_score,
'survived': success_score > 70 # 前~15%存活
})

survived = [s for s in startups if s['survived']]
failed = [s for s in startups if not s['survived']]

avg_talent_survived = sum(s['talent'] for s in survived) / len(survived)
avg_luck_survived = sum(s['luck'] for s in survived) / len(survived)
avg_talent_failed = sum(s['talent'] for s in failed) / len(failed)
avg_luck_failed = sum(s['luck'] for s in failed) / len(failed)

print(f"总共 {n_startups} 家创业公司,存活 {len(survived)} 家")
print(f"\n存活公司: 平均能力={avg_talent_survived:.1f}, 平均运气={avg_luck_survived:.1f}")
print(f"失败公司: 平均能力={avg_talent_failed:.1f}, 平均运气={avg_luck_failed:.1f}")
print(f"\n如果只采访成功者,你会以为:")
print(f" '成功者能力平均{avg_talent_survived:.0f}分,远超常人'")
print(f" 但你看不到运气的巨大作用(平均运气{avg_luck_survived:.0f}分)")
print(f" 也看不到能力不差但运气不好的失败者(能力{avg_talent_failed:.0f}分)")

simulate_survivor_bias()

演示双Y轴骗术

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import matplotlib.pyplot as plt
import numpy as np
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
matplotlib.rcParams['axes.unicode_minus'] = False

months = ['1月','2月','3月','4月','5月','6月',
'7月','8月','9月','10月','11月','12月']

# 完全不相关的两组数据
np.random.seed(42)
ice_cream = [20, 25, 35, 50, 70, 85, 95, 90, 65, 40, 25, 20]
drowning = [3, 4, 5, 8, 12, 15, 18, 16, 10, 6, 3, 2]

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 图1:骗人的双Y轴图(暗示因果)
ax1 = axes[0]
ax2 = ax1.twinx()
l1 = ax1.plot(months, ice_cream, 'o-', color='#e74c3c', label='冰淇淋销量', linewidth=2)
l2 = ax2.plot(months, drowning, 's-', color='#3498db', label='溺水人数', linewidth=2)
ax1.set_ylabel('冰淇淋销量(万)', color='#e74c3c')
ax2.set_ylabel('溺水人数', color='#3498db')
ax1.set_title('误导图:看起来冰淇淋导致溺水!', fontweight='bold')
lines = l1 + l2
labels = [l.get_label() for l in lines]
ax1.legend(lines, labels, loc='upper left')
ax1.tick_params(axis='x', rotation=45)

# 图2:正确的解释
temperature = [2, 5, 12, 18, 25, 30, 33, 32, 26, 18, 10, 3]
axes[1].scatter(temperature, ice_cream, color='#e74c3c', s=80, label='冰淇淋销量')
axes[1].scatter(temperature, [d*5 for d in drowning], color='#3498db', s=80, label='溺水人数(×5)')
axes[1].set_xlabel('气温 (°C)')
axes[1].set_ylabel('数值')
axes[1].set_title('真相:气温才是共同因子', fontweight='bold')
axes[1].legend()
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('correlation_not_causation.png', dpi=150)
plt.show()

练习题

题1:图表识别

某公司财报显示”收入增长”图表,Y轴从950万开始,最高1050万

实际收入从980万增长到1020万

问:实际增长率是多少?图表视觉上让你感觉增长了多少?

实际增长率 = (1020-980)/980 × 100% ≈ 4.1%

视觉上:柱状图从底部30%位置涨到70%位置,感觉翻了一倍多

教训:永远看Y轴起点和数字,不要只看柱子高度

题2:辛普森悖论

两个广告方案在两个渠道的转化率:

渠道A(高质量流量):方案1: 8/100=8%, 方案2: 50/500=10%

渠道B(低质量流量):方案1: 50/900=5.6%, 方案2: 5/50=10%

合并后哪个方案更好?分开看呢?该选哪个?

合并:方案1: (8+50)/(100+900)=58/1000=5.8%, 方案2: (50+5)/(500+50)=55/550=10%

合并看方案2远好于方案1(10% vs 5.8%)

分开看:渠道A方案2更好(10%>8%), 渠道B方案2也更好(10%>5.6%)

这次没有悖论——方案2确实更好

但要注意方案1在渠道B投了900个样本,方案2只投了50个

渠道B的方案2样本太小,10%可能不可靠

题3:幸存者偏差

你分析了过去一年WAF日志,发现SQL注入攻击最多,XSS其次,请求走私最少

你据此决定重点加强SQL注入防护,减少请求走私的投入

这个决策有什么问题?

幸存者偏差:WAF能检测到的攻击才会出现在日志中

SQL注入多可能是因为WAF对它的检测规则最完善

请求走私少可能是因为WAF根本检测不到(绕过了)

正确做法:结合其他数据源(应用日志、安全审计、漏洞扫描)综合判断

安全领域的原则:你看到的威胁 ≠ 全部威胁

题4:基数谬误

某安全产品宣称”检测率99.99%”

假设每天有100万次正常请求和100次真实攻击

按99.99%检测率和0.01%误报率计算

问:报警时真的是攻击的概率是多少?

真阳性 = 100 × 99.99% ≈ 100次

假阳性 = 1000000 × 0.01% = 100次

报警总数 = 100 + 100 = 200次

报警时真的是攻击的概率 = 100/200 = 50%

即使检测率99.99%、误报率0.01%,一半的报警还是误报

因为正常请求的基数远大于攻击请求——这就是基数谬误

题5:相关与因果

数据显示:某公司加班越多的团队,项目上线越快

管理层结论:”应该鼓励加班来加速交付”

列出至少3个可能的混杂因素说明这个因果推断的问题

  1. 反向因果:项目deadline紧→被迫加班,是紧迫感导致快而非加班导致快

  2. 团队能力:优秀团队既有动力加班也有能力快速交付(共同因子:团队积极性)

  3. 项目规模:小项目容易完成也不需要太多加班,但相对上线快

  4. 选择偏差:上线快的项目被记录了,加班多但延期的项目可能被排除了

  5. 沉没成本:越加班的团队越不想延期(心理因素而非效率提升)

正确做法:对照实验,或者用回归分析控制混杂变量

本章小结

图表骗术:Y轴截断、3D饼图、双Y轴、面积误导——看数字不看形状

相关≠因果:有共同因子、反向因果、纯巧合三种可能

辛普森悖论:分组结论和合并结论相反,看样本量分布

幸存者偏差:你看到的不是全部,特别是安全领域

基数谬误:当真实事件很稀有时,误报会淹没真阳性

核心认知:带着怀疑看数据,问来源、问样本、问对照、问因果

上一章 → 06-生活中的数学

下一章 → 数学重学/08-方程与代数思维


上一章 目录 下一章
06-生活中的数学 数学重学路线图 08-进制与位运算