Pandas 秘籍(二)
四、选择数据子集
在本章中,我们将介绍以下主题:
- 选择序列数据
- 选择数据帧的行
- 同时选择数据帧的行和列
- 同时通过整数和标签和选择数据
- 加速标量选择
- 以延迟方式对行切片
- 按词典顺序切片
介绍
序列或数据帧中数据的每个维度都通过索引对象标记。 正是这个索引将 Pandas 数据结构与 NumPy 的 n 维数组分开。 索引为数据的每一行和每一列提供了有意义的标签,而 Pandas 用户可以通过使用这些标签来选择数据。 此外,pandas 允许其用户通过行和列的整数位置选择数据。 这种双重选择功能(一种使用标签,另一种使用整数位置)使得强大而又令人困惑的语法可以选择数据子集。
通过使用标签或整数位置选择数据并非 Pandas 所独有。 Python 字典和列表是内置的数据结构,它们以下列其中一种方式选择其数据。 字典和列表都具有精确的说明,并且对于传递给索引运算符的内容都具有有限的用例。 字典的键(其标签)必须是不可变的对象,例如字符串,整数或元组。 列表必须使用整数或切片对象进行选择。 通过将键传递给索引运算符,词典一次只能选择一个对象。 从某种意义上说,Pandas 结合了使用整数(如列表)和标签(如字典)选择数据的能力。
选择序列数据
序列和数据帧是复杂的数据容器,具有多个属性,这些属性使用索引运算符以不同方式选择数据。 除了索引运算符本身之外,.iloc和.loc属性也可用,并以其自己的独特方式使用索引运算符。 这些属性统称为索引器。
索引术语可能会引起混淆。 这里使用术语“索引运算符”将其与其他索引器区分开。 它指代直接在序列或数据帧之后的括号[]。 例如,给定一个s序列,您可以通过以下方式选择数据:s[item]和s.loc[item]。 第一个使用索引运算符。 第二个使用.loc索引器。
序列和数据帧索引器允许按整数位置(如 Python 列表)和标签(如 Python 字典)进行选择。.iloc索引器仅按整数位置选择,并且与 Python 列表类似。.loc索引器仅按索引标签进行选择,这与 Python 词典的工作方式类似。
准备
.loc和。iloc与序列和数据帧一起使用。 此秘籍展示了如何通过.iloc通过整数位置以及通过.loc通过标签选择序列数据。 这些索引器不仅获取标量值,还获取列表和切片。
操作步骤
- 读取以机构名称作为索引的大学数据集,并使用索引运算符选择一个列作为“序列”:
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> city = college['CITY']
>>> city.head()
INSTNM
Alabama A & M University Normal
University of Alabama at Birmingham Birmingham
Amridge University Montgomery
University of Alabama in Huntsville Huntsville
Alabama State University Montgomery
Name: CITY, dtype: object
.iloc索引器仅按整数位置进行选择。 向其传递整数将返回标量值:
>>> city.iloc[3]
Huntsville
- 要选择几个不同的整数位置,请将列表传递给
.iloc。 这将返回一个序列:
>>> city.iloc[[10,20,30]]
INSTNM
Birmingham Southern College Birmingham
George C Wallace State Community College-Hanceville Hanceville
Judson College Marion
Name: CITY, dtype: object
- 要选择等间距的数据分区,请使用切片符号:
>>> city.iloc[4:50:10]
INSTNM
Alabama State University Montgomery
Enterprise State Community College Enterprise
Heritage Christian University Florence
Marion Military Institute Marion
Reid State Technical College Evergreen
Name: CITY, dtype: object
- 现在,我们转到
.loc索引器,该索引器仅使用索引标签进行选择。 传递单个字符串将返回标量值:
>>> city.loc['Heritage Christian University']
Florence
- 要选择几个不相交的标签,请使用一个列表:
>>> np.random.seed(1)
>>> labels = list(np.random.choice(city.index, 4))
>>> labels
['Northwest HVAC/R Training Center',
'California State University-Dominguez Hills',
'Lower Columbia College',
'Southwest Acupuncture College-Boulder']
>>> city.loc[labels]
INSTNM
Northwest HVAC/R Training Center Spokane
California State University-Dominguez Hills Carson
Lower Columbia College Longview
Southwest Acupuncture College-Boulder Boulder
Name: CITY, dtype: object
- 要选择等间距的数据分区,请使用切片符号。 确保起始值和终止值是字符串。 您可以使用整数来指定切片的步长:
>>> city.loc['Alabama State University':
'Reid State Technical College':10]
INSTNM
Alabama State University Montgomery
Enterprise State Community College Enterprise
Heritage Christian University Florence
Marion Military Institute Marion
Reid State Technical College Evergreen
Name: CITY, dtype: object
工作原理
序列中的值由从 0 开始的整数引用。步骤 2 使用.loc索引器选择序列的第四个元素。 步骤 3 将三个项目的整数列表传递给索引运算符,该运算符返回选择了那些整数位置的序列。 此功能是对 Python 列表的增强,它无法以这种方式选择多个不相交的项目。
在步骤 4 中,使用指定了start,stop和step值的切片符号来选择序列的整个部分。
步骤 5 至 7 使用基于标签的索引器.loc复制步骤 2 至 4。 标签必须与索引中的值完全匹配。 为了确保标签正确,我们在步骤 6 中从索引中随机选择四个标签,并将它们存储到列表中,然后再将它们的值选择为序列。 使用.loc索引器的选择始终包含最后一个元素,如步骤 7 所示。
更多
与步骤 2 和步骤 5 一样,当将标量值传递给索引运算符时,将返回标量值。 与其他步骤一样,传递列表或切片时,将返回一个序列。 此返回值似乎不一致,但是如果我们将序列视为将标签映射到值的类似于字典的对象,则返回值是有意义的。 要选择单个项目并将其保留在序列中,请以单项列表而不是标量值的形式传递:
>>> city.iloc[[3]]
INSTNM
University of Alabama in Huntsville Huntsville
Name: CITY, dtype: object
将切片符号与.loc一起使用时需要格外小心。 如果start索引出现在stop索引之后,则返回一个空序列,而不会引发异常:
>>> city.loc['Reid State Technical College':
'Alabama State University':10]
Series([], Name: CITY, dtype: object)
另见
选择数据帧的行
选择[DataGate]行的最明确,最优选的方法是使用.iloc和.loc索引器。 它们能够独立且同时选择行或列。
准备
此秘籍向您展示如何使用.iloc和.loc索引器从数据帧中选择行。
操作步骤
- 读取大学数据集,并将索引设置为机构名称:
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> college.head()
- 将整数传递给
.iloc索引器,以选择该位置的整个行:
>>> college.iloc[60]
CITY Anchorage
STABBR AK
HBCU 0
...
UG25ABV 0.4386
MD_EARN_WNE_P10 42500
GRAD_DEBT_MDN_SUPP 19449.5
Name: University of Alaska Anchorage, Length: 26, dtype: object
- 要获得与上一步相同的行,请将索引标签传递给
.loc索引器:
>>> college.loc['University of Alaska Anchorage']
CITY Anchorage
STABBR AK
HBCU 0
...
UG25ABV 0.4386
MD_EARN_WNE_P10 42500
GRAD_DEBT_MDN_SUPP 19449.5
Name: University of Alaska Anchorage, Length: 26, dtype: object
- 要将不相交的一组行选择为数据帧,请将整数列表传递给
.iloc索引器:
>>> college.iloc[[60, 99, 3]]
- 可以使用
.loc通过将确切的机构名称列表传递给第 4 步中相同的数据帧:
>>> labels = ['University of Alaska Anchorage',
'International Academy of Hair Design',
'University of Alabama in Huntsville']
>>> college.loc[labels]
- 将切片符号与
.iloc一起使用以选择整个数据段:
>>> college.iloc[99:102]
- 切片符号也可以与
.loc索引器一起使用,并且包含最后一个标签:
>>> start = 'International Academy of Hair Design'
>>> stop = 'Mesa Community College'
>>> college.loc[start:stop]
工作原理
将标量值,标量列表或切片对象传递给.iloc或.loc索引器,会使 Pandas 扫描索引标签中的适当行并返回它们。 如果传递单个标量值,则返回一个序列。 如果传递了列表或切片对象,则返回一个数据帧。
更多
在步骤 5 中,可以直接从步骤 4 中返回的数据帧中选择索引标签列表,而无需复制和粘贴:
>>> college.iloc[[60, 99, 3]].index.tolist()
['University of Alaska Anchorage',
'International Academy of Hair Design',
'University of Alabama in Huntsville']
另见
- 请参阅第 6 章,“索引对齐”的“检查索引对象”秘籍。
同时选择数据帧的行和列
直接使用索引运算符是从数据帧中选择一列或多列的正确方法。 但是,它不允许您同时选择行和列。 要同时选择行和列,您将需要将有效的行和列选择都用逗号传递给.iloc或.loc索引器。
准备
选择行和列的通用形式将类似于以下代码:
>>> df.iloc[rows, columns]
>>> df.loc[rows, columns]
rows和columns变量可以是标量值,列表,切片对象或布尔序列。
第 5 章,“布尔索引”中介绍了将布尔序列传递给索引器。
在此秘籍中,每个步骤都显示使用.iloc同时选择行和列,以及使用.loc进行精确复制。
操作步骤
- 读入大学数据集,并将索引设置为机构名称。 用切片符号选择前三行和前四列:
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> college.iloc[:3, :4]
>>> college.loc[:'Amridge University', :'MENONLY']
- 选择两个不同列的所有行:
>>> college.iloc[:, [4,6]].head()
>>> college.loc[:, ['WOMENONLY', 'SATVRMID']].head()
- 选择不相交的行和列:
>>> college.iloc[[100, 200], [7, 15]]
>>> rows = ['GateWay Community College',
'American Baptist Seminary of the West']
>>> columns = ['SATMTMID', 'UGDS_NHPI']
>>> college.loc[rows, columns]
- 选择一个标量值:
>>> college.iloc[5, -4]
>>> college.loc['The University of Alabama', 'PCTFLOAN']
-.401
- 切片行并选择单个列:
>>> college.iloc[90:80:-2, 5]
>>> start = 'Empire Beauty School-Flagstaff'
>>> stop = 'Arizona State University-Tempe'
>>> college.loc[start:stop:-2, 'RELAFFIL']
INSTNM
Empire Beauty School-Flagstaff 0
Charles of Italy Beauty College 0
Central Arizona College 0
University of Arizona 0
Arizona State University-Tempe 0
Name: RELAFFIL, dtype: int64
工作原理
同时选择行和列的关键之一是了解方括号中逗号的用法。 逗号左侧的选择始终根据行索引选择行。 逗号右边的选择始终根据列索引选择列。
不必同时选择行和列。 步骤 2 显示了如何选择所有行和列的子集。 冒号表示一个切片对象,该对象仅返回该维度的所有值。
更多
选择行的子集以及所有列时,不必在逗号后使用冒号。 如果没有逗号,则默认行为是选择所有列。 先前的秘籍正是以这种方式选择了行。 但是,您可以使用冒号表示所有列的一部分。 以下代码行是等效的:
>>> college.iloc[:10]
>>> college.iloc[:10, :]
同时通过整数和标签和选择数据
.iloc和.loc索引器均通过整数或标签位置选择数据,但不能同时处理两种输入类型的组合。 在早期版本的 Pandas 中,可以使用另一个索引器.ix通过整数和标签位置选择数据。 尽管这在某些特定情况下很方便,但是它本质上是模棱两可的,并且使许多 Pandas 使用者感到困惑。.ix索引器随后被弃用,因此应避免使用。
准备
在.ix弃用之前,可以使用college.ix[:5, 'UGDS_WHITE':'UGDS_UNKN']从UGDS_WHITE到UGDS_UNKN选择大学数据集的前五行和列。 现在不可能直接使用.loc或.iloc来做到这一点。 以下秘籍显示了如何找到列的整数位置,然后使用.iloc完成选择。
操作步骤
- 读入大学数据集,并将机构名称(
INSTNM)分配为索引:
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
- 使用索引方法
get_loc查找所需列的整数位置:
>>> col_start = college.columns.get_loc('UGDS_WHITE')
>>> col_end = college.columns.get_loc('UGDS_UNKN') + 1
>>> col_start, col_end
- 使用
col_start和col_end使用.iloc按整数位置选择列:
>>> college.iloc[:5, col_start:col_end]
工作原理
步骤 2 首先通过columns属性检索列索引。 索引具有get_loc方法,该方法接受索引标签并返回其整数位置。 我们找到要切片的列的开始和结束整数位置。 我们添加一个是因为用.iloc切片不包括最后一项。 步骤 3 将切片符号与行和列一起使用。
更多
我们可以做一个非常相似的操作来使.loc与整数和位置的混合使用。 下面显示了如何选择第 10 至 15 行(包括第 10 行)以及UGDS_WHITE至UGDS_UNKN列:
>>> row_start = df_college.index[10]
>>> row_end = df_college.index[15]
>>> college.loc[row_start:row_end, 'UGDS_WHITE':'UGDS_UNKN']
使用.ix进行相同的操作(已弃用,因此请勿执行此操作)如下所示:
>>> college.ix[10:16, 'UGDS_WHITE':'UGDS_UNKN']
通过将.loc和.iloc链接在一起可以实现相同的结果,但是链接索引器通常不是一个好主意:
>>> college.iloc[10:16].loc[:, 'UGDS_WHITE':'UGDS_UNKN']
另见
- 请参考第 2 章,“基本数据帧操作”的“用方法选择列”。
加速标量选择
.iloc和.loc索引器都能够从序列或数据帧中选择单个元素(标量值)。 但是,存在分度器.iat和.at,它们分别以更快的速度实现相同的功能。 与.iloc相似,.iat索引器使用整数位置进行选择,并且必须传递两个以逗号分隔的整数。 与.loc相似,.at索引使用标签进行选择,并且必须传递一个索引和由逗号分隔的列标签。
准备
如果计算时间至关重要,则此秘籍很有价值。 当使用标量选择时,它显示了.iat和.at相对于.iloc和.loc的性能提高。
操作步骤
- 以机构名称作为索引,读取
college记分板数据集。 将大学名称和列名称传递给.loc,以便选择标量值:
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> cn = 'Texas A & M University-College Station'
>>> college.loc[cn, 'UGDS_WHITE']
.661
- 使用
.at获得相同的结果:
>>> college.at[cn, 'UGDS_WHITE']
.661
- 使用
%timeit魔术命令查找速度差异:
>>> %timeit college.loc[cn, 'UGDS_WHITE']
8.97 µs ± 617 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
>>> %timeit college.at[cn, 'UGDS_WHITE']
6.28 µs ± 214 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
- 找到前面选择的整数位置,然后计时
.iloc和.iat之间的差:
>>> row_num = college.index.get_loc(cn)
>>> col_num = college.columns.get_loc('UGDS_WHITE')
>>> row_num, col_num
(3765, 10)
>>> %timeit college.iloc[row_num, col_num]
9.74 µs ± 153 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
>>> %timeit college.iat[row_num, col_num]
7.29 µs ± 431 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
工作原理
标量索引器.iat和.at仅接受标量值。 如果其他任何东西传递给他们,他们就会失败。 在进行标量选择时,它们是.iloc和.loc的直接替代品。timeit魔术命令在以两个百分号开头时对整个代码块计时,而在以一个百分号开头时一次。 它表明,通过切换到标量索引器,平均可节省约 2.5 微秒。 这可能并不多,但是如果在程序中重复进行标量选择,则可能会很快加起来。
更多
.iat和.at都可以与序列一起使用。 给它们传递一个标量值,它们将返回一个标量:
>>> state = college['STBBR'] # Select a Series
>>> state.iat[1000]
'IL'
>>> state.at['Stanford University']
'CA'
以延迟方式对行切片
本章前面的秘籍展示了如何使用.iloc和.loc索引器选择任一维度中的序列和数据帧的子集。 选择行的快捷方式仅包含索引运算符本身。 这只是显示 Pandas 其他功能的捷径,但索引运算符的主要功能实际上是选择数据帧的列。 如果要选择行,则最好使用.iloc或.loc,因为它们是明确的。
准备
在此秘籍中,我们将切片对象传递给序列和数据帧索引运算符。
操作步骤
- 读取以机构名称作为索引的大学数据集,然后从索引 10 到 20 选择每隔一行:
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> college[10:20:2]
- 序列也存在相同的切片:
>>> city = college['CITY']
>>> city[10:20:2]
INSTNM
Birmingham Southern College Birmingham
Concordia College Alabama Selma
Enterprise State Community College Enterprise
Faulkner University Montgomery
New Beginning College of Cosmetology Albertville
Name: CITY, dtype: object
- 序列和数据帧都可以仅通过索引运算符按标签进行切片:
>>> start = 'Mesa Community College'
>>> stop = 'Spokane Community College'
>>> college[start:stop:1500]
- 这是带有序列的同一标签:
>>> city[start:stop:1500]
INSTNM
Mesa Community College Mesa
Hair Academy Inc-New Carrollton New Carrollton
National College of Natural Medicine Portland
Name: CITY, dtype: object
工作原理
索引运算符根据传递给它的对象类型来更改行为。 以下伪代码概述了数据帧索引运算符如何处理其传递的对象:
>>> df[item] # Where `df` is a DataFrame and item is some object
If item is a string then
Find a column name that matches the item exactly
Raise KeyError if there is no match
Return the column as a Series
If item is a list of strings then
Raise KeyError if one or more strings in item don't match columns
Return a DataFrame with just the columns in the list
If item is a slice object then
Works with either integer or string slices
Raise KeyError if label from label slice is not in index
Return all ROWS that are selected by the slice
If item is a list, Series or ndarray of booleans then
Raise ValueError if length of item not equal to length of DataFrame
Use the booleans to return only the rows with True in same location
前面的逻辑涵盖了所有最常见的情况,但并不详尽。 序列的逻辑与数据帧的逻辑稍有不同,实际上更为复杂。 由于其复杂性,最好避免在序列上仅使用索引运算符本身,而应使用显式的.iloc和.loc索引器。
序列的索引运算符的一种可接受的用例是在进行布尔索引时。 有关更多详细信息,请参见第 6 章“索引对齐”。
我在本节中将这种行切片称为惰性,因为它不使用更明确的.iloc或.loc。 就个人而言,我总是在对行进行切片时使用这些索引器,因为从来没有确切地知道我在做什么。
更多
重要的是要知道,这种延迟切片不适用于列,仅适用于数据帧的行和序列,也不能同时选择行和列。 以下面的代码为例,该代码尝试选择前十行和两列:
>>> college[:10, ['CITY', 'STABBR']]
TypeError: unhashable type: 'slice'
要以这种方式进行选择,您需要使用.loc或.iloc。 这是一种可能的方法,该方法首先选择所有机构标签,然后使用基于标签的索引器.loc:
>>> first_ten_instnm = college.index[:10]
>>> college.loc[first_ten_instnm, ['CITY', 'STABBR']]
按词典顺序切片
.loc索引器通常根据索引的确切字符串标签选择数据。 但是,它还允许您根据索引中值的字典顺序选择数据。 具体来说,.loc允许您使用切片符号按词典顺序选择带有索引的所有行。 仅在对索引排序时有效。
准备
在本秘籍中,您将首先对索引进行排序,然后在.loc索引器中使用切片符号选择两个字符串之间的所有行。
操作步骤
- 读取大学数据集,并将机构名称设置为索引:
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
- 尝试选择所有词典名称在
'Sp'和'Su'之间的大学:
>>> college.loc['Sp':'Su']
KeyError: 'Sp'
- 由于索引未排序,因此前面的命令失败。 让我们继续对索引进行排序:
>>> college = college.sort_index()
- 现在,让我们从步骤 2 重新运行相同的命令:
>>> college.loc['Sp':'Su']
工作原理
.loc的正常行为是根据传递给它的确切标签来选择数据。 在索引中找不到这些标签时,将引发KeyError。 但是,只要按字典顺序对索引进行排序并将切片传递给该索引,就会存在对此行为的一个特殊例外。 现在可以在切片的start和stop标签之间进行选择,即使它们不是索引的精确值也是如此。
更多
使用此秘籍,可以轻松地在两个字母之间选择大学。 例如,要选择所有以字母D至S开头的大学,则可以使用college.loc['D':'T']。 像这样的切片仍然包含最后一个索引,因此从技术上讲,这将返回一确切名称为T的大学。
当索引按相反方向排序时,这种切片方式也适用。 您可以使用索引属性is_monotonic_increasing或is_monotonic_decreasing确定索引的排序方向。 为了使字典式切片能够正常工作,这些参数中的任何一个都必须为True。 例如,以下代码按字典顺序对索引从Z到A进行排序:
>>> college = college.sort_index(ascending=False)
>>> college.index.is_monotonic_decreasing
True
>>> college.loc['E':'B']
Python 将所有大写字母排在小写字母之前,并将所有整数排在大写字母之前。
五、布尔索引
在本章中,我们将介绍以下主题:
- 计算布尔统计量
- 构造多个布尔条件
- 使用布尔索引进行过滤
- 使用索引选择来替代布尔索引
- 使用唯一索引和排序索引进行选择
- 了解股票价格
- 翻译 SQL
WHERE子句 - 确定股票市场收益的正态性
- 使用
query方法提高布尔索引的可读性 - 使用
where方法保留序列 - 屏蔽数据帧的行
- 使用布尔值,整数位置和标签进行选择
介绍
从数据集中过滤数据是最常见的基本操作之一。 有许多方法可以使用布尔下标过滤(或子集)Pandas 中的数据。 布尔索引(也称为布尔选择)可能是一个令人困惑的术语,但出于 Pandas 的目的,它是指通过为每行提供布尔值(True或False)来选择行 。 这些布尔值通常存储在序列或 NumPy ndarray中,通常是通过将布尔条件应用于数据帧中的一个或多个列来创建的。 我们首先创建布尔序列并计算它们的统计量,然后继续创建更复杂的条件,然后以多种方式使用布尔索引来过滤数据。
计算布尔统计量
首次引入布尔序列时,计算有关它们的基本摘要统计信息可能会很有帮助。 布尔序列的每个值的取值为 0 或 1,因此所有适用于数值的序列方法也适用于布尔值。
准备
在此秘籍中,我们通过将条件应用于数据列来创建布尔序列,然后从中计算汇总统计信息。
操作步骤
- 读取
movie数据集,将索引设置为电影标题,然后检查前几行:
>>> movie = pd.read_csv('data/movie.csv', index_col='movie_title')
>>> movie.head()
- 通过对
duration序列使用大于比较运算符,确定每部电影的时长是否大于两个小时:
>>> movie_2_hours = movie['duration'] > 120
>>> movie_2_hours.head(10)
movie_title
Avatar True
Pirates of the Caribbean: At World's End True
Spectre True
The Dark Knight Rises True
Star Wars: Episode VII - The Force Awakens False
John Carter True
Spider-Man 3 True
Tangled False
Avengers: Age of Ultron True
Harry Potter and the Half-Blood Prince True
Name: duration, dtype: bool
- 现在,我们可以使用该序列来确定超过两个小时的电影数量:
>>> movie_2_hours.sum()
1039
- 要查找超过两个小时的数据集中的电影百分比,请使用
mean方法:
>>> movie_2_hours.mean()
0.2114
- 不幸的是,步骤 4 的输出具有误导性。
duration列缺少一些值。 如果回头看步骤 1 的数据帧输出,您将看到最后一行缺少duration的值。 为此,步骤 2 中的布尔条件返回False。 我们需要先删除丢失的值,然后求值条件并取均值:
>>> movie['duration'].dropna().gt(120).mean()
.2112
- 使用
describe方法输出有关布尔序列的一些摘要统计信息:
>>> movie_2_hours.describe()
count 4916
unique 2
top False
freq 3877
Name: duration, dtype: object
工作原理
大多数数据帧不会像我们的电影数据集那样具有布尔值列。 产生布尔序列的最直接方法是使用比较运算符之一将条件应用于列之一。 在步骤 2 中,我们使用大于号运算符来测试每部电影的时长是否超过两个小时(120 分钟)。 第 3 步和第 4 步从布尔序列计算两个重要量,即和和均值。 这些方法是可行的,因为 Python 将False/True求值为 0/1。
您可以自己证明布尔级数的均值代表True值的百分比。 为此,请使用value_counts方法,将normalize参数设置为True,以获取其分布:
>>> movie_2_hours.value_counts(normalize=True)
False 0.788649
True 0.211351
Name: duration, dtype: float64
步骤 5 提醒我们步骤 4 的错误结果。即使duration列缺少值,布尔条件也将所有这些比较与缺少的值求值为False。 删除这些缺失值使我们能够计算出正确的统计量。 通过方法链接,只需一步即可完成。
步骤 6 显示,Pandas 通过显示频率信息对待布尔列的方式类似于对待对象数据类型的方式。 这是考虑布尔序列的自然方法,而不是像对数字数据那样显示分位数。
更多
可以比较来自同一数据帧的两列以生成布尔序列。 例如,我们可以确定具有演员 1 的 Facebook 点赞数比演员 2 更多的电影的百分比。要做到这一点,我们将选择这两列,然后删除任何其中一部电影缺少值的行。 然后,我们将进行比较并计算均值:
>>> actors = movie[['actor_1_facebook_likes',
'actor_2_facebook_likes']].dropna()
>>> (actors['actor_1_facebook_likes'] >
actors['actor_2_facebook_likes']).mean()
.978
另见
- 参阅第 1 章,“Pandas 基础”的“将序列方法链接到一起”秘籍
- 参阅第 1 章,“Pandas 基础”中的“使用运算符”秘籍
构造多个布尔条件
在 Python 中,布尔表达式使用内置的逻辑运算符and,or和not。 这些关键字不适用于 Pandas 中的布尔索引,而是分别用&,|和~代替。 此外,每个表达式必须用括号括起来,否则会产生错误。
准备
为数据集构造一个精确的过滤器可能会使您将多个布尔表达式组合在一起以提取一个精确的子集。 在此秘籍中,我们将构造多个布尔表达式,然后将它们组合在一起以查找title_year为 2000 之前或 2009 年之后,imdb_score大于 8,并且content_rating为PG-13的所有电影。
操作步骤
- 加载电影数据集并将索引设置为标题:
>>> movie = pd.read_csv('data/movie.csv', index_col='movie_title')
- 创建一个变量以将每个条件集作为布尔序列独立保存:
>>> criteria1 = movie.imdb_score > 8
>>> criteria2 = movie.content_rating == 'PG-13'
>>> criteria3 = ((movie.title_year < 2000) |
(movie.title_year > 2009))
>>> criteria2.head() # all criteria Series look similar
movie_title
Avatar True
Pirates of the Caribbean: At World's End True
Spectre True
The Dark Knight Rises True
Star Wars: Episode VII - The Force Awakens False
Name: content_rating, dtype: bool
- 将所有条件组合到一个布尔序列中:
>>> criteria_final = criteria1 & criteria2 & criteria3
>>> criteria_final.head()
movie_title
Avatar False
Pirates of the Caribbean: At World's End False
Spectre False
The Dark Knight Rises True
Star Wars: Episode VII - The Force Awakens False
dtype: bool
工作原理
可以使用标准比较运算符(<,>,==,!=,<=和>=)将序列中的所有值与标量值进行比较。 表达式movie.imdb_score > 8产生布尔序列,其中所有超过 8 的imdb_score值价格均为True,而小于或等于 8 的价格为False。 此布尔序列的索引保留与原始索引相同的索引,在这种情况下,为电影的标题。
criteria3变量由两个独立的布尔表达式创建。 每个表达式必须用括号括起来才能正常运行。 管道字符|用于在两个序列的每个值之间创建逻辑or条件。
所有三个条件都必须为True以匹配秘籍要求。 它们每个都与和号字符&组合在一起,后者在每个序列值之间创建逻辑and条件。
更多
Pandas 对逻辑运算符使用不同语法的结果是运算符优先级不再相同。 比较运算符的优先级高于and,or和not。 但是,Pandas 的新运算符(按位运算符&,|和~)比比较运算符具有更高的优先级,因此需要括号。 一个例子可以帮助清除这一点。 采取以下表达式:
>>> 5 < 10 and 3 > 4
False
在前面的表达式中,首先求值5 < 10,然后求值3 < 4,最后求值and。 Python 通过表达式进行如下操作:
>>> 5 < 10 and 3 > 4
>>> True and 3 > 4
>>> True and False
>>> False
让我们看一下如果criteria3中的表达式编写如下会发生什么:
>>> movie.title_year < 2000 | movie.title_year > 2009
TypeError: cannot compare a dtyped [float64] array with a scalar of type [bool]
由于按位运算符的优先级比比较运算符的优先级高,因此2000 | movie.title_year首先被求值,这是没有意义的,并且会产生错误。 因此,需要括号以正确的顺序求值操作。
为何 Pandas 不能使用and,or和not? 当求值这些关键字时,Python 尝试查找整个对象的真实性。 因为将整个序列而不是每个元素作为True或False都没有意义,Pandas 都会引发错误。
Python 中的许多对象都具有布尔表示形式。 例如,除 0 以外的所有整数都被视为True。 除空字符串外,所有字符串均为True。 所有非空集,元组,字典和列表都是True。 空的数据帧或序列不会求值为True或False,而是会引发错误。 通常,要检索 Python 对象的真实性,请将其传递给bool函数。
另见
使用布尔索引进行过滤
序列和数据帧对象的布尔选择实际上是相同的。 两者都通过将与要过滤的对象索引相同的布尔序列传递给索引运算符来工作。
准备
此秘籍为不同的电影组构造了两个复杂且独立的布尔标准。 第一组电影是根据之前的秘籍制作的,包括imdb_score大于 8,content_rating为PG-13和title_year在 2000 年之前或 2009 年之后的电影。第二组电影包括imdb_score小于 5,content_rating的 R 和title_year在 2000 年至 2010 年之间的数据。
操作步骤
- 读取
movie数据集,将索引设置为movie_title,并创建第一组条件:
>>> movie = pd.read_csv('data/movie.csv', index_col='movie_title')
>>> crit_a1 = movie.imdb_score > 8
>>> crit_a2 = movie.content_rating == 'PG-13'
>>> crit_a3 = (movie.title_year < 2000) | (movie.title_year > 2009)
>>> final_crit_a = crit_a1 & crit_a2 & crit_a3
- 为第二组电影创建条件:
>>> crit_b1 = movie.imdb_score < 5
>>> crit_b2 = movie.content_rating == 'R'
>>> crit_b3 = ((movie.title_year >= 2000) &
(movie.title_year <= 2010))
>>> final_crit_b = crit_b1 & crit_b2 & crit_b3
- 使用 pandas
or运算符组合两组标准。 这将产生一个布尔序列,其中的任何一部电影都是这两组电影的成员:
>>> final_crit_all = final_crit_a | final_crit_b
>>> final_crit_all.head()
movie_title
Avatar False
Pirates of the Caribbean: At World's End False
Spectre False
The Dark Knight Rises True
Star Wars: Episode VII - The Force Awakens False
dtype: bool
- 拥有布尔序列后,只需将其传递给索引运算符即可过滤数据:
>>> movie[final_crit_all].head()
- 我们已经成功过滤了数据和数据帧的所有列。 我们无法轻松地执行手动检查来确定过滤器是否正常工作。 让我们使用
.loc索引器过滤行和列:
>>> cols = ['imdb_score', 'content_rating', 'title_year']
>>> movie_filtered = movie.loc[final_crit_all, cols]
>>> movie_filtered.head(10)
工作原理
在步骤 1 和步骤 2 中,每组条件都是从更简单的布尔表达式构建的。 不必像此处所做的那样为每个布尔表达式创建一个不同的变量,但是这样做确实使读取和调试任何逻辑错误变得容易得多。 当我们需要两组电影时,步骤 3 使用 Pandas 逻辑or运算符将它们组合在一起。
步骤 4 显示了布尔索引工作原理的确切语法。 您只需将从第 3 步创建的布尔值序列直接传递给索引运算符即可。 仅选择final_crit_all中具有True值的电影。
如步骤 5 所示,布尔索引还可以与.loc索引器配合使用,同时执行布尔索引和单个列选择。 精简的数据帧易于手动检查 逻辑是否正确实现。
布尔索引与.iloc索引运算符不能完全兼容。 如果将布尔序列传递给它,则会引发异常。 但是,如果您传递布尔 N 维数组,则它将与其他索引器在此秘籍中的行为相同。
更多
如前所述,可以使用一个长布尔表达式代替其他几个短布尔表达式。 要使用一长行代码复制第 1 步中的final_crit_a变量,我们可以执行以下操作:
>>> final_crit_a2 = (movie.imdb_score > 8) & \
(movie.content_rating == 'PG-13') & \
((movie.title_year < 2000) |
(movie.title_year > 2009))
>>> final_crit_a2.equals(final_crit_a)
True
另见
使用索引选择来替代布尔索引
通过使用索引,可以复制布尔选择的特定情况。 通过索引进行选择更加直观,并提高了可读性。
准备
在此秘籍中,我们使用college数据集通过布尔索引和索引选择从特定状态中选择所有机构,然后将它们各自的性能相互比较。
操作步骤
- 读取
college数据集,并使用布尔索引从德克萨斯(TX)州选择所有机构:
>>> college = pd.read_csv('data/college.csv')
>>> college[college['STABBR'] == 'TX'].head()
Pandas official documentation on
- 要使用索引选择复制此内容,我们需要将
STABBR列移入索引。 然后,我们可以在.loc索引器中使用基于标签的选择:
>>> college2 = college.set_index('STABBR')
>>> college2.loc['TX'].head()
- 让我们比较两种方法的速度:
>>> %timeit college[college['STABBR'] == 'TX']
1.43 ms ± 53.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit college2.loc['TX']
526 µs ± 6.67 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
- 布尔索引的时间是索引选择时间的三倍。 由于设置索引不是免费的,所以让我们也计时一下该操作:
>>> %timeit college2 = college.set_index('STABBR')
1.04 ms ± 5.37 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
工作原理
步骤 1 通过确定哪些数据行具有STABBR等于TX来创建布尔序列。 该序列传递给索引运算符,该运算符对数据进行子集化。 可以通过将同一列移到索引,并简单地将基本的基于标签的索引选择与.loc一起使用来复制此过程。 通过索引选择比布尔选择快得多。
更多
此秘籍仅选择一个状态。 可以使用布尔选择和索引选择来选择多个状态。 让我们选择德州(TX),加利福尼亚(CA)和纽约(NY)。 使用布尔选择时,可以使用isin方法,但是使用索引时,只需将列表传递给.loc即可:
>>> states = ['TX', 'CA', 'NY']
>>> college[college['STABBR'].isin(states)]
>>> college2.loc[states]
故事的内容比该秘籍的解释要多得多。 Pandas 根据索引是唯一索引还是排序索引来不同地实现索引。 有关更多详细信息,请参见以下秘籍。
使用唯一索引和排序索引进行选择
当索引是唯一的或已排序时,索引选择性能会大大提高。 先前的秘籍使用了包含重复项的未排序索引,因此选择速度相对较慢。
准备
在此秘籍中,我们使用college数据集来形成唯一索引或排序索引,以提高索引选择的性能。 我们还将继续将性能与布尔索引进行比较。
操作步骤
- 读取大学数据集,以
STABBR作为索引创建一个单独的数据帧,然后检查索引是否已排序:
>>> college = pd.read_csv('data/college.csv')
>>> college2 = college.set_index('STABBR')
>>> college2.index.is_monotonic
False
- 对
college2中的索引进行排序,并将其存储为另一个对象:
>>> college3 = college2.sort_index()
>>> college3.index.is_monotonic
True
- 从所有三个数据帧中选择德克萨斯州(
TX)的时间:
>>> %timeit college[college['STABBR'] == 'TX']
1.43 ms ± 53.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit college2.loc['TX']
526 µs ± 6.67 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit college3.loc['TX']
183 µs ± 3.67 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
- 排序后的索引执行速度比布尔选择快近一个数量级。 现在让我们转向唯一索引。 为此,我们使用机构名称作为索引:
>>> college_unique = college.set_index('INSTNM')
>>> college_unique.index.is_unique
True
- 让我们选择带有布尔索引的斯坦福大学:
>>> college[college['INSTNM'] == 'Stanford University']
- 让我们通过索引选择来选择斯坦福大学:
>>> college_unique.loc['Stanford University']
CITY Stanford
STABBR CA
HBCU 0
...
UG25ABV 0.0401
MD_EARN_WNE_P10 86000
GRAD_DEBT_MDN_SUPP 12782
Name: Stanford University, dtype: object
- 它们都产生相同的数据,只是对象不同。 让我们为每种方法计时:
>>> %timeit college[college['INSTNM'] == 'Stanford University']
1.3 ms ± 56.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit college_unique.loc['Stanford University']
157 µs ± 682 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
工作原理
当索引未排序且包含重复项时(如college2一样),Pandas 将需要检查索引中的每个单个值以进行正确选择。 像college3一样对索引进行排序时,pandas 利用称为二分搜索的算法来大大提高性能。
在秘籍的后半部分,我们使用唯一列作为索引。 Pandas 通过哈希表实现唯一索引,从而使选择速度更快。 几乎可以在同一时间查找每个索引位置,而不管其长度如何。
更多
布尔选择比索引选择具有更大的灵活性,因为可以对任意数量的列进行条件调整。 在此秘籍中,我们使用单列作为索引。 可以将多个列连接在一起以形成索引。 例如,在以下代码中,我们将索引设置为等于city和state列的连接:
>>> college.index = college['CITY'] + ', ' + college['STABBR']
>>> college = college.sort_index()
>>> college.head()
从这里,我们可以从特定城市和州的组合中选择所有大学,而无需布尔索引。 让我们从Miami, FL中选择所有大学:
>>> college.loc['Miami, FL'].head()
我们可以将这种复合索引选择与布尔索引的速度进行比较。 有一个数量级以上的差异:
>>> %%timeit
>>> crit1 = college['CITY'] == 'Miami'
>>> crit2 = college['STABBR'] == 'FL'
>>> college[crit1 & crit2]
2.43 ms ± 80.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
>>> %timeit college.loc['Miami, FL']
197 µs ± 8.69 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
另见
了解股票价格
购买了多头股票的投资者显然希望以历史最高价或接近历史最高价的价格出售股票。 当然,这在实践中很难做到,尤其是当股价仅将其历史的一小部分花费在一定阈值之上时。 我们可以使用布尔索引来查找股票花费高于或低于某个特定值的所有时间点。 此练习可以帮助我们了解某些股票的交易范围。
准备
在此秘籍中,我们研究了从 2010 年初到 2017 年中期的斯伦贝谢股票。 我们使用布尔索引来提取这段时间内收盘价的最低和最高百分之十的序列。 然后,我们绘制所有点并突出显示上下百分之十的点。
操作步骤
- 读入斯伦贝谢股票数据,将
Date列放入索引,并将其转换为DatetimeIndex:
>>> slb = pd.read_csv('data/slb_stock.csv', index_col='Date',
parse_dates=['Date'])
>>> slb.head()
- 选择收盘价作为序列,然后使用
describe方法返回摘要统计信息作为序列:
>>> slb_close = slb['Close']
>>> slb_summary = slb_close.describe(percentiles=[.1, .9])
>>> slb_summary
count 1895.000000
mean 79.121905
std 11.767802
min 51.750000
10% 64.892000
50% 78.000000
90% 93.248000
max 117.950000
Name: Close, dtype: float64
- 使用布尔选择,选择最高或最低百分之十的所有收盘价:
>>> upper_10 = slb_summary.loc['90%']
>>> lower_10 = slb_summary.loc['10%']
>>> criteria = (slb_close < lower_10) | (slb_close > upper_10)
>>> slb_top_bottom_10 = slb_close[criteria]
- 在所有收盘价黑色上方,将生成的过滤后的序列以浅灰色绘制。 使用
matplotlib库在第十和第九十个百分位处绘制水平线:
>>> slb_close.plot(color='black', figsize=(12,6))
>>> slb_top_bottom_10.plot(marker='o', style=' ',
ms=4, color='lightgray')
>>> xmin = criteria.index[0]
>>> xmax = criteria.index[-1]
>>> plt.hlines(y=[lower_10, upper_10], xmin=xmin,
xmax=xmax, color='black')
工作原理
步骤 2 中describe方法的结果本身就是一个以识别摘要统计量作为其索引标签的序列。 该摘要序列用于将第十和九十个百分位存储为它们自己的变量。 步骤 3 使用布尔索引来仅选择分布的高和低十分之一的那些值。
序列和数据帧都具有通过plot方法的直接绘图函数。 对plot方法的第一个调用来自slb_close序列,其中包含所有 SLB 收盘价。 这是绘图中的黑线。 来自slb_filtered的点直接在收盘价上方绘制为灰色标记。style参数设置为单个空格,因此不会画线。ms参数设置标记大小。
Matplotlib 带有便利函数hlines,它可以绘制水平线。 它获取y值的列表,并将它们从xmin绘制到xmax。
从我们创建的图块的新角度来看,很明显地看到,尽管 SLB 的历史最高价接近每股 120 美元,但在过去七年中只有 10% 的交易日超过了 93 美元。
更多
我们可以使用 matplotlib 的fill_between函数,而不是在收盘价上方绘制红点(黑点)以指示上下十分之一百分位。 此函数填充两行之间的所有区域。 它带有一个可选的where参数,该参数接受一个布尔序列,并警告其确切要填充的位置:
>>> slb_close.plot(color='black', figsize=(12,6))
>>> plt.hlines(y=[lower_10, upper_10],
xmin=xmin, xmax=xmax,color='lightgray')
>>> plt.fill_between(x=criteria.index, y1=lower_10,
y2=slb_close.values, color='black')
>>> plt.fill_between(x=criteria.index,y1=lower_10,
y2=slb_close.values, where=slb_close < lower_10,
color='lightgray')
>>> plt.fill_between(x=criteria.index, y1=upper_10,
y2=slb_close.values, where=slb_close > upper_10,
color='lightgray')
另见
- 请参阅第 11 章,“使用 Matplotlib,Pandas 和 Seaborn 进行可视化”
翻译 SQL WHERE 子句
许多 Pandas 用户将使用通用的结构化查询语言(SQL)直接从数据库中处理数据。 SQL 是用于定义,操作和控制存储在数据库中的数据的标准化语言。SELECT语句是使用 SQL 选择,过滤,聚合和排序数据的最常用方法。 Pandas 可以连接数据库并向它们发送 SQL 语句。
SQL 是数据科学家要了解的非常重要的语言。 世界上许多数据都存储在数据库中,这需要 SQL 来检索,操作和执行分析。 SQL 语法非常简单易学。 Oracle,Microsoft,IBM 等公司提供了许多不同的 SQL 实现。 尽管语法在不同的实现之间不兼容,但其核心看起来几乎相同。
准备
在 SQL SELECT语句中,WHERE子句非常常见,并过滤数据。 此秘籍将编写与选择雇员数据集的特定子集的 SQL 查询等效的 Pandas 代码。
无需了解任何 SQL 语法即可使用此秘籍。
假设我们有一项任务是找到所有在警察或消防部门工作的女性雇员,其基本工资在 80 到 12 万美元之间。 以下 SQL 语句将为我们回答此查询:
SELECT
UNIQUE_ID,
DEPARTMENT,
GENDER,
BASE_SALARY
FROM
EMPLOYEE
WHERE
DEPARTMENT IN ('Houston Police Department-HPD',
'Houston Fire Department (HFD)') AND
GENDER = 'Female' AND
BASE_SALARY BETWEEN 80000 AND 120000;
操作步骤
- 读取
employee数据集作为数据帧:
>>> employee = pd.read_csv('data/employee.csv')
- 在过滤出数据之前,对每个过滤后的列进行一些手动检查以了解将在过滤器中使用的确切值会有所帮助:
>>> employee.DEPARTMENT.value_counts().head()
Houston Police Department-HPD 638
Houston Fire Department (HFD) 384
Public Works & Engineering-PWE 343
Health & Human Services 110
Houston Airport System (HAS) 106
Name: DEPARTMENT, dtype: int64
>>> employee.GENDER.value_counts()
Male 1397
Female 603
>>> employee.BASE_SALARY.describe().astype(int)
count 1886
mean 55767
std 21693
min 24960
25% 40170
50% 54461
75% 66614
max 275000
Name: BASE_SALARY, dtype: int64
- 为每个条件写一个声明。 使用
isin方法测试是否等于多个值之一:
>>> depts = ['Houston Police Department-HPD',
'Houston Fire Department (HFD)']
>>> criteria_dept = employee.DEPARTMENT.isin(depts)
>>> criteria_gender = employee.GENDER == 'Female'
>>> criteria_sal = (employee.BASE_SALARY >= 80000) & \
(employee.BASE_SALARY <= 120000)
- 将所有布尔序列结合在一起:
>>> criteria_final = (criteria_dept &
criteria_gender &
criteria_sal)
- 使用布尔索引来选择仅符合最终条件的行:
>>> select_columns = ['UNIQUE_ID', 'DEPARTMENT',
'GENDER', 'BASE_SALARY']
>>> employee.loc[criteria_final, select_columns].head()
工作原理
在实际进行任何过滤之前,您显然需要知道将使用的确切字符串名称。 序列value_counts方法是获取确切的字符串名称和该值的出现次数的极好方法。
isin序列方法等效于 SQL IN运算符,并接受要保留的所有可能值的列表。 可以使用OR条件序列来复制此表达式,但效率不高或惯用。
薪水标准criteria_sal是通过组合两个简单的不等式表达式形成的。 最后,所有条件都与 Pandasand运算符&结合在一起,以产生单个布尔序列作为过滤器。
更多
对于许多操作,Pandas 有多种方法来做同一件事。 在前面的秘籍中,薪水标准使用两个单独的布尔表达式。 与 SQL 相似,序列具有between方法,其工资标准等效编写如下:
>>> criteria_sal = employee.BASE_SALARY.between(80000, 120000)
isin的另一个有用的应用是提供由其他一些 pandas 语句自动生成的值序列。 这样可以避免进行任何手动调查来查找要存储在列表中的确切字符串名称。 相反,让我们尝试从最经常出现的五个部门中排除行:
>>> top_5_depts = employee.DEPARTMENT.value_counts().index[:5]
>>> criteria = ~employee.DEPARTMENT.isin(top_5_depts)
>>> employee[criteria]
SQL 的等效项如下:
SELECT
*
FROM
EMPLOYEE
WHERE
DEPARTMENT not in
(
SELECT
DEPARTMENT
FROM (
SELECT
DEPARTMENT,
COUNT(1) as CT
FROM
EMPLOYEE
GROUP BY
DEPARTMENT
ORDER BY
CT DESC
LIMIT 5
)
);
注意,使用了 pandas 否定运算符~,它否定了序列的所有布尔值。
另见
- Pandas
isin和between序列方法的官方文档 - 请参阅第 9 章,“合并 Pandas 对象”中的“连接到 SQL 数据库”秘籍。
- W3Schools 的 SQL 基本介绍
- SQL
IN运算符 - SQL
BETWEEN运算符 - SQL 子句
确定股票市场收益的正态性
在基础统计教科书中,正态分布非常依赖于描述许多不同的数据种群。 尽管大多数时间里许多随机过程的确看起来像正态分布,但现实生活中往往更为复杂。 股市回报率是分布的主要示例,该分布看上去看起来很正常,但实际上相差很远。
准备
该秘籍描述了如何查找互联网零售巨头亚马逊的每日股市收益,并非正式地测试它们是否遵循正态分布。
操作步骤
- 加载亚马逊库存数据并将日期设置为索引:
>>> amzn = pd.read_csv('data/amzn_stock.csv', index_col='Date',
parse_dates=['Date'])
>>> amzn.head()
- 通过仅选择收盘价然后使用
pct_change方法获得每日收益率来创建序列:
>>> amzn_daily_return = amzn.Close.pct_change()
>>> amzn_daily_return.head()
Date
2010-01-04 NaN
2010-01-05 0.005900
2010-01-06 -0.018116
2010-01-07 -0.017013
2010-01-08 0.027077
Name: Close, dtype: float64
- 删除缺失值并绘制收益的直方图,以目视检查分布:
>>> amzn_daily_return = amzn_daily_return.dropna()
>>> amzn_daily_return.hist(bins=20)
- 正态分布大致遵循 68-95-99.7 规则-这意味着 68% 的数据介于平均值的 1 个标准差之间,95% 介于 2 个平均值之间和 39.7% 介于 3 个平均值之间。我们现在将计算均值介于 1、2 和 3 个标准差之间的每日收益的百分比。 为此,我们需要均值和标准差:
>>> mean = amzn_daily_return.mean()
>>> std = amzn_daily_return.std()
- 计算每个观察值的
z-score的绝对值。z-score是偏离平均值的标准差数:
>>> abs_z_score = amzn_daily_return.sub(mean).abs().div(std)
- 查找在 1、2 和 3 个标准差内的收益百分比:
>>> pcts = [abs_z_score.lt(i).mean() for i in range(1,4)]
>>> print('{:.3f} fall within 1 standard deviation. '
'{:.3f} within 2 and {:.3f} within 3'.format(*pcts))
0.787 fall within 1 standard deviation. 0.957 within 2 and 0.985 within 3
工作原理
默认情况下,pct_change序列方法计算当前元素和上一个元素之间的百分比变化。 这会将原始股票收盘价转换为每日百分比收益。 返回的序列的第一个元素是缺少值,因为没有先前的价格。
直方图是用于汇总和可视化一维数字数据的奇妙图。 从图中可以明显看出,分布是对称的,但仍然很难确定其是否为正态。 有正式的统计程序可以确定分布的正态性,但是我们仅会发现数据与 68-95-99.7 规则的匹配程度。
步骤 5 为每个观测值计算远离平均值的标准差数,称为z-score。 此步骤使用方法而不是符号(-和/)进行减法和除法。 小于的方法也用于步骤 6 中的符号。
在步骤 6 中取平均值似乎有些奇怪。abs_z_score.lt(1)表达式的结果是布尔序列。 当布尔值求值为 0 或 1 时,取该序列的平均值将返回True元素的百分比,这就是我们所希望的。
现在,我们可以将结果数(78.7-95.7-98.5)与 68-95-99.7 规则进行比较,从而更轻松地确定收益的正态性。 百分比与 1 和 3 个标准差的规则有很大差异,我们可以得出结论,亚马逊的每日股票收益率不遵循正态分布。
更多
为了使这一过程自动化,我们可以编写一个函数,该函数在中接收股票数据,并输出日收益率的直方图以及与平均值相差 1、2 和 3 个标准差的百分比。 下面的函数执行此操作,并将方法替换为其对应的符号:
>>> def test_return_normality(stock_data):
close = stock_data['Close']
daily_return = close.pct_change().dropna()
daily_return.hist(bins=20)
mean = daily_return.mean()
std = daily_return.std()
abs_z_score = abs(daily_return - mean) / std
pcts = [abs_z_score.lt(i).mean() for i in range(1,4)]
print('{:.3f} fall within 1 standard deviation. '
'{:.3f} within 2 and {:.3f} within 3'.format(*pcts))
>>> slb = pd.read_csv('data/slb_stock.csv', index_col='Date',
parse_dates=['Date'])
>>> test_return_normality(slb)
0.742 fall within 1 standard deviation. 0.946 within 2 and 0.986 within 3
另见
使用query方法提高布尔索引的可读性
布尔索引不一定是读取或写入的最令人愉快的语法,尤其是在使用单行编写复杂过滤器时。 Pandas 通过数据帧的query方法具有替代的基于字符串的语法,该语法可提供更高的清晰度。
数据帧的query方法是实验性的,不具备布尔索引功能,因此不应用于生产代码。
准备
此秘籍复制了本章中的较早秘籍“转换 SQL 数据帧的WHERE子句”,但是利用了query方法。 此处的目标是为来自警察局或消防局的,薪水在 80 至 12 万美元之间的女性雇员筛选雇员数据。
操作步骤
- 读入员工数据,分配所选部门,并将列导入变量:
>>> employee = pd.read_csv('data/employee.csv')
>>> depts = ['Houston Police Department-HPD',
'Houston Fire Department (HFD)']
>>> select_columns = ['UNIQUE_ID', 'DEPARTMENT',
'GENDER', 'BASE_SALARY']
- 构建查询字符串并执行方法:
>>> qs = "DEPARTMENT in @depts " \
"and GENDER == 'Female' " \
"and 80000 <= BASE_SALARY <= 120000"
>>> emp_filtered = employee.query(qs)
>>> emp_filtered[select_columns].head()
工作原理
传递给query方法的字符串看起来比普通的 Pandas 代码更像普通的英语。 与depts一样,可以使用 at 符号(@)来引用 Python 变量。 通过简单地引用其名称而不用内引号,可在查询名称空间中使用所有数据帧的列名称。 如果需要一个字符串,例如Female,则需要用引号将其引起来。
query语法的另一个不错的功能是能够在单个表达式中编写双重不等式,并且能够理解冗长的逻辑运算符and,or和not,而不是像布尔值那样的按位等效索引。
更多
不用手动输入部门名称列表,我们可以以编程方式创建它。 例如,如果我们要按频率查找不是前十名部门成员的所有女性雇员,则可以运行以下代码:
>>> top10_depts = employee.DEPARTMENT.value_counts() \
.index[:10].tolist()
>>> qs = "DEPARTMENT not in @top10_depts and GENDER == 'Female'"
>>> employee_filtered2 = employee.query(qs)
>>> employee_filtered2.head()
另见
使用where方法保留序列
布尔索引必须通过删除不符合条件的所有行来过滤数据集。 除了丢弃所有这些值外,还可以使用where方法保留它们。where方法将保留序列或数据帧的大小,并将不符合条件的值设置为缺失或将其替换为其他值。
准备
在此秘籍中,我们通过where方法布尔条件,在movie数据集中,针对演员 1 的 Facebook 点赞的最小和最大数目设置上下限。
操作步骤
- 读取
movie数据集,将影片标题设置为索引,然后选择actor_1_facebook_likes列中所有不丢失的值:
>>> movie = pd.read_csv('data/movie.csv', index_col='movie_title')
>>> fb_likes = movie['actor_1_facebook_likes'].dropna()
>>> fb_likes.head()
movie_title
Avatar 1000.0
Pirates of the Caribbean: At World's End 40000.0
Spectre 11000.0
The Dark Knight Rises 27000.0
Star Wars: Episode VII - The Force Awakens 131.0
Name: actor_1_facebook_likes, dtype: float64
- 让我们使用
describe方法来了解分布情况:
>>> fb_likes.describe(percentiles=[.1, .25, .5, .75, .9]) \
.astype(int)
count 4909
mean 6494
std 15106
min 0
10% 240
25% 607
50% 982
75% 11000
90% 18000
max 640000
Name: actor_1_facebook_likes, dtype: int64
- 此外,我们可以绘制此序列的直方图以直观地检查分布:
>>> fb_likes.hist()
- 这是非常糟糕的可视化,并且很难了解分布情况。 另一方面,第 2 步的汇总统计信息似乎在告诉我们,在很多观察中,该数据高度偏向右侧,比中位数大一个数量级。 让我们创建标准来测试点赞次数是否少于 20,000:
>>> criteria_high = fb_likes < 20000
>>> criteria_high.mean().round(2)
.91
- 大约 91% 的电影的演员 1 少于 20,000 个。 现在,我们将使用
where方法,该方法接受布尔条件。 默认行为是返回与原始大小相同的序列,但将所有False位置替换为缺少的值:
>>> fb_likes.where(criteria_high).head()
movie_title
Avatar 1000.0
Pirates of the Caribbean: At World's End NaN
Spectre 11000.0
The Dark Knight Rises NaN
Star Wars: Episode VII - The Force Awakens 131.0
Name: actor_1_facebook_likes, dtype: float64
where方法的第二个参数other允许您控制替换值。 让我们将所有缺失值更改为 20,000:
>>> fb_likes.where(criteria_high, other=20000).head()
movie_title
Avatar 1000.0
Pirates of the Caribbean: At World's End 20000.0
Spectre 11000.0
The Dark Knight Rises 20000.0
Star Wars: Episode VII - The Force Awakens 131.0
Name: actor_1_facebook_likes, dtype: float64
- 类似地,我们可以创建条件来为最少的点赞次数设置下限。 在这里,我们链接另一个
where方法,并将不符合条件的值替换为300:
>>> criteria_low = fb_likes > 300
>>> fb_likes_cap = fb_likes.where(criteria_high, other=20000)\
.where(criteria_low, 300)
>>> fb_likes_cap.head()
movie_title
Avatar 1000.0
Pirates of the Caribbean: At World's End 20000.0
Spectre 11000.0
The Dark Knight Rises 20000.0
Star Wars: Episode VII - The Force Awakens 300.0
Name: actor_1_facebook_likes, dtype: float64
- 原始序列和修改后的序列的长度相同:
>>> len(fb_likes), len(fb_likes_cap)
(4909, 4909)
- 让我们使用修改后的序列创建直方图。 数据范围更窄时,应该可以绘制出更好的图:
>>> fb_likes_cap.hist()
工作原理
where方法再次保留调用对象的大小和形状,并且不修改传递的布尔值为True的值。 重要的是在步骤 1 中删除丢失的值,因为where方法最终将在以后的步骤中将其替换为有效数字。
第 2 步中的摘要统计信息为我们提供了一些直观的方法来限定数据上限。 另一方面,第 3 步中的直方图似乎会将所有数据聚集到一个桶中。 对于纯直方图,数据有太多离群值,因此无法绘制出正确的图。where方法允许我们在数据上放置一个上限和下限,这将导致带有更多可见条的直方图。
更多
Pandas 实际上具有复制此操作的内置方法clip,clip_lower和clip_upper。clip方法可以同时设置地板和天花板。 我们还检查此替代方法是否产生完全相同的序列,它会执行以下操作:
>>> fb_likes_cap2 = fb_likes.clip(lower=300, upper=20000)
>>> fb_likes_cap2.equals(fb_likes_cap)
True
另见
屏蔽数据帧的行
mask方法执行与where方法完全相反的操作。 默认情况下,无论布尔条件为True,它都会创建缺失值。 从本质上讲,它实际上是掩盖或掩盖数据集中的值。
准备
在此秘籍中,我们将屏蔽 2010 年之后制作的电影数据集的所有行,然后过滤所有缺少值的行。
操作步骤
- 读取
movie数据集,将电影标题设置为索引,并创建条件:
>>> movie = pd.read_csv('data/movie.csv', index_col='movie_title')
>>> c1 = movie['title_year'] >= 2010
>>> c2 = movie['title_year'].isnull()
>>> criteria = c1 | c2
- 在数据帧上使用
mask方法可以使从 2010 年开始制作的带有电影的行中的所有值都丢失。 最初具有title_year缺失值的所有电影也会被屏蔽:
>>> movie.mask(criteria).head()
- 请注意,前面的数据帧中的第三,第四和第五行中的所有值是如何丢失的。 链接
dropna方法以删除所有值均缺失的行:
>>> movie_mask = movie.mask(criteria).dropna(how='all')
>>> movie_mask.head()
- 步骤 3 中的操作只是执行基本布尔索引的一种复杂方法。 我们可以检查两个方法是否产生相同的数据帧:
>>> movie_boolean = movie[movie['title_year'] < 2010]
>>> movie_mask.equals(movie_boolean)
False
equals方法告诉我们它们不相等。 出了点问题。 让我们进行一些完整性检查,看看它们是否具有相同的形状:
>>> movie_mask.shape == movie_boolean.shape
True
- 当我们使用前面的
mask方法时,它创建了许多缺失值。 缺少值是float数据类型,因此任何以前的整数列现在都是浮点数。 如果列的数据类型不同,即使值相同,equals方法也会返回False。 让我们检查数据类型的相等性,以查看是否发生了这种情况:
>>> movie_mask.dtypes == movie_boolean.dtypes
color True
director_name True
num_critic_for_reviews True
duration True
director_facebook_likes True
actor_3_facebook_likes True
actor_2_name True
actor_1_facebook_likes True
gross True
genres True
actor_1_name True
num_voted_users False
cast_total_facebook_likes False
.....
dtype: bool
- 事实证明,几列没有相同的数据类型。 Pandas 对于这些情况有另一种选择。 在其开发人员主要使用的测试模块中,有一个函数
assert_frame_equal,您可以使用它检查序列和数据帧的相等性,而无需同时检查数据类型的相等性:
from pandas.testing import assert_frame_equal
>>> assert_frame_equal(movie_boolean, movie_mask, check_dtype=False)
工作原理
默认情况下,mask方法覆盖缺少值的数据。mask方法的第一个参数是条件,该条件通常是布尔级数,例如criteria。 因为mask方法是从数据帧调用的,所以条件为False的每一行中的所有值都将变为丢失。 步骤 3 使用此掩码的数据帧删除包含所有缺失值的行。 步骤 4 显示了如何使用布尔索引执行相同的过程。
在数据分析过程中,持续验证结果非常重要。 检查序列和数据帧的相等性是一种非常通用的验证方法。 我们在步骤 4 中的首次尝试产生了意外结果。 在深入研究之前,一些基本的健全性检查(例如确保行和列的数目相同或行和列的名称相同)是很好的检查。
步骤 6 将两个序列的数据类型一起比较。 在这里,我们揭示了数据帧不等效的原因。equals方法检查值和数据类型是否相同。 步骤 7 中的assert_frame_equal函数具有许多可用参数,可以通过各种方式测试相等性。 注意,调用assert_frame_equal后没有输出。 当两个传递的数据帧相等时,此方法返回None;否则,将引发错误。
更多
让我们比较掩盖和删除丢失的行与布尔索引之间的速度差异。 在这种情况下,布尔索引大约快一个数量级:
>>> %timeit movie.mask(criteria).dropna(how='all')
11.2 ms ± 144 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
>>> %timeit movie[movie['title_year'] < 2010]
1.07 ms ± 34.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
另见
使用布尔值,整数位置和标签进行选择
第 4 章,“选择数据子集”涵盖了有关通过.iloc和.loc索引器选择不同数据子集的各种方法。 这两个索引器都通过整数位置或标签同时选择行和列。 这两个索引器都可以通过布尔索引进行数据选择,即使布尔不是整数也不是标签。
准备
在本秘籍中,我们将为.iloc和.loc索引器使用布尔索引过滤行和列。
操作步骤
- 读入电影数据集,将索引设置为标题,然后创建一个布尔序列,匹配内容分级为
G和 IMDB 得分小于4的所有电影:
>>> movie = pd.read_csv('data/movie.csv', index_col='movie_title')
>>> c1 = movie['content_rating'] == 'G'
>>> c2 = movie['imdb_score'] < 4
>>> criteria = c1 & c2
- 首先,将这些条件传递给
.loc索引器以过滤行:
>>> movie_loc = movie.loc[criteria]
>>> movie_loc.head()
- 让我们检查一下此数据帧是否完全等于直接由索引运算符生成的数据帧:
>>> movie_loc.equals(movie[criteria])
True
- 现在,让我们尝试使用
.iloc索引器进行相同的布尔索引:
>>> movie_iloc = movie.iloc[criteria]
ValueError: iLocation based boolean indexing cannot use an indexable as a mask
- 事实证明,由于存在索引,我们不能直接使用布尔序列。 但是,我们可以使用布尔值的 ndarray。 要提取数组,请使用
values属性:
>>> movie_iloc = movie.iloc[criteria.values]
>>> movie_iloc.equals(movie_loc)
True
- 尽管不是很常见,但可以进行布尔索引来选择特定的列。 在这里,我们选择所有数据类型为 64 位整数的列:
>>> criteria_col = movie.dtypes == np.int64
>>> criteria_col.head()
color False
director_name False
num_critic_for_reviews False
duration False
director_facebook_likes False
dtype: bool
>>> movie.loc[:, criteria_col].head()
- 由于
criteria_col是一个序列,始终有一个索引,因此必须使用基础 ndarray 使其与.iloc一起使用。 以下产生与步骤 6 相同的结果。
>>> movie.iloc[:, criteria_col.values].head()
- 布尔序列可以用于选择行,然后同时选择具有整数或标签的列。 请记住,您需要在行和列选择之间加上逗号。 让我们保留行标准,然后选择
content_rating,imdb_score,title_year和gross:
>>> cols = ['content_rating', 'imdb_score', 'title_year', 'gross']
>>> movie.loc[criteria, cols].sort_values('imdb_score')
- 可以使用
.iloc复制相同的操作,但是您需要获取所有列的整数位置:
>>> col_index = [movie.columns.get_loc(col) for col in cols]
>>> col_index
[20, 24, 22, 8]
>>> movie.iloc[criteria.values, col_index]
工作原理
布尔索引可以用.iloc和.loc索引器完成,但要注意的是.iloc不能传递给序列而是基础ndarray。 让我们看一下条件序列的一维ndarray:
>>> a = criteria.values
>>> a[:5]
array([False, False, False, False, False], dtype=bool)
>>> len(a), len(criteria)
(4916, 4916)
数组的长度与序列的长度相同,而序列与电影的数据帧长度相同。 布尔数组的整数位置与数据帧的整数位置对齐,并且过滤器按预期进行。 这些数组也可以与.loc运算符一起使用,但是它们对于.iloc是必需的。
步骤 6 和 7 显示了如何按列而不是按行进行过滤。 需要冒号:来指示所有行的选择。 冒号后面的逗号分隔行和列的选择。 实际上,通过select_dtypes方法可以更轻松地选择具有整数数据类型的列。
步骤 8 和 9 显示了一种同时对行和列选择进行布尔索引的非常通用和有用的方法。 您只需在行和列选择之间放置一个逗号。 第 9 步使用列表推导式遍历所有所需的列名,以使用索引方法get_loc查找其整数位置。
更多
实际上,可以将数组和布尔值列表传递给序列对象,这些对象的长度与您要建立索引的数据帧的长度不同。 让我们通过选择第一行和第三行以及第一列和第四列来查看一个示例:
>>> movie.loc[[True, False, True], [True, False, False, True]]
这两个布尔列表的长度与其所索引的轴的长度不同。 列表中未明确指定布尔值的其余行和列将被删除。
另见
- 请参阅第 4 章,“选择数据子集”中的“同时使用整数和标签选择数据”秘籍。
- 请参阅第 2 章,“基本数据帧操作”的“用方法选择列”。
六、索引对齐
在本章中,我们将介绍以下主题:
- 检查索引对象
- 生成笛卡尔积
- 索引爆炸
- 用不相等的索引填充值
- 追加来自不同数据帧的列
- 突出显示每一列的最大值
- 用方法链复制
idxmax - 寻找最常见的最大值
介绍
当以某种方式组合多个序列或数据帧时,在进行任何计算之前,数据的每个维度会首先自动在每个轴上对齐。 轴的这种无声且自动的对齐会给初学者造成极大的困惑,但它为超级用户提供了极大的灵活性。 本章将深入探讨索引对象,然后展示利用其自动对齐功能的各种秘籍。
检查索引对象
如第 1 章,“Pandas 基础”中所讨论的,序列和数据帧的每个轴都有一个索引对象,用于标记值。 有许多不同类型的索引对象,但是它们都具有相同的共同行为。 除特殊的多重索引之外,所有索引对象都是一维数据结构,结合了 Python 集和 NumPy ndarrays的功能和实现。
准备
在本秘籍中,我们将检查大学数据集的列索引并探索其许多功能。
操作步骤
- 读入大学数据集,为列索引分配一个变量,然后输出:
>>> college = pd.read_csv('data/college.csv')
>>> columns = college.columns
>>> columns
Index(['INSTNM', 'CITY', 'STABBR', 'HBCU', ...], dtype='object')
- 使用
values属性访问基础的 NumPy 数组:
>>> columns.values
array(['INSTNM', 'CITY', 'STABBR', 'HBCU', ...], dtype=object)
- 通过带有标量,列表或切片的整数位置从索引中选择项目:
>>> columns[5]
'WOMENONLY'
>>> columns[[1,8,10]]
Index(['CITY', 'SATMTMID', 'UGDS'], dtype='object')
>>> columns[-7:-4]
Index(['PPTUG_EF', 'CURROPER', 'PCTPELL'], dtype='object')
- 索引与序列和数据帧共享许多相同的方法:
>>> columns.min(), columns.max(), columns.isnull().sum()
('CITY', 'WOMENONLY', 0)
- 直接在
Index对象上使用基本算术和比较运算符:
>>> columns + '_A'
Index(['INSTNM_A', 'CITY_A', 'STABBR_A', 'HBCU_A', ...], dtype='object')
>>> columns > 'G'
array([ True, False, True, True, ...], dtype=bool)
- 创建索引后尝试直接更改索引值失败。 索引是不可变的对象:
>>> columns[1] = 'city'
TypeError: Index does not support mutable operations
工作原理
从许多索引对象操作中可以看到,它与序列和ndarrays似乎有很多共同点。 最大的差异之一来自第 6 步。索引是不可变的,创建后就无法更改它们的值。
更多
索引支持集合运算,并集,交集,差和对称差:
>>> c1 = columns[:4]
>>> c1
Index(['INSTNM', 'CITY', 'STABBR', 'HBCU'], dtype='object')
>>> c2 = columns[2:6]
>>> c2
Index(['STABBR', 'HBCU', 'MENONLY'], dtype='object')
>>> c1.union(c2) # or `c1 | c2`
Index(['CITY', 'HBCU', 'INSTNM', 'MENONLY', 'RELAFFIL', 'STABBR'], dtype='object')
>>> c1.symmetric_difference(c2) # or `c1 ^ c2`
Index(['CITY', 'INSTNM', 'MENONLY'], dtype='object')
索引与 Python 集共享一些相同的操作。 索引在另一重要方面类似于 Python 集。 它们(通常)是使用哈希表实现的,当从数据帧中选择行或列时,哈希表的访问速度非常快。 当使用哈希表实现它们时,索引对象的值必须是不可变的,例如字符串,整数或元组,就像 Python 字典中的键一样。
索引支持重复值,并且如果在任何索引中碰巧有重复项,则哈希表将无法再用于其实现,并且对象访问会变得很慢。
另见
生成笛卡尔积
每当两个序列或数据帧与另一个序列或数据帧一起操作时,每个对象的索引(行索引和列索引)都首先对齐,然后再开始任何操作。 这种索引对齐方式是无声的,对于那些刚接触 Pandas 的人来说可能是非常令人惊讶的。 除非索引相同,否则这种对齐方式总是在索引之间创建笛卡尔积。
笛卡尔积是一个数学术语,通常出现在集合论中。 两个集之间的笛卡尔积是两个集的偶对的所有组合。 例如,标准纸牌中的 52 张纸牌代表 13 个等级(A, 2, 3,..., Q, K)和四个花色之间的笛卡尔积。
准备
生成笛卡尔积并非总是预期的结果,但是了解发生的方式和时间以避免意外后果至关重要。 在此秘籍中,将具有重叠但不相同的索引的两个序列相加在一起,产生了令人惊讶的结果。
操作步骤
请按照以下步骤创建笛卡尔积:
- 构造两个具有不同索引但包含一些相同值的序列:
>>> s1 = pd.Series(index=list('aaab'), data=np.arange(4))
>>> s1
a 0
a 1
a 2
b 3
dtype: int64
>>> s2 = pd.Series(index=list('cababb'), data=np.arange(6))
>>> s2
c 0
a 1
b 2
a 3
b 4
b 5
dtype: int64
- 将两个序列加在一起以生成笛卡尔积:
>>> s1 + s2
a 1.0
a 3.0
a 2.0
a 4.0
a 3.0
a 5.0
b 5.0
b 7.0
b 8.0
c NaN
dtype: float64
工作原理
每个序列都是使用类构造器创建的,该类构造器接受各种各样的输入,最简单的是每个参数index和数据的值序列。
笛卡尔数学乘积与对两个 Pandas 对象进行运算的结果略有不同。s1中的每个a标签与s2中的每个a标签配对。 该配对在所得序列中产生六个a标签,三个b标签和一个c标签。 笛卡尔积在所有相同的索引标签之间发生。
由于带有标签c的元素是序列s2所特有的,因此 pandas 默认将其值设置为 missing,因为s1中没有标签可以对齐。 每当索引标签对于一个对象唯一时,Pandas 默认为缺少值。 不幸的结果是,将序列的数据类型更改为float,而每个序列仅具有整数作为值。 发生这种情况是因为 NumPy 缺少值对象。np.nan仅对于浮点数存在,而对于整数不存在。序列和数据帧的列必须具有齐次数值数据类型; 因此,每个值都转换为浮点数。 对于这个小的数据集,这几乎没有什么区别,但是对于较大的数据集,这可能会对内存产生重大影响。
更多
当索引以相同顺序包含相同的完全相同的元素时,将发生上述示例的异常。 发生这种情况时,不会发生笛卡尔积,而是按其位置对齐索引。 请注意,每个元素均按位置精确对齐,并且数据类型仍为整数:
>>> s1 = pd.Series(index=list('aaabb'), data=np.arange(5))
>>> s2 = pd.Series(index=list('aaabb'), data=np.arange(5))
>>> s1 + s2
a 0
a 2
a 4
b 6
b 8
dtype: int64
如果索引的元素相同,但是序列之间的顺序不同,则会产生笛卡尔积。 让我们在s2中更改索引的顺序,然后重新运行相同的操作:
>>> s1 = pd.Series(index=list('aaabb'), data=np.arange(5))
>>> s2 = pd.Series(index=list('bbaaa'), data=np.arange(5))
>>> s1 + s2
a 2
a 3
a 4
a 3
a 4
a 5
a 4
a 5
a 6
b 3
b 4
b 4
b 5
dtype: int64
有趣的是,Pandas 在同一项操作中有两个截然不同的结果。 如果笛卡尔积是 Pandas 的唯一选择,那么将数据帧的列加在一起这样的简单操作将使返回的元素数量激增。
在此秘籍中,每个序列具有不同数量的元素。 通常,当操作维中不包含相同数量的元素时,Python 和其他语言中的类似数组的数据结构将不允许进行操作。 Pandas 可以通过在完成操作之前先对齐索引来实现此目的。
另见
- 第 3 章,“开始数据分析”中的“通过更改数据类型来减少内存”秘籍
索引爆炸
先前的秘籍中有一个琐碎的示例,其中将两个小序列与不相等的索引一起添加。 处理较大的数据时,此问题可能会产生可笑的错误结果。
准备
在此秘籍中,我们添加了两个较大的序列,它们的索引只有几个唯一值,但顺序不同。 结果将使索引中的值数量爆炸。
操作步骤
- 读入员工数据并将索引设置为与
race列相等:
>>> employee = pd.read_csv('data/employee.csv', index_col='RACE')
>>> employee.head()
- 选择
BASE_SALARY列作为两个不同的序列。 检查此操作是否确实创建了两个新对象:
>>> salary1 = employee['BASE_SALARY']
>>> salary2 = employee['BASE_SALARY']
>>> salary1 is salary2
True
salary1和salary2变量实际上是指同一对象。 这意味着对一个的任何更改都会更改另一个。 为确保您收到数据的全新副本,请使用copy方法:
>>> salary1 = employee['BASE_SALARY'].copy()
>>> salary2 = employee['BASE_SALARY'].copy()
>>> salary1 is salary2
False
- 让我们通过对其中一个序列进行排序来更改其索引顺序:
>>> salary1 = salary1.sort_index()
>>> salary1.head()
RACE
American Indian or Alaskan Native 78355.0
American Indian or Alaskan Native 81239.0
American Indian or Alaskan Native 60347.0
American Indian or Alaskan Native 68299.0
American Indian or Alaskan Native 26125.0
Name: BASE_SALARY, dtype: float64
>>> salary2.head()
RACE
Hispanic/Latino 121862.0
Hispanic/Latino 26125.0
White 45279.0
White 63166.0
White 56347.0
Name: BASE_SALARY, dtype: float64
- 让我们将这些
salary序列加在一起:
>>> salary_add = salary1 + salary2
>>> salary_add.head()
RACE
American Indian or Alaskan Native 138702.0
American Indian or Alaskan Native 156710.0
American Indian or Alaskan Native 176891.0
American Indian or Alaskan Native 159594.0
American Indian or Alaskan Native 127734.0
Name: BASE_SALARY, dtype: float64
- 操作成功完成。 让我们再添加一个
salary1序列,然后输出每个序列的长度。 我们只是将该指数从 2,000 个值分解为超过 100 万个值:
>>> salary_add1 = salary1 + salary1
>>> len(salary1), len(salary2), len(salary_add), len(salary_add1)
(2000, 2000, 1175424, 2000)
工作原理
首先出现步骤 2,以创建两个唯一的对象,但实际上,它创建了一个由两个不同的变量名称引用的对象。 表达式employee['BASE_SALARY']从技术上讲创建的是视图,而不是全新的副本。 使用is运算符对此进行了验证。
在熊猫中,视图不是新对象,而只是对另一个对象的引用,通常是数据帧的某些子集。 此共享对象可能导致许多问题。
为了确保两个变量都引用完全不同的对象,我们使用copy序列方法,并再次使用is运算符验证它们是否是不同的对象。 步骤 4 使用sort_index方法按种族对序列进行排序。 第 5 步将这些不同的序列加在一起以产生一些结果。 仅检查头部,仍不清楚产生了什么。
步骤 6 向其自身添加salary1,以显示两个不同序列添加之间的比较。 此秘籍中所有序列的长度都已输出,我们清楚地看到series_add现已爆炸超过一百万个值。 索引中的每个唯一值都会产生笛卡尔积,因为索引并不完全相同。 此秘籍显着显示了将多个序列或数据帧组合在一起时索引可能产生的影响。
更多
通过做一些数学运算,我们可以验证salary_add的值的数量。 当笛卡尔积在所有相同的索引值之间发生时,我们可以求和它们各自计数的平方。 索引中甚至缺少的值也会与它们自身产生笛卡尔积:
>>> index_vc = salary1.index.value_counts(dropna=False)
>>> index_vc
Black or African American 700
White 665
Hispanic/Latino 480
Asian/Pacific Islander 107
NaN 35
American Indian or Alaskan Native 11
Others 2
Name: RACE, dtype: int64
>>> index_vc.pow(2).sum()
1175424
用不相等的索引填充值
当使用加法运算符将两个序列加在一起并且一个索引标签没有出现在另一个索引标签中时,结果值始终会丢失。 Pandas 提供了add方法,该方法提供了一种填充缺失值的选项。
准备
在本秘籍中,我们使用add方法的fill_value参数将baseball数据集中具有不等索引的多个序列合并在一起,以确保结果中没有缺失值。
操作步骤
- 读取三个
baseball数据集,并将索引设置为playerID:
>>> baseball_14 = pd.read_csv('data/baseball14.csv',
index_col='playerID')
>>> baseball_15 = pd.read_csv('data/baseball15.csv',
index_col='playerID')
>>> baseball_16 = pd.read_csv('data/baseball16.csv',
index_col='playerID')
>>> baseball_14.head()
- 使用索引方法
difference发现baseball_14中而不是baseball_15中的索引标签,反之亦然:
>>> baseball_14.index.difference(baseball_15.index)
Index(['corpoca01', 'dominma01', 'fowlede01', 'grossro01',
'guzmaje01', 'hoeslj01', 'krausma01', 'preslal01',
'singljo02'], dtype='object', name='playerID')
>>> baseball_14.index.difference(baseball_16.index)
Index(['congeha01', 'correca01', 'gattiev01', 'gomezca01',
'lowrije01', 'rasmuco01', 'tuckepr01', 'valbulu01'],
dtype='object', name='playerID')
- 每个索引都有很多独特的参与者。 让我们找出三年内每个玩家的总点击数。
H列包含匹配数:
>>> hits_14 = baseball_14['H']
>>> hits_15 = baseball_15['H']
>>> hits_16 = baseball_16['H']
>>> hits_14.head()
playerID
altuvjo01 225
cartech02 115
castrja01 103
corpoca01 40
dominma01 121
Name: H, dtype: int64
- 我们首先使用加法运算符将两个序列相加:
>>> (hits_14 + hits_15).head()
playerID
altuvjo01 425.0
cartech02 193.0
castrja01 174.0
congeha01 NaN
corpoca01 NaN
Name: H, dtype: float64
- 即使玩家
congeha01和corpoca01记录了 2015 年的热门歌曲,但他们的成绩仍然缺失。 让我们使用add方法及其参数fill_value来避免丢失值:
>>> hits_14.add(hits_15, fill_value=0).head()
playerID
altuvjo01 425.0
cartech02 193.0
castrja01 174.0
congeha01 46.0
corpoca01 40.0
Name: H, dtype: float64
- 我们通过再次链接
add方法来添加 2016 年的匹配:
>>> hits_total = hits_14.add(hits_15, fill_value=0) \
.add(hits_16, fill_value=0)
>>> hits_total.head()
playerID
altuvjo01 641.0
bregmal01 53.0
cartech02 193.0
castrja01 243.0
congeha01 46.0
Name: H, dtype: float64
- 检查结果中是否缺少值:
>>> hits_total.hasnans
False
工作原理
add方法的工作方式与加法运算符相似,但通过提供fill_value参数代替不匹配的索引,可以提供更大的灵活性。 在此问题中,将不匹配的索引值默认设置为 0 是有意义的,但是您可以使用其他任何数字。
有时每个序列都包含与缺失值相对应的索引标签。 在此特定实例中,当添加两个序列时,无论是否使用fill_value参数,索引标签仍将对应于缺失值。 为了澄清这一点,请看以下示例,其中索引标签a对应于每个序列中的缺失值:
>>> s = pd.Series(index=['a', 'b', 'c', 'd'],
data=[np.nan, 3, np.nan, 1])
>>> s
a NaN
b 3.0
c NaN
d 1.0
dtype: float64
>>> s1 = pd.Series(index=['a', 'b', 'c'], data=[np.nan, 6, 10])
>>> s1
a NaN
b 6.0
c 10.0
dtype: float64
>>> s.add(s1, fill_value=5)
a NaN
b 9.0
c 15.0
d 6.0
dtype: float64
更多
此秘籍展示了如何仅将单个索引添加到序列中。 也完全可以将数据帧一起添加。 将数据帧加在一起将在计算之前对齐索引和列,并产生不匹配索引的缺失值。 首先,从 2014 年棒球数据集中选择一些列。
>>> df_14 = baseball_14[['G','AB', 'R', 'H']]
>>> df_14.head()
Let's also select a few of the same and a few different columns from the 2015 baseball dataset:
>>> df_15 = baseball_15[['AB', 'R', 'H', 'HR']]
>>> df_15.head()
如果行或列标签无法对齐,则将两个数据帧一起添加会丢失值。 使用style属性访问highlight_null方法可轻松查看缺失值的位置:
>>> (df_14 + df_15).head(10).style.highlight_null('yellow')
只有两个数据帧中都出现playerID的行才会丢失。 类似地,AB,H和R列是两个数据帧中唯一出现的列。 即使我们在指定fill_value参数的情况下使用add方法,我们仍然缺少值。 这是因为在我们的输入数据中从来没有行和列的某些组合。 例如,playerID congeha01和列G的交集。 他只出现在 2015 年没有G列的数据集中。 因此,没有任何值被填充:
>>> df_14.add(df_15, fill_value=0).head(10) \
.style.highlight_null('yellow')
追加来自不同数据帧的列
所有数据帧都可以向自己添加新列。 但是,像往常一样,每当一个数据帧从另一个数据帧或序列添加一个新列时,索引都将在创建新列之前首先对齐。
准备
此秘籍使用employee数据集添加一个新列,其中包含该员工部门的最高薪水。
操作步骤
- 导入
employee数据,然后在新的数据帧中选择DEPARTMENT和BASE_SALARY列:
>>> employee = pd.read_csv('data/employee.csv')
>>> dept_sal = employee[['DEPARTMENT', 'BASE_SALARY']]
- 将此较小的数据帧按每个部门内的薪水排序:
>>> dept_sal = dept_sal.sort_values(['DEPARTMENT', 'BASE_SALARY'],
ascending=[True, False])
- 使用
drop_duplicates方法保留每个DEPARTMENT的第一行:
>>> max_dept_sal = dept_sal.drop_duplicates(subset='DEPARTMENT')
>>> max_dept_sal.head()
- 将
DEPARTMENT列放入每个数据帧的索引中:
>>> max_dept_sal = max_dept_sal.set_index('DEPARTMENT')
>>> employee = employee.set_index('DEPARTMENT')
- 现在索引包含匹配的值,我们可以将新列追加到
employee数据帧:
>>> employee['MAX_DEPT_SALARY'] = max_dept_sal['BASE_SALARY']
>>> employee.head()
- 我们可以使用
query方法验证结果,以检查是否存在BASE_SALARY大于MAX_DEPT_SALARY的行:
>>> employee.query('BASE_SALARY > MAX_DEPT_SALARY')
工作原理
步骤 2 和 3 找到每个部门的最高工资。 为了使索引自动对齐正常工作,我们将每个数据帧索引设置为部门。 步骤 5 之所以有效,是因为左侧的数据帧中的每行索引;employee与来自右侧数据帧max_dept_sal的一个且仅一个索引对齐。 如果max_dept_sal在其索引中重复了任何部门,则该操作将失败。
例如,让我们看看当我们在具有重复索引值的等式的右侧使用数据帧时会发生什么。 我们使用数据帧的sample方法随机选择十行而不进行替换:
>>> np.random.seed(1234)
>>> random_salary = dept_sal.sample(n=10).set_index('DEPARTMENT')
>>> random_salary
注意索引中有几个重复的部门。 现在,当我们尝试创建新列时,将引发一个错误,警告我们有重复项。employee数据帧中的至少一个索引标签与random_salary中的两个或多个索引标签结合在一起:
>>> employee['RANDOM_SALARY'] = random_salary['BASE_SALARY']
ValueError: cannot reindex from a duplicate axis
更多
并非等号左侧的所有索引都需要匹配,但最多只能有一个匹配。 如果左对齐的数据帧索引没有任何内容,则将缺少结果值。 让我们创建一个发生这种情况的示例。 我们将仅使用max_dept_sal序列的前三行来创建新列:
>>> employee['MAX_SALARY2'] = max_dept_sal['BASE_SALARY'].head(3)
>>> employee.MAX_SALARY2.value_counts()
140416.0 29
100000.0 11
64251.0 5
Name: MAX_SALARY2, dtype: int64
>>> employee.MAX_SALARY2.isnull().mean()
.9775
该操作成功完成,但仅为三个部门的填充了薪水。 没有出现在max_dept_sal序列的前三行中的所有其他部门导致值丢失。
另见
- 第 3 章“开始数据分析”中的“从最大值中选择最小值”秘籍
突出显示每一列的最大值
college数据集有许多数字列,它们描述了有关每所学校的不同指标。 许多人都对在某些指标上表现最好的学校感兴趣。
准备
此秘籍发现每个数字列具有最大值的学校,并设置数据帧的样式以突出显示信息,以便用户轻松使用。
操作步骤
- 阅读以机构名称作为索引的大学数据集:
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> college.dtypes
CITY object
STABBR object
HBCU float64
MENONLY float64
...
PCTFLOAN float64
UG25ABV float64
MD_EARN_WNE_P10 object
GRAD_DEBT_MDN_SUPP object
Length: 26, dtype: object
CITY和STABBR以外的所有其他列似乎都是数字。 检查上一步中的数据类型会意外显示MD_EARN_WNE_P10和GRAD_DEBT_MDN_SUPP列属于对象类型,而不是数字类型。 为了更好地了解这些列中的值是什么,让我们检查它们的第一个值:
>>> college.MD_EARN_WNE_P10.iloc[0]
'30300'
>>> college.GRAD_DEBT_MDN_SUPP.iloc[0]
'33888'
- 这些值是字符串,但我们希望它们是数字。 这意味着在序列的其他地方可能会出现非数字字符。 一种检查方法是按降序对这些列进行排序并检查前几行:
>>> college.MD_EARN_WNE_P10.sort_values(ascending=False).head()
INSTNM
Sharon Regional Health System School of Nursing PrivacySuppressed
Northcoast Medical Training Academy PrivacySuppressed
Success Schools PrivacySuppressed
Louisiana Culinary Institute PrivacySuppressed
Bais Medrash Toras Chesed PrivacySuppressed
Name: MD_EARN_WNE_P10, dtype: object
- 罪魁祸首似乎是一些学校对这两列数据存在隐私问题。 要将这些列强制为数字,请使用 pandas 函数
to_numeric:
>>> cols = ['MD_EARN_WNE_P10', 'GRAD_DEBT_MDN_SUPP']
>>> for col in cols:
college[col] = pd.to_numeric(college[col], errors='coerce')
>>> college.dtypes.loc[cols]
MD_EARN_WNE_P10 float64
GRAD_DEBT_MDN_SUPP float64
dtype: object
- 使用
select_dtypes方法仅过滤数字列。 这将排除STABBR和CITY列,列,其中最大值对于此问题没有意义:
>>> college_n = college.select_dtypes(include=[np.number])
>>> college_n.head()
- 通过利用数据字典,有几列仅具有二进制(0/1)值,不会提供有用的信息。 为了以编程方式找到这些列,我们可以创建布尔序列并使用
nunique方法找到具有两个唯一值的所有列:
>>> criteria = college_n.nunique() == 2
>>> criteria.head()
HBCU True
MENONLY True
WOMENONLY True
RELAFFIL True
SATVRMID False
dtype: bool
- 将此布尔序列传递给列索引对象的索引运算符,并创建二进制列的列表:
>>> binary_cols = college_n.columns[criteria].tolist()
>>> binary_cols
['HBCU', 'MENONLY', 'WOMENONLY', 'RELAFFIL', 'DISTANCEONLY', 'CURROPER']
- 使用
drop方法删除二进制列:
>>> college_n2 = college_n.drop(labels=binary_cols, axis='columns')
>>> college_n2.head()
- 使用
idxmax方法查找每一列的最大值的索引标签:
>>> max_cols = college_n2.idxmax()
>>> max_cols
SATVRMID California Institute of Technology
SATMTMID California Institute of Technology
UGDS University of Phoenix-Arizona
UGDS_WHITE Mr Leon's School of Hair Design-Moscow
...
PCTFLOAN ABC Beauty College Inc
UG25ABV Dongguk University-Los Angeles
MD_EARN_WNE_P10 Medical College of Wisconsin
GRAD_DEBT_MDN_SUPP Southwest University of Visual Arts-Tucson
Length: 18, dtype: object
- 在
max_cols序列上调用unique方法。 这将返回唯一列名称的ndarray:
>>> unique_max_cols = max_cols.unique()
>>> unique_max_cols[:5]
array(['California Institute of Technology',
'University of Phoenix-Arizona',
"Mr Leon's School of Hair Design-Moscow",
'Velvatex College of Beauty Culture',
'Thunderbird School of Global Management'], dtype=object)
- 使用
max_cols的值选择仅具有最大值的学校的行,然后使用style属性突出显示这些值:
>>> college_n2.loc[unique_max_cols].style.highlight_max()
工作原理
idxmax方法非常强大,当索引被有意义地标记时,它变得非常有用。 出乎意料的是,MD_EARN_WNE_P10和GRAD_DEBT_MDN_SUPP均为object数据类型。 导入时,如果列中至少包含一个字符串,则 pandas 将列的所有数值强制转换为字符串。
通过检查步骤 2 中的特定列值,我们可以清楚地看到 在这些列中有字符串。 在第 3 步中,我们以降序排序,因为数字字符首先出现。 这会将所有字母值提升到该序列的顶部。 我们发现PrivacySuppressed字符串造成严重破坏。 Pandas 可以使用to_numeric函数将仅包含数字字符的所有字符串强制转换为实际的数字数据类型。 要覆盖在to_numeric遇到无法转换的字符串时引发错误的默认行为,必须将coerce传递给errors参数。 这将强制所有非数字字符串变为缺失值(np.nan)。
几列没有有用或有意义的最大值。 在第 4 步到第 6 步中已将它们删除。select_dtypes对于具有许多列的非常宽的数据帧极为有用。
在步骤 7 中,idxmax遍历所有列以找到每个列的最大值的索引。 它将结果作为序列输出。 SAT 数学和口语成绩均最高的学校是加利福尼亚理工学院。 洛杉矶东国大学的 25 岁以上的学生人数最多。
尽管idxmax提供的信息很好,但它不会产生相应的最大值。 为此,我们从max_cols序列的值中收集所有唯一的学校名称。
最后,在步骤 8 中,我们使用.loc索引器根据索引标签选择行,在第一步中将其作为学校名称。 此过滤器仅适用于具有最大值的学校。数据帧具有实验性style属性,该属性本身具有一些方法来更改显示的数据帧的外观。 突出显示最大值可使结果更加清晰。
更多
默认情况下,highlight_max方法突出显示每列的最大值。 我们可以使用axis参数突出显示每行的最大值。 在这里,我们只选择college数据集的种族百分比列,并突出显示每所学校百分比最高的种族:
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> college_ugds = college.filter(like='UGDS_').head()
>>> college_ugds.style.highlight_max(axis='columns')
尝试在大型数据帧上应用样式会导致 Jupyter 崩溃,这就是为什么仅将样式应用于数据帧的头部的原因。
另见
使用方法链接复制idxmax
尝试自行实现内置数据帧方法可能是一个很好的练习。 这种复制可以使您对通常不会遇到的其他 Pandas 方法有更深入的了解。idxmax是仅使用本书到目前为止介绍的方法进行复制的一种挑战性方法。
准备
此秘籍将基本方法缓慢地链接在一起,以最终找到包含最大列值的所有行索引值。
操作步骤
- 加载大学数据集并执行与上一个秘籍相同的操作,以仅获取感兴趣的数字列:
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> cols = ['MD_EARN_WNE_P10', 'GRAD_DEBT_MDN_SUPP']
>>> for col in cols:
college[col] = pd.to_numeric(college[col], errors='coerce')
>>> college_n = college.select_dtypes(include=[np.number])
>>> criteria = college_n.nunique() == 2
>>> binary_cols = college_n.columns[criteria].tolist()
>>> college_n = college_n.drop(labels=binary_cols, axis='columns')
- 使用
max方法查找每列的最大值:
>>> college_n.max().head()
SATVRMID 765.0
SATMTMID 785.0
UGDS 151558.0
UGDS_WHITE 1.0
UGDS_BLACK 1.0
dtype: float64
- 使用数据帧的
eq方法使用其max列测试每个值。 默认情况下,eq方法将列数据帧的列与传递的序列索引的标签对齐:
>>> college_n.eq(college_n.max()).head()
- 此数据帧中所有具有至少一个
True值的行都必须包含最大列数。 让我们使用any方法查找具有至少一个True值的所有此类行:
>>> has_row_max = college_n.eq(college_n.max()).any(axis='columns')
>>> has_row_max.head()
INSTNM
Alabama A & M University False
University of Alabama at Birmingham False
Amridge University False
University of Alabama in Huntsville False
Alabama State University False
dtype: bool
- 只有 18 列,这意味着
has_row_max中最多只能有 18 个True值。 让我们找出实际有多少个:
>>> college_n.shape
(7535, 18)
>>> has_row_max.sum()
401
- 这有点出乎意料,但是事实证明,有些列的许多行等于最大值。 这对于许多最大值为 1 的百分比列很常见。
idxmax返回第一次出现的最大值。 让我们备份一下,删除any方法,然后看一下步骤 3 的输出。让我们运行cumsum方法来累积所有True值。 显示了前三行:
>>> college_n.eq(college_n.max()).cumsum()
- 有些列具有一个唯一的最大值,例如
SATVRMID和SATMTMID,而另一些列则具有UGDS_WHITE。 109 所学校的本科生中有 100% 是白人。 如果我们再链接一次cumsum方法,则值 1 在每一列中只会出现一次,并且它将是最大值的第一次出现:
>>> college_n.eq(college_n.max()).cumsum().cumsum()
- 现在,我们可以使用
eq方法测试每个值是否等于 1,然后使用any方法查找具有至少一个True值的行:
>>> has_row_max2 = college_n.eq(college_n.max()) \
.cumsum() \
.cumsum() \
.eq(1) \
.any(axis='columns')
>>> has_row_max2.head()
INSTNM
Alabama A & M University False
University of Alabama at Birmingham False
Amridge University False
University of Alabama in Huntsville False
Alabama State University False
dtype: bool
- 测试
has_row_max2的True值不超过列数:
>>> has_row_max2.sum()
16
- 我们需要
has_row_max2为True的所有机构。 我们可以简单地在序列本身上使用布尔索引:
>>> idxmax_cols = has_row_max2[has_row_max2].index
>>> idxmax_cols
Index(['Thunderbird School of Global Management',
'Southwest University of Visual Arts-Tucson',
'ABC Beauty College Inc',
'Velvatex College of Beauty Culture',
'California Institute of Technology',
'Le Cordon Bleu College of Culinary Arts-San Francisco',
'MTI Business College Inc', 'Dongguk University-Los Angeles',
'Mr Leon's School of Hair Design-Moscow',
'Haskell Indian Nations University', 'LIU Brentwood',
'Medical College of Wisconsin', 'Palau Community College',
'California University of Management and Sciences',
'Cosmopolitan Beauty and Tech School',
'University of Phoenix-Arizona'], dtype='object', name='INSTNM')
- 这些机构中的所有 16 个都是至少其中一列的第一个最大出现次数的索引。 我们可以检查它们是否与
idxmax方法中找到的相同:
>>> set(college_n.idxmax().unique()) == set(idxmax_cols)
True
工作原理
第一步通过将两列转换为数字并消除二进制列来复制上一个秘籍的工作。 我们在步骤 2 中找到每列的最大值。在这里,需要谨慎,因为 Pandas 会默默地丢弃无法产生最大值的列。 如果发生这种情况,则第 3 步仍将完成,但将为每列生成所有False值,而没有可用的最大值。
步骤 4 使用any方法在每一行中进行扫描,以搜索至少一个True值。 具有至少一个True值的任何行都包含一列的最大值。 我们在步骤 5 中对所得的布尔序列求和,以确定多少行包含最大值。 出乎意料的是,行多于列。 步骤 6 深入说明了为什么会发生这种情况。 我们对步骤 3 的输出进行累计,并检测等于每列最大值的总行数。
许多大学只有一个种族就拥有 100% 的学生人数。 到目前为止,这是最大的多个行的最大贡献者。 如您所见,SAT 成绩栏和大学本科生只有一排具有最大值的行,但是某些种族栏有最大值。
我们的目标是找到具有最大值的第一行。 我们需要再次取累加总和,以使每一列只有一行等于 1。步骤 8 将代码格式化为每行只有一个方法,并完全按照步骤 4 的方式运行any方法。 此步骤成功后,则True值应不超过列数。 步骤 9 断言这是真的。
为了验证我们是否在前几列中找到与idxmax相同的列,我们对has_row_max2本身使用了布尔选择。 列将以不同的顺序排列,因此我们将列名称的顺序转换为集合,这些集合固有地无序比较相等性。
更多
可以用一长串代码将索引运算符与匿名函数链接起来,从而完成此秘籍。 这个小技巧使您无需执行第 10 步。在此秘籍中,我们可以估算直接idxmax方法与我们的手动工作之间的差异:
>>> %timeit college_n.idxmax().values
1.12 ms ± 28.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit college_n.eq(college_n.max()) \
.cumsum() \
.cumsum() \
.eq(1) \
.any(axis='columns') \
[lambda x: x].index
5.35 ms ± 55.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
不幸的是,我们的工作速度是 Pandas idxmax内置方法的五倍,但是不管其性能如何下降,许多创新且实用的解决方案都使用布尔序列和cumsum累积方法来查找条纹或一个轴的特定模式。
寻找最常见的最大值
大学数据集包含超过 7,500 所大学的 8 个不同种族的本科人口百分比。 找到每所学校本科生人数最多的种族,然后为整个数据集找到此结果的分布将是很有趣的。 我们将能够回答一个类似“哪个机构的白人学生比其他任何种族都要多”的问题。
准备
在此秘籍中,我们使用idxmax方法找到每所学校的本科生百分比最高的种族,然后找到这些最大值的分布。
操作步骤
- 阅读大学数据集,然后仅选择那些包含大学种族百分比信息的列:
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> college_ugds = college.filter(like='UGDS_')
>>> college_ugds.head()
- 使用
idxmax方法获取每一行具有最高竞争百分比的列名称:
>>> highest_percentage_race = college_ugds.idxmax(axis='columns')
>>> highest_percentage_race.head()
INSTNM
Alabama A & M University UGDS_BLACK
University of Alabama at Birmingham UGDS_WHITE
Amridge University UGDS_BLACK
University of Alabama in Huntsville UGDS_WHITE
Alabama State University UGDS_BLACK
dtype: object
- 使用
value_counts方法返回最大出现次数的分布:
>>> highest_percentage_race.value_counts(normalize=True)
UGDS_WHITE 0.670352
UGDS_BLACK 0.151586
UGDS_HISP 0.129473
UGDS_UNKN 0.023422
UGDS_ASIAN 0.012074
UGDS_AIAN 0.006110
UGDS_NRA 0.004073
UGDS_NHPI 0.001746
UGDS_2MOR 0.001164
dtype: float64
工作原理
此秘籍的关键是要认识到所有列都代表相同的信息单元。 我们可以将这些列相互比较,通常是而不是情况。 例如,直接将 SAT 口语成绩与大学生人数进行比较是没有意义的。 由于数据是以这种方式构造的,因此我们可以将idxmax方法应用于数据的每一行,以找到具有最大值的列。 我们需要使用axis参数更改其默认行为。
第 2 步完成此操作并返回一个序列,我们现在可以简单地对其应用value_counts方法以返回分布。 我们将True传递给normalize参数,因为我们对分布(相对频率)感兴趣,而不是原始计数。
更多
我们可能想探索更多并回答这个问题:对于黑人学生多于其他种族的学校,第二高种族百分比的分布是什么?
>>> college_black = college_ugds[highest_percentage_race == 'UGDS_BLACK']
>>> college_black = college_black.drop('UGDS_BLACK', axis='columns')
>>> college_black.idxmax(axis='columns').value_counts(normalize=True)
UGDS_WHITE 0.661228
UGDS_HISP 0.230326
UGDS_UNKN 0.071977
UGDS_NRA 0.018234
UGDS_ASIAN 0.009597
UGDS_2MOR 0.006718
UGDS_AIAN 0.000960
UGDS_NHPI 0.000960
dtype: float64
在此秘籍中应用相同的方法之前,我们需要删除UGDS_BLACK列。 有趣的是,这些黑人人口较多的学校似乎倾向于拥有较高的西班牙裔人口。