《Python一行流》学习笔记5——NumPy 库

1 Numpy 库安装        

        本节介绍的内容将会使用到Python数值计算库 NumPy 来处理和分析数值数据。Numpy 库是如今在数值计算和数据科学领域疯狂流行的重要基础库。

        Windows 10下安装 NumPy 库的语法如下——在终端中执行,其他操作系统的安装方法自行百度吧。

python -m pip install numpy

2 使用列表创建 Numpy 数组

        NumPy 库的核心是 NumPy 数组(array),其保存着你想要操作、分析和可视化的数据。很多高阶数据科学库,如 Pandas,都以显式或隐式的方式依赖 NumPy 数组作为其基础。

        NumPy 数组类似于 Python 的列表,但有一些额外的优势:

        1)NumPy 数组占用内存比较小,在大多数情况下速度会更块

        2)NumPy 数组在访问具有两个以上的轴(axis)的高维数据时会很方便;相比较之下,Python列表要访问和修改高维数据会比较麻烦

        3)NumPy 数组拥有一些更加强大的数组操作功能,如广播

        下面的事例演示了如何创建一维、二维和三维的 NumPy 数组,NumPy 数组与列表一样,除了数值类型的数据,也是可以包含其他类型的数据的。

import numpy as np
# 导入 NumPy 库,并将其命名为 np,这时该库实际的标准名称

# 从列表创建一个一维数组
a1 = np.array([1, 2, 3])
print(a1)
'''
[1 2 3]
'''

# 从一个二维列表中创建一个二维数组
a2 = np.array([[1, 2],
               [3, 4]])
print(a2)
'''
[[1 2]
 [3 4]]
'''

# 从一个三维的列表中创建一个三维数组
a3 = np.array([[[1, 2], [3, 4]],
               [[5, 6], [7, 8]]])
print(a3)
'''
[[[1 2]
  [3 4]]

 [[5 6]
  [7 8]]]
'''

3 NumPy 数组的基本算术运算

        NumPy 数组比 Python 列表更加强大。例如,你可以在两个 NumPy 数组上应用基本的算术运算符 +、-、*、/。数组算术运算会作用于相应元素上,通过吧两个数组 a 和 b 中对应的元素进行计算,来生成针对两个数组的计算结果。即,对应元素的算术运算会将数组 a 和 b 中处于相同位置的两个元素聚合在一起。

        下面的例子显示了如何对二维数组进行基本算术运算

import numpy as np

a = np.array([[1, 0, 0],
              [1, 1, 1],
              [2, 0, 0]])

b = np.array([[1, 1, 1],
              [1, 1, 2],
              [1, 1, 2]])

# NumPy 数组的运算为相同位置的元素进行运算
# 加法运算
print(a + b)
'''
[[2 1 1]
 [2 2 3]
 [3 1 2]]
'''

# 减法运算
print(a - b)
'''
[[ 0 -1 -1]
 [ 0  0 -1]
 [ 1 -1 -2]]
'''

# 乘法运算
print(a * b)
'''
[[1 0 0]
 [1 1 2]
 [2 0 0]]
'''

# 除法运算
print(a / b)
'''
[[1.  0.  0. ]
 [1.  1.  0.5]
 [2.  0.  0. ]]
'''

4 NumPy 数组的一些基本操作

        NumPy 提供了更丰富的操作数组的能力,如 np.max() 函数——计算NumPy数组中所有元素的最大值;np.min() ——计算所有元素的最小值;np.average() ——计算所有元素的平均值。

import numpy as np

a = np.array([[1, 0, 0],
              [1, 1, 1],
              [2, 0, 0]])

# 计算最大值
print(np.max(a))
# 2

# 计算最小值
print(np.min(a))
# 0

# 计算平均值
print(np.average(a))
# 0.6666666666666666

        现在给定一群人的年薪和税率,要求找到其中税后收入最高的收入

import numpy as np

# 数据
# 每个人3年的工作
alice = [99, 101, 103]
bob = [110, 108, 105]
tim = [90, 88, 85]
# 将上面的列表转换为 NumPy 数组
salaries = np.array([alice, bob, tim])
# 每个人3年对应的税率
taxation = np.array([[0.2, 0.25, 0.22],
                     [0.4, 0.5, 0.5],
                     [0.1, 0.2, 0.1]])

# 一行流
max_income = np.max(salaries - salaries * taxation)
# 使用 np.max() 函数来找出 NumPy 数组中的最大值
# 两个多为数组的对应元素相乘得到的结果称为哈达玛积(Hadamard product)
print(salaries - salaries * taxation)
'''
[[79.2  75.75 80.34]
 [66.   54.   52.5 ]
 [81.   70.4  76.5 ]]
'''

# 结果
print(max_income)
# 81.0

5 NumPy 数组的切片操作

        NumPy 中的切片于和索引与 Python 中的切片与索引类似:都是通过使用方括号语法 [] 指定索引或索引范围,可以访问一维数组中的若干元素。例如索引操作 x[3] 会返回 NumPy 数组 x 的第4个元素(索引 0 访问第一个元素)。

        也可以使用逗号分割的索引来访问多维数组,每个索引对应了一个不同维度。例如,y[0, 1, 2]会从第1个轴上的第1个索引、第2个轴上的第2个索引、第3个轴上的第3个索引去访问元素,但改语法对 Python 的多维列表是无效的。

        下面的例子是 NumPy 的一维切片操作

import numpy as np

# 从一维列表中创建一维 NumPy 数组
a = np.array([55, 56, 57, 58, 59, 60, 61])

print(a[:])
# [55 56 57 58 59 60 61]

print(a[2:])
# [57 58 59 60 61]

print(a[1:4])
# [56 57 58]

print(a[2:-2])
# [57 58 59]
# 此处的 -2 表示倒数第2个元素

print(a[::2])
# [55 57 59 61]

print(a[1::2])
# [56 58 60]

print(a[::-1])
# [61 60 59 58 57 56 55]
# 此处的 -1 表示倒序遍历

print(a[:1:-2])
# [61 59 57]

print(a[-1:1:-2])
# [61 59 57]

        下面的例子演示了如何使用 NumPy 切片,可以在每个轴上分别进行一维切片操作(用逗号分隔每一维)以选择该轴上某个范围内的元素

import numpy as np

a = np.array([[0, 1, 2, 3],
              [4, 5, 6, 7],
              [8, 9, 10, 11],
              [12, 13, 14, 15]])

# 选择第 3 列元素
print(a[:, 2])
# [ 2  6 10 14]

# 选择第 2 行元素
print(a[1, :])
# [4 5 6 7]

# 选择第 2 行,并间隔取值
print(a[1, ::2])
# [4 6]

# 选择所哟的行,但不带最后一列
print(a[:, :-1])
'''
[[ 0  1  2]
 [ 4  5  6]
 [ 8  9 10]
 [12 13 14]]
'''

# 选额除最后两行的有元素
print(a[:-2])
# 等价于 a[:-2, :]
'''
[[0 1 2 3]
 [4 5 6 7]]
'''

6 广播(broadcasting)

        广播描述了一种自动处理过程,它可以将两个 NumPy 数组变为相同的形状(shape),这样就可以对它们进行相应元素的算术运算。

        广播跟 NumPy 数组的形状属性密切相关,形状又与轴的概念密切相关,所以,接下来将初步了解一下轴、形状和广播的概念。

        每个数组都包含了若干个轴,每个轴对应了一个维度——ndim属性

import numpy as np

a = np.array([1, 2, 3, 4])
print(a.ndim)
# ndim 属性可以得到该 NumPy 数组有多少个轴,即维度是多少
# 1

b = np.array([[2, 1, 2],
              [3, 2, 3],
              [4, 3, 4]])
print(b.ndim)
# 2

c = np.array([[[1, 2, 3], [2, 3, 4], [3, 4, 5]],
              [[1, 2, 4], [2, 3, 5], [3, 4, 6]]])
print(c.ndim)
# 3

        每个数组都有一个相关联的形状(shape)属性:由每个轴的元素数构成的元组。对于一个二维数组,该元组有两个值,分别代表行数和列数。对各更高维的数组,元组里的第 i 个值指明了第 i 个轴上的元素数。因此,该元组的元素数量就是这个 NumPy 数组的维度。

        注意:如果增加一个数组的维度(比如从二维到三维数组),新的轴将变成第 0 个轴,而原来的第 i 个轴将变成第 i+1 个轴。

        下面的例子演示了如何使用 NumPy 数组的 shape 属性

import numpy as np

a = np.array([1, 2, 3, 4])
print(a.shape)
# (4,)

b = np.array([[2, 1, 2],
              [3, 2, 3],
              [4, 3, 4]])
print(b.shape)
# (3, 3)

c = np.array([[[1, 2, 3], [2, 3, 4], [3, 4, 5]],
              [[1, 2, 4], [2, 3, 5], [3, 4, 6]]])
print(c.shape)
# (2, 3, 3)

        当我们对不同形状的  NumPy 数组进行的对应元素运算时,广播会自动解决形状不匹配的问题。例如,当把惩罚运算符 * 应用于 NumPy 数组之间时,通常会执行对应元素的乘法。但如果左右数据不匹配,比如左边时一个 NumPy 数组,右边是一个浮点数,会怎么样呢?在这种情况下,NumPy 不会抛出错误,而是自动将右边的数据创建为一个新的数组,新数组的元素个数和维度与左边数组相同,并且包含了相同的浮点数。

         因此,广播是一种为了进行对应元素运算,而将低维数组转为高维数组的操作。

        另外,NumPy 数组是同质的——数组中所有元素必须是相同的类型。下面是数组可能的数据类型(非数组独有)

        1)bool:Python中的布类型(1字节)

        2)int:Python中的整数类型(4或8字节)

        3)float:Python中浮点类型(8字节)

        4)complex:Python中的复数类型(16字节)

        5)np.int8:一种整数类型(1字节)

        7)np.int16:一种整数类型(2字节)

        8)np.int32:一种整数类型(4字节)

        9)np.int64:一种整数类型(8字节)

        10)np.float16:一种浮点类型(2字节)

        11)np.float32:一种浮点类型(4字节)

        12)np.int64:一种浮点类型(8字节)

        下面的例子演示了如何创建不同类型的 NumPy 数组——dtype 参数

import numpy as np

a = np.array([1, 2, 3, 4], dtype=np.int16)
print(a)    # [1 2 3 4]
print(a.dtype)  # int16

b = np.array([1, 2, 3, 4], dtype=np.float64)
print(b)    # [1. 2. 3. 4.]
print(b.dtype)  # float64

        从上面的例子可以看到:①NumPy 数组的数据类型是可控的——可以自定义;②NumPy 数组是同质的——所有元素的类型是一样的

        现在你手上有各种职业的数据,要求每隔一年就将数据科学家(dataScientst)的工资提高10%

import numpy as np

# 数据
dataScientist = [130, 132, 137]
productManager = [127, 140, 145]
designer = [118, 118, 127]
softwareEngineer = [129, 131, 137]

# 将上面的 4 个列表合并为一个二维 NumPy 数组
employees = np.array([dataScientist,
                      productManager,
                      designer,
                      softwareEngineer])

# 原始数据
print(employees)
'''
[[130 132 137]
 [127 140 145]
 [118 118 127]
 [129 131 137]]
'''

# 一行流
employees[0, ::2] = employees[0, ::2] * 1.1
# 将 employees 的第 0 行的数据——dataScientist,每隔 1 个就 *1.1
# 这里还使用了广播,其会将 1.1 转换为 np.array([1.1, 1.1]) 再进行乘法运算

# 结果
print(employees)
'''
[[143 132 150]
 [127 140 145]
 [118 118 127]
 [129 131 137]]
'''

print(employees.dtype)
# int64
'''
从结果可以看出,即使是在执行浮点算术运算,得到的数据类型不是浮点型而是整型。
这是因为,再创建数组时,NumPy 意识到它只包含整数值,于是会假设它是一个整数
数组。对这个整数数组进行的任何操作都不会改变其数据类型,只会向下取整。
'''

 7 使用条件数组查询、过滤和广播检测异常值

        现在给定一个二维 NumPy 数组,其中行代表不同的城市,列代表不同时间的污染测量值,要求你找出再测量值平均污染水平以上的城市——至少出现一个峰值高于测量值的平均值。

        解决方案中的一个关键点是如何在 NumPy 数组中找出符合某个约束条件的所有元素——这是数据科学中经常会遇到的问题。

        所以,接下来先探讨一下怎样找到满足特定条件的数组元素。NumPy 提供了一个 nonzero() 函数,可以得到数组中非零元素的索引

import numpy as np

a = np.array([[1, 0, 0],
              [0, 2, 2],
              [3, 0, 0]])

# 结果
print(np.nonzero(a))
# (array([0, 1, 1, 2]), array([0, 1, 2, 0]))
'''
可以看出,得到的结果是两个 NumPy 数组组成的元组
第一个数组给出的是非零元素的行索引,第二个数组是列索引
'''

        现在,如何使用 nonzero() 函数在数组中找到满足特定条件的元素呢?你可以用到另一个特别重要的 NumPy 特性:利用广播进行布尔数组计算

import numpy as np

a = np.array([[1, 0, 0],
              [0, 2, 2],
              [3, 0, 0]])

print(a == 2)
'''
[[False False False]
 [False  True  True]
 [False False False]]
'''
'''
广播发生时,整数 2 被(虚拟第)拷贝到一个与数组 a 形状(shape)
相同的新数组中。然后,NumPy 把每个元素跟 2 进行对比,并返回一个
布尔数组作为结果
'''

        现在,你就可以把 nonzero() 函数跟布尔数组操作的特性结合起来,以找到满足特定条件的所有元素。在本例子中,你会根据数据集找出污染峰值超过平均值的城市

import numpy as np

a = np.array(
    [
        [42, 40, 41, 43, 44, 43],   # Hong Kong
        [30, 31, 29, 29, 29, 3],    # New York
        [8, 13, 31, 11, 11, 9],     # Berlin
        [11, 11, 12, 13, 11, 12],   # Montreal
    ])
# 数组 a 包含 4 行(每行代表一个城市)和 6 列(每列是一个测量值)

cities = np.array(['Hong Kong', 'New York', 'Berlin', 'Montreal'])
# 字符串数组 cities 包含 4 个城市名,其顺序与 数组 a 中出现的顺序相同

# 一行流
polluted = set(cities[np.nonzero(a > np.average(a))[0]])
# 这行程序的核心是布尔数组操作
print(a > np.average(a))
'''
[[ True  True  True  True  True  True]
 [ True  True  True  True  True False]
 [False False  True False False False]
 [False False False False False False]]
'''
'''
使用布尔表达式时,广播会让两个操作元素变为同样的形状。通过调用 np.average()
计算出 NumPy 数组中所有元素的平均值,然后这个表达式会对两个数组中每个对应元素
进行比较,得出一个布尔数组。如果某个位置得到测量值大于平均值,则布尔数组对应位
置的值就会是 True
通过生成这个布尔数组,可以准确第知道哪些元素高于平均值,哪些元素没有满足条件
在Python中,True 值使用整数 1 来表示的;False 用 0 来表示。事实上,True 和
False 类型是 bool,它是 int 的子类。因此,每个布尔值同时也是一个整数值。根据
这一点,就可以使用 nonzero() 来找出所有符合条件的行和列索引了
'''
print(np.nonzero(a > np.average(a)))
'''
(array([0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2]), 
array([0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 2]))

得到两个元组,第一个元组给出了非零元素的行索引,第二个给出了对应的列索引

因为只需找出那些高于平均值的城市名称,不需要其他信息,所以只需用到行索引—— [0] 的作用
'''
print(cities[np.nonzero(a > np.average(a))[0]])
'''
['Hong Kong' 'Hong Kong' 'Hong Kong' 'Hong Kong' 'Hong Kong' 'Hong Kong'
 'New York' 'New York' 'New York' 'New York' 'New York' 'Berlin']
'''
'''
从上面的结果可以看出,得出来的结果有很多的重复值,所以需要将其转换成集合(set())来去掉重复值
'''

# 结果
print(polluted)
# {np.str_('Berlin'), np.str_('New York'), np.str_('Hong Kong')}

8 使用布尔索引过滤二维数组

        给定一个二维数组,每行是一个 Instagram 用户的数据,第一列定义了其粉丝数,第二列订立了其名字,要求找出粉丝数超过 100 的用户的名字

        NumPy 数组通过附加功能,如多维切片和多维索引,大大丰富了基本的列表数据类型,下面的例子 NumPy 中的选择性(布尔)索引

import numpy as np

# 二维数据数组
a = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]])

# 二维布尔数组,可以把它看作索引数组
indices = np.array([[False, False, True],
                    [False, False, False],
                    [True, True, False]])

print(a[indices])
# [3 7 8]
'''
NumPy 的一个强大的特性是,可以使用布尔数组对数据数组进行细粒度访问。即,可以创建
这样一个新数组,这个数组只包含数据数组 a 的某些元素,这些元素在索引数组 indices
的对应位置上的值为 True。即,如果 indices[i, j] == True,则新数组将会包含元素
a[i, j];同理,如果 indices[i, j] == False,则新数组不会包含元素 a[i, j]
'''

        现在就可以找出粉丝数在 100 以上的用户名了。

import numpy as np

# 数据
user = np.array([[232, '@Tom'],
                 [133, '@Jack'],
                 [59, '@Jenny'],
                 [120, '@Ben'],
                 [111, '@Mary'],
                 [76, '@June']])

# print(user.dtype)
# # <U21    为了同质性,NumPy 自动生成了一个非数值数据类型

# 一行流
superUser = user[user[:, 0].astype(float) > 100, 1]
# 这行代码的核心是内部的表达式,其计算了一个布尔值,表示每个用户时候有超过100名粉丝
print(user[:, 0].astype(float) > 100)
# [True True False True True False]
'''
因为粉丝数存储在第一列中,所以需要使用切片取出这些数据,通过 user[:, 0]就可以得到
所有行的第一列数据
不过,由于这个数据数组包含了混合的数据类型(整数和字符串),NumPy 会自动为这个数组
分配一个非数值类型,原因是数值类型将无法使用其中的字符串,所以 NumPy 自动转换为可以
表示数组中所有数据的类型。而现在你想要对数组的第一列进行数值比较,以检查每个数值是否
大于 100, 所欲首先得用 .astype(float) 把切片生成的数组转换为浮点数,然后再进行比
较
这里,NumPy 再次使用广播把两个操作对象自动转换为相同的形状(shape),以逐个对元素进行
比较,生成一个布尔型的数组。True 表示用户的粉丝数大于 100;False 表示用户的粉丝数不
大于 100
得到这个布尔数组后,就可以通过布尔索引,从数组数组中选出粉丝数超过 100 的用户了,即对
应布尔数组中的值为 True 的行
因为你只对这些超级用户的名字感兴趣,因此选择这些行的第二列作为结果:
user[user[:, 0].astype(float) > 100, 1]
'''

# 结果
print(superUser)
# ['@Tom' '@Jack' '@Ben' '@Mary']

9 使用广播、切片赋值和重塑清洗固定步长的数组元素

        现实世界的数据很少是干净的,可能会包含错误或者缺失的值,原因有很多,比如数据损坏或者传感器故障。本节将学习怎样进行小型的清理任务,以消除错误的数据点。

9.1 切片赋值

        通过 NumPy 的切片赋值功能,可以在等式的左边指定你想要替换掉的值,在右边写上你想要替换为的值

import numpy as np

a = np.array([4] * 16)
print(a)
# [4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4]

a[1::] = [42] * 15
# 把除第一个元素外的值全部替换掉
print(a)
# [ 4 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42]

a[1:8:2] = 16
# 领用 Numpy 的广播功能,进行切片赋值
print(a)
# [ 4 16 42 16 42 16 42 16 42 42 42 42 42 42 42 42]

9.2 重塑(reshape)

        重塑——一个 NumPy 重要的函数,x.reshape((a,b)),它可以把 NumPy 数组 x 转换为一个具有 a 行和 b 列的新 NumPy 数组(即形状(shape)变为(a,b))

import numpy as np

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

# 将数组 a 重塑为 2 行,3 列
print(a.reshape((2, 3)))
'''
[[1 2 3]
 [4 5 6]]
'''

# 注意 a.reshape() 操作不会就地改变 a 的形状(shape)
print(a)
# [1 2 3 4 5 6]

'''
如果给定的列数不准确,也可以让 NumPy 自动完成计算列数的计算工作,
只要在形状中代表列数的参数赋值为 -1 即可
'''
print(a.reshape((2, -1)))
'''
[[1 2 3]
 [4 5 6]]
 
同理,如果行数不确定,也可以将其赋值为 -1,但此时列数必须是一个准确的值,
即同一时间最多只能有一个维度的参数赋值为 -1
'''

9.3 轴参数(axis argument)

import numpy as np

# 数据
solar_x = np.array(
    [[1, 2, 3],
     [4, 5, 6]]
)

# 沿轴 0 (行)的方向计算平均值,即将每列的元素压缩成一个,axis=0
print(np.average(solar_x, axis=0))
# [2.5 3.5 4.5]

9.4 项目

        给定一个温度值数据,要求把所有第 7 个温度值用过去 7 天的平均值(包括第 7 天)代替

import numpy as np

# 温度数据(Mo, Tu, We, Th, Fr, Sa, Su)
tmp = np.array([1, 2, 3, 4, 3, 4, 4,
                5, 3, 3, 4, 3, 4, 6,
                6, 5, 5, 5, 4, 5, 5])

# 一行流
tmp[6::7] = np.average(tmp.reshape((-1, 7)), axis=1)
'''
tmp[6::7] 表示从第6个元素(索引从0开始)开始,每个7个元素就进行替换

tmp.reshape((-1, 7)) 表示将一维数组 tmp 重塑(reshape)为具有 7 列
数据,行数由 NumPy 自动计算赋值的二维数组

np.average(tmp.reshape((-1, 7)), axis=1) 表示沿着轴 1——列——的方向
(把每行的数值压缩成一个),对重塑后的 tmp 数组计算平均值
'''

# 接轨
print(tmp)
# [1 2 3 4 3 4 3 5 3 3 4 3 4 4 6 5 5 5 4 5 5]
print(tmp.reshape((-1, 7)))
'''
[[1 2 3 4 3 4 3]
 [5 3 3 4 3 4 4]
 [6 5 5 5 4 5 5]]
'''

10 NumPy 中的排序:sort() 函数和 argsort() 函数

        在商业计算、操作系统中的进程调度(优先级队列)以及搜索算法等各种高级应用中,排序都处于核心的位置。幸运的是,NumPy 提供了各种排序算法,默认使用的是快速排序。

        很多时候,对未排序数值进行排序时,获取其索引也是很重要的。比如,未排序数组中的元素 1 的索引是 7,而排序后其变成了第一个元素。则它的索引 7 也会变成已排序数组的第一个元素。这就是 NumPy 的 argsort() 函数做的事情:它在排序后创建一个原数组的索引组成的数组。简单来说就是,这些索引的次序对原数组进行了排序,通过使用这个数组,可以重新构建排序后的数组与未排序的数组

        下面的例子演示了在 NumPy 中 sort() 和 argsort() 的使用方法

import numpy as np

a = np.array([10, 6, 8, 2, 5, 4, 9, 1])

print(np.sort(a))
# [ 1  2  4  5  6  8  9 10]
# 词数组为原数组排序后的结果

print(np.argsort(a))
# [7 3 5 4 1 2 6 0]
# 此数组为排序数组对应元素在未排序数组的索引

# 注意,np.sort() 操作不会就地改变原数组的顺序
print(a)
# [10  6  8  2  5  4  9  1]

        NumPy 的 sort() 函数,与 Python 原生的 sorted() 函数的不同之处在于,其可以对多维数组进行排序

        下面的例子演示了对一个二维数组进行排序的两种方式。这个数组有两条轴:轴0(所有行)和轴1(所有列)。你可以沿着轴0的方向排序,也被称为垂直排序;或者沿着轴1,即水平排序。一般来说,参数 axis 会定义沿什么方向进行 NumPy 的各种操作

import numpy as np

# 原始二维数组
a = np.array([[1, 6, 2],
              [5, 1, 1],
              [8, 0, 1]])

# 对同一列的元素进行排序
print(np.sort(a, axis=0))
'''
[[1 0 1]
 [5 1 1]
 [8 6 2]]
'''

# 对同一行的元素进行排序,参数 axis 省略时的默认操作
print(np.sort(a, axis=1))
'''
[[1 2 6]
 [1 1 5]
 [0 1 8]]
'''

        现在给出 7 名学生的成绩,要求找出分数最高的 3 名学生的名字

import numpy as np

# 7 名学生的分数个名字
scores = np.array([110, 125, 143, 104, 98, 141, 134])
students = np.array(['John', 'Bob', 'Alice', 'Joe', 'Jane', 'Frank', 'Carl'])

# 一行流:找出成绩最高的 3 名同学的名字
top_3 = students[np.argsort(scores)][:-4:-1]
'''
要找到成绩最高的三名学生的名字,不是简单地对分数进行排序就行了,而是要通过 argsort() 函
数,获取原始数组的索引在排序后的新位置所构成的数组
'''
# 下面是 argsort() 函数对分数进行排序后的结果
print(np.argsort(scores))
# [4 3 0 1 6 5 2]
'''
需要保留这些索引,因为要从students数组中找出对应的名字,就得知道前三名对应到原数组数据的
索引,在输出索引数组里,索引 4 排在首位,这是因为 Jane 的分数最低(98分)。
sort() 和 argsort() 都以升序的方式帕西,即从最低值到最高值
'''
# 现在有了排序的索引,只需要根据索引,从 students 数组中取出对用的学生的名字即可
print(students[np.argsort(scores)])
# ['Jane' 'Joe' 'John' 'Bob' 'Carl' 'Frank' 'Alice']
'''
因为只需要获取成绩最高的前三名学生的名字,所以只需要倒叙偏离后面三个数据即可:[:-4:-1]
'''

# 结果
print(top_3)
# ['Alice' 'Frank' 'Carl']

11 使用 lamdba 函数和布尔索引来过滤数组

        真实世界的数据是嘈杂的,这个鱼龙混杂的数据很可能会引导你做出错误的决定,因此有效地过滤数据对于真实世界至关重要。

        要在仅仅一行程序中编写函数,lambda 函数是一定会用到的。lambda  函数是可以在单行代码中定义的匿名函数,其基本语法如下

lambda 参数: 表达式

定义 lambda 函数时,定义一个以逗号分隔的参数列表作为输入,然后函数对表达式
部分进行运算并返回结果

        下面探索一下如何通过定义 lambda 函数来创建一个过滤函数,以解决问题。

        创建一个过滤函数,传入一个图书列表 x 和一个最低评分 y,返回一个评分高于最低评分的桥在畅销书列表,即满足 y' > y

import numpy as np

# 数据 (row = [title, rating])
books = np.array([['Coffee Break NumPy', 4.6],
                  ['Lord of the Rings', 5.0],
                  ['Harry Potter', 4.3],
                  ['Winnie-the-Pooh', 3.9],
                  ['The Clown of God', 2.2],
                  ['Coffee Break Python', 4.7]])

# 一行流:找出评分高于阈值的畅销书书的信息列表
predict_bestseller = lambda x, y: x[x[:, 1].astype(float) > y]
'''
使用 lambda 函数编定义了一个过滤函数
将图书评分数据集 x 和阈值 y 作为输入,返回评分高于阈值 y 的图书的信息:
    x[x[:, 1].astype(float) > y]
参数 x 假定是一个两列的数组
该 lambda 函数的工作步骤如下:
1)x[:, 1].astype(float)
    切割出数组 x 的第二列,集图书评分的数据,然后调用 astype(float)方
    法——同质性,把它转换为一个浮点型数组
2)x[:, 1].astype(float) > y
    然后,常见了一个布尔数组,如果行索引对应的图书评分大于 y,则相应的值为
    True。注意,浮点数 y 被隐式地广播到一个新的 NumPy 数组中,这样布尔运
    算符 > 两边的操作数都具有相同的形状。这时,就得到一个布尔数组,代表每本
    书是否可以被认为是畅销书:x[:, 1].astype(float) > y = [True True
    True False False True],即前三本和最后一本书为畅销书
3)把这被布尔数组作为原图书评分数组的索引数组,抽取出所有评分高于阈值的图书。
    更具体地说,就是使用布尔索引 x[[True True True False False True]]
    得到原数组的一个子数组,其中只包含四本书——对应 True 值的书
'''

# 结果
print(predict_bestseller(books, 3.9))
'''
[['Coffee Break NumPy' '4.6']
 ['Lord of the Rings' '5.0']
 ['Harry Potter' '4.3']
 ['Coffee Break Python' '4.7']]
'''

12 使用统计、数学和逻辑来创建高级数组过滤器

        本节将展示最基本的异常值检测算法:如果一个观测值与平均值的偏差超过标准差,则它就会被认为是异常值。

        要解决本节的异常检测问题,首先需要学习三个基本技能:理解均值和标准差、寻找绝对值,以及进行逻辑与(and)运算

12.1 理解均值和标准差

        这里做一个基本的假设,即所偶观察到的数据都围绕一个平均值呈正态分布的。在正态分布中,记为N(μ,σ^2),符号 μ 代表序列中所有数的平均值;符号 σ 代表标准差,用来衡量数据集偏离其平均值的离散程度。依照定义,如果数据是真正的正态分布,68.2% 的样本值将落入[ω1=μ-σ, ω2=μ+σ] 的标准偏差区间内。这为辨认异常值提供了一个标准:任何不在该范围内的数据都会被认为是异常值。

        在 NumPy 库中,用 np.random 模块提供的函数 normal(mean, deviation, shape) 就可以通过给定的平均值(mean)和标准差(deviation),以正态分布的随机抽样创建一个一维新 NumPy 数组。参数 shape 表示想要创建多少个数据点。

import numpy as np

sequence = np.random.normal(10.0, 1.0 ,50)
# 平均值为10.0;标准差为1.0;50个数据点
print(sequence)
'''
[ 8.33780068  9.20197539 10.43227035  9.45093537  7.33158169  9.9992125
 10.59335884 10.14525111  8.81469292  8.59726522 10.65247568  9.78666709
  9.02092756 10.37937993 11.80630566 10.564944   10.12688142  9.05656909
 11.43864369  9.67153038 10.89367971 10.52824136 10.49017322 11.53825318
 10.49411025 10.44940402  9.83045654  9.14850403 11.51384739  7.16237524
 11.78550241 10.39326186 10.55550266 10.37102978 11.36776808  9.19113709
  9.38920368  9.40327929  9.53035515  9.95613804 10.97705618 10.01130623
 11.2985778  10.96812958 11.52301699 10.06624148  9.88922418 10.95453869
 12.29167223 12.80130951]
'''

12.2 寻找绝对值

        为了检查每个值是否离平均值超过了标准差,需要把负数转为正数,这时因为只需要关心对对的距离,而不是正负。这个操作被称为取绝对值。NumPy 库的 np.abs() 函数会把一个 NumPy 数组中的负值转为正值

import numpy as np

a = np.array([1, -1, 2, -2])

print(np.abs(a))
# [1 1 2 2]

12.3 执行逻辑与(and)运算

        NumPy 库的 np.logical_and(a, b) 把数组 a 的第 i 个元素和数组 b 的第 i 个元素合并,得到一个布尔数组,第 i 位为 True 当且仅当 a[i] 和 b[i] 均为 True 时,否则为 False。通过这种方式,就可以把多个布尔数组用标准的逻辑操作符合并在一起。这个技巧的应用场景之一就是联合多个布尔数组过滤器。

import numpy as np

a = np.array([True, True, False, False])
b = np.array([False, True, False, True])

print(np.logical_and(a, b))
# [False  True False False]

        注意,也可以把两个布尔数组相乘,这跟 np.logical_and(a, b) 的计算是等价的。Python 把 True 值用整数 1(或其他非零整数)表示,False 用整数 0 表示。如果用 0 乘任何数,都只会得到0,即 False。这意味着,只有所有的操作数都为 True 时,结果才为  True。

12.4 项目:找出异常日期

        现在给定活跃用户数、跳出率和平均会话持续时间(以秒为单位)的数据,要求找出统计数据与统计平均值偏离超过一个标准差的异常日期。这里的跳出率是指只访问了一个网页就立刻离开网站的访问者百分比。高跳出率是一个不好的信号,它可能表示该网站很无聊或者没有帮助。

import numpy as np

# 数据,每行为一天(日活跃用户数、跳出数、平均会话时长)
a = np.array([[815, 70, 115],
              [767, 80, 50],
              [912, 74, 77],
              [554, 88, 70],
              [1008, 65, 128]])

# 计算平均值和标准差
mean, stdev = np.mean(a, axis=0), np.std(a, axis=0)
# mean: [811.2  75.4  88. ]
# stdev: [152.97764543   7.98999374  29.04479299]

# 一行流:找出与平均值偏离超过一个标准差的异常日期
outliers = ((np.abs(a[:, 0] - mean[0]) > stdev[0])
            * (np.abs(a[:, 1] - mean[1]) > stdev[1])
            * (np.abs(a[:, 2] - mean[2]) > stdev[2]))
'''
(np.abs(a[:, 0] - mean[0]) > stdev[0]) 表示对于多有行的日活跃用户数那一列,其值
是否时异常值——与平均值偏离超过一个标准差
(np.abs(a[:, 1] - mean[1]) > stdev[1]) 和 
(np.abs(a[:, 2] - mean[2]) > stdev[2])) 同理

只要当 3 个数据都为异常值时,这一天才会被认为是异常日期,这里使用 * 运算符来进行逻辑与运算
'''

# 结果
print(outliers)
# [False False False False  True]
print(a[outliers])
# [[1008   65  128]]
'''
从结果可以看出,只有最后一天是异常日期
'''

13 简单的关联分析:买了 X 的人也买了 Y

        推荐算法通常基于一种叫作关联分析的技术,该算法被应用各种购物网站、视频网站等。

        关联分析潜在的假设是,如果两个人在过去进行了类似的行为,则他们很可能在未来也会进行类似的行为。比如 X 和 Y 都买了同一本书,然后 X 又买了另一本新书,可以推断 Y 很可能也会去买这本新书。

        现在考虑这个问题:有多少比例的顾客同时购买了某两本电子书呢?基于这个数据,推荐系统就可以向那些原本打算只够买一本的顾客推荐“捆绑”购买这两本电子书了。

import numpy as np

# 数据:每行代表一个顾客的购物篮
# 行 = [course 1, course 2, ebook 1, ebook 2]
# 数值 1 代表已购买,0 代表未购买
basket = np.array([[0, 1, 1, 0],
                   [0, 0, 0, 1],
                   [1, 1, 0, 0],
                   [0, 1, 1, 1],
                   [1, 1, 1, 0],
                   [0, 1, 1, 0],
                   [1, 1, 0, 1],
                   [1, 1, 1, 1]])

# 一行流:找出同时购买两本电子书的顾客的比例
copurchases = np.sum(np.all(basket[:, 2:], axis=1)) / basket.shape[0]
'''
因为我们的目标是找出同时购买了两本电子书的顾客的比例,所以只需要列 2 和列 3 的数据
即可:basket[:, 2:]
np.all() 函数会检查 NumPy 数组中是否所有值都为 True。如果是则返回 True, 否则返
回 False。当指定 axis 参数时,函数将沿着指定的轴方向执行操作
'''
print(np.all(basket[:, 2:], axis=1))
# [False False False  True False False False  True]
# 只有第四个和最后一个顾客同时购买了两本电子书

print(basket.shape[0])
# 8
# 求出有多少行数据——shape[0]

'''
np.sum() 函数用来求和,由于Python 中 True 用整数 1 来表示,因此,求和得到的值即为
True 的个数
'''

# 结果
print(copurchases)
# 0.25

14 使用中间关联分析寻找最佳捆绑策略

        考虑上一节的例子:顾客会从四种产品张,独立第购买不同产品的组合。现在你的公司想要加售一些相关产品(向顾客提供额外的、通常是相关的产品),对于每种产品组合,需要计算它们被同一顾客购买的频率,并找出一起购买频率最高的两种产品。

import numpy as np

# 数据:每行代表一个顾客的购物篮
# 行 = [course 1, course 2, ebook 1, ebook 2]
# 数值 1 代表已购买,0 代表未购买
basket = np.array([[0, 1, 1, 0],
                   [0, 0, 0, 1],
                   [1, 1, 0, 0],
                   [0, 1, 1, 1],
                   [1, 1, 1, 0],
                   [0, 1, 1, 0],
                   [1, 1, 0, 1],
                   [1, 1, 1, 1]])

# 一行流:计算两个产品被共同购买的频次
copurchases = [(i, j, np.sum(basket[:, i] + basket[:, j] == 2))
               for i in range(4) for j in range(i+1, 4)]
'''
copurchases 为一个有元组构成的列表,每个元组描述了一种1产品组合以及该组合被一并购买
的频率。每个元组的前两个值代表了这两种产品的列索引,元组的第三个值是这对产品被一并购买
的次数,比如,元组(0, 1, 4)表示购买产品 0 的顾客也购买了产品 1 的情况发生了 4 次

为了实现这个目标,需要使用列表解析创建一个由元组组成的列表:
[(..., ..., ...) for ... in ... for ... in ...]
这个列表解析会从数组的四个索引中选择有两个构成的所有不同组合
'''
# 下面但看这个一行流外层部分的输出
print([(i, j) for i in range(4) for j in range(i+1, 4)])
# [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
'''
可以看出,列表中由 6 个元组,每个都是由两个列索引构成额不同组合
现在可以深入讨酒改哦元组的第三个元素了——这两个产品 i 和 j 一并被购买的次数:
np.sum(basket[:, i] + basket[:, j] == 2)
使用切片从原 NumPy 数列中提取出 i 和 j 两列,然后把它们的对应元素相加,对得到的
结果数组,再逐个元素检查是否为 2,即两个产品是否被一并购买(均为 1)。检查结果为
一个布尔数组,对每个顾客而言,如果同时购买了这两个产品,结果就为 True
'''
# 把所有的计算结果元素保存到列表 copurchases 中
print(copurchases)
'''
[(0, 1, np.int64(4)), 
(0, 2, np.int64(2)), 
(0, 3, np.int64(2)), 
(1, 2, np.int64(5)), 
(1, 3, np.int64(3)), 
(2, 3, np.int64(2))]
'''

# 结果:找出被共同购买频次最高的两个产品
print(max(copurchases, key=lambda x: x[2]))
# (1, 2, np.int64(5))
'''
使用 max() 函数来查找 copurchases 列表中最长购买的组合。定义了一个 key 函数,
它接受一个元组作呕为参数,并返回该元组的第三个值,然后通过 max() 函数从列表中找
出最大值。
结果的 (1, 2, np.int64(5)) 表示,第二个产品和第三个产品被一并购买的频次最多,
为 5 次
'''

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值