這一節的標題是0.4 A Normal Distribution of Random Numbers
因為方格子標題字數限制,所以沒完整顯現
在念統計學時,常態分布(normal distribution),也叫高斯分布(Gaussian distribution),是一定會念到的內容。
常態分布是在自然界中常見的分布方式。例如,以人類的身高而言,大部分人的身高,會集中在某一範圍內,只有少數人,身高會特別高或特別矮。所以,在模擬自然界中的事物時,如果需要使用到亂數,就不能單純的使用會產生uniform distribution亂數的亂數產生器。例如,要模擬一群猴子時,假如我們用random.randint(200, 300)
來設定每隻猴子的身高有多少個像素,那將會發現,不同身高猴子的數量,都會差不多。這樣子的設定,很不真實,畢竟在大自然中,應該不會出現像這樣的一群猴子,不然就太詭異了。
常態分布,值會集中在平均值附近,而形成像下圖一樣的鐘形曲線(繪製程式見本節文末):
圖中的曲線,是由機率密度函數(probability density function, PDF)所產生。透過PDF,可以算出隨機變數出現在某個範圍內的機率。計算PDF的值,要先知道該分布的均值(mean)及標準差(standard deviation)。均值,常記為μ,指的就是平均值;而標準差,常記為σ,則跟數值集中在均值附近的程度有關。在左圖中,曲線比較高、尖,數值比較集中在均值附近,所以標準差比較小;而在右圖中,曲線比較矮、平,數值散佈的範圍比較廣,所以標準差比較大。
常態分布有個特性:不管長得高、矮、胖、瘦,所有的數值,有68%會落在距離均值1個標準差的範圍內;有95%會落在在距離均值2個標準差的範圍內;有99.7%會落在在距離均值3個標準差的範圍內。所以,以前面那群猴子的身高設定為例,如果我們讓整群猴子的身高,呈現均值為250個像素,而標準差為5個像素的常態分布,那就只會有0.3%的猴子,身高少於235個像素,或者高於265個像素。這是因為235和265都距離均值3個標準差,身高在這個範圍內的猴子,佔了99.7%。所以,身高在這個範圍外的猴子,就只佔0.3%
在Python中,可用
random.gauss(mu, sigma)
來產生呈常態分布的亂數。下面的例子,便是在寬度為640像素的畫面上,不斷地在水平方向上畫出圓盤,而這些圓盤的位置,會呈現均值320像素、標準差60像素的常態分布。這些圓盤有些許透明,而且畫的時候是不斷地疊上去的。
Example 0.4: A Gaussian Distribution
# python version 3.10.9
import random
import sys
import pygame # version 2.3.0
pygame.init()
pygame.display.set_caption("Example 0.4: A Gaussian Distribution")
WHITE = (255, 255, 255)
GRAY = (25, 25, 25, 25) # RGBA
screen_size = WIDTH, HEIGHT = 640, 360
screen = pygame.display.set_mode(screen_size)
FPS = 60
frame_rate = pygame.time.Clock()
disk_radius = 8
disk_center = (8, 8)
surface_size = (2*disk_radius, 2*disk_radius)
surface = pygame.Surface(surface_size, pygame.SRCALPHA)
pygame.draw.circle(surface, GRAY, disk_center, disk_radius)
y = HEIGHT//2
screen.fill(WHITE)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
x = random.gauss(320, 60)
screen.blit(surface, (x, y))
pygame.display.update()
frame_rate.tick(FPS)
因為pygame.draw()
所畫出的圖案,並沒有透明度的效果,所以即使在顏色中有設定alpha值,所畫出的圖案,仍然是不透明的。為了讓圖案有透明度,需先造出一個具有per pixel alpha性質的surface,然後把圖畫在這個surface上,最後再把這個surface疊到要顯示的畫面上。這樣子的做法,類似於許多繪圖軟體所提供的圖層功能。
要造出具有per pixel alpha性質的surface,需使用pygame.SRCALPHA
參數。
Exercise 0.4
圓點位置之x、y座標,分別由2個常態分布亂數產生。顏色之RGB值,分別由3個常態分布亂數產生。但應注意的是,RGB值的範圍,是在0~255。所以,應該檢查、調整,使亂數值落於這個範圍內。完整的程式碼如下:
# python version 3.10.9
import random
import sys
import pygame # version 2.3.0
pygame.init()
pygame.display.set_caption("Exercise 0.4")
WHITE = (255, 255, 255)
screen_size = WIDTH, HEIGHT = 640, 360
screen = pygame.display.set_mode(screen_size)
FPS = 60
frame_rate = pygame.time.Clock()
dot_size = 5
mu_x, mu_y = WIDTH/2, HEIGHT/2
sigma_x, sigma_y = WIDTH/8, HEIGHT/8
mu_rgb, sigma_rgb = 255//2, 255//6
rgb = [0, 0, 0]
screen.fill(WHITE)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
for i in range(3):
rgb[i] = random.gauss(mu_rgb, sigma_rgb)
if rgb[i] > 255:
rgb[i] = 255
elif rgb[i] < 0:
rgb[i] = 0
x = random.gauss(mu_x, sigma_x)
y = random.gauss(mu_y, sigma_y)
pygame.draw.circle(screen, rgb, (x, y), dot_size)
pygame.display.update()
frame_rate.tick(FPS)
程式執行結果的截圖如下圖
Exercise 0.5
將Walker
這個class的__init__()
、step()
修改如下:
def __init__(self, x=0, y=0, mu=0, sigma=1):
# pygame的畫面
self.screen = pygame.display.get_surface()
# walker的位置,預設值是(0, 0)
self.x = x
self.y = y
self.mu = mu
self.sigma = sigma
def step(self):
choice = random.randint(0, 3)
step_size = abs(int(random.gauss(self.mu, self.sigma)))
if choice == 0:
self.y -= step_size
elif choice == 1:
self.y += step_size
elif choice == 2:
self.x -= step_size
elif choice == 3:
self.x += step_size
產生Walker
的instance時,可設定步伐大小的均值與標準差。例如:
walker = Walker(WIDTH//2, HEIGHT//2, 0, 3)
程式執行結果的截圖如下圖
常態分布鐘形曲線繪製程式
# python version 3.10.9
import math
import matplotlib.pyplot as plt # version 3.7.1
def pdf(x, mu=0.0, sigma=1.0):
return math.exp(-(x-mu)**2/(2*sigma**2))/(sigma*math.sqrt(2*math.pi))
plt.figure(figsize=(10, 6))
itvl = range(-70, 71)
x1 = [0.1*i for i in itvl]
y1 = [pdf(0.1*i, 0.0, 1) for i in itvl]
x2 = [0.1*i for i in itvl]
y2 = [pdf(0.1*i, 0.0, 2) for i in itvl]
plt.subplot(1, 2, 1)
plt.xticks([])
plt.yticks([])
plt.ylim([0.0, 0.45])
plt.plot(x1, y1)
plt.subplot(1, 2, 2)
plt.xticks([])
plt.yticks([])
plt.ylim([0.0, 0.45])
plt.plot(x2, y2)
plt.show()