本文示例代碼已上傳至個人
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
數據分析操做。
以上就是本文的所有內容,歡迎在評論區與我討論~