Pandas與SQL對(duì)比
作為一名數(shù)據(jù)分析師叭首,平常用的最多的工具是SQL(包括MySQL和Hive SQL等)。對(duì)于存儲(chǔ)在數(shù)據(jù)庫(kù)中的數(shù)據(jù)堡掏,自然用SQL提取會(huì)比較方便宋梧,但有時(shí)我們會(huì)處理一些文本數(shù)據(jù)(txt,csv),這個(gè)時(shí)候就不太好用SQL了专缠。Python也是分析師常用的工具之一雷酪,尤其pandas更是一個(gè)數(shù)據(jù)分析的利器。雖然二者的語(yǔ)法涝婉,原理可能有很大差別哥力,但在實(shí)現(xiàn)的功能上,他們有很多相通的地方墩弯,這里特進(jìn)行一個(gè)總結(jié)吩跋,方便大家對(duì)比學(xué)習(xí)~
什么是 Pandas?
Pandas渔工,即 Python 數(shù)據(jù)分析庫(kù)(Python Data Analysis Library)锌钮,是一個(gè)用于數(shù)據(jù)分析和處理的 Python 庫(kù)。它是開源的引矩,被 Anaconda 所支持梁丘。它特別適合結(jié)構(gòu)化(表格化)數(shù)據(jù)。有關(guān)更多信息,請(qǐng)參考 pandas.pydata.org/pandas-docs脓魏。
使用它可以做什么兰吟?
之前您在 SQL 里面進(jìn)行的查詢數(shù)據(jù)以及其他各種操作,都可以由 Pandas 完成茂翔!
太好了混蔼!我要從哪里開始呢?
對(duì)于已經(jīng)習(xí)慣于用 SQL 語(yǔ)句來(lái)處理數(shù)據(jù)問(wèn)題的人來(lái)說(shuō)珊燎,這是一個(gè)令人生畏的部分惭嚣。
SQL 是一種 聲明式編程語(yǔ)言
:en.wikipedia.org/wiki/List_o…。
使用 SQL悔政,你通過(guò)聲明語(yǔ)句來(lái)聲明想要的內(nèi)容晚吞,這些聲明讀起來(lái)幾乎就如同普通英文短句一樣順暢。
而 Pandas 的語(yǔ)法與 SQL 完全不同谋国。在 Pandas 中槽地,您對(duì)數(shù)據(jù)集進(jìn)行處理,并將它們鏈在一起,以便按照您希望的方式進(jìn)行轉(zhuǎn)換和重構(gòu)捌蚊。
我們需要一本 phrasebook(常用語(yǔ)手冊(cè))集畅!
剖析 SQL 查詢
SQL 查詢由幾個(gè)重要的關(guān)鍵字組成。在這些關(guān)鍵字之間缅糟,添加您想要看到的具體數(shù)據(jù)挺智。下面是一些沒(méi)有具體數(shù)據(jù)的查詢語(yǔ)句的框架:
SELECT… FROM… WHERE…
GROUP BY… HAVING…
ORDER BY…
LIMIT… OFFSET…
當(dāng)然還有其他命令,但上面這些是最重要的窗宦。那么我們?nèi)绾螌⑦@些命令在 Pandas 實(shí)現(xiàn)呢赦颇?
首先,我們需要向 Pandas 里面加載一些數(shù)據(jù)赴涵,因?yàn)樗鼈冞€沒(méi)有在數(shù)據(jù)庫(kù)中媒怯。如下所示:
import pandas as pd
airports = pd.read_csv('data/airports.csv')
airport_freq = pd.read_csv('data/airport-frequencies.csv')
runways = pd.read_csv('data/runways.csv')
我的數(shù)據(jù)來(lái)自 ourairports.com/data/。
SELECT, WHERE, DISTINCT, LIMIT
這是一些 SELECT 語(yǔ)句句占。我們使用 LIMIT 來(lái)截取結(jié)果沪摄,使用 WHERE 來(lái)進(jìn)行過(guò)濾篩選躯嫉,使用 DISTINCT 去除重復(fù)的結(jié)果纱烘。
SQL | Pandas |
---|---|
select * from airports | airports |
select * from airports limit 3 | airports.head(3) |
select id from airports where ident = 'KLAX' | airports[airports.ident == 'KLAX'].id |
select distinct type from airport | airports.type.unique() |
使用多個(gè)條件進(jìn)行 SELECT 操作
我們將多個(gè)條件通過(guò)符號(hào)&
組合在一起。如果我們只想要表格列中條件的子集條件祈餐,那么可以通過(guò)添加另外一對(duì)方括號(hào)來(lái)表示擂啥。
SQL | Pandas |
---|---|
select * from airports where iso_region = 'US-CA' and type = 'seaplane_base' | airports[(airports.iso_region == 'US-CA') & (airports.type == 'seaplane_base')] |
select ident, name, municipality from airports where iso_region = 'US-CA' and type = 'large_airport' | airports[(airports.iso_region == 'US-CA') & (airports.type == 'large_airport')][['ident', 'name', 'municipality']] |
ORDER BY(排序)
默認(rèn)情況下,Pandas 會(huì)使用升序排序帆阳。如果要使用降序哺壶,請(qǐng)?jiān)O(shè)置 asending=False
。
SQL | Pandas |
---|---|
select * from airport_freq where airport_ident = 'KLAX' order by type | airport_freq[airport_freq.airport_ident == 'KLAX'].sort_values('type') |
select * from airport_freq where airport_ident = 'KLAX' order by type desc | airport_freq[airport_freq.airport_ident == 'KLAX'].sort_values('type', ascending=False) |
IN… NOT IN(包含……不包含)
我們知道了如何對(duì)值進(jìn)行篩選蜒谤,但如何對(duì)一個(gè)列表進(jìn)行篩選呢山宾,如同 SQL 的IN
語(yǔ)句那樣?在 Pandas 中鳍徽,.isin()
操作符的工作方式與 SQL 的IN
相同资锰。要使用否定條件,請(qǐng)使用 ~
阶祭。
SQL | Pandas |
---|---|
select * from airports where type in ('heliport', 'balloonport') | airports[airports.type.isin(['heliport', 'balloonport'])] |
select * from airports where type not in ('heliport', 'balloonport') | airports[~airports.type.isin(['heliport', 'balloonport'])] |
GROUP BY, COUNT, ORDER BY(分組)
分組操作很簡(jiǎn)單:使用 .groupby()
操作符绷杜。SQL 和 pandas 中的 COUNT
語(yǔ)句存在微妙的差異。在 Pandas 中濒募,.count()
將返回非空/非 NaN 的值鞭盟。要獲得與 SQL COUNT
相同的結(jié)果,請(qǐng)使用 .size()
瑰剃。
SQL | Pandas |
---|---|
select iso_country, type, count(*) from airports group by iso_country, type order by iso_country, type | airports.groupby(['iso_country', 'type']).size() |
select iso_country, type, count() from airports group by iso_country, type order by iso_country, count() desc | airports.groupby(['iso_country', 'type']).size().to_frame('size').reset_index().sort_values(['iso_country', 'size'], ascending=[True, False]) |
下面齿诉,我們對(duì)多個(gè)字段進(jìn)行分組。Pandas 默認(rèn)情況下將對(duì)列表中相同字段上的內(nèi)容進(jìn)行排序,因此在第一個(gè)示例中不需要 .sort_values()
粤剧。如果我們想使用不同的字段進(jìn)行排序遗座,或者想使用 DESC
而不是 ASC
,就像第二個(gè)例子那樣俊扳,那我們就必須明確使用 .sort_values()
:
SQL | Pandas |
---|---|
select iso_country, type, count(*) from airports group by iso_country, type order by iso_country, type | airports.groupby(['iso_country', 'type']).size() |
select iso_country, type, count() from airports group by iso_country, type order by iso_country, count() desc | airports.groupby(['iso_country', 'type']).size().to_frame('size').reset_index().sort_values(['iso_country', 'size'], ascending=[True, False]) |
其中使用.to_frame()
和 reset_index()
是為什么呢途蒋?因?yàn)槲覀兿Mㄟ^(guò)計(jì)算出的字段(size
)進(jìn)行排序,所以這個(gè)字段需要成為 DataFrame 的一部分馋记。在 Pandas 中進(jìn)行分組之后号坡,我們得到了一個(gè)名為 GroupByObject 的新類型。所以我們需要使用 .to_frame()
把它轉(zhuǎn)換回 DataFrame類型梯醒。再使用 .reset_index()
宽堆,我們重新進(jìn)行數(shù)據(jù)幀的行編號(hào)。
HAVING(包含)
在 SQL 中茸习,您可以使用 HAVING 條件語(yǔ)句對(duì)分組數(shù)據(jù)進(jìn)行追加過(guò)濾畜隶。在 Pandas 中,您可以使用 .filter()
号胚,并給它提供一個(gè) Python 函數(shù)(或 lambda 函數(shù))籽慢,如果結(jié)果中包含這個(gè)組,該函數(shù)將返回 True
猫胁。
SQL | Pandas |
---|---|
select type, count() from airports where iso_country = 'US' group by type having count() > 1000 order by count(*) desc | airports[airports.iso_country == 'US'].groupby('type').filter(lambda g: len(g) > 1000).groupby('type').size().sort_values(ascending=False) |
前 N 個(gè)記錄
假設(shè)我們做了一些初步查詢箱亿,現(xiàn)在有一個(gè)名為by_country
的 dataframe,它包含每個(gè)國(guó)家的機(jī)場(chǎng)數(shù)量:
在接下來(lái)的第一個(gè)示例中弃秆,我們通過(guò) airport_count
來(lái)進(jìn)行排序届惋,只選擇數(shù)量最多的 10 個(gè)國(guó)家。第二個(gè)例子比較復(fù)雜菠赚,我們想要“前 10 名之后的另外 10 名脑豹,即 11 到 20 名”:
SQL | Pandas |
---|---|
select iso_country from by_country order by size desc limit 10 | by_country.nlargest(10, columns='airport_count') |
select iso_country from by_country order by size desc limit 10 offset 10 | by_country.nlargest(20, columns='airport_count').tail(10) |
聚合函數(shù)(MIN,MAX衡查,MEAN)
現(xiàn)在給定一組 dataframe瘩欺,或者一組跑道數(shù)據(jù):
計(jì)算跑道長(zhǎng)度的最小值,最大值峡捡,平均值和中值:
SQL | Pandas |
---|---|
select max(length_ft), min(length_ft), avg(length_ft), median(length_ft) from runways | runways.agg({'length_ft': ['min', 'max', 'mean', 'median']}) |
SQL沒(méi)有median函數(shù)击碗。假設(shè)您編寫了一個(gè)用戶定義的函數(shù)來(lái)計(jì)算該統(tǒng)計(jì)信息(因?yàn)檫@里的重要部分是SQL和Pandas之間的語(yǔ)法差異)。
您會(huì)注意到们拙,使用 SQL 查詢稍途,每個(gè)統(tǒng)計(jì)結(jié)果都是一列數(shù)據(jù)。但是使用 Pandas 的聚集方法砚婆,每個(gè)統(tǒng)計(jì)結(jié)果都是一行數(shù)據(jù):
不用擔(dān)心 — 只需將 dataframe 通過(guò) .T
進(jìn)行轉(zhuǎn)換就可以得到成列的數(shù)據(jù):
JOIN(連接)
使用 .merge()
來(lái)連接 Pandas 的 dataframes械拍。您需要提供要連接哪些列(left_on
和 right_on
)和連接類型:inner
(默認(rèn))突勇,left
(對(duì)應(yīng) SQL 中的 LEFT OUTER
),right
(RIGHT OUTER
)坷虑,或 OUTER
(FULL OUTER
)甲馋。
SQL | Pandas |
---|---|
select airport_ident, type, description, frequency_mhz from airport_freq join airports on airport_freq.airport_ref = airports.id where airports.ident = 'KLAX' | airport_freq.merge(airports[airports.ident == 'KLAX'][['id']], left_on='airport_ref', right_on='id', how='inner')[['airport_ident', 'type', 'description', 'frequency_mhz']] |
UNION ALL and UNION(合并)
使用 pd.concat()
替代 UNION ALL
來(lái)合并兩個(gè) dataframes:
SQL | Pandas |
---|---|
select name, municipality from airports where ident = 'KLAX' union all select name, municipality from airports where ident = 'KLGB' | pd.concat([airports[airports.ident == 'KLAX'][['name', 'municipality']], airports[airports.ident == 'KLGB'][['name', 'municipality']]]) |
合并過(guò)程中想要?jiǎng)h除重復(fù)數(shù)據(jù)(等價(jià)于 UNION
),你還需要添加 .drop_duplicates()
迄损。
INSERT(插入)
到目前為止定躏,我們一直在講篩選,但是在您的探索性分析過(guò)程中芹敌,您可能也需要修改痊远。如果您想添加一些遺漏的記錄你該怎么辦?
Pandas 里面沒(méi)有形同 INSERT
語(yǔ)句的方法。相反氏捞,您只能創(chuàng)建一個(gè)包含新記錄的新 dataframe碧聪,然后合并兩個(gè) dataframe:
SQL | Pandas |
---|---|
create table heroes (id integer, name text); | df1 = pd.DataFrame({'id': [1, 2], 'name': ['Harry Potter', 'Ron Weasley']}) |
insert into heroes values (1, 'Harry Potter'); | df2 = pd.DataFrame({'id': [3], 'name': ['Hermione Granger']}) |
insert into heroes values (2, 'Ron Weasley'); | |
insert into heroes values (3, 'Hermione Granger'); | pd.concat([df1, df2]).reset_index(drop=True) |
UPDATE(更新)
現(xiàn)在我們需要修改原始 dataframe 中的一些錯(cuò)誤數(shù)據(jù):
SQL | Pandas |
---|---|
update airports set home_link = 'http://www.lawa.org/welcomelax.aspx' where ident == 'KLAX' | airports.loc[airports['ident'] == 'KLAX', 'home_link'] = 'http://www.lawa.org/welcomelax.aspx' |
DELETE(刪除)
從 Pandas dataframe 中刪除數(shù)據(jù)的最簡(jiǎn)單(也是最易讀的)方法是將 dataframe 提取包含您希望保留的行數(shù)據(jù)的子集∫壕ィ或者逞姿,您可以通過(guò)獲取行索引來(lái)進(jìn)行刪除,使用 .drop()
方法刪除這些索引的行:
SQL | Pandas |
---|---|
delete from lax_freq where type = 'MISC' | lax_freq = lax_freq[lax_freq.type != 'MISC'] |
lax_freq.drop(lax_freq[lax_freq.type == 'MISC'].index) |
Immutability(不變性)
我需要提及一件重要的事情 — 不可變性捆等。默認(rèn)情況下滞造,大部分應(yīng)用于 Pandas dataframe 的操作符都會(huì)返回一個(gè)新對(duì)象。有些操作符可以接收 inplace=True
參數(shù)楚里,這樣您可以繼續(xù)使用原始的 dataframe断部。例如猎贴,以下是一個(gè)就地重置索引的方法:
df.reset_index(drop=True, inplace=True)
然而,上面的 UPDATE
示例中的 .loc
操作符僅定位需要更新記錄的索引班缎,并且這些值會(huì)就地更改。此外她渴,如果您更新了一列的所有值:
df['url'] = 'http://google.com'
或者添加一個(gè)計(jì)算得出的新列:
df['total_cost'] = df['price'] * df['quantity']
這些都會(huì)就地發(fā)生變化达址。
更多!
Pandas 的好處在于它不僅僅是一個(gè)查詢引擎趁耗。你可以用你的數(shù)據(jù)做更多事情沉唠,例如:
- 以多種格式輸出:
df.to_csv(...) # csv file
df.to_hdf(...) # HDF5 file
df.to_pickle(...) # serialized object
df.to_sql(...) # to SQL database
df.to_excel(...) # to Excel sheet
df.to_json(...) # to JSON string
df.to_html(...) # render as HTML table
df.to_feather(...) # binary feather-format
df.to_latex(...) # tabular environment table
df.to_stata(...) # Stata binary data files
df.to_msgpack(...) # msgpack (serialize) object
df.to_gbq(...) # to a Google BigQuery table.
df.to_string(...) # console-friendly tabular output.
df.to_clipboard(...) # clipboard that can be pasted into Excel
- 繪制圖表:
top_10.plot(
x='iso_country',
y='airport_count',
kind='barh',
figsize=(10, 7),
title='Top 10 countries with most airports')
去看看一些很不錯(cuò)的圖表!
- 共享:
共享 Pandas 查詢結(jié)果苛败、繪圖和相關(guān)內(nèi)容的最佳媒介是 Jupyter notebooks(jupyter.org)满葛。事實(shí)上,有些人(比如杰克·范德普拉斯(Jake Vanderplas)罢屈,他太棒了)會(huì)把整本書都發(fā)布在 Jupyter notebooks 上:github.com/jakevdp/Pyt…嘀韧。
很簡(jiǎn)單就可以創(chuàng)建一個(gè)新的筆記本:
pip install jupyter
jupyter notebook
之后:
- 打開 localhost:8888
- 點(diǎn)擊
新建
,并給筆記本起個(gè)名字 - 查詢并顯示數(shù)據(jù)
- 創(chuàng)建一個(gè) GitHub 倉(cāng)庫(kù)缠捌,并添加您的筆記本到倉(cāng)庫(kù)中(后綴為 .ipynb 的文件)锄贷。
GitHub 有一個(gè)很棒的內(nèi)置查看器,可以以 Markdown 的格式顯示 Jupyter notebooks 的內(nèi)容。
現(xiàn)在谊却,你可以開始你的 Pandas 之旅了柔昼!
我希望您現(xiàn)在確信,Pandas 庫(kù)可以像您的老朋友 SQL 一樣幫助您進(jìn)行探索性數(shù)據(jù)分析炎辨,在某些情況下甚至?xí)龅酶貌锻浮J菚r(shí)候你自己動(dòng)手開始在 Pandas 里查詢數(shù)據(jù)了!