检查悖论
检查悖论,又称检验悖论,描述是:当观察量的概率与观察量有关时,则会出现检查悖论
举个例子,不知道大家会不会感觉自己朋友拥有比自己更多的朋友,这并不是错觉,你的朋友的确有超过一半的概率有比你更多的朋友,一句话解释就是:因为TA拥有的朋友多,所以你是TA的朋友的概率就会比较大。类似的,抖音上你关注的人大概率粉丝比你多,是因为TA粉丝很多,所以你是TA粉丝的概率也就更大
上述的“自己朋友拥有比自己更多的朋友”这个现象,在1991年由社会学家斯科特·L·菲尔德提出,被称为友谊悖论(Friendship paradox),后面会尝试使用Python进行模拟
公交车悖论(Waiting paradox)
引入
公交车悖论是检查悖论的一种情况,大概是说:
有一路公交车,每小时发车6辆,按理来说我平均等个5分钟就能等到,但为什么我每次等车都这么久呢???
确实,假设公交车每10分钟发一辆车,那么我无论什么时候去等车,我的平均等候时间应该就是5分钟。但现实中,公交车不会平均的发车,它可能抽个风,在前5分钟里同时发4辆车,然后在剩下的55分钟内发2辆车,因为我会有更大的概率在这55分钟内去等车,那么我等车的时间就会远大于所计算的平均等候时间
不过这或许有些极端了,那么接下来就来用Python模拟一下稍微正常一点的情况吧
Python模拟
1.import
import random
from tqdm import tqdm
random模块用于实现随机功能
tqdm包用于实现进度条功能,可选
2.创建变量
bus_arrive_time = []
wait_time = []
person_arrive_time = random.randint(0, 60)
从上到下,分别是:
公交车到站时间(list)、人等车时间(list)、人到站时间(int)
注:模拟时忽略了时和秒,只保留分,方便表示和计算
3.随机得到公交车到达时间,并计算人的等车时间
for i in range(6):
bus_arrive_time.append(random.randint(0, 60))
for i in bus_arrive_time:
wait_time.append(i - person_arrive_time)
第一段代码:循环6次,得到6辆车的到达时间,并添加至列表
第二段代码:计算人等车的时间,并添加至列表
4.处理人等车时间列表,并计算最终的人等车时间
wait_time = [k for k in wait_time if k > 0]
if not wait_time:
wait_time = 60 - person_arrive_time + min(bus_arrive_time)
else:
wait_time = min(wait_time)
第一行代码:列表推导式,取等待时间中大于0的时间。因为在上一步的计算中,如果 i(公交车到达时间)比 person_arrive_time小,那么就会得到负数结果,因为后面要通过min取最小值,所以要去掉负数部分。去负数不可使用绝对值,只能直接砍掉负数部分
“if ... else:” 部分:先判断列表是否为空(当人等车时车已经全部过站,此时计算出来的人等车时间列表全是负值,经过第一行代码处理后会得到一个空列表)
为空则将等待时间设定为:60分钟 - 等待时间 + 第一辆公交车的到达时间
非空则直接取人等车时间列表的最小值
最后我们就得出了在某小时内,某人至少要等多久才能等到车
5.通过循环,进行多次实验,获得人等车时间的平均值
import random
from tqdm import tqdm
ROUNDS = 0
total_wait_time = 0
for i in tqdm(range(ROUNDS)):
bus_arrive_time = []
wait_time = []
person_arrive_time = random.randint(0, 60)
for i in range(6):
bus_arrive_time.append(random.randint(0, 60))
for i in bus_arrive_time:
wait_time.append(i - person_arrive_time)
wait_time = [k for k in wait_time if k > 0]
if not wait_time:
wait_time = 60 - person_arrive_time + min(bus_arrive_time)
else:
wait_time = min(wait_time)
total_wait_time += wait_time
print(total_wait_time/ROUNDS)
将上面各部分代码拼接起来,套一层迭代循环,循环次数为ROUNDS。再加一个变量 total_wait_time,用于存储等待时间的总和,并最终打印等待时间的平均数
模拟进行一千万次实验,用时1分钟,最终得到平均等待时间为 9 分钟
(一千万次已经够多了,没有必要再增加次数,加了也还是这个结果,这是频率的稳定性)
6.稍作修改
现实中司机会有一个发车时间表,可以理解为在10分钟内只能发1辆车,那么如果加上这个限制,平均等待时间还是这么长吗?
可以来试试
将这一行代码
bus_arrive_time.append(random.randint(0, 60))
改成这样
bus_arrive_time.append(random.randint(10*i, 10*(i+1)))
还是一千万次实验,用时1分6秒,最终得到平均等待时间为 6.5 分钟
这也是更贴近现实的情况
不过无论是哪种情况,最终等待时间都会超过理想情况中的5分钟
所以要想不等那么久,还得用一些科技[doge]
缺陷
这次模拟中,为了方便,没有考虑到秒,并且还在人等车时车全过站的情况中,直接以本轮的公交车到达时间为参照,把等待时间算出来。另外,random实现的随机是伪随机(当然计算机无法实现真随机)
这三点都会对结果造成一定的影响,但我个人感觉影响不大,大家可以尝试改进一下这个程序,看看最后的结果是否会有不同
友谊悖论(Friendship paradox)
引入
上面提到过了,这里就不重复了
Python模拟
法一:使用random强行模拟
思路
假设在一个有40人的班级里:
首先通过randint随机得到每个人的朋友数,再得到每个人的朋友都是谁
接着,因为友谊是双向的,即A的朋友是B,所以B肯定也有A这个朋友。通过这个原理,完善所有人的朋友列表
接下来抽取每一个人,将TA的朋友数和TA的每一个朋友的朋友数的平均数进行比较,最终计算出某人的朋友的朋友数比某人的朋友数更多的概率
代码
这一块比较复杂,我就通过在代码中写注释的方式帮助各位理解了
import random
import numpy as np # 用于实现平均数的计算,可以不用np,像上面公交车悖论一样直接算也行
# 初始化参数,即班级里有40人
num_people = 40
# 随机生成每个人的朋友数量(这是一个列表推导式,关于它的具体知识请自行了解,这里就不赘述了)
# num_people - 1 的原因是:一个人不能和自己成为朋友,所以朋友数量最多最多就是班级人数-1
friends_count = [random.randint(0, num_people - 1) for _ in range(num_people)]
# 初始化朋友列表
friends_list = [[] for _ in range(num_people)]
# 为每个人随机生成朋友列表
for person in range(num_people):
possible_friends = list(range(num_people)) # range()返回的是迭代器对象,所以要用list()将其变为列表
possible_friends.remove(person) # 不能自己成为自己的朋友
friends_list[person] = random.sample(possible_friends, friends_count[person]) # 从 possible_friends 列表中随机选择 friends_count[person] 个朋友,并将其分配给 person
# 确保友谊是双向的
for person in range(num_people):
for friend in friends_list[person]:
if person not in friends_list[friend]:
friends_list[friend].append(person)
# 计算每个人的朋友的平均朋友数量
avg_friends_of_friends = [] # 初始化一个空列表,用于存储每个人的朋友的平均朋友数量
for person in range(num_people):
friends = friends_list[person] # 获取当前人的朋友列表
if friends: # 如果列表非空(即有朋友)
friends_friends_counts = [len(friends_list[friend]) for friend in friends] # 获取当前人的朋友的朋友数,并添加进列表
avg_friends_of_friends.append(np.mean(friends_friends_counts)) # 取平均值,并存储到上面创建的列表中
else: # 若空(其实基本不会有这种情况)
avg_friends_of_friends.append(0) # 直接填写0
# 比较每个人的朋友数和他们的朋友的平均朋友数
more_friends_count = 0
for person in range(num_people):
if avg_friends_of_friends[person] > len(friends_list[person]):
more_friends_count += 1
# 计算朋友的朋友数比朋友数多的概率
probability = more_friends_count / num_people
# 输出结果
print(f'\n朋友的朋友数比朋友数多的概率: {probability:.3f}') # 保留小数点后三位
几轮测试下来,概率都在 0.55~0.6间
我总感觉逻辑上可能出现了一些错误,所以上述代码仅供参考
法二:使用Networkx模拟社交网络(ChatGPT-4o)
写完上面的方法后,我就去问了一下ChatGPT,它给出了另一种方法,反正我是看不懂,就先放这了,以后再来看吧
代码
import networkx as nx
import numpy as np
def create_random_graph(num_nodes, avg_degree):
p = avg_degree / (num_nodes - 1) # 边的概率
G = nx.erdos_renyi_graph(num_nodes, p)
return G
def average_friends_of_friends(G):
avg_fof = {}
for node in G.nodes:
friends = list(G.neighbors(node))
if friends:
fof_counts = [len(list(G.neighbors(friend))) for friend in friends]
avg_fof[node] = np.mean(fof_counts)
else:
avg_fof[node] = 0
return avg_fof
def run_experiment(num_nodes, avg_degree, num_experiments):
probabilities = []
for _ in range(num_experiments):
G = create_random_graph(num_nodes, avg_degree)
avg_fof = average_friends_of_friends(G)
more_friends_count = 0
for node in G.nodes:
num_friends = len(list(G.neighbors(node)))
if avg_fof[node] > num_friends:
more_friends_count += 1
probability = more_friends_count / num_nodes
probabilities.append(probability)
return np.mean(probabilities)
# 参数设置
num_nodes = 40 # 节点数量(班级人数)
avg_degree = 4 # 平均度
num_experiments = 1000 # 实验次数
# 运行实验
average_probability = run_experiment(num_nodes, avg_degree, num_experiments)
# 输出结果
print(f'朋友的平均朋友数量比自己多的概率: {average_probability:.2f}')
修改了几次参数,测试了几轮下来,概率也都在 0.55~0.6间
结尾
本文灵感来源于毕导的这个视频,由于本文仅讨论如何用Python进行模拟,所以对检查悖论没有提很多。如果想了解更多关于检查悖论的知识,可以去看一下他的视频
【毕导】看了这个视频,你会释怀你倒霉的一生