數據分析(6)--Pandas+MultiIndex多層索引與分組

MultiIndex

MultiIndex,即具有多個層次的索引,有些類似於根據索引進行分組的形式。通過多層次索引,我們就可以使用高層次的索引,來操作整個索引組的數據。

創建方式

第一種

我們在創建Series或DataFrame時,可以通過給index(columns)參數傳遞多維數組,進而構建多維索引。【數組中每個維度對應位置的元素,組成每個索引值】
多維索引的也可以設置名稱(names屬性),屬性的值爲一維數組,元素的個數需要與索引的層數相同(每層索引都需要具有一個名稱)。

第二種

我們可以通過MultiIndex類的相關方法,預先創建一個MultiIndex對象,然後作爲Series與DataFrame中的index(或columns)參數值。同時,可以通過names參數指定多層索引的名稱。

  • from_arrays:接收一個多維數組參數,高維指定高層索引,低維指定底層索引。
  • from_tuples:接收一個元組的列表,每個元組指定每個索引(高維索引,低維索引)。
  • from_product:接收一個可迭代對象的列表,根據多個可迭代對象元素的笛卡爾積進行創建索引。

from_product相對於前兩個方法而言,實現相對簡單,但是,也存在侷限。

mport numpy as np
import pandas as pd

# 創建單層次的索引。
x = pd.Series([1, 2, 3], index=["a", "b", "c"])
# 創建多層次索引。index指定一個二維數組,每個層次是一個一維數組。
x = pd.Series([123, 2123, 13123], index=[["東部地區", "東部地區", "南部地區"], ["城市1", "城市2", "城市3"]])
# x.index

# x = pd.DataFrame(np.random.randint(10, 100, size=(4, 4)), 
#                  index=[["東部地區", "東部地區", "南部地區", "南部地區"], ["城市1", "城市2", "城市3", "城市4"]],
#                 columns=[["2017", "2017", "2018", "2018"], ["上半年", "下半年", "上半年", "下半年"]])
# display(x.index, x.columns)

# 也可以這樣做。
# ind = pd.Index(["a", "b", "c"], name="索引的名字")
# x = pd.Series([1, 2, 3], index=ind)
# x.index

# 通過構造器創建MultiIndex對象。
# mi = pd.MultiIndex(levels=[['東部地區', '南部地區'], ['城市1', '城市2', '城市3']],
#            labels=[[0, 0, 1], [0, 1, 2]], names=["外層索引名稱", "內層索引名稱"])
# x = pd.Series([1, 2, 3], index=mi)
# 還可以通過MultiIndex類提供的類方法來創建對象。
# 傳遞一個二維列表(數組)來創建多層次索引。每個元素(一維數組來指定每層索引的標籤內容。)
# mind = pd.MultiIndex.from_arrays([["東部地區", "東部地區", "南部地區"], ["城市1", "城市2", "城市3"]], names=["外層", "內層"])
# 參數爲含有元組的列表,每個元素的元素個數與索引的層數數量相同。每個元組分別指定(外層, 內層)
# from_arrays可以看做是按列的方式指定索引。from_tuples可以看做是按行的方式來指定索引。
# mind = pd.MultiIndex.from_tuples([("東部地區", "城市1"), ("東部地區", "城市2"), ("南部地區", "城市3")])

# 通過笛卡爾積組合,創建多層次索引。
mind = pd.MultiIndex.from_product([["東部地區", "南部地區"], ["城市1", "城市2"]])
x = pd.Series([1, 2, 3, 4], index=mind)
x
# 使用笛卡爾積的方式,創建多層次索引,相對簡單,但是也具有侷限。

多層索引操作

對於多層索引,同樣也支持單層索引的相關操作,例如,索引元素,切片,索引數組選擇元素等。我們也可以根據多級索引,按層次逐級選擇元素。
多層索引的優勢:通過創建多層索引,我們就可以使用高層次的索引,來操作整個索引組的數據。
格式:

  • s[操作]
  • s.loc[操作]
  • s.iloc[操作]

其中,操作可以是索引,切片,數組索引,布爾索引。

Series多層索引

  • 通過loc(標籤索引)操作,可以通過多層索引,獲取該索引所對應的一組值。
  • 通過iloc(位置索引)操作,會獲取對應位置的元素值(與是否多層索引無關)。
  • 通過s[操作]的行爲有些詭異,建議不用。
  • 對於索引(單級),首先按照標籤選擇,如果標籤不存在,則按照位置選擇。
  • 對於多級索引,則按照標籤進行選擇。
  • 對於切片,如果提供的是整數,則按照位置選擇,否則按照標籤選擇。
  • 對於數組索引, 如果數組元素都是整數,則根據位置進行索引,否則,根據標籤進行索引(此時如果標籤不存在,也不會出現錯誤)。
ind = pd.MultiIndex.from_product([["1東部地區", "2南部地區", "3西部地區", "4北部地區"], ["城市1", "城市2"]])
x = pd.Series([1, 2, 3, 4, 5, 6, 7, 8], index=mind)
display(x)
# 通過層次索引,我們可以獲取屬於該標籤下的一組元素。
# x.loc["東部地區"]
# 我們也可以按照層次來逐級進行訪問。[外層索引, 內層索引]
# x.loc["東部地區", "城市1"]

# 多層次索引也支持切片操作
# x.loc["1東部地區":"3西部地區"]

# 多層次索引的數組索引與布爾索引操作。
# x.loc[["1東部地區", "4北部地區"]]

# 當通過位置訪問元素時,就是按照記錄的位置進行訪問的,與當前的Series(DataFrame)是否具有
# 多層次索引無關。
# x.iloc[2:5]
x.loc[[True, False, False, True]]

DataFrame多層索引

  • 通過loc(標籤索引)操作,可以通過多層索引,獲取該索引所對應的一組值。
  • 通過iloc(位置索引)操作,會獲取對應位置的一行(與是否多層索引無關)。
  • 通過s[操作]的行爲有些詭異,建議不用。
  • 對於索引,根據標籤獲取相應的列(如果是多層索引,則可以獲得多列)。
  • 對於數組索引, 根據標籤,獲取相應的列(如果是多層索引,則可以獲得多列)。
  • 對於切片,首先按照標籤進行索引,然後再按照位置進行索引(取行)。
df = pd.DataFrame(np.random.randint(10, 100, size=(4, 4)), 
                 index=[["東部地區", "東部地區", "南部地區", "南部地區"], ["城市1", "城市2", "城市3", "城市4"]],
                columns=[["2017", "2017", "2018", "2018"], ["上半年", "下半年", "上半年", "下半年"]])
# df.loc["南部地區", "城市3"]
# df.iloc[0:2]
df

交換索引

我們可以調用DataFrame對象的swaplevel方法來交換兩個層級索引。該方法默認對倒數第2層與倒數第1層進行交換。我們也可以指定交換的層。層次從0開始,由外向內遞增(或者由上到下遞增),也可以指定負值,負值表示倒數第n層。除此之外,我們也可以使用層次索引的名稱來進行交換。

索引排序

我們可以使用sort_index方法對索引進行排序處理。

  • level:指定根據哪一層進行排序,默認爲最外(上)層。該值可以是數值,索引名,或者是由二者構成的列表。
  • inplace:是否就地修改。默認爲False。
df = pd.DataFrame(np.random.randint(1, 10, (5, 3)), index=[["b", "b", "b", "a", "a"], ["y", "y", "x", "x", "x"], ["u","k", "w", "w","u" ]])
df.index.names = ["level1", "level2", "level3"]
display(df)
# 交換索引的層級。索引的層級,由外向內,0,1,2這樣進行遞增。如果我們沒有顯式指定索引的層架,
# 則默認交換最內層與倒數第二層。索引的層級也可以爲負值,從最內從到最外層,-1,-2,-3這樣進行遞增。
# df.swaplevel()   # 相當於df.swaplevel(-1, -2)
# 將最內層與最外層索引進行交換。
# df.swaplevel(0, -1)
# df.swaplevel(0, 2)
# 我們除了可以指定索引的層次之外,我們也可以通過索引的名字(name)來表示對應的索引。
# df.swaplevel("level1", "level3")

# 對索引進行排序。如果沒有指定層次,則按最外層進行排序。
# df.sort_index(level="level3")
# df.sort_index(level=1
# 我們也可以提供列表,進行多層次的排列。
# df.sort_index(level=[1, "level3"])

索引堆疊

通過DataFrame對象的stack方法,可以進行索引堆疊,即將指定層級的列轉換成行。
level:指定轉換的層級,默認爲-1。

取消堆疊

通過DataFrame對象的unstack方法,可以取消索引堆疊,即將指定層級的行轉換成列。
level:指定轉換的層級,默認爲-1。
fill_value:指定填充值。默認爲NaN。

df = pd.DataFrame(np.random.randint(1, 10, (5, 3)), index=[["b", "b", "b", "a", "a"], ["y", "y", "x", "x", "x"], ["u","k", "w", "w","u" ]]
                 ,columns=[["a", "a", "b"], ["c", "d", "d"]])
display(df)
# 進行索引堆疊。將列索引轉換成行索引。
# df.stack()
# 我們可以通過level指定轉換的層。默認爲最裏層(-1)。
# df.stack(0)

# 驚醒索引取消堆疊,將行索引轉換成列索引。我們可以通過level來指定層級。(默認爲-1。)
# 通過fill_value參數指定,如果產生空值,使用該參數的值進行填充。
df.unstack(1, fill_value=1000)

設置索引

在DataFrame中,如果我們需要將現有的某一(幾)列作爲索引列,可以調用set_index方法來實現。

  • drop:是否丟棄作爲新索引的列,默認爲True。
  • append:是否以追加的方式設置索引,默認爲False。
  • inplace:是否就地修改,默認爲False。

重置索引

調用在DataFrame對象的reset_index,可以重置索引。該操作與set_index正好相反。

  • level:重置索引的層級,默認重置所有層級的索引。如果重置所有索引,將會創建默認整數序列索引。
  • drop:是否丟棄重置的索引列,默認爲False。
  • inplace:是否就地修改,默認爲False。
df = pd.DataFrame(np.random.randint(0, 100, (5, 3)))
# 設置索引。drop參數指定,設置索引列後,該列是否從當前的DataFrame中刪除。默認爲True。
# df.set_index([0, 1], drop=False)

# append參數設置以前的索引列是否保留。(新的索引列是替換以前的索引,還是追加。),默認爲False,替換的方式。
# df.set_index(2, append=True)

df = pd.DataFrame(np.random.randint(0, 100, (5, 3)), index=[["a", "a", "a", "b", "b"], ["c", "c", "d", "d", "d"]])
display(df)
# 重置索引,可以看成是設置索引的逆操作。
# level指定重置的層級。如果沒有指定,則重置所有層級。
# df.reset_index(level=0)
# 可以通過drop參數來指定,重置索引後,該索引列是否丟棄。默認爲False。
df.reset_index(level=0, drop=True)

分組與聚合

分組與聚合操作與數據庫中的分組與聚合相似。

groupby分組

我們可以通過groupby方法來對Series或DataFrame對象實現分組操作。該方法會返回一個分組對象:

  • 對於Series分組,返回SeriesGroupBy對象。
  • 對於DataFrame分組,DataFrameGroupBy對象。

迭代

如果直接查看(輸出)該對象,並不能看到任何的分組信息(這點不同於列表類型)。不過,我們還是有能力獲取分組對象的數據信息的。分組對象是可迭代對象類型(Iterable),因此,我們可以採用如下的方式進行遍歷:

  • 獲取迭代器,進行迭代。迭代每次會返回一個元組,第1個元素爲用來分組的key,第2個元素爲該組對應的數據。
  • 使用for循環來對分組對象進行迭代。

說明:

  • 對於多層索引,迭代返回元組的第一個元素(分組的key),依然還是一個元組。

聚合

所謂聚合,就是執行多個值變成一個值的操作。而在Python中,就是將一個矢量(數組)變成標量。
在分組之後,我們就可以調用分組對象的mean,sum等聚合函數,對每個分組進行聚合。

說明:

  • 在聚合的結果集中,默認會使用分組的鍵作爲索引,我們可以在groupby方法中通過as_index參數來控制。
  • 我們也可以在聚合之後調用reset_index方法來實現as_index=False的功能。

分組對象的屬性與方法

  • groups(屬性) 返回一個字典類型對象,包含分組信息。
  • size 返回每組記錄的數量。
  • discribe 分組查看統計信息。
  • get_group 接受一個組名(key),返回該組對應的數據。

分組的方式

通過by參數進行分組

使用groupby進行分組時,通過參數by指定分組,參數by可以是如下的形式:

  • 字符串或字符串數組:指定分組的列名或索引名。(如果索引沒有命名,可以使用pd.Grouper(level=1)來指定索引的層級)
  • 字典或Series:key指定索引,value指定分組依據,即value值相等的,會分爲一組。
  • 數組(列表):會根據數組的值進行對位分組。長度需要與列(行)相同。
  • 函數:接受索引,返回分組依據的value值。

說明:

  • 使用groupby分組時,可以使用sort參數指定是否對分組的key進行排序,默認爲True(指定False可以提高性能)。

通過level參數進行分組

我們也可以通過level指定索引的層級,groupby會將該層級索引值相同的數據進行分組。level可以是整數值,索引名。

df = pd.DataFrame({"a":[1, 2, 1, 2], "b":[5, 6, 7, 8], "c":[9, 10, 11, 1]})
display(df)
g = df.groupby("a")
# 分組對象不像列表對象那樣,可以打印直接看到數據信息。
# 如果需要看到分組對象的數據信息,需要自行迭代查看。

# 迭代方式:
# 1 獲得分組對象的迭代器,然後調用next獲取元素。
# 2 使用for循環。
# g.__iter__()
# i = iter(g)
# i.__next__()
# display(next(i))
# display(next(i))

# 當對分組對象進行迭代時,每次會返回一個元組類型。元組中含有兩個元素:組名(索引爲0的與元素)與該組
# 對應的數據(索引爲1的元素)
# for k, v in g:
#     display(k, v)

# display(df.sum(axis=1)) 
# 在分組對象上調用聚合方法。就可以針對每個組進行聚合操作。

# 分組聚合時,默認會使用分組鍵來充當索引,如果需要重置索引,可以在groupby中,將as_index參數
# 設置爲False。(默認爲True)
g = df.groupby("a", as_index=False)
# g.mean()
# 返回一個字典類型,字典類型中包含分組的信息。(key:組名【用來分組的值】, value:每組數據記錄的索引。)
# g.groups
# 返回每組的記錄數。
# display(g.size())
# 按組來統計每列的信息。
# display(g.describe())
# 根據組名返回該組對應的數據。
# display(g.get_group(1))
# display(g.get_group(2))
# groupby分組的幾種形式。
# display(df)
# 1 指定一個索引。
# g = df.groupby("a")
# g.mean()
# g["b"].mean()
# 2 指定多個索引分組。(數組)
# g = df.groupby(["a", "b"])
# 3 根據Series或字典對象來進行分組。key指定索引,value映射爲組的值。分組時,就會把value相同的值分到一組。
# g = df.groupby({0: "g1", 1:"g2", 2:"g1", 3: "g2"})
# 4 根據函數進行分組。函數需要具有一個參數,該參數用來接收索引,然後返回該索引對應的組。
# g = df.groupby(lambda x : x % 2)
# for k, v in g:
#     display(k, v)

# 根據索引層級進行分組。此時,會把該層級索引值相同的,劃分爲一組。
df = pd.DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], index=[["a", "a", "b"], ["c", "d", "d"]])
display(df)
g = df.groupby(level=1)
for k, v in g:
    display(k, v)

結果樣例

agg聚合

我們可以使用DataFrame或者分組對象的agg / aggregate方法實現多個聚合操作。
方法可以接受如下形式:

  • 字符串函數名
  • 函數對象
  • 列表 可提供多個函數(函數名),進行多種聚合。
  • 字典 可針對不同的列實現不同的聚合方式。

說明:

  • 對於分組對象,會使用函數名作爲聚合之後的列名。如果需要自定義列名,可以提供元組的列表,格式爲:
    [(列名1, 聚合函數1), (列名2, 聚合函數2), ……]
# agg聚合方法。
df = pd.DataFrame({"a":[1, 2, 1, 2], "b":[5, 6, 7, 8], "c":[9, 10, 11, 1]})
display(df)
# DataFrame對象與分組對象可以調用agg(aggregate)進行聚合操作。

# agg進行聚合的方式:
# 1 傳遞函數名字符串。
# df.agg("mean")
# df.mean()
# 2 傳遞一個列表。
# df.agg(["mean", "max", "min"])
# 3 傳遞一個字典,key指定列名(索引),value指定聚合方法。這樣,
# 可以做到每個類進行不同的聚合方式。
# df.agg({"a":"mean", "b":"max", "c":"min"})
# df.agg({"a":["mean", "max"], "b":["max", "min"], "c":["min", "sum"]})
# 4 傳遞一個函數。該函數具有一個參數,參數用來接收每一列(行),然後返回聚合之後的結果。
# 通過函數,我們可以實現自定義的聚合。
# df.agg(lambda x: x.max() - x.min())

g = df.groupby("a")
# 默認情況下,會使用聚合方法名稱來作爲列名稱,我們可以通過指定一個元素,
# (顯示名稱,聚合操作)來更改顯示的列名稱。
g.agg([("平均值","mean"), ("最大值", "max"), ("最小值", "min")])

結果樣例

apply

對分組對象,可以調用apply函數,apply函數需要傳入一個操作函數,該函數會依次接收每個分組的數據,並在函數體中進行處理,返回處理之後的結果。
最後,apply會將每個分組調用函數的返回結果合併(concat),作爲最終的處理結果。

說明:

  • apply的函數的參數函數除了必要的一個參數(用來接收每個分組的數據)外,還可以定義額外的參數。其他參數可以在調用apply時,一併傳入(置於參數函數的後面)。
  • 在apply分組中,傳入每個組的數據,name屬性爲該分組鍵的值。
df = pd.DataFrame({"a":[1, 2, 1, 2], "b":[5, 6, 7, 8], "c":[9, 10, 11, 1]})
display(df)
g = df.groupby("a")
# 分組對象可以調用apply方法,對每個組執行指定的應用。
# 注意:在當前的實現中,分組對象的apply方法調用時,第一個組會執行兩次。(傳遞兩次)
# g.apply(lambda x: x.std())

# 函數的參數會接收每個組,我們在函數中處理組,並返回結果。
# 
display(g.apply(lambda x : x.describe()))
display(g.describe())

結果樣例

部分數據來自光環筆記!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章