python 排列组合函数_Python概率统计(1):排列组合

〇、写在前面

最近面试了很多数据分析师的候选人,包括很多背景相当不错的同学,但是都不是特别满意。

为什么呢?其实说来很简单,我们这边对于经验较少的分析师,尤其是刚毕业或者校招的同学,会着重考察统计基础和基本的代码能力。但是碰到的候选人要么是统计基础不错但是不会代码,要么是有一定的代码能力但对许多处理背后的统计学原理一知半解。因此有很多优秀的候选人最终都被我们放弃了。

我们做出这一选择是基于这样的一系列假设:毕业两年内的同学,很少能有独立支持核心项目的经验,因此仅从这一项中我们可能不足以判断候选人的基本素质;

工作经验少的同学,如果有自主学习的意识以及对这个岗位的期待的话,一定会主动充电,夯实自己的数据分析基本功;

数据分析师的基本功,一方面体现在统计学的基础上,另一方面则体现在对于概率统计知识的应用上(比如用代码能力检验一些假设、构建一些模型等);

统计学或代码能力不足的同学,要么是没有进行过足够的思考以至于不清楚要学习什么,要么是有清楚的目标但是执行力不足,要么就是没想过要在这一行长期发展。

可能有些同学要问了,那对于经验丰富的分析师就不看这些了吗?

其实也看的。但是对于经验丰富的分析师,我们会首重过往的工作经验和项目经历,从中判断业务理解是否深刻、思维逻辑是否清晰、考虑问题是否全面等,最后才会看一下技术能力和统计基础。毕竟分析师的工作并不是天天在做统计推演,其最重要的目标是帮助业务健康发展。如果工作了一些年头之后,还要依靠这些基本功来获得工作,那简直是对过去工作经历的一种侮辱。当然,面试时间有限也是一个原因。

这一现象也让我有了写这一系列文章的想法。我跟很多候选人聊过,很多人在学习的时候也知道统计学和代码能力都是现在数据分析师的基本能力,但是统计学的海洋很大,提升代码能力也不是一蹴而就的,在精力不足的时候就不能兼顾了。

其实根据我的经验,我们只要能在每个模块掌握两三成的基本知识,并了解额外的两三成、听说过更多的两三成,就基本上够用了。因此我准备把这些年积累下的经验写出来,帮助新入行的数据分析师、有志于入行的在校生或者想提升数据分析能力的其他行业工作者少走一些弯路。

今后的内容基本上都包括三个部分:原理、案例以及Python实战。

一、概率基础

A. 那些谁让我讲我就跟谁急的部分

在概率统计中,最基础的知识如:样本空间、集合、事件等概念;

事件的并、交、补运算;

集合论的一些运算律如交换律、组合率和分配率;

概率测度的一些基本定理,如两个对立事件的概率之和为1、P(A或B)=P(A)+P(B)-P(A与B)等;

……

这些属于初中甚至小学就应该了解的东西,我就不浪费大家时间了,即使有些同学可能不太记得了,稍微动脑子想一下或者花上个几十分钟补一下也就应该能跟上了。谁有脸让我讲这些开裆裤级别的东西我就把谁置顶给大家鉴赏。

B. 排列组合

我单独将排列组合列出来并不是因为他们难,我就是担心真的有部分同学记不清楚了。万一到后边讲到二项分布什么的,关于这一部分你们不懂,我岂不是还得返工?小朋友的问号一点都不可爱。

B.1 排列

从n个元素的集合中抽取样本容量为r的样本,重复抽样的有序样本数为:

equation?tex=n%5Er

无重复抽样的有序样本数为:

equation?tex=n%28n-1%29%5Ccdots%28n-4%2B1%29+%3D+%5Cfrac%7Bn%21%7D%7B%28n-r%29%21%7D

这个理解起来应该是没有难度的,比如有10个人,我现在想选3个人出来拍我马屁: 1. 那我先从10个人里随便挑一个,这已经有10种情况了; 2. 我再从剩下的的9个人里随便挑一个,又是9种情况; 3. 然后我又从剩下的8个人力随便挑一个,又是8种情况; 4. 根据乘法原理,一共有10×9×8=720种情况。

上述等式右边的部分是为了便于表示和计算,分子分母中都存在(n-r)!这一部分,两两抵消就是等式左边的部分了。

B.1.1 Python计算

对于一个新时代的数据分析师来说,用笔或者用计算器来计算简直是值得羞耻的一件事。如果学完这一节课还有人需要手动计算,我建议你用咸鱼抽自己,抽到脸上没有血色不会脸红为止。(工具限制或过于简单的场景除外,因为这种事情我也干过……)

我们来实现一个函数,用于计算无重复抽样的排列数。

from functools import reduce

import numpy as np

# @author: 头条号/各种号:数据洞察指南

# @date: 2020/04/16

def permutation(n, k):

""":param n: 全集大小:param k: 要抽取的样本容量:return: 无重复抽样的有序样本数"""

# 非法输入返回空

if n <= 0 or k < 0 or n < k:

print('Wrong Input, Baby!')

return None

# k为0时,排列数恒为1!

if k == 0:

return 1

# 生成倒序的序列

series_desc = np.arange(n, n-k, -1)

# 计算有序排列数

permutation = reduce(lambda x, y: x * y, series_desc)

# 返回结果

return permutation

if __name__ == "__main__":

for n in range(1, 5):

for k in range(0, 3):

print('n={0}, k={1}: {2}'.format(n, k, permutation(n, k)))

返回的结果正常。

n=1, k=0: 1

n=1, k=1: 1

Wrong Input, Baby!

n=1, k=2: None

n=2, k=0: 1

n=2, k=1: 2

n=2, k=2: 2

n=3, k=0: 1

n=3, k=1: 3

n=3, k=2: 6

n=4, k=0: 1

n=4, k=1: 4

n=4, k=2: 12

我来解释一下这里的代码。

1. 首先,我们要考虑到非法输入的情况,比如样本空间不大于0、抽样的样本容量小于0或者抽样的样本容量小于样本空间的场景,这些情况下我们都给一个提示:输入错误,宝贝儿!

2. 然后就是抽取的样本容量为0的时候,排列数等于1,这个和总的样本空间大小是没有关系的。为什么呢?抽0个,相当于抽取的样本就是空集,这就是仅有的一种情况;

3. 然后我们先生成一个倒序的序列,从样本空间大小n开始,一个一个往下数,数k个整数出来,没忘记上边公式的话,不难想到我们下一步就是要把这些数字乘起来。在这里,np.arange(start, stop, step)用于生成一个整数列表,第一个参数是开始的整数,第二个参数是结束的整数,第三个参数为步长,-1代表每次减1;

4. 接下来我们用functools.reduce()函数,将这些数字的乘积求出来。reduce()是一个神器,它可以将我们定义的一个函数在一个列表型数据中不断迭代,比如在这个例子中,我们用一个lambda函数,结合reduce函数,先用n*(n-1),然后再用这个结果乘以(n-2)...

5. lambda函数是Python中另一个常用的工具,它可以帮助我们定义一些“轻量级”的隐函数,比如上边这一个x*y,我们也可以用def来写一个完整的函数,但是没必要,省下一两行代码不香吗?

这里的解释已经尽可能详尽,如果还有疑问的同学,可以在下方留言,我会主动回复,或者选择正确的分析回答置顶。

B.1.2 生日问题

为了加强理解,我再给大家举一个经典的例子。假设一个班级中共有n个人,一年有365天,其中每天作为生日的概率是相等的,那么其中至少有两个人的生日在同一天的概率是多少?

如果我们去穷举任意两个或以上的人在同一天生日的情况,明显会陷入一个复杂的问题之中。但是如果我们反过来考虑,“至少两个人在同一天生日”的对立事件就是“任意两个人都没有在同一天生日”,那么根据我们前文提到的“对立事件的概率之和为1”,假设“至少两人同一天生日”为时间A,有:

equation?tex=P%28A%29+%3D+1+-+P%28A%5Ec%29

而任意两个人都没有在同一天生日就相当于n个人的生日,分别分布在了365天中的n天,即从365天中选取n天。假设没有这一限制的话,这n位同学的生日分布共有365的n次方中情况,因此:

equation?tex=P%28A%29+%3D+1+-+P%28A%5Ec%29+%3D+1+-+%5Cfrac%7B365+%5Ctimes+364+%5Ctimes+%5Ccdots+%5Ctimes+%28365+-+n+%2B+1%29%7D%7B365%5En%7D+

假设现在有50个同学,那这个概率的值为多少?

就这个问题,哪个靓仔有能耐给我手动算一下试试?我也不要求你手算,就拿计算器来敲!如果真有谁有这个时间,麻烦计一下时,留言告诉大家你的壮举。我一定也给你顶上去。

我们用刚才的函数来算一下看看:

permutation_cnt = permutation(365, 50)

result_prob = 1 - permutation_cnt / (365 ** 50)

print(result_prob)

结果发现,报错了。

RuntimeWarning: overflow encountered in long_scalars

什么意思呢?数字太大,溢出了。那我们稍微调整一下:

equation?tex=%5Cbegin%7Baligned%7D+P%28A%29+%3D+1+-+P%28A%5Ec%29+%26%3D+1+-+%5Cfrac%7B365+%5Ctimes+364+%5Ctimes+%5Ccdots+%5Ctimes+%28365+-+n+%2B+1%29%7D%7B365%5En%7D+%5C%5C+%26%3D+1+-+%5Cfrac%7B365%7D%7B365%7D+%5Ctimes+%5Cfrac%7B364%7D%7B365%7D+%5Ctimes+%5Ccdots+%5Ctimes+%5Cfrac%7B365-n%2B1%7D%7B365%7D++%5Cend%7Baligned%7D

我们再写一段代码看看:

series_desc = np.arange(365, 365-50, -1)

series_desc_div = series_desc / 365

p_ac = reduce(lambda x, y: x * y, series_desc_div)

p_a = 1 - p_ac

print('{0:.2f}%'.format(p_a*100))

答案揭晓:

97.04%

看来50人的班级,大概率是要有同一天生日的同学啦!

B.2 组合

从n个对象中,无重复地抽取r个无需样本的个数为:

equation?tex=%5Cleft%28%5En_r+%5Cright%29+%3D+%5Cfrac%7Bn%28n-1%29%5Ccdots%28n-r%2B1%29%7D%7Br%21%7D

即,用n选r的有序排列数除以r选r的有序排列数。

B.2.1 Python计算

可以看出来,组合与排列的区别就在于组合是无序样本。当我们选择了一个大小为r的样本时,其中的排列数为r!种,因此用n选r的有序样本数除以该样本中的排列数,就可以得到最终的组合数。

那么我们的计算就可以沿用前边的成果了:

def combination(n, k):

""":param n: 全集大小:param k: 要抽取的样本容量:return: 无重复抽样的无序样本数"""

# 消灭非法输入

if n <= 0 or k < 0 or n < k:

print('Wrong Input, Baby!')

return None

# k为0时,组合数恒为1

if k == 0:

return 1

# 返回组合数结果

return int(permutation(n, k) / permutation(k, k))

没错,就这么简单。

我们检验一下:

print('组合计算测试')

for n in range(1, 5):

for k in range(0, n+1):

print('n={0}, k={1}: {2}'.format(n, k, combination(n, k)))

看下结果:

组合计算测试

n=1, k=0: 1

n=1, k=1: 1

n=2, k=0: 1

n=2, k=1: 2

n=2, k=2: 1

n=3, k=0: 1

n=3, k=1: 3

n=3, k=2: 3

n=3, k=3: 1

n=4, k=0: 1

n=4, k=1: 4

n=4, k=2: 6

n=4, k=3: 4

n=4, k=4: 1

结果正常。

接下来,我给一道题目,希望大家能自己练习一下,然后留言到评论里。

大乐透的玩法是这样的,从35个红球中选择5个,从12个红球中选择2个,如果全中,那就中一等奖。那么请问,中一等奖的概率是多少?

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值