美文网首页一文讲完系列
一文讲完random:python中的随机模块

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

作者: fakeProgramer | 来源:发表于2023-04-22 21:16 被阅读0次

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

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/etusjdtx.html