我们在python工程和数据分析中经常用到随机的操作,比如随机生成某个值,对一串数据进行随机排序等等。random是python一个很强的第三方库,可以实现常用的随机算法。
安装:pip install random
一:生成随机的数字
0~1之间的随机小数(float):random.random()
a~b之间的随机小数(float):random.uniform(a, b)
[a, b)之间的随机整数(int):random.randint(a, b)
import random
print(f'''
random.random():0~1之间抽取随机小数:
{random.random()}
''')
print(f'''
random.uniform():a~b之间抽取随机小数:
{random.uniform(1,10)}
''')
print(f'''
random.randint():[a, b)之间抽取随机整数:
{random.randint(3,8)}
''')
# 运行结果:
random.random():0~1之间抽取随机小数:
0.75106286590147
random.uniform():a~b之间抽取随机小数:
1.1154946513637323
random.randint():[a, b)之间抽取随机整数:
6
二:对非空序列(元组、列表、字符串)操作随机拿取
随机地取一个值(等权):random.choice(lis)
这个很简单,从对象中随机取出一个值,每个值取到的概率相同。
import random
lis = (1,2,3,4,5,'cat')
print(random.choice(lis))
# 运行结果:
4
随机地取k个值(可分配权重):random.choices(lis, weights=[], cum_weights=[], k=n)
这里重点讲一下几个参数:
- 参数1:population=lis,即样本集。
- 参数2:设置权重weights或cum_weights,传入一个权重列表,为每个样本元素分别设置权重(权重列表个数要与样本列表个数一致)。
weights(相对权重)和cum_weights(累加权重)都是权重参数,设置方式不同而已,使用时选择其一。 - 参数3:k=n,即从样本中取几个元素。
重点来了:
要了解weights(相对权重)和cum_weghts(累加权重),我们看两个例子:
weights相对权重:把每个权重要素加一起,其总和为总权重。每个权重要素占总权重的比例,即为对应样本的概率。
import random
lis = (1,2,3,4,5,'cat')
print(random.choices(population=lis, weights=[1,2,1,1,3,4], k=3))
'''
weights=[1,2,1,1,3,4],权重总和为:1+2+1+1+3+4 =12
即:
第一个元素1抽到的概率为:1/12,约8.3%
第二个元素2抽到的概率为:2/12,约16.7%
...
第五个元素5抽到的概率为:3/12,25%
第六个元素‘cat’抽到的概率为:4/12,约33.3%
'''
# 运行结果:
[2, 'cat', 5]
cum_weights累加权重:网上的教程对累加权重解释的都很模糊,我在这里详细说一下。
- 累加权重是“分蛋糕”的概念,样本按照所设权重的总份数(总蛋糕),从第一个开始依次分。分完即止,后面没分到权重的概率为0。
- 列表的最后一项为权重的总份数(共有多少份蛋糕)。
- 列表的每一项代表截至到此,累计拿了几份权重(拿走了多少份蛋糕)。
下面看几个例子。为了便于直观看到概率是多少,我们写个函数test_cumweights(),专门统计样本出现的概率。
def test_cumweights(lis, cw, m, n):
'''
【输入】lis:样本集,cw:累加权重,m:抽多少次,n:每次抽几个
【输出】每个样本出现的概率
'''
result = {} # 用于记录样本出现的次数,{样本:次数}
for i in lis:
result[i] = 0 # 初始化,出现次数均为0
num = 0
while num < m: # 抽 m 次
res = random.choices(population=lis, cum_weights=cw, k=n)
# 抽n个的结果res
for i in res:
result[i] += 1 # 将本次的结果保存到字典
num += 1
times = num * n # 一共抽了多少次
for k,v in result.items(): # 打印结果
print(f'共抽了{times}次,其中【{k}】出现了{v}次,概率约为:{round((v/times),3)}')
例一:cum_weights=[1,2,2,3,7],总权重为7份。
第一个元素:累计拿了一份,即1/7为14.3%;
第二个元素:累计拿了两份,即第二个元素拿了一份权重,1/7为14.3%;
第三个元素:累计拿了两份,说明第三个元素并未拿权重,即0%;
第四个元素:累计拿了三份,即第四个元素也拿了一份权重,1/7为14.3%;
第五个元素:到此累计拿了七份,即第五个元素拿了四份权重,4/7为57.1%。
lis = ['张飞','马超','荀彧','颜良文丑','毒苹果']
cw = [1,2,2,3,7]
m = 10000
n = 3
test_cumweights(lis=lis,cw=cw, m=m, n=n)
# 运行结果:
共抽了30000次,其中【张飞】出现了4284次,概率约为:0.143
共抽了30000次,其中【马超】出现了4366次,概率约为:0.146
共抽了30000次,其中【荀彧】出现了0次,概率约为:0.0
共抽了30000次,其中【颜良文丑】出现了4322次,概率约为:0.144
共抽了30000次,其中【毒苹果】出现了17028次,概率约为:0.568
是不是很好理解?别急,下面还有难的:
例二:cum_weights=[2,3,1,2,7],总权重为7份。
第一个元素:累计拿了两份,即2/7为28.6%;
第二个元素:累计拿了三份,即第二个元素拿了一份权重,即1/7为14.3%;
第三个元素:累计拿了一份,权重分配到此,说明又收回了两份,只分了一份出去。谁拿了呢?显然是第一个,因为第一个是2,是最先分配的(如果是0呢?)。所以第一个元素变成了1/7为14.3%,第二个元素没分到为0%,第三个元素也没拿,为0%;
第四个元素:累计拿了两份,即第四个元素拿了一份权重,1/7为14.3%;
第五个元素:到此累计拿了七份,即第五个元素拿了五份权重,5/7为71.4%。
lis = ['张飞','马超','荀彧','颜良文丑','毒苹果']
cw = [2,3,1,2,7]
m = 10000
n = 3
test_cumweights(lis=lis,cw=cw, m=m, n=n)
# 运行结果:
共抽了30000次,其中【张飞】出现了4281次,概率约为:0.143
共抽了30000次,其中【马超】出现了0次,概率约为:0.0
共抽了30000次,其中【荀彧】出现了0次,概率约为:0.0
共抽了30000次,其中【颜良文丑】出现了4425次,概率约为:0.147
共抽了30000次,其中【毒苹果】出现了21294次,概率约为:0.71
例二中,如果cw第一个是0呢?显然,第一个就没要份额,后面重新分配时就会把权重分给第二个,即cw=[0, 3, 1, 2, 7]:
元素一:0%
元素二:14.3%
元素三:0%
元素四:14.3%
元素五:71.4%
lis = ['张飞','马超','荀彧','颜良文丑','毒苹果']
cw = [0,3,1,2,7]
m = 10000
n = 3
test_cumweights(lis=lis,cw=cw, m=m, n=n)
# 运行结果:
共抽了30000次,其中【张飞】出现了0次,概率约为:0.0
共抽了30000次,其中【马超】出现了4281次,概率约为:0.143
共抽了30000次,其中【荀彧】出现了0次,概率约为:0.0
共抽了30000次,其中【颜良文丑】出现了4292次,概率约为:0.143
共抽了30000次,其中【毒苹果】出现了21427次,概率约为:0.714
累加权重cum_weights支持整数和小数分配,逻辑是一样的。
累加权重的概念到此讲完,着实很抽象。
要知道,random.choices()的运算逻辑就是把相对权重weights先转为累加权重cum_weights,再执行随机算法。因此使用cum_weights参数的运算效率要高一些。
如果不是追求极致的算法,使用weights就能满足日常需要。但我还是建议你掌握cum_weights的逻辑,以便日后装杯用。
三、随机打乱一个可迭代对象
random.shuffle(lis)
该用法是对lis的原数据执行打乱操作,本身无返回值,lis将被随机打散,原数据再也回不来了。
lis = ['张飞','马超','荀彧','颜良文丑','毒苹果']
random.shuffle(lis)
print(random.shuffle(lis))
print(lis)
# 运行结果:
None
['颜良文丑', '荀彧', '毒苹果', '张飞', '马超']
因此shuffle()使用起来会有诸多不便。我们可以构建一个自定义的shuffle函数,结合在numpy系列里讲的copy用法,实现对传入对象的打散,且不改变原对象的数据。
import random
import copy
def my_shuffle(lis):
copy_lis = copy.deepcopy(lis) # 深拷贝一个副本对象
try:
random.shuffle(copy_lis) # 对副本对象操作打散
except:
return None
return copy_lis
lis = ['张飞','马超','荀彧','颜良文丑','毒苹果']
result = my_shuffle(lis=lis)
print(f'执行打散后的数据:{result}')
print(f'原数据:{lis}')
# 运行结果:
执行打散后的数据:['马超', '颜良文丑', '毒苹果', '荀彧', '张飞']
原数据:['张飞', '马超', '荀彧', '颜良文丑', '毒苹果']
四、固定某个随机结果
使用random.seed(n)可以将接下来的随机操作结果固定下来,如果后面的代码调用该seed,将复现本次的随机操作,使两次随机结果相同。
参数n可以为整型、浮点型、甚至为字符串。其本身没有任何意义,只作区分本次标记用。
因此要注意,每个seed值对应的随机结果是唯一的。
我们看个抽牌的小游戏,随机从一叠牌中抽取一张,如果抽到了JOKER,则使用seed模仿赌神,再抽一次JOKER出来。
res = ['10', 'J', 'Q', 'K', 'A', 'JOKER'] # 定义几张牌
num = 0
while num < 10: # 抽10次
set_seed = random.random() # 定义一个随机seed, 用于标记本次随机的记录
random.seed(set_seed)
get_it = random.choice(res) # 抽牌
print(f'本次抽到了{get_it}。')
if get_it == 'JOKER':
random.seed(set_seed) # 如果为JOKER, 则使用本次标记的seed再抽一张JOKER出来
get_again = random.choice(res) # 执行第二次抽取
print(f'触发第二次,本次抽到了{get_again}')
else:
pass
num += 1
print('- ' * 20)
# 运行结果:
本次抽到了A。
- - - - - - - - - - - - - - - - - - - -
本次抽到了Q。
- - - - - - - - - - - - - - - - - - - -
本次抽到了K。
- - - - - - - - - - - - - - - - - - - -
本次抽到了Q。
- - - - - - - - - - - - - - - - - - - -
本次抽到了JOKER。
触发第二次,本次抽到了JOKER
- - - - - - - - - - - - - - - - - - - -
本次抽到了Q。
- - - - - - - - - - - - - - - - - - - -
本次抽到了JOKER。
触发第二次,本次抽到了JOKER
- - - - - - - - - - - - - - - - - - - -
本次抽到了Q。
- - - - - - - - - - - - - - - - - - - -
本次抽到了A。
- - - - - - - - - - - - - - - - - - - -
本次抽到了A。
- - - - - - - - - - - - - - - - - - - -
五、生成固定bit大小的随机整数:
random.getrandbits(s)
关于比特位bit和字节Byte的知识:
- 比特位bit指二进制的机器码(0和1),1011 代表4 bit;
- 1 Byte = 8 bit。
用最后一个例子结束random的讲解。生成一个随机bit位的随机数,执行十次看看结果如何:
num = 0
while num < 10: # 试验十次
size = random.randint(1,20) # 定义一个随机大小的bit位(1-19之间)
result = random.getrandbits(size) # 生成size大小的随机整数
result_b = bin(result) # 转成二进制用做验证
print(f'''
第{num+1}次试验:
生成结果为:{result},其二进制为:{result_b}
该数字大小为{size}bit,{round(size/8,2)}byte
''')
num += 1
print('- ' * 20)
# 运行结果:
第1次试验:
生成结果为:3,其二进制为:0b11
该数字大小为4bit,0.5byte
- - - - - - - - - - - - - - - - - - - -
第2次试验:
生成结果为:211,其二进制为:0b11010011
该数字大小为8bit,1.0byte
- - - - - - - - - - - - - - - - - - - -
第3次试验:
生成结果为:435,其二进制为:0b110110011
该数字大小为9bit,1.12byte
- - - - - - - - - - - - - - - - - - - -
第4次试验:
生成结果为:784,其二进制为:0b1100010000
该数字大小为14bit,1.75byte
- - - - - - - - - - - - - - - - - - - -
第5次试验:
生成结果为:33159,其二进制为:0b1000000110000111
该数字大小为19bit,2.38byte
- - - - - - - - - - - - - - - - - - - -
第6次试验:
生成结果为:3,其二进制为:0b11
该数字大小为4bit,0.5byte
- - - - - - - - - - - - - - - - - - - -
第7次试验:
生成结果为:56,其二进制为:0b111000
该数字大小为7bit,0.88byte
- - - - - - - - - - - - - - - - - - - -
第8次试验:
生成结果为:0,其二进制为:0b0
该数字大小为4bit,0.5byte
- - - - - - - - - - - - - - - - - - - -
第9次试验:
生成结果为:1,其二进制为:0b1
该数字大小为2bit,0.25byte
- - - - - - - - - - - - - - - - - - - -
第10次试验:
生成结果为:26,其二进制为:0b11010
该数字大小为5bit,0.62byte
- - - - - - - - - - - - - - - - - - - -
值得注意的是,我们以2举例,其二进制既可以为10,也可以为0010或00010。
其对应的值相同,只是占的bit大小不同(2bit、4bit、5bit)。
网友评论