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這篇文章用字典進行了處理,運算速度會快很多。但是我的菜鳥水平用字典會有點懵逼,希望繼續努力~

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章