在Pandas中选择数据的子集 第二部分
第二部分:布尔索引
这是关于如何从pandas数据框架或系列中选择数据子集的四部分系列的第二部分。Pandas为子集的选择提供了各种各样的选项,这就需要有多篇文章。这个系列被分成以下四个主题。
用[]、.loc和.iloc选择
布尔索引
指派数据子集
如何不选择数据子集
了解更多
Master Data Analysis with Python是一本非常全面的书,有80多个章节和500个练习,可以帮助你成为一个专家。
第1部分与第2部分子集选择
本系列的第一部分涵盖了用[]、.loc和.iloc进行子集选择。这三个索引器都使用行/列标签或其整数位置来进行选择。在选择过程中完全不使用系列/数据框架的实际数据。
在本系列的第二部分,关于布尔索引,我们将根据系列/数据框架中数据的实际值,而不是根据它们的行/列标签或整数位置来选择数据子集。
关于布尔选择的文档
在学习布尔选择时,除了本教程外,我一直建议阅读官方文档。文档中使用了更多带有假数据的正式例子,但仍然是一个很好的资源。
文档中使用了布尔索引这个词,但你也会看到布尔选择。
pandas文档中的布尔索引
Stack Overflow数据
我们在本教程中使用的数据来自Stack Overflow的数据资源管理器,这是一个神奇的工具,可以从网站上收集大量的数据。你必须知道SQL,才能使用数据探索器。数据资源管理器允许你保存查询。看一看我用来收集数据的查询。
下表包含了在 stack overflow 上被标记为 pandas 的每个问题的数据。
第一个问题是2011年3月30日提出的。从那时起,截至2017年12月2日,已经有超过56,000个问题被添加。
import pandas as pd
import numpy as np
so = pd.read_csv('././data/stackoverflow_qa.csv')
so.head()
用浅显的英语提出简单的问题
在我们讨论布尔索引的技术定义之前,让我们看看它能回答的问题类型的一些例子。
找到所有在2014年之前创建的问题
找到所有得分超过50分的问题
查找所有得分在50至100之间的问题
查找所有由斯科特-波士顿回答的问题
查找所有由以下5位用户回答的问题
查找所有在2014年3月至2014年10月期间创建的、由Unutbu回答且得分低于5分的问题。
查找所有得分在5-10之间或浏览量大于10,000的问题
找到所有未被斯科特-波士顿回答的问题
你也会看到这样的例子被称为查询。
所有的查询都有标准
上述每个查询都有一个严格的逻辑标准,必须一次检查一行。
保留或丢弃整行数据
如果你要手动回答上述查询,你需要扫描每一行,并确定该行作为一个整体是否符合标准。如果该行符合标准,那么它就会被保留,如果不符合,那么它就会被丢弃。
每一行都会有一个与之相关的真或假的值
当你执行布尔索引时,DataFrame的每一行(或一个系列的值)将有一个True或False的值与之相关,这取决于它是否符合标准。真/假值被称为布尔值。文档中称整个过程为布尔式索引。
因为我们是用布尔值来选择数据,所以有时被称为布尔选择。从本质上讲,我们是用布尔值来选择数据的子集。
使用[ ]和.loc进行布尔选择
我们将使用第一部分中相同的三个索引器,[]和.loc来完成我们的布尔式选择。我们将通过在这些索引器中放置一个布尔序列来实现。这个序列的行数/值与它正在进行选择的DataFrame/Series的行数/值相同。
.iloc索引器可以与布尔选择一起工作,但几乎从未被使用。最后有一个小节会说明为什么它是不必要的。
暂时集中在[ ]上
为了简化事情,我们将只使用大括号,[],我把它称为第一部分中的索引操作符而已。我们将在稍后得到其他的索引器。
用Python进行主数据分析
Master Data Analysis with Python是一个非常全面的课程,它将帮助你学习pandas来做数据分析。
我相信这是学习如何用pandas进行数据分析的最佳资源,如果你不满意,我提供30天100%退款保证。
使用一个小的DataFrame来开始
在我们做第一个布尔选择之前,让我们简化一下,使用堆栈溢出数据的前五行作为我们的起始DataFrame。
so_head = so.head()
so_head
手动创建一个布尔值的列表
对于我们的第一个布尔值选择,我们不会回答任何有趣的 "英语 "查询,而只是用布尔值列表来选择行。
例如,让我们通过创建以下列表来选择第一和第三行。
criteria = [True, False, True, False, False)
我们可以把这个布尔运算的列表传递给索引运算符,完成我们的选择。
so_head[criteria]
等一下......[ ]不是只用于列选择吗?
对于DataFrame来说,仅仅是索引操作符的主要目的是通过使用一个字符串或一个字符串列表来选择一个或多个列。现在,突然间,这个例子显示整个行是用布尔值选择的。这就是让pandas,不幸的是,成为使用起来最混乱的库之一。
操作符重载
只有索引操作符是重载的。这意味着,根据输入的不同,pandas会做一些完全不同的事情。下面是你传递给仅仅是索引操作符的不同对象的规则。
字符串--返回一个列作为一个系列
字符串列表--将所有这些列作为一个DataFrame返回
一个片断--选择行(可以同时做标签和整数的位置--令人困惑!)。
一个布尔运算的序列--选择所有为真的行
综上所述,主要是索引操作符选择列,但如果你传递给它一个布尔运算序列,它将选择所有为 "真 "的行。
你说的 "序列 "是什么意思?
我一直用布尔运算序列这个词来指代真/假值。从技术上讲,最常见的内置 Python 序列类型是列表和图元。除了列表之外,你最常使用的是 pandas 系列作为你的布尔运算 "序列"。
让我们手动创建一个布尔序列来选择 so_head 的最后三行。
s = pd.Series([False, False, True, True, True])
s
0 False
1 False
2 True
3 True
4 True
dtype: bool
so_head[s]
手工创建布尔型系列时要注意
上面的例子之所以有效,是因为布尔系列和so_head的索引完全相同。让我们输出它们,以便你能清楚地看到这一点。
s.index
RangeIndex(start=0, stop=5, step=1)
so_head.index
RangeIndex(start=0, stop=5, step=1)
当索引不对齐时,布尔型选择失败
当你使用一个布尔系列来做布尔选择时,两个对象的索引必须完全相同。让我们创建一个稍微不同的系列,其索引与它所索引的DataFrame不同。
s = pd.Series([False, False, True, True, True], index=[2, 3, 4, 5, 6])
s
2 False
3 False
4 True
5 True
6 True
dtype: bool
so_head[s]
IndexingError: 作为索引器提供的布尔系列无法对齐(布尔系列的索引和被索引对象的索引不匹配
IndexingError: 无法对齐的布尔系列!
如果布尔系列和你正在进行布尔选择的对象的索引不完全匹配,你会得到上述错误。这就是为什么你几乎不会像这样手工创建布尔系列的原因之一,你会在下面看到。
同时使用NumPy数组
你也可以使用NumPy数组来做布尔选择。NumPy数组没有索引,所以你不会得到上面的错误,但是你的数组需要和你要做布尔选择的对象的长度完全相同。
a = np.array([True, False, False, False])
so_head[a]
不要手工创建布尔系列
你可能永远不会像上面那样手工创建一个布尔系列。相反,你会根据你的数据值来产生它们。
使用比较运算符来创建布尔型系列
创建布尔系列的主要方法是使用六个比较运算符中的一个。
<
<=
=
==
!=
对单列数据使用比较运算符
你几乎总是在单列或系列的数据上使用比较运算符。例如,让我们从分数列创建一个布尔系列。让我们来确定分数是否至少为10。
我们选择分数列,然后测试每个值是否大于或等于10的条件。注意,这个操作被应用于系列中的每个值。一个布尔型系列被返回。
criteria = so['score'] >= 10
criteria.head(10)
0 False
1 False
2 False
3 False
4 False
5 False
6 True
7 True
8 True
9 False
Name: score, dtype: bool
最后做一个布尔型选择
现在我们在变量criteria中存储了我们的布尔系列,我们可以将其传递给索引操作符,以便只选择得分至少为10的行。
在本教程的其余部分,我们将使用整个so数据框架。
so_score_10_or_more = so[criteria]
so_score_10_or_more.head()
有多少行的分数至少为10分
仅仅通过查看结果DataFrame的头部,我们不知道有多少行通过了我们的标准。让我们输出我们的原始数据框架和结果数据框架的形状。
so.shape
(56398, 12)
so_score_10_or_more.shape
(1505, 12)
只有大约3%的问题得到10分或更多。
一行中的布尔选择
通常,你会看到布尔选择发生在一行代码中,而不是我们上面使用的多行代码。如果下面的内容让你感到困惑,那么我建议将你的布尔系列存储到一个变量中,就像我在上面对标准所做的那样。
也可以把创建布尔系列的操作放在索引操作符里面,就像这样。
so[so['score'] >= 10].head()
单一条件表达式
我们的第一个例子测试了一个单一条件(分数是否为10或更多)。让我们来测试一个不同的单一条件,寻找所有由Scott Boston回答的问题。ans_name变量保存了发布接受的问题答案的人的显示名称。
我们使用==运算符来测试是否相等,并再次将这个结果存储到变量criteria中。同样,我们把这个变量传给索引运算符,这样就完成了我们的选择。
第一步--创建布尔系列
criteria = so['ans_name'] == 'Scott Boston' 。
第2步--做布尔型选择
so[criteria].head()
多条件表达式
到目前为止,我们的两个布尔选择都涉及到一个单一的条件。当然,你可以有你想要的多个条件。要做到这一点,你需要使用三个逻辑运算符and、or和not来组合你的布尔表达式。
使用&, | , ~
尽管Python使用了and, or, and not的语法,但是当用pandas测试多个条件时,这些语法将不起作用。
你必须在 pandas 中使用以下运算符。
& 用于和
| 表示或
~ 表示不
我们的第一个多条件表达式
让我们找到所有得分至少为5分并且由Scott Boston回答的问题。首先,我们将创建两个单独的变量来保存每个条件。
criteria_1 = so['score'] >= 5
criteria_2 = so['ans_name'] == 'Scott Boston'。
然后,我们将使用和运算符,即安培号&,来合并它们
criteria_all = criteria_1 & criteria_2
现在我们可以把这个最后的标准传递给索引运算符
so[critical_all]
如果你喜欢这篇文章,可以考虑购买 "全能通行证",它包括我所有当前和未来的材料,价格低廉。
一行中的多个条件
可以将整个表达式合并成一行。许多pandas用户喜欢这样做,其他人则讨厌这样做。无论如何,知道如何这样做是个好主意,因为你肯定会遇到这种情况。
使用小括号来分隔条件
你必须将每个条件封装在一组小括号中,才能使其发挥作用。
每个条件将像这样分开。
(so['score'] >= 5) & (so['ans_name'] == 'Scott Boston')
然后,我们可以把这个表达式丢在只有索引操作符的里面
与之前的结果相同
so[(so['score'] >= 5) & (so['ans_name'] == 'Scott Boston')] 。
使用一个或条件
让我们找到所有分数至少为100分或至少有10个答案的问题。
对于or条件,我们使用管道|。
so[(so['score'] >= 100) | (so['answerercount'] >= 10)] .head()
用not操作符反转一个条件
斜体字~代表not操作符,可以反转一个条件。例如,如果我们想要所有分数大于100的问题,我们可以这样做。
so[~(so['score'] <= 100)].head()
注意,在 "分数小于等于100 "的条件周围有圆括号。我们必须在这里使用小括号,否则操作就不能正确进行。
当然,这个微不足道的例子不需要not运算符,可以用大于运算符来代替,但这很容易验证。
让我们回过头来看一个例子,把分数至少为100或答案数至少为10的条件反过来。要做到这一点,我们必须用小括号把我们的整个表达式包起来,像这样。
~((so['score'] >= 100) | (so['answerercount'] >= 10))
每个内部表达式周围也有一组小括号。
复杂的条件
我们可以建立极其复杂的条件来选择符合特定条件的DataFrame的行。例如,我们可以选择Scott Boston回答的所有问题,得分在5分或以上,或者Ted Petrou回答的问题,答案数在5分或以上。
对于多个条件,最好将逻辑分成多个步骤。
criteria_1 = (so['score'] >= 5) & (so['ans_name'] == 'Scott Boston')
criteria_2 = (so['answerercount'] >= 5) & (so['ans_name'] == 'Ted Petrou')
criteria_all = criteria_1 | criteria_2
so[candal_all]
单一列中的大量或条件 - 使用isin
偶尔,我们会想在一列中测试多个值的相等性。这在字符串列中是最常见的。例如,假设我们想找到Scott Boston, Ted Petrou, MaxU和unutbu回答的所有问题。
做到这一点的一个方法是用四个或条件。
criteria = (((so['ans_name'] == 'Scott Boston') |)
(so['ans_name'] == 'Ted Petrou') | |
(so['ans_name'] == 'MaxU') | (so['ans_name'] == 'MaxU')
(so['ans_name'] == 'unutbu')
一个更简单的方法是使用系列方法isin。把你要检查的所有项目的列表传给它,以确定是否相等。
criteria = so['ans_name'].isin(['Scott Boston', 'Ted Petrou',
'MaxU', 'unutbu'])
criteria.head()
0 False
1 False
2 False
3 False
4 False
Name: ans_name, dtype: bool
so[criteria].head()
练习Python - 对Python的全面介绍(200多页,100多道练习题)。
Master Data Analysis with Python - 最全面的学习pandas的课程。(800多页,350多个练习)
用Python掌握机器学习 - 深入研究用scikit-learn进行机器学习,不断更新以展示最新和最伟大的工具。(300多页)
将isin与其他标准相结合
你可以像使用逻辑运算符一样,使用isin方法产生的布尔系列。例如,如果我们想找到上面这些人回答的所有问题,并且分数大于30分,我们可以做以下工作。
criteria_1 = so['ans_name'].isin(['Scott Boston', 'Ted Petrou',
'MaxU', 'unutbu'])
criteria_2 = so['score'] > 30
criteria_all = criteria_1 & criteria_2
so[critical_all].tail()
使用isnull来查找有缺失值的记录
isnull方法返回一个布尔系列,其中True表示一个缺失的值。例如,没有接受的答案的问题,其ans_name的值是缺失的。让我们在这一列上调用isnull。
no_answer = so['ans_name'].isnull()
no_answer.head(6)
0 False
1 False
2 False
3 False
4 False
5 True
Name: ans_name, dtype: bool
这只是另一个布尔系列,我们可以把它传递给仅仅是索引运算符。
so[no_answer].head()
isnull的一个别名是isna方法。别名意味着它是相同的方法,只是名字不同而已。
在一个系列上的布尔选择
到目前为止,所有的例子都是发生在So DataFrame上的。在系列上进行布尔选择的情况几乎相同。由于只有一个维度的数据,你的查询通常会比较简单。
首先,让我们选择一个单列的数据作为系列,比如commentcount列。
s = so['commentcount']
s.head()
0 4
1 6
2 0
3 0
4 0
Name: commentcount, dtype: int64
让我们测试一下大于10的评论数
criteria = s > 10
criteria.head()
0 False
1 False
2 False
3 False
4 False
Name: commentcount, dtype: bool
注意,这里没有列的选择,因为我们已经只剩下一列了。让我们把这个标准传递给索引运算符,只选择大于10的值。
s[criteria].head()
17 16
76 14
566 11
763 12
781 19
Name: commentcount, dtype: int64
我们可以像这样一步到位地完成这个工作
s[s > 10].head()
17 16
76 14
566 11
763 12
781 19
Name: commentcount, dtype: int64
如果我们想找到那些大于10但小于15的评论,我们可以使用一个和条件,像这样。
s[(s > 10) & (s < 15)].head()
76 14
566 11
763 12
787 12
837 13
Name: commentcount, dtype: int64
另一种可能是between方法
Pandas中内置了很多重复的功能。与其像上面那样写两个布尔条件来选择一个范围内的所有值,你可以使用between方法来创建一个布尔系列。使用时,将范围的左端和右端点传给它。这些端点是包括在内的。
因此,为了复制前面的例子,你可以这样做。
s[s.between(11, 14)].head()
76 14
566 11
763 12
787 12
837 13
Name: commentcount, dtype: int64
用.loc同时选择行和列标签的布尔值
.loc索引器在第一部分中已经彻底介绍了,现在将在这里介绍同时选择行和列。在第一部分中,我们说过.loc只通过标签进行选择。这并不严格,因为它也能在按标签选择的同时进行布尔式选择。
So.loc[(so['score'] >= 5) & (so['ans_name'] == 'Scott Boston')]
criteria = so['ans_name'].isin(['Scott Boston', 'Ted Petrou',
'MaxU', 'unutbu'])
so.loc[criteria].head()
.loc的行和列的选择用逗号分开
.loc的最大好处是,它允许你同时沿着行做布尔选择,并通过标签进行列选择
例如,假设我们想找到所有浏览量超过20k的问题,但只返回creationdate、viewcount和ans_name列你可以做如下操作
so.loc[so['viewcount'] > 20000, ['creationdate', 'viewcount',
'ans_name']].head(10)
你可以像这样把每个选择分成几块
row_selection = so['viewcount'] > 20000
col_selection = ['creationdate', 'viewcount', 'ans_name']
so.loc[row_selection, col_selection]
用.loc可以有很多的组合
记住,.loc可以接受一个字符串、一个字符串列表或一个片断你可以使用所有三种可能的方式来选择你的数据你还可以为你的行做非常复杂的布尔选择
让我们来选择 favoritecount 在 30 和 40 之间的行,以及从标题开始到最后的每三列 奇怪但可行
代码块
so.loc[so['favoritecount'].between(30, 40), 'title':3].head()
对列进行布尔选择?
实际上是可以用一串布尔值来选择列的你将一个长度与列数相同的布尔值的列表、系列或数组传递给.loc
让我们做一个简单的手工例子,我们手工创建一个布尔运算的列表首先,让我们找出在我们的数据集中有多少列
so.shape
(56398, 12)
让我们创建一个有12个布尔的列表
col_bools = [True, False, False] * 4
col_bools
[True,
False,
False,
True,
False,
False,
True,
False,
False,
True,
False,
False]
使用.loc从col_bools中选择所有只有True列的行
so.loc[:, col_bools].head()
你也可以同时选择行和列让我们选择同样的列,但选择有超过500,000次浏览的行
so.loc[so['viewcount'] > 500000, col_bools]
一个更实际的例子
让我们来看看在列上做布尔选择的一个稍微实际一点的例子假设我们将10个硬币翻转了100次,并将每一次试验存储在下面的DataFrame的一个列中
coins = pd.DataFrame(np.random.randint(0, 2, (100, 10))
columns=list('abcdefghij'))
coins.head()
coins.shape
(100, 10)
如果我们对只选择有50%以上的头的列感兴趣,我们可以首先像这样取每一列的平均值
coin_mean = coins.mean()
coin_mean
a 0.50
b 0.46
c 0.48
d 0.47
e 0.43
f 0.52
g 0.44
h 0.47
i 0.57
j 0.44
dtype: float64
让我们测试一下百分比大于0.5的条件
coin_mean > .5
a False
b False
c False
d False
e False
f True
g False
h False
i True
j False
dtype: bool
最后,我们可以使用这个布尔系列,只选择符合我们条件的列
coins.loc[:, coins.mean() > .5].head()
列与列之间的比较
之前所有的系列比较都是针对一个单一的标量值进行的我们可以通过将一列与另一列进行比较来创建一个布尔系列例如,我们可以找到所有答案多于分数的问题
criteria = so['answerercount'] > so['score']
so[criteria].head()
在一行中,上面的内容会是这样的
so[so['answerercount'] > so['score']]
几乎不要在布尔选择中使用.iloc
首先,记住.iloc使用INTEGER位置来进行选择
你将很少使用.iloc来做布尔选择,几乎总是只使用索引操作符或.loc为了了解原因,让我们尝试运行一个简单的布尔选择,找到所有拥有超过100,000个视图的行
so.iloc[so['viewcount'] > 100000]
NotImplementedError:
基于iLocation的整数类型的布尔索引不可用
NotImplementedError
pandas开发者还没有决定对.iloc进行布尔选择(用系列),所以它不能工作然而,你可以将系列转换为一个列表或NumPy数组,作为一种变通方法
让我们把我们的系列保存为一个变量并仔细检查其类型
criteria = so['viewcount'] > 100000
type(criteria)
pandas.core.series.Series
让我们用values属性抓取底层的NumPy数组,并把它传递给.iloc
a = criteria.values
so.iloc[a].head()
你也可以用整数进行同步列选择
so.iloc[a, [5, 10, 11]].head()
我想我从来没有将.iloc用于布尔选择,因为它没有实现系列。我添加的原因是它是pandas中三个主要的索引器之一,重要的是要知道它在布尔选择中根本就不怎么用。
s = so['score']
s[s > 100].head()
8 201
17 136
75 199
100 144
106 340
Name: score, dtype: int64
.loc和[]在系列中对布尔选择的作用是一样的。
布尔选择在.loc中的作用与在系列中的索引操作符中的作用是一样的。当传递一个布尔系列时,两个索引器都会进行行选择。由于Series没有列,在这种情况下,两个索引器是相同的。
s.loc[s > 100].head()
8 201
17 136
75 199
100 144
106 340
Name: score, dtype: int64
摘要
布尔索引或布尔选择是根据数值本身而不是行/列标签或整数位置来选择一个系列/数据框架的子集。
布尔选择用于回答常见的查询,如 "找到所有年薪超过15万的女工程师"
要进行布尔选择,首先要创建一个真/假值的序列,并将其传递给DataFrame/系列索引器
每一行数据被保留或丢弃
索引操作符是重载的--根据传递给它们的内容来改变功能
通常情况下,你将首先用6个比较运算符中的一个创建一个布尔系列
你将把这个布尔系列传递给其中一个索引器来进行选择
使用isin方法来测试同一列中是否有多个相等的数据
使用isnull来查找某一列中所有缺失值的行
可以使用between系列方法来测试系列值是否在一个范围内
你可以用and (&), or (|), and not (~)逻辑运算符创建复杂的条件
当你在一行中有多个条件时,你必须用圆括号来包裹每个表达式。
如果你有复杂的条件,考虑将每一组条件存储到它自己的变量中(也就是说,不要在一行中做所有的事情)。
如果你只选择行,那么你几乎总是只使用索引操作符
如果你同时对行进行布尔选择并选择列标签,那么你将使用.loc
你几乎不会使用.iloc来做布尔选择。
布尔选择对系列的作用和对数据框架的作用是一样的
网友评论