Numpy 预览
完整内容可移步至官方指南:Numpy and Scipy Documentation — Numpy and Scipy documentation,这里仅介绍基本功能。
Numpy 是于 2005 年创建并开源的库,旨在提供比传统 Python 列表快 50 倍的数组对象。为了保证更高的性能,Numpy 有近 35% 的部分是由 C 语言实现的。见:GitHub - numpy/numpy: The fundamental package for scientific computing with Python.
Numpy 常用于科学计算,因此在本章默认数组的元素全都是不可变数值,且仅讨论一维数组和二维数组。
数组与矩阵
使用 Numpy 可以创建出数组 ( array )。它可以被看作是原 Python 列表类型的内存优化版本 ( 通过 dtype
指定元素类型以从内存中分配更加紧凑的空间 ),因此兼容原列表的各种操作。
import numpy as np
arr1d = np.array([1, 2, 3, 4], dtype="int")
# 支持列表推导式
print(*[x*2 for x in arr1d])
arr2d = np.array(
[[1, 2, 3],
[4, 5, 6],
[7, 8, 9]], dtype="int"
)
# 支持 (高维的) 切片
sub = arr2d[0:2, 0:2]
"""
[[1,2],
[4,5]]
"""
print(*sub)
其中,二维数组可被视为矩阵 ( Matrix ) 或列向量 ( Vector )。可以通过调用数组的 shape
属性确定维度。比如:
print(arr1d.shape) # (4,) -> 长度为 4 的一维数组
print(sub.shape) # (2, 2) -> 2×2 的方阵
使用 reshape()
方法可以将一维数组堆叠为二维数组。-1
参数有特殊的语义,表示 "任意长度",实际取决于原数组的形状。比如对一个二维数组做如下操作:
reshape(-1)
表示展平成任意长度的一维数组。reshape(-1, 1)
表示变成任意行数,但列数为 1 的列向量,是二维数组。reshape(1, -1)
表示变成行数为 1,但列数任意的行向量,是二维数组。"""
np.arange(n) 相当于 range(n) 的 numpy 版本。
matrix:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
"""
matrix = np.arange(12).reshape(3, 4)
print(matrix)
# [ 0 1 2 3 4 5 6 7 8 9 10 11]
vect_1d = matrix.reshape(-1)
print(vect_1d)
# [[ 0 1 2 3 4 5 6 7 8 9 10 11]]
vect_v = matrix.reshape(1,-1)
print(vect_v)
"""
[[ 0]
[ 1]
:
:
[11]]
"""
vect_h = matrix.reshape(-1,1)
print(vect_h)
对于一维数组向二维数组的堆叠,可以通过 order
参数指定填充顺序。选 "C"
进行横向次序填充 ( 默认的 ) 或者 "F"
纵向次序填充,比如:
"""
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
"""
m1 = np.arange(12).reshape([3,4],order="C")
print(m1)
"""
[[ 0 3 6 9]
[ 1 4 7 10]
[ 2 5 8 11]]
"""
m2 = np.arange(12).reshape([3,4],order="F")
print(m2)
通过向 np.where()
函数传入搜索条件,可以得到满足条件的元素下标。比如:
v1 = np.array([1, 2, 3, 3, 6, 3])
# 谓词 'v1 == 3' 中的 'v1' 表示 v1 数组中的每一个元素。
indexes = np.where(v1 == 3)
# 表示 [2, 3, 5] 下标出现了值为 3 的元素。
print(indexes)
数组拼接
Numpy 支持对数组进行拼接。对于一维数组的拼接,称之 堆叠 ( stack )。堆叠可以分为水平堆叠 ( hstack()
函数 ) 和纵向堆叠 ( stack()
函数 )。水平堆叠的结果仍然是一维数组,而纵向堆叠的结果将是矩阵。
v1 = np.array([1, 2, 3])
v2 = np.array([4, 5, 6])
v3 = np.hstack([v1,v2]) # [1, 2, 3, 4, 5, 6]
print(v3)
"""
[[1 2 3]
[4 5 6]]
"""
v4 = np.stack([v1,v2])
print(v4)
而 np.concatenate()
函数支持矩阵拼接,类似地,这里使用 axis
标识了拼接方向。在不指定参数的情况下默认 axis=1
。
m1 = np.array([
[1, 2],
[3, 4]
])
m2 = np.array([
[5, 6],
[7, 8]
])
"""
列数不变,对行追加 (纵向拼接)
[[1 2]
[3 4]
[5 6]
[7 8]]
"""
m3 = np.concatenate([m1, m2], axis=0)
print(m3)
"""
行数不变,对列追加 (横向拼接)
[[1 2 5 6]
[3 4 7 8]]
"""
m4 = np.concatenate([m1, m2], axis=1)
print(m4)
线性代数
由于二维数组可被视作矩阵或者向量,因此可以延伸出线性代数的相关计算。比如,通过 matrix.T
即可实现转置操作:
matrix = np.arange(12).reshape(3, 4)
"""
[[ 0 4 8]
[ 1 5 9]
[ 2 6 10]
[ 3 7 11]]
"""
print(matrix.T)
Numpy 还提供四个函数用于生成单位矩阵,全 0 矩阵,全 1 矩阵,以及随机数矩阵。见:
n, m = 3, 4
E = np.eye(n) # 生成 m 阶单位矩阵
I = np.zeros([m, n]) # 生成 m × n 维全 0 矩阵
K = np.ones([m, n]) # 生成 m × n 维全 1 矩阵
H = np.empty([m, n]) # 生成 m × n 随机数矩阵
np.linalg.det()
函数可以展开矩阵行列式并计算求值,而 np.linalg.inv()
函数可求矩阵的逆 ( 底层是求解 Ax = E 的解 x,因此存在精度问题 )。比如:
A = np.array([
[1,2],
[3,4]
],dtype=int)
det = int(np.linalg.det(A))
print(det) # 1*4 - 2*3
A_inverse = np.linalg.inv(A)
print(A_inverse)
现在假设有另一个矩阵 B。通过 np.matmul()
函数或者 @
运算符 ( 运算符重载 ) 可以计算矩阵的乘积 ( 不满足交换律 ):
B = np.array([
[1,3],
[2,4]
], dtype=int)
"""
[[1*1 + 2*2, 1*3 + 2*4],
[3*1 + 4*2, 3*3 + 4*4]]
"""
print(np.matmul(A, B))
print(A @ B)
特殊地,假设有两个一维数组 a 和 b,此时的 @
将视作向量内积 ( 满足交换律 ),结果是一个数值。比如:
a = np.array([1, 2, 3], dtype=int)
b = np.array([4, 5, 6], dtype=int)
c = np.arrat([1, 2], dtype=int)
print(np.matmul(a,b))
通过 np.multiply()
函数或者 *
运算符可以计算矩阵与标量的积 ( 满足交换律 ):
"""
[[1*3, 2*3],
[3*3, 4*3]]
"""
print(np.multiply(A,3))
print(A * 3)
以上方法可以使用一个 np.dot()
函数概括。根据不同的输入,它将表示不同的行为:
A * n
。A @ B
。@
。见:numpy中dot()、outer()、multiply()以及matmul()的区别 - 简书 (jianshu.com)
# np.array_equal 用于比较两个数组元素是否按位相同。
assert np.array_equal(np.dot(a, b), a @ b)
assert np.array_equal(np.dot(A, B), A @ B)
assert np.array_equal(np.dot(A, c), A @ c)
assert np.array_equal(np.dot(A, 3), A * 3)
print("test passed")
为了加以区分,二维矩阵的运算更推荐使用 np.matmul()
或者 @
。
Pandas 预览
如果你很熟悉 SQL 操作,那么 Pandas 上手起来会非常容易。完整内容见官方指南:User Guide — pandas 1.4.3 documentation (pydata.org),这里仅介绍基础功能。
我们主要使用 Pandas 库处理 表结构 的数据,它们被抽象为了 Dataframe
类型。首先演示基本的 IO 操作:先将 Python 原生的数据结构转换为 Dataframe
,然后将它输出到 music.csv
文件,程序演示如下:
import pandas as pd
# 在 Pandas 中,表数据的单个列被称之 Series.
artist = ["Billie Holiday", "Jimi Hendrix", "Miles Davis", "SIA"]
genre = ["Jazz", "Rock", "Jazz", "Pop"]
listeners = [1_300_000, 2_700_000, 1_500_000, 2_000_000]
plays = [27_000_000, 70_000_000, 48_000_000, 74_000_000]
dict_ = {"artist": artist,
"genre": genre,
"listeners": listeners,
"plays": plays
}
# dict 的 kw 对已经隐含了列名信息,因此这里不需要显式指定 columns。
df = pd.DataFrame(dict_)
# 这里不需要额外生成索引下标序列,因此做以下设置: index=False。
df.to_csv(path_or_buf="music.csv",index=False)
pd.DataFrame
可以将 Python 原生的 dict
字典结构转化为表。其中 key
表示了列名,而 value
表示了该列的数据。另一种更加自然的想法是:以行的形式组织 DataFrame
,这里将每一行抽象成了元组。
table = [
("Billie Holiday", "Jazz", 1_300_000, 27_000_000),
("Jimi Hendrix", "Rock", 2_700_000, 70_000_000),
("Miles Davis", "Jazz", 1_500_000, 48_000_000),
("SIA", "Pop", 2_000_000, 74_000_000)
]
df = pd.DataFrame(data=table, columns=["artist", "genre", "listeners", "plays"])
df.to_csv(path_or_buf="music.csv", index=False)
生成的表结构如下:
artist | genre | listener | plays |
---|---|---|---|
Billie Holiday | Jazz | 1300000 | 27000000 |
Jimi Hendrix | Rock | 2700000 | 70000000 |
Miles Davis | Jazz | 1500000 | 48000000 |
SIA | Pop | 2000000 | 74000000 |
下一步,试着 pd.read_csv(path)
重新将它以 Dataframe
的形式读取进内存。在默认情况下,Pandas 将读取到的第一行视作表头 Header 。
df = pd.read_csv("music.csv")
"""
artist genre listeners plays
0 Billie Holiday Jazz 1300000 27000000
1 Jimi Hendrix Rock 2700000 70000000
2 Miles Davis Jazz 1500000 48000000
3 SIA Pop 2000000 74000000
"""
print(df)
数据提取
下面从最基本的操作开始说起。若要提取出 df
的某一列数据,可以直接使用重载的 []
运算符 ( 见前一章的运算符重载 ) 进行指定,支持传入单个列名,或者是由多个列名组成的列表。下面演示了提取单个列和多个列的情形:
df["artist"] # 提取一个列
df[["artist","plays"]] # 提取多个列
特地强调,如果仅提取一个列,得到的将是
Series
类型;如果提取多个列,得到的将是Dataframe
类型。
如果要从 df
中提取出第 n
行,则使用 df.loc[n]
实现。事实上,这里的 n
代表索引,在默认情况下是 0
起始的数据下标。比如提取第一行的行号:
# 提取下标 0 行的 row
# 0 Billie Holiday Jazz 1300000 27000000
row = df.loc[0]
# 从 row 中再提取 artist 列
# Billie Holiday
print(row["artist"])
Pandas 提供了查看数据的前 n
行或最后 n
行的函数:
n = 2
print(df.head(n)) # 查看前 n 行数据
print(df.tail(n)) # 查看后 n 行数据
过滤 ( WHERE 谓词 ) 是最基本的表操作。比如,筛选出 plays
数据高于 5000 万的数据:
top_df = df[df["plays"] > 50_000_000]
print(top_df)
另一种直观的方式是调用 query()
方法并直接传递谓词语句,字符串内容遵守 Python 语法。比如:
top_df = df.query("plays >= 50_000_000 and listeners >= 2_500_000")
print(top_df)
假定要从 genre
列中归纳出音乐人的歌曲类型,则可以使用 unique()
方法进行去重。比如:
col = df["genre"].unique()
# ['Jazz' 'Rock' 'Pop']
# col.tolist()
print(col)
排列 ( SORT BY 谓词 ) 也是常见的表操作之一。比如,这里按照 plays
列的数据以降序 Desc
排列:
sorted_df = df.sort_values("plays",ascending=False)
"""
artist genre listeners plays
3 SIA Pop 2000000 74000000
1 Jimi Hendrix Rock 2700000 70000000
2 Miles Davis Jazz 1500000 48000000
0 Billie Holiday Jazz 1300000 27000000
"""
print(sorted_df)
聚合操作
可以参考这篇优质文章:Pandas教程 | 超好用的Groupby用法详解 - 知乎 (zhihu.com)
Pandas 就像 SQL 那样提供聚合操作。比如我们期望按照 genre
字段对表进行分组,并统计每个组的行数。它可以被表达为:
"""SQL:
select genre, count(*)
from df
group by genre
"""
print(df.groupby("genre")["artist"].count())
除了 count()
计数方法之外,其它的聚合方法还包括:mean()
均值,sum()
求和,median()
中位数,min()
最小值,max()
最大值,var()
方差,std()
标准差。一种更通用的聚合方法为 agg()
,它允许接收 **kwargs
来对不同列进行不同的聚合操作,key
为列名,value
为聚合函数名。比如:
report_df = df.groupby("genre").agg({
"artist": "count",
"listeners": "mean",
"plays": "max"
})
"""
artist listeners plays
genre
Jazz 2 1400000.0 48000000
Pop 1 2000000.0 74000000
Rock 1 2700000.0 70000000
"""
print(report_df)
转换操作
Pandas 提供的 Dataframe
是 可变 数据,这意味着我们可以对原数据表进行改动。比如,根据 listensers
和 plays
计算每个音乐人的影响力 score
,并将其作为新的数据列附着在原数据表上:
df = pd.read_csv("music.csv")
"""
artist genre listeners plays score
0 Billie Holiday Jazz 1300000 27000000 0.605
1 Jimi Hendrix Rock 2700000 70000000 1.535
2 Miles Davis Jazz 1500000 48000000 1.035
3 SIA Pop 2000000 74000000 1.580
"""
# 假定 score = 5 * listeners_count + 2 * plays
df["score"] = (df["listeners"] * 5 + df["plays"] * 2) / 10**8
print(df)
map()
函数提供了以 lambda 表达式对 单列数据 ( 即 Series
类型 ) 进行变换的途径。比如:
"""
artist genre listeners plays
0 Billie Holiday Jazz 1300000 50mio-
1 Jimi Hendrix Rock 2700000 50mio+
2 Miles Davis Jazz 1500000 50mio-
3 SIA Pop 2000000 50mio+
"""
df["plays"] = df["plays"].map(lambda x: "50mio+" if x > 50_000_000 else "50mio-")
print(df)
如果要基于多列数据 ( 此时为 DataFrame
类型 ) 进行变换,则需要引入 apply()
方法。比如:
# axis=1 表示按数据列进行操作
df["scores"] = df[["listeners","plays"]].apply(lambda t: (t["listeners"]*5 + 2*t["plays"]) / 10**8, axis=1)
print(df)
表连接
可以通过 merge()
方法实现类似 SQL 表的内连接 ( 默认 ),左连接,右连接,全外连接操作,通过 how
参数进行配置。比如:
info = [
("Billie Holiday", "US"),
("Jimi Hendrix", "US"),
("Justin bieber", "Canada"),
]
artist_info = pd.DataFrame(data=info, columns=["artist", "country"])
m0 = df.merge(artist_info) # how="inner"
m1 = df.merge(artist_info, how="outer")
m2 = df.merge(artist_info, how="left")
m3 = df.merge(artist_info, how="right")
使用 Pandas 自身提供的 concat()
函数可以连接两个数据表。比如:
row = [("Justin Bieber", "Pop", 300_000, 1_000_000)]
append_df = pd.DataFrame(data=row, columns=["artist", "genre", "listeners", "plays"])
# axis=0 表示按行进行尾追加
ndf = pd.concat([df, append_df], axis=0)
print(ndf)
作者:花花子
来源:稀土掘金