Numpy教程(1) 数组的介绍

numpy教程的第一部分涵盖了利用numpy多维数组进行数据操作与分析的核心方面。Numpy是python中用于科学计算与数据操作的最基础而又强力的包。

photo by joren frielink

1. Numpy介绍

如果你想在数据分析与机器学习方面进行工作,那么就肯定需要对numpy有一个强硬的理解。
因为其他数据分析的包( 像pandas)是基于numpy的,又或者是用于构建机器学习应用的scikit-learn包,也与numpy有很大的联系。
所以,numpy提供了什么?
从核心层面来讲,numpy提供了极好的ndarray objects,n维数组的缩写。
在一个ndarray objects(又称array)中,你可以存储相同数据类型的多个值,围绕着它,能使numpy更方便地进行数学与数据操作。
你可能会这么想:我能在python的list中存储数字跟其他类型对象,并且能通过列表推导式,for循环来运行计算与操作的排序,为什么我还需要一个numpy数组呢?
当然,有很多显著的优点能说明numpy比list更好用。为了理解这一部分,先让我们看看如何创建一个numpy array

2. 创建一个numpy数组

有很多方法创建一个numpy数组。但是最常用一个方式是从list或者一个类似list的对象中通过np.array方法创建过来。

# Create an 1d array from a list
import numpy as np
list1 = [0,1,2,3,4]
arr1d = np.array(list1)

# Print the array and its type
print(type(arr1d))
arr1d

#> <class 'numpy.ndarray'>
#> array([0, 1, 2, 3, 4])  

这就意味着,你可以在数组内的每个值上面应用一个函数方法操作,而不是整个数组对象。
让我们对每个列表中的数值加上2,最直观的方法这样的:

list1 + 2  # error

这个做法在列表上是不存在的,但你可以在多维数组上这么做:

# Add 2 to each element of arr1d
arr1d + 2

#> array([2, 3, 4, 5, 6])

另一个特点是,一旦一个数组被创建,那么你将无法增加它的大小,你只有重建一个数组。而列表就不一样了,你可以随意增加删除里面的值。
虽然如此,可数组有更多的优点,让我们往下看看。

你可以用过一个包含列表的列表创建一个类似于二维数组的矩阵(二维数组也可以称为数学中的矩阵)。

# Create a 2d array from a list of lists
list2 = [[0,1,2], [3,4,5], [6,7,8]]
arr2d = np.array(list2)
arr2d

#> array([[0, 1, 2],
#>        [3, 4, 5],
#>        [6, 7, 8]])

也可以通过设置dtype参数来设置数据类型。最常用的几个numpy数据类型:'float', 'int', 'bool', 'str' 和 'object'。为了控制内存分配你可以从‘float32’, ‘float64’, ‘int8’, ‘int16’ or ‘int32’中选择其中一个。

# Create a float 2d array
arr2d_f = np.array(list2, dtype='float')
arr2d_f

#> array([[ 0.,  1.,  2.],
#>        [ 3.,  4.,  5.],
#>        [ 6.,  7.,  8.]])

小数点是float数据类型的体现,你可以用astype方法将它转换成其他不同类型。

# Convert to 'int' datatype
arr2d_f.astype('int')

#> array([[0, 1, 2],
#>        [3, 4, 5],
#>        [6, 7, 8]])


# Convert to int then to str datatype
arr2d_f.astype('int').astype('str')

#> array([['0', '1', '2'],
#>        ['3', '4', '5'],
#>        ['6', '7', '8']],
#>       dtype='U21')

数组中的每个数据必须是同一个类型,这个有别于列表。这是另一个显著的差异。
但是如果你不确定你的数组要用什么类型的数据,或者说你想将数字与字符放在一个数组中,你可以将dtype设置为'object'

# Create a boolean array
arr2d_b = np.array([1, 0, 10], dtype='bool')
arr2d_b

#> array([ True, False,  True], dtype=bool)


# Create an object array to hold numbers as well as strings
arr1d_obj = np.array([1, 'a'], dtype='object')
arr1d_obj

#> array([1, 'a'], dtype=object)

最后说明下,你总是可以用tolist()将一个数组转换成一个python列表。

总结下数组与python列表的主要区别:

  1. 数组支持矢量运算,而列表不能
  2. 数组一旦创建就不能修改大小,只能重新创建或者覆盖一个已存在的
  3. 每个数组有且仅有一个数据类型,所有数据都是这一个类型
  4. 包含相同数量的数组与列表,数组所占空间更少点

3. 对数组查看以及重塑大小

让我们来看一下一个二维数组。既然同时从一个列表嵌套列表创建而来的,那么它是二维,有行有列,像一个矩阵。

如果从一个列表嵌套列表再嵌套列表,那么它就是三位数组,像一个立方体。

所以我们需要知道一些属性:

  • 一维二维还是更多 (ndim)
  • 每个维度中有多少项 (shape)
  • 它的数据类型是什么 (dtype)
  • 总共有多少项数据 (size)
  • 如何拿到前几项的数据值 (through indexing)

example:

# Create a 2d array with 3 rows and 4 columns
list2 = [[1, 2, 3, 4],[3, 4, 5, 6], [5, 6, 7, 8]]
arr2 = np.array(list2, dtype='float')
arr2

#> array([[ 1.,  2.,  3.,  4.],
#>        [ 3.,  4.,  5.,  6.],
#>        [ 5.,  6.,  7.,  8.]])



# shape
print('Shape: ', arr2.shape)

# dtype
print('Datatype: ', arr2.dtype)

# size
print('Size: ', arr2.size)

# ndim
print('Num Dimensions: ', arr2.ndim)

#> Shape:  (3, 4)
#> Datatype:  float64
#> Size:  12
#> Num Dimensions:  2

4. 提取一个数组中的具体项

arr2

#> array([[ 1.,  2.,  3.,  4.],
#>          [ 3.,  4.,  5.,  6.],
#>          [ 5.,  6.,  7.,  8.]])

你可以用索引从0开始拿数据,跟python列表一样。
但有些地方有不像列表,数组可以选择性在方括号中接收多个参数,参数数量与维度一致。

# Extract the first 2 rows and columns
arr2[:2, :2]
list2[:2, :2]  # error

#> array([[ 1.,  2.],
#>        [ 3.,  4.]])

另外,numpy数组支持布尔索引。布尔索引将会生成一个有True和False值填充且与原来大小一致的数组。

# Get the boolean output by applying the condition to each element.
b = arr2 > 4
b

#> array([[False, False, False, False],
#>        [False, False,  True,  True],
#>        [ True,  True,  True,  True]], dtype=bool)



arr2[b]
arr[b].ndim

#> array([ 5.,  6.,  5.,  6.,  7.,  8.])
#> 1

4.1 倒转行或者整个数组

倒转数组跟倒转列表的操作差不多,但是如果想要对数组完全的倒转你就需要对所有轴(维度)进行倒转。

# Reverse only the row positions
arr2[::-1, ]

#> array([[ 5.,  6.,  7.,  8.],
#>        [ 3.,  4.,  5.,  6.],
#>        [ 1.,  2.,  3.,  4.]])


# Reverse the row and column positions
arr2[::-1, ::-1]

#> array([[ 8.,  7.,  6.,  5.],
#>        [ 6.,  5.,  4.,  3.],
#>        [ 4.,  3.,  2.,  1.]])

4.2 代表数据缺失与无穷大

数据缺失可以用np.nan对象表示。而np.inf代表无穷大。让我们展示下。

# Insert a nan and an inf
arr2[1,1] = np.nan  # not a number
arr2[1,2] = np.inf  # infinite
arr2

#> array([[  1.,   2.,   3.,   4.],
#>        [  3.,  nan,  inf,   6.],
#>        [  5.,   6.,   7.,   8.]])


# Replace nan and inf with -1. Don't use arr2 == np.nan
missing_bool = np.isnan(arr2) | np.isinf(arr2)
arr2[missing_bool] = -1  
arr2

#> array([[ 1.,  2.,  3.,  4.],
#>        [ 3., -1., -1.,  6.],
#>        [ 5.,  6.,  7.,  8.]])

4.3 在多维数组计算平均值,最大最小值

多维数组对这些值有各自的计算方法。

# mean, max and min
print("Mean value is: ", arr2.mean())
print("Max value is: ", arr2.max())
print("Min value is: ", arr2.min())

#> Mean value is:  3.58333333333
#> Max value is:  8.0
#> Min value is:  -1.0

但是,如果你想逐行逐列计算最小值,那么就要用np.amin了。

# Row wise and column wise min
print("Column wise minimum: ", np.amin(arr2, axis=0))
print("Row wise minimum: ", np.amin(arr2, axis=1))

#> Column wise minimum:  [ 1. -1. -1.  4.]
#> Row wise minimum:  [ 1. -1.  5.]

注:这里的amin函数与min函数是同一个,你也可以用arr2.min(axis=0)来求得相同值,关于axis可以看这篇文章

逐行计算最小值很好用。但是如果你想做一些其他逐行的计算/函数那么你就要用到np.apply_over_axis,如下。

# Cumulative Sum(计算累加值)
np.cumsum(arr2)

#> array([  1.,   3.,   6.,  10.,  13.,  12.,  11.,  17.,  22.,  28.,  35., 43.])

5. 从一个已存在的数组创建新数组

如果你只是想分配数组中的一部分到另一个数组中,那么这个新创建的数组实际上在父数组的内存中。这就意味着,如果你想对新数组进行一些操作,那么它将影响到父数组。所以为了避免打扰到父数组,你需要使用copy()来进行复制。所有的numpy数组都来自这个方法。

# Assign portion of arr2 to arr2a. Doesn't really create a new array.
arr2a = arr2[:2,:2]  
arr2a[:1, :1] = 100  # 100 will reflect in arr2
arr2

#> array([[ 100.,    2.,    3.,    4.],
#>        [   3.,   -1.,   -1.,    6.],
#>        [   5.,    6.,    7.,    8.]])

# Copy portion of arr2 to arr2b
arr2b = arr2[:2, :2].copy()
arr2b[:1, :1] = 101  # 101 will not reflect in arr2
arr2

#> array([[ 100.,    2.,    3.,    4.],
#>        [   3.,   -1.,   -1.,    6.],
#>        [   5.,    6.,    7.,    8.]])

6. flatten() 和 ravel()的区别

有两种方式实现扁平(平铺),flatten()ravel()方法。两者的区别是:
使用ravel()创建新数组实际上是对父数组的引用。所以,新数组会影响到父数组,跟上面那个例子一样,但是在内存方面效率更高点,因为不创建一个副本。

# Flatten it to a 1d array
arr2.flatten()

#> array([ 100.,    2.,    3.,    4.,    3.,   -1.,   -1.,    6.,    5., 6.,    7.,    8.])


# Changing the flattened array does not change parent
b1 = arr2.flatten()  
b1[0] = 99  # changing b1 does not affect arr2
arr2

#> array([[ 100.,    2.,    3.,    4.],
#>        [   3.,   -1.,   -1.,    6.],
#>        [   5.,    6.,    7.,    8.]])


# Changing the raveled array changes the parent also.
b2 = arr2.ravel()  
b2[0] = 99  # changing b2 changes arr2 also
arr2

#> array([[ 99.,    2.,    3.,    4.],
#>        [   3.,   -1.,   -1.,    6.],
#>        [   5.,    6.,    7.,    8.]])

7. 使用numpy创建序列,重复以及随机数字

np.arange函数能很方便地创建定制数字序列作为一个多维数组

# Lower limit is 0 be default
print(np.arange(5))  

# 0 to 9
print(np.arange(0, 10))  

# 0 to 9 with step of 2
print(np.arange(0, 10, 2))  

# 10 to 1, decreasing order
print(np.arange(10, 0, -1))

#> [0 1 2 3 4]
#> [0 1 2 3 4 5 6 7 8 9]
#> [0 2 4 6 8]
#> [10  9  8  7  6  5  4  3  2  1]

你可以在np.arange中设置开始与结束位置。但是如果你专注于数组中的项目数量,则必须手动计算适当的步长(step)。
比如,你想创建一个正好在1到50之间的10个数字的数组,你能计算出什么是步长?然而我会选择使用np.linspace

# Start at 1 and end at 50
np.linspace(start=1, stop=50, num=10, dtype=int)

#> array([ 1,  6, 11, 17, 22, 28, 33, 39, 44, 50])

注意,因为我明确强制dtype为int,所以数字不会等间隔因为舍入了。

np.linspace类似,也有np.logspace以对数形式上升。在np.logspace中,给定的起始值实际上是base ^ start,以base ^ stop结束,默认基础(base)值为10。

# Limit the number of digits after the decimal to 2
np.set_printoptions(precision=2)  

# Start at 10^1 and end at 10^50
np.logspace(start=1, stop=50, num=10, base=10) 

#> array([  1.00e+01,   2.78e+06,   7.74e+11,   2.15e+17,   5.99e+22,
#>          1.67e+28,   4.64e+33,   1.29e+39,   3.59e+44,   1.00e+50])

使用np.zerosnp.ones函数可以创建所需形状的数组,其中所有项都是0或1。

np.zeros([2,2])
#> array([[ 0.,  0.],
#>        [ 0.,  0.]])

np.ones([2,2])
#> array([[ 1.,  1.],
#>        [ 1.,  1.]])

7.1 创建重复序列

np.tile将重复整个列表或数组n次。而np.repeat重复每一项n次。

a = [1,2,3] 

# Repeat whole of 'a' two times
print('Tile:   ', np.tile(a, 2))

# Repeat each element of 'a' two times
print('Repeat: ', np.repeat(a, 2))

#> Tile:    [1 2 3 1 2 3]
#> Repeat:  [1 1 2 2 3 3]

7.2 生成随机数

random模块提供了很好的函数来生成任意给定形状的随机数(以及统计分布)。

# Random numbers between [0,1) of shape 2,2
print(np.random.rand(2,2))

# Normal distribution with mean=0 and variance=1 of shape 2,2 
print(np.random.randn(2,2))

# Random integers between [0, 10) of shape 2,2
print(np.random.randint(0, 10, size=[2,2]))

# One random number between [0,1)
print(np.random.random())

# Random numbers between [0,1) of shape 2,2
print(np.random.random(size=[2,2]))

# Pick 10 items from a given list, with equal probability
print(np.random.choice(['a', 'e', 'i', 'o', 'u'], size=10))  

# Pick 10 items from a given list with a predefined probability 'p'
print(np.random.choice(['a', 'e', 'i', 'o', 'u'], size=10, p=[0.3, .1, 0.1, 0.4, 0.1]))  # picks more o's

#> [[ 0.84  0.7 ]
#>  [ 0.52  0.8 ]]

#> [[-0.06 -1.55]
#>  [ 0.47 -0.04]]

#> [[4 0]
#>  [8 7]]

#> 0.08737272424956832

#> [[ 0.45  0.78]
#>  [ 0.03  0.74]]

#> ['i' 'a' 'e' 'e' 'a' 'u' 'o' 'e' 'i' 'u']
#> ['o' 'a' 'e' 'a' 'a' 'o' 'o' 'o' 'a' 'o']

现在,每次运行上述任何功能时,都会得到一组不同的随机数

如果你想每次重复同一组随机数,你需要设置种子或随机状态。看到的可以是任何值。唯一的要求是你必须在每次要生成相同的一组随机数时将种子设置为相同的值。

一旦创建了np.random.RandomStatenp.random模块的所有功能就可用于创建的随机状态对象。

# Create the random state
rn = np.random.RandomState(100)

# Create random numbers between [0,1) of shape 2,2
print(rn.rand(2,2))

#> [[ 0.54  0.28]
#>  [ 0.42  0.84]]


# Set the random seed
np.random.seed(100)

# Create random numbers between [0,1) of shape 2,2
print(np.random.rand(2,2))

#> [[ 0.54  0.28]
#>  [ 0.42  0.84]]

7.3 获得独特的项目和数量

np.unique方法可用于获取唯一项目。如果您想要每个项目的重复计数,请将return_counts参数设置为True。

# Create random integers of size 10 between [0,10)
np.random.seed(100)
arr_rand = np.random.randint(0, 10, size=10)
print(arr_rand)

#> [8 8 3 7 7 0 4 2 5 2]


# Get the unique items and their counts
uniqs, counts = np.unique(arr_rand, return_counts=True)
print("Unique items : ", uniqs)
print("Counts       : ", counts)

#> Unique items :  [0 2 3 4 5 7 8]
#> Counts       :  [1 2 1 1 1 2 2]

8.0 总结

这完成了numpy系列的第一部分。下一个是用于数据分析的高级numpy,其中我将详细说明作为数据分析必不可少的工具包的功能。

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

推荐阅读更多精彩内容