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