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