5 Getting Started with pandas
pandas 将成为本书其余大部分内容中令人感兴趣的主要工具。它包含数据结构和数据操作工具,旨在使 Python 中的数据清理和分析变得快速、方便。pandas 通常与 NumPy 和 SciPy 等数值计算工具、statsmodels 和 scikit-learn 等分析库以及 matplotlib 等数据可视化库配合使用。pandas 采用了 NumPy 的基于数组的计算惯用风格的重要部分,特别是基于数组的函数以及对不使用 for 循环的数据处理的偏好。
虽然 pandas 采用了 NumPy 的许多编码习惯,但最大的区别是 pandas 是为处理表格或异构数据而设计的。相比之下,NumPy 最适合处理同质类型的数值数组数据。
自 2010 年成为开源项目以来,pandas 已经发展成为一个相当大的库,适用于广泛的实际用例。开发者社区已发展到超过 2,500 名不同的贡献者,他们一直在帮助构建该项目,并使用该项目解决日常数据问题。充满活力的 pandas 开发者和用户社区是其成功的关键部分。
很多人不知道,自 2013 年以来,我就没有积极参与 pandas 的日常开发;从那时起,它就成为一个完全由社区管理的项目。请务必向核心开发人员和所有贡献者的辛勤工作表示感谢!
在本书的其余部分中,我对 NumPy 和 pandas 使用以下导入约定:
In [1]: import numpy as np
In [2]: import pandas as pd因此,每当你在代码中看到 pd.,它指的是 pandas。您可能还会发现将 Series 和 DataFrame 导入本地命名空间更容易,因为它们使用得非常频繁:
In [3]: from pandas import Series, DataFrame5.1 Introduction to pandas Data Structures
要开始使用 pandas,您需要熟悉它的两种主力数据结构:Series 和 DataFrame。虽然它们并不是解决所有问题的通用解决方案,但它们为各种数据任务提供了坚实的基础。
5.1.1 Series
Series 是一个类似一维数组的对象,包含相同类型的值序列(与 NumPy 类型类似)和关联的数据标签数组(称为 index)。最简单的 Series 仅由数据数组组成:
In [14]: obj = pd.Series([4, 7, -5, 3])
In [15]: obj
Out[15]:
0 4
1 7
2 -5
3 3
dtype: int64以交互方式显示的 Series 的字符串表示形式在左侧显示 index,在右侧显示值。由于我们没有为数据指定 index,因此会创建一个由整数 0 到 N - 1(其中 N 是数据的长度)组成的默认索引。您可以分别通过其 array 和 index 属性获取 Series 的 array 表示形式和 index 对象:
In [16]: obj.array
Out[16]:
<PandasArray>
[4, 7, -5, 3]
Length: 4, dtype: int64
In [17]: obj.index
Out[17]: RangeIndex(start=0, stop=4, step=1).array 属性的结果是一个 PandasArray,它通常包装一个 NumPy 数组,但也可以包含特殊的扩展数组类型,这将在 Ch 7.3: Extension Data Types 中详细讨论。
通常,您需要创建一个带有 index 的 Series,该 index 用标签标识每个数据点:
In [18]: obj2 = pd.Series([4, 7, -5, 3], index=["d", "b", "a", "c"])
In [19]: obj2
Out[19]:
d 4
b 7
a -5
c 3
dtype: int64
In [20]: obj2.index
Out[20]: Index(['d', 'b', 'a', 'c'], dtype='object')与 NumPy 数组相比,在选择单个值或一组值时可以在 index 中使用标签:
In [21]: obj2["a"]
Out[21]: -5
In [22]: obj2["d"] = 6
In [23]: obj2[["c", "a", "d"]]
Out[23]:
c 3
a -5
d 6
dtype: int64这里 ["c", "a", "d"] 被解释为索引列表,即使它包含字符串而不是整数。
使用 NumPy 函数或类似 NumPy 的操作(例如使用布尔数组进行过滤、标量乘法或应用数学函数)将保留索引值链接:
In [24]: obj2[obj2 > 0]
Out[24]:
d 6
b 7
c 3
dtype: int64
In [25]: obj2 * 2
Out[25]:
d 12
b 14
a -10
c 6
dtype: int64
In [26]: import numpy as np
In [27]: np.exp(obj2)
Out[27]:
d 403.428793
b 1096.633158
a 0.006738
c 20.085537
dtype: float64将 Series 视为固定长度的有序字典的另一种方式,因为它是索引值到数据值的映射。它可以在许多可能使用字典的情况下使用:
In [28]: "b" in obj2
Out[28]: True
In [29]: "e" in obj2
Out[29]: False如果 Python 字典中包含数据,则可以通过传递字典来创建 Series:
In [30]: sdata = {"Ohio": 35000, "Texas": 71000, "Oregon": 16000, "Utah": 5000}
In [31]: obj3 = pd.Series(sdata)
In [32]: obj3
Out[32]:
Ohio 35000
Texas 71000
Oregon 16000
Utah 5000
dtype: int64Series 可以使用其 to_dict 方法转换回字典:
In [33]: obj3.to_dict()
Out[33]: {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}当您仅传递字典时,结果 Series 中的索引将根据字典的键方法遵循键的顺序,这取决于键插入顺序。您可以通过传递带有字典键的索引来覆盖它,按照您希望它们在结果系列中出现的顺序:
In [34]: states = ["California", "Ohio", "Oregon", "Texas"]
In [35]: obj4 = pd.Series(sdata, index=states)
In [36]: obj4
Out[36]:
California NaN
Ohio 35000.0
Oregon 16000.0
Texas 71000.0
dtype: float64在这里,在 sdata 中找到的三个值被放置在适当的位置,但由于没有找到 "California" 的值,因此它显示为 NaN(Not a Number),这在 pandas 中被视为标记缺失或 NA 值。由于 "Utah" 未包含在 states 中,因此它被排除在结果对象之外。
我将交替使用术语 “missing”、“NA” 或 “null” 来指代缺失的数据。pandas 中的 isna 和 notna 函数应该用于检测丢失的数据:
In [37]: pd.isna(obj4)
Out[37]:
California True
Ohio False
Oregon False
Texas False
dtype: bool
In [38]: pd.notna(obj4)
Out[38]:
California False
Ohio True
Oregon True
Texas True
dtype: boolSeries 还有这些实例方法:
In [39]: obj4.isna()
Out[39]:
California True
Ohio False
Oregon False
Texas False
dtype: bool我在 Ch 7: Data Cleaning and Preparation 中更详细地讨论了如何处理丢失的数据。
对于许多应用程序来说,一个有用的 Series 功能是它在算术运算中按索引标签自动对齐:
In [40]: obj3
Out[40]:
Ohio 35000
Texas 71000
Oregon 16000
Utah 5000
dtype: int64
In [41]: obj4
Out[41]:
California NaN
Ohio 35000.0
Oregon 16000.0
Texas 71000.0
dtype: float64
In [42]: obj3 + obj4
Out[42]:
California NaN
Ohio 70000.0
Oregon 32000.0
Texas 142000.0
Utah NaN
dtype: float64稍后将更详细地讨论数据对齐功能。如果您有数据库方面的经验,您可以将其视为类似于连接操作。
Series 对象本身及其索引都有一个 name 属性,它与 pandas 功能的其他区域集成:
In [43]: obj4.name = "population"
In [44]: obj4.index.name = "state"
In [45]: obj4
Out[45]:
state
California NaN
Ohio 35000.0
Oregon 16000.0
Texas 71000.0
Name: population, dtype: float64Series 的索引可以通过赋值来改变:
In [46]: obj
Out[46]:
0 4
1 7
2 -5
3 3
dtype: int64
In [47]: obj.index = ["Bob", "Steve", "Jeff", "Ryan"]
In [48]: obj
Out[48]:
Bob 4
Steve 7
Jeff -5
Ryan 3
dtype: int645.1.2 DataFrame
DataFrame 表示一个矩形数据表,并包含有序的、命名的列集合,每个列可以是不同的值类型(数字、字符串、布尔值等)。DataFrame 同时具有行索引和列索引;它可以被认为是共享相同索引的 Series 的字典。
虽然 DataFrame 物理上是二维的,但您可以使用分层索引以表格格式表示更高维的数据,我们将在 Ch 8: Data Wrangling: Join, Combine, and Reshape 中讨论该主题,也是某些 pandas 中更高级的数据处理功能。
构造 DataFrame 的方法有很多,最常见的方法之一是使用等长列表或 NumPy 数组的字典:
data = {"state": ["Ohio", "Ohio", "Ohio", "Nevada", "Nevada", "Nevada"],
"year": [2000, 2001, 2002, 2001, 2002, 2003],
"pop": [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
frame = pd.DataFrame(data)与 Series 一样,生成的 DataFrame 将自动分配其索引,并且列根据数据中键的顺序放置(这取决于它们在字典中的插入顺序):
In [50]: frame
Out[50]:
state year pop
0 Ohio 2000 1.5
1 Ohio 2001 1.7
2 Ohio 2002 3.6
3 Nevada 2001 2.4
4 Nevada 2002 2.9
5 Nevada 2003 3.2如果您使用的是 Jupyter notebook,pandas DataFrame 对象将显示为对浏览器更友好的 HTML 表格。有关示例,请参见 Figure 5.1。

对于大型 DataFrame,head 方法仅选择前五行:
In [51]: frame.head()
Out[51]:
state year pop
0 Ohio 2000 1.5
1 Ohio 2001 1.7
2 Ohio 2002 3.6
3 Nevada 2001 2.4
4 Nevada 2002 2.9同样,tail 返回最后五行:
In [52]: frame.tail()
Out[52]:
state year pop
1 Ohio 2001 1.7
2 Ohio 2002 3.6
3 Nevada 2001 2.4
4 Nevada 2002 2.9
5 Nevada 2003 3.2如果指定列序列,DataFrame 的列将按该顺序排列:
In [53]: pd.DataFrame(data, columns=["year", "state", "pop"])
Out[53]:
year state pop
0 2000 Ohio 1.5
1 2001 Ohio 1.7
2 2002 Ohio 3.6
3 2001 Nevada 2.4
4 2002 Nevada 2.9
5 2003 Nevada 3.2如果您传递字典中未包含的列,则结果中将显示缺失值:
In [54]: frame2 = pd.DataFrame(data, columns=["year", "state", "pop", "debt"])
In [55]: frame2
Out[55]:
year state pop debt
0 2000 Ohio 1.5 NaN
1 2001 Ohio 1.7 NaN
2 2002 Ohio 3.6 NaN
3 2001 Nevada 2.4 NaN
4 2002 Nevada 2.9 NaN
5 2003 Nevada 3.2 NaN
In [56]: frame2.columns
Out[56]: Index(['year', 'state', 'pop', 'debt'], dtype='object')DataFrame 中的列可以通过类似字典的表示法或使用点属性表示法作为 Series 进行检索:
In [57]: frame2["state"]
Out[57]:
0 Ohio
1 Ohio
2 Ohio
3 Nevada
4 Nevada
5 Nevada
Name: state, dtype: object
In [58]: frame2.year
Out[58]:
0 2000
1 2001
2 2002
3 2001
4 2002
5 2003
Name: year, dtype: int64为了方便起见,IPython 中提供了类似属性的访问(例如,frame2.year)和制表符完成列名称。
frame2[column] 适用于任何列名称,但 frame2.column 仅当列名称是有效的 Python 变量名称并且不与 DataFrame 中的任何方法名称冲突时才有效。例如,如果列的名称包含空格或下划线以外的符号,则无法使用点属性方法访问它。
请注意,返回的 Series 与 DataFrame 具有相同的索引,并且它们的 name 属性已适当设置。
还可以使用特殊的 iloc 和 loc 属性按位置或名称检索行(稍后将在 Selection on DataFrame with loc and iloc 中详细介绍):
In [59]: frame2.loc[1]
Out[59]:
year 2001
state Ohio
pop 1.7
debt NaN
Name: 1, dtype: object
In [60]: frame2.iloc[2]
Out[60]:
year 2002
state Ohio
pop 3.6
debt NaN
Name: 2, dtype: object可以通过分配来修改列。例如,可以为空 debt 列分配标量值或值数组:
In [61]: frame2["debt"] = 16.5
In [62]: frame2
Out[62]:
year state pop debt
0 2000 Ohio 1.5 16.5
1 2001 Ohio 1.7 16.5
2 2002 Ohio 3.6 16.5
3 2001 Nevada 2.4 16.5
4 2002 Nevada 2.9 16.5
5 2003 Nevada 3.2 16.5
In [63]: frame2["debt"] = np.arange(6.)
In [64]: frame2
Out[64]:
year state pop debt
0 2000 Ohio 1.5 0.0
1 2001 Ohio 1.7 1.0
2 2002 Ohio 3.6 2.0
3 2001 Nevada 2.4 3.0
4 2002 Nevada 2.9 4.0
5 2003 Nevada 3.2 5.0将列表或数组分配给列时,值的长度必须与 DataFrame 的长度匹配。如果您分配一个 Series,它的标签将完全与 DataFrame 的索引重新对齐,在任何不存在的索引值中插入缺失值:
In [65]: val = pd.Series([-1.2, -1.5, -1.7], index=[2, 4, 5])
In [66]: frame2["debt"] = val
In [67]: frame2
Out[67]:
year state pop debt
0 2000 Ohio 1.5 NaN
1 2001 Ohio 1.7 NaN
2 2002 Ohio 3.6 -1.2
3 2001 Nevada 2.4 NaN
4 2002 Nevada 2.9 -1.5
5 2003 Nevada 3.2 -1.7分配不存在的列将创建一个新列。
del 关键字将像字典一样删除列。作为示例,我首先添加一个新的布尔值列,其中 state 列等于 "Ohio":
In [68]: frame2["eastern"] = frame2["state"] == "Ohio"
In [69]: frame2
Out[69]:
year state pop debt eastern
0 2000 Ohio 1.5 NaN True
1 2001 Ohio 1.7 NaN True
2 2002 Ohio 3.6 -1.2 True
3 2001 Nevada 2.4 NaN False
4 2002 Nevada 2.9 -1.5 False
5 2003 Nevada 3.2 -1.7 False无法使用 frame2.eastern 点属性表示法创建新列。
然后可以使用 del 方法删除该列:
In [70]: del frame2["eastern"]
In [71]: frame2.columns
Out[71]: Index(['year', 'state', 'pop', 'debt'], dtype='object')对 DataFrame 进行索引返回的列是基础数据的视图,而不是副本。因此,对 Series 的任何就地修改都将反映在 DataFrame 中。可以使用 Series 的 copy 方法显式复制该列。
另一种常见的数据形式是字典的嵌套字典:
In [72]: populations = {"Ohio": {2000: 1.5, 2001: 1.7, 2002: 3.6},
....: "Nevada": {2001: 2.4, 2002: 2.9}}如果嵌套字典传递给 DataFrame,pandas 会将外部字典键解释为列,将内部键解释为行索引:
In [73]: frame3 = pd.DataFrame(populations)
In [74]: frame3
Out[74]:
Ohio Nevada
2000 1.5 NaN
2001 1.7 2.4
2002 3.6 2.9您可以使用与 NumPy 数组类似的语法转置 DataFrame(交换行和列):
In [75]: frame3.T
Out[75]:
2000 2001 2002
Ohio 1.5 1.7 3.6
Nevada NaN 2.4 2.9请注意,如果列不全部具有相同的数据类型,转置会丢弃列数据类型,因此转置然后转回可能会丢失以前的类型信息。在这种情况下,列变成纯 Python 对象的数组。
内部字典中的键组合起来形成结果中的索引。如果指定了显式索引,则情况并非如此:
In [76]: pd.DataFrame(populations, index=[2001, 2002, 2003])
Out[76]:
Ohio Nevada
2001 1.7 2.4
2002 3.6 2.9
2003 NaN NaNSeries 字典的处理方式大致相同:
In [77]: pdata = {"Ohio": frame3["Ohio"][:-1],
....: "Nevada": frame3["Nevada"][:2]}
In [78]: pd.DataFrame(pdata)
Out[78]:
Ohio Nevada
2000 1.5 NaN
2001 1.7 2.4有关可以传递给 DataFrame 构造函数的许多内容的列表,请参阅 Table 5.1。
| Type | Notes |
|---|---|
| 2D ndarray | 数据矩阵,传递可选的行和列标签 |
| Dictionary of arrays, lists, or tuples | 每个序列成为 DataFrame 中的一列;所有序列的长度必须相同 |
| NumPy structured/record array | 被视为 “dictionary of arrays” 的情况 |
| Dictionary of Series | 每个值成为一列;如果没有传递显式索引,则每个 Series 的索引将联合在一起形成结果的行索引 |
| Dictionary of dictionaries | 每个内部字典成为一列;键被联合起来形成行索引,如 “dictionary of Series” 的情况 |
| List of dictionaries or Series | 每个项目都成为 DataFrame 中的一行;字典键或 Series 索引的并集成为 DataFrame 的列标签 |
| List of lists or tuples | 被视为 “2D ndarray” 情况 |
| Another DataFrame | 除非传递不同的索引,否则将使用 DataFrame 的索引 |
| NumPy MaskedArray | 与 “2D ndarray” 情况类似,只是 DataFrame 结果中缺少屏蔽值 |
如果 DataFrame 的 index 和 columns 设置了 name 属性,这些属性也会显示:
In [79]: frame3.index.name = "year"
In [80]: frame3.columns.name = "state"
In [81]: frame3
Out[81]:
state Ohio Nevada
year
2000 1.5 NaN
2001 1.7 2.4
2002 3.6 2.9与 Series 不同,DataFrame 没有 name 属性。DataFrame 的 to_numpy 方法将 DataFrame 中包含的数据作为二维 ndarray 返回:
In [82]: frame3.to_numpy()
Out[82]:
array([[1.5, nan],
[1.7, 2.4],
[3.6, 2.9]])如果 DataFrame 的列是不同的数据类型,则将选择返回数组的数据类型来容纳所有列:
In [83]: frame2.to_numpy()
Out[83]:
array([[2000, 'Ohio', 1.5, nan],
[2001, 'Ohio', 1.7, nan],
[2002, 'Ohio', 3.6, -1.2],
[2001, 'Nevada', 2.4, nan],
[2002, 'Nevada', 2.9, -1.5],
[2003, 'Nevada', 3.2, -1.7]], dtype=object)5.1.3 Index Objects
pandas 的 Index 对象负责保存轴标签(包括 DataFrame 的列名称)和其他元数据(如轴名称或名称)。构建 Series 或 DataFrame 时使用的任何数组或其他标签序列都会在内部转换为 Index:
In [84]: obj = pd.Series(np.arange(3), index=["a", "b", "c"])
In [85]: index = obj.index
In [86]: index
Out[86]: Index(['a', 'b', 'c'], dtype='object')
In [87]: index[1:]
Out[87]: Index(['b', 'c'], dtype='object')Index 对象是不可变的,因此用户不能修改:
index[1] = "d" # TypeError不变性使得在数据结构之间共享 Index 对象更加安全:
In [88]: labels = pd.Index(np.arange(3))
In [89]: labels
Out[89]: Index([0, 1, 2], dtype='int64')
In [90]: obj2 = pd.Series([1.5, -2.5, 0], index=labels)
In [91]: obj2
Out[91]:
0 1.5
1 -2.5
2 0.0
dtype: float64
In [92]: obj2.index is labels
Out[92]: True有些用户不会经常利用 Index 提供的功能,但由于某些操作会产生包含索引数据的结果,因此了解它们的工作原理非常重要。
除了类似于数组之外,索引的行为也类似于固定大小的集合:
In [93]: frame3
Out[93]:
state Ohio Nevada
year
2000 1.5 NaN
2001 1.7 2.4
2002 3.6 2.9
In [94]: frame3.columns
Out[94]: Index(['Ohio', 'Nevada'], dtype='object', name='state')
In [95]: "Ohio" in frame3.columns
Out[95]: True
In [96]: 2003 in frame3.index
Out[96]: False与 Python 集合不同,pandas 索引可以包含重复标签:
In [97]: pd.Index(["foo", "foo", "bar", "bar"])
Out[97]: Index(['foo', 'foo', 'bar', 'bar'], dtype='object')具有重复标签的选择将选择该标签的所有出现位置。
每个索引都有许多用于集合逻辑的方法和属性,它们回答了有关其包含的数据的其他常见问题。Table 5.2 总结了一些有用的内容。
| Method/Property | Description |
|---|---|
append() |
与其他 Index 对象连接,生成新的 Index |
difference() |
将集合差异计算为 Index |
intersection() |
计算集合交集 |
union() |
计算集并集 |
isin() |
计算布尔数组,指示每个值是否包含在传递的集合中 |
delete() |
删除索引 i 处的元素计算新索引 |
drop() |
通过删除传递的值来计算新索引 |
insert() |
通过在索引 i 处插入元素来计算新索引 |
is_monotonic |
如果每个元素大于或等于前一个元素,则返回 True |
is_unique |
如果索引没有重复值,则返回 True |
unique() |
计算索引中唯一值的数组 |
5.2 Essential Functionality
本节将引导您了解与 Series 或 DataFrame 中包含的数据进行交互的基本机制。在接下来的章节中,我们将更深入地研究使用 pandas 进行数据分析和操作的主题。本书无意作为 pandas 库的详尽文档;相反,我们将专注于让您熟悉常用的功能,而将不太常见(即更深奥)的内容留给您通过阅读在线 pandas 文档来了解更多信息。
5.2.1 Reindexing
pandas 对象的一个重要方法是 reindex,这意味着创建一个新对象,并重新排列值以与新索引对齐。考虑一个例子:
In [98]: obj = pd.Series([4.5, 7.2, -5.3, 3.6], index=["d", "b", "a", "c"])
In [99]: obj
Out[99]:
d 4.5
b 7.2
a -5.3
c 3.6
dtype: float64在此 Series 上调用 reindex 会根据新索引重新排列数据,如果尚不存在任何索引值,则会引入缺失值:
In [100]: obj2 = obj.reindex(["a", "b", "c", "d", "e"])
In [101]: obj2
Out[101]:
a -5.3
b 7.2
c 3.6
d 4.5
e NaN
dtype: float64对于时间序列等有序数据,您可能需要在重新索引时进行一些插值或填充值。 method 选项允许我们使用诸如 ffill 之类的方法来执行此操作,该方法向前填充值:
In [102]: obj3 = pd.Series(["blue", "purple", "yellow"], index=[0, 2, 4])
In [103]: obj3
Out[103]:
0 blue
2 purple
4 yellow
dtype: object
In [104]: obj3.reindex(np.arange(6), method="ffill")
Out[104]:
0 blue
1 blue
2 purple
3 purple
4 yellow
5 yellow
dtype: object使用 DataFrame,reindex 可以更改(行)索引、列或两者。当仅传递一个序列时,它会重新索引结果中的行:
In [105]: frame = pd.DataFrame(np.arange(9).reshape((3, 3)),
.....: index=["a", "c", "d"],
.....: columns=["Ohio", "Texas", "California"])
In [106]: frame
Out[106]:
Ohio Texas California
a 0 1 2
c 3 4 5
d 6 7 8
In [107]: frame2 = frame.reindex(index=["a", "b", "c", "d"])
In [108]: frame2
Out[108]:
Ohio Texas California
a 0.0 1.0 2.0
b NaN NaN NaN
c 3.0 4.0 5.0
d 6.0 7.0 8.0可以使用 columns 关键字对列重新索引:
In [109]: states = ["Texas", "Utah", "California"]
In [110]: frame.reindex(columns=states)
Out[110]:
Texas Utah California
a 1 NaN 2
c 4 NaN 5
d 7 NaN 8由于 "Ohio" 不在 states 内,因此该列的数据将从结果中删除。
重新索引特定轴的另一种方法是将新轴标签作为位置参数传递,然后使用 axis 关键字指定要重新索引的轴:
In [111]: frame.reindex(states, axis="columns")
Out[111]:
Texas Utah California
a 1 NaN 2
c 4 NaN 5
d 7 NaN 8有关 reindex 参数的更多信息,请参阅 Table 5.3。
| Argument | Description |
|---|---|
labels |
用作索引的新序列。可以是 Index 实例或任何其他类似序列的 Python 数据结构。索引将按原样使用,无需任何复制。 |
index |
使用传递的序列作为新的索引标签。 |
columns |
使用传递的序列作为新的列标签。 |
axis |
要重新索引的轴,无论是 "index"(行)还是 "columns"。默认值为 "index"。您可以选择执行 reindex(index=new_labels) 或 reindex(columns=new_labels)。 |
method |
插值(填充)法;"ffill" 向前填充,而 "bfill" 向后填充。 |
fill_value |
通过重新索引引入缺失数据时要使用的替代值。当您希望结果中缺少的标签具有空值时,请使用 fill_value="missing" (默认行为)。 |
limit |
前向填充或回填时,要填充的最大间隙(以元素数量为单位)。 |
tolerance |
向前填充或回填时,用于填充不精确匹配的最大尺寸间隙(以绝对数字距离表示)。 |
level |
在 MultiIndex 级别上匹配简单索引;否则选择子集。 |
copy |
如果为 True,则始终复制基础数据,即使新索引与旧索引等效;如果为 False,则当索引相等时不复制数据。 |
正如我们稍后将在 Selection on DataFrame with loc and iloc 中探讨的那样,您还可以使用 loc 运算符重新索引,并且许多用户更喜欢始终这样做。仅当所有新索引标签已存在于 DataFrame 中时,此方法才有效(而 reindex 将为新标签插入缺失的数据):
In [112]: frame.loc[["a", "d", "c"], ["California", "Texas"]]
Out[112]:
California Texas
a 2 1
d 8 7
c 5 45.2.2 Dropping Entries from an Axis
如果您已经有一个没有这些条目的索引数组或列表,则从轴中删除一个或多个条目很简单,因为您可以使用 reindex 方法或基于 .loc 的索引。由于这可能需要一些修改和设置逻辑,因此 drop 方法将返回一个新对象,其中包含从轴删除的一个或多个指示值:
In [113]: obj = pd.Series(np.arange(5.), index=["a", "b", "c", "d", "e"])
In [114]: obj
Out[114]:
a 0.0
b 1.0
c 2.0
d 3.0
e 4.0
dtype: float64
In [115]: new_obj = obj.drop("c")
In [116]: new_obj
Out[116]:
a 0.0
b 1.0
d 3.0
e 4.0
dtype: float64
In [117]: obj.drop(["d", "c"])
Out[117]:
a 0.0
b 1.0
e 4.0
dtype: float64使用 DataFrame,可以从任一轴删除索引值。为了说明这一点,我们首先创建一个示例 DataFrame:
In [118]: data = pd.DataFrame(np.arange(16).reshape((4, 4)),
.....: index=["Ohio", "Colorado", "Utah", "New York"],
.....: columns=["one", "two", "three", "four"])
In [119]: data
Out[119]:
one two three four
Ohio 0 1 2 3
Colorado 4 5 6 7
Utah 8 9 10 11
New York 12 13 14 15使用一系列标签调用 drop 将从行标签(axis 0)中删除值:
In [120]: data.drop(index=["Colorado", "Ohio"])
Out[120]:
one two three four
Utah 8 9 10 11
New York 12 13 14 15要从列中删除标签,请使用 columns 关键字:
In [121]: data.drop(columns=["two"])
Out[121]:
one three four
Ohio 0 2 3
Colorado 4 6 7
Utah 8 10 11
New York 12 14 15您还可以通过传递 axis=1 (类似于 NumPy)或 axis="columns" 来删除列中的值:
In [122]: data.drop("two", axis=1)
Out[122]:
one three four
Ohio 0 2 3
Colorado 4 6 7
Utah 8 10 11
New York 12 14 15
In [123]: data.drop(["two", "four"], axis="columns")
Out[123]:
one three
Ohio 0 2
Colorado 4 6
Utah 8 10
New York 12 145.2.3 Indexing, Selection, and Filtering
Series 索引 (obj[...]) 的工作方式与 NumPy 数组索引类似,只不过您可以使用 Series 的索引值而不仅仅是整数。以下是一些例子:
In [124]: obj = pd.Series(np.arange(4.), index=["a", "b", "c", "d"])
In [125]: obj
Out[125]:
a 0.0
b 1.0
c 2.0
d 3.0
dtype: float64
In [126]: obj["b"]
Out[126]: 1.0
In [127]: obj[1]
Out[127]: 1.0
In [128]: obj[2:4]
Out[128]:
c 2.0
d 3.0
dtype: float64
In [129]: obj[["b", "a", "d"]]
Out[129]:
b 1.0
a 0.0
d 3.0
dtype: float64
In [130]: obj[[1, 3]]
Out[130]:
b 1.0
d 3.0
dtype: float64
In [131]: obj[obj < 2]
Out[131]:
a 0.0
b 1.0
dtype: float64虽然您可以通过这种方式通过标签选择数据,但选择索引值的首选方法是使用特殊的 loc 运算符:
In [132]: obj.loc[["b", "a", "d"]]
Out[132]:
b 1.0
a 0.0
d 3.0
dtype: float64更喜欢 loc 的原因是因为用 [] 索引时对整数的处理不同。如果索引包含整数,则基于常规 [] 的索引会将整数视为标签,因此行为会根据索引的数据类型而有所不同。例如:
In [133]: obj1 = pd.Series([1, 2, 3], index=[2, 0, 1])
In [134]: obj2 = pd.Series([1, 2, 3], index=["a", "b", "c"])
In [135]: obj1
Out[135]:
2 1
0 2
1 3
dtype: int64
In [136]: obj2
Out[136]:
a 1
b 2
c 3
dtype: int64
In [137]: obj1[[0, 1, 2]]
Out[137]:
0 2
1 3
2 1
dtype: int64
In [138]: obj2[[0, 1, 2]]
Out[138]:
a 1
b 2
c 3
dtype: int64使用 loc 时,当索引不包含整数时,表达式 obj.loc[[0, 1, 2]] 将失败:
In [134]: obj2.loc[[0, 1]]
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
/tmp/ipykernel_804589/4185657903.py in <module>
----> 1 obj2.loc[[0, 1]]
^ LONG EXCEPTION ABBREVIATED ^
KeyError: "None of [Int64Index([0, 1], dtype="int64")] are in the [index]"由于 loc 运算符仅使用标签进行索引,因此还有一个 iloc 运算符仅使用整数进行索引,无论索引是否包含整数,都可以一致地工作:
In [139]: obj1.iloc[[0, 1, 2]]
Out[139]:
2 1
0 2
1 3
dtype: int64
In [140]: obj2.iloc[[0, 1, 2]]
Out[140]:
a 1
b 2
c 3
dtype: int64您还可以使用标签进行切片,但它的工作方式与普通 Python 切片不同,因为端点是包含的:
In [141]: obj2.loc["b":"c"]
Out[141]:
b 2
c 3
dtype: int64使用这些方法赋值会修改 Series 的相应部分:
In [142]: obj2.loc["b":"c"] = 5
In [143]: obj2
Out[143]:
a 1
b 5
c 5
dtype: int64尝试调用类似 loc 或 iloc 的函数而不是用方括号“索引”它们可能是一个常见的新手错误。方括号表示法用于启用切片操作并允许使用 DataFrame 对象在多个轴上进行索引。
对 DataFrame 进行索引可检索具有单个值或序列的一列或多列:
In [144]: data = pd.DataFrame(np.arange(16).reshape((4, 4)),
.....: index=["Ohio", "Colorado", "Utah", "New York"],
.....: columns=["one", "two", "three", "four"])
In [145]: data
Out[145]:
one two three four
Ohio 0 1 2 3
Colorado 4 5 6 7
Utah 8 9 10 11
New York 12 13 14 15
In [146]: data["two"]
Out[146]:
Ohio 1
Colorado 5
Utah 9
New York 13
Name: two, dtype: int64
In [147]: data[["three", "one"]]
Out[147]:
three one
Ohio 2 0
Colorado 6 4
Utah 10 8
New York 14 12像这样的索引有一些特殊情况。第一个是使用布尔数组切片或选择数据:
In [148]: data[:2]
Out[148]:
one two three four
Ohio 0 1 2 3
Colorado 4 5 6 7
In [149]: data[data["three"] > 5]
Out[149]:
one two three four
Colorado 4 5 6 7
Utah 8 9 10 11
New York 12 13 14 15提供行选择语法 data[:2] 是为了方便起见。将单个元素或列表传递给 [] 运算符会选择列。
另一个用例是使用布尔 DataFrame 进行索引,例如通过标量比较生成的索引。考虑一个 DataFrame,其中所有布尔值都是通过与标量值进行比较而生成的:
In [150]: data < 5
Out[150]:
one two three four
Ohio True True True True
Colorado True False False False
Utah False False False False
New York False False False False我们可以使用此 DataFrame 将值 0 分配给值为 True 的每个位置,如下所示:
In [151]: data[data < 5] = 0
In [152]: data
Out[152]:
one two three four
Ohio 0 0 0 0
Colorado 0 5 6 7
Utah 8 9 10 11
New York 12 13 14 15Selection on DataFrame with loc and iloc
与 Series 一样,DataFrame 具有特殊属性 loc 和 iloc,分别用于基于标签和基于整数的索引。由于 DataFrame 是二维的,因此您可以使用轴标签 (loc) 或整数 (iloc) 以类似 NumPy 的表示法选择行和列的子集。
作为第一个示例,让我们按标签选择一行:
In [153]: data
Out[153]:
one two three four
Ohio 0 0 0 0
Colorado 0 5 6 7
Utah 8 9 10 11
New York 12 13 14 15
In [154]: data.loc["Colorado"]
Out[154]:
one 0
two 5
three 6
four 7
Name: Colorado, dtype: int64选择单行的结果是一个 Series,其索引包含 DataFrame 的列标签。要选择多个角色,创建一个新的 DataFrame,传递一系列标签:
In [155]: data.loc[["Colorado", "New York"]]
Out[155]:
one two three four
Colorado 0 5 6 7
New York 12 13 14 15您可以通过用逗号分隔选择来组合 loc 中的行和列选择:
In [156]: data.loc["Colorado", ["two", "three"]]
Out[156]:
two 5
three 6
Name: Colorado, dtype: int64然后,我们将使用 iloc 对整数执行一些类似的选择:
In [157]: data.iloc[2]
Out[157]:
one 8
two 9
three 10
four 11
Name: Utah, dtype: int64
In [158]: data.iloc[[2, 1]]
Out[158]:
one two three four
Utah 8 9 10 11
Colorado 0 5 6 7
In [159]: data.iloc[2, [3, 0, 1]]
Out[159]:
four 11
one 8
two 9
Name: Utah, dtype: int64
In [160]: data.iloc[[1, 2], [3, 0, 1]]
Out[160]:
four one two
Colorado 7 0 5
Utah 11 8 9除了单个标签或标签列表之外,这两个索引函数还可以使用切片:
In [161]: data.loc[:"Utah", "two"]
Out[161]:
Ohio 0
Colorado 5
Utah 9
Name: two, dtype: int64
In [162]: data.iloc[:, :3][data.three > 5]
Out[162]:
one two three
Colorado 0 5 6
Utah 8 9 10
New York 12 13 14布尔数组可以与 loc 一起使用,但不能与 iloc 一起使用:
In [163]: data.loc[data.three >= 2]
Out[163]:
one two three four
Colorado 0 5 6 7
Utah 8 9 10 11
New York 12 13 14 15有多种方法可以选择和重新排列 pandas 对象中包含的数据。对于 DataFrame,Table 5.4 提供了其中许多内容的简短摘要。正如您稍后将看到的,还有许多用于处理分层索引的附加选项。
| Type | Notes |
|---|---|
df[column] |
从 DataFrame 中选择单列或列序列;特殊情况的便利:布尔数组(过滤行)、切片(切片行)或布尔 DataFrame(根据某些标准设置值) |
df.loc[rows] |
按标签从 DataFrame 中选择单行或行子集 |
df.loc[:, cols] |
按标签选择单列或列的子集 |
df.loc[rows, cols] |
通过标签选择行和列 |
df.iloc[rows] |
按整数位置从 DataFrame 中选择单行或行子集 |
df.iloc[:, cols] |
按整数位置选择单列或列子集 |
df.iloc[rows, cols] |
按整数位置选择行和列 |
df.iat[row, col] |
按行和列位置(整数)选择单个标量值 |
reindex |
通过标签选择行或列 |
Integer indexing pitfalls
使用由整数索引的 pandas 对象可能会成为新用户的绊脚石,因为它们的工作方式与内置的 Python 数据结构(如列表和元组)不同。例如,您可能不希望以下代码生成错误:
In [164]: ser = pd.Series(np.arange(3.))
In [165]: ser
Out[165]:
0 0.0
1 1.0
2 2.0
dtype: float64
In [166]: ser[-1]
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
~/miniforge-x86/envs/book-env/lib/python3.10/site-packages/pandas/core/indexes/ra
nge.py in get_loc(self, key)
344 try:
--> 345 return self._range.index(new_key)
346 except ValueError as err:
ValueError: -1 is not in range
The above exception was the direct cause of the following exception:
KeyError Traceback (most recent call last)
<ipython-input-166-44969a759c20> in <module>
----> 1 ser[-1]
~/miniforge-x86/envs/book-env/lib/python3.10/site-packages/pandas/core/series.py
in __getitem__(self, key)
1010
1011 elif key_is_scalar:
-> 1012 return self._get_value(key)
1013
1014 if is_hashable(key):
~/miniforge-x86/envs/book-env/lib/python3.10/site-packages/pandas/core/series.py
in _get_value(self, label, takeable)
1119
1120 # Similar to Index.get_value, but we do not fall back to position
al
-> 1121 loc = self.index.get_loc(label)
1122
1123 if is_integer(loc):
~/miniforge-x86/envs/book-env/lib/python3.10/site-packages/pandas/core/indexes/ra
nge.py in get_loc(self, key)
345 return self._range.index(new_key)
346 except ValueError as err:
--> 347 raise KeyError(key) from err
348 self._check_indexing_error(key)
349 raise KeyError(key)
KeyError: -1在这种情况下,pandas 可以“回退(fall back)”整数索引,但通常很难在不向用户代码中引入细微错误的情况下做到这一点。这里我们有一个包含 0、1 和 2 的索引,但 pandas 不想猜测用户想要什么(基于标签的索引或基于位置的索引):
In [167]: ser
Out[167]:
0 0.0
1 1.0
2 2.0
dtype: float64另一方面,对于非整数索引,就不存在这样的歧义:
In [168]: ser2 = pd.Series(np.arange(3.), index=["a", "b", "c"])
In [169]: ser2[-1]
Out[169]: 2.0如果您的轴索引包含整数,则数据选择将始终面向标签。正如我上面所说,如果您使用 loc (用于标签)或 iloc (用于整数),您将得到您想要的:
In [170]: ser.iloc[-1]
Out[170]: 2.0另一方面,整数切片始终是面向整数的:
In [171]: ser[:2]
Out[171]:
0 0.0
1 1.0
dtype: float64由于这些陷阱,最好始终首选使用 loc 和 iloc 进行索引以避免歧义。
Pitfalls with chained indexing
在上一节中,我们了解了如何使用 loc 和 iloc 在 DataFrame 上进行灵活的选择。这些索引属性也可用于就地修改 DataFrame 对象,但这样做需要小心。
例如,在上面的示例 DataFrame 中,我们可以通过标签或整数位置分配给列或行:
In [172]: data.loc[:, "one"] = 1
In [173]: data
Out[173]:
one two three four
Ohio 1 0 0 0
Colorado 1 5 6 7
Utah 1 9 10 11
New York 1 13 14 15
In [174]: data.iloc[2] = 5
In [175]: data
Out[175]:
one two three four
Ohio 1 0 0 0
Colorado 1 5 6 7
Utah 5 5 5 5
New York 1 13 14 15
In [176]: data.loc[data["four"] > 5] = 3
In [177]: data
Out[177]:
one two three four
Ohio 1 0 0 0
Colorado 3 3 3 3
Utah 5 5 5 5
New York 3 3 3 3对于新的 pandas 用户来说,一个常见的问题是在分配时链接选择,如下所示:
In [177]: data.loc[data.three == 5]["three"] = 6
<ipython-input-11-0ed1cf2155d5>:1: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead根据数据内容,这可能会打印一个特殊的 SettingWithCopyWarning,它警告您正在尝试修改临时值(data.loc[data. Three == 5]的非空结果)而不是原始 DataFrame 数据,这可能就是你想要的。这里,data 未修改:
In [179]: data
Out[179]:
one two three four
Ohio 1 0 0 0
Colorado 3 3 3 3
Utah 5 5 5 5
New York 3 3 3 3在这些情况下,修复方法是重写链式赋值以使用单个 loc 操作:
In [180]: data.loc[data.three == 5, "three"] = 6
In [181]: data
Out[181]:
one two three four
Ohio 1 0 0 0
Colorado 3 3 3 3
Utah 5 5 6 5
New York 3 3 3 3一个好的经验法则是在进行分配时避免链式索引。在其他情况下,pandas 会生成与链式索引有关的 SettingWithCopyWarning。我建议您参考在线 pandas 文档中的这个主题。
5.2.4 Arithmetic and Data Alignment
pandas 可以使处理具有不同索引的对象变得更加简单。例如,当您添加对象时,如果任何索引对不相同,则结果中的相应索引将是索引对的并集。让我们看一个例子:
In [182]: s1 = pd.Series([7.3, -2.5, 3.4, 1.5], index=["a", "c", "d", "e"])
In [183]: s2 = pd.Series([-2.1, 3.6, -1.5, 4, 3.1],
.....: index=["a", "c", "e", "f", "g"])
In [184]: s1
Out[184]:
a 7.3
c -2.5
d 3.4
e 1.5
dtype: float64
In [185]: s2
Out[185]:
a -2.1
c 3.6
e -1.5
f 4.0
g 3.1
dtype: float64相加:
In [186]: s1 + s2
Out[186]:
a 5.2
c 1.1
d NaN
e 0.0
f NaN
g NaN
dtype: float64内部数据对齐在不重叠的标签位置引入了缺失值。缺失值将在进一步的算术计算中传播。
对于 DataFrame,对齐是在行和列上执行的:
In [187]: df1 = pd.DataFrame(np.arange(9.).reshape((3, 3)), columns=list("bcd"),
.....: index=["Ohio", "Texas", "Colorado"])
In [188]: df2 = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list("bde"),
.....: index=["Utah", "Ohio", "Texas", "Oregon"])
In [189]: df1
Out[189]:
b c d
Ohio 0.0 1.0 2.0
Texas 3.0 4.0 5.0
Colorado 6.0 7.0 8.0
In [190]: df2
Out[190]:
b d e
Utah 0.0 1.0 2.0
Ohio 3.0 4.0 5.0
Texas 6.0 7.0 8.0
Oregon 9.0 10.0 11.0添加这些会返回一个带有索引和列的 DataFrame,这些索引和列是每个 DataFrame 中的索引和列的并集:
In [191]: df1 + df2
Out[191]:
b c d e
Colorado NaN NaN NaN NaN
Ohio 3.0 NaN 6.0 NaN
Oregon NaN NaN NaN NaN
Texas 9.0 NaN 12.0 NaN
Utah NaN NaN NaN NaN由于在两个 DataFrame 对象中均未找到 "c" 和 "e" 列,因此它们在结果中显示为缺失。这同样适用于带有两个对象不常见的标签的行。
如果添加没有共同列或行标签的 DataFrame 对象,结果将包含所有空值:
In [192]: df1 = pd.DataFrame({"A": [1, 2]})
In [193]: df2 = pd.DataFrame({"B": [3, 4]})
In [194]: df1
Out[194]:
A
0 1
1 2
In [195]: df2
Out[195]:
B
0 3
1 4
In [196]: df1 + df2
Out[196]:
A B
0 NaN NaN
1 NaN NaNArithmetic methods with fill values
在不同索引对象之间的算术运算中,当在一个对象中找到轴标签但在另一个对象中未找到时,您可能需要填充特殊值,例如 0。下面是一个示例,我们通过将 np.nan 分配给它来将特定值设置为 NA (null):
In [197]: df1 = pd.DataFrame(np.arange(12.).reshape((3, 4)),
.....: columns=list("abcd"))
In [198]: df2 = pd.DataFrame(np.arange(20.).reshape((4, 5)),
.....: columns=list("abcde"))
In [199]: df2.loc[1, "b"] = np.nan
In [200]: df1
Out[200]:
a b c d
0 0.0 1.0 2.0 3.0
1 4.0 5.0 6.0 7.0
2 8.0 9.0 10.0 11.0
In [201]: df2
Out[201]:
a b c d e
0 0.0 1.0 2.0 3.0 4.0
1 5.0 NaN 7.0 8.0 9.0
2 10.0 11.0 12.0 13.0 14.0
3 15.0 16.0 17.0 18.0 19.0相加导致不重叠位置的缺失值:
In [202]: df1 + df2
Out[202]:
a b c d e
0 0.0 2.0 4.0 6.0 NaN
1 9.0 NaN 13.0 15.0 NaN
2 18.0 20.0 22.0 24.0 NaN
3 NaN NaN NaN NaN NaN使用 df1 上的 add 方法,我将 df2 和一个参数传递给 fill_value,这将用传递的值替换操作中的任何缺失值:
In [203]: df1.add(df2, fill_value=0)
Out[203]:
a b c d e
0 0.0 2.0 4.0 6.0 4.0
1 9.0 5.0 13.0 15.0 9.0
2 18.0 20.0 22.0 24.0 14.0
3 15.0 16.0 17.0 18.0 19.0请参阅 Table 5.5,了解用于算术的 Series 和 DataFrame 方法的列表。每个都有一个对应的,以字母 r 开头,其参数相反。所以这两个语句是等价的:
In [204]: 1 / df1
Out[204]:
a b c d
0 inf 1.000000 0.500000 0.333333
1 0.250 0.200000 0.166667 0.142857
2 0.125 0.111111 0.100000 0.090909
In [205]: df1.rdiv(1)
Out[205]:
a b c d
0 inf 1.000000 0.500000 0.333333
1 0.250 0.200000 0.166667 0.142857
2 0.125 0.111111 0.100000 0.090909相关地,在重新索引 Series 或 DataFrame 时,您还可以指定不同的填充值:
In [206]: df1.reindex(columns=df2.columns, fill_value=0)
Out[206]:
a b c d e
0 0.0 1.0 2.0 3.0 0
1 4.0 5.0 6.0 7.0 0
2 8.0 9.0 10.0 11.0 0| Method | Description |
|---|---|
add, radd |
Methods for addition (+) |
sub, rsub |
Methods for subtraction (-) |
div, rdiv |
Methods for division (/) |
floordiv, rfloordiv |
Methods for floor division (//) |
mul, rmul |
Methods for multiplication (*) |
pow, rpow |
Methods for exponentiation (**) |
Operations between DataFrame and Series
与不同维度的 NumPy 数组一样,DataFrame 和 Series 之间的算术也被定义。首先,作为一个激励示例,考虑二维数组与其行之一之间的差异:
In [207]: arr = np.arange(12.).reshape((3, 4))
In [208]: arr
Out[208]:
array([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.]])
In [209]: arr[0]
Out[209]: array([0., 1., 2., 3.])
In [210]: arr - arr[0]
Out[210]:
array([[0., 0., 0., 0.],
[4., 4., 4., 4.],
[8., 8., 8., 8.]])当我们从 arr 中减去 arr[0] 时,每行执行一次减法。这称为广播(broadcasting),并在 Appendix A: Advanced NumPy 中更详细地解释了它与通用 NumPy 数组的关系。DataFrame 和 Series 之间的操作类似:
In [211]: frame = pd.DataFrame(np.arange(12.).reshape((4, 3)),
.....: columns=list("bde"),
.....: index=["Utah", "Ohio", "Texas", "Oregon"])
In [212]: series = frame.iloc[0]
In [213]: frame
Out[213]:
b d e
Utah 0.0 1.0 2.0
Ohio 3.0 4.0 5.0
Texas 6.0 7.0 8.0
Oregon 9.0 10.0 11.0
In [214]: series
Out[214]:
b 0.0
d 1.0
e 2.0
Name: Utah, dtype: float64默认情况下,DataFrame 和 Series 之间的算术与 DataFrame 列上 Series 的索引相匹配,并向下广播行:
In [215]: frame - series
Out[215]:
b d e
Utah 0.0 0.0 0.0
Ohio 3.0 3.0 3.0
Texas 6.0 6.0 6.0
Oregon 9.0 9.0 9.0如果在 DataFrame 的列或 Series 的索引中都找不到索引值,则对象将被重新索引以形成并集:
In [216]: series2 = pd.Series(np.arange(3), index=["b", "e", "f"])
In [217]: series2
Out[217]:
b 0
e 1
f 2
dtype: int64
In [218]: frame + series2
Out[218]:
b d e f
Utah 0.0 NaN 3.0 NaN
Ohio 3.0 NaN 6.0 NaN
Texas 6.0 NaN 9.0 NaN
Oregon 9.0 NaN 12.0 NaN如果您想在列上进行广播,在行上进行匹配,则必须使用其中一种算术方法并指定在索引上进行匹配。例如:
In [219]: series3 = frame["d"]
In [220]: frame
Out[220]:
b d e
Utah 0.0 1.0 2.0
Ohio 3.0 4.0 5.0
Texas 6.0 7.0 8.0
Oregon 9.0 10.0 11.0
In [221]: series3
Out[221]:
Utah 1.0
Ohio 4.0
Texas 7.0
Oregon 10.0
Name: d, dtype: float64
In [222]: frame.sub(series3, axis="index")
Out[222]:
b d e
Utah -1.0 0.0 1.0
Ohio -1.0 0.0 1.0
Texas -1.0 0.0 1.0
Oregon -1.0 0.0 1.0您传递的轴是要匹配的轴。在本例中,我们的意思是匹配 DataFrame 的行索引 (axis="index") 并跨列广播。
5.2.5 Function Application and Mapping
NumPy ufuncs(逐元素数组方法)也适用于 pandas 对象:
In [223]: frame = pd.DataFrame(np.random.standard_normal((4, 3)),
.....: columns=list("bde"),
.....: index=["Utah", "Ohio", "Texas", "Oregon"])
In [224]: frame
Out[224]:
b d e
Utah -0.204708 0.478943 -0.519439
Ohio -0.555730 1.965781 1.393406
Texas 0.092908 0.281746 0.769023
Oregon 1.246435 1.007189 -1.296221
In [225]: np.abs(frame)
Out[225]:
b d e
Utah 0.204708 0.478943 0.519439
Ohio 0.555730 1.965781 1.393406
Texas 0.092908 0.281746 0.769023
Oregon 1.246435 1.007189 1.296221另一种常见的操作是将一维数组上的函数应用于每一列或行。DataFrame 的 apply 方法正是这样做的:
In [226]: def f1(x):
.....: return x.max() - x.min()
In [227]: frame.apply(f1)
Out[227]:
b 1.802165
d 1.684034
e 2.689627
dtype: float64这里,函数 f 计算 Series 的最大值和最小值之间的差,在 frame 中的每一列上调用一次。结果是一个以 frame 列作为索引的系列。
如果您传递 axis="columns" 给 apply,则该函数将每行调用一次。思考这个问题的一个有用方法是“跨列应用”:
In [228]: frame.apply(f1, axis="columns")
Out[228]:
Utah 0.998382
Ohio 2.521511
Texas 0.676115
Oregon 2.542656
dtype: float64许多最常见的数组统计数据(例如 sum 和 Mean)都是 DataFrame 方法,因此没有必要使用 apply。
传递给 apply 的函数不需要返回标量值;它还可以返回具有多个值的 Series:
In [229]: def f2(x):
.....: return pd.Series([x.min(), x.max()], index=["min", "max"])
In [230]: frame.apply(f2)
Out[230]:
b d e
min -0.555730 0.281746 -1.296221
max 1.246435 1.965781 1.393406也可以使用逐元素 Python 函数。假设您想根据 frame 中的每个浮点值计算格式化字符串。您可以使用 applymap 执行此操作:
In [231]: def my_format(x):
.....: return f"{x:.2f}"
In [232]: frame.applymap(my_format)
Out[232]:
b d e
Utah -0.20 0.48 -0.52
Ohio -0.56 1.97 1.39
Texas 0.09 0.28 0.77
Oregon 1.25 1.01 -1.30名称 applymap 的原因是 Series 有一个用于应用逐元素函数的 map 方法:
In [233]: frame["e"].map(my_format)
Out[233]:
Utah -0.52
Ohio 1.39
Texas 0.77
Oregon -1.30
Name: e, dtype: object5.2.6 Sorting and Ranking
按某种标准对数据集进行排序是另一个重要的内置操作。要按行或列标签按字典顺序排序,请使用 sort_index 方法,该方法返回一个新的排序对象:
In [234]: obj = pd.Series(np.arange(4), index=["d", "a", "b", "c"])
In [235]: obj
Out[235]:
d 0
a 1
b 2
c 3
dtype: int64
In [236]: obj.sort_index()
Out[236]:
a 1
b 2
c 3
d 0
dtype: int64使用 DataFrame,您可以按任一轴上的索引进行排序:
In [237]: frame = pd.DataFrame(np.arange(8).reshape((2, 4)),
.....: index=["three", "one"],
.....: columns=["d", "a", "b", "c"])
In [238]: frame
Out[238]:
d a b c
three 0 1 2 3
one 4 5 6 7
In [239]: frame.sort_index()
Out[239]:
d a b c
one 4 5 6 7
three 0 1 2 3
In [240]: frame.sort_index(axis="columns")
Out[240]:
a b c d
three 1 2 3 0
one 5 6 7 4数据默认按升序排序,但也可以按降序排序:
In [241]: frame.sort_index(axis="columns", ascending=False)
Out[241]:
d c b a
three 0 3 2 1
one 4 7 6 5要按值对 Series 进行排序,请使用其 sort_values 方法:
In [242]: obj = pd.Series([4, 7, -3, 2])
In [243]: obj.sort_values()
Out[243]:
2 -3
3 2
0 4
1 7
dtype: int64默认情况下,所有缺失值都会排序到 Series 末尾:
In [244]: obj = pd.Series([4, np.nan, 7, np.nan, -3, 2])
In [245]: obj.sort_values()
Out[245]:
4 -3.0
5 2.0
0 4.0
2 7.0
1 NaN
3 NaN
dtype: float64可以使用 na_position 选项将缺失值排序到开头:
In [246]: obj.sort_values(na_position="first")
Out[246]:
1 NaN
3 NaN
4 -3.0
5 2.0
0 4.0
2 7.0
dtype: float64对 DataFrame 进行排序时,可以使用一列或多列中的数据作为排序键。为此,请将一个或多个列名称传递给 sort_values:
In [247]: frame = pd.DataFrame({"b": [4, 7, -3, 2], "a": [0, 1, 0, 1]})
In [248]: frame
Out[248]:
b a
0 4 0
1 7 1
2 -3 0
3 2 1
In [249]: frame.sort_values("b")
Out[249]:
b a
2 -3 0
3 2 1
0 4 0
1 7 1要按多列排序,请传递名称列表:
In [250]: frame.sort_values(["a", "b"])
Out[250]:
b a
2 -3 0
0 4 0
3 2 1
1 7 1排名从最低值开始,从 1 到数组中有效数据点的数量分配排名。Series 和 DataFrame 的 rank 方法是值得关注的地方;默认情况下,rank 通过为每个组分配平均排名来打破平局:
In [251]: obj = pd.Series([7, -5, 7, 4, 2, 0, 4])
In [252]: obj.rank()
Out[252]:
0 6.5
1 1.0
2 6.5
3 4.5
4 3.0
5 2.0
6 4.5
dtype: float64还可以根据数据中观察到的顺序来分配排名:
In [253]: obj.rank(method="first")
Out[253]:
0 6.0
1 1.0
2 7.0
3 4.0
4 3.0
5 2.0
6 5.0
dtype: float64此处,条目 0 和 2 没有使用平均排名 6.5,而是将它们设置为 6 和 7,因为数据中标签 0 位于标签 2 之前。
您也可以按降序排列:
In [254]: obj.rank(ascending=False)
Out[254]:
0 1.5
1 7.0
2 1.5
3 3.5
4 5.0
5 6.0
6 3.5
dtype: float64请参阅 Table 5.6,了解可用的打破平局方法的列表。
DataFrame 可以计算行或列的排名:
In [255]: frame = pd.DataFrame({"b": [4.3, 7, -3, 2], "a": [0, 1, 0, 1],
.....: "c": [-2, 5, 8, -2.5]})
In [256]: frame
Out[256]:
b a c
0 4.3 0 -2.0
1 7.0 1 5.0
2 -3.0 0 8.0
3 2.0 1 -2.5
In [257]: frame.rank(axis="columns")
Out[257]:
b a c
0 3.0 2.0 1.0
1 3.0 1.0 2.0
2 1.0 2.0 3.0
3 3.0 2.0 1.0| Method | Description | ||
|---|---|---|---|
"average" |
默认值:为同等组中的每个条目分配平均排名 | ||
"min" |
使用整个组的最低排名 | ||
"max" |
使用整个组的最大排名 | ||
"first" |
按照值在数据中出现的顺序分配排名 | ||
"dense" |
与 method="min" 类似,但组之间的排名总是增加 1,而不是组中相等元素的数量 |
5.2.7 Axis Indexes with Duplicate Labels
到目前为止,我们看过的几乎所有示例都有唯一的轴标签(索引值)。虽然许多 pandas 函数(如 reindex)要求标签是唯一的,但这不是强制性的。让我们考虑一个具有重复索引的小 Series:
In [258]: obj = pd.Series(np.arange(5), index=["a", "a", "b", "b", "c"])
In [259]: obj
Out[259]:
a 0
a 1
b 2
b 3
c 4
dtype: int64索引的 is_unique 属性可以告诉你它的标签是否唯一:
In [260]: obj.index.is_unique
Out[260]: False数据选择是与重复项表现不同的主要因素之一。对具有多个条目的标签进行索引会返回一个 Series,而单个条目则返回一个标量值:
In [261]: obj["a"]
Out[261]:
a 0
a 1
dtype: int64
In [262]: obj["c"]
Out[262]: 4这可能会使您的代码更加复杂,因为索引的输出类型可能会根据标签是否重复而有所不同。
相同的逻辑扩展到 DataFrame 中的索引行(或列):
In [263]: df = pd.DataFrame(np.random.standard_normal((5, 3)),
.....: index=["a", "a", "b", "b", "c"])
In [264]: df
Out[264]:
0 1 2
a 0.274992 0.228913 1.352917
a 0.886429 -2.001637 -0.371843
b 1.669025 -0.438570 -0.539741
b 0.476985 3.248944 -1.021228
c -0.577087 0.124121 0.302614
In [265]: df.loc["b"]
Out[265]:
0 1 2
b 1.669025 -0.438570 -0.539741
b 0.476985 3.248944 -1.021228
In [266]: df.loc["c"]
Out[266]:
0 -0.577087
1 0.124121
2 0.302614
Name: c, dtype: float645.3 Summarizing and Computing Descriptive Statistics
pandas 对象配备了一套常见的数学和统计方法。其中大多数属于归约或汇总统计的类别,从 Series 中提取单个值(如总和或平均值)的方法,或从 DataFrame 的行或列中提取一系列值的方法。与 NumPy 数组上的类似方法相比,它们具有针对丢失数据的内置处理。考虑一个小的 DataFrame:
In [267]: df = pd.DataFrame([[1.4, np.nan], [7.1, -4.5],
.....: [np.nan, np.nan], [0.75, -1.3]],
.....: index=["a", "b", "c", "d"],
.....: columns=["one", "two"])
In [268]: df
Out[268]:
one two
a 1.40 NaN
b 7.10 -4.5
c NaN NaN
d 0.75 -1.3调用 DataFrame 的 sum 方法会返回一个包含列总和的 Series:
In [269]: df.sum()
Out[269]:
one 9.25
two -5.80
dtype: float64而是传递 axis="columns" 或 axis=1 跨列求和:
In [270]: df.sum(axis="columns")
Out[270]:
a 1.40
b 2.60
c 0.00
d -0.55
dtype: float64当整行或整列包含所有 NA 值时,总和为 0,而如果任何值不为 NA,则结果为 NA。可以使用 skipna 选项禁用此功能,在这种情况下,行或列中的任何 NA 值都会命名相应的结果 NA:
In [271]: df.sum(axis="index", skipna=False)
Out[271]:
one NaN
two NaN
dtype: float64
In [272]: df.sum(axis="columns", skipna=False)
Out[272]:
a NaN
b 2.60
c NaN
d -0.55
dtype: float64一些聚合(例如 mean)需要至少一个非 NA 值才能产生值结果,因此这里我们有:
In [273]: df.mean(axis="columns")
Out[273]:
a 1.400
b 1.300
c NaN
d -0.275
dtype: float64请参阅 Table 5.7,了解每种缩减方法的常用选项列表。
| Method | Description |
|---|---|
axis |
轴减少过度;DataFrame 的行的“index”和列的“columns” |
skipna |
排除缺失值;默认为 True |
level |
如果轴是分层索引的(MultiIndex),则减少按级别分组 |
某些方法(例如 idxmin 和 idxmax)返回间接统计信息,例如达到最小值或最大值的索引值:
In [274]: df.idxmax()
Out[274]:
one b
two d
dtype: object其他方法都是累加:
In [275]: df.cumsum()
Out[275]:
one two
a 1.40 NaN
b 8.50 -4.5
c NaN NaN
d 9.25 -5.8有些方法既不是减法,也不是累加。describe 就是这样一个例子,一次性生成多个汇总统计信息:
In [276]: df.describe()
Out[276]:
one two
count 3.000000 2.000000
mean 3.083333 -2.900000
std 3.493685 2.262742
min 0.750000 -4.500000
25% 1.075000 -3.700000
50% 1.400000 -2.900000
75% 4.250000 -2.100000
max 7.100000 -1.300000对于非数字数据,describe 会生成替代的汇总统计数据:
In [277]: obj = pd.Series(["a", "a", "b", "c"] * 4)
In [278]: obj.describe()
Out[278]:
count 16
unique 3
top a
freq 8
dtype: object有关汇总统计数据和相关方法的完整列表,请参阅 Table 5.8。
| Method | Description |
|---|---|
count |
Number of non-NA values |
describe |
Compute set of summary statistics |
min, max |
Compute minimum and maximum values |
argmin, argmax |
Compute index locations (integers) at which minimum or maximum value is obtained, respectively; not available on DataFrame objects |
idxmin, idxmax |
Compute index labels at which minimum or maximum value is obtained, respectively |
quantile |
Compute sample quantile ranging from 0 to 1 (default: 0.5) |
sum |
Sum of values |
mean |
Mean of values |
median |
Arithmetic median (50% quantile) of values |
mad |
Mean absolute deviation from mean value |
prod |
Product of all values |
var |
Sample variance of values |
std |
Sample standard deviation of values |
skew |
Sample skewness (third moment) of values |
kurt |
Sample kurtosis (fourth moment) of values |
cumsum |
Cumulative sum of values |
cummin, cummax |
Cumulative minimum or maximum of values, respectively |
cumprod |
Cumulative product of values |
diff |
Compute first arithmetic difference (useful for time series) |
pct_change |
Compute percent changes |
5.3.1 Correlation and Covariance
一些汇总统计数据(例如相关性和协方差)是根据参数对计算的。让我们考虑一些最初从 Yahoo! 获得的股票价格和交易量的 DataFrames。财务并以二进制 Python pickle 文件形式提供,您可以在本书随附的数据集中找到:
In [279]: price = pd.read_pickle("examples/yahoo_price.pkl")
In [280]: volume = pd.read_pickle("examples/yahoo_volume.pkl")我现在计算价格的百分比变化,这是一种时间序列运算,将在 Ch 11: Time Series 中进一步探讨:
In [281]: returns = price.pct_change()
In [282]: returns.tail()
Out[282]:
AAPL GOOG IBM MSFT
Date
2016-10-17 -0.000680 0.001837 0.002072 -0.003483
2016-10-18 -0.000681 0.019616 -0.026168 0.007690
2016-10-19 -0.002979 0.007846 0.003583 -0.002255
2016-10-20 -0.000512 -0.005652 0.001719 -0.004867
2016-10-21 -0.003930 0.003011 -0.012474 0.042096Series 的 corr 方法计算两个 Series 中重叠的、非 NA 的、按索引对齐的值的相关性。相关地,cov 计算协方差:
In [283]: returns["MSFT"].corr(returns["IBM"])
Out[283]: 0.49976361144151166
In [284]: returns["MSFT"].cov(returns["IBM"])
Out[284]: 8.870655479703549e-05另一方面,DataFrame 的 corr 和 cov 方法分别返回完整的相关或协方差矩阵作为 DataFrame:
In [285]: returns.corr()
Out[285]:
AAPL GOOG IBM MSFT
AAPL 1.000000 0.407919 0.386817 0.389695
GOOG 0.407919 1.000000 0.405099 0.465919
IBM 0.386817 0.405099 1.000000 0.499764
MSFT 0.389695 0.465919 0.499764 1.000000
In [286]: returns.cov()
Out[286]:
AAPL GOOG IBM MSFT
AAPL 0.000277 0.000107 0.000078 0.000095
GOOG 0.000107 0.000251 0.000078 0.000108
IBM 0.000078 0.000078 0.000146 0.000089
MSFT 0.000095 0.000108 0.000089 0.000215使用 DataFrame 的 corrwith 方法,您可以计算 DataFrame 的列或行与另一个 Series 或 DataFrame 之间的成对相关性。传递 Series 返回一个 Series,其中包含为每列计算的相关值:
In [287]: returns.corrwith(returns["IBM"])
Out[287]:
AAPL 0.386817
GOOG 0.405099
IBM 1.000000
MSFT 0.499764
dtype: float64传递 DataFrame 会计算匹配列名称的相关性。在这里,我计算百分比变化与交易量的相关性:
In [288]: returns.corrwith(volume)
Out[288]:
AAPL -0.075565
GOOG -0.007067
IBM -0.204849
MSFT -0.092950
dtype: float64传递 axis="columns" 会逐行执行操作。在所有情况下,在计算相关性之前,数据点都会按标签对齐。
5.3.2 Unique Values, Value Counts, and Membership
另一类相关方法提取有关一维 Series 中包含的值的信息。为了说明这些,请考虑以下示例:
In [289]: obj = pd.Series(["c", "a", "d", "a", "a", "b", "b", "c", "c"])第一个函数是 unique,它为您提供 Series 中唯一值的数组:
In [290]: uniques = obj.unique()
In [291]: uniques
Out[291]: array(['c', 'a', 'd', 'b'], dtype=object)唯一值不一定按它们首次出现的顺序返回,也不是按排序顺序返回,但如果需要,可以在事后对它们进行排序 (uniques.sort())。相关地,value_counts 计算包含值频率的 Series:
In [292]: obj.value_counts()
Out[292]:
c 3
a 3
b 2
d 1
Name: count, dtype: int64为了方便起见,该 Series 按值降序排列。value_counts 也可用作顶级 pandas 方法,可与 NumPy 数组或其他 Python 序列一起使用:
In [293]: pd.value_counts(obj.to_numpy(), sort=False)
Out[293]:
c 3
a 3
d 1
b 2
Name: count, dtype: int64isin 执行矢量化集成员资格检查,可用于将数据集过滤为 DataFrame 中的 Series 或列中的值的子集:
In [294]: obj
Out[294]:
0 c
1 a
2 d
3 a
4 a
5 b
6 b
7 c
8 c
dtype: object
In [295]: mask = obj.isin(["b", "c"])
In [296]: mask
Out[296]:
0 True
1 False
2 False
3 False
4 False
5 True
6 True
7 True
8 True
dtype: bool
In [297]: obj[mask]
Out[297]:
0 c
5 b
6 b
7 c
8 c
dtype: object与 isin 相关的是 Index.get_indexer 方法,它为您提供一个索引数组,从一个可能非不同值的数组到另一个不同值的数组:
In [298]: to_match = pd.Series(["c", "a", "b", "b", "c", "a"])
In [299]: unique_vals = pd.Series(["c", "b", "a"])
In [300]: indices = pd.Index(unique_vals).get_indexer(to_match)
In [301]: indices
Out[301]: array([0, 2, 1, 1, 0, 2])有关这些方法的参考,请参阅 Table 5.9。
| Method | Description |
|---|---|
isin |
计算一个布尔数组,指示每个 Series 或 DataFrame 值是否包含在传递的值序列中 |
get_indexer |
将数组中每个值的整数索引计算到另一个不同值的数组中;有助于数据对齐和连接类型操作 |
unique |
计算 Series 中唯一值的数组,按观察到的顺序返回 |
value_counts |
返回一个包含唯一值作为其索引和频率作为其值的 Series,按降序排列计数 |
在某些情况下,您可能需要计算 DataFrame 中多个相关列的直方图。这是一个例子:
In [302]: data = pd.DataFrame({"Qu1": [1, 3, 4, 3, 4],
.....: "Qu2": [2, 3, 1, 2, 3],
.....: "Qu3": [1, 5, 2, 4, 4]})
In [303]: data
Out[303]:
Qu1 Qu2 Qu3
0 1 2 1
1 3 3 5
2 4 1 2
3 3 2 4
4 4 3 4我们可以计算单个列的值计数,如下所示:
In [304]: data["Qu1"].value_counts().sort_index()
Out[304]:
Qu1
1 1
3 2
4 2
Name: count, dtype: int64要计算所有列的值,请将 pandas.value_counts 传递给 DataFrame 的 apply 方法:
In [305]: result = data.apply(pd.value_counts).fillna(0)
In [306]: result
Out[306]:
Qu1 Qu2 Qu3
1 1.0 1.0 1.0
2 0.0 2.0 1.0
3 2.0 2.0 0.0
4 2.0 0.0 2.0
5 0.0 0.0 1.0在这里,结果中的行标签是所有列中出现的不同值。这些值是每列中这些值的相应计数。
还有一个 DataFrame.value_counts 方法,但它将 DataFrame 的每一行视为一个元组来计算计数,以确定每个不同行的出现次数:
In [307]: data = pd.DataFrame({"a": [1, 1, 1, 2, 2], "b": [0, 0, 1, 0, 0]})
In [308]: data
Out[308]:
a b
0 1 0
1 1 0
2 1 1
3 2 0
4 2 0
In [309]: data.value_counts()
Out[309]:
a b
1 0 2
2 0 2
1 1 1
Name: count, dtype: int64在这种情况下,结果有一个索引,将不同的行表示为分层索引,我们将在 Ch 8: Data Wrangling: Join, Combine, and Reshape 中更详细地探讨该主题。
5.4 Conclusion
在下一章中,我们将讨论使用 pandas 读取(或加载)和写入数据集的工具。之后,我们将更深入地研究使用 pandas 的数据清理、整理、分析和可视化工具。