运用Python模拟检查悖论

检查悖论

检查悖论,又称检验悖论,描述是:当观察量的概率与观察量有关时,则会出现检查悖论

举个例子,不知道大家会不会感觉自己朋友拥有比自己更多的朋友,这并不是错觉,你的朋友的确有超过一半的概率有比你更多的朋友,一句话解释就是:因为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进行模拟,所以对检查悖论没有提很多。如果想了解更多关于检查悖论的知识,可以去看一下他的视频

【毕导】看了这个视频,你会释怀你倒霉的一生

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值