一文讲完random:python中的随机模块

我们在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)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,875评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,569评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,475评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,459评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,537评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,563评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,580评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,326评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,773评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,086评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,252评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,921评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,566评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,190评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,435评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,129评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,125评论 2 352