本文示例代码已上传至个人
Github
仓库https://github.com/CNFeffery/DataScienceStudyNotespython
利用pandas
进行数据分析的过程,不只仅是计算出结果那么简单,不少初学者喜欢在计算过程当中建立一堆命名为所欲为的中间变量,一方面使得代码读起来费劲,另外一方面越多的没必要要的中间变量意味着越高的内存占用,越多的计算资源消耗。git
所以不少时候为了提高整个数据分析工做流的执行效率以及代码的简洁性,须要配合一些pandas
中的高级特性。本文就将带你们学习如何在pandas
中化繁为简,利用query()
和eval()
来实现高效简洁的数据查询与运算。github
query()
顾名思义,是pandas
中专门执行数据查询的API,其实早在2014年,pandas
0.13版本中这个特性就已经出现了,随着后续众多版本的迭代更新,目前pandas
中的query()
已经进化得很是好用(笔者目前使用的pandas
版本为1.1.0)。express
首先从一个实际例子认识一下query()
的用法,这里咱们使用到netflix电影与剧集发行数据集,包含了6234个做品的基本属性信息,你能够在文章开头的Github
仓库对应目录下找到它。app
正常读入数据后,咱们分别使用传统方法和query()
来执行这样的组合条件查询,不一样的条件之间用对应的and or
或& |
链接都可:函数
找出类型为TV Show且国家不含美国的Kids' TV学习
经过比较能够发如今使用query()
时咱们在不须要重复书写数据框名称[字段名]
这样的内容,字段名也直接能够看成变量使用,并且不一样条件之间不须要用括号隔开,在条件繁杂的时候简化代码的效果更为明显。3d
经过上面的小例子咱们认识到query()
的强大之处,下面咱们就来学习query()
的经常使用特性:code
query()
最核心的特性就是能够直接根据传入的查询表达式,将字段名解析为对应的列,其中对字段名的命名规范有必定要求:当字段名符合Python
中对变量命名规范的要求时,即变量名彻底由字母、数字、下划线构成且不以数字开头,这样的字段是能够直接写入query()
表达式的。orm
但你们若是尝试过会发现一些不符合上述规范的变量名也不报错,譬如:
所以能够记住只要在Python
里做为变量名不报错,就能够直接填入字段名,不然须要在字段名两边加上`,譬以下面的例子:
query()
中还支持链式表达式(chained expressions),使得咱们能够进一步简化多条件组合时的语法:
demo = pd.DataFrame({ 'a': [5, 4, 3, 2, 1], 'b': [1, 2, 3, 4, 5] }) demo.query("a <= b != 4")
query()
支持Python
原生的in
判断以及not in
判断,从而简化了多条件判断,好比咱们针对netflix数据集想找出release_year
等于2018或2019的做品:
netflix.query("release_year in [2018, 2019]")
query()
表达式还支持使用外部变量,只须要在外部变量前加上@
符号便可:
query()
我我的以为最惊人的功能就是其能够直接解析Python
语句,这赋予咱们极大的自由度:
def country_count(s): ''' 计算涉及国家数量 ''' return s.split(',').__len__() # 找出发行年份在2018或2019年且合做国家数量超过5个的剧集 netflix.query("release_year.isin([2018, 2019]) and country.apply(@country_count) > 5")
除了对常规字段进行条件筛选,query()
还支持对数据框自身的index
进行条件筛选,具体可分为三种状况:
对于只具备单列Index
的数据框,直接在表达式中使用index
:
# 找出索引列中包含king的记录,忽略大小写 netflix.set_index('title').query("index.str.contains('king', case=False)")
对于MultiIndex
的状况,可分为两种,首先咱们来看看MultiIndex
的names
为空的状况,按照顺序,用ilevel_n
表示MultiIndex
中的第n列index:
# 构造含有MultiIndex的数据框,并重置index的names为None temp = netflix.set_index(['title', 'type']);temp.index.names = (None, None) # 找出第一个index包含king(忽略大小写),第二个index等于Movie的记录 temp.query("ilevel_0.str.contains('king', case=False) and ilevel_1 == 'Movie'")
而对于MultiIndex
的names
有内容的状况,直接用对应的名称传入表达式便可:
# 构造含有MultiIndex的数据框,并重置index的names为None temp = netflix.set_index(['title', 'type']) # 找出第一个index包含king(忽略大小写),第二个index等于Movie的记录 temp.query("title.str.contains('king', case=False) and type == 'Movie'")
而eval()
相似Python
的eval()
函数,能够将字符串形式的命令直接解析并执行。
而pandas
中的eval()
有两种,一种是top-level
级别的eval()
函数,而另外一种是针对数据框的DataFrame.eval()
,咱们接下来要介绍的是后者,其与query()
有不少相同之处,下面只介绍其独有特色。
一样从实际例子出发,一样针对netflix数据,咱们按照必定的计算方法为其新增两列数据,对基于assign()
的方式和基于eval()
的方式进行比较,其中最后一列是False是由于日期转换使用coerce
策略以后没法被解析的日期会填充pd.NAT,而缺失值之间是没法进行相等比较的:
# 利用assign进行新增字段计算并保存为新数据框 result1 = netflix.assign(years_to_now=2020 - netflix['release_year'], new_date_added=pd.to_datetime(netflix['date_added'].str.strip(), format='%B %d, %Y', errors='coerce')) # 利用eval()进行新增字段计算并保存为新数据框 result2 = netflix.eval(''' years_to_now = 2020 - release_year new_date_added = @pd.to_datetime(date_added.str.strip(), format='%B %d, %Y', errors='coerce')''') (result1 == result2).all()
虽然assign()
已经算是pandas
中简化代码的很好用的API了,但面对eval()
,仍是逊色很多
DataFrame.eval()
经过传入多行表达式,每行做为独立的赋值语句,其中对应前面数据框中数据字段能够像query()
同样直接书写字段名,亦可像query()
那样直接执行Python
语句。
但要注意的是eval()
中每一个新字段的赋值必须写在同一行,不然会出错:
netflix.eval(''' years_to_now = 2020 - release_year new_date_added = @pd.to_datetime(date_added.str.strip(), format='%B %d, %Y', errors='coerce')''')
所以若是你要使用到的函数参数不少,能够利用functools
中的partial
将一些参数固化并保存,从而达到简化eval()
表达式的目的:
from functools import partial # 利用partial固化指定参数 func = partial(pd.to_datetime, format='%B %d, %Y', errors='coerce') netflix.eval(''' years_to_now = 2020 - release_year new_date_added = @func(date_added.str.strip())''')
而我最喜欢DataFrame.eval()
的地方在于配合他,我能够在不少数据分析场景中实现0中间变量,一直链式下去,延续上面的例子,当咱们新增了这两列数据以后,接下来咱们按顺序进行按月统计影片数量、字段重命名、新增当月数量在所有记录排名字段、排序,其中关键的是新增当月数量在所有记录排名字段,若是不用eval()
,你是没法在不建立中间变量的前提下如此简洁地完成需求的:
netflix.eval(''' years_to_now = 2020 - release_year new_date_added = @func(date_added.str.strip())''') \ .resample('M', on='new_date_added') \ .agg({'new_date_added': 'count'}) \ .rename(columns={'new_date_added': '月度发行数量'}) \ .eval('''月度发行数量排名 = 月度发行数量.rank(ascending=False).astype('int')''') \ .sort_values('月度发行数量排名')
使用query()+eval()
,升华pandas
数据分析操做。
以上就是本文的所有内容,欢迎在评论区与我讨论~