一个财富分配游戏:
房间里有100个人,每人都有100元钱,他们在玩一个游戏。每轮游戏中,每个人都要拿出一元钱随机给另一个人,最后这100个人的财富分布是怎样的?
涉及的知识:
np.random.choice
Generates a random sample from a given 1-D array
numpy.random.choice(a, size=None, replace=True, p=None)
- a: 1-D ndarray,如果是int则生成np.arange(a),理解为从a中取样
- size:抽样尺寸, (m, n, k), then m * n * k samples are drawn,默认None返回一个值
- replace:个人理解为True放回采样,False不放回采样
- p:1-D array-like,对应a中每个元素的抽样概率
DataFrame iloc loc
- iloc:整数标签(从0行开始标签)
- loc:轴标签(类似于行名称)
帕累托图绘图步骤(游戏二可视化中有代码)
- sort_values对元素降序排列
- 利用sum计算单个元素在总数中的占比
- 利用cumsum计算概率的累积求和,找到80%的节点
- 用bar画柱状图后,绘制累积的概率图。
- 利用节点进行进行辅助线的标注
游戏一:没有借贷的社会
模型假设:
- 每个人初始基金100元
- 从18岁到65岁,每天玩一次,简化运算按照一共玩17000轮
- 每天拿出一元钱,并且随机分配给另一个人
- 当某人的财富值降到0元时,他在该轮无需拿出1元钱给别人,但仍然有机会得到别人给出的钱
导入需要的包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
import time
import warnings
warnings.filterwarnings('ignore')
搭建模型
分为两种情况:
当所有人财富值都>0的时候,每个玩家都需要付1元钱。
当存在财富值=0的玩家,财富值不为0的玩家需要付1元钱。
因此在函数中用if进行筛选,分为两种情况构建函数。
def game1(data,roundi):
if len(data[data[roundi-1]==0])>0:
#当数据包含财富值为0的玩家,为0的玩家不需要出钱
round_i=pd.DataFrame({'pre_round':data[roundi-1],'lost':0})
con=round_i['pre_round']>0#筛选>0的玩家bool值
round_i['lost'][con]=1#符合条件的lost=1
roundi_players_i=round_i[con]#筛选总玩家数用于choice函数
#利用choice函数,随机选择收到1元钱的玩家
choice_i=pd.Series(np.random.choice(person_n,len(roundi_players_i)))
#利用value_counts计算每个玩家收到的钱总数
gain_i=pd.DataFrame({'gain':choice_i.value_counts()})
#按照index连接两个表,并填充缺失值(nan无法参与return中的计算)
round_i=round_i.join(gain_i)
round_i.fillna(0,inplace=True)
#玩家本轮结束的财富=上一轮-付钱(只有财富值>0的lost=1)+得到(通过value_counts计算)
return round_i['pre_round']-round_i['lost']+round_i['gain']
else:
#所有玩家都出1元钱
round_i=pd.DataFrame({'pre_round':data[roundi-1],'lost':1})
choice_i=pd.Series(np.random.choice(person_n,100))
gain_i=pd.DataFrame({'gain':choice_i.value_counts()})
round_i=round_i.join(gain_i)
round_i.fillna(0,inplace=True)
return round_i['pre_round']-round_i['lost']+round_i['gain']
运行模型
#构建最初财富值,均为100
person_n=[x for x in range(1,101)]
fortune=pd.DataFrame([100 for i in range(100)],index=person_n)
fortune.index.name='id'
#在进行大数量的训练前 先10次 100次进行模型的测试,无误后再进行
starttime=time.time()
for round in range(1,17001):
fortune[round]=game1(fortune,round)
#fortune 行是游戏者编号 列是游戏数据
#转置后 每一次游戏数据为一行
game1_result=fortune.T
endtime=time.time()
print('总用时%.3f 秒'%(endtime-starttime))
结果可视化
- 前100轮,每10轮绘制一个图
- 100-1000轮,每100轮绘制一个图
- 1000-17000轮,每400轮绘制一个图
在不对游戏者财富排序的情况下使用plt.bar生成柱状图,其中x轴是Player ID,y轴是当前局的财富值。
注意range是左闭右开取数,画最后一张图需要注意。
os.chdir('D:\\fortunegame\\result_game1\\')
def graph1(data,start,end,strip):
for n in list(range(start,end,strip)):
#用行位置筛选得到第n次游戏 每个人的财富
datai=data.iloc[n]
plt.figure(figsize=(10,6))
#x为游戏者编号 y为财富值
plt.bar(datai.index,datai.values,color='gray',alpha=0.9)
#game1_result.iloc[17000].max() 332
#根据范围调整x y范围
plt.ylim([0,400])
plt.xlim([-10,110])#左右对称空白10个单位
plt.title('Round %d' %n)
plt.xlabel('Player ID')
plt.ylabel('fortune')
plt.grid(color='gray',linestyle='--',linewidth=0.5)
plt.savefig('graph1_round_%d.png' %n,dpi=200)
print('成功绘制%d轮结果柱状图' %n)
graph1(game1_result,0,100,10)
graph1(game1_result,100,1000,100)
graph1(game1_result,1000,17001,400)#range左闭右开 注意一下17000刚好在右边
第17000轮的结果如下,因为按照编号排列无法判断分布,因此在绘图时按照财富值降序绘制柱状图。

绘制fortune排序后的柱状图:
os.chdir('D:\\fortunegame\\result_game1_sort\\')
def graph2(data,start,end,strip):
for n in list(range(start,end,strip)):
#用行位置筛选得到第n次游戏 每个人的财富
#提取数据后 进行sort_values从低到高排序 并reset_index()对排序后的数据增加索引
#处理后尺寸(100,2),末尾[n]是选取列名为n的列 也就是排序前的财富值列
datai=data.iloc[n].sort_values().reset_index()[n]
plt.figure(figsize=(10,6))
#x为打乱的游戏者编号(不是原编号) y为财富值
plt.bar(datai.index,datai.values,color='gray',alpha=0.9)
#根据范围调整x y范围
plt.ylim([0,400])
plt.xlim([-10,110])
plt.title('Round %d' %n)
plt.xlabel('Player')
plt.ylabel('fortune')
plt.grid(color='gray',linestyle='--',linewidth=0.5)
plt.savefig('graph1_round_%d.png' %n,dpi=200)
print('成功绘制%d轮结果柱状图' %n)
graph2(game1_result,0,100,10)
graph2(game1_result,100,1000,100)
graph2(game1_result,1000,17001,400)
绘制过程图代码如下:(这个代码写的太拖沓了)
fig,axes = plt.subplots(4,4,figsize=(16,10),sharex=True,sharey=True)
ipic=[0,50,100,500,1000,1500,2000,2500,3000,3500,4000,4500,5000,9000,13000,17000]
k=0
for i in range(4):
for j in range(4):
datai=game1_result.iloc[ipic[k]].sort_values().reset_index()[ipic[k]]
datai.plot(kind='bar',color='gray',ax=axes[i,j])
plt.xticks([])
plt.yticks([])
plt.ylim([0,350])
plt.xlim([-10,110])
axes[i,j].set_title('Round %d' %ipic[k],fontsize=10)
plt.subplots_adjust(wspace=0.1,hspace=0.2)
k=k+1

第17000轮的结果如下:

由上图可视,随着游戏次数的增加,贫富差距逐渐被拉大,不同玩家的财富情况逐渐呈指数分布,少数人拥有多数财富。
帕累托法则表明,财富在人口的分配是不平衡的,20%的人占有80%的社会财富,对17000轮的结果绘制帕累托图,由分析可以得知47名玩家掌控80%的财富。(真的好羡慕被运气送到财富顶端的幸运儿)
#帕累托图
data=pd.DataFrame({'fortune':game1_result.iloc[17000]})
#data=game1_result.iloc[17000]
data.sort_values(by='fortune',ascending=False,inplace=True)
data.reset_index(inplace=True)
data.drop('id',axis=1,inplace=True)
data['p']=data['fortune'].cumsum()/data['fortune'].sum()#计算累计占比
key=data[data['p']>0.8].index[0]#找到超过80%的第一个index
key_list=data.index.tolist()#字符串输出
print(' %d%% 的玩家拥有超过80%%的财富:' %key)
#print('超过80%累计占比的索引位置',key_num)
plt.figure(figsize=(10,6))
fig=data['fortune'].plot(kind='bar',color='gray',alpha=0.8)
plt.ylim([0,400])
plt.xlim([-5,105])
plt.xticks([1,10,20,30,40,50,60,70,80,90,100])
fig.set_xticklabels([1,10,20,30,40,50,60,70,80,90,100])
plt.title('round17000 帕累托图')
plt.xlabel('Player')
plt.ylabel('fortune')
data['p'].plot(style='--',color='g',secondary_y=True)
plt.axvline(key,color='r',linestyle='--')
plt.text(key+0.2,data['p'][key]-0.05,'累计占比为%.3f%%' % (data['p'][key]*100))
plt.ylabel('财富_比例')#添加到较近的图旁边
plt.show()

游戏二:允许借贷的社会
允许借贷的游戏,意味着财富值为0时同样需要拿出钱,即使财富值是负值,也可以继续参与游戏。
- 从18岁到65岁,每天玩一次,简化为一共进行17000轮游戏
- 每天每个人拿出一元钱,并且随机分配给另一个人
- 即使财富值为负数,也需要拿出1元钱,随机分配给一个玩家
构建模型
和游戏一的模型区别在于,不需要考虑是否财富值为0,所有人的lost均为1,100个人均出钱所以choice函数中随机选择100个人分配。
def game2(data,roundi):
round_i=pd.DataFrame({'pre_round':data[roundi-1],'lost':1})
choice_i=pd.Series(np.random.choice(person_n,100))
gain_i=pd.DataFrame({'gain':choice_i.value_counts()})
round_i=round_i.join(gain_i)
round_i.fillna(0,inplace=True)
return round_i['pre_round']-round_i['lost']+round_i['gain']
person_n=[x for x in range(1,101)]
fortune=pd.DataFrame([100 for i in range(100)],index=person_n)
fortune.index.name='id'
#在进行训练前 先10次 100次进行模型的测试
starttime=time.time()
for round in range(1,17001):
fortune[round]=game2(fortune,round)
print('已完成%i轮'%round)
#fortune 行是游戏者编号 列是游戏数据
#转置后 每一次游戏数据为一行
game2_result=fortune.T
endtime=time.time()
print('总用时%.3f 秒'%(endtime-starttime))
可视化分析
财富值随时间变换图:

财富值分布
data=pd.DataFrame({'fortune':game2_result.iloc[17000]})
#data=game1_result.iloc[17000]
data.sort_values(by='fortune',ascending=False,inplace=True)
data.reset_index(inplace=True)
data.drop('id',axis=1,inplace=True)
data['p']=data['fortune'].cumsum()/data['fortune'].sum()#计算累计占比
data['fortune'].describe()

由describe可以看出,在游戏2中,最大财富值为374元,最小财富值为-343,贫富差距就是被运气拉开的。
读取data数据可以看出,游戏2中有49人财富值超过了100元(初始财富值),有12人财富值低于0元。
财富值标准差随游戏轮数变化:
game2_st=game2_result.std(axis=1)
game2_st.plot(figsize=(12,5),color='red')
plt.title('财富值标准差变化图')
plt.xlabel(u'游戏次数')
plt.ylabel(u'标准差')
plt.xlim([-500,17500])
plt.ylim([0,150])

可以观察到随实验次数增多,标准差逐渐变大。可以发现前6000次标准差增幅较大,6000次游戏后,曲线接近于直线。
35岁破产能逆风翻盘吗?
18岁开始游戏,35岁时进行6205盘游戏,简化为6200局游戏进行分析。
筛选出6200轮资产为负的人,在17000轮时判断是否资产为正。
#排序6200轮结果,红色标识破产
game2_round6200=pd.DataFrame({'money':game2_result.iloc[6200].sort_values().reset_index()[6200],
'id':game2_result.iloc[6200].sort_values().reset_index()['id'],
'color':'gray'})
game2_round6200['color'][game2_round6200['money']<0]='red'
#筛选出来破产的人的编号,存为列表格式
id_red=game2_round6200['id'][game2_round6200['money']<0].tolist()
#绘图
plt.figure(figsize=(10,6))
plt.bar(game2_round6200.index,game2_round6200['money'],color=game2_round6200['color'],alpha=0.9)
plt.ylim([-250,450])
plt.xlim([-10,110])#左右对称空白10个单位
plt.title('Round 6200 35岁破产')
plt.xlabel('Player')
plt.ylabel('fortune')
plt.grid(color='gray',linestyle='--',linewidth=0.5)

datai=pd.DataFrame({'money':game2_result.iloc[17000],'color':'gray'})
#id_red是6200轮破产的名单list,不会改变,loc读取行索引
datai['color'].loc[id_red]='red'#loc iloc都要[]!注意!
datai=datai.sort_values(by='money').reset_index()#进行排序
plt.figure(figsize=(10,6))
plt.bar(datai.index,datai['money'],color=datai['color'],alpha=0.9)
#game2_result.iloc[17000].max() 414
#game2_result.iloc[17000].min() -222
#根据范围调整x y范围
plt.ylim([-350,450])
plt.xlim([-10,110])#左右对称空白10个单位
plt.title('Round 17000 红色标注破产者')
plt.xlabel('Player')
plt.ylabel('fortune')
plt.grid(color='gray',linestyle='--',linewidth=0.5)

由上图可以看出,在35岁破产的13人中,只有6个人成功跳出破产的境界,但遗憾的是他们的金钱都没有到达初始资产100。再一次感受到命运之手的神奇力量,跌倒的人真的难再爬起来。
游戏三:努力增加1%的成功概率
仍然是17000轮游戏,但有10%的玩家(定义其游戏编号为1,11,21,...91),通过他们的努力增加了1%的获胜概率。
之前游戏一、二中每个人choice中的概率都为1%,现在努力的玩家被选中的概率为1.1%。
因为概率总和为1,其余90名玩家被选中的概率为(1-0.011)/90。
构建一个list,其中包含每个玩家的编号和其对应的概率,用于choice函数。
构建模型
def game3(data,roundi):
round_i=pd.DataFrame({'pre':data[roundi-1],'cost':1})
choice=pd.Series(np.random.choice(person_n,100,p=person_p))
gain=pd.DataFrame({'gain':choice.value_counts()})
round_i=round_i.join(gain)
round_i.fillna(0,inplace=True)
return round_i['pre']-round_i['cost']+round_i['gain']
person_p=[0.899/90 for i in range(0,100,1)]#list 100个元素
for i in range(1,101,10):
person_p[i-1]=0.0101
person_n=[x for x in range(1,101)]
fortune=pd.DataFrame([100 for i in range(100)],index=person_n)
fortune.index.name='id'
starttime=time.time()
for i in range(1,17001):
fortune[i]=game3(fortune,i)
print('round %d' %i)
endtime=time.time()
result=fortune.T
print('use time %.2f' %(-starttime+endtime))
可视化结果


利用上帝之手在模拟中对努力的人多赋予1%的获胜概率,可以看到最后17000轮时,标红的数据都没有出现负债情况,比较集中在前20名,但有一个略微倒霉的努力者资产反而缩水了。在跑另一次实验时,第17000轮时有一个努力者居然资产为负了,努力会带来更高的成功概率,但也有努力却不幸运的人。
总结
for循环运算偏慢,https://blog.csdn.net/hao1994121/article/details/81301477这篇文章用字典进行了处理,运算速度会快很多。但是我的菜鸟水平用字典会有点懵逼,希望继续努力~


被折叠的 条评论
为什么被折叠?



