Pandas 展示
請(qǐng)看下表:
它描述了一個(gè)在線商店的不同產(chǎn)品線,共有四種不同的產(chǎn)品。與前面的例子不同,它可以用NumPy數(shù)組或Pandas DataFrame表示。但讓我們看一下它的一些常見(jiàn)操作。
1. 排序
使用Pandas按列排序更具可讀性,如下所示:
這里argsort(a[:,1])計(jì)算使a的第二列按升序排序的排列,然后a[…]相應(yīng)地對(duì)a的行重新排序。Pandas可以一步完成。
2.按多列排序
如果我們需要使用weight列來(lái)對(duì)價(jià)格列進(jìn)行排序,情況會(huì)變得更糟。這里有幾個(gè)例子來(lái)說(shuō)明我們的觀點(diǎn):
在NumPy中,我們先按重量排序,然后再按價(jià)格排序。穩(wěn)定排序算法保證第一次排序的結(jié)果不會(huì)在第二次排序期間丟失。NumPy還有其他實(shí)現(xiàn)方法,但沒(méi)有一種方法像Pandas那樣簡(jiǎn)單優(yōu)雅。
3. 添加一列
使用Pandas添加列在語(yǔ)法和架構(gòu)上要好得多。下面的例子展示了如何操作:
Pandas不需要像NumPy那樣為整個(gè)數(shù)組重新分配內(nèi)存;它只是添加了對(duì)新列的引用,并更新了列名的` registry `。
4. 快速元素搜索
在NumPy數(shù)組中,即使你查找的是第一個(gè)元素,你仍然需要與數(shù)組大小成正比的時(shí)間來(lái)查找它。使用Pandas,你可以索引你期望被查詢(xún)最多的列,并將搜索時(shí)間減少到一個(gè)常量。
index列有以下限制。
-
它需要內(nèi)存和時(shí)間來(lái)構(gòu)建。
-
它是只讀的(需要在每次追加或刪除操作后重新構(gòu)建)。
-
這些值不需要是唯一的,但是只有當(dāng)元素是唯一的時(shí)候加速才會(huì)發(fā)生。
-
它需要預(yù)熱:第一次查詢(xún)比NumPy稍慢,但后續(xù)查詢(xún)明顯快得多。
5. 按列連接(join)
如果你想從另一張表中獲取基于同一列的信息,NumPy幾乎沒(méi)有任何幫助。Pandas更好,特別是對(duì)于1:n的關(guān)系。
Pandas join具有所有熟悉的“內(nèi)”、“左”、“右”和“全外部”連接模式。
按列分組
數(shù)據(jù)分析中的另一個(gè)常見(jiàn)操作是按列分組。例如,要獲得每種產(chǎn)品的總銷(xiāo)量,你可以這樣做:
除了sum之外,Pandas還支持各種聚合函數(shù):mean、max、min、count等。
7. 數(shù)據(jù)透視表
Pandas最強(qiáng)大的功能之一是“樞軸”表。這有點(diǎn)像將多維空間投影到二維平面上。
雖然用NumPy當(dāng)然可以實(shí)現(xiàn)它,但這個(gè)功能沒(méi)有開(kāi)箱即用,盡管它存在于所有主要的關(guān)系數(shù)據(jù)庫(kù)和電子表格應(yīng)用程序(Excel,WPS)中。
Pandas用df.pivot_table將分組和旋轉(zhuǎn)結(jié)合在一個(gè)工具中。
簡(jiǎn)而言之,NumPy和Pandas的兩個(gè)主要區(qū)別如下:
現(xiàn)在,讓我們看看這些功能是否以性能損失為代價(jià)。
Pandas速度
我在Pandas的典型工作負(fù)載上對(duì)NumPy和Pandas進(jìn)行了基準(zhǔn)測(cè)試:5-100列,103- 10?行,整數(shù)和浮點(diǎn)數(shù)。下面是1行和1億行的結(jié)果:
看起來(lái)在每一次操作中,Pandas都比NumPy慢!
當(dāng)列數(shù)增加時(shí),情況不會(huì)改變(可以預(yù)見(jiàn))。至于行數(shù),依賴(lài)關(guān)系(在對(duì)數(shù)尺度下)如下所示:
對(duì)于小數(shù)組(少于100行),Pandas似乎比NumPy慢30倍,對(duì)于大數(shù)組(超過(guò)100萬(wàn)行)則慢3倍。
怎么可能呢?也許是時(shí)候提交一個(gè)功能請(qǐng)求,建議Pandas通過(guò)df.column.values.sum()重新實(shí)現(xiàn)df.column.sum()了?這里的values屬性提供了訪問(wèn)底層NumPy數(shù)組的方法,性能提升了3 ~ 30倍。
答案是否定的。Pandas在這些基本操作方面非常緩慢,因?yàn)樗_地處理了缺失值。Pandas需要NaNs (not-a-number)來(lái)實(shí)現(xiàn)所有這些類(lèi)似數(shù)據(jù)庫(kù)的機(jī)制,比如分組和旋轉(zhuǎn),而且這在現(xiàn)實(shí)世界中是很常見(jiàn)的。在Pandas中,我們做了大量工作來(lái)統(tǒng)一所有支持的數(shù)據(jù)類(lèi)型對(duì)NaN的使用。根據(jù)定義(在CPU級(jí)別上強(qiáng)制執(zhí)行),nan+anything會(huì)得到nan。所以
>>> np.sum([1, np.nan, 2])
nan
但是
>>> pd.Series([1, np.nan, 2]).sum()
3.0
一個(gè)公平的比較是使用np.nansum代替np.sum,用np.nanmean而不是np.mean等等。突然間……
對(duì)于超過(guò)100萬(wàn)個(gè)元素的數(shù)組,Pandas的速度是NumPy的1.5倍。對(duì)于較小的數(shù)組,它仍然比NumPy慢15倍,但通常情況下,無(wú)論操作在0.5 ms還是0.05 ms內(nèi)完成都沒(méi)有太大關(guān)系——無(wú)論如何它都是快速的。
最重要的是,如果您100%確定列中沒(méi)有缺失值,則使用df.column.values.sum()而不是df.column.sum()可以獲得x3-x30的性能提升。在存在缺失值的情況下,Pandas的速度相當(dāng)不錯(cuò),甚至在巨大的數(shù)組(超過(guò)10個(gè)同質(zhì)元素)方面優(yōu)于NumPy。
第二部分. Series 和 Index
Series是NumPy中的一維數(shù)組,是表示其列的DataFrame的基本組成部分。盡管與DataFrame相比,它的實(shí)際重要性正在降低(你可以在不知道Series是什么的情況下完美地解決許多實(shí)際問(wèn)題),但如果不首先學(xué)習(xí)Series和Index,你可能很難理解DataFrame是如何工作的。
在內(nèi)部,Series將值存儲(chǔ)在普通的NumPy vector中。因此,它繼承了它的優(yōu)點(diǎn)(緊湊的內(nèi)存布局、快速的隨機(jī)訪問(wèn))和缺點(diǎn)(類(lèi)型同質(zhì)、緩慢的刪除和插入)。最重要的是,Series允許使用類(lèi)似于字典的結(jié)構(gòu)index通過(guò)label訪問(wèn)它的值。標(biāo)簽可以是任何類(lèi)型(通常是字符串和時(shí)間戳)。它們不必是唯一的,但唯一性是提高查找速度所必需的,許多操作都假定唯一性。
如你所見(jiàn),現(xiàn)在每個(gè)元素都可以通過(guò)兩種替代方式尋址:通過(guò)` label `(=使用索引)和通過(guò)` position `(=不使用索引):
按“位置”尋址有時(shí)被稱(chēng)為“位置索引”,這只是增加了混淆。
一對(duì)方括號(hào)是不夠的。特別是:
-
S[2:3]不是解決元素2最方便的方式
-
如果名稱(chēng)恰好是整數(shù),s[1:3]就會(huì)產(chǎn)生歧義。它可能意味著名稱(chēng)1到3包含或位置索引1到3不包含。
為了解決這些問(wèn)題,Pandas還有兩種“風(fēng)格”的方括號(hào),你可以在下面看到:
.loc總是使用標(biāo)號(hào),并且包含間隔的兩端。
.iloc總是使用“位置索引”并排除右端。
使用方括號(hào)而不是圓括號(hào)的目的是為了訪問(wèn)Python的切片約定:你可以使用單個(gè)或雙冒號(hào),其含義是熟悉的startstep。像往常一樣,缺少開(kāi)始(結(jié)束)意味著從序列的開(kāi)始(到結(jié)束)。step參數(shù)允許使用s.iloc[::2]引用偶數(shù)行,并使用s['Paris':'Oslo':-1]以相反的順序獲取元素。
它們還支持布爾索引(使用布爾數(shù)組進(jìn)行索引),如下圖所示:
你可以在下圖中看到它們?nèi)绾沃С謄 fancy indexing `(用整數(shù)數(shù)組進(jìn)行索引):
Series最糟糕的地方在于它的視覺(jué)表現(xiàn):出于某種原因,它沒(méi)有一個(gè)很好的富文本外觀,所以與DataFrame相比,它感覺(jué)像是二等公民:
我對(duì)這個(gè)Series做了補(bǔ)丁,讓它看起來(lái)更好,如下所示:
垂直線表示這是一個(gè)Series,而不是一個(gè)DataFrame。Footer在這里被禁用了,但它可以用于顯示dtype,特別是分類(lèi)類(lèi)型。
您還可以使用pdi.sidebyside(obj1, obj2,…)并排顯示多個(gè)Series或dataframe:
pdi(代表pandas illustrated)是github上的一個(gè)開(kāi)源庫(kù),具有本文所需的這個(gè)和其他功能。要使用它,就要寫(xiě)
pip install pandas-illustrated
索引(Index)
負(fù)責(zé)通過(guò)標(biāo)簽獲取元素的對(duì)象稱(chēng)為index。它非常快:無(wú)論你有5行還是50億行,你都可以在常量時(shí)間內(nèi)獲取一行數(shù)據(jù)。
指數(shù)是一個(gè)真正的多態(tài)生物。默認(rèn)情況下,當(dāng)創(chuàng)建一個(gè)沒(méi)有索引的序列(或DataFrame)時(shí),它會(huì)初始化為一個(gè)惰性對(duì)象,類(lèi)似于Python的range()。和range一樣,幾乎不使用任何內(nèi)存,并且與位置索引無(wú)法區(qū)分。讓我們用下面的代碼創(chuàng)建一個(gè)包含一百萬(wàn)個(gè)元素的序列:
>>> s = pd.Series(np.zeros(10**6))
>>> s.index
RangeIndex(start=0, stop=1000000, step=1)
>>> s.index.memory_usage() # in bytes
128 # the same as for Series([0.])
現(xiàn)在,如果我們刪除一個(gè)元素,索引隱式地轉(zhuǎn)換為類(lèi)似于dict的結(jié)構(gòu),如下所示:
>>> s.drop(1, inplace=True)
>>> s.index
Int64Index([ 0, 2, 3, 4, 5, 6, 7,
...
999993, 999994, 999995, 999996, 999997, 999998, 999999],
dtype='int64', length=999999)
>>> s.index.memory_usage()
7999992
該結(jié)構(gòu)消耗8Mb內(nèi)存!為了擺脫它,回到輕量級(jí)的類(lèi)range結(jié)構(gòu),添加如下代碼:
>>> s.reset_index(drop=True, inplace=True)
>>> s.index
RangeIndex(start=0, stop=999999, step=1)
>>> s.index.memory_usage()
128
如果你不熟悉Pandas,你可能想知道為什么Pandas自己沒(méi)有做到這一點(diǎn)?好吧,對(duì)于非數(shù)字標(biāo)簽,有一點(diǎn)很明顯:為什么(以及如何)Pandas在刪除一行后,會(huì)重新標(biāo)記所有后續(xù)的行?對(duì)于數(shù)值型標(biāo)簽,答案就有點(diǎn)復(fù)雜了。
首先,正如我們已經(jīng)看到的,Pandas允許您純粹按位置引用行,因此,如果您想在刪除第3行之后定位第5行,則可以無(wú)需重新索引(這就是iloc的作用)。
其次,保留原始標(biāo)簽是一種與過(guò)去時(shí)刻保持聯(lián)系的方法,就像“保存游戲”按鈕一樣。假設(shè)您有一個(gè)100x1000000的大表,需要查找一些數(shù)據(jù)。你正在一個(gè)接一個(gè)地進(jìn)行幾次查詢(xún),每次都縮小了搜索范圍,但只查看了一小部分列,因?yàn)橥瑫r(shí)查看數(shù)百個(gè)字段是不切實(shí)際的。現(xiàn)在您已經(jīng)找到感興趣的行,您希望在原始表中查看有關(guān)它們的所有信息。數(shù)字索引可以幫助您立即獲得它,而無(wú)需任何額外的努力。
一般來(lái)說(shuō),在索引中保持值的唯一性是一個(gè)好主意。例如,在索引中存在重復(fù)值時(shí),查找速度不會(huì)得到提升。Pandas不像關(guān)系型數(shù)據(jù)庫(kù)那樣有“唯一約束”(該功能仍然是實(shí)驗(yàn)性的),但它有檢查索引中的值是否唯一的函數(shù),并以各種方式消除重復(fù)。
有時(shí),一列不足以唯一標(biāo)識(shí)一行。例如,同一個(gè)名字的城市有時(shí)會(huì)碰巧出現(xiàn)在不同的國(guó)家,甚至是同一個(gè)國(guó)家的不同地區(qū)。所以(城市,州)是一個(gè)比城市更好的標(biāo)識(shí)一個(gè)地方的候選者。在數(shù)據(jù)庫(kù)中,這被稱(chēng)為“復(fù)合主鍵”。在Pandas中,它被稱(chēng)為多索引(參見(jiàn)下面的第4部分),索引中的每一列都被稱(chēng)為“級(jí)別”。
索引的另一個(gè)重要特性是不可變。與DataFrame中的普通列不同,你不能就地更改它。索引中的任何更改都涉及從舊索引中獲取數(shù)據(jù),修改它,并將新數(shù)據(jù)作為新索引重新附加。通常情況下,它是透明的,這就是為什么不能直接寫(xiě)df.City.name = ' city ',而必須寫(xiě)一個(gè)不那么明顯的df.rename(columns={' A ': ' A '}, inplace=True)
Index有一個(gè)名稱(chēng)(在MultiIndex的情況下,每個(gè)級(jí)別都有一個(gè)名稱(chēng))。不幸的是,這個(gè)名稱(chēng)在Pandas中沒(méi)有得到充分使用。一旦你在索引中包含了這一列,就不能再使用df了。不再使用列名表示法,并且必須恢復(fù)為可讀性較差的df。指數(shù)還是更通用的df。loc對(duì)于多索引,情況更糟。一個(gè)明顯的例外是df。Merge -你可以通過(guò)名稱(chēng)指定要合并的列,無(wú)論它是否在索引中。
同樣的索引機(jī)制用于標(biāo)記dataframe的行和列,以及序列。
按值查找元素
Series內(nèi)部由一個(gè)NumPy數(shù)組和一個(gè)類(lèi)似數(shù)組的結(jié)構(gòu)index組成,如下所示:
Index提供了一種通過(guò)標(biāo)簽查找值的方便方法。那么如何通過(guò)值查找標(biāo)簽?zāi)?
s.index[s.tolist().find(x)] # faster for len(s) < 1000
s.index[np.where(s.values==x)[0][0]] # faster for len(s) > 1000
我編寫(xiě)了find()和findall()兩個(gè)簡(jiǎn)單的封裝器,它們運(yùn)行速度快(因?yàn)樗鼈儠?huì)根據(jù)序列的大小自動(dòng)選擇實(shí)際的命令),而且使用起來(lái)更方便。代碼如下所示:
>>> import pdi
>>> pdi.find(s, 2)
'penguin'
>>> pdi.findall(s, 4)
Index(['cat', 'dog'], dtype='object')
缺失值
Pandas開(kāi)發(fā)人員特別關(guān)注缺失值。通常,你通過(guò)向read_csv提供一個(gè)標(biāo)志來(lái)接收一個(gè)帶有NaNs的dataframe。否則,可以在構(gòu)造函數(shù)或賦值運(yùn)算符中使用None(盡管不同數(shù)據(jù)類(lèi)型的實(shí)現(xiàn)略有不同,但它仍然有效)。這張圖片有助于解釋這個(gè)概念:
你可以使用NaNs做的第一件事是了解你是否有NaNs。從上圖可以看出,isna()生成了一個(gè)布爾數(shù)組,而.sum()給出了缺失值的總數(shù)。
現(xiàn)在你知道了它們的存在,你可以選擇用常量值填充它們或通過(guò)插值來(lái)一次性刪除它們,如下所示:
另一方面,你可以繼續(xù)使用它們。大多數(shù)Pandas函數(shù)會(huì)很高興地忽略缺失值,如下圖所示:
更高級(jí)的函數(shù)(median、rank、quantile等)也可以做到這一點(diǎn)。
算術(shù)運(yùn)算與索引對(duì)齊:
如果索引中存在非唯一值,則結(jié)果不一致。不要對(duì)索引不唯一的序列使用算術(shù)運(yùn)算。
比較
比較有缺失值的數(shù)組可能會(huì)比較棘手。下面是一個(gè)例子:
>>> np.all(pd.Series([1., None, 3.]) ==
pd.Series([1., None, 3.]))
False
>>> np.all(pd.Series([1, None, 3], dtype='Int64') ==
pd.Series([1, None, 3], dtype='Int64'))
True
>>> np.all(pd.Series(['a', None, 'c']) ==
pd.Series(['a', None, 'c']))
False
為了正確地比較nan,需要用數(shù)組中一定沒(méi)有的元素替換nan。例如,使用-1或∞:
>>> np.all(s1.fillna(np.inf) == s2.fillna(np.inf)) # works for all dtypes
True
或者,更好的做法是使用NumPy或Pandas的標(biāo)準(zhǔn)比較函數(shù):
>>> s = pd.Series([1., None, 3.])
>>> np.array_equal(s.values, s.values, equal_nan=True)
True
>>> len(s.compare(s)) == 0
True
這里,compare函數(shù)返回一個(gè)差異列表(實(shí)際上是一個(gè)DataFrame), array_equal則直接返回一個(gè)布爾值。
當(dāng)比較混合類(lèi)型的DataFrames時(shí),NumPy比較失敗(issue #19205),而Pandas工作得很好。如下所示:
>>> df = pd.DataFrame({'a': [1., None, 3.], 'b': ['x', None, 'z']})
>>> np.array_equal(df.values, df.values, equal_nan=True)
TypeError
<...>
>>> len(df.compare(df)) == 0
True
追加、插入、刪除
雖然Series對(duì)象被認(rèn)為是size不可變的,但它可以在原地追加、插入和刪除元素,但所有這些操作都是:
-
慢,因?yàn)樗鼈冃枰獮檎麄€(gè)對(duì)象重新分配內(nèi)存和更新索引。
-
非常不方便。
下面是插入值的一種方式和刪除值的兩種方式:
第二種刪除值的方法(通過(guò)drop)比較慢,并且在索引中存在非唯一值時(shí)可能會(huì)導(dǎo)致復(fù)雜的錯(cuò)誤。
Pandas有df.insert方法,但它只能將列(而不是行)插入到dataframe中(并且對(duì)series不起作用)。
添加和插入的另一種方法是使用iloc對(duì)DataFrame進(jìn)行切片,應(yīng)用必要的轉(zhuǎn)換,然后使用concat將其放回。我實(shí)現(xiàn)了一個(gè)名為insert的函數(shù),可以自動(dòng)執(zhí)行這個(gè)過(guò)程:
注意(就像在df.insert中一樣)插入位置由位置0<=i<=len(s)指定,而不是索引中元素的標(biāo)簽。如下所示:
要按元素的名稱(chēng)插入,可以合并pdi。用pdi查找。插入,如下所示:
請(qǐng)注意,unlikedf.insert、pdi.insert返回一個(gè)副本,而不是原地修改Series/DataFrame
統(tǒng)計(jì)數(shù)據(jù)
Pandas提供了全方位的統(tǒng)計(jì)函數(shù)。它們可以讓您了解百萬(wàn)元素序列或DataFrame中的內(nèi)容,而無(wú)需手動(dòng)滾動(dòng)數(shù)據(jù)。
所有Pandas統(tǒng)計(jì)函數(shù)都會(huì)忽略NaNs,如下所示:
注意,Pandas std給出的結(jié)果與NumPy std不同,如下所示:
>>> pd.Series([1, 2]).std()
0.7071067811865476
>>> pd.Series([1, 2]).values.std()
0.5
這是因?yàn)镹umPy std默認(rèn)使用N作為分母,而Pandas std默認(rèn)使用N-1作為分母。兩個(gè)std都有一個(gè)名為ddof (` delta degrees of freedom `)的參數(shù),NumPy默認(rèn)為0,Pandas默認(rèn)為1,這可以使結(jié)果一致。N-1是你通常想要的值(在均值未知的情況下估計(jì)樣本的偏差)。這里有一篇維基百科的文章詳細(xì)介紹了貝塞爾的修正。
由于序列中的每個(gè)元素都可以通過(guò)標(biāo)簽或位置索引訪問(wèn),因此argmin (argmax)有一個(gè)姐妹函數(shù)idxmin (idxmax),如下圖所示:
下面是Pandas的自描述統(tǒng)計(jì)函數(shù)供參考:
-
std:樣本標(biāo)準(zhǔn)差
-
var,無(wú)偏方差
-
sem,均值的無(wú)偏標(biāo)準(zhǔn)誤差
-
quantile分位數(shù),樣本分位數(shù)(s.quantile(0.5)≈s.median())
-
oode是出現(xiàn)頻率最高的值
-
默認(rèn)為Nlargest和nsmallest,按出現(xiàn)順序排列
-
diff,第一個(gè)離散差分
-
cumsum 和 cumprod、cumulative sum和product
-
cummin和cummax,累積最小值和最大值
以及一些更專(zhuān)業(yè)的統(tǒng)計(jì)函數(shù):
-
pct_change,當(dāng)前元素與前一個(gè)元素之間的變化百分比
-
skew偏態(tài),無(wú)偏態(tài)(三階矩)
-
kurt或kurtosis,無(wú)偏峰度(四階矩)
-
cov、corr和autocorr、協(xié)方差、相關(guān)和自相關(guān)
-
rolling滾動(dòng)窗口、加權(quán)窗口和指數(shù)加權(quán)窗口
重復(fù)數(shù)據(jù)
在檢測(cè)和處理重復(fù)數(shù)據(jù)時(shí)需要特別小心,如下圖所示:
drop_duplicates和duplication可以保留最后一次出現(xiàn)的副本,而不是第一次出現(xiàn)的副本。
請(qǐng)注意,s.a uint()比np快。唯一性(O(N) vs O(NlogN)),它會(huì)保留順序,而不會(huì)返回排序結(jié)果。獨(dú)特的。
缺失值被視為普通值,有時(shí)可能會(huì)導(dǎo)致令人驚訝的結(jié)果。
如果你想排除nan,需要顯式地這樣做。在這個(gè)例子中,是s.l opdropna().is_unique == True。
還有一類(lèi)單調(diào)函數(shù),它們的名字是自描述的:
-
s.is_monotonic_increasing ()
-
s.is_monotonic_decreasing ()
-
s._strict_monotonic_increasing ()
-
s._string_monotonic_decreasing ()
-
s.is_monotonic()。這是意料之外的,出于某種原因,這是s.is_monotonic_increasing()。它只對(duì)單調(diào)遞減序列返回False。
分組
在數(shù)據(jù)處理中,一個(gè)常見(jiàn)的操作是計(jì)算一些統(tǒng)計(jì)量,不是針對(duì)整個(gè)數(shù)據(jù)集,而是針對(duì)其中的某些組。第一步是通過(guò)提供將一系列(或一個(gè)dataframe)分解為組的標(biāo)準(zhǔn)來(lái)定義一個(gè)“智能對(duì)象”。這個(gè)`智能對(duì)象`沒(méi)有立即的表示,但可以像Series一樣查詢(xún)它,以獲得每個(gè)組的某個(gè)屬性,如下圖所示:
在這個(gè)例子中,我們根據(jù)數(shù)值除以10的整數(shù)部分將序列分成三組。對(duì)于每個(gè)組,我們請(qǐng)求每個(gè)組中元素的和、元素的數(shù)量以及平均值。
除了這些聚合函數(shù),您還可以根據(jù)特定元素在組中的位置或相對(duì)值訪問(wèn)它們。如下所示:
你也可以使用g.ag (['min', 'max'])一次調(diào)用計(jì)算多個(gè)函數(shù),或者使用g.c describe()一次顯示一堆統(tǒng)計(jì)函數(shù)。
如果這些還不夠,你還可以通過(guò)自己的Python函數(shù)傳遞數(shù)據(jù)。它可以是:
一個(gè)函數(shù)f,它接受一個(gè)組x(一個(gè)Series對(duì)象)并生成一個(gè)值(例如sum())與g.eapply (f)一起使用。
一個(gè)函數(shù)f,它接受一個(gè)組x(一個(gè)Series對(duì)象),并與g.transform(f)生成一個(gè)大小與x相同的Series對(duì)象(例如cumsum())。
在上面的例子中,輸入數(shù)據(jù)是有序的。groupby不需要這樣做。實(shí)際上,如果分組中的元素不是連續(xù)存儲(chǔ)的,它也同樣有效,因此它更接近于collections.defaultdict,而不是itertools.groupby。它總是返回一個(gè)沒(méi)有重復(fù)項(xiàng)的索引。
與defaultdict和關(guān)系數(shù)據(jù)庫(kù)GROUP BY子句不同,Pandas groupby按組名對(duì)結(jié)果進(jìn)行排序??梢杂胹ort=False來(lái)禁用它。
免責(zé)聲明:實(shí)際上,g.apply(f)比上面描述的更通用:
-
如果f(x)返回與x大小相同的序列,它可以模擬transform
-
如果f(x)返回一系列不同大小或不同的dataframe,則會(huì)得到一個(gè)具有相應(yīng)多索引的序列。
但文檔警告說(shuō),這些使用方法可能比相應(yīng)的transform和agg方法慢,所以要小心。
第三部分. DataFrames
Pandas的主要數(shù)據(jù)結(jié)構(gòu)是DataFrame。它將一個(gè)二維數(shù)組與它的行和列的標(biāo)簽捆綁在一起。它由一系列對(duì)象組成(具有共享索引),每個(gè)對(duì)象表示一列,可能具有不同的dtype。
讀寫(xiě)CSV文件
構(gòu)造DataFrame的一種常用方法是讀取csv(逗號(hào)分隔值)文件,如下圖所示:
pd.read_csv()函數(shù)是一個(gè)完全自動(dòng)化且可瘋狂定制的工具。如果你只想學(xué)習(xí)Pandas的一件事,那就學(xué)習(xí)使用read_csv——它會(huì)有回報(bào)的:)。
下面是一個(gè)解析非標(biāo)準(zhǔn)的.csv文件的例子:
以及一些簡(jiǎn)要描述:
因?yàn)镃SV沒(méi)有嚴(yán)格的規(guī)范,所以有時(shí)需要一些試錯(cuò)才能正確地閱讀它。read_csv最酷的地方在于它會(huì)自動(dòng)檢測(cè)很多東西:
-
列名和類(lèi)型
-
布爾值的表示
-
缺失值的表示等。
與其他自動(dòng)化一樣,你最好確保它做了正確的事情。如果在Jupyter單元中簡(jiǎn)單地編寫(xiě)df的結(jié)果碰巧太長(zhǎng)(或太不完整),您可以嘗試以下操作:
-
df.head(5)或df[:5]顯示前5行
-
df.dtypes返回列的類(lèi)型
-
df.shape返回行數(shù)和列數(shù)
-
Df.info()匯總所有相關(guān)信息
將一列或幾列設(shè)置為索引是一個(gè)好主意。下圖展示了這個(gè)過(guò)程:
Index在Pandas中有很多用途:
-
算術(shù)運(yùn)算按索引對(duì)齊
-
它使按該列進(jìn)行的查找更快,等等。
所有這些都是以較高的內(nèi)存消耗和不太明顯的語(yǔ)法為代價(jià)的。
構(gòu)建DataFrame
另一種選擇是從內(nèi)存中已經(jīng)存儲(chǔ)的數(shù)據(jù)中構(gòu)建一個(gè)dataframe。它的構(gòu)造函數(shù)非常全能,可以轉(zhuǎn)換(或包裝)任何類(lèi)型的數(shù)據(jù):
在第一種情況下,在沒(méi)有行標(biāo)簽的情況下,Pandas用連續(xù)的整數(shù)標(biāo)記行。在第二種情況下,它對(duì)行和列都進(jìn)行了相同的操作。為Pandas提供列的名稱(chēng)總是一個(gè)好主意,而不是整數(shù)標(biāo)簽(使用columns參數(shù)),有時(shí)也可以提供行(使用index參數(shù),盡管rows聽(tīng)起來(lái)可能更直觀)。這張圖片會(huì)有幫助:
不幸的是,無(wú)法在DataFrame構(gòu)造函數(shù)中為索引列設(shè)置名稱(chēng),所以唯一的選擇是手動(dòng)指定,例如,df.index.name = '城市名稱(chēng)'
下一種方法是使用NumPy向量組成的字典或二維NumPy數(shù)組構(gòu)造一個(gè)DataFrame:
請(qǐng)注意,在第二種情況下,人口數(shù)量的值被轉(zhuǎn)換為浮點(diǎn)數(shù)。實(shí)際上,它在之前的構(gòu)建NumPy數(shù)組時(shí)就發(fā)生過(guò)。這里需要注意的另一件事是,從2D NumPy數(shù)組構(gòu)建dataframe默認(rèn)是視圖。這意味著改變?cè)紨?shù)組中的值會(huì)改變dataframe,反之亦然。另外,它節(jié)省了內(nèi)存。
第一種情況(NumPy向量組成的字典)也可以啟用這種模式,設(shè)置copy=False即可。不過(guò),它非常脆弱。簡(jiǎn)單的操作就可以把它變成副本而不需要通知。
另外兩個(gè)(不太有用的)創(chuàng)建DataFrame的選項(xiàng)是:
-
從一個(gè)dict列表(其中每個(gè)dict表示一行,其鍵是列名,其值是相應(yīng)的單元格值)
-
來(lái)自由Series組成的dict(其中每個(gè)Series表示一列;默認(rèn)情況下,可以讓它返回一個(gè)copy=False的視圖)。
如果你“動(dòng)態(tài)”注冊(cè)流數(shù)據(jù),最好的選擇是使用列表的dict或列表的列表,因?yàn)镻ython會(huì)透明地在列表末尾預(yù)分配空間,以便快速追加。NumPy數(shù)組和Pandas dataframes都不能做到這一點(diǎn)。另一種可能性(如果你事先知道行數(shù))是用DataFrame(np.zeros)之類(lèi)的東西手動(dòng)預(yù)分配內(nèi)存。
DataFrames的基本操作
DataFrame最好的地方(在我看來(lái))是你可以:
-
輕松訪問(wèn)其列,如d.area返回列值(或者df[' Area ']——適用于包含空格的列名)
-
將列作為自變量進(jìn)行操作,例如使用afterdf. population /= 10**6人口以百萬(wàn)計(jì)存儲(chǔ),下面的命令根據(jù)現(xiàn)有列中的值創(chuàng)建一個(gè)名為` density `的新列。更多信息見(jiàn)下圖:
注意,創(chuàng)建新列時(shí),即使列名中不包含空格,也必須使用方括號(hào)。
此外,你可以對(duì)不同dataframe中的列使用算術(shù)操作,只要它們的行具有有意義的標(biāo)簽,如下所示:
索引DataFrames
正如我們?cè)诒鞠盗兄幸呀?jīng)看到的,普通的方括號(hào)不足以滿足索引的所有需求。你不能通過(guò)名稱(chēng)訪問(wèn)行,不能通過(guò)位置索引訪問(wèn)不相交的行,你甚至不能引用單個(gè)單元格,因?yàn)閐f['x', 'y']是為多索引保留的!
為了滿足這些需求,dataframes,就像series一樣,有兩種可選的索引模式:按標(biāo)簽索引的loc和按位置索引的iloc。
在Pandas中,引用多行/多列是一個(gè)副本,而不是視圖。但它是一種特殊的復(fù)制,允許賦值作為一個(gè)整體:
-
df.loc[‘a(chǎn)’]=10 works (一行作為一個(gè)整體是一個(gè)可寫(xiě)的)
-
df.loc[‘a(chǎn)’][‘A’]=10 works (元素訪問(wèn)傳播到原始df)
-
df.loc[‘a(chǎn)’:’b’] = 10 works (assigning to a subar將整個(gè)作品賦值給一個(gè)子數(shù)組)
-
df.loc[‘a(chǎn)’:’b’][‘A’] = 10 doesn’t (對(duì)其元素賦值不會(huì)).
在最后一種情況下,該值只會(huì)被設(shè)置在切片的副本上,而不會(huì)反映在原始df上(會(huì)相應(yīng)地顯示一個(gè)警告)。
根據(jù)不同的背景,有不同的解決方案:
-
你想要改變?cè)嫉膁f。然后使用df。loc[' a': ' b ', ' a'] = 10
-
你故意創(chuàng)建了一個(gè)副本,然后想要處理這個(gè)副本:df1 = df.loc[' a ': ' b '];df1[' A ']=10 # SettingWithCopy warning要在這種情況下消除警告,請(qǐng)使其成為一個(gè)真正的副本:df1 = df.loc[' A ': ' b '].copy();df1 [A] = 10
Pandas還支持一種方便的NumPy語(yǔ)法來(lái)進(jìn)行布爾索引。
當(dāng)使用多個(gè)條件時(shí),必須將它們括起來(lái),如下所示:
當(dāng)你期望返回一個(gè)值時(shí),需要特別注意。
因?yàn)榭赡苡卸嘈衅ヅ錀l件,所以loc返回一個(gè)序列。要從中得到標(biāo)量值,你可以使用:
-
float(s)或更通用的s.e item(),除非序列中只有一個(gè)值,否則都會(huì)引發(fā)ValueError
S.iloc[0],僅在沒(méi)有找到時(shí)引發(fā)異常;此外,它是唯一支持賦值的函數(shù):df[…].Iloc[0] = 100,但當(dāng)你想修改所有匹配時(shí),肯定不需要它:df[…]= 100。
或者,你可以使用基于字符串的查詢(xún):
-
df.query (' name = =“Vienna”)
df.query('population>1e6 and area<1000')它們更短,適合多索引,并且邏輯操作符優(yōu)先于比較操作符(=需要更少的括號(hào)),但它們只能按行過(guò)濾,并且不能通過(guò)它們修改Dataframe。
幾個(gè)第三方庫(kù)允許你使用SQL語(yǔ)法直接查詢(xún)dataframe (duckdb),或者通過(guò)將dataframe復(fù)制到SQLite并將結(jié)果包裝回Pandas objects (pandasql)來(lái)間接查詢(xún)dataframe。不出所料,直接法更快。
DataFrame算術(shù)
你可以對(duì)dataframes、series和它們的組合應(yīng)用普通操作,如加、減、乘、除、求模、冪等。
所有的算術(shù)運(yùn)算都是根據(jù)行標(biāo)簽和列標(biāo)簽對(duì)齊的:
在dataframe和Series之間的混合操作中,Series(天知道為什么)表現(xiàn)得(和廣播)像一個(gè)行向量,并相應(yīng)地對(duì)齊:
可能是為了與列表和一維NumPy向量保持一致(它們不按標(biāo)簽對(duì)齊,并被認(rèn)為是一個(gè)簡(jiǎn)單的二維NumPy數(shù)組的DataFrame):
因此,在不太幸運(yùn)(也是最常見(jiàn)的!)的情況下,將一個(gè)dataframe除以列向量序列,你必須使用方法而不是操作符,如下所示:
由于這個(gè)有問(wèn)題的決定,每當(dāng)你需要在dataframe和列式序列之間執(zhí)行混合操作時(shí),你必須在文檔中查找它(或記住它):
結(jié)合DataFrames
Pandas有三個(gè)函數(shù),concat、merge和join,它們做同樣的事情:將來(lái)自多個(gè)dataframe的信息合并為一個(gè)。但是每個(gè)工具的實(shí)現(xiàn)方式都略有不同,因?yàn)樗鼈兪菫椴煌挠美可矶ㄖ频摹?/span>
垂直疊加
這可能是將兩個(gè)或多個(gè)dataframe合并為一個(gè)的最簡(jiǎn)單方法:您獲取第一個(gè)dataframe中的行,并將第二個(gè)dataframe中的行追加到底部。為了使其工作,這兩個(gè)dataframe需要(大致)具有相同的列。這類(lèi)似于NumPy中的vstack,正如你在圖像中所看到的:
索引中有重復(fù)的值是不好的。你可能會(huì)遇到各種各樣的問(wèn)題(參見(jiàn)下面的` drop `示例)。即使你不關(guān)心索引,也要盡量避免出現(xiàn)重復(fù)的值:
-
要么使用reset_index=True參數(shù)
-
調(diào)用df.reset_index(drop=True)將行從0重新索引到len(df)-1,
-
使用keys參數(shù)可以解決MultiIndex的二義性(見(jiàn)下文)。
如果dataframe的列不能完美匹配(不同的順序在這里不計(jì)算在內(nèi)),Pandas可以取列的交集(默認(rèn)值kind='inner ')或插入nan來(lái)標(biāo)記缺失值(kind='outer'):
水平疊加
concat也可以執(zhí)行“水平”堆疊(類(lèi)似于NumPy中的hstack):
join比concat更可配置:特別是,它有五種連接模式,而concat只有兩種。詳情請(qǐng)參閱下面的“1:1關(guān)系連接”部分。
基于多指數(shù)的數(shù)據(jù)疊加
如果行標(biāo)簽和列標(biāo)簽一致,concat可以執(zhí)行與垂直堆疊類(lèi)似的多索引(就像NumPy中的dstack):
如果行和/或列部分重疊,Pandas將相應(yīng)地對(duì)齊名稱(chēng),這很可能不是你想要的。下面的圖表可以幫助你將這個(gè)過(guò)程可視化:
一般來(lái)說(shuō),如果標(biāo)簽重疊,這意味著dataframe在某種程度上彼此相關(guān),實(shí)體之間的關(guān)系最好使用關(guān)系數(shù)據(jù)庫(kù)的術(shù)語(yǔ)來(lái)描述。
1:1 連接的關(guān)系
當(dāng)同一組對(duì)象的信息存儲(chǔ)在幾個(gè)不同的DataFrame中時(shí),你希望將它們合并為一個(gè)DataFrame。
如果要合并的列不在索引中,則使用merge。
它所做的第一件事是丟棄索引中的任何內(nèi)容。然后執(zhí)行聯(lián)結(jié)操作。最后,將結(jié)果從0重新編號(hào)為n-1。
如果列已經(jīng)在索引中,則可以使用join(這只是merge的別名,將left_index或right_index設(shè)置為T(mén)rue,并設(shè)置不同的默認(rèn)值)。
從這個(gè)簡(jiǎn)化的例子中可以看出(參見(jiàn)上面的全外連接),與關(guān)系型數(shù)據(jù)庫(kù)相比,Pandas對(duì)行順序的處理相當(dāng)輕松。左外聯(lián)結(jié)和右外聯(lián)結(jié)比內(nèi)外聯(lián)結(jié)更容易預(yù)測(cè)(至少在需要合并的列中有重復(fù)值之前是這樣)。因此,如果你想保證行順序,就必須顯式地對(duì)結(jié)果進(jìn)行排序。
1:n 連接的關(guān)系
這是數(shù)據(jù)庫(kù)設(shè)計(jì)中使用最廣泛的關(guān)系,表A中的一行(例如“State”)可以與表B中的幾行(例如城市)相關(guān)聯(lián),但表B中的每一行只能與表A中的一行相關(guān)聯(lián)(即一個(gè)城市只能處于一種狀態(tài),但一個(gè)狀態(tài)由多個(gè)城市組成)。
就像1:1關(guān)系一樣,在Pandas中連接一對(duì)1:n相關(guān)的表,你有兩種選擇。如果要合并的列或者不在索引中,并且可以丟棄碰巧在兩張表的索引中都存在的列,則使用merge。下面的例子會(huì)有所幫助:
正如我們已經(jīng)看到的,merge對(duì)行順序的處理沒(méi)有Postgres嚴(yán)格:所有聲明的語(yǔ)句,保留的鍵順序只適用于left_index=True和/或right_index=True(這就是join的別名),并且只在要合并的列中沒(méi)有重復(fù)值的情況下。這就是為什么join有一個(gè)sort參數(shù)。
現(xiàn)在,如果要合并的列已經(jīng)在右側(cè)DataFrame的索引中,可以使用join(或者merge with right_index=True,這是完全相同的事情):
這次Pandas保留了左DataFrame的索引值和行順序。
注意:注意,如果第二個(gè)表有重復(fù)的索引值,你最終將在結(jié)果中得到重復(fù)的索引值,即使左表索引是唯一的!
有時(shí),合并的dataframe具有同名的列。merge和join都有解決二義性的方法,但語(yǔ)法略有不同(默認(rèn)情況下merge會(huì)用` _x `, ` _y `來(lái)解決,而join會(huì)拋出異常),如下圖所示:
總結(jié):
-
合并非索引列上的連接,連接要求列被索引
-
merge丟棄左DataFrame的索引,join保留它
-
默認(rèn)情況下,merge執(zhí)行內(nèi)聯(lián)結(jié),join執(zhí)行左外聯(lián)結(jié)
-
合并不保持行順序
-
Join可以保留它們(有一些限制)
-
join是合并的別名,left_index=True和/或right_index=True
多個(gè)連接
如上所述,當(dāng)對(duì)兩個(gè)dataframe(如df.join(df1))運(yùn)行join時(shí),它充當(dāng)了合并的別名。但是join也有一個(gè)` multiple join `模式,它只是concat(axis=1)的別名。
與普通模式相比,該模式有一些限制:
-
它沒(méi)有提供解析重復(fù)列的方法
-
它只適用于1:1關(guān)系(索引到索引連接)。
因此,多個(gè)1:n關(guān)系應(yīng)該一個(gè)接一個(gè)地連接。倉(cāng)庫(kù)` panda -illustrated `也提供了一個(gè)輔助方法,如下所示:
pdi.join是Join的一個(gè)簡(jiǎn)單包裝器,它接受on、how和后綴參數(shù),以便您可以在一個(gè)命令中進(jìn)行多個(gè)聯(lián)結(jié)。與原始的關(guān)聯(lián)操作一樣,關(guān)聯(lián)的是屬于第一個(gè)DataFrame的列,其他DataFrame根據(jù)它們的索引進(jìn)行關(guān)聯(lián)操作。
插入和刪除
由于DataFrame是列的集合,因此將這些操作應(yīng)用到行上比應(yīng)用到列上更容易。例如,插入一列總是在原地完成,而插入一行總是會(huì)生成一個(gè)新的DataFrame,如下所示:
刪除列通常不用擔(dān)心,除了del df['D']和del df。D則沒(méi)有(Python級(jí)別的限制)。
使用drop刪除行非常慢,如果原始標(biāo)簽不是唯一的,可能會(huì)導(dǎo)致復(fù)雜的bug。下圖將幫助解釋這個(gè)概念:
一種解決方案是使用ignore_index=True,它告訴concat在連接后重置行名稱(chēng):
在這種情況下,將name列設(shè)置為索引將有所幫助。但對(duì)于更復(fù)雜的濾波器,它不會(huì)。
另一種快速、通用、甚至可以處理重復(fù)行名的解決方案是索引而不是刪除。為了避免顯式地否定條件,我寫(xiě)了一個(gè)(只有一行代碼的)自動(dòng)化程序。
分組
這個(gè)操作已經(jīng)在Series部分詳細(xì)描述過(guò)了。但是DataFrame的groupby在此基礎(chǔ)上有一些特定的技巧。
首先,你可以使用一個(gè)名稱(chēng)來(lái)指定要分組的列,如下圖所示:
如果沒(méi)有as_index=False, Pandas將進(jìn)行分組的列指定為索引。如果這不是我們想要的,可以使用reset_index()或指定as_index=False。
通常,數(shù)據(jù)框中的列比你想在結(jié)果中看到的多。默認(rèn)情況下,Pandas會(huì)對(duì)所有遠(yuǎn)端可求和的東西進(jìn)行求和,因此你需要縮小選擇范圍,如下所示:
注意,當(dāng)對(duì)單個(gè)列求和時(shí),你將得到一個(gè)Series而不是DataFrame。如果出于某種原因,你想要一個(gè)DataFrame,你可以:
-
使用雙括號(hào):df.groupby('product')[['quantity']].sum()
-
顯式轉(zhuǎn)換:df.groupby('product')['quantity'].sum().to_frame()
切換到數(shù)值索引也會(huì)創(chuàng)建一個(gè)DataFrame:
-
df.groupby('product', as_index=False)['quantity'].sum()
-
df.groupby('product')['quantity'].sum().reset_index()
但是,盡管外觀不尋常,Series的行為就像DataFrames一樣,所以可能對(duì)pdi.patch_series_repr()進(jìn)行“整容”就足夠了。
顯然,不同的列在分組時(shí)表現(xiàn)不同。例如,對(duì)數(shù)量求和完全沒(méi)問(wèn)題,但對(duì)價(jià)格求和就沒(méi)有意義了。使用。agg可以為不同的列指定不同的聚合函數(shù),如下圖所示:
或者,你可以為一列創(chuàng)建多個(gè)聚合函數(shù):
或者,為了避免繁瑣的列重命名,你可以這樣做:
有時(shí),預(yù)定義的函數(shù)不足以產(chǎn)生所需的結(jié)果。例如,在平均價(jià)格時(shí)使用權(quán)重會(huì)更好。你可以為此提供一個(gè)自定義函數(shù)。與Series不同的是,該函數(shù)可以訪問(wèn)組中的多個(gè)列(它以子dataframe作為參數(shù)),如下所示:
不幸的是,你不能把預(yù)定義的聚合和幾個(gè)列級(jí)的自定義函數(shù)結(jié)合在一起,比如上面的那個(gè),因?yàn)閍gg只接受單列級(jí)的用戶(hù)函數(shù)。單列范圍的用戶(hù)函數(shù)唯一可以訪問(wèn)的是索引,這在某些情況下很方便。例如,那天香蕉以5折的價(jià)格出售,如下圖所示:
為了從自定義函數(shù)中訪問(wèn)group by列的值,它事先已經(jīng)包含在索引中。
通常,定制最少的函數(shù)可以獲得最好的性能。為了提高速度:
-
通過(guò)g.apply()實(shí)現(xiàn)多列范圍的自定義函數(shù)
-
通過(guò)g.agg()實(shí)現(xiàn)單列范圍的自定義函數(shù)(支持使用Cython或Numba進(jìn)行加速)
-
預(yù)定義函數(shù)(Pandas或NumPy函數(shù)對(duì)象,或其字符串名稱(chēng))。
-
預(yù)定義函數(shù)(Pandas或NumPy函數(shù)對(duì)象,或其字符串名稱(chēng))。
數(shù)據(jù)透視表(pivot table)是一種有用的工具,通常與分組一起使用,從不同的角度查看數(shù)據(jù)。
旋轉(zhuǎn)和`反旋轉(zhuǎn)`
假設(shè)你有一個(gè)變量a,它依賴(lài)于兩個(gè)參數(shù)i和j。有兩種等價(jià)的方法將它表示為一個(gè)表:
當(dāng)數(shù)據(jù)是“密集的”(當(dāng)有很少的0元素)時(shí),` short `格式更合適,而當(dāng)數(shù)據(jù)是“稀疏的”(大多數(shù)元素為0,可以從表中省略)時(shí),` long `格式更好。當(dāng)有兩個(gè)以上的參數(shù)時(shí),情況會(huì)變得更加復(fù)雜。
當(dāng)然,應(yīng)該有一種簡(jiǎn)單的方法來(lái)轉(zhuǎn)換這些格式。Pandas為此提供了一個(gè)簡(jiǎn)單方便的解決方案:數(shù)據(jù)透視表。
作為一個(gè)不那么抽象的例子,考慮下表中的銷(xiāo)售數(shù)據(jù)。有兩個(gè)客戶(hù)購(gòu)買(mǎi)了兩種產(chǎn)品的指定數(shù)量最初,這個(gè)數(shù)據(jù)是短格式的。`要將其轉(zhuǎn)換為`長(zhǎng)格式`,請(qǐng)使用df.pivot:
該命令丟棄了與操作無(wú)關(guān)的任何信息(索引、價(jià)格),并將來(lái)自三個(gè)請(qǐng)求列的信息轉(zhuǎn)換為長(zhǎng)格式,將客戶(hù)名稱(chēng)放入結(jié)果的索引中,將產(chǎn)品名稱(chēng)放入列中,將銷(xiāo)售數(shù)量放入DataFrame的` body `中。
至于相反的操作,你可以使用stack。它將索引和列合并到MultiIndex中:
另一種選擇是使用melt:
注意,melt以不同的方式對(duì)結(jié)果行進(jìn)行排序。
Pivot丟失了結(jié)果的` body `的名稱(chēng)信息,因此無(wú)論是stack還是melt,我們都必須提醒pandas ` quantity `列的名稱(chēng)。
在上面的例子中,所有的值都存在,但這不是必須的:
分組值然后旋轉(zhuǎn)結(jié)果的做法是如此常見(jiàn),以至于groupby和pivot被捆綁在一個(gè)專(zhuān)用的函數(shù)(以及相應(yīng)的DataFrame方法)數(shù)據(jù)透視表中:
-
如果沒(méi)有columns參數(shù),它的行為與groupby類(lèi)似
-
當(dāng)沒(méi)有重復(fù)的行進(jìn)行分組時(shí),它的工作原理與pivot類(lèi)似
-
否則,它會(huì)進(jìn)行分組和旋轉(zhuǎn)
aggfunc參數(shù)控制哪一個(gè)聚合函數(shù)應(yīng)該用于分組行(默認(rèn)為均值)。
為了方便,pivot_table可以計(jì)算小計(jì)和合計(jì):
一旦創(chuàng)建,pivot表就變成了一個(gè)普通的DataFrame,因此可以使用前面描述的標(biāo)準(zhǔn)方法查詢(xún)它。
當(dāng)使用多索引時(shí),透視表特別方便。我們已經(jīng)見(jiàn)過(guò)很多Pandas函數(shù)返回多索引DataFrame的例子。讓我們仔細(xì)看看。
第四部分. MultiIndex
對(duì)于從未聽(tīng)說(shuō)過(guò)Pandas的人來(lái)說(shuō),多索引(MultiIndex)最直接的用法是使用第二個(gè)索引列作為第一個(gè)索引列的補(bǔ)充,以唯一地標(biāo)識(shí)每行。例如,為了消除來(lái)自不同州的城市的歧義,州的名字通常附加在城市的名字后面。例如,在美國(guó)大約有40個(gè)springfield(在關(guān)系型數(shù)據(jù)庫(kù)中,它被稱(chēng)為復(fù)合主鍵)。
你可以在從CSV解析DataFrame后指定要包含在索引中的列,也可以立即作為read_csv的參數(shù)。
您還可以使用append=True將現(xiàn)有級(jí)別添加到多重索引,如下圖所示:
另一個(gè)更典型的用例是表示多維。當(dāng)你有一組具有特定屬性的對(duì)象或者隨著時(shí)間的推移而演變的對(duì)象時(shí)。例如:
-
社會(huì)學(xué)調(diào)查的結(jié)果
-
` Titanic `數(shù)據(jù)集
-
歷史天氣觀測(cè)
-
錦標(biāo)賽排名的年表。
這也被稱(chēng)為“面板數(shù)據(jù)”,Pandas就是以此命名的。
讓我們添加這樣一個(gè)維度:
現(xiàn)在我們有了一個(gè)四維空間,如下所示:
-
年形成一個(gè)(幾乎連續(xù)的)維度
-
城市名稱(chēng)沿第二條排列
-
第三個(gè)州的名字
-
特定的城市屬性(“人口”、“密度”、“面積”等)在第四個(gè)維度上起到了“刻度線”的作用。
下圖說(shuō)明了這個(gè)概念:
為了給對(duì)應(yīng)列的尺寸名稱(chēng)留出空間,Pandas將整個(gè)標(biāo)題向上移動(dòng):
分組
關(guān)于多重索引需要注意的第一件事是,它并不按照它可能出現(xiàn)的情況對(duì)任何內(nèi)容進(jìn)行分組。在內(nèi)部,它只是一個(gè)扁平的標(biāo)簽序列,如下所示:
你可以通過(guò)對(duì)行標(biāo)簽進(jìn)行排序來(lái)獲得相同的groupby效果:
你甚至可以通過(guò)設(shè)置相應(yīng)的Pandas選項(xiàng)來(lái)完全禁用視覺(jué)分組
:pd.options.display.multi_sparse=False。
類(lèi)型轉(zhuǎn)換
Pandas(以及Python本身)區(qū)分?jǐn)?shù)字和字符串,因此在無(wú)法自動(dòng)檢測(cè)數(shù)據(jù)類(lèi)型時(shí),通常最好將數(shù)字轉(zhuǎn)換為字符串:
pdi.set_level(df.columns, 0, pdi.get_level(df.columns, 0).astype('int'))
如果你喜歡冒險(xiǎn),可以使用標(biāo)準(zhǔn)工具做同樣的事情:
df.columns = df.columns.set_levels(df.columns.levels[0].astype(int), level=0)
但為了正確使用它們,你需要理解什么是` levels `和` codes `,而pdi允許你使用多索引,就像使用普通的列表或NumPy數(shù)組一樣。
如果你真的想知道,` levels `和` codes `是特定級(jí)別的常規(guī)標(biāo)簽列表被分解成的東西,以加速像pivot、join等操作:
-
pdi.get_level(df, 0) == Int64Index([2010, 2010, 2020, 2020])
-
df.columns.levels[0] == Int64Index([2010, 2020])
-
df.columns.codes[0] == Int64Index([0, 1, 0, 1])
使用多重索引構(gòu)建一個(gè)Dataframe
除了從CSV文件讀取和從現(xiàn)有列構(gòu)建外,還有一些方法可以創(chuàng)建多重索引。它們不太常用——主要用于測(cè)試和調(diào)試。
由于歷史原因,使用Panda自己的多索引表示的最直觀的方法不起作用。
這里的` Levels `和` codes `(現(xiàn)在)被認(rèn)為是不應(yīng)該暴露給最終用戶(hù)的實(shí)現(xiàn)細(xì)節(jié),但我們已經(jīng)擁有了我們所擁有的。
可能最簡(jiǎn)單的構(gòu)建多重索引的方法如下:
這樣做的缺點(diǎn)是必須在單獨(dú)的一行中指定級(jí)別的名稱(chēng)。有幾種可選的構(gòu)造函數(shù)將名稱(chēng)和標(biāo)簽捆綁在一起。
當(dāng)關(guān)卡形成規(guī)則結(jié)構(gòu)時(shí),您可以指定關(guān)鍵元素,并讓Pandas自動(dòng)交織它們,如下所示:
上面列出的所有方法也適用于列。例如:
使用多重索引進(jìn)行索引
通過(guò)多重索引訪問(wèn)DataFrame的好處是,您可以輕松地使用熟悉的語(yǔ)法一次引用所有級(jí)別(可能省略?xún)?nèi)部級(jí)別)。
列——通過(guò)普通的方括號(hào)
行和單元格——使用.loc[]
現(xiàn)在,如果你想選擇俄勒岡州的所有城市,或者只留下包含人口的列,該怎么辦?Python語(yǔ)法在這里有兩個(gè)限制。
1. 沒(méi)有辦法區(qū)分df['a', 'b']和df[('a', 'b')]——它是以同樣的方式處理的,所以你不能只寫(xiě)df[:, ' Oregon ']。否則,Pandas將永遠(yuǎn)不知道你指的是列Oregon還是第二級(jí)行Oregon
2. Python只允許在方括號(hào)內(nèi)使用冒號(hào),而不允許在圓括號(hào)內(nèi)使用冒號(hào),所以你不能寫(xiě)df.loc[(:, 'Oregon'),:]
在技術(shù)方面,這并不難安排。我給DataFrame打了猴補(bǔ)丁,添加了這樣的功能,你可以在這里看到:
這種語(yǔ)法唯一的缺點(diǎn)是,當(dāng)你使用兩個(gè)索引器時(shí),它返回一個(gè)副本,所以你不能寫(xiě)df.mi[:, ' Oregon ']。Co [' population '] = 10。有許多可選的索引器,其中一些允許這樣的賦值,但它們都有自己的特點(diǎn):
1. 您可以將內(nèi)層與外層交換,并使用括號(hào)。
因此,df[:, 'population']可以用df.swaplevel(axis=1)['population']實(shí)現(xiàn)。
這感覺(jué)很hacky,不方便超過(guò)兩層。
2. 你可以使用xs方法:df.xs (' population ', level=1, axis=1)。
它給人的感覺(jué)不夠python化,尤其是在選擇多個(gè)關(guān)卡時(shí)。這種方法無(wú)法同時(shí)過(guò)濾行和列,因此名稱(chēng)xs(代表“橫截面”)背后的原因并不完全清楚。它不能用于設(shè)置值。
3.可以為pd創(chuàng)建別名。idx=pd.IndexSlice;df.loc [:, idx[:, ' population ']]
這更符合python風(fēng)格,但要訪問(wèn)元素,必須使用別名,這有點(diǎn)麻煩(沒(méi)有別名的代碼太長(zhǎng)了)。您可以同時(shí)選擇行和列??蓪?xiě)的。
4. 你可以學(xué)習(xí)如何使用slice代替冒號(hào)。如果你知道a[32] == a[slice(3,10,2)],那么你可能也會(huì)理解下面的代碼:df.loc[:, (slice(None), ' population ')],但它幾乎無(wú)法讀懂。您可以同時(shí)選擇行和列。可寫(xiě)的。
作為底線,Pandas有多種使用括號(hào)使用多重索引訪問(wèn)DataFrame元素的方法,但沒(méi)有一種方法足夠方便,因此他們不得不采用另一種索引語(yǔ)法:
5. 一個(gè)用于.query方法的迷你語(yǔ)言:df.query(' state=="Oregon" or city=="Portland" ')。
它方便快捷,但缺乏IDE的支持(沒(méi)有自動(dòng)補(bǔ)全,沒(méi)有語(yǔ)法高亮等),而且它只過(guò)濾行,而不是列。這意味著你不能在不轉(zhuǎn)置DataFrame的情況下用它實(shí)現(xiàn)df[:, ' population '](除非所有列的類(lèi)型都相同,否則會(huì)丟失類(lèi)型)。Non-writable。
疊加與拆分
Pandas沒(méi)有針對(duì)列的set_index。向列中添加層次的一種常見(jiàn)方法是將現(xiàn)有的層次從索引中“解?!?
Pandas的棧與NumPy的棧有很大不同。讓我們看看文檔中對(duì)命名約定的說(shuō)明:
“該函數(shù)的命名類(lèi)似于重新組織的書(shū)籍集合,從水平位置并排(dataframe的列)到垂直堆疊(在dataframe的索引中)?!?/span>
“在上面”的部分聽(tīng)起來(lái)并不能讓我信服,但至少這個(gè)解釋有助于記住誰(shuí)把東西朝哪個(gè)方向移動(dòng)。順便說(shuō)一下,Series有unstack,但沒(méi)有stack,因?yàn)樗呀?jīng)“堆疊”了。由于是一維的,Series在不同情況下可以作為行向量或列向量,但通常被認(rèn)為是列向量(例如dataframe列)。
例如:
您還可以通過(guò)名稱(chēng)或位置索引指定要堆疊/解堆疊的級(jí)別。在這個(gè)例子中,df.stack()、df.stack(1)和df.stack(' year ')與df1.unstack()、df1.unstack(2)和df1.unstack(' year ')產(chǎn)生相同的結(jié)果。目的地總是在“最后一層之后”,并且不可配置。如果需要將級(jí)別放在其他地方,可以使用df.swaplevel().sort_index()或pdi。swap_level (df = True)
列必須不包含重復(fù)的值才能堆疊(在反堆疊時(shí),索引也是如此):
如何防止疊加/分解排序
stack和unstack都有一個(gè)壞習(xí)慣,會(huì)不可預(yù)測(cè)地按字典順序排序結(jié)果索引。這有時(shí)可能令人惱火,但這是在有大量缺失值時(shí)給出可預(yù)測(cè)結(jié)果的唯一方法。
考慮下面的例子。你希望一周中的天數(shù)以何種順序出現(xiàn)在右邊的表中?
你可以推測(cè),如果John的星期一在John的星期五的左邊,那么就是' Mon ' < ' Fri ',類(lèi)似地,Silvia的' Fri ' < ' Sun ',因此結(jié)果應(yīng)該是' Mon ' < ' Fri ' < ' Sun '。這是合法的,但是如果剩余的列順序不同,比如' Mon ' < ' frii '和' Tue ' < ' frii ',該怎么辦?或者' Mon ' < ' friday '和' Wed ' < ' Sat ' ?
好吧,一周沒(méi)有那么多天,Pandas可以根據(jù)先驗(yàn)知識(shí)推斷出順序。但是,人類(lèi)還沒(méi)有得出一個(gè)決定性的結(jié)論,那就是星期天應(yīng)該作為一周的結(jié)束還是開(kāi)始。Pandas應(yīng)該默認(rèn)使用哪種順序?閱讀區(qū)域設(shè)置?那么不那么瑣碎的順序呢,比如美國(guó)的州的順序?
在這種情況下,Pandas所做的只是簡(jiǎn)單地按字母順序排序,如下所示:
雖然這是一個(gè)合理的默認(rèn),但感覺(jué)上仍然是錯(cuò)誤的。應(yīng)該有一個(gè)解決方案!有一個(gè)。它被稱(chēng)為CategoricalIndex。即使缺少一些標(biāo)簽,它也會(huì)記住順序。它最近已經(jīng)順利集成到Pandas工具鏈中。它唯一缺少的是基礎(chǔ)設(shè)施。它很難建立;它是脆弱的(在某些操作中會(huì)退回到對(duì)象),但它是完全可用的,并且pdi庫(kù)有一些幫助程序可以陡峭地提高學(xué)習(xí)曲線。
例如,要告訴Pandas鎖定存儲(chǔ)產(chǎn)品的簡(jiǎn)單索引的順序(如果你決定將一周中的天數(shù)解?;亓?,則不可避免地會(huì)排序),你需要編寫(xiě)像df這樣可怕的代碼。index = pd.CategoricalIndex(df. index)df指數(shù)。指數(shù)排序= True)。它更適合多索引。
pdi庫(kù)有一個(gè)輔助函數(shù)locked(以及一個(gè)默認(rèn)為inplace=True的別名lock),通過(guò)將某個(gè)多索引級(jí)別提升到CategoricalIndex來(lái)鎖定該級(jí)別的順序:
等級(jí)名稱(chēng)旁邊的勾選標(biāo)記表示等級(jí)被鎖定。它可以使用pdi.vis(df)手動(dòng)可視化,也可以使用pdi.vis_patch()對(duì)DataFrame HTML輸出進(jìn)行monkey補(bǔ)丁自動(dòng)可視化。應(yīng)用補(bǔ)丁后,在Jupyter單元中簡(jiǎn)單地寫(xiě)` df `將顯示鎖定順序的所有級(jí)別的復(fù)選標(biāo)記。
Lock和locked在簡(jiǎn)單的情況下自動(dòng)工作(如客戶(hù)端名稱(chēng)),但在更復(fù)雜的情況下(如缺少日期的星期幾)需要用戶(hù)提示。
在級(jí)別切換到CategoricalIndex之后,它會(huì)在sort_index、stack、unstack、pivot、pivot_table等操作中保持原來(lái)的順序。
不過(guò),它很脆弱。即使像df[' new_col '] = 1這樣簡(jiǎn)單的操作也會(huì)破壞它。使用pdi.insert (df。columns, 0, ' new_col ', 1)用CategoricalIndex正確處理級(jí)別。
操作級(jí)別
除了前面提到的方法之外,還有一些其他的方法:
-
pdi.get_level(obj, level_id)返回通過(guò)數(shù)字或名稱(chēng)引用的特定級(jí)別,可用于DataFrames, Series和MultiIndex
-
pdi.set_level(obj, level_id, labels)用給定的數(shù)組(list, NumPy array, Series, Index等)替換關(guān)卡的標(biāo)簽
-
pdi.insert_level (obj, pos, labels, name)使用給定的值添加一個(gè)層級(jí)(必要時(shí)適當(dāng)廣播)
-
pdi.drop_level(obj, level_id)從多重索引中刪除指定的級(jí)別
pdi.swap_levels (obj, src=-2, dst=-1)交換兩個(gè)級(jí)別(默認(rèn)是兩個(gè)最內(nèi)層的級(jí)別)
pdi.move_level (obj, src, dst)將特定級(jí)別src移動(dòng)到指定位置dst
除了上述參數(shù)外,本節(jié)中的所有函數(shù)還有以下參數(shù):
-
axis=None其中None對(duì)于DataFrame表示“列”,對(duì)于Series表示“索引”
-
sort=False,可選在操作之后對(duì)相應(yīng)的多索引進(jìn)行排序
-
inplace=False,可選地原地執(zhí)行操作(不能用于單個(gè)索引,因?yàn)樗遣豢勺兊?。
上面的所有操作都是從傳統(tǒng)意義上理解“級(jí)別”這個(gè)詞的(級(jí)別的標(biāo)簽數(shù)量與數(shù)據(jù)框中的列數(shù)量相同),隱藏了索引的機(jī)制。標(biāo)簽和索引。來(lái)自最終用戶(hù)的代碼。
在極少數(shù)情況下,當(dāng)移動(dòng)和交換單獨(dú)的關(guān)卡不夠時(shí),您可以使用純Pandas調(diào)用:df一次性重新排序所有關(guān)卡。columns = df.columns.reorder_levels([' M ', ' L ', ' K '])其中[' M ', ' L ', ' K ']是層的期望順序。
通常,使用get_level和set_level對(duì)標(biāo)簽進(jìn)行必要的修復(fù)就足夠了,但如果你想一次對(duì)多索引的所有級(jí)別應(yīng)用轉(zhuǎn)換,Pandas有一個(gè)(命名不明確)函數(shù)rename接受一個(gè)dict或一個(gè)函數(shù):
至于重命名級(jí)別,它們的名稱(chēng)存儲(chǔ)在.names字段中。該字段不支持直接賦值(為什么不?):df.index.names[1] = ' x ' # TypeError,但可以作為一個(gè)整體替換:
當(dāng)你只需要重命名一個(gè)特定的級(jí)別時(shí),語(yǔ)法如下:
將多索引轉(zhuǎn)換為平面索引并恢復(fù)它
正如我們?cè)谏厦婵吹降模憬莸牟樵?xún)方法只解決了處理行中的多索引的復(fù)雜性。盡管有這么多的輔助函數(shù),但當(dāng)某些Pandas函數(shù)返回列中的多索引時(shí),對(duì)初學(xué)者來(lái)說(shuō)會(huì)有一個(gè)震驚的效果。因此,pdi庫(kù)具有以下內(nèi)容:
-
join_levels(obj, sep=’_’, name=None) 將所有多索引級(jí)別連接到一個(gè)索引
-
split_level(obj, sep=’_’, names=None)將索引拆分回多索引
它們都有可選的axis和inplace參數(shù)。
排序MultiIndex
由于多索引由多個(gè)級(jí)別組成,因此排序比單索引更做作。這仍然可以使用sort_index方法完成,但可以使用以下參數(shù)進(jìn)行進(jìn)一步微調(diào)。
要對(duì)列級(jí)別進(jìn)行排序,指定axis=1。
讀寫(xiě)多索引dataframe到磁盤(pán)
Pandas可以以完全自動(dòng)化的方式將具有多重索引的DataFrame寫(xiě)入CSV文件:df.to_csv('df.csv ')。但是在讀取這樣的文件時(shí),Pandas無(wú)法自動(dòng)解析多重索引,需要用戶(hù)的一些提示。例如,要讀取具有三層高列和四層寬索引的DataFrame,你需要指定pd.read_csv('df.csv', header=[0,1,2], index_col=[0,1,2,3])。
這意味著前三行包含有關(guān)列的信息,后續(xù)每一行的前四個(gè)字段包含索引級(jí)別(如果列的級(jí)別不止一個(gè),你不能再通過(guò)名稱(chēng)來(lái)引用行級(jí)別,只能通過(guò)編號(hào))。
手動(dòng)解讀多索引中的層數(shù)是不方便的,所以更好的主意是在將DataFrame保存到CSV之前,stack()所有列頭層,并在讀取后將它們解stack()。
如果你需要“置之不理”的解決方案,可能需要研究二進(jìn)制格式,例如Python的pickle格式:
直接調(diào)用:df.to_pickle('df.pkl'), pd.read_pickle('df.pkl')
使用storemagic在Jupyter %store df然后%store -r df(存儲(chǔ)在$
HOME/.ipython/profile_default/db/autorestore)
Python的pickle小巧而快速,但只能在Python中訪問(wèn)。如果您需要與其他生態(tài)系統(tǒng)互操作,請(qǐng)查看更標(biāo)準(zhǔn)的格式,如Excel格式(在讀取MultiIndex時(shí)需要與read_csv相同的提示)。代碼如下:
!pip install openpyxl
df.to_excel('df3.xlsx')
df.to_pd.read_excel('df3.xlsx', header=[0,1,2], index_col=[0,1,2,3])
或者查看其他選項(xiàng)(參見(jiàn)文檔)。
MultiIndex算術(shù)
當(dāng)使用多索引數(shù)據(jù)框時(shí),與普通數(shù)據(jù)框適用相同的規(guī)則(見(jiàn)上文)。但是處理細(xì)胞的一個(gè)子集有它自己的一些特性。
用戶(hù)可以通過(guò)外部的多索引級(jí)別更新部分列,如下所示:
如果想保持原始數(shù)據(jù)不變,可以使用df1 = df.assign(population=df.population*10)。
你也可以用density=df.population/df.area輕松獲得人口密度。
但不幸的是,你不能用df.assign將結(jié)果賦值給原始的dataframe。
一種方法是將列索引的所有不相關(guān)級(jí)別堆疊到行索引中,執(zhí)行必要的計(jì)算,然后將它們解堆疊回去(使用pdi)。鎖以保持列的原始順序)。
或者,你也可以使用pdi.assign:
pdi.assign是鎖定順序感知的,所以如果你給它一個(gè)(多個(gè))鎖定級(jí)別的dataframe,它不會(huì)解鎖它們或后續(xù)的棧/解棧/等。操作將保持原始的列和行順序。
總而言之,Pandas是分析和處理數(shù)據(jù)的好工具。希望這篇文章能幫助你理解“如何”和“為什么”解決典型問(wèn)題,并欣賞Pandas庫(kù)的真正價(jià)值和美麗。
審核編輯 :李倩
-
算法
+關(guān)注
關(guān)注
23文章
4587瀏覽量
92501 -
排序
+關(guān)注
關(guān)注
0文章
31瀏覽量
9697 -
數(shù)組
+關(guān)注
關(guān)注
1文章
412瀏覽量
25881
原文標(biāo)題:一圖勝千言|圖解Pandas常用操作!
文章出處:【微信號(hào):DBDevs,微信公眾號(hào):數(shù)據(jù)分析與開(kāi)發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論