Python 机器学习秘籍第二版(一)
原文:
annas-archive.org/md5/343c5e6c97737f77853e89eacb95df75译者:飞龙
前言
当本书的第一版于 2018 年出版时,填补了机器学习(ML)内容日益丰富的关键空白。通过提供经过充分测试的、实用的 Python 示例,使从业者能够轻松地复制和粘贴代码,然后根据自己的用例进行调整。在短短五年时间里,机器学习领域继续随着深度学习(DL)和相关的 DL Python 框架的进展而蓬勃发展。
现在,到了 2023 年,有必要提供与最新 Python 库相适应的机器学习(ML)和深度学习(DL)从业者需要的实用内容,这本书旨在基于第一版作者已完成的(而且出色的)工作:
-
更新现有示例以使用最新的 Python 版本和框架
-
结合数据源、数据分析、ML 和 DL 中的现代实践
-
扩展 DL 内容,包括 PyTorch 中的张量、神经网络以及文本和视觉的 DL
-
通过在 API 中为我们的模型提供服务,将我们的模型提升一步
与第一版相似,本书采用基于任务的方法来进行机器学习,提供超过 200 个独立的解决方案(复制、粘贴和运行),用于数据科学家或机器学习工程师在建模过程中遇到的最常见任务。
本书使用的约定
本书使用以下排版约定:
斜体
表示新术语、URL、电子邮件地址、文件名和文件扩展名。
恒定宽度
用于程序清单,以及在段落中引用程序元素,如变量或函数名称、数据库、数据类型、环境变量、语句和关键字。
恒定宽度加粗
显示用户应该按字面意义键入的命令或其他文本。
*恒定宽度斜体*
显示应由用户提供值或根据上下文确定值的文本。
使用代码示例
本书配有一个GitHub 存储库,其中提供了在 Docker 容器中运行 Jupyter Notebook 的说明及本书中使用的所有依赖项。通过在笔记本中复制本书中的命令,可以确保本书中的示例完全可再现。
如果您有技术问题或使用代码示例时遇到问题,请发送电子邮件至support@oreilly.com。
本书旨在帮助您完成工作。一般来说,如果本书提供示例代码,您可以在您的程序和文档中使用它。除非您复制了大量代码,否则无需联系我们请求授权。例如,编写使用本书多个代码片段的程序不需要授权。销售或分发 O’Reilly 书籍的示例需要授权。通过引用本书回答问题并引用示例代码不需要授权。将本书的大量示例代码整合到您产品的文档中需要授权。
我们感谢您的使用,但通常不需要署名。署名通常包括标题、作者、出版商和 ISBN。例如:“Python 机器学习菜谱, 第 2 版,Kyle Gallatin 和 Chris Albon (O’Reilly)。版权所有 2023 Kyle Gallatin, 978-1-098-13572-0。”
如果您觉得您使用的代码示例超出了合理使用范围或以上授权,请随时通过permissions@oreilly.com与我们联系。
O’Reilly Online Learning
注意
O’Reilly Media已经提供技术和商业培训、知识和见解超过 40 年,帮助公司取得成功。
我们独特的专家和创新者网络通过书籍、文章以及我们的在线学习平台分享他们的知识和专长。O’Reilly 的在线学习平台为您提供按需访问的现场培训课程、深入的学习路径、交互式编码环境,以及来自 O’Reilly 和其他 200 多个出版商的广泛文本和视频资源。欲了解更多信息,请访问https://oreilly.com。
如何联系我们
请将关于本书的评论和问题发送给出版商:
-
O’Reilly Media, Inc.
-
1005 Gravenstein Highway North
-
Sebastopol, CA 95472
-
800-889-8969(美国或加拿大)
-
707-829-7019(国际或本地)
-
707-829-0104(传真)
我们为本书设有网页,列出勘误、示例以及任何额外信息。您可以访问https://oreil.ly/ml_python_2e。
关于我们的书籍和课程的新闻和信息,请访问https://oreilly.com。
在 LinkedIn 找到我们:https://linkedin.com/company/oreilly-media
在 Twitter 关注我们:https://twitter.com/oreillymedia
在 YouTube 观看我们:https://youtube.com/oreillymedia
致谢
本书的第二版之所以能够顺利出版,完全得益于第一版的出色内容、结构和质量,这些都是原作者克里斯·阿尔本所铺陈的。作为第二版的第一作者,我无法言尽这使得我的工作变得轻松了许多的程度。
当然,机器学习领域也在迅速发展,本书第二版的更新内容离不开同行们深思熟虑的反馈。特别感谢我的 Etsy 同事安德烈亚·海曼、玛丽亚·戈麦斯、亚力克·迈尔斯特鲁姆和布莱恩·施密特,他们不厌其烦地响应对各章节的意见征集,并被逼迫进入突如其来的头脑风暴会议,共同塑造了本版新增的内容。还要感谢技术审阅者吉吉亚莎·格罗弗、马特乌斯·坦哈和甘尼什·哈尔克,以及 O'Reilly 的编辑们:杰夫·布莱尔、妮可·巴特菲尔德和克莱尔·莱洛克。话虽如此,帮助我和这本书取得现在成就的人数(或多或少)是庞大的。我想感谢所有在我机器学习之旅中的人,以及在某种方式上帮助这本书成就今天的每一个人。爱你们。
第一章:在 NumPy 中处理向量、矩阵和数组
1.0 介绍
NumPy 是 Python 机器学习堆栈的基础工具。NumPy 允许在机器学习中经常使用的数据结构(向量、矩阵和张量)上进行高效操作。虽然本书的重点不是 NumPy,但在接下来的章节中会经常出现。本章涵盖了我们在机器学习工作流中可能遇到的最常见的 NumPy 操作。
1.1 创建向量
问题
您需要创建一个向量。
解决方案
使用 NumPy 创建一维数组:
# Load library
import numpy as np
# Create a vector as a row
vector_row = np.array([1, 2, 3])
# Create a vector as a column
vector_column = np.array([[1],
[2],
[3]])
讨论
NumPy 的主要数据结构是多维数组。向量只是一个单维数组。要创建向量,我们只需创建一个一维数组。就像向量一样,这些数组可以水平表示(即行)或垂直表示(即列)。
参见
1.2 创建矩阵
问题
您需要创建一个矩阵。
解决方案
使用 NumPy 创建二维数组:
# Load library
import numpy as np
# Create a matrix
matrix = np.array([[1, 2],
[1, 2],
[1, 2]])
讨论
要创建矩阵,我们可以使用 NumPy 的二维数组。在我们的解决方案中,矩阵包含三行和两列(一列为 1,一列为 2)。
实际上,NumPy 有一个专用的矩阵数据结构:
matrix_object = np.mat([[1, 2],
[1, 2],
[1, 2]])
matrix([[1, 2],
[1, 2],
[1, 2]])
然而,矩阵数据结构由于两个原因而不推荐使用。首先,数组是 NumPy 的事实标准数据结构。其次,绝大多数 NumPy 操作返回数组,而不是矩阵对象。
参见
1.3 创建稀疏矩阵
问题
鉴于数据中只有很少的非零值,您希望以高效的方式表示它。
解决方案
创建稀疏矩阵:
# Load libraries
import numpy as np
from scipy import sparse
# Create a matrix
matrix = np.array([[0, 0],
[0, 1],
[3, 0]])
# Create compressed sparse row (CSR) matrix
matrix_sparse = sparse.csr_matrix(matrix)
讨论
在机器学习中经常遇到的情况是有大量数据;然而,数据中大多数元素都是零。例如,想象一下一个矩阵,其中列是 Netflix 上的每部电影,行是每个 Netflix 用户,值是用户观看该特定电影的次数。这个矩阵将有成千上万的列和数百万的行!然而,由于大多数用户不会观看大多数电影,大多数元素将为零。
稀疏矩阵 是一个大部分元素为 0 的矩阵。稀疏矩阵仅存储非零元素,并假设所有其他值都为零,从而显著节省计算资源。在我们的解决方案中,我们创建了一个具有两个非零值的 NumPy 数组,然后将其转换为稀疏矩阵。如果查看稀疏矩阵,可以看到只存储了非零值:
# View sparse matrix
print(matrix_sparse)
(1, 1) 1
(2, 0) 3
有许多类型的稀疏矩阵。然而,在压缩稀疏行(CSR)矩阵中,(1, 1) 和 (2, 0) 表示非零值 1 和 3 的(从零开始计数的)索引。例如,元素 1 在第二行第二列。如果我们创建一个具有更多零元素的更大矩阵,然后将其与我们的原始稀疏矩阵进行比较,我们可以看到稀疏矩阵的优势:
# Create larger matrix
matrix_large = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[3, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
# Create compressed sparse row (CSR) matrix
matrix_large_sparse = sparse.csr_matrix(matrix_large)
# View original sparse matrix
print(matrix_sparse)
(1, 1) 1
(2, 0) 3
# View larger sparse matrix
print(matrix_large_sparse)
(1, 1) 1
(2, 0) 3
正如我们所见,尽管在更大的矩阵中添加了更多的零元素,但其稀疏表示与我们原始的稀疏矩阵完全相同。也就是说,添加零元素并没有改变稀疏矩阵的大小。
正如前面提到的,稀疏矩阵有许多不同的类型,如压缩稀疏列、列表列表和键字典。虽然解释这些不同类型及其影响超出了本书的范围,但值得注意的是,虽然没有“最佳”稀疏矩阵类型,但它们之间存在显著差异,我们应该意识到为什么选择一种类型而不是另一种类型。
另请参阅
1.4 预分配 NumPy 数组
问题
您需要预先分配给定大小的数组,并使用某些值。
解决方案
NumPy 具有使用 0、1 或您选择的值生成任意大小向量和矩阵的函数:
# Load library
import numpy as np
# Generate a vector of shape (1,5) containing all zeros
vector = np.zeros(shape=5)
# View the matrix
print(vector)
array([0., 0., 0., 0., 0.])
# Generate a matrix of shape (3,3) containing all ones
matrix = np.full(shape=(3,3), fill_value=1)
# View the vector
print(matrix)
array([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]])
讨论
使用预填充数据生成数组对于许多目的非常有用,例如使代码更具性能或使用合成数据来测试算法。在许多编程语言中,预先分配一个带有默认值(例如 0)的数组被认为是常见做法。
1.5 选择元素
问题
您需要在向量或矩阵中选择一个或多个元素。
解决方案
NumPy 数组使得选择向量或矩阵中的元素变得很容易:
# Load library
import numpy as np
# Create row vector
vector = np.array([1, 2, 3, 4, 5, 6])
# Create matrix
matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# Select third element of vector
vector[2]
3
# Select second row, second column
matrix[1,1]
5
讨论
像大多数 Python 中的事物一样,NumPy 数组是从零开始索引的,这意味着第一个元素的索引是 0,而不是 1。除此之外,NumPy 提供了大量方法来选择(即索引和切片)数组中的元素或元素组:
# Select all elements of a vector
vector[:]
array([1, 2, 3, 4, 5, 6])
# Select everything up to and including the third element
vector[:3]
array([1, 2, 3])
# Select everything after the third element
vector[3:]
array([4, 5, 6])
# Select the last element
vector[-1]
6
# Reverse the vector
vector[::-1]
array([6, 5, 4, 3, 2, 1])
# Select the first two rows and all columns of a matrix
matrix[:2,:]
array([[1, 2, 3],
[4, 5, 6]])
# Select all rows and the second column
matrix[:,1:2]
array([[2],
[5],
[8]])
1.6 描述矩阵
问题
您想要描述矩阵的形状、大小和维度。
解决方案
使用 NumPy 对象的 shape、size 和 ndim 属性:
# Load library
import numpy as np
# Create matrix
matrix = np.array([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]])
# View number of rows and columns
matrix.shape
(3, 4)
# View number of elements (rows * columns)
matrix.size
12
# View number of dimensions
matrix.ndim
2
讨论
这可能看起来很基础(而且确实如此);然而,一次又一次地,检查数组的形状和大小都是非常有价值的,无论是为了进一步的计算还是仅仅作为操作后的直觉检查。
1.7 对每个元素应用函数
问题
您想将某些函数应用于数组中的所有元素。
解决方案
使用 NumPy 的 vectorize 方法:
# Load library
import numpy as np
# Create matrix
matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# Create function that adds 100 to something
add_100 = lambda i: i + 100
# Create vectorized function
vectorized_add_100 = np.vectorize(add_100)
# Apply function to all elements in matrix
vectorized_add_100(matrix)
array([[101, 102, 103],
[104, 105, 106],
[107, 108, 109]])
讨论
NumPy 的vectorize方法将一个函数转换为可以应用于数组或数组切片的所有元素的函数。值得注意的是,vectorize本质上是对元素的for循环,不会提高性能。此外,NumPy 数组允许我们在数组之间执行操作,即使它们的维度不同(这称为广播)。例如,我们可以使用广播创建一个更简单的版本:
# Add 100 to all elements
matrix + 100
array([[101, 102, 103],
[104, 105, 106],
[107, 108, 109]])
广播不适用于所有形状和情况,但它是在 NumPy 数组的所有元素上应用简单操作的常见方法。
1.8 查找最大值和最小值
问题
您需要在数组中找到最大或最小值。
解决方案
使用 NumPy 的max和min方法:
# Load library
import numpy as np
# Create matrix
matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# Return maximum element
np.max(matrix)
9
# Return minimum element
np.min(matrix)
1
讨论
我们经常想知道数组或数组子集中的最大值和最小值。这可以通过max和min方法来实现。使用axis参数,我们还可以沿着特定轴应用操作:
# Find maximum element in each column
np.max(matrix, axis=0)
array([7, 8, 9])
# Find maximum element in each row
np.max(matrix, axis=1)
array([3, 6, 9])
1.9 计算平均值、方差和标准差
问题
您希望计算数组的一些描述性统计信息。
解决方案
使用 NumPy 的mean、var和std:
# Load library
import numpy as np
# Create matrix
matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# Return mean
np.mean(matrix)
5.0
# Return variance
np.var(matrix)
6.666666666666667
# Return standard deviation
np.std(matrix)
2.5819888974716112
讨论
就像使用max和min一样,我们可以轻松地获得关于整个矩阵的描述性统计信息,或者沿着单个轴进行计算:
# Find the mean value in each column
np.mean(matrix, axis=0)
array([ 4., 5., 6.])
1.10 重新塑形数组
问题
您希望更改数组的形状(行数和列数),而不更改元素值。
解决方案
使用 NumPy 的reshape:
# Load library
import numpy as np
# Create 4x3 matrix
matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12]])
# Reshape matrix into 2x6 matrix
matrix.reshape(2, 6)
array([[ 1, 2, 3, 4, 5, 6],
[ 7, 8, 9, 10, 11, 12]])
讨论
reshape允许我们重构一个数组,以便我们保持相同的数据但将其组织为不同数量的行和列。唯一的要求是原始矩阵和新矩阵的形状包含相同数量的元素(即,大小相同)。我们可以使用size来查看矩阵的大小:
matrix.size
12
reshape中一个有用的参数是-1,它实际上意味着“需要多少就多少”,因此reshape(1, -1)意味着一行和所需的列数:
matrix.reshape(1, -1)
array([[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]])
最后,如果我们提供一个整数,reshape将返回一个长度为该整数的一维数组:
matrix.reshape(12)
array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
1.11 转置向量或矩阵
问题
您需要转置向量或矩阵。
解决方案
使用T方法:
# Load library
import numpy as np
# Create matrix
matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# Transpose matrix
matrix.T
array([[1, 4, 7],
[2, 5, 8],
[3, 6, 9]])
讨论
转置是线性代数中的常见操作,其中每个元素的列和行索引被交换。在线性代数课程之外通常被忽略的一个微妙的点是,从技术上讲,向量不能被转置,因为它只是一组值:
# Transpose vector
np.array([1, 2, 3, 4, 5, 6]).T
array([1, 2, 3, 4, 5, 6])
然而,通常将向量的转置称为将行向量转换为列向量(请注意第二对括号)或反之亦然:
# Transpose row vector
np.array([[1, 2, 3, 4, 5, 6]]).T
array([[1],
[2],
[3],
[4],
[5],
[6]])
1.12 扁平化矩阵
问题
您需要将矩阵转换为一维数组。
解决方案
使用flatten方法:
# Load library
import numpy as np
# Create matrix
matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# Flatten matrix
matrix.flatten()
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
讨论
flatten是一个简单的方法,将矩阵转换为一维数组。或者,我们可以使用reshape创建一个行向量:
matrix.reshape(1, -1)
array([[1, 2, 3, 4, 5, 6, 7, 8, 9]])
另一种常见的数组展平方法是ravel方法。与返回原始数组副本的flatten不同,ravel直接操作原始对象,因此速度稍快。此外,ravel还允许我们展平数组列表,而flatten方法则无法做到。这种操作对于展平非常大的数组和加速代码非常有用:
# Create one matrix
matrix_a = np.array([[1, 2],
[3, 4]])
# Create a second matrix
matrix_b = np.array([[5, 6],
[7, 8]])
# Create a list of matrices
matrix_list = [matrix_a, matrix_b]
# Flatten the entire list of matrices
np.ravel(matrix_list)
array([1, 2, 3, 4, 5, 6, 7, 8])
1.13 矩阵的秩
问题
你需要知道一个矩阵的秩。
解决方案
使用 NumPy 的线性代数方法matrix_rank:
# Load library
import numpy as np
# Create matrix
matrix = np.array([[1, 1, 1],
[1, 1, 10],
[1, 1, 15]])
# Return matrix rank
np.linalg.matrix_rank(matrix)
2
讨论
矩阵的秩是由其列或行张成的向量空间的维数。在 NumPy 中由于matrix_rank函数,计算矩阵的秩非常容易。
参见
1.14 获取矩阵的对角线
问题
你需要获取一个矩阵的对角线元素。
解决方案
使用 NumPy 的diagonal:
# Load library
import numpy as np
# Create matrix
matrix = np.array([[1, 2, 3],
[2, 4, 6],
[3, 8, 9]])
# Return diagonal elements
matrix.diagonal()
array([1, 4, 9])
讨论
使用 NumPy 轻松获取矩阵的对角线元素,使用diagonal函数。还可以通过使用offset参数获取主对角线之外的对角线:
# Return diagonal one above the main diagonal
matrix.diagonal(offset=1)
array([2, 6])
# Return diagonal one below the main diagonal
matrix.diagonal(offset=-1)
array([2, 8])
1.15 计算一个矩阵的迹
问题
你需要计算一个矩阵的迹。
解决方案
使用trace:
# Load library
import numpy as np
# Create matrix
matrix = np.array([[1, 2, 3],
[2, 4, 6],
[3, 8, 9]])
# Return trace
matrix.trace()
14
讨论
一个矩阵的迹是其对角线元素的和,通常在机器学习方法中被广泛使用。对于给定的 NumPy 多维数组,我们可以使用trace函数来计算迹。或者,我们可以返回一个矩阵的对角线并计算其总和:
# Return diagonal and sum elements
sum(matrix.diagonal())
14
参见
1.16 计算点积
问题
你需要计算两个向量的点积。
解决方案
使用 NumPy 的dot函数:
# Load library
import numpy as np
# Create two vectors
vector_a = np.array([1,2,3])
vector_b = np.array([4,5,6])
# Calculate dot product
np.dot(vector_a, vector_b)
32
讨论
两个向量a和b的点积定义如下:
∑ i=1 n a i b i
其中ai是向量a的第i个元素,bi是向量b的第i个元素。我们可以使用 NumPy 的dot函数来计算点积。或者,在 Python 3.5+ 中,我们可以使用新的@运算符:
# Calculate dot product
vector_a @ vector_b
32
参见
1.17 矩阵的加法和减法
问题
你想要对两个矩阵进行加法或减法。
解决方案
使用 NumPy 的add和subtract:
# Load library
import numpy as np
# Create matrix
matrix_a = np.array([[1, 1, 1],
[1, 1, 1],
[1, 1, 2]])
# Create matrix
matrix_b = np.array([[1, 3, 1],
[1, 3, 1],
[1, 3, 8]])
# Add two matrices
np.add(matrix_a, matrix_b)
array([[ 2, 4, 2],
[ 2, 4, 2],
[ 2, 4, 10]])
# Subtract two matrices
np.subtract(matrix_a, matrix_b)
array([[ 0, -2, 0],
[ 0, -2, 0],
[ 0, -2, -6]])
讨论
或者,我们可以简单地使用+和–运算符:
# Add two matrices
matrix_a + matrix_b
array([[ 2, 4, 2],
[ 2, 4, 2],
[ 2, 4, 10]])
1.18 矩阵的乘法
问题
你想要对两个矩阵进行乘法运算。
解决方案
使用 NumPy 的dot:
# Load library
import numpy as np
# Create matrix
matrix_a = np.array([[1, 1],
[1, 2]])
# Create matrix
matrix_b = np.array([[1, 3],
[1, 2]])
# Multiply two matrices
np.dot(matrix_a, matrix_b)
array([[2, 5],
[3, 7]])
讨论
或者,在 Python 3.5+ 中我们可以使用@运算符:
# Multiply two matrices
matrix_a @ matrix_b
array([[2, 5],
[3, 7]])
如果我们想要进行逐元素乘法,可以使用*运算符:
# Multiply two matrices element-wise
matrix_a * matrix_b
array([[1, 3],
[1, 4]])
参见
1.19 矩阵求逆
问题
您想要计算一个方阵的逆。
解决方案
使用 NumPy 的线性代数 inv 方法:
# Load library
import numpy as np
# Create matrix
matrix = np.array([[1, 4],
[2, 5]])
# Calculate inverse of matrix
np.linalg.inv(matrix)
array([[-1.66666667, 1.33333333],
[ 0.66666667, -0.33333333]])
讨论
方阵 A 的逆,A^(–1),是第二个矩阵,满足以下条件:
A A -1 = I
其中 I 是单位矩阵。在 NumPy 中,如果存在的话,我们可以使用 linalg.inv 计算 A^(–1)。为了看到这一点,我们可以将一个矩阵乘以它的逆,结果是单位矩阵:
# Multiply matrix and its inverse
matrix @ np.linalg.inv(matrix)
array([[ 1., 0.],
[ 0., 1.]])
参见
1.20 生成随机值
问题
您想要生成伪随机值。
解决方案
使用 NumPy 的 random:
# Load library
import numpy as np
# Set seed
np.random.seed(0)
# Generate three random floats between 0.0 and 1.0
np.random.random(3)
array([ 0.5488135 , 0.71518937, 0.60276338])
讨论
NumPy 提供了生成随机数的多种方法,远远超出了此处所能涵盖的范围。在我们的解决方案中,我们生成了浮点数;然而,生成整数也很常见:
# Generate three random integers between 0 and 10
np.random.randint(0, 11, 3)
array([3, 7, 9])
或者,我们可以通过从分布中抽取数字来生成数字(请注意,这在技术上不是随机的):
# Draw three numbers from a normal distribution with mean 0.0
# and standard deviation of 1.0
np.random.normal(0.0, 1.0, 3)
array([-1.42232584, 1.52006949, -0.29139398])
# Draw three numbers from a logistic distribution with mean 0.0 and scale of 1.0
np.random.logistic(0.0, 1.0, 3)
array([-0.98118713, -0.08939902, 1.46416405])
# Draw three numbers greater than or equal to 1.0 and less than 2.0
np.random.uniform(1.0, 2.0, 3)
array([ 1.47997717, 1.3927848 , 1.83607876])
最后,有时候返回相同的随机数多次以获取可预测的、可重复的结果是有用的。我们可以通过设置伪随机生成器的“种子”(一个整数)来实现这一点。具有相同种子的随机过程将始终产生相同的输出。我们将在本书中使用种子,以确保您在书中看到的代码和在您的计算机上运行的代码产生相同的结果。
第二章:载入数据
2.0 简介
任何机器学习工作的第一步是将原始数据导入到我们的系统中。原始数据可以是日志文件、数据集文件、数据库,或者像亚马逊 S3 这样的云存储。此外,通常我们会希望从多个来源检索数据。
本章中的示例将介绍从多种来源加载数据的方法,包括 CSV 文件和 SQL 数据库。我们还将介绍如何使用具有可配置属性的模拟数据生成方法进行实验。最后,虽然在 Python 生态系统中有许多加载数据的方式,但我们将重点介绍使用 pandas 库的广泛方法来加载外部数据,以及使用 scikit-learn——一个开源的 Python 机器学习库——来生成模拟数据。
2.1 载入一个示例数据集
问题
您希望加载 scikit-learn 库中预先存在的示例数据集。
解决方案
scikit-learn 自带许多流行的数据集供您使用:
# Load scikit-learn's datasets
from sklearn import datasets
# Load digits dataset
digits = datasets.load_digits()
# Create features matrix
features = digits.data
# Create target vector
target = digits.target
# View first observation
features[0]
array([ 0., 0., 5., 13., 9., 1., 0., 0., 0., 0., 13.,
15., 10., 15., 5., 0., 0., 3., 15., 2., 0., 11.,
8., 0., 0., 4., 12., 0., 0., 8., 8., 0., 0.,
5., 8., 0., 0., 9., 8., 0., 0., 4., 11., 0.,
1., 12., 7., 0., 0., 2., 14., 5., 10., 12., 0.,
0., 0., 0., 6., 13., 10., 0., 0., 0.])
讨论
我们通常不希望在能够探索一些机器学习算法或方法之前,就必须加载、转换和清理真实世界的数据集。幸运的是,scikit-learn 提供了一些常见的数据集,我们可以快速加载。这些数据集通常被称为“玩具”数据集,因为它们比真实世界中的数据集要小得多,也更干净。scikit-learn 中一些流行的示例数据集包括:
load_iris
包含 150 个鸢尾花测量数据的观察结果。这是一个很好的数据集,用于探索分类算法。
load_digits
包含 1,797 个手写数字图像的观察结果。这是一个很好的数据集,适合用于图像分类的教学。
要查看这些数据集的更多细节,请打印 DESCR 属性:
# Load scikit-learn's datasets
from sklearn import datasets
# Load digits dataset
digits = datasets.load_digits()
# Print the attribute
print(digits.DESCR)
.. _digits_dataset:
Optical recognition of handwritten digits dataset
--------------------------------------------------
**Data Set Characteristics:**
:Number of Instances: 1797
:Number of Attributes: 64
:Attribute Information: 8x8 image of integer pixels in the range 0..16.
:Missing Attribute Values: None
:Creator: E. Alpaydin (alpaydin '@' boun.edu.tr)
:Date: July; 1998
...
参见
2.2 创建一个模拟数据集
问题
您需要生成一个模拟数据集。
解决方案
scikit-learn 提供了许多用于创建模拟数据的方法。其中,三种方法特别有用:make_regression、make_classification 和 make_blobs。
当我们需要一个设计用于线性回归的数据集时,make_regression 是一个不错的选择:
# Load library
from sklearn.datasets import make_regression
# Generate features matrix, target vector, and the true coefficients
features, target, coefficients = make_regression(n_samples = 100,
n_features = 3,
n_informative = 3,
n_targets = 1,
noise = 0.0,
coef = True,
random_state = 1)
# View feature matrix and target vector
print('Feature Matrix\n', features[:3])
print('Target Vector\n', target[:3])
Feature Matrix
[[ 1.29322588 -0.61736206 -0.11044703]
[-2.793085 0.36633201 1.93752881]
[ 0.80186103 -0.18656977 0.0465673 ]]
Target Vector
[-10.37865986 25.5124503 19.67705609]
如果我们有兴趣创建一个用于分类的模拟数据集,我们可以使用 make_classification:
# Load library
from sklearn.datasets import make_classification
# Generate features matrix and target vector
features, target = make_classification(n_samples = 100,
n_features = 3,
n_informative = 3,
n_redundant = 0,
n_classes = 2,
weights = [.25, .75],
random_state = 1)
# View feature matrix and target vector
print('Feature Matrix\n', features[:3])
print('Target Vector\n', target[:3])
Feature Matrix
[[ 1.06354768 -1.42632219 1.02163151]
[ 0.23156977 1.49535261 0.33251578]
[ 0.15972951 0.83533515 -0.40869554]]
Target Vector
[1 0 0]
最后,如果我们需要一个设计用于聚类技术的数据集,scikit-learn 提供了 make_blobs:
# Load library
from sklearn.datasets import make_blobs
# Generate features matrix and target vector
features, target = make_blobs(n_samples = 100,
n_features = 2,
centers = 3,
cluster_std = 0.5,
shuffle = True,
random_state = 1)
# View feature matrix and target vector
print('Feature Matrix\n', features[:3])
print('Target Vector\n', target[:3])
Feature Matrix
[[ -1.22685609 3.25572052]
[ -9.57463218 -4.38310652]
[-10.71976941 -4.20558148]]
Target Vector
[0 1 1]
讨论
从解决方案中可以看出,make_regression 返回一个浮点值的特征矩阵和一个浮点值的目标向量,而 make_classification 和 make_blobs 返回一个浮点值的特征矩阵和一个整数的目标向量,代表类的成员身份。
scikit-learn 的模拟数据集提供了广泛的选项来控制生成数据的类型。scikit-learn 的文档包含了所有参数的详细描述,但有几个值得注意。
在 make_regression 和 make_classification 中,n_informative 确定用于生成目标向量的特征数量。如果 n_informative 小于总特征数 (n_features),则生成的数据集将具有冗余特征,可通过特征选择技术识别。
此外,make_classification 包含 weights 参数,允许我们模拟不平衡类别的数据集。例如,weights = [.25, .75] 将返回一个数据集,其中 25%的观测属于一类,75%的观测属于第二类。
对于 make_blobs,centers 参数确定生成的簇数量。使用 matplotlib 可视化库,我们可以可视化 make_blobs 生成的簇:
# Load library
import matplotlib.pyplot as plt
# View scatterplot
plt.scatter(features[:,0], features[:,1], c=target)
plt.show()
参见
2.3 加载 CSV 文件
问题
您需要导入逗号分隔值(CSV)文件。
解决方案
使用 pandas 库的 read_csv 将本地或托管的 CSV 文件加载到 pandas DataFrame 中:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/data.csv'
# Load dataset
dataframe = pd.read_csv(url)
# View first two rows
dataframe.head(2)
| integer | datetime | category | |
|---|---|---|---|
| 0 | 5 | 2015-01-01 00:00:00 | 0 |
| 1 | 5 | 2015-01-01 00:00:01 | 0 |
讨论
关于加载 CSV 文件有两件事情需要注意。首先,在加载之前快速查看文件内容通常很有用。事先了解数据集的结构以及我们需要设置的参数是非常有帮助的。其次,read_csv 有超过 30 个参数,因此文档可能令人望而却步。幸运的是,这些参数大多是为了处理各种 CSV 格式而设定的。
CSV 文件的名称源于数值之间确实以逗号分隔(例如,一行可能是 2,"2015-01-01 00:00:00",0);然而,常见的 CSV 文件使用其他分隔符,如制表符(称为 TSV 文件)。pandas 的sep参数允许我们定义文件中使用的分隔符。尽管并非总是如此,CSV 文件常见的格式问题是文件的第一行用于定义列标题(例如,在我们的解决方案中是 integer, datetime, category)。header参数允许我们指定是否存在标题行以及其位置。如果不存在标题行,我们设置header=None。
read_csv 函数返回一个 pandas DataFrame:这是处理表格数据常见且有用的对象,在本书中我们将更深入地讨论它。
2.4 加载 Excel 文件
问题
您需要导入 Excel 电子表格。
解决方案
使用 pandas 库的 read_excel 加载 Excel 电子表格:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/data.xlsx'
# Load data
dataframe = pd.read_excel(url, sheet_name=0, header=0)
# View the first two rows
dataframe.head(2)
| integer | datetime | category | |
|---|---|---|---|
| 5 | 2015-01-01 00:00:00 | 0 | |
| 0 | 5 | 2015-01-01 00:00:01 | 0 |
| 1 | 9 | 2015-01-01 00:00:02 | 0 |
讨论
此解决方案类似于我们用于读取 CSV 文件的解决方案。主要区别在于附加参数 sheet_name,它指定了我们希望加载的 Excel 文件中的哪个工作表。sheet_name 可以接受包含工作表名称的字符串和指向工作表位置(从零开始计数)的整数。如果我们需要加载多个工作表,我们将它们包含在列表中。例如,sheet_name=[0,1,2, "Monthly Sales"] 将返回一个包含第一个、第二个和第三个工作表以及名为 Monthly Sales 的工作表的 pandas DataFrame 字典。
2.5 加载 JSON 文件
问题
您需要加载一个 JSON 文件进行数据预处理。
解决方案
pandas 库提供了 read_json 来将 JSON 文件转换为 pandas 对象:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/data.json'
# Load data
dataframe = pd.read_json(url, orient='columns')
# View the first two rows
dataframe.head(2)
| 类别 | 时间 | 整数 | |
|---|---|---|---|
| 0 | 0 | 2015-01-01 00:00:00 | 5 |
| 1 | 0 | 2015-01-01 00:00:01 | 5 |
讨论
将 JSON 文件导入 pandas 类似于我们之前看到的几个示例。主要区别在于 orient 参数,它指示 pandas JSON 文件的结构。但是,可能需要一些试验才能弄清楚哪个参数(split、records、index、columns 或 values)是正确的。另一个 pandas 提供的有用工具是 json_normalize,它可以帮助将半结构化的 JSON 数据转换为 pandas DataFrame。
参见
2.6 加载 Parquet 文件
问题
您需要加载一个 Parquet 文件。
解决方案
pandas 的 read_parquet 函数允许我们读取 Parquet 文件:
# Load library
import pandas as pd
# Create URL
url = 'https://machine-learning-python-cookbook.s3.amazonaws.com/data.parquet'
# Load data
dataframe = pd.read_parquet(url)
# View the first two rows
dataframe.head(2)
| 类别 | 时间 | 整数 | |
|---|---|---|---|
| 0 | 0 | 2015-01-01 00:00:00 | 5 |
| 1 | 0 | 2015-01-01 00:00:01 | 5 |
讨论
Parquet 是大数据领域中流行的数据存储格式。它通常与 Hadoop 和 Spark 等大数据工具一起使用。虽然 PySpark 超出了本书的重点,但大规模运营的公司很可能会使用高效的数据存储格式,比如 Parquet,了解如何将其读入数据框架并对其进行操作是很有价值的。
参见
2.7 加载 Avro 文件
问题
您需要将 Avro 文件加载到 pandas DataFrame 中。
解决方案
使用 pandavro 库的 read_avro 方法:
# Load library
import requests
import pandavro as pdx
# Create URL
url = 'https://machine-learning-python-cookbook.s3.amazonaws.com/data.avro'
# Download file
r = requests.get(url)
open('data.avro', 'wb').write(r.content)
# Load data
dataframe = pdx.read_avro('data.avro')
# View the first two rows
dataframe.head(2)
| 类别 | 时间 | 整数 | |
|---|---|---|---|
| 0 | 0 | 2015-01-01 00:00:00 | 5 |
| 1 | 0 | 2015-01-01 00:00:01 | 5 |
讨论
Apache Avro 是一种开源的二进制数据格式,依赖于数据结构。在撰写本文时,它还不像 Parquet 那样普遍。但是,由于其高效的特性,大型二进制数据格式(如 Avro、thrift 和 Protocol Buffers)正变得越来越流行。如果您使用大型数据系统,很可能在不久的将来会遇到其中一种格式。
参见
2.8 查询 SQLite 数据库
问题
您需要使用结构化查询语言(SQL)从数据库加载数据。
解决方案
pandas 的read_sql_query允许我们向数据库发出 SQL 查询并加载数据:
# Load libraries
import pandas as pd
from sqlalchemy import create_engine
# Create a connection to the database
database_connection = create_engine('sqlite:///sample.db')
# Load data
dataframe = pd.read_sql_query('SELECT * FROM data', database_connection)
# View first two rows
dataframe.head(2)
| 名字 | 姓氏 | 年龄 | 预测试分数 | 后测试分数 | |
|---|---|---|---|---|---|
| 0 | Jason | Miller | 42 | 4 | 25 |
| 1 | Molly | Jacobson | 52 | 24 | 94 |
讨论
SQL 是从数据库提取数据的通用语言。在这个配方中,我们首先使用create_engine定义了一个连接到名为 SQLite 的 SQL 数据库引擎。接下来,我们使用 pandas 的read_sql_query使用 SQL 查询该数据库,并将结果放入 DataFrame 中。
SQL 是一门独立的语言,虽然超出本书的范围,但对于希望学习机器学习的任何人来说,了解它肯定是值得的。我们的 SQL 查询SELECT * FROM data要求数据库给我们表名为data的所有列(*)。
请注意,这是本书中几个配方之一,如果没有额外的代码将无法运行。具体来说,create_engine('sqlite:///sample.db')假定 SQLite 数据库已经存在。
参见
2.9 查询远程 SQL 数据库
问题
您需要连接并从远程 SQL 数据库中读取数据。
解决方案
使用pymysql建立连接,并用 pandas 将其读入数据框:
# Import libraries
import pymysql
import pandas as pd
# Create a DB connection
# Use the following example to start a DB instance
# https://github.com/kylegallatin/mysql-db-example
conn = pymysql.connect(
host='localhost',
user='root',
password = "",
db='db',
)
# Read the SQL query into a dataframe
dataframe = pd.read_sql("select * from data", conn)
# View the first two rows
dataframe.head(2)
| 整数 | 日期时间 | 类别 | |
|---|---|---|---|
| 0 | 5 | 2015-01-01 00:00:00 | 0 |
| 1 | 5 | 2015-01-01 00:00:01 | 0 |
讨论
在本章中呈现的所有配方中,这可能是我们在现实世界中最常使用的一个。虽然连接并从示例sqlite数据库中读取数据很有用,但它可能不代表您将需要连接的企业环境中的表。您将连接的大多数 SQL 实例都将要求您连接到远程计算机的主机和端口,并指定用于身份验证的用户名和密码。此示例需要您在本地启动运行的 SQL 实例,以模仿远程服务器上的工作流程。
参见
2.10 从 Google 表格加载数据
问题
您需要直接从 Google 表格中读取数据。
解决方案
使用 pandas 的read_CSV并传递一个将 Google 表格导出为 CSV 的 URL:
# Import libraries
import pandas as pd
# Google Sheet URL that downloads the sheet as a CSV
url = "https://docs.google.com/spreadsheets/d/"\
"1ehC-9otcAuitqnmWksqt1mOrTRCL38dv0K9UjhwzTOA/export?format=csv"
# Read the CSV into a dataframe
dataframe = pd.read_csv(url)
# View the first two rows
dataframe.head(2)
| 整数 | 日期时间 | 类别 | |
|---|---|---|---|
| 0 | 5 | 2015-01-01 00:00:00 | 0 |
| 1 | 5 | 2015-01-01 00:00:01 | 0 |
讨论
虽然 Google 表格可以轻松下载,但直接在 Python 中读取它们而无需任何中间步骤有时会很有帮助。上述 URL 末尾的/export?format=csv查询参数创建了一个端点,我们可以从中下载文件或将其读入 pandas。
参见
2.11 从 S3 存储桶加载数据
问题
您需要从您有访问权限的 S3 存储桶中读取 CSV 文件。
解决方案
向 pandas 添加存储选项,使其可以访问 S3 对象:
# Import libraries
import pandas as pd
# S3 path to CSV
s3_uri = "s3://machine-learning-python-cookbook/data.csv"
# Set AWS credentials (replace with your own)
ACCESS_KEY_ID = "*`xxxxxxxxxxxxx`*"
SECRET_ACCESS_KEY = "*`xxxxxxxxxxxxxxxx`*"
# Read the CSV into a dataframe
dataframe = pd.read_csv(s3_uri,storage_options={
"key": ACCESS_KEY_ID,
"secret": SECRET_ACCESS_KEY,
}
)
# View first two rows
dataframe.head(2)
| 整数 | 日期时间 | 类别 | |
|---|---|---|---|
| 0 | 5 | 2015-01-01 00:00:00 | 0 |
| 1 | 5 | 2015-01-01 00:00:01 | 0 |
讨论
许多企业现在将数据保存在云提供商的 Blob 存储中,如 Amazon S3 或 Google Cloud Storage(GCS)。机器学习从业者通常连接到这些来源以检索数据。虽然 S3 URI(s3://machine-learning-python-cookbook/data.csv)是公共的,但仍然需要您提供自己的 AWS 访问凭据才能访问它。值得注意的是,公共对象还有 HTTP URL,可以从中下载文件,比如这个 CSV 文件的链接。
参见
2.12 加载非结构化数据
问题
您需要加载文本或图像等非结构化数据。
解决方案
使用基本的 Python open函数加载信息:
# Import libraries
import requests
# URL to download the txt file from
txt_url = "https://machine-learning-python-cookbook.s3.amazonaws.com/text.txt"
# Get the txt file
r = requests.get(txt_url)
# Write it to text.txt locally
with open('text.txt', 'wb') as f:
f.write(r.content)
# Read in the file
with open('text.txt', 'r') as f:
text = f.read()
# Print the content
print(text)
Hello there!
讨论
虽然结构化数据可以轻松从 CSV、JSON 或各种数据库中读取,但非结构化数据可能更具挑战性,可能需要稍后进行定制处理。有时使用 Python 的基本open函数打开并读取文件会很有帮助。这样我们就可以打开文件然后读取文件的内容。
参见
第三章:数据整理
3.0 引言
数据整理是一个广义术语,通常非正式地用来描述将原始数据转换为干净、有组织的格式,以便于使用的过程。对于我们来说,数据整理只是数据预处理的一个步骤,但是这是一个重要的步骤。
“整理”数据最常用的数据结构是数据框,它既直观又非常灵活。数据框是表格型的,意味着它们基于行和列,就像您在电子表格中看到的那样。这是一个根据泰坦尼克号乘客数据创建的数据框示例:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'
# Load data as a dataframe
dataframe = pd.read_csv(url)
# Show first five rows
dataframe.head(5)
| 名称 | PClass | 年龄 | 性别 | 幸存 | 性别代码 | |
|---|---|---|---|---|---|---|
| 0 | 艾伦,伊丽莎白·沃尔顿小姐 | 1st | 29.00 | 女性 | 1 | 1 |
| 1 | 艾莉森,海伦·洛林小姐 | 1st | 2.00 | 女性 | 0 | 1 |
| 2 | 艾莉森,哈德森·约书亚·克莱顿先生 | 1st | 30.00 | 男性 | 0 | 0 |
| 3 | 艾莉森,哈德森 JC 夫人(贝西·沃尔多·丹尼尔斯) | 1st | 25.00 | 女性 | 0 | 1 |
| 4 | 艾莉森,哈德森·特雷弗小主人 | 1st | 0.92 | 男性 | 1 | 0 |
在这个数据框中,有三个重要的事情需要注意。
首先,在数据框中,每一行对应一个观察结果(例如一个乘客),每一列对应一个特征(性别、年龄等)。例如,通过查看第一个观察结果,我们可以看到伊丽莎白·沃尔顿·艾伦小姐住在头等舱,年龄为 29 岁,是女性,并且幸存于这场灾难。
其次,在数据框中,每一行对应一个观察结果(例如一个乘客),每一列对应一个特征(性别、年龄等)。例如,通过查看第一个观察结果,我们可以看到伊丽莎白·沃尔顿·艾伦小姐住在头等舱,年龄为 29 岁,是女性,并且幸存于这场灾难。
第三,两列Sex和SexCode以不同格式包含相同的信息。在Sex中,女性用字符串female表示,而在SexCode中,女性用整数1表示。我们希望所有的特征都是唯一的,因此我们需要删除其中一列。
在本章中,我们将涵盖使用 pandas 库操作数据框的各种技术,旨在创建一个干净、结构良好的观察结果集以便进行进一步的预处理。
3.1 创建数据框
问题
您想要创建一个新的数据框。
解决方案
pandas 有许多用于创建新数据框对象的方法。一个简单的方法是使用 Python 字典实例化一个DataFrame。在字典中,每个键是列名,每个值是一个列表,其中每个项目对应一行:
# Load library
import pandas as pd
# Create a dictionary
dictionary = {
"Name": ['Jacky Jackson', 'Steven Stevenson'],
"Age": [38, 25],
"Driver": [True, False]
}
# Create DataFrame
dataframe = pd.DataFrame(dictionary)
# Show DataFrame
dataframe
| 名称 | 年龄 | 驾驶员 | |
|---|---|---|---|
| 0 | 杰基·杰克逊 | 38 | True |
| 1 | 史蒂文·史蒂文森 | 25 | False |
使用值列表很容易向任何数据框添加新列:
# Add a column for eye color
dataframe["Eyes"] = ["Brown", "Blue"]
# Show DataFrame
dataframe
| 名称 | 年龄 | 驾驶员 | 眼睛 | |
|---|---|---|---|---|
| 0 | 杰基·杰克逊 | 38 | True | Brown |
| 1 | 史蒂文·史蒂文森 | 25 | False | 蓝色 |
讨论
pandas 提供了几乎无限种方法来创建 DataFrame。在实际应用中,几乎不会先创建一个空的 DataFrame,然后再填充数据。相反,我们的 DataFrame 通常是从其他来源(如 CSV 文件或数据库)加载真实数据而创建的。
3.2 获取关于数据的信息
问题
您想查看 DataFrame 的一些特征。
解决方案
加载数据后,最简单的事情之一是使用 head 查看前几行数据:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'
# Load data
dataframe = pd.read_csv(url)
# Show two rows
dataframe.head(2)
| 姓名 | 舱位 | 年龄 | 性别 | 生还 | 性别编码 | |
|---|---|---|---|---|---|---|
| 0 | Allen, Miss Elisabeth Walton | 1st | 29.0 | 女性 | 1 | 1 |
| 1 | Allison, Miss Helen Loraine | 1st | 2.0 | 女性 | 0 | 1 |
我们也可以查看行数和列数:
# Show dimensions
dataframe.shape
(1313, 6)
我们可以使用 describe 获取任何数值列的描述统计信息:
# Show statistics
dataframe.describe()
| 年龄 | 生还 | 性别编码 | |
|---|---|---|---|
| count | 756.000000 | 1313.000000 | 1313.000000 |
| mean | 30.397989 | 0.342727 | 0.351866 |
| std | 14.259049 | 0.474802 | 0.477734 |
| min | 0.170000 | 0.000000 | 0.000000 |
| 25% | 21.000000 | 0.000000 | 0.000000 |
| 50% | 28.000000 | 0.000000 | 0.000000 |
| 75% | 39.000000 | 1.000000 | 1.000000 |
| max | 71.000000 | 1.000000 | 1.000000 |
另外,info 方法可以显示一些有用的信息:
# Show info
dataframe.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1313 entries, 0 to 1312
Data columns (total 6 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Name 1313 non-null object
1 PClass 1313 non-null object
2 Age 756 non-null float64
3 Sex 1313 non-null object
4 Survived 1313 non-null int64
5 SexCode 1313 non-null int64
dtypes: float64(1), int64(2), object(3)
memory usage: 61.7+ KB
讨论
加载数据后,了解其结构和包含的信息是个好主意。理想情况下,我们可以直接查看完整的数据。但在大多数实际情况下,数据可能有数千到数百万行和列。因此,我们必须依靠提取样本来查看小部分数据片段,并计算数据的汇总统计信息。
在我们的解决方案中,我们使用了 Titanic 乘客的玩具数据集。使用 head 可以查看数据的前几行(默认为五行)。或者,我们可以使用 tail 查看最后几行。使用 shape 可以查看 DataFrame 包含多少行和列。使用 describe 可以查看任何数值列的基本描述统计信息。最后,info 显示了关于 DataFrame 的一些有用数据点,包括索引和列的数据类型、非空值和内存使用情况。
值得注意的是,汇总统计数据并不总是能完全反映事物的全部情况。例如,pandas 将 Survived 和 SexCode 列视为数值列,因为它们包含 1 和 0。然而,在这种情况下,这些数值实际上表示的是类别。例如,如果 Survived 等于 1,则表示该乘客在事故中生还。因此,某些汇总统计数据可能不适用,比如 SexCode 列的标准差(乘客性别的指示器)。
3.3 切片 DataFrame
问题
您需要选择特定的数据子集或 DataFrame 的切片。
解决方案
使用 loc 或 iloc 来选择一个或多个行或值:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'
# Load data
dataframe = pd.read_csv(url)
# Select first row
dataframe.iloc[0]
Name Allen, Miss Elisabeth Walton
PClass 1st
Age 29
Sex female
Survived 1
SexCode 1
Name: 0, dtype: object
我们可以使用:来定义我们想要的行切片,比如选择第二、第三和第四行:
# Select three rows
dataframe.iloc[1:4]
| 名称 | PClass | 年龄 | 性别 | 幸存 | 性别代码 | |
|---|---|---|---|---|---|---|
| 1 | 艾莉森小姐海伦·洛林 | 1st | 2.0 | 女性 | 0 | 1 |
| 2 | 艾莉森先生哈德森·乔舒亚·克莱顿 | 1st | 30.0 | 男性 | 0 | 0 |
| 3 | 艾莉森夫人哈德森 JC(贝西·沃尔多·丹尼尔斯) | 1st | 25.0 | 女性 | 0 | 1 |
我们甚至可以使用它来获取某个点之前的所有行,比如包括第四行在内的所有行:
# Select four rows
dataframe.iloc[:4]
| 名称 | PClass | 年龄 | 性别 | 幸存 | 性别代码 | |
|---|---|---|---|---|---|---|
| 0 | 艾伦小姐伊丽莎白·沃尔顿 | 1st | 29.0 | 女性 | 1 | 1 |
| 1 | 艾莉森小姐海伦·洛林 | 1st | 2.0 | 女性 | 0 | 1 |
| 2 | 艾莉森先生哈德森·乔舒亚·克莱顿 | 1st | 30.0 | 男性 | 0 | 0 |
| 3 | 艾莉森夫人哈德森 JC(贝西·沃尔多·丹尼尔斯) | 1st | 25.0 | 女性 | 0 | 1 |
数据框不需要数值索引。我们可以将数据框的索引设置为任何唯一的值,比如乘客的姓名,然后通过姓名选择行:
# Set index
dataframe = dataframe.set_index(dataframe['Name'])
# Show row
dataframe.loc['Allen, Miss Elisabeth Walton']
Name Allen, Miss Elisabeth Walton
PClass 1st
Age 29
Sex female
Survived 1
SexCode 1
Name: Allen, Miss Elisabeth Walton, dtype: object
讨论
pandas 数据框中的所有行都有唯一的索引值。默认情况下,这个索引是一个整数,表示数据框中的行位置;然而,并不一定要这样。数据框索引可以设置为唯一的字母数字字符串或客户号码。为了选择单独的行和行的切片,pandas 提供了两种方法:
-
loc在数据框的索引是标签时非常有用(例如,字符串)。 -
iloc的工作原理是查找数据框中的位置。例如,iloc[0]将返回数据框中的第一行,无论索引是整数还是标签。
熟悉loc和iloc在数据清洗过程中非常有用。
3.4 根据条件选择行
问题
您希望根据某些条件选择数据框的行。
解决方案
这在 pandas 中很容易实现。例如,如果我们想要选择所有泰坦尼克号上的女性:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'
# Load data
dataframe = pd.read_csv(url)
# Show top two rows where column 'sex' is 'female'
dataframe[dataframe['Sex'] == 'female'].head(2)
| 名称 | PClass | 年龄 | 性别 | 幸存 | 性别代码 | |
|---|---|---|---|---|---|---|
| 0 | 艾伦小姐伊丽莎白·沃尔顿 | 1st | 29.0 | 女性 | 1 | 1 |
| 1 | 艾莉森小姐海伦·洛林 | 1st | 2.0 | 女性 | 0 | 1 |
请花点时间看一下这个解决方案的格式。我们的条件语句是dataframe['Sex'] == 'female';通过将其包装在dataframe[]中,我们告诉 pandas“选择数据框中dataframe['Sex']值为'female'的所有行”。这些条件导致了一个布尔值的 pandas 系列。
多条件也很容易。例如,这里我们选择所有女性且年龄在 65 岁或以上的乘客:
# Filter rows
dataframe[(dataframe['Sex'] == 'female') & (dataframe['Age'] >= 65)]
| 名称 | PClass | 年龄 | 性别 | 幸存 | 性别代码 | |
|---|---|---|---|---|---|---|
| 73 | 克罗斯比夫人爱德华·吉福德(凯瑟琳·伊丽莎白... | 1st | 69.0 | 女性 | 1 | 1 |
讨论
在数据整理中,有条件地选择和过滤数据是最常见的任务之一。您很少需要源数据的所有原始数据;相反,您只对某些子集感兴趣。例如,您可能只对特定州的商店或特定年龄段的患者记录感兴趣。
3.5 排序数值
问题
您需要按列中的值对数据框进行排序。
解决方案
使用 pandas 的 sort_values 函数:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'
# Load data
dataframe = pd.read_csv(url)
# Sort the dataframe by age, show two rows
dataframe.sort_values(by=["Age"]).head(2)
| 姓名 | PClass | 年龄 | 性别 | 幸存 | 性别代码 | |
|---|---|---|---|---|---|---|
| 763 | 迪恩,伊丽莎白·格莱迪斯(米尔文娜)小姐 | 3rd | 0.17 | 女性 | 1 | 1 |
| 751 | 丹博姆,吉尔伯特·西格瓦德·埃马纽尔大师 | 3rd | 0.33 | 男性 | 0 | 0 |
讨论
在数据分析和探索过程中,按照特定列或一组列对 DataFrame 进行排序通常非常有用。sort_values 的 by 参数接受一个列名列表,按列表中列名的顺序对 DataFrame 进行排序。
默认情况下,ascending 参数设置为 True,因此它会将值从最低到最高排序。如果我们想要最年长的乘客而不是最年轻的,我们可以将其设置为 False。
3.6 替换值
问题
您需要在 DataFrame 中替换值。
解决方案
pandas 的 replace 方法是查找和替换值的简便方法。例如,我们可以将 Sex 列中的任何 "female" 实例替换为 "Woman":
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'
# Load data
dataframe = pd.read_csv(url)
# Replace values, show two rows
dataframe['Sex'].replace("female", "Woman").head(2)
0 Woman
1 Woman
Name: Sex, dtype: object
我们还可以同时替换多个值:
# Replace "female" and "male" with "Woman" and "Man"
dataframe['Sex'].replace(["female", "male"], ["Woman", "Man"]).head(5)
0 Woman
1 Woman
2 Man
3 Woman
4 Man
Name: Sex, dtype: object
我们还可以通过指定整个 DataFrame 而不是单个列来查找并替换 DataFrame 对象中的所有值:
# Replace values, show two rows
dataframe.replace(1, "One").head(2)
| 姓名 | PClass | 年龄 | 性别 | 幸存 | 性别代码 | |
|---|---|---|---|---|---|---|
| 0 | 艾伦,伊丽莎白·沃尔顿小姐 | 1st | 29 | 女性 | One | One |
| 1 | 艾莉森,洛林小姐海伦 | 1st | 2 | 女性 | 0 | One |
replace 也接受正则表达式:
# Replace values, show two rows
dataframe.replace(r"1st", "First", regex=True).head(2)
| 姓名 | PClass | 年龄 | 性别 | 幸存 | 性别代码 | |
|---|---|---|---|---|---|---|
| 0 | 艾伦,伊丽莎白·沃尔顿小姐 | First | 29.0 | 女性 | 1 | 1 |
| 1 | 艾莉森,洛林小姐海伦 | First | 2.0 | 女性 | 0 | 1 |
讨论
replace 是我们用来替换值的工具。它简单易用,同时能够接受正则表达式。
3.7 重命名列
问题
您想要在 pandas DataFrame 中重命名列。
解决方案
使用 rename 方法重命名列:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'
# Load data
dataframe = pd.read_csv(url)
# Rename column, show two rows
dataframe.rename(columns={'PClass': 'Passenger Class'}).head(2)
| 姓名 | 乘客等级 | 年龄 | 性别 | 幸存 | 性别代码 | |
|---|---|---|---|---|---|---|
| 0 | 艾伦,伊丽莎白·沃尔顿小姐 | 1st | 29.0 | 女性 | 1 | 1 |
| 1 | 艾莉森,洛林小姐海伦 | 1st | 2.0 | 女性 | 0 | 1 |
注意,rename 方法可以接受一个字典作为参数。我们可以使用字典一次性更改多个列名:
# Rename columns, show two rows
dataframe.rename(columns={'PClass': 'Passenger Class', 'Sex': 'Gender'}).head(2)
| 姓名 | 乘客等级 | 年龄 | 性别 | 幸存 | 性别代码 | |
|---|---|---|---|---|---|---|
| 0 | 艾伦,伊丽莎白·沃尔顿小姐 | 1st | 29.0 | 女性 | 1 | 1 |
| 1 | 艾莉森,洛林小姐海伦 | 1st | 2.0 | 女性 | 0 | 1 |
讨论
使用将字典作为参数传递给 columns 参数的 rename 是我首选的重命名列的方法,因为它适用于任意数量的列。 如果我们想一次重命名所有列,这段有用的代码片段会创建一个以旧列名作为键、空字符串作为值的字典:
# Load library
import collections
# Create dictionary
column_names = collections.defaultdict(str)
# Create keys
for name in dataframe.columns:
column_names[name]
# Show dictionary
column_names
defaultdict(str,
{'Age': '',
'Name': '',
'PClass': '',
'Sex': '',
'SexCode': '',
'Survived': ''})
3.8 寻找最小值、最大值、总和、平均值和计数
问题
您想要找到数值列的最小值、最大值、总和、平均值或计数。
解决方案
pandas 提供了一些内置方法,用于常用的描述统计,如 min、max、mean、sum 和 count:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'
# Load data
dataframe = pd.read_csv(url)
# Calculate statistics
print('Maximum:', dataframe['Age'].max())
print('Minimum:', dataframe['Age'].min())
print('Mean:', dataframe['Age'].mean())
print('Sum:', dataframe['Age'].sum())
print('Count:', dataframe['Age'].count())
Maximum: 71.0
Minimum: 0.17
Mean: 30.397989417989415
Sum: 22980.879999999997
Count: 756
讨论
除了解决方案中使用的统计数据外,pandas 还提供了方差(var)、标准差(std)、峰度(kurt)、偏度(skew)、均值标准误(sem)、众数(mode)、中位数(median)、值计数以及其他几种统计数据。
此外,我们还可以将这些方法应用于整个 DataFrame:
# Show counts
dataframe.count()
Name 1313
PClass 1313
Age 756
Sex 1313
Survived 1313
SexCode 1313
dtype: int64
3.9 寻找唯一值
问题
您想选择某一列中的所有唯一值。
解决方案
使用 unique 查看列中所有唯一值的数组:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'
# Load data
dataframe = pd.read_csv(url)
# Select unique values
dataframe['Sex'].unique()
array(['female', 'male'], dtype=object)
或者,value_counts 将显示所有唯一值及其出现次数:
# Show counts
dataframe['Sex'].value_counts()
male 851
female 462
Name: Sex, dtype: int64
讨论
unique 和 value_counts 对于操作和探索分类列非常有用。 在分类列中,通常需要在数据整理阶段处理类别。 例如,在 Titanic 数据集中,PClass 是指乘客票的类别。 在 Titanic 上有三个等级; 但是,如果我们使用 value_counts,我们会发现一个问题:
# Show counts
dataframe['PClass'].value_counts()
3rd 711
1st 322
2nd 279
* 1
Name: PClass, dtype: int64
尽管几乎所有乘客都属于预期的三个类别之一,但是有一个乘客的类别是 *。 在处理这类问题时有多种策略,我们将在第五章中进行讨论,但现在只需意识到,在分类数据中,“额外”类别是常见的,不应忽略。
最后,如果我们只想计算唯一值的数量,我们可以使用 nunique:
# Show number of unique values
dataframe['PClass'].nunique()
4
3.10 处理缺失值
问题
您想要选择 DataFrame 中的缺失值。
解决方案
isnull 和 notnull 返回布尔值,指示值是否缺失:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'
# Load data
dataframe = pd.read_csv(url)
## Select missing values, show two rows
dataframe[dataframe['Age'].isnull()].head(2)
| 名称 | 舱位 | 年龄 | 性别 | 幸存 | 性别代码 | |
|---|---|---|---|---|---|---|
| 12 | Aubert, Mrs Leontine Pauline | 1st | NaN | female | 1 | 1 |
| 13 | Barkworth, Mr Algernon H | 1st | NaN | male | 1 | 0 |
讨论
缺失值是数据整理中普遍存在的问题,然而许多人低估了处理缺失数据的难度。 pandas 使用 NumPy 的 NaN(非数值)值表示缺失值,但重要的是要注意,pandas 中没有完全本地实现 NaN。 例如,如果我们想用包含 male 的所有字符串替换缺失值,我们会得到一个错误:
# Attempt to replace values with NaN
dataframe['Sex'] = dataframe['Sex'].replace('male', NaN)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-7-5682d714f87d> in <module>()
1 # Attempt to replace values with NaN
----> 2 dataframe['Sex'] = dataframe['Sex'].replace('male', NaN)
NameError: name 'NaN' is not defined
---------------------------------------------------------------------------
要完全使用 NaN 的功能,我们首先需要导入 NumPy 库:
# Load library
import numpy as np
# Replace values with NaN
dataframe['Sex'] = dataframe['Sex'].replace('male', np.nan)
很多时候,数据集使用特定的值来表示缺失的观察值,例如 NONE、-999 或 ..。pandas 的 read_csv 函数包括一个参数,允许我们指定用于指示缺失值的值:
# Load data, set missing values
dataframe = pd.read_csv(url, na_values=[np.nan, 'NONE', -999])
我们还可以使用 pandas 的 fillna 函数来填充列的缺失值。在这里,我们使用 isna 函数显示 Age 为空的位置,然后用乘客的平均年龄填充这些值。
# Get a single null row
null_entry = dataframe[dataframe["Age"].isna()].head(1)
print(null_entry)
| 姓名 | 舱位 | 年龄 | 性别 | 幸存 | 性别编码 | |
|---|---|---|---|---|---|---|
| 12 | 奥贝特,利昂汀·波琳娜夫人 | 1st | NaN | 女性 | 1 | 1 |
# Fill all null values with the mean age of passengers
null_entry.fillna(dataframe["Age"].mean())
| 姓名 | 舱位 | 年龄 | 性别 | 幸存 | 性别编码 | |
|---|---|---|---|---|---|---|
| 12 | 奥贝特,利昂汀·波琳娜夫人 | 1st | 30.397989 | 女性 | 1 | 1 |
3.11 删除列
问题
您想要从您的 DataFrame 中删除一列。
解决方案
删除列的最佳方法是使用带有参数 axis=1(即列轴)的 drop:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'
# Load data
dataframe = pd.read_csv(url)
# Delete column
dataframe.drop('Age', axis=1).head(2)
| 姓名 | 舱位 | 性别 | 幸存 | 性别编码 | |
|---|---|---|---|---|---|
| 0 | 艾伦,伊丽莎白·沃尔顿小姐 | 1st | 女性 | 1 | 1 |
| 1 | 艾莉森,露萍小姐 | 1st | 女性 | 0 | 1 |
你也可以使用列名的列表作为删除多列的主要参数:
# Drop columns
dataframe.drop(['Age', 'Sex'], axis=1).head(2)
| 姓名 | 舱位 | 幸存 | 性别编码 | |
|---|---|---|---|---|
| 0 | 艾伦,伊丽莎白·沃尔顿小姐 | 1st | 1 | 1 |
| 1 | 艾莉森,露萍小姐 | 1st | 0 | 1 |
如果列没有名称(有时可能会发生),您可以使用 dataframe.columns 按其列索引删除它:
# Drop column
dataframe.drop(dataframe.columns[1], axis=1).head(2)
| 姓名 | 年龄 | 性别 | 幸存 | 性别编码 | |
|---|---|---|---|---|---|
| 0 | 艾伦,伊丽莎白·沃尔顿小姐 | 29.0 | 女性 | 1 | 1 |
| 1 | 艾莉森,露萍小姐 | 2.0 | 女性 | 0 | 1 |
讨论
drop 是删除列的成语方法。另一种方法是 del dataframe['Age'],大多数情况下可以工作,但不建议使用,因为它在 pandas 中的调用方式(其细节超出本书的范围)。
我建议您避免使用 pandas 的 inplace=True 参数。许多 pandas 方法包括一个 inplace 参数,当设置为 True 时,直接编辑 DataFrame。这可能会导致在更复杂的数据处理管道中出现问题,因为我们将 DataFrame 视为可变对象(从技术上讲确实如此)。我建议将 DataFrame 视为不可变对象。例如:
# Create a new DataFrame
dataframe_name_dropped = dataframe.drop(dataframe.columns[0], axis=1)
在这个例子中,我们没有改变 DataFrame dataframe,而是创建了一个新的 DataFrame,称为 dataframe_name_dropped,它是 dataframe 的修改版本。如果您将 DataFrame 视为不可变对象,那么您将会在将来避免很多麻烦。
3.12 删除行
问题
您想要从 DataFrame 中删除一行或多行。
解决方案
使用布尔条件创建一个新的 DataFrame,排除你想要删除的行:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'
# Load data
dataframe = pd.read_csv(url)
# Delete rows, show first three rows of output
dataframe[dataframe['Sex'] != 'male'].head(3)
| 姓名 | 舱位 | 年龄 | 性别 | 幸存 | 性别编码 | |
|---|---|---|---|---|---|---|
| 0 | Allen, Miss Elisabeth Walton | 1st | 29.0 | 女性 | 1 | 1 |
| 1 | Allison, Miss Helen Loraine | 1st | 2.0 | 女性 | 0 | 1 |
| 3 | Allison, Mrs Hudson JC (Bessie Waldo Daniels) | 1st | 25.00 | 女性 | 0 | 1 |
讨论
技术上你可以使用drop方法(例如,dataframe.drop([0, 1], axis=0)来删除前两行),但更实用的方法是简单地将布尔条件包装在dataframe[]中。这使我们能够利用条件语句的威力来删除单行或(更有可能)多行。
我们可以使用布尔条件轻松删除单行,通过匹配唯一值:
# Delete row, show first two rows of output
dataframe[dataframe['Name'] != 'Allison, Miss Helen Loraine'].head(2)
| 姓名 | 票类 | 年龄 | 性别 | 生存 | 性别编码 | |
|---|---|---|---|---|---|---|
| 0 | Allen, Miss Elisabeth Walton | 1st | 29.0 | 女性 | 1 | 1 |
| 2 | Allison, Mr Hudson Joshua Creighton | 1st | 30.0 | 男性 | 0 | 0 |
我们甚至可以通过指定行索引来使用它删除单行:
# Delete row, show first two rows of output
dataframe[dataframe.index != 0].head(2)
| 姓名 | 票类 | 年龄 | 性别 | 生存 | 性别编码 | |
|---|---|---|---|---|---|---|
| 1 | Allison, Miss Helen Loraine | 1st | 2.0 | 女性 | 0 | 1 |
| 2 | Allison, Mr Hudson Joshua Creighton | 1st | 30.0 | 男性 | 0 | 0 |
3.13 删除重复行
问题
您想从 DataFrame 中删除重复的行。
解决方案
使用drop_duplicates,但要注意参数:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'
# Load data
dataframe = pd.read_csv(url)
# Drop duplicates, show first two rows of output
dataframe.drop_duplicates().head(2)
| 姓名 | 票类 | 年龄 | 性别 | 生存 | 性别编码 | |
|---|---|---|---|---|---|---|
| 0 | Allen, Miss Elisabeth Walton | 1st | 29.0 | 女性 | 1 | 1 |
| 1 | Allison, Miss Helen Loraine | 1st | 2.0 | 女性 | 0 | 1 |
讨论
一个敏锐的读者会注意到,解决方案实际上并没有删除任何行:
# Show number of rows
print("Number Of Rows In The Original DataFrame:", len(dataframe))
print("Number Of Rows After Deduping:", len(dataframe.drop_duplicates()))
Number Of Rows In The Original DataFrame: 1313
Number Of Rows After Deduping: 1313
这是因为drop_duplicates默认仅删除所有列完全匹配的行。因为我们 DataFrame 中的每一行都是唯一的,所以不会被删除。然而,通常我们只想考虑部分列来检查重复行。我们可以使用subset参数来实现这一点:
# Drop duplicates
dataframe.drop_duplicates(subset=['Sex'])
| 姓名 | 票类 | 年龄 | 性别 | 生存 | 性别编码 | |
|---|---|---|---|---|---|---|
| 0 | Allen, Miss Elisabeth Walton | 1st | 29.0 | 女性 | 1 | 1 |
| 2 | Allison, Mr Hudson Joshua Creighton | 1st | 30.0 | 男性 | 0 | 0 |
仔细观察上述输出:我们告诉drop_duplicates仅考虑具有相同Sex值的任意两行为重复行,并将其删除。现在我们只剩下两行的 DataFrame:一个女性和一个男性。你可能会问为什么drop_duplicates决定保留这两行而不是两行不同的行。答案是drop_duplicates默认保留重复行的第一次出现并丢弃其余的。我们可以使用keep参数来控制这种行为:
# Drop duplicates
dataframe.drop_duplicates(subset=['Sex'], keep='last')
| 姓名 | 票类 | 年龄 | 性别 | 生存 | 性别编码 | |
|---|---|---|---|---|---|---|
| 1307 | Zabour, Miss Tamini | 3rd | NaN | 女性 | 0 | 1 |
| 1312 | Zimmerman, Leo | 3rd | 29.0 | 男性 | 0 | 0 |
一个相关的方法是duplicated,它返回一个布尔系列,指示行是否是重复的。如果您不想简单地删除重复项,这是一个不错的选择:
dataframe.duplicated()
0 False
1 False
2 False
3 False
4 False
...
1308 False
1309 False
1310 False
1311 False
1312 False
Length: 1313, dtype: bool
3.14 按值分组行
问题
您希望根据某些共享值对单独的行进行分组。
解决方案
groupby是 pandas 中最强大的特性之一:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'
# Load data
dataframe = pd.read_csv(url)
# Group rows by the values of the column 'Sex', calculate mean # of each group
dataframe.groupby('Sex').mean(numeric_only=True)
| 性别 | 年龄 | 幸存者 | 性别代码 |
|---|---|---|---|
| 女性 | 29.396424 | 0.666667 | 1.0 |
| 男性 | 31.014338 | 0.166863 | 0.0 |
讨论
groupby是数据处理真正开始成形的地方。DataFrame 中每行代表一个人或事件是非常普遍的,我们想根据某些标准对它们进行分组,然后计算统计量。例如,您可以想象一个 DataFrame,其中每行是全国餐厅连锁店的单笔销售,我们想要每个餐厅的总销售额。我们可以通过按独立餐厅分组行,然后计算每组的总和来实现这一点。
新用户对groupby经常写出这样的一行,然后对返回的内容感到困惑:
# Group rows
dataframe.groupby('Sex')
<pandas.core.groupby.DataFrameGroupBy object at 0x10efacf28>
为什么它没有返回更有用的东西?原因是groupby需要与我们想应用于每个组的某些操作配对,比如计算聚合统计(例如均值、中位数、总和)。在讨论分组时,我们经常使用简写说“按性别分组”,但这是不完整的。为了使分组有用,我们需要按某些标准分组,然后对每个组应用函数:
# Group rows, count rows
dataframe.groupby('Survived')['Name'].count()
Survived
0 863
1 450
Name: Name, dtype: int64
注意在groupby后添加了Name?这是因为特定的摘要统计只对某些类型的数据有意义。例如,按性别计算平均年龄是有意义的,但按性别计算总年龄不是。在这种情况下,我们将数据分组为幸存或未幸存,然后计算每个组中的名称数量(即乘客数)。
我们还可以按第一列分组,然后按第二列对该分组进行分组:
# Group rows, calculate mean
dataframe.groupby(['Sex','Survived'])['Age'].mean()
Sex Survived
female 0 24.901408
1 30.867143
male 0 32.320780
1 25.951875
Name: Age, dtype: float64
3.15 按时间分组行
问题
您需要按时间段对单独的行进行分组。
解决方案
使用resample按时间段分组行:
# Load libraries
import pandas as pd
import numpy as np
# Create date range
time_index = pd.date_range('06/06/2017', periods=100000, freq='30S')
# Create DataFrame
dataframe = pd.DataFrame(index=time_index)
# Create column of random values
dataframe['Sale_Amount'] = np.random.randint(1, 10, 100000)
# Group rows by week, calculate sum per week
dataframe.resample('W').sum()
| 销售金额 | |
|---|---|
| 2017-06-11 | 86423 |
| 2017-06-18 | 101045 |
| 2017-06-25 | 100867 |
| 2017-07-02 | 100894 |
| 2017-07-09 | 100438 |
| 2017-07-16 | 10297 |
讨论
我们的标准Titanic数据集不包含日期时间列,因此对于这个示例,我们生成了一个简单的 DataFrame,其中每行代表一次单独的销售。对于每个销售,我们知道其日期时间和金额(这些数据并不真实,因为销售间隔正好为 30 秒,金额为确切的美元数,但为了简单起见,我们假装是这样的)。
原始数据如下所示:
# Show three rows
dataframe.head(3)
| 销售金额 | |
|---|---|
| 2017-06-06 00:00:00 | 7 |
| 2017-06-06 00:00:30 | 2 |
| 2017-06-06 00:01:00 | 7 |
注意每次销售的日期和时间是 DataFrame 的索引;这是因为resample需要索引是类似日期时间的值。
使用resample,我们可以按照各种时间段(偏移)对行进行分组,然后可以在每个时间组上计算统计信息:
# Group by two weeks, calculate mean
dataframe.resample('2W').mean()
| Sale_Amount | |
|---|---|
| 2017-06-11 | 5.001331 |
| 2017-06-25 | 5.007738 |
| 2017-07-09 | 4.993353 |
| 2017-07-23 | 4.950481 |
# Group by month, count rows
dataframe.resample('M').count()
| | Sale_Amount | | --- | --- | --- | | 2017-06-30 | 72000 | | 2017-07-31 | 28000 |
您可能注意到,在这两个输出中,日期时间索引是日期,即使我们按周和月进行分组。原因是默认情况下,resample返回时间组的右“边缘”标签(最后一个标签)。我们可以使用label参数控制此行为:
# Group by month, count rows
dataframe.resample('M', label='left').count()
| Sale_Amount | |
|---|---|
| 2017-05-31 | 72000 |
| 2017-06-30 | 28000 |
另请参阅
3.16 聚合操作和统计
问题
您需要对数据框中的每列(或一组列)进行聚合操作。
解决方案
使用 pandas 的agg方法。在这里,我们可以轻松地获得每列的最小值:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'
# Load data
dataframe = pd.read_csv(url)
# Get the minimum of every column
dataframe.agg("min")
Name Abbing, Mr Anthony
PClass *
Age 0.17
Sex female
Survived 0
SexCode 0
dtype: object
有时,我们希望将特定函数应用于特定列集:
# Mean Age, min and max SexCode
dataframe.agg({"Age":["mean"], "SexCode":["min", "max"]})
| Age | SexCode | |
|---|---|---|
| mean | 30.397989 | NaN |
| min | NaN | 0.0 |
| max | NaN | 1.0 |
我们还可以将聚合函数应用于组,以获取更具体的描述性统计信息:
# Number of people who survived and didn't survive in each class
dataframe.groupby(
["PClass","Survived"]).agg({"Survived":["count"]}
).reset_index()
| PClass | Survived | Count | |
|---|---|---|---|
| 0 | * | 0 | 1 |
| 1 | 1st | 0 | 129 |
| 2 | 1st | 1 | 193 |
| 3 | 2nd | 0 | 160 |
| 4 | 2nd | 1 | 119 |
| 5 | 3rd | 0 | 573 |
| 6 | 3rd | 1 | 138 |
讨论
在探索性数据分析中,聚合函数特别有用,可用于了解数据的不同子群体和变量之间的关系。通过对数据进行分组并应用聚合统计,您可以查看数据中的模式,这些模式在机器学习或特征工程过程中可能会很有用。虽然视觉图表也很有帮助,但有这样具体的描述性统计数据作为参考,有助于更好地理解数据。
另请参阅
3.17 遍历列
问题
您希望遍历列中的每个元素并应用某些操作。
解决方案
您可以像对待 Python 中的任何其他序列一样处理 pandas 列,并使用标准 Python 语法对其进行循环:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'
# Load data
dataframe = pd.read_csv(url)
# Print first two names uppercased
for name in dataframe['Name'][0:2]:
print(name.upper())
ALLEN, MISS ELISABETH WALTON
ALLISON, MISS HELEN LORAINE
讨论
除了循环(通常称为for循环)之外,我们还可以使用列表推导:
# Show first two names uppercased
[name.upper() for name in dataframe['Name'][0:2]]
['ALLEN, MISS ELISABETH WALTON', 'ALLISON, MISS HELEN LORAINE']
尽管有诱惑使用for循环,更符合 Python 风格的解决方案应该使用 pandas 的apply方法,详见配方 3.18。
3.18 在列中的所有元素上应用函数
问题
您希望在一列的所有元素上应用某些函数。
解决方案
使用apply在列的每个元素上应用内置或自定义函数:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'
# Load data
dataframe = pd.read_csv(url)
# Create function
def uppercase(x):
return x.upper()
# Apply function, show two rows
dataframe['Name'].apply(uppercase)[0:2]
0 ALLEN, MISS ELISABETH WALTON
1 ALLISON, MISS HELEN LORAINE
Name: Name, dtype: object
讨论
apply是进行数据清理和整理的好方法。通常会编写一个函数来执行一些有用的操作(将名字分开,将字符串转换为浮点数等),然后将该函数映射到列中的每个元素。
3.19 对组应用函数
问题
您已经使用groupby对行进行分组,并希望对每个组应用函数。
解决方案
结合groupby和apply:
# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'
# Load data
dataframe = pd.read_csv(url)
# Group rows, apply function to groups
dataframe.groupby('Sex').apply(lambda x: x.count())
| 性别 | 姓名 | 舱位 | 年龄 | 性别 | 幸存 | 性别编码 |
|---|---|---|---|---|---|---|
| 女性 | 462 | 462 | 288 | 462 | 462 | 462 |
| 男性 | 851 | 851 | 468 | 851 | 851 | 851 |
讨论
在 Recipe 3.18 中提到了apply。当你想对分组应用函数时,apply特别有用。通过结合groupby和apply,我们可以计算自定义统计信息或将任何函数分别应用于每个组。
3.20 合并数据框
问题
您想要将两个数据框连接在一起。
解决方案
使用concat和axis=0沿行轴进行连接:
# Load library
import pandas as pd
# Create DataFrame
data_a = {'id': ['1', '2', '3'],
'first': ['Alex', 'Amy', 'Allen'],
'last': ['Anderson', 'Ackerman', 'Ali']}
dataframe_a = pd.DataFrame(data_a, columns = ['id', 'first', 'last'])
# Create DataFrame
data_b = {'id': ['4', '5', '6'],
'first': ['Billy', 'Brian', 'Bran'],
'last': ['Bonder', 'Black', 'Balwner']}
dataframe_b = pd.DataFrame(data_b, columns = ['id', 'first', 'last'])
# Concatenate DataFrames by rows
pd.concat([dataframe_a, dataframe_b], axis=0)
| id | first | last | |
|---|---|---|---|
| 0 | 1 | Alex | Anderson |
| 1 | 2 | Amy | Ackerman |
| 2 | 3 | Allen | Ali |
| 0 | 4 | Billy | Bonder |
| 1 | 5 | Brian | Black |
| 2 | 6 | Bran | Balwner |
您可以使用axis=1沿列轴进行连接:
# Concatenate DataFrames by columns
pd.concat([dataframe_a, dataframe_b], axis=1)
| id | first | last | id | first | last | |
|---|---|---|---|---|---|---|
| 0 | 1 | Alex | Anderson | 4 | Billy | Bonder |
| 1 | 2 | Amy | Ackerman | 5 | Brian | Black |
| 2 | 3 | Allen | Ali | 6 | Bran | Balwner |
讨论
合并通常是一个你在计算机科学和编程领域听得较多的词汇,所以如果你以前没听说过,别担心。concatenate的非正式定义是将两个对象粘合在一起。在解决方案中,我们使用axis参数将两个小数据框粘合在一起,以指示我们是否想要将两个数据框叠加在一起还是并排放置它们。
3.21 合并数据框
问题
您想要合并两个数据框。
解决方案
要进行内连接,使用merge并使用on参数指定要合并的列:
# Load library
import pandas as pd
# Create DataFrame
employee_data = {'employee_id': ['1', '2', '3', '4'],
'name': ['Amy Jones', 'Allen Keys', 'Alice Bees',
'Tim Horton']}
dataframe_employees = pd.DataFrame(employee_data, columns = ['employee_id',
'name'])
# Create DataFrame
sales_data = {'employee_id': ['3', '4', '5', '6'],
'total_sales': [23456, 2512, 2345, 1455]}
dataframe_sales = pd.DataFrame(sales_data, columns = ['employee_id',
'total_sales'])
# Merge DataFrames
pd.merge(dataframe_employees, dataframe_sales, on='employee_id')
| 员工编号 | 姓名 | 总销售额 | |
|---|---|---|---|
| 0 | 3 | Alice Bees | 23456 |
| 1 | 4 | Tim Horton | 2512 |
merge默认为内连接。如果我们想进行外连接,可以使用how参数来指定:
# Merge DataFrames
pd.merge(dataframe_employees, dataframe_sales, on='employee_id', how='outer')
| 员工编号 | 姓名 | 总销售额 | |
|---|---|---|---|
| 0 | 1 | Amy Jones | NaN |
| 1 | 2 | Allen Keys | NaN |
| 2 | 3 | Alice Bees | 23456.0 |
| 3 | 4 | Tim Horton | 2512.0 |
| 4 | 5 | NaN | 2345.0 |
| 5 | 6 | NaN | 1455.0 |
同一个参数可以用来指定左连接和右连接:
# Merge DataFrames
pd.merge(dataframe_employees, dataframe_sales, on='employee_id', how='left')
| 员工编号 | 姓名 | 总销售额 | |
|---|---|---|---|
| 0 | 1 | Amy Jones | NaN |
| 1 | 2 | Allen Keys | NaN |
| 2 | 3 | Alice Bees | 23456.0 |
| 3 | 4 | Tim Horton | 2512.0 |
我们还可以在每个数据框中指定要合并的列名:
# Merge DataFrames
pd.merge(dataframe_employees,
dataframe_sales,
left_on='employee_id',
right_on='employee_id')
| 员工编号 | 姓名 | 总销售额 | |
|---|---|---|---|
| 0 | 3 | Alice Bees | 23456 |
| 1 | 4 | Tim Horton | 2512 |
如果我们希望不是在两个列上进行合并,而是在每个 DataFrame 的索引上进行合并,我们可以将left_on和right_on参数替换为left_index=True和right_index=True。
讨论
我们需要使用的数据通常很复杂;它不总是一次性出现。相反,在现实世界中,我们通常面对来自多个数据库查询或文件的不同数据集。为了将所有数据汇总到一个地方,我们可以将每个数据查询或数据文件作为单独的 DataFrame 加载到 pandas 中,然后将它们合并成一个单一的 DataFrame。
这个过程对于使用过 SQL 的人可能会很熟悉,SQL 是一种用于执行合并操作(称为连接)的流行语言。虽然 pandas 使用的确切参数会有所不同,但它们遵循其他软件语言和工具使用的相同一般模式。
任何merge操作都有三个方面需要指定。首先,我们必须指定要合并的两个 DataFrame。在解决方案中,我们将它们命名为dataframe_employees和dataframe_sales。其次,我们必须指定要合并的列名(们)-即,两个 DataFrame 之间共享值的列名。例如,在我们的解决方案中,两个 DataFrame 都有一个名为employee_id的列。为了合并这两个 DataFrame,我们将匹配每个 DataFrame 的employee_id列中的值。如果这两个列使用相同的名称,我们可以使用on参数。但是,如果它们有不同的名称,我们可以使用left_on和right_on。
什么是左 DataFrame 和右 DataFrame?左 DataFrame 是我们在merge中指定的第一个 DataFrame,右 DataFrame 是第二个。这种语言在我们将需要的下一组参数中再次出现。
最后一个方面,也是一些人难以掌握的最难的方面,是我们想要执行的合并操作的类型。这由how参数指定。merge支持四种主要类型的连接操作:
内部
仅返回在两个 DataFrame 中都匹配的行(例如,返回任何在dataframe_employees和dataframe_sales中的employee_id值都出现的行)。
外部
返回两个 DataFrame 中的所有行。如果一行存在于一个 DataFrame 中但不在另一个 DataFrame 中,则填充缺失值 NaN(例如,在dataframe_employee和dataframe_sales中返回所有行)。
左
返回左 DataFrame 中的所有行,但只返回与左 DataFrame 匹配的右 DataFrame 中的行。对于缺失值填充NaN(例如,从dataframe_employees返回所有行,但只返回dataframe_sales中具有出现在dataframe_employees中的employee_id值的行)。
右
返回右侧数据框的所有行,但仅返回左侧数据框中与右侧数据框匹配的行。对于缺失的值填充NaN(例如,返回dataframe_sales的所有行,但仅返回dataframe_employees中具有出现在dataframe_sales中的employee_id值的行)。
如果你还没有完全理解,我鼓励你在你的代码中尝试调整how参数,看看它如何影响merge返回的结果。