02-创建数组,你还可以这么玩

01-Numpy初体验:数组创建与数据类型

上一小节我们学习了先创建一个序列型对象,再基于此生成一个数组。但是这样也有一定的局限性,因为如果我们创建的数组比较大,比方说 100×100 的大小,那就有点麻烦了。

实际上,Numpy 内置了很多创建数组的方式,除了上一小节我们讲解的方法外,Numpy 还提供了快速创建等差数组、等比数组、随机数数组等方法。

1. 创建等差数组

  • arange() 方法

如果让大家创建一个等差列表,从1到100,一共 100 项,那一定非常方便,用 range(1,101,1) 快速搞定。Numpy 也提供了类似的方法 arange() ,用法与 range() 非常相似。

import numpy as np
# 指定 start、stop、以及step。arange和range一样,是左闭右开的区间。
arr_uniform0 = np.arange(1,10,1)
# 也可以只传入一个参数,这种情况下默认start=0,step=1
arr_uniform1 = np.arange(10)
arr_uniform0
Out:
    array([1, 2, 3, 4, 5, 6, 7, 8, 9])
arr_uniform1
Out:
    array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

结果和我们想象的基本一致。

那么 arange() 和 range() 到底有什么区别呢?

答案是二者返回结果的类型不一样。一个是 numpy.ndarray ,一个是 range 。此外, arange() 能够接收浮点数,创建浮点数类型的数组,而 range() 只能接收整数创建整形序列。可以说 arange() 的应用范围更加广泛了。

arr_uniform2 = np.arange(1.2, 3.8, 0.3)
arr_uniform2
Out:
    array([1.2, 1.5, 1.8, 2.1, 2.4, 2.7, 3. , 3.3, 3.6])

这里先抛出一个问题,用 arange() 方法的局限性是什么?

在实际工程问题中,我们经常碰到步长为长尾小数甚至步长不清楚的场景。这个时候如果要套用 arange() 方法的话,就要求先精确计算出步长,才能输出数列。事实上也不用这么麻烦,Numpy 都帮你考虑好了。

  • linspace() 方法

linspace() 方法稍微复杂一些,它的函数调用参数如下:

np.linspace(start, stop[, num=50[, endpoint=True[, retstep=False[, dtype=None]]]]])
# start、stop参数,和arange()中一致;
# num为待创建的数组中的元素的个数,默认为50
# endpoint=True,则为左闭右闭区间,默认为True;endpoint=False,则为左闭右开区间
# retstep用来控制返回值的形式。默认为False,返回数组;若为True,则返回由数组和步长组成的元祖

简单看几个案例:

# 不设置endpoint,默认为Ture,结果为左闭右闭区间
arr_uniform3 = np.linspace(1,99, 11)
arr_uniform3
Out:
    array([ 1. , 10.8, 20.6, 30.4, 40.2, 50. , 59.8, 69.6, 79.4, 89.2, 99. ])
# retstep设置为True,分别返回数组和步长
arr_uniform4 = np.linspace(1,99, 11, retstep=True)
arr_uniform4
Out:
    (array([ 1. , 10.8, 20.6, 30.4, 40.2, 50. , 59.8, 69.6, 79.4, 89.2, 99. ]), 9.8)
# 设置endpoint为False,结果为左闭右开区间
arr_uniform5 = np.linspace(1,99, 11, endpoint=False)
arr_uniform5
Out:
    array([ 1.        ,  9.90909091, 18.81818182, 27.72727273, 36.63636364,
           45.54545455, 54.45454545, 63.36363636, 72.27272727, 81.18181818,
           90.09090909])

linspace() 方法最大的特点是可以直接定义数组的长度,这为我们调整数组的大小提供了方便。这里给大家介绍 reshape 方法:

arr_uniform6 = np.linspace(1,100, 20)
# 这里定义了一个长度为20的等差数组,然后通过reshape方法,调整数组的大小为5×4
arr_uniform6.reshape(5,4)
Out:
    array([[  1.        ,   6.21052632,  11.42105263,  16.63157895],
       [ 21.84210526,  27.05263158,  32.26315789,  37.47368421],
       [ 42.68421053,  47.89473684,  53.10526316,  58.31578947],
       [ 63.52631579,  68.73684211,  73.94736842,  79.15789474],
       [ 84.36842105,  89.57894737,  94.78947368, 100.        ]])

reshape 方法非常灵活地应用于调整数组的大小,但是不改变数组的长度。即长度 100 的数组,你可以非常方便地调整为 1×100 或者是 4×25 或者是 5×20。这点在将横向量调整为列向量的时候非常有用。

2. 创建等比数组

等比数列在计算中也有着广泛的应用,比如说计算利率的时候。这里我们介绍两种方法创建等比数据。

  • geomspace() 方法,创建指数等比数列

比如说我想创建从 2 到 16 的等比数列,我不知道具体的公比,但是我希望我的数列长度是 4个。那我可以这样:

# 起始项为2,结束项为16,数列的长度为4。这里要注意,默认是左闭右闭的数组
arr_geo0 = np.geomspace(2,16,4)
arr_geo0
Out: array([ 2.,  4.,  8., 16.])

geomspace() 方法非常简单,它的参数说明如下,以后用到,朋友们可以根据实际情况,自定义输入参数:

geomspace(start, stop, num=50, endpoint=True, dtype=None# start和stop,分别为区间的起始和终止值,为强制参数;
# num 为待生成等比数列的长度,指定后,程序会自动计算取等比数列的公比;
# endpoint默认为True,结果为左闭右必区间。否则为False,左闭右开区间;
  • logspace() 方法,创建对数等比数列

logspace() 方法和 geomspace() 类似,唯一不同的是,在定义区间的起始值和终止值的时候,是以指数的形式定义的,logspace() 用法如下:

logspace(start, stop, num=50, endpoint=True, base=10.0, dtype=None# start:区间起始值为base的start次方
# stop:区间终止值为base的stop次方(是否取得到,需要设定参数endpoint)
# num:为待生成等比数列的长度。按照对数,即start和stop值进行等分。默认值为50
# endpoint:若为True(默认),则可以取到区间终止值,即左闭右闭区间,规则同上

如果我们向生产和上面一样的等比数列,改如何写呢?

# 起始项为2^1,结束项为2^4,数列的长度为4。这里要注意,起始项是以base为底,start值为指数的幂。
# 另外,因为logspace的参数比较多,建议除了start和stop,其他的参数都以键值对的形式进行传参,避免发生错误
arr_geo1 = np.logspace(1, 4, num=4, base=2)
arr_geo1
Out: array([ 2.,  4.,  8., 16.])

上面就是创建等比数列的全部内容了。窥一斑而知全豹,经过 Numpy 的封装,确实为我们提供很大的便利。这也正是 Python 做数据分析的魅力所在。

3. 创建随机数数组

随机数在编程世界里有很多妙用,比如我们都玩过的消消乐游戏,消掉一块后,屏幕顶端会自动下落一部分随机色块;还有欢乐玩斗地主的时候,洗牌就是一个随机的过程。

但是有的时候我们对生成的随机数也有一定的要求,比如我们在消消乐游戏里面,各个色块出现的概率是不一样的,特别是在高难度的关卡里,程序似乎可以故意提高“游戏难度”。其实这里的随机数都是经过缜密计算、精心设计的,那下面我们就来看看,如何生成一些“高阶”的随机数。

  • 创建[0, 1)之间的均匀分布的随机数组
# 函数的输入为若干个整数,表示输出随机数的大小为d0×d1× ...×dn
# 如果没有参数输入,则返回一个float型的随机数
numpy.random.rand(d0, d1, ..., dn)
# 产生一个大小为3×2,符合0-1之间的均匀分布的数组
arr_rand0 = np.random.rand(3, 2)
arr_rand0
Out: 
    array([[0.07870998, 0.09327187],
           [0.49848953, 0.07535019],
           [0.64401283, 0.11176563]])
  • 创建 [low, high) 之间的均匀分布的随机数组
# uniform方法可以指定产生随机数的范围[low, high),size为数组的形状,输入格式为整形(一维)或者整形元祖
# 如果不指定size的话,则返回一个服从该分布的随机数
numpy.random.uniform(low=0.0, high=1.0, size=None)
# 产生一个大小为3×2,符合0-10之间的均匀分布的数组
arr_rand1 = np.random.uniform(1, 10, (3, 2))
arr_rand1
Out: 
    array([[6.72617294, 5.32504844],
           [7.6895909 , 6.97631457],
           [1.3057397 , 3.51288886]])
  • 创建服从标准正态分布的数组(均值为0,方差为1)
# 该方法和rand类似,函数的输入为若干个整数,表示输出随机数的大小为d0×d1× ...×dn
# 如果没有参数输入,则返回一个服从标准正态分布的float型随机数
numpy.random.randn(d0, d1, ..., dn)
# 产生一个大小为3×2,符合标准正态分布的数组
arr_rand2 = np.random.randn(3, 2)
arr_rand2
Out: 
    array([[-0.70354968, -0.85339511],
           [ 0.22804958,  0.28517509],
           [ 0.736904  , -2.98846222]])
  • 创建服从 μ=loc,σ=scale 的正态分布的数组
# loc:指定均值 μ; scale:指定标准差 σ
# size:输入格式为整形(一维)或者整形元祖,指定了数组的形状
numpy.random.normal(loc=0.0, scale=1.0, size=None)
# 产生一个大小为3×2,符合均值为5,标准差为10的正态分布的数组
arr_rand3 = np.random.normal(5, 10, (3, 2))
arr_rand3
Out:
    array([[ -7.77480714,  -2.68529581],
           [  4.40425363,  -8.39891281],
           [-13.08126657,  -9.74238828]])

截止到现在,我们都是在产生某一区间或者符合某一规律的随机浮点数,那我们能不能随机产生整数呢?显然是可以的。

  • 在指定区间 [low, high) 中离散均匀抽样的数组
# 函数返回区间[low, high)内的离散均匀抽样,dtype指定返回抽样数组的数据类型,默认为整形
# size:输入格式为整形(一维)或者整形元祖,指定了数组的形状
numpy.random.randint(low, high=None, size=None, dtype=np.int64)
# 在[1, 5)之间离散均匀抽样,数组形状为3行2列
arr_rand4 = np.random.randint(1, 5, (3, 2))
arr_rand4
Out:
    array([[4, 4],
           [3, 3],
           [4, 2]])

对于 np.random.randint(1, 5, (3, 2)) 的执行结果,可以这样去理解:假设现有编号分别为1、2、3、4的4个小球,我们每次有放回抽样,分别抽样6次,把每次抽得的小球编号组合并调整为3×2大小的数组。

numpy.random.randint 可以非常方便地实现在某一整数区间内进行有放回的抽样,那如果我希望对具体实物进行抽样,有没有更好的方法呢?我们继续看。

  • 对具体样本进行有放回或者无放回的抽样
# 从样本a中进行抽样,a可以为数组、列表或者整数,若为整数,表示[0,a)的离散抽样;
# replace为False,表示无放回抽样;replace为True,表示有放回抽样
# size为生成样本的大小
# p为给定数组中元素出现的概率
numpy.random.choice(a, size=None, replace=True, p=None)

我们看一个案例,小明同学在罚球线投篮命中的概率为0.65,总共投10次,我们看一下计算机的模拟结果:

# 因为理想情况下,每次投篮都不影响下一次的结果,所以把这个问题归结为有放回的抽样,一共进行10次
# shoot_lst用来存储投篮的结果
# 从["命中", "未命中"]中有放回抽样,其中命中的概率为0.65,共抽取10次,返回的格式为为numpy.ndarray
shoot_lst = np.random.choice(["命中", "未命中"], size=10, replace=True, p=[0.65, 0.35])
shoot_lst
Out: ['未命中', '命中', '未命中', '命中', '命中', '命中', '命中', '命中', '命中', '未命中']

从结果看,10次投篮,命中了3次。实际命中率在70%。当然了,如果计算机继续模拟,命中率会最终逼近65%。

4. 小试牛刀:采样的秘密

统计学是一门研究随机现象,以推断为特征的方法论科学,“由部分推及全体”的思想贯穿统计学的始终。

例如,有关部门每年都会发布在读大学生的身体素质报告,其中最基础的一项特征就是身高。那么这些身高是怎么来的呢?显然要获取全部在读大学生身高的成本是非常高的,所以为了降低成本,最简单的小窍门就是随机采样。那今天我们就来研究一下随机采样是如何能够反映样本的整体情况的。

通过查阅部分资料,我们知道大学生的身高都是服从某一正态分布规律的。我们假设大学生平均身高是175厘米,身高的标准差是10厘米,那我们通过 Numpy 可以生成10万大学生的身高样本。我们本次的研究就是基于这10万样本,通过不断地采样,观察随着采样样本数的增多,其均值是如何变化的。我们用 numpy.random.choice 函数来模拟采样的过程。(程序会用到简单的 Numpy 的运算和matplotlib 的绘图功能,朋友们可以先了解一下,详细细节会在本专栏的后续章节持续介绍。)

# 设置matplotlib图片样式在jupyter notebook中显示
%matplotlib inline
# 导包
import numpy as np
import matplotlib.pyplot as plt

# 生成10万大学生的身高样本
arr_height = np.random.normal(175, 10, size=100000)
# 进行第一次采样,采样的样本赋值给sample_height,存储格式为ndarray
sample_height = np.random.choice(arr_height, size=1, replace=True)
# average 用来存储每次采样后计算的平均身高
average = []
# 进行1000轮循环采样,因为每次仅采集1个样本,所以整个过程可以视为有放回抽样
n = 10000
for round in range(n):
    sample = np.random.choice(arr_height, size=1, replace=True)
    sample_height = np.append(sample_height, sample)
    average.append(np.average(sample_height))

# 进行绘图,具体过程在第四章详细说明
plt.figure(figsize=(8,6))
plt.plot(np.arange(n), average, alpha=0.6, color='blue')
plt.plot(np.arange(n), [175 for i in range(n)], alpha=0.6, color='red', linestyle='--')
plt.xlabel("Sample Rounds", fontsize=10)
plt.ylabel("Average Height", fontsize=10)
plt.show()

img

从图形可视化的效果看,在前 2000 次采样的过程中,采样样本的均值变化比较剧烈;但是随着样本数的增多,采样样本的均值越来越逼近175厘米。所以说,通过设置合理科学的采样方式,可以大大降低有关部门在日常统计工作的成本。

5. 总结

这一章节向朋友们解锁了创建数组的新玩法,包括如何灵活地创建各类等差、等比、随机数组,并且通过一个简单的demo,初步展示了Python在进行数据统计及可视化方面的灵活性。

需要注意的是,Numpy提供了更多更加高级的随机数方法,这点需要和Python自带的random方法区分开来。

当然了,特别需要提醒的是,我们再也不用为缺乏练手的数据发愁了,在以后的学习过程中,我们可以灵活地创建各类大型数组,来辅助我们操练语法。

到这里呢,如何创建数组对象就讲完了,下一章节我将给大家讲解数组的魔变身术。敬请关注下一小节: 《索引与切片,玩转数组之七十二变》。

猜你喜欢

转载自blog.csdn.net/qq_33254766/article/details/108362487