第1-3章

数据分析分为以下几个大类:

  • 与外部世界交互
    阅读编写多种⽂件格式和数据商店;
  • 数据准备
    清洗、修改、结合、标准化、重塑、切⽚、切割、转换数 据,以进⾏分析;
  • 转换数据
    对旧的数据集进⾏数学和统计操作,⽣成新的数据集(例如,通过各组变量聚类成⼤的表);
  • 建模和计算
    将数据绑定统计模型、机器学习算法、或其他计算⼯具;
  • 展示
    创建交互式和静态的图表可视化和⽂本总结

模块命名惯例

1
2
3
4
5
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import statsmodels as sm

⾏话:

  • 数据规整(Munge/Munging/Wrangling)
    指的是将⾮结构化和(或)散乱数据处理为结构化或整洁形式的整个过程。
    Munge这个词跟Lunge押韵。
  • 伪码(Pseudocode)
    算法或过程的“代码式”描述,⽽这些代码本身并不是实际有效的源代码。

使用ipython

  1. 运行py文件:
    %run hello_world.py

  2. 补全方法:
    因为ipython本身就是增强交互版的shell,比起原生的shell,还可以使用tab自动补全方法:
    1557024995148
    这同样适用于模块和函数的参数:
    1557025068872
    1557025207336

    注意:默认情况下,tab补全会自动隐藏魔术⽅法和内部的“私有”⽅法和属性

  3. 使用?显示对象的信息:
    (前/后都可以:eg:?b,b?)
    1557025362978

    1557025534915

  4. 使用??显示函数源码:
    1557025613342

  5. %paste:直接运行剪贴板的代码:
    1557025761716
    %cpaste:粘贴任意多的代码再运⾏。

  6. 魔术命令:
    IPython中特殊的命令(Python中没有)被称作“魔术”命令。这些命令可以使普通任务更便捷,更容易控制IPython系统。
    1557026045397


python知识复习:

  1. Python解释器同⼀时间只能运⾏⼀个程序的⼀条语句。

  2. 强类型化语⾔:
    意味着每个对象都有明确的类型(或类),不会发默许转换:

    1
    2
    3
    4
    a = 4
    # js会默许a变成字符串类型,然后拼接成54
    # py中,a的类型已经确定,不会默许转化类型
    print('5' + a)

    判断强类型语言的标准:
    如果语言经常隐式地转换变量的类型,那这个语言就是弱类型语言,如果很少会这样做,那就是强类型语言。

    1
    2
    3
    4
    a = 1
    # py也有默许转换,但是只会发⽣在特定的情况下
    # 此时a会从int改为float
    print(1.0 + a)
  3. 静态语言和动态语言的区别:
    在编译时检查类型的语言是静态类型语言,在运行时检查类型的语言是动态类型语言。
    静态类型需要声明类型(有些现代语言使用类型推导避免部分类型声明)Fortran和Lisp是最早的两门语言,现在仍在使用,它们分别是静态类型语言和动态类型语言。

    所以python是动态强类型语言.

  4. 标量:
    标量类型,即标准库中内建的类型(None,str,bytes,float,bool,int)

  5. datatime:
    datetime.datetime是不可变类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    from datetime import datetime, date, time

    dt = datetime(2011,10,29,20,30,21)
    print(dt.day) # 29
    print(dt.minute) # 30

    print(dt.date()) # 2011-10-29
    print(dt.time()) # 20:30:21

    print(dt.strftime('%m/%d/%Y %H:%M')) # 10/29/2011 20:30
    # 转为datetime对象 2009-10-31 00:00:00
    print(datetime.strptime('20091031','%Y%m%d'))
    # 替换time字段
    dt.replace(minute=0,second=0)
  6. 两个datetime对象的差会产⽣⼀个datetime.timedelta类型:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from datetime import datetime, date, time

    dt1 = datetime(2011,10,29,20,30,21)
    dt2 = datetime(2011,11,15,22,30)

    delta = dt2 - dt1
    print(delta) # 17 days, 1:59:39
    print(type(delta)) # <class 'datetime.timedelta'>

    # 将timedelta添加到datetime,会产⽣⼀个新的偏移datetime:
    print(dt1 + delta) # 2011-11-15 22:30:00
  7. 加号运算符将元组串联起来:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    x = (1,2,3)
    y = (4,5,6)

    print(id(x)) # 2575049627832
    print(id(y)) # 2575049655352

    z = x + y
    print(z) # (1, 2, 3, 4, 5, 6)
    print(id(z)) # 2575048177896
  8. 乘法只是引用对象,并没有复制对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    x = [[1]]
    y = x * 4

    print(y)
    # [[1], [1], [1], [1]]

    # 乘法只是引用对象,并没有复制对象
    print(id(y[0])) # 1782212092424
    print(id(y[1])) # 1782212092424
    print(id(y[2])) # 1782212092424
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    x = (1,)
    y = x * 4

    print(y)
    # (1, 1, 1, 1)

    # 乘法只是引用对象,并没有复制对象
    print(id(y[0])) # 140720600772352
    print(id(y[1])) # 140720600772352
    print(id(y[2])) # 140720600772352
  9. 一般将将不需要的变量使⽤下划线:

    1
    2
    values = (1,2,3,4)
    a,b,*_ = values
  10. list也支持拆包:

    1
    2
    3
    values = [1,2,3,4]
    a,b,c,*_ = values
    print(a,b,c) # 1 2 3
  11. list就是数组.
    所以lsit.append比list.insert复杂度低的主要原因不是index的查找,而是因为对后续
    元素的引⽤必须在内部迁移,以便为新元素提供空间。

  12. 不要使用+来拓展list,应该使用extend
    通过加法将列表串联的计算量较⼤,因为要新建⼀个列表,并且要复制对象。

  13. 内建的二分查找:bisect模块:

    • bisect.bisect可以找到插⼊值的位置,
    • bisect.insort是向这个位置插⼊值
    1
    2
    3
    4
    5
    6
    7
    8
    import bisect

    c = [1,3,5,7,9]
    print(bisect.bisect(c,4)) # 2
    print(c) # [1, 3, 5, 7, 9]

    bisect.insort(c,6)
    print(c) # [1, 3, 5, 6, 7, 9]

    bisect模块不会检查列表是否已排好序因此,对未排序的列表使⽤bisect不会产⽣错误,但结果不⼀定正确。

  14. 一旦要同时迭代多个序列就要想到使用zip:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    seq1 = ['foo','bar','baz']
    seq2 = ['one','two','three']

    for i,(a,b) in enumerate(zip(seq1,seq2)):
    print('{0}: {1},{2}'.format(i,a,b))

    # 0:foo,one
    # 1:bar,two
    # 2:baz,three
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    x = [1,2,3,4]
    y = [1,2,3,4]
    z = [1,2,3,4]

    for each in iter(zip(x,y,z)):
    print(each)

    # (1, 1, 1)
    # (2, 2, 2)
    # (3, 3, 3)
    # (4, 4, 4)
  15. zip还可以用来解压缩:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    x = [1,2,3,4]
    y = [1,2,3,4]
    z = [1,2,3,4]

    a,b,c,d = zip(*(x,y,z))
    print(a) # (1, 1, 1)
    print(b) # (2, 2, 2)
    print(c) # (3, 3, 3)
    print(d) # (4, 4, 4)
  16. 使用zip将两个序列配对组合成字典:

    1
    2
    3
    4
    5
    seq1 = ['foo','bar','baz']
    seq2 = ['one','two','three']

    x = dict(zip(seq1,seq2))
    print(x) # {'foo': 'one', 'bar': 'two', 'baz': 'three'}
  17. 删除字典不仅可以使用pop,还可以使用del

    1
    2
    3
    4
    x = {'hyl':89,'dsz':100,'czj':50}

    del x['hyl']
    print(x) # {'dsz': 100, 'czj': 50}
  18. 虽然字典的键值对没有顺序,这两个⽅法可以⽤相同的顺序输出键和值:

    1
    2
    3
    4
    x = {'hyl':1,'dsz':2,'czj':3,'jzr':4}

    print(list(x.keys())) # ['hyl', 'dsz', 'czj', 'jzr']
    print(list(x.values())) # [1, 2, 3, 4]
  19. Python 字典 setdefault() 函数:获取,获取不到就设置为键.
    和get()方法类似, 如果键不存在于字典中,将会添加键并将值设为默认值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    x = {'hyl':1,'dsz':2,'czj':3,'jzr':4}

    a = x.setdefault('hyl',9)
    print(a,x)
    # 1 {'hyl': 1, 'dsz': 2, 'czj': 3, 'jzr': 4}

    b = x.setdefault('yyy',9)
    print(b,x)
    # 9 {'hyl': 1, 'dsz': 2, 'czj': 3, 'jzr': 4, 'yyy': 9}

    将单词按字母分类:

    1
    2
    3
    4
    5
    6
    7
    8
    x = ['apple','box','class','an']
    d = {}
    for each in x:
    letter = each[0]
    d.setdefault(letter,[]).append(each)

    print(d)
    # {'a': ['apple', 'an'], 'b': ['box'], 'c': ['class']}
  20. 集合的所有逻辑运算都有原地方法和非原地方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    s1 = set([1,2,3])
    s2 = set([2,3,4])

    # 非原地实现方法
    x = s1 | s2
    print(x) # {1, 2, 3, 4}
    # 原地实现方法
    s1 |= s2
    print(s1) # {1, 2, 3, 4}
  21. set某种意义和tuple是完全相反的:

    • tuple本身是不可变对象,但是tuple内部对象可以是可变的.
    • 虽然set本身是可变对象,但是内部元素必须是不可变对象:
      (因为内部元素必须进行哈希映射)
    1
    2
    3
    4
    5
    s1 = set([1,2,3,4])

    print(id(s1)) # 1642137660552
    s1.add(5)
    print(id(s1)) # 1642137660552
    1
    2
    # TypeError: unhashable type: 'list'
    s1 = set([1,2,3,4,[7,8,9]])
  22. 柯⾥化(currying):
    其实就是使用偏函数冻结一部分参数

    1
    2
    3
    4
    5
    6
    from functools import partial

    def func(x,y):
    return x + y

    func2 = partial(func,5)
  23. itertools模块:
    groupby分组。参数为序列和函数。函数返回相同值的序列为一组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import itertools

    first_letter = lambda x: x[0]
    names = ['Alan', 'Adam', 'Wes', 'Will', 'Albert', 'Steven']

    for letter, names in itertools.groupby(names, first_letter):
    print(letter, list(names)) # names is a generator

    # A ['Alan', 'Adam']
    # W ['Wes', 'Will']
    # A ['Albert']
    # S ['Steven']

第4章 NumPy基础:数组和⽮量计算

NumPy的部分功能如下:

  • ndarray,⼀个具有⽮量算术运算和复杂⼴播能⼒的快速且节省空间的多维数组。
  • ⽤于对整组数据进⾏快速运算的标准数学函数(⽆需编写循环)。
  • ⽤于读写磁盘数据的⼯具以及⽤于操作内存映射⽂件的⼯具。
  • 线性代数、随机数⽣成以及傅⾥叶变换功能。
  • ⽤于集成由C、C++、Fortran等语⾔编写的代码的A C API。

NumPy可以⾼效处理⼤数组的数据。这是因为:

  • NumPy是在⼀个连续的内存块中存储数据,独⽴于其他Python内置对象。NumPy的C语⾔编写的算法库可以操作内存,⽽不必进⾏类型检查或其它前期⼯作。⽐起Python的内置序列,NumPy数组使⽤的内存更少。
  • NumPy可以在整个数组上执⾏复杂的计算,⽽不需要Python的for循环。

4.1 NumPy的ndarray:⼀种多维数组对象

NumPy最重要的⼀个特点就是其N维数组对象(即ndarray).既然是一个数组对象,所以他也是一个容器,其语法跟标量元素之间的运算⼀样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import numpy as np

# 生成两个数组,每个数组有3个元素
data = np.random.randn(2,3)
print(data)
# [[-2.10200456 0.02878915 0.38511873]
# [ 0.55724521 -0.69371297 -0.717718 ]]

print(data*10)
# [[-21.0200456 0.28789147 3.85118735]
# [ 5.57245214 -6.93712972 -7.17717996]]

# 每个元素都与⾃身相加。
print(data + data)
# [[-4.20400912 0.05757829 0.77023747]
# [ 1.11449043 -1.38742594 -1.43543599]]

ndarray对象是⼀个通⽤的同构数据多维容器。
每个数组都有⼀个shape和⼀个dtype:

  • shape
    表示各维度⼤⼩的元组
  • dtype
    ⽤于说明数组数据类型的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np

data = np.random.randn(2,3)

print(data)
# [[-0.60307723 -0.80704376 1.3647226 ]
# [-0.16659969 0.63194806 0.97809585]]

print(data.shape)
# (2, 3) 表示二维数组,每个数组3个元素

print(data.dtype)
# float64 表示数组数据类型为float64

创建ndarray

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import numpy as np

data1 = [6,7.5,8,0,100,2]
data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]
data3 = [[1,2],[80,1.0,0]]

arr1 = np.array(data1)
arr2 = np.array(data2)
arr3 = np.array(data3)

print(arr1)
# [ 6. 7.5 8. 0. 100. 2. ]
# 其实就是[6.0 7.5 8.0 0.0 100.0 2.0]

print(arr2)
# [[1 2 3 4]
# [5 6 7 8]]

print(arr3)
# [list([1, 2]) list([80, 1.0, 0])]

因为data2是列表的列表,NumPy数组arr2的两个维度的shape
是从data2引⼊的。可以⽤属性ndim和shape验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import numpy as np

data1 = [6,7.5,8,0,100,2]
data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]
data3 = [[1,2],[80,1.0,0]]

arr1 = np.array(data1)
arr2 = np.array(data2)
arr3 = np.array(data3)

print(arr1.ndim,arr1.shape)
# 1 (6,)

# 等⻓列表组成的列表转为多维数组
print(arr2.ndim,arr2.shape)
# 2 (2, 4)

# 非等长
print(arr3.ndim,arr3.shape)
# 1 (2,)

所以就是说,ndarray.shape表示了这个多维数组的形状:

1
2
3
(2,3)表示2行3列
(10)表示1行10列
(4,3,2)表示三维数组,第一维4个元素,第二维3个元素,第三维2个元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import numpy as np

data1 = np.random.randn(1,2,3)
data2 = np.random.randn(2,2,3)
data3 = np.random.randn(3,2,3)

print(data1)
print('--------')
print(data2)
print('--------')
print(data3)

# [[[-0.8071717 0.10112021 1.21315047]
# [ 2.06134452 0.52341324 -0.9417632 ]]]
# --------
# [[[ 0.22009258 -1.38361836 0.99233159]
# [ 0.80876499 1.16215538 0.15391251]]

# [[-2.17604067 0.38863864 1.08298809]
# [-0.08271547 -1.49779443 1.56485888]]]
# --------
# [[[ 1.737971 1.46302365 -0.33018833]
# [ 0.92194819 0.56506825 0.71456561]]

# [[ 0.57141615 -0.44381919 -1.213822 ]
# [ 0.132801 -0.7962571 -0.54658224]]

# [[-0.72645018 -0.15283847 -1.83275693]
# [-0.86466428 -1.49513288 -0.21682686]]]

另外创建数组的函数:

  • zeros/ones:
    创建指定⻓度或形状的全0或全1数组。

  • empty:

    创建⼀个没有任何具体值的数组

  • arange:
    Python内置函数range的数组版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np

x = np.zeros(10)
print(x)
# [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]

y = np.zeros((3,6))
print(y)
# [[0. 0. 0. 0. 0. 0.]
# [0. 0. 0. 0. 0. 0.]
# [0. 0. 0. 0. 0. 0.]]

z = np.empty((2,3,2))
print(z)
# [[[7.33371104e+069 6.33874203e-307]
# [8.87280296e+132 1.80090755e+108]
# [2.00272360e-307 1.86919785e-306]]

# [[8.06624499e-308 1.24610383e-306]
# [1.37961709e-306 1.29057883e-306]
# [1.24610927e-306 6.23057688e-307]]]

np.empty返回的是⼀些未初始化的垃圾值。

1
2
3
4
5
6
7
8
9
import numpy as np

x = np.arange(10)
print(x)
# [0 1 2 3 4 5 6 7 8 9]

x = np.arange(2,10,3)
print(x)
# [2 5 8]

创建数组函数:

函数 说明
array 将输入数据(列表、元组、数组或其它序列类型)转换为 ndarray.要么推断出 dtype,要么特别指定dtype.默认直接复制输入数据
asarray 将输入转换为 ndarray,如果输入本身就是一个 narray就不进行复制
arange 类似于内置的range,但返回的是一个narray而不是列表
ones,ones_like 根据指定的形状和 dtype创建一个全1数组,one like以另一个数组为参数,并根据其形状和dype创建一个全1数组
zeros,zeros_like 类似于ones和ones like,只不过产生的是全0数组而已
empty,empty_like 创建新数组,只分配内存空间但不填充任何值
full,full_like 用 fill value中的所有值,根据指定的形状和dype创建一个数组,full_like使用另一个数组,用相同的形状和dtype创建
eye,identity 创建一个正方的NxN单位矩阵(对角线为1,其余为0)

array()创建是是对原始对象的一份copy创建的,而asarray是当输入是ndarry并不进行复制操作,如果改变创新的数组,则原始数组也会跟着改变。


ndarray的数据类型

dtype(数据类型)是⼀个特殊的对象,它含有ndarray将⼀块内存解释为特定数据类型所需的信息:
简单来说就是==数组元素的类型==.

1
2
3
4
5
6
7
import numpy as np

arr1 = np.array([1,2,3],dtype=np.float64)
arr2 = np.array([1,2,3],dtype=np.int32)

print(arr1.dtype) # float64
print(arr2.dtype) # int32

dtype命名规则:
⼀个类型名(如float或int),后⾯跟⼀个⽤于表示各元素位⻓的数字。
标准的双精度浮点值(即Python中的float对象)需要占⽤8字节(即64位)。因此,该类型在NumPy中就记作float64。

使用astype来转化数组的dtype

1
2
3
4
5
6
7
8
9
import numpy as np

arr1 = np.array([1,2,3])
print(arr1.dtype) # int32

# 将整形转化为浮点型
x = arr1.astype(np.float64)
print(x.dtype) # float64
print(x) # [1. 2. 3.]

注意,如果将浮点型转化为整形,那么就会截取小数部分:
不管小数部分是什么值,一律舍去

1
2
3
4
5
6
7
8
import numpy as np

arr1 = np.array([1.1,2.9,3.0,4.5])
print(arr1.dtype) # float64

x = arr1.astype(np.int32)
print(x.dtype) # int32
print(x) # [1 2 3 4]

字符串类型的数字一样可以转化为数字类型:

1
2
3
4
5
6
7
8
9
import numpy as np

# 字符串类型的数字.
# 对应的dtype是np.string_
numeric_strings = np.array(['1.25', '-9.6', '42'], dtype=np.string_)
# 偷懒写法.真正的完整写法为x = numeric_strings.astype(np.float64)
x = numeric_strings.astype(float)
print(x) # [ 1.25 -9.6 42. ]
print(x.dtype) # float64

注意转换的时候可能会发生截取

1
2
3
4
5
6
7
8
import numpy as np

numeric_strings = np.array(['1.2555555555555', '-9.611111111111111', '42.000000000005'], dtype=np.string_)
x = numeric_strings.astype(float)

# 发生了截取
print(x) # [ 1.25555556 -9.61111111 42. ]
print(x.dtype) # float64
1
2
3
4
5
6
7
import numpy as np

int_array = np.arange(10)
calibers = np.array([.22, .270, .357, .380, .44, .50], dtype=np.float64)

# 使用别人的dtype来修改自己的dtype
print(int_array.astype(calibers.dtype))

NumPy数组的运算

为什么要使用多维数组?
因为这样可以让你不用编写循环就可以对数据执行批量运算.

==运算时,把多维数组当成是矢量.==
⼤⼩相等的数组之间的任何算术运算都会将运算应⽤到元素级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import numpy as np

arr = np.array([[1.,2.,3.],[4.,5.,6.]])

print(arr)
# [[1. 2. 3.]
# [4. 5. 6.]]

print(arr * arr)
# [[ 1. 4. 9.]
# [16. 25. 36.]]

print(arr - arr)
# [[0. 0. 0.]
# [0. 0. 0.]]

print(1 / arr)
# [[1. 0.5 0.33333333]
# [0.25 0.2 0.16666667]]

print(arr ** .5)
# [[1. 1.41421356 1.73205081]
# [2. 2.23606798 2.44948974]]

大小相同的数组之间的⽐较会⽣成布尔值数组:

1
2
3
4
5
6
7
8
9
10
11
12
import numpy as np

arr1 = np.array([[1.,2.,3.],[4., 5. ,6. ]])
arr2 = np.array([[0.,4.,3.],[4.0,5.1,6.00]])

x = (arr1 >= arr2)
print(x)
# [[ True False True]
# [ True False True]]

print(x.dtype)
# bool

不同⼤⼩的数组之间的运算叫做⼴播(broadcasting)

基本的索引和切⽚

1
2
3
4
5
6
7
8
9
10
11
12
import numpy as np

arr = np.arange(10)
print(arr) # [0 1 2 3 4 5 6 7 8 9]

print(arr[2]) # 2
print(arr[1:3]) # [1 2]

# 注意这里和list不一样.
# list返回[ 0 99 3 4 5 6 7 8 9]
arr[1:3] = 99
print(arr) # [ 0 99 99 3 4 5 6 7 8 9]

当你将⼀个标量值赋值给⼀个切⽚时(如arr[5:8]=99),该值会⾃动⼴播到整个选区。

跟列表最重要的区别在于,==数组切片是原始数组的视图==。
这意味着数据不会被复制,==视图上的任何修改都会直接反映到源数组上==。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# arr_slice只是arr的视图而已.
# 也就是说arr_slice只是引用了arr
# 任何arr_slice的修改都会反映到arr上
import numpy as np

arr = np.arange(10)
print(arr) # [0 1 2 3 4 5 6 7 8 9]


arr[1:4] = 99
arr_slice = arr[1:4]
print(arr_slice) # [99 99 99]


print(arr) # [ 0 99 99 99 4 5 6 7 8 9]

# 将arr_slice的索引为1的元素改为12345
arr_slice[1] = 12345
print(arr) # [ 0 99 12345 99 4 5 6 7 8 9]

arr_slice[:] = 66
print(arr) # [ 0 66 66 66 4 5 6 7 8 9]
1
2
3
4
5
6
7
8
9
10
alist = [1,2,3,4,5,6]

# slice是alist复制一部分下来的,二者已经毫不相干了
slice = alist[1:3]
print(slice) # [2, 3]
print(alist) # [1, 2, 3, 4, 5, 6]

slice = 999
print(slice) # 999
print(alist) # [1, 2, 3, 4, 5, 6]

由于NumPy的设计目的是处理⼤数据,假如NumPy坚持要将数据复制来复制去的话会大量的性能和内存问题。

多维数组的索引问题

  • 切片索引:
    使用视图
  • 布尔索引:
    创建数据副本
  • 花式索引:
    1. ⼀次传⼊多个索引数组会返回的⼀个⼀维数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np

arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

print(arr2d[2]) # [7 8 9]


# 下面两种方法等价
print(arr2d[0][2]) # 3
print(arr2d[0,2]) # 3

print(arr2d[:2,1:])
# [[2 3]
# [5 6]]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import numpy as np

arr3d = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])
print(arr3d.shape)
# (2, 2, 3)

print(arr3d)
# [[[ 1 2 3]
# [ 4 5 6]]

# [[ 7 8 9]
# [10 11 12]]]

print(arr3d[0])
# [[1 2 3]
# [4 5 6]]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import numpy as np

arr3d = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])

print(arr3d)
print('******************1')

# 和list切片一样,这才是真正的复制
# 这样old_values和多维数组就没有关系了
old_values = arr3d[0].copy()
print(old_values)
print('******************2')


arr3d[0] = 42
print(arr3d)
print('******************3')


arr3d[0] = old_values
print(arr3d)

# [[[ 1 2 3]
# [ 4 5 6]]

# [[ 7 8 9]
# [10 11 12]]]
# ******************1
# [[1 2 3]
# [4 5 6]]
# ******************2
# [[[42 42 42]
# [42 42 42]]

# [[ 7 8 9]
# [10 11 12]]]
# ******************3
# [[[ 1 2 3]
# [ 4 5 6]]

# [[ 7 8 9]
# [10 11 12]]]

可以发现,使用索引就能降维(切片不能).
也就是说:arr[1,:2]arr[1:2,:2]是不一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np

arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

print(arr2d.shape) # (3, 3)

arr1 = arr2d[1,:2]
arr2 = arr2d[1:2,:2]

print(arr1) # [4 5]
print(arr2) # [[4 5]]

print(arr1.shape) # (2,)
print(arr2.shape) # (1, 2)

现在有7个名字,还有一个7行4列的数据,每个名字对应每一行,现在要找出名字为bob对应的行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import numpy as np

names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
data = np.random.randn(7, 4)
print(names)
print(data)
print('***************')

print(names=='Bob')
print('***************')

# 找出bob对应的行
print(data[names=='Bob'])
print('***************')


# ['Bob' 'Joe' 'Will' 'Bob' 'Will' 'Joe' 'Joe']

# [[-1.09713394 0.41574381 -1.33751931 -0.93130873]
# [ 1.12539937 -0.16825646 -1.13447574 0.52805166]
# [-0.98992142 -1.19969387 -0.53664124 -1.08782551]
# [-0.33742991 -0.34564667 -0.18650757 0.51191268]
# [-2.8074128 -1.60146652 -1.1361489 -1.54189387]
# [-0.88793971 1.28222486 -0.23209788 0.25965213]
# [-0.37233941 0.07983729 -0.008324 -0.31428477]]
# ***************
# [ True False False True False False False]
# ***************
# [[-1.09713394 0.41574381 -1.33751931 -0.93130873]
# [-0.33742991 -0.34564667 -0.18650757 0.51191268]]
# ***************

注意:布尔型数组的⻓度必须跟被索引的轴⻓度⼀致
(就是这里的7个名字对应7行4列数据)

注意data[names=='Bob',2:]的意义:
选取names==’Bob’的行,再选取2:的列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np

names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
data = np.random.randn(7, 4)

print(data[names=='Bob'])
# [[-0.17698359 0.13282278 0.73329761 0.31348624]
# [-0.70594974 -0.30617791 0.47489702 1.23911999]]


# 选取names=='Bob'的行,再选取2:的列
print(data[names=='Bob',2:])
# [[0.73329761 0.31348624]
# [0.47489702 1.23911999]]


print(data[names=='Bob',3])
# [0.31348624 1.23911999]

多维数组的否定:
可以使用!=,也可以使用~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import numpy as np

names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
data = np.random.randn(7, 4)


print(data[names!='Bob'])
# [[ 0.54165578 0.31375648 0.54207654 0.15667595]
# [ 0.64080727 -2.50505078 -0.14822336 -1.04459888]
# [ 0.03085972 1.05541171 0.19207644 -0.80538298]
# [ 0.7813859 1.34868491 -0.42149489 0.47952686]
# [ 2.23452939 -0.50605417 0.20597698 -0.87535366]]


print(data[~(names=='Bob')])
# [[ 0.54165578 0.31375648 0.54207654 0.15667595]
# [ 0.64080727 -2.50505078 -0.14822336 -1.04459888]
# [ 0.03085972 1.05541171 0.19207644 -0.80538298]
# [ 0.7813859 1.34868491 -0.42149489 0.47952686]
# [ 2.23452939 -0.50605417 0.20597698 -0.87535366]]

简单来说:

  • &:和
  • |:或
  • ~:非
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import numpy as np

names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
data = np.random.randn(7, 4)

x = (names=='Bob')
y = (names=='Joe')


print(data[~x])
# [[ 0.4073387 0.40540836 0.63610871 1.06568761]
# [-1.05935519 -1.40400291 0.37939374 -0.10043416]
# [ 1.24084139 0.46000896 0.69128399 -0.84889116]
# [-0.09564331 0.8738597 1.53244759 -0.25765163]
# [-0.69661272 0.0305966 -0.65082183 -0.30457331]]

print(data[x|y])
# [[ 0.57919847 -0.66925921 0.17013694 -1.15948502]
# [ 0.4073387 0.40540836 0.63610871 1.06568761]
# [ 1.3716326 1.12825945 -0.50435748 2.17636178]
# [-0.09564331 0.8738597 1.53244759 -0.25765163]
# [-0.69661272 0.0305966 -0.65082183 -0.30457331]]

print(data[x&y])
# []

注意:

  • 使用布尔型索引选取数组中的数据,会创建数据的副本
  • Python的and和or在布尔型数组中⽆效。要是⽤&与|

布尔型数组的使用:

1
2
3
4
5
6
7
8
9
10
11
12
# 将负值设置为0
import numpy as np

names = np.array(['Bob', 'Will', 'Bob'])
data = np.random.randn(3, 2)

data[data<0]=0

print(data)
# [[1.89720605 0. ]
# [1.00057664 0.46105366]
# [0. 0. ]]

深入理解广播:
简单来讲,就是所有运算操作,赋值操作,逻辑操作,比较操作等等都会扩散到整个选区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np

arr = np.empty((4,3))

for i in range(4):
# arr[i]表示一行
# 将i的赋值扩散到整个arr[i]选区
arr[i] = i

print(arr)
# [[0. 0. 0.]
# [1. 1. 1.]
# [2. 2. 2.]
# [3. 3. 3.]]
1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np

arr = np.empty((4,3))

for i in range(4):
for j in range(3):
arr[i,j] = i

print(arr)
# [[0. 0. 0.]
# [1. 1. 1.]
# [2. 2. 2.]
# [3. 3. 3.]]

上面两段代码效果是一样的.一个使用了一次循环,另一个使用了两次循环.

  • 代码1的i是对一整行操作.只不过是扩散到那一行的全部元素而已
  • 代码2的i是对一个元素的操作,对每个元素都进行了赋值.

reshape函数:
在不改变矩阵的数值的前提下修改矩阵的形状。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np

arr = np.array([[1,2],[3,4]])

print(arr)
# [[1 2]
# [3 4]]
print(arr.shape)
# (2, 2)

new1 = np.reshape(arr,(1,4))
print(new1)
# [[1 2 3 4]]
print(new1.shape)
# (1, 4)

new2 = np.reshape(arr,(4))
print(new2)
# [1 2 3 4]
print(new2.shape)
# (4,)

数组新的shape属性应该要与原来的配套,如果等于-1的话,那么Numpy会根据剩下的维度计算出数组的另外一个shape属性值。
(简单来说,-1代表我不知道要给行(或者列)设置为几,reshape函数会根据原矩阵的形状自动调整。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np

z = np.array([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]])

new1 = z.reshape(-1)
print(new1)
# [ 1 2 3 4 5 6 7 8 9 10 11 12]


new2 = z.reshape(1,-1)
print(new2)
# [[ 1 2 3 4 5 6 7 8 9 10 11 12]]


# 列数为6,行数未知,让numpy自己计算
new3 = z.reshape(-1,6)
print(new3)
# [[ 1 2 3 4 5 6]
# [ 7 8 9 10 11 12]]

既然有了reshape,那么构造多维数组就可以使用:

1
2
3
4
5
6
7
8
9
10
11
12
import numpy as np

arr = np.arange(1,33).reshape((8,4))
print(arr)
# [[ 1 2 3 4]
# [ 5 6 7 8]
# [ 9 10 11 12]
# [13 14 15 16]
# [17 18 19 20]
# [21 22 23 24]
# [25 26 27 28]
# [29 30 31 32]]

花式索引:
利⽤整数数组进⾏索引。

==花式索引跟切⽚不⼀样,它总是将数据复制到新数组中。==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import numpy as np

arr = np.empty((8,4))
for i in range(8):
arr[i] = i

print(arr)
# [[0. 0. 0. 0.]
# [1. 1. 1. 1.]
# [2. 2. 2. 2.]
# [3. 3. 3. 3.]
# [4. 4. 4. 4.]
# [5. 5. 5. 5.]
# [6. 6. 6. 6.]
# [7. 7. 7. 7.]]

print(arr[[4,3,0,6]])
# [[4. 4. 4. 4.]
# [3. 3. 3. 3.]
# [0. 0. 0. 0.]
# [6. 6. 6. 6.]]

print(arr[[-1,-3,-5,-8]])
# [[7. 7. 7. 7.]
# [5. 5. 5. 5.]
# [3. 3. 3. 3.]
# [0. 0. 0. 0.]]

注意arr[1,2]arr[[1,2]]的区别:

  • arr[1,2]表示选取第一维度中索引值为1的数据,选区第二维度中索引值为2的数据
  • arr[[1,2]]表示选取第一维度中索引值为1和2的数据

简单来说,arr[1,2]是多维度选取,arr[[1,2]]只选择了一个维度

==⼀次传⼊多个索引数组会有⼀点特别。它返回的是⼀个⼀维数组,其中的元素对应各个索引元组==:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import numpy as np

arr = np.arange(32).reshape((8,4))
print(arr)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]
# [12 13 14 15]
# [16 17 18 19]
# [20 21 22 23]
# [24 25 26 27]
# [28 29 30 31]]

print(arr[[1,5,7,2]])
# [[ 4 5 6 7]
# [20 21 22 23]
# [28 29 30 31]
# [ 8 9 10 11]]

print(arr[[1,5,7,2],[1,]])
# [ 5 21 29 9]

print(arr[[1,5,7,2],[0,3,1,2]])
# [ 4 23 29 10]

也就是说:
arr[[1,5,7,2],[0,3,1,2]]最终选出的是元素(1,0)、(5,3)、(7,1)和(2,2)。

所以,arr[[1,5,7,2],[1,]]最终选出的是元素(1,1)、(5,1)、(7,1)和(2,1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 花式索引跟切⽚不⼀样,它总是将数据复制到新数组中。
import numpy as np

z = np.arange(20).reshape((4,5))
print(z)
# [[ 0 1 2 3 4]
# [ 5 6 7 8 9]
# [10 11 12 13 14]
# [15 16 17 18 19]]

print(z[[1,2]])
# [[ 5 6 7 8 9]
# [10 11 12 13 14]]

# 花式索引是复制数据的
# 所以[1]会拿第二行数据
print(z[[1,2]] [1])
# [10 11 12 13 14]

# [[1,2],1]整体属于花式搜索
# 所以[,1]会拿第二列数据
print(z[[1,2],1])
# [ 6 11]

重复使用花式索引:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import numpy as np

arr = np.arange(32).reshape((8,4))
print(arr)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]
# [12 13 14 15]
# [16 17 18 19]
# [20 21 22 23]
# [24 25 26 27]
# [28 29 30 31]]

# 普通花式索引,返回1,5,7,2行
print(arr[[1,5,7,2]])
# [[ 4 5 6 7]
# [20 21 22 23]
# [28 29 30 31]
# [ 8 9 10 11]]


# ⼀次传⼊多个索引数组返回的是⼀个⼀维数组
print(arr[[1,5,7,2],[0,3,1,2]])
# [ 4 23 29 10]

# 花式索引返回数据后,使用[:]获取全部行(第一维数据)
print(arr[[1,5,7,2]] [:])
# [[ 4 5 6 7]
# [20 21 22 23]
# [28 29 30 31]
# [ 8 9 10 11]]

# 对于arr[[1,5,7,2]],先取全部行,然后取第2列
print(arr[[1,5,7,2]] [:,1])
# [ 5 21 29 9]

# 对于arr[[1,5,7,2]],获取0,3,1,2行
print(arr[[1,5,7,2]] [[0,3,1,2]])
# [[ 4 5 6 7]
# [ 8 9 10 11]
# [20 21 22 23]
# [28 29 30 31]]

# 对于arr[[1,5,7,2]],获取全部行,然后取0,3,1,2列
print(arr[[1,5,7,2]] [:,[0,3,1,2]])
# [[ 4 7 5 6]
# [20 23 21 22]
# [28 31 29 30]
# [ 8 11 9 10]]

数组转置和轴对换:

  • 数组转置:
    返回源数据的视图(不会进⾏任何复制操作)
  • 轴对换:

数组转置和内积:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import numpy as np

arr = np.arange(15).reshape((3,5))

print(arr)
# [[ 0 1 2 3 4]
# [ 5 6 7 8 9]
# [10 11 12 13 14]]

# 转置
print(arr.T)
# [[ 0 5 10]
# [ 1 6 11]
# [ 2 7 12]
# [ 3 8 13]
# [ 4 9 14]]

print(np.dot(arr.T,arr))
# [[125 140 155 170 185]
# [140 158 176 194 212]
# [155 176 197 218 239]
# [170 194 218 242 266]
# [185 212 239 266 293]]

内积:
结果数组中的每个元素都是:数组a的最后一维上的所有元素与数组b的倒数第二位上的所有元素的乘积和: **dot(a, b)[i,j,k,m] = sum(a[i,j,:] * b[k,:,m])**。

1
2
3
4
结果的第一行=arr.T的第一行分别乘以arr的第一列
125 = 0*0 + 5*5 + 10*10
140 = 0*1 + 5*6 + 10*11
155 = 0*2 + 5*7 + 10*12

高维数组的转置需要传递一个由轴编号组成的元组,才能进行转置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np

arr = np.arange(16).reshape((2, 2, 4))

print(arr)
# [[[ 0 1 2 3]
# [ 4 5 6 7]]

# [[ 8 9 10 11]
# [12 13 14 15]]]

print(arr.transpose((1, 0, 2)))
# [[[ 0 1 2 3]
# [ 8 9 10 11]]

# [[ 4 5 6 7]
# [12 13 14 15]]]

这⾥,第⼀个轴被换成了第⼆个,第⼆个轴被换成了第⼀个,最后⼀个轴不变。

所谓转置,不过就是进行轴对换而已.所以还有一个swapaxes(交换轴)方法,他需要接受一对轴编号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np

arr = np.arange(16).reshape((2, 2, 4))

print(arr)
# [[[ 0 1 2 3]
# [ 4 5 6 7]]

# [[ 8 9 10 11]
# [12 13 14 15]]]

print(arr.swapaxes(1, 2))
# [[[ 0 4]
# [ 1 5]
# [ 2 6]
# [ 3 7]]

# [[ 8 12]
# [ 9 13]
# [10 14]
# [11 15]]]

4.2 通⽤函数:快速的元素级数组函数

通⽤函数可以将其看做简单函数(接受⼀个或多个标量值,并
产⽣⼀个或多个标量值)的⽮量化包装器。

一元ufunc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np

arr = np.arange(10)
print(arr)
# [0 1 2 3 4 5 6 7 8 9]

print(np.sqrt(arr))
# [0. 1. 1.41421356 1.73205081 2. 2.23606798
# 2.44948974 2.64575131 2.82842712 3. ]

# 以自然常数e为底的指数函数
print(np.exp(arr))
# [1.00000000e+00 2.71828183e+00 7.38905610e+00 2.00855369e+01
# 5.45981500e+01 1.48413159e+02 4.03428793e+02 1.09663316e+03
# 2.98095799e+03 8.10308393e+03]

二元ufunc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import numpy as np

x = np.random.randn(8)
y = np.random.randn(8)
print(x)
# [ 0.35022148 0.45710385 1.20352152 -0.82159956 -0.21798144 -0.05278152
# 0.44903238 0.31557328]

print(y)
# [ 0.49612057 1.3556842 0.59048793 -0.63601289 -0.59404775 -0.38498382
# -0.51052469 -0.82994331]

# x和y中元素级别最⼤的元素。
print(np.maximum(x, y))
# [ 0.49612057 1.3556842 1.20352152 -0.63601289 -0.21798144 -0.05278152
# 0.44903238 0.31557328]

ufunc还可以返回多个数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np

arr = np.random.randn(7) * 5
print(arr)
# [ 1.43744836 0.22309718 -6.94064709 -0.69602561 4.5460506 -0.17366774
# 3.3419101 ]

# modf:divmod的⽮量化版本,
# 返回浮点数数组的⼩数和整数部分
remainder, whole_part = np.modf(arr)

print(remainder)
# [ 0.43744836 0.22309718 -0.94064709 -0.69602561 0.5460506 -0.17366774
# 0.3419101 ]

print(whole_part)
# [ 1. 0. -6. -0. 4. -0. 3.]

Ufuncs接受out选项参数,可以让它们在数组的原地进⾏操作:
(提供可选的out参数,并将结果放入给定的输出数组中。 out参数必须是 ndarray 并且具有相同数量的元素。 它可以具有不同的数据类型,在这种情况下将执行转换。)
eg:
ndarray.argmax([axis, out]) 返回给定轴的最大值索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import numpy as np

arr = np.random.randn(5) * 5
print(arr)
# [-1.51314487 1.56401655 4.05014912 -3.04612416 1.38332197]

# 调用np.sqrt(arr),返回新的数组
x = np.sqrt(arr)
print(x)
# [ nan 1.25060647 2.01249823 nan 1.17614708]

# 原数组不变
print(arr)
# [-1.51314487 1.56401655 4.05014912 -3.04612416 1.38332197]

# 将np.sqrt(arr)的结果放入arr中
np.sqrt(arr, arr)
# 原数组改变
print(arr)
# [ nan 1.25060647 2.01249823 nan 1.17614708]

4.3利⽤数组进⾏数据处理

⽤数组表达式代替循环的做法,通常被称为⽮量化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import numpy as np

points = np.arange(0, 5, 1)
print(points)
# [0 1 2 3 4]

xs, ys = np.meshgrid(points, points)
print(ys)
# [[0 0 0 0 0]
# [1 1 1 1 1]
# [2 2 2 2 2]
# [3 3 3 3 3]
# [4 4 4 4 4]]

print(xs)
# [[0 1 2 3 4]
# [0 1 2 3 4]
# [0 1 2 3 4]
# [0 1 2 3 4]
# [0 1 2 3 4]]

z = np.sqrt(xs ** 2 + ys ** 2)
print(z)
# [[0. 1. 2. 3. 4. ]
# [1. 1.41421356 2.23606798 3.16227766 4.12310563]
# [2. 2.23606798 2.82842712 3.60555128 4.47213595]
# [3. 3.16227766 3.60555128 4.24264069 5. ]
# [4. 4.12310563 4.47213595 5. 5.65685425]]

z矩阵上的每一个元素都是sqrt(x^2^+y^2^)的一个解:

1
2
1.41421356就是ys=1,xs=1的解
2.23606798就是ys=1,xs=2的解

简单来说,首先使用np.meshgrid来构造一个平面点网格(xs,ys)

20190506171815

这个网格被拆分成:xs和ys.
然后对于网格的每个点都执行np.sqrt(xs ** 2 + ys ** 2)
最后就得到一个解的矩阵


将条件逻辑表述为数组运算

numpy.where函数是三元表达式x if condition else y的⽮量化版
本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np

xarr = np.array([1.1, 1.2, 1.3, 1.4, 1.5])
yarr = np.array([2.1, 2.2, 2.3, 2.4, 2.5])
cond = np.array([True, False, True, True, False])

# 根据cond中的值选取xarr和yarr的值:
# 当cond中的值为True时,选取xarr的值,否则从yarr中选取。

# 普通python写法:
# result = [(x if c else y)
# for x, y, c in zip(xarr, yarr, cond)]


# numpy写法
result = np.where(cond, xarr, yarr)
print(result)
# [1.1 2.2 1.3 1.4 2.5]

np.where的第⼆个和第三个参数不必是数组,它们都可以是标量值。
np.where通常⽤于根据另⼀个数组⽽产⽣⼀个新的数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 将矩阵的负值改为0,正值不变
import numpy as np

arr = np.random.randn(4, 4)
print(arr)
# [[-0.78980305 0.52319264 -0.25377278 0.18849952]
# [ 1.03219533 -1.31224948 0.25765788 -0.13075013]
# [-0.10867121 -0.02572357 1.55566117 -0.32628939]
# [ 0.79880395 -0.30460497 0.0056869 -0.83580255]]

print(arr > 0)
# [[False True False True]
# [ True False True False]
# [False False True False]
# [ True False True False]]

print(np.where(arr > 0, arr, 0))
# [[0. 0.52319264 0. 0.18849952]
# [1.03219533 0. 0.25765788 0. ]
# [0. 0. 1.55566117 0. ]
# [0.79880395 0. 0.0056869 0. ]]

数学和统计⽅法

mean和sum这类的函数可以接受⼀个axis(轴)选项参数,⽤于计算该轴向上的统计值,最终结果是⼀个少⼀维的数组:

  • axis: 不设置值,对全部元素操作
  • axis = 0:压缩行,对各列求值,返回 1* n 矩阵
  • axis =1 :压缩列,对各行求值,返回 m *1 矩阵

(二维数组就两个轴,所以axis=0/1,三维数组的axis可以等于2,四维数组同理,最高为3)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import numpy as np

arr = np.arange(18).reshape((6,3))
print(arr)
# [[ 0 1 2]
# [ 3 4 5]
# [ 6 7 8]
# [ 9 10 11]
# [12 13 14]
# [15 16 17]]

# 取均值
print(arr.mean()) # 8.5
# np.mean(arr)等同于arr.mean()
print(np.mean(arr)) # 8.5


print(arr.sum()) # 153
print(arr.sum(axis=1)) # [ 3 12 21 30 39]
print(arr.sum(axis=0)) # [45 51 57]

print(arr.mean(axis=1)) # [ 1. 4. 7. 10. 13. 16.]
print(arr.mean(axis=0)) # [7.5 8.5 9.5]

理解什么是axis(轴):

轴即维度,轴的个数被称作rank
(这里的rank不是线性代数中的rank(秩),它指代的依旧是维数(number of dimensions))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import numpy as np

np.random.seed(123)
X = np.random.randint(0, 5, [3, 2, 2])
print(X.shape) # [3, 2, 2]

print(X)
# [[[5 2]
# [4 2]]

# [[1 3]
# [2 3]]

# [[1 1]
# [0 1]]]


# 7=5+1+1
# 6=2+3+1
print(X.sum(axis=0))
# [[7, 6],
# [6, 6]]

# 9=5+4
# 4=2+2
print(X.sum(axis=1))
# [[9, 4],
# [3, 6],
# [1, 2]]

# 7=5+2
# 6=4+2
print(X.sum(axis=2))
# [[7, 6],
# [4, 5],
# [2, 1]]

这里的X.shape为[3, 2, 2],每一个元素就对应一个轴:

  • axis=0对应3
  • axis=1对应2
  • axis=2对应2

img

对于多维数组,numpy对轴的编号是先行后列,由外向内!

所以:X.sum(axis=0)就是把第一个轴压缩掉.
简单来说,就是==把第一个轴同类的元素全部聚合成一个==

什么是第一个轴同类的元素?
就是==在同一个中括号里面的元素==


累加cumsum/累积cumprod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np

arr = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
print(arr)
# [[0 1 2]
# [3 4 5]
# [6 7 8]]

print(arr.cumsum(axis=0))
# [[ 0 1 2]
# [ 3 5 7]
# [ 9 12 15]]

print(arr.cumprod(axis=1))
# [[ 0 0 0]
# [ 3 12 60]
# [ 6 42 336]]

⽤于布尔型数组的⽅法:
在运算中,布尔值会被强制转换为1和0

1
2
3
4
5
import numpy as np

arr = np.random.randn(100)
# Number of positive values
print((arr > 0).sum()) # 49

any和all方法:
和python的any和all方法功能一样:

1
2
3
4
5
6
import numpy as np

bools = np.array([False, False, True, False])

print(bools.any()) # True
print(bools.all()) # False

这两个⽅法也能⽤于⾮布尔型数组,所有⾮0元素将会被当做True。


排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# sort()的参数axis默认为-1
# 即对最大轴进行排序
import numpy as np

arr = np.random.randn(4, 3)
print(arr)
# [[-0.1670582 -0.69387437 0.23216131]
# [-0.7415621 -0.95199615 0.21348631]
# [-0.62017205 -0.73696748 -1.43722725]
# [ 1.16732207 -1.86634413 0.31744036]]

# 和list的sort一样,无返回值(axis默认为-1)
print(arr.sort())
# None

print(arr)
# [[-0.69387437 -0.1670582 0.23216131]
# [-0.95199615 -0.7415621 0.21348631]
# [-1.43722725 -0.73696748 -0.62017205]
# [-1.86634413 0.31744036 1.16732207]]

将轴编号传给sort(),多维数组就可以在任何⼀个轴向上进⾏排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np

arr = np.array([[[1, 2, 3, 4],
[1, 3, 4, 5]],

[[2, 4, 7, 5],
[8, 4, 3, 5]],

[[2, 5, 7, 3],
[1, 5, 3, 7]]])

arr.sort(0)
print(arr)
# [[[1 2 3 3]
# [1 3 3 5]]

# [[2 4 7 4]
# [1 4 3 5]]

# [[2 5 7 5]
# [8 5 4 7]]]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np

arr = np.array([[[1, 2, 3, 4],
[1, 3, 4, 5]],

[[2, 4, 7, 5],
[8, 4, 3, 5]],

[[2, 5, 7, 3],
[1, 5, 3, 7]]])

arr.sort(1)
print(arr)
# [[[1 2 3 4]
# [1 3 4 5]]

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

# [[1 5 3 3]
# [2 5 7 7]]]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np

arr = np.array([[[1, 2, 3, 4],
[1, 3, 4, 5]],

[[2, 4, 7, 5],
[8, 4, 3, 5]],

[[2, 5, 7, 3],
[1, 5, 3, 7]]])

arr.sort(2)
print(arr)
# [[[1 2 3 4]
# [1 3 4 5]]

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

# [[2 3 5 7]
# [1 3 5 7]]]

上面是对三个轴的排序,解析如下:

1
2
3
4
5
6
7
8
[[[1, 2, 3, 4],
[1, 3, 4, 5]],

[[2, 4, 7, 5],
[8, 4, 3, 5]],

[[2, 5, 7, 3],
[1, 5, 3, 7]]]
  1. 第一个轴有三个元素:

    1
    2
    3
    4
    5
    6
    7
    8
     [[1, 2, 3, 4],
    [1, 3, 4, 5],

    [[2, 4, 7, 5],
    [8, 4, 3, 5]],

    [[2, 5, 7, 3],
    [1, 5, 3, 7]]

    那么这三个元素要排序

  2. 所以就是将(1,2,2),(2,4,5),(3,7,7),(3,4,3),(1,8,1),(3,4,5),(4,3,3),(5,5,7)共8组排好.

  3. 所以这八组就是(1,2,2),(2,4,5),(3,7,7),(3,3,5),(1,1,8),(3,4,5),(3,3,4),(5,5,7)

  4. 对应的矩阵就是:

    1
    2
    3
    4
    5
    6
    7
    8
    [[[1 2 3 3]
    [1 3 3 5]]

    [[2 4 7 4]
    [1 4 3 5]]

    [[2 5 7 5]
    [8 5 4 7]]]

总结:
对于轴的问题,紧抓两个概念:

  • 同类元素
  • ==多个同类元素最终要变成一个元素==

唯⼀化以及其它的集合逻辑

unique:python里的set的多维数组版

1
2
3
4
5
6
7
8
9
10
# 唯一化
import numpy as np

names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
print(np.unique(names))
# ['Bob' 'Joe' 'Will']

ints = np.array([3, 3, 3, 2, 2, 1, 1, 4, 4])
print(np.unique(ints))
# [1 2 3 4]

in1d:python里的in的多维数组版

1
2
3
4
5
import numpy as np

values = np.array([6, 0, 0, 3, 2, 5, 6])
print(np.in1d(values, [2, 3, 6]))
# [ True False False True True False True]

4.4⽤于数组的⽂件输⼊输出

NumPy能够读写磁盘上的⽂本数据或⼆进制数据。

np.save和np.load是读写磁盘数组数据的两个主要函数。
默认情况下,数组是以未压缩的原始⼆进制格式保存在扩展名为.npy的⽂件中的:

1
2
3
4
5
6
7
8
9
10
import numpy as np

arr = np.arange(10)
# 写入
# 如果⽂件路径末尾没有扩展名.npy
np.save('some_array', arr)

# 读取
arr = np.load('some_array.npy')
print(arr)

numpy支持将多个数组存储到同一文件中,使用关键字参数标志不同数组:

1
2
3
4
5
6
7
8
import numpy as np

arr1 = np.arange(20).reshape((4,5))
arr2 = np.arange(10)

# 将多个数组存储到同一文件中
# 使用关键字参数标志不同数组
np.savez('array_archive.npz', a=arr1, b=arr2)
1
2
3
4
5
6
7
8
9
# 读取
import numpy as np

# arch类似于一个字典
# 对各个数组进行延迟加载
arch = np.load('array_archive.npz')

print(arch['a'])
print(arch['b'])

4.5线性代数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 前面说过的矩阵内积
import numpy as np

x = np.array([[1., 2., 3.], [4., 5., 6.]])
y = np.array([[6., 23.], [-1, 7], [8, 9]])

print(x)
# [[1. 2. 3.]
# [4. 5. 6.]]

print(y)
# [[ 6. 23.]
# [-1. 7.]
# [ 8. 9.]]

print(x.dot(y))
# [[ 28. 64.]
# [ 67. 181.]]

# x.dot(y)等价于
print(np.dot(x,y))
# [[ 28. 64.]
# [ 67. 181.]]

@符:
进⾏矩阵乘法:

1
2
3
4
5
6
import numpy as np

x = np.array([[1., 2., 3.], [4., 5., 6.]])

print(x @ np.ones(3))
# [ 6. 15.]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import numpy as np
from numpy.linalg import inv, qr


X = np.random.randn(3, 3)
# X和它的转置X.T的点积
mat = X.T.dot(X)

# inv:方阵的逆
print(inv(mat))
# [[ 4.53196293 -1.22236952 -2.6589505 ]
# [-1.22236952 0.98196249 0.52036233]
# [-2.6589505 0.52036233 2.19274825]]

print(mat.dot(inv(mat)))
# [[1.00000000e+00 6.61023090e-17 2.25066451e-16]
# [3.71522059e-16 1.00000000e+00 4.00154639e-16]
# [1.85802669e-15 7.98912193e-17 1.00000000e+00]]

# QR分解
q, r = qr(mat)
print(r)
# [[-1.7824303 -1.5471616 -2.09238743]
# [ 0. -1.15383991 0.46558606]
# [ 0. 0. 0.28690018]]

4.6伪随机数⽣成

1
2
3
4
5
6
7
import numpy as np

# normal:得到⼀个标准正态分布数组
arr = np.random.normal(size=(2,3))
print(arr)
# [[ 0.01038301 1.22216728 -0.77523556]
# [-1.56709083 -0.07124537 -0.73354664]]

numpy的伪随机数一样是使用随机种子:

1
np.random.seed(1234)

4.7随机漫步:

python实现:

1
2
3
4
5
6
7
8
9
10
11
12
import numpy as np
import random

position = 0
walk = [position]
steps = 1000
for i in range(steps):
step = 1 if random.randint(0, 1) else -1
position += step
walk.append(position)

print(walk)

numpy实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import numpy as np

nsteps = 1000
draws = np.random.randint(0, 2, size=nsteps)
steps = np.where(draws > 0, 1, -1)
walk = steps.cumsum()

print(walk)

# 求最小最大值
print(walk.min())
print(walk.max())

# 求要几次随机漫步才能距离0点10步远
# argmax返回的是该布尔型数组第⼀个最⼤值的索引(True就是最⼤值)
print((np.abs(walk) >= 10).argmax())

产生多个随机漫步过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import numpy as np


nwalks = 5000
nsteps = 1000
# 只要给numpy.random的函数传⼊⼀个⼆元元组就可以产⽣⼀个⼆维数组,
# 然后我们就可以⼀次性计算5000个随机漫步过程
draws = np.random.randint(0, 2, size=(nwalks, nsteps))
steps = np.where(draws > 0, 1, -1)
# axis =1 :压缩列,对各行求值
walks = steps.cumsum(axis=1)

print(walks)
# [[ 1 2 3 ... 0 -1 -2]
# [ 1 0 -1 ... 54 53 54]
# [ -1 0 1 ... 10 9 10]
# ...
# [ -1 0 -1 ... -22 -23 -22]
# [ -1 0 1 ... 46 47 46]
# [ 1 2 3 ... -16 -17 -18]]

# 计算所有随机漫步过程的最⼤值和最⼩值:
print(walks.max())
print(walks.min())

计算30或-30的最⼩穿越时间。这⾥稍微复杂些,因为不是5000个过程都到达了30。我们可以⽤any⽅法来对此进⾏检查:

1
2
3
4
5
6
7
# 每个随机漫步是否抵达30
hits30 = (np.abs(walks) >= 30).any(axis=1)
print(hits30)
# [False True True ... False True True]

# 达到30或-30的随机漫步过程的数量(总共5000)
print(hits30.sum()) # 3434
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# cleanned_walks:所有到达30的随机漫步的完整数据
# 比起walks,cleanned_walks去除了未到达30的数据,并且将负数改为正数
cleanned_walks = np.abs(walks[hits30])
print(cleanned_walks)

# y:cleanned_walks每个元素都和30比较,
# 得到的第一个True就是第一次达到30的元素
y = (cleanned_walks >= 30)
print(y)
# [[False False False ... False False False]
# [False False False ... False False False]
# [False False False ... False False False]
# ...
# [False False False ... True True True]
# [False False False ... True True True]
# [False False False ... False False False]]

# crossing_times:每个随机漫步过程的第一次到达30的索引
crossing_times = y.argmax(axis=1)
print(crossing_times)

# 全部随机漫步第一次到达30的索引 的平均值
print(crossing_times.mean())
# 495.9609770628537

所以,完整的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import numpy as np


nwalks = 5000
nsteps = 1000
# 只要给numpy.random的函数传⼊⼀个⼆元元组就可以产⽣⼀个⼆维数组,
# 然后我们就可以⼀次性计算5000个随机漫步过程
draws = np.random.randint(0, 2, size=(nwalks, nsteps))
steps = np.where(draws > 0, 1, -1)
# walks:所有随机漫步的完整原始数据
walks = steps.cumsum(axis=1)

# hits30:每个随机漫步是否抵达30
hits30 = (np.abs(walks) >= 30).any(axis=1)
print(hits30)
# [False True True ... False True True]

# 达到30或-30的随机漫步过程的数量(总共5000)
print(hits30.sum()) # 3434


# cleanned_walks:所有到达30的随机漫步的完整数据
# 比起walks,cleanned_walks去除了未到达30的数据,并且将负数改为正数
cleanned_walks = np.abs(walks[hits30])
print(cleanned_walks)

# y:cleanned_walks每个元素都和30比较,
# 得到的第一个True就是第一次达到30的元素
y = (cleanned_walks >= 30)
print(y)
# [[False False False ... False False False]
# [False False False ... False False False]
# [False False False ... False False False]
# ...
# [False False False ... True True True]
# [False False False ... True True True]
# [False False False ... False False False]]

# crossing_times:每个随机漫步过程的第一次到达30的索引
crossing_times = y.argmax(axis=1)
print(crossing_times)

# 全部随机漫步第一次到达30的索引 的平均值
print(crossing_times.mean())
# 495.9609770628537