只要你自己试试模拟随机现象几次,就会加强对概率的了解,比读很多页的数理统计和概率论的文章还有用。学习模拟,不仅是为了解模拟本身,也是为更了解概率而了解模拟。
1 伪随机数
生成(0,1)之间均匀分布的伪随机数的函数为 uniform()
di uniform()
di uniform()
di uniform()
每次都得到一个大于零小于 1 的随机数。
如果要生成一位数的随机数(即 0,1,2,3,4,5,6,7,8,9),可以取小数点后第一位数,通常用下面的命令
di int(10uniform())
两位随机数(0-99)则取小数点后两位小数,即
di int(100uniform())
任意均匀分布随机数(a,b)由下述函数得到
a+(b-a)uniform()
任意均匀分布整数随机数(a,b)由下述函数得到
a+int((b-a)uniform())
也可以同时生成多个随机数,然后将该随机数赋给某个变量。要注意的是,电脑中给出的随机数不是真正的随机数,而是伪随机数,因为它是按照一定的规律生成的。如果给定基于生成伪随机数的初始数值(即 set seed #),则对相同的初始数值,生成的伪随机数序列完
全一样。
clear
set obs 10
gen x1=uniform()
gen x2=uniform() //注意到 x1 与 x2 不一样
set seed 1234
gen y1=uniform()
set seed 1234
gen y2=uniform()
gen y3=uniform() //注意到 y1 与 y2 一样,但均与 y3 不同
set seed 5634
gen z1=uniform()
set seed 1234
gen z2=uniform() //注意到 z2 与 y1,y2 一样,但 z1 与 z2 不同
list
可以从 STATA 上下载专门用于生成服从若干主要分布的随机数生成命令。
search rnd, net
rndbin 1000 0.5 1 //生成 1000 个 0-1 分布,取 1 的概率为 0.5(抛均匀的硬币)
2 简单模拟
利用随机数字表或者电脑软件中的随机数字,来模仿机遇现象,叫模拟(simulation)、一旦有了可靠的概率模型,模拟是找出复杂事件发生概率的有效工具。一个事件在重复结果中发生的比例,迟早会接近它的概率,所以模拟可以对概率做适当的估计。
例 1:如何执行模拟
掷一枚硬币 10 次,结果中会出现至少 3 个连续正面或者至少 3 个连续反面的概率是多少?
思考:
(1) 猜想这个概率大约是多少?
(2) 如何从理论上计算出这个概率?
(3) 如何模拟计算这个概率?
第一步:提出概率模型。
z 每一次掷,正面和反面的概率各为 0.5
z 投掷之间,彼此是独立的。也就是说,知道某一次掷出的结果,不会改变任何其他次所
掷结果的概率。
第二步:分配随机数字以代表不同的结果。
z 随机数字表中的 0-9 每个数字出现的概率都是 0.1
z 每个数字模拟掷一次硬币的结果。
z 奇数代表正面,偶数代表反面。
第三步:模拟多次重复。
z 生成 10 个随机数字
z 记录开心的事件(至少连续三个正面或反面)是否发生,如果发生,记为 1,否则为 0
z 重复 10 次(或者 100,1000,1000000 次),计算概率=开心事件发生/总重复次数。
capt prog drop seq3
prog seq3,rclass //rclass 选项表示计算结果将由 return 返回到 r()
drop _all //清空所有数据,不能用 clear
set obs 10 //将生成 10 个观察值
tempvar x y z //设定 x,y,z 为临时变量
gen `x'=int(10*uniform()) //产生 10 个随机变量,可能为 0,1,…,9
gen `y'=(mod(`x',2)==0) //如果生成的随机变量为奇数,则 y=0;为偶数,y=1
gen `z'=0 //生成 Z=0
forvalues i=3/10 {
replace `z'=1 if `y'==`y'[_n-1] & `y'==`y'[_n-2] in `i' //连续三个变量相等时 z=1
}
sum `z'
return scalar max=r(max) //z 取 1 表示高兴的事发生(三连续),否则失败
end
simulate max=r(max),reps(10000) nodots:seq3 //重复 1 万次,取平均结果
sum
另一种更简单,但不尽规范的程序是:
clear
set obs 10 //将生成 10 个观察值
gen y=0
gen z=0
capt prog drop seq3
prog seq3
replace y=uniform()>0.5 //如果生成的随机变量为奇数,则 y=0;为偶数,y=1
replace z=0
forvalues i=3/10 {
replace z=1 if y==y[_n-1] & y==y[_n-2] in `i'
}
sum z
end
simulate r(max),reps(10000) nodots:seq3 //重复 1 万次,取平均结果
sum
由于上述命令要不停生成变量,进行变量代换,所以计算速度较慢,如果使用矩阵,则
计算速度会大大加快,命令也更简捷。
capt prog drop seq3
program seq3
drop _all //清空所有数据,不能用 clear
rndbin 10 0.5 1
gen z=0 //生成 Z=0
forvalues i=3/10 {
replace z=1 if xb==xb[_n-1] & xb==xb[_n-2] in `i' //连续三个变量相等时 z=1
}
sum z
end
simulate max=r(max),reps(10000) nodots:seq3 //重复 1 万次,取平均结果
sum
另一种形式:
mata
A=uniform(10000,10):>0.5 //每行为一次试验, 每列为某次试验中硬币的正反面结果
B=J(10000,1,0) //记录每次试验的结果,先记为 0. 若高兴结果出现再改为 1
for (j=1;j<=rows(A);j++) { //第 j 次试验
for (i=3;i<=cols(A);i++) { //第 j 次试验中第 i 次硬币出现结果
if ( A[j,(i-2,i-1,i)]==(0,0,0) | A[j,(i-2,i-1,i)]==(1,1,1) ) {
B[j,1]=1 //若连续为正面或者连续为反面,则记为 1
break
}
}
}
mean(B) //求开心事件的次数
end
3 复杂模拟
例 2:我们要女儿
任务:一对夫妇计划生孩子生到有女儿才停,或者生了三个就停,他们拥有女儿的概率
是多大?
思考:理论上,概率是多少?
第一步:概率模型
每一个孩子是女孩的概率是0.49,且各个孩子的性别是互相独立的。
第二步:分配数字
00,01,02,。。。,49=女孩
49,50,51,。。。,99=男孩
第三步:模拟
从随机数表中生成一对一对的数字,直到有了女儿,或已有3个孩子。
重复100次。
6905 16 48 17 8717 648987
男女 女 女 女 男女 男男男
用数学可以计算出来,有女孩的真正概率是0.867
capt program drop girl
program girl, rclass
drop _all
set obs 3
gen x=int(100*uniform())
gen y=(x<49) //生女孩 y=1,生男孩,y=0
sum y
return scalar max=r(max) //若至少有一个女孩,则 max 取 1,若三个全为男孩,取 0
end
simulate max=r(max),reps(10000) nodots:girl
sum
另一种办法
clear
set obs 3
gen y=0
capt program drop girl
program girl
replace y=(int(100*uniform())<49) //生女孩 y=1,生男孩,y=0
sort y
scalar girl=y[_N] //若至少有一个女孩,则 girl 取 1,若三个全为男孩,取 0
end
simulate girl, reps(10000) nodots:girl
sum
更快的模拟方式.
capt program drop girl
program girl
mat A=matuniform(1,3)
scalar girl=1
if A[1,1]>0.49 & A[1,2]>0.49 & A[1,3]>0.49 {
scalar girl=0 //若三个全为男孩,生女孩数为 0
}
end
simulate girl,reps(10000) nodots:girl
tab _sim
mata
A=uniform(10000,3):<0.49
B=J(10000,1,1)
for (i=1;i<=rows(A);i++) {
if (A[i,.]==(0,0,0)) {
B[i,1]=0 //若三个全为男孩,生女孩数为 0
}
}
mean(B)
end
4 多阶段模拟
张三肾脏不行了,正在等待换肾,他的医师提供了和他情况类似的病人资料。“换肾后的五年存活率:撑过手术的有0.9,术后存活的人中有0.6移植成功,0.4还得回去洗肾;五年后,有新肾的人70%仍然活着,而洗肾的只有一半仍活着。”张三希望知道,他能活过五年的概率,然后再决定是否换肾。
capt program drop surv
program surv
scalar z=1
if uniform()<0.1 {
scalar z=0
}
else {
if uniform()<0.6 {
if uniform()>=0.7 {
scalar z=0
}
}
else {
if uniform()>=0.5 {
scalar z=0
}
}
}
end
simulate z,reps(10000) nodots:surv
sum