Python模拟社会财富分配——努力者和幸运儿谁更可能是富翁

 

一个财富分配游戏:

房间里有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这篇文章用字典进行了处理,运算速度会快很多。但是我的菜鸟水平用字典会有点懵逼,希望继续努力~

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值