參考datawhale:https://datawhalechina.github.io/joyful-pandas/build/html/%E7%9B%AE%E5%BD%95/ch3.html
import numpy as np
import pandas as pd
一、索引器
- 表的列索引
列索引是最常見的索引形式,一般通過 [] 來實現(xiàn)。通過 [列名] 可以從 DataFrame 中取出相應(yīng)的列,返回值為 Series ,例如從表中取出姓名一列:
df = pd.read_csv('data/learn_pandas.csv',
...: usecols = ['School', 'Grade', 'Name', 'Gender',
...: 'Weight', 'Transfer'])
...:
df['Name'].head()
0 Gaopeng Yang
1 Changqiang You
2 Mei Sun
3 Xiaojuan Sun
4 Gaojuan You
Name: Name, dtype: object
如果要取出多個列,則可以通過 [列名組成的列表] ,其返回值為一個 DataFrame ,例如從表中取出性別和姓名兩列:
df[['Gender', 'Name']].head()
Gender Name
0 Female Gaopeng Yang
1 Male Changqiang You
2 Male Mei Sun
3 Female Xiaojuan Sun
4 Male Gaojuan You
此外,若要取出單列,且列名中不包含空格,則可以用 .列名 取出,這和 [列名] 是等價的:
df.Name.head()
0 Gaopeng Yang
1 Changqiang You
2 Mei Sun
3 Xiaojuan Sun
4 Gaojuan You
Name: Name, dtype: object
- 序列的行索引
【a】以字符串為索引的 Series
如果取出單個索引的對應(yīng)元素,則可以使用 [item] ,若 Series 只有單個值對應(yīng),則返回這個標(biāo)量值,如果有多個值對應(yīng),則返回一個 Series:
s = pd.Series([1, 2, 3, 4, 5, 6],
...: index=['a', 'b', 'a', 'a', 'a', 'c'])
...:
s['a']
a 1
a 3
a 4
a 5
dtype: int64
s['b']
2
如果取出多個索引的對應(yīng)元素,則可以使用 [items的列表] :
s[['c', 'b']]
c 6
b 2
dtype: int64
如果想要取出某兩個索引之間的元素,并且這兩個索引是在整個索引中唯一出現(xiàn),則可以使用切片,同時需要注意這里的切片會包含兩個端點(diǎn):
s['c': 'b': -2]
c 6
a 4
b 2
dtype: int64
【b】以整數(shù)為索引的 Series
在使用數(shù)據(jù)的讀入函數(shù)時,如果不特別指定所對應(yīng)的列作為索引,那么會生成從0開始的整數(shù)索引作為默認(rèn)索引。當(dāng)然,任意一組符合長度要求的整數(shù)都可以作為索引。
和字符串一樣,如果使用 [int] 或 [int_list] ,則可以取出對應(yīng)索引 元素 的值:
s = pd.Series(['a', 'b', 'c', 'd', 'e', 'f'],
....: index=[1, 3, 1, 2, 5, 4])
....:
s[1]
1 a
1 c
dtype: object
s[[2,3]]
2 d
3 b
dtype: object
如果使用整數(shù)切片,則會取出對應(yīng)索引 位置 的值,注意這里的整數(shù)切片同 Python 中的切片一樣不包含右端點(diǎn):
s[1:-1:2]
3 b
2 d
dtype: object
- loc索引器
前面講到了對 DataFrame 的列進(jìn)行選取,下面要討論其行的選取。對于表而言,有兩種索引器,一種是基于 元素 的 loc 索引器,另一種是基于 位置 的 iloc 索引器。
loc 索引器的一般形式是 loc[*, ] ,其中第一個 * 代表行的選擇,第二個 * 代表列的選擇,如果省略第二個位置寫作 loc[] ,這個 * 是指行的篩選。其中, * 的位置一共有五類合法對象,分別是:單個元素、元素列表、元素切片、布爾列表以及函數(shù),下面將依次說明。
為了演示相應(yīng)操作,先利用 set_index 方法把 Name 列設(shè)為索引,關(guān)于該函數(shù)的其他用法將在多級索引一章介紹。
df_demo = df.set_index('Name')
df_demo.head()
School Grade Gender Weight Transfer
Name
Gaopeng Yang Shanghai Jiao Tong University Freshman Female 46.0 N
Changqiang You Peking University Freshman Male 70.0 N
Mei Sun Shanghai Jiao Tong University Senior Male 89.0 N
Xiaojuan Sun Fudan University Sophomore Female 41.0 N
Gaojuan You Fudan University Sophomore Male 74.0 N
【a】 * 為單個元素
此時,直接取出相應(yīng)的行或列,如果該元素在索引中重復(fù)則結(jié)果為 DataFrame,否則為 Series :
df_demo.loc['Qiang Sun'] # 多個人叫此名字
School Grade Gender Weight Transfer
Name
Qiang Sun Tsinghua University Junior Female 53.0 N
Qiang Sun Tsinghua University Sophomore Female 40.0 N
Qiang Sun Shanghai Jiao Tong University Junior Female NaN N
df_demo.loc['Quan Zhao'] # 名字唯一
School Shanghai Jiao Tong University
Grade Junior
Gender Female
Weight 53
Transfer N
Name: Quan Zhao, dtype: object
也可以同時選擇行和列:
df_demo.loc['Qiang Sun', 'School'] # 返回Series
Name
Qiang Sun Tsinghua University
Qiang Sun Tsinghua University
Qiang Sun Shanghai Jiao Tong University
Name: School, dtype: object
df_demo.loc['Quan Zhao', 'School'] # 返回單個元素
'Shanghai Jiao Tong University'
【b】 * 為元素列表
此時,取出列表中所有元素值對應(yīng)的行或列:
df_demo.loc[['Qiang Sun','Quan Zhao'], ['School','Gender']]
School Gender
Name
Qiang Sun Tsinghua University Female
Qiang Sun Tsinghua University Female
Qiang Sun Shanghai Jiao Tong University Female
Quan Zhao Shanghai Jiao Tong University Female
【c】 * 為切片
之前的 Series 使用字符串索引時提到,如果是唯一值的起點(diǎn)和終點(diǎn)字符,那么就可以使用切片,并且包含兩個端點(diǎn),如果不唯一則報錯:
df_demo.loc['Gaojuan You':'Gaoqiang Qian', 'School':'Gender']
School Grade Gender
Name
Gaojuan You Fudan University Sophomore Male
Xiaoli Qian Tsinghua University Freshman Female
Qiang Chu Shanghai Jiao Tong University Freshman Female
Gaoqiang Qian Tsinghua University Junior Female
需要注意的是,如果 DataFrame 使用整數(shù)索引,其使用整數(shù)切片的時候和上面字符串索引的要求一致,都是 元素 切片,包含端點(diǎn)且起點(diǎn)、終點(diǎn)不允許有重復(fù)值。
df_loc_slice_demo = df_demo.copy()
df_loc_slice_demo.index = range(df_demo.shape[0],0,-1)
df_loc_slice_demo.loc[5:3]
School Grade Gender Weight Transfer
5 Fudan University Junior Female 46.0 N
4 Tsinghua University Senior Female 50.0 N
3 Shanghai Jiao Tong University Senior Female 45.0 N
df_loc_slice_demo.loc[3:5] # 沒有返回,說明不是整數(shù)位置切片
Empty DataFrame
Columns: [School, Grade, Gender, Weight, Transfer]
Index: []
【d】 * 為布爾列表
在實際的數(shù)據(jù)處理中,根據(jù)條件來篩選行是極其常見的,此處傳入 loc 的布爾列表與 DataFrame 長度相同,且列表為 True 的位置所對應(yīng)的行會被選中, False 則會被剔除。
例如,選出體重超過70kg的學(xué)生:
df_demo.loc[df_demo.Weight>70].head()
School Grade Gender Weight Transfer
Name
Mei Sun Shanghai Jiao Tong University Senior Male 89.0 N
Gaojuan You Fudan University Sophomore Male 74.0 N
Xiaopeng Zhou Shanghai Jiao Tong University Freshman Male 74.0 N
Xiaofeng Sun Tsinghua University Senior Male 71.0 N
Qiang Zheng Shanghai Jiao Tong University Senior Male 87.0 N
前面所提到的傳入元素列表,也可以通過 isin 方法返回的布爾列表等價寫出,例如選出所有大一和大四的同學(xué)信息:
df_demo.loc[df_demo.Grade.isin(['Freshman', 'Senior'])].head()
School Grade Gender Weight Transfer
Name
Gaopeng Yang Shanghai Jiao Tong University Freshman Female 46.0 N
Changqiang You Peking University Freshman Male 70.0 N
Mei Sun Shanghai Jiao Tong University Senior Male 89.0 N
Xiaoli Qian Tsinghua University Freshman Female 51.0 N
Qiang Chu Shanghai Jiao Tong University Freshman Female 52.0 N
對于復(fù)合條件而言,可以用 |(或), &(且), ~(取反) 的組合來實現(xiàn),例如選出復(fù)旦大學(xué)中體重超過70kg的大四學(xué)生,或者北大男生中體重超過80kg的非大四的學(xué)生:
condition_1_1 = df_demo.School == 'Fudan University'
condition_1_2 = df_demo.Grade == 'Senior'
condition_1_3 = df_demo.Weight > 70
condition_1 = condition_1_1 & condition_1_2 & condition_1_3
condition_2_1 = df_demo.School == 'Peking University'
condition_2_2 = df_demo.Grade == 'Senior'
condition_2_3 = df_demo.Weight > 80
condition_2 = condition_2_1 & (~condition_2_2) & condition_2_3
df_demo.loc[condition_1 | condition_2]
School Grade Gender Weight Transfer
Name
Qiang Han Peking University Freshman Male 87.0 N
Chengpeng Zhou Fudan University Senior Male 81.0 N
Changpeng Zhao Peking University Freshman Male 83.0 N
Chengpeng Qian Fudan University Senior Male 73.0 Y
【e】 * 為函數(shù)
這里的函數(shù),必須以前面的四種合法形式之一為返回值,并且函數(shù)的輸入值為 DataFrame 本身。假設(shè)仍然是上述復(fù)合條件篩選的例子,可以把邏輯寫入一個函數(shù)中再返回,需要注意的是函數(shù)的形式參數(shù) x 本質(zhì)上即為 df_demo :
def condition(x):
....: condition_1_1 = x.School == 'Fudan University'
....: condition_1_2 = x.Grade == 'Senior'
....: condition_1_3 = x.Weight > 70
....: condition_1 = condition_1_1 & condition_1_2 & condition_1_3
....: condition_2_1 = x.School == 'Peking University'
....: condition_2_2 = x.Grade == 'Senior'
....: condition_2_3 = x.Weight > 80
....: condition_2 = condition_2_1 & (~condition_2_2) & condition_2_3
....: result = condition_1 | condition_2
....: return result
....:
df_demo.loc[condition]
School Grade Gender Weight Transfer
Name
Qiang Han Peking University Freshman Male 87.0 N
Chengpeng Zhou Fudan University Senior Male 81.0 N
Changpeng Zhao Peking University Freshman Male 83.0 N
Chengpeng Qian Fudan University Senior Male 73.0 Y
此外,還支持使用 lambda 表達(dá)式,其返回值也同樣必須是先前提到的四種形式之一:
df_demo.loc[lambda x:'Quan Zhao', lambda x:'Gender']
'Female'
由于函數(shù)無法返回如 start: end: step 的切片形式,故返回切片時要用 slice 對象進(jìn)行包裝:
df_demo.loc[lambda x: slice('Gaojuan You', 'Gaoqiang Qian')]
School Grade Gender Weight Transfer
Name
Gaojuan You Fudan University Sophomore Male 74.0 N
Xiaoli Qian Tsinghua University Freshman Female 51.0 N
Qiang Chu Shanghai Jiao Tong University Freshman Female 52.0 N
Gaoqiang Qian Tsinghua University Junior Female 50.0 N
最后需要指出的是,對于 Series 也可以使用 loc 索引,其遵循的原則與 DataFrame 中用于行篩選的 loc[*] 完全一致,此處不再贅述。
在對表或者序列賦值時,應(yīng)當(dāng)在使用一層索引器后直接進(jìn)行賦值操作,這樣做是由于進(jìn)行多次索引后賦值是賦在臨時返回的 copy 副本上的,而沒有真正修改元素從而報出 SettingWithCopyWarning 警告。例如,下面給出的例子:
df_chain = pd.DataFrame([[0,0],[1,0],[-1,0]], columns=list('AB'))
df_chain
A B
0 0 0
1 1 0
2 -1 0
import warnings
with warnings.catch_warnings():
....: warnings.filterwarnings('error')
....: try:
....: df_chain[df_chain.A!=0].B = 1 # 使用方括號列索引后,再使用點(diǎn)的列索引
....: except Warning as w:
....: Warning_Msg = w
....:
print(Warning_Msg)
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
df_chain
A B
0 0 0
1 1 0
2 -1 0
df_chain.loc[df_chain.A!=0,'B'] = 1
df_chain
Out[50]:
A B
0 0 0
1 1 1
2 -1 1
- iloc索引器
iloc 的使用與 loc 完全類似,只不過是針對位置進(jìn)行篩選,在相應(yīng)的 * 位置處一共也有五類合法對象,分別是:整數(shù)、整數(shù)列表、整數(shù)切片、布爾列表以及函數(shù),函數(shù)的返回值必須是前面的四類合法對象中的一個,其輸入同樣也為 DataFrame 本身。
df_demo.iloc[1, 1] # 第二行第二列
Out[51]: 'Freshman'
df_demo.iloc[[0, 1], [0, 1]] # 前兩行前兩列
School Grade
Name
Gaopeng Yang Shanghai Jiao Tong University Freshman
Changqiang You Peking University Freshman
df_demo.iloc[1: 4, 2:4] # 切片不包含結(jié)束端點(diǎn)
Gender Weight
Name
Changqiang You Male 70.0
Mei Sun Male 89.0
Xiaojuan Sun Female 41.0
df_demo.iloc[lambda x: slice(1, 4)] # 傳入切片為返回值的函數(shù)
School Grade Gender Weight Transfer
Name
Changqiang You Peking University Freshman Male 70.0 N
Mei Sun Shanghai Jiao Tong University Senior Male 89.0 N
Xiaojuan Sun Fudan University Sophomore Female 41.0 N
在使用布爾列表的時候要特別注意,不能傳入 Series 而必須傳入序列的 values ,否則會報錯。因此,在使用布爾篩選的時候還是應(yīng)當(dāng)優(yōu)先考慮 loc 的方式。
例如,選出體重超過80kg的學(xué)生:
df_demo.iloc[(df_demo.Weight>80).values].head()
School Grade Gender Weight Transfer
Name
Mei Sun Shanghai Jiao Tong University Senior Male 89.0 N
Qiang Zheng Shanghai Jiao Tong University Senior Male 87.0 N
Qiang Han Peking University Freshman Male 87.0 N
Chengpeng Zhou Fudan University Senior Male 81.0 N
Feng Han Shanghai Jiao Tong University Sophomore Male 82.0 N
對 Series 而言同樣也可以通過 iloc 返回相應(yīng)位置的值或子序列:
df_demo.School.iloc[1]
'Peking University'
df_demo.School.iloc[1:5:2]
Name
Changqiang You Peking University
Xiaojuan Sun Fudan University
Name: School, dtype: object
- query方法
在 pandas 中,支持把字符串形式的查詢表達(dá)式傳入 query 方法來查詢數(shù)據(jù),其表達(dá)式的執(zhí)行結(jié)果必須返回布爾列表。在進(jìn)行復(fù)雜索引時,由于這種檢索方式無需像普通方法一樣重復(fù)使用 DataFrame 的名字來引用列名,一般而言會使代碼長度在不降低可讀性的前提下有所減少。
例如,將 loc 一節(jié)中的復(fù)合條件查詢例子可以如下改寫:
df.query('((School == "Fudan University")&'
....: ' (Grade == "Senior")&'
....: ' (Weight > 70))|'
....: '((School == "Peking University")&'
....: ' (Grade != "Senior")&'
....: ' (Weight > 80))')
....:
School Grade Name Gender Weight Transfer
38 Peking University Freshman Qiang Han Male 87.0 N
66 Fudan University Senior Chengpeng Zhou Male 81.0 N
99 Peking University Freshman Changpeng Zhao Male 83.0 N
131 Fudan University Senior Chengpeng Qian Male 73.0 Y
在 query 表達(dá)式中,幫用戶注冊了所有來自 DataFrame 的列名,所有屬于該 Series 的方法都可以被調(diào)用,和正常的函數(shù)調(diào)用并沒有區(qū)別,例如查詢體重超過均值的學(xué)生:
df.query('Weight > Weight.mean()').head()
School Grade Name Gender Weight Transfer
1 Peking University Freshman Changqiang You Male 70.0 N
2 Shanghai Jiao Tong University Senior Mei Sun Male 89.0 N
4 Fudan University Sophomore Gaojuan You Male 74.0 N
10 Shanghai Jiao Tong University Freshman Xiaopeng Zhou Male 74.0 N
14 Tsinghua University Senior Xiaomei Zhou Female 57.0 N
同時,在 query 中還注冊了若干英語的字面用法,幫助提高可讀性,例如: or, and, or, is in, not in 。例如,篩選出男生中不是大一大二的學(xué)生:
df.query('(Grade not in ["Freshman", "Sophomore"]) and'
....: '(Gender == "Male")').head()
....:
School Grade Name Gender Weight Transfer
2 Shanghai Jiao Tong University Senior Mei Sun Male 89.0 N
16 Tsinghua University Junior Xiaoqiang Qin Male 68.0 N
17 Tsinghua University Junior Peng Wang Male 65.0 N
18 Tsinghua University Senior Xiaofeng Sun Male 71.0 N
21 Shanghai Jiao Tong University Senior Xiaopeng Shen Male 62.0 NaN
此外,在字符串中出現(xiàn)與列表的比較時, == 和 != 分別表示元素出現(xiàn)在列表和沒有出現(xiàn)在列表,等價于 is in 和 not in,例如查詢所有大三和大四的學(xué)生:
df.query('Grade == ["Junior", "Senior"]').head()
School Grade Name Gender Weight Transfer
2 Shanghai Jiao Tong University Senior Mei Sun Male 89.0 N
7 Tsinghua University Junior Gaoqiang Qian Female 50.0 N
9 Peking University Junior Juan Xu Female NaN N
11 Tsinghua University Junior Xiaoquan Lv Female 43.0 N
12 Shanghai Jiao Tong University Senior Peng You Female 48.0 NaN
對于 query 中的字符串,如果要引用外部變量,只需在變量名前加 @ 符號。例如,取出體重位于70kg到80kg之間的學(xué)生:
low, high =70, 80
df.query('Weight.between(@low, @high)').head()
School Grade Name Gender Weight Transfer
1 Peking University Freshman Changqiang You Male 70.0 N
4 Fudan University Sophomore Gaojuan You Male 74.0 N
10 Shanghai Jiao Tong University Freshman Xiaopeng Zhou Male 74.0 N
18 Tsinghua University Senior Xiaofeng Sun Male 71.0 N
35 Peking University Freshman Gaoli Zhao Male 78.0 N
- 隨機(jī)抽樣
如果把 DataFrame 的每一行看作一個樣本,或把每一列看作一個特征,再把整個 DataFrame 看作總體,想要對樣本或特征進(jìn)行隨機(jī)抽樣就可以用 sample 函數(shù)。有時在拿到大型數(shù)據(jù)集后,想要對統(tǒng)計特征進(jìn)行計算來了解數(shù)據(jù)的大致分布,但是這很費(fèi)時間。同時,由于許多統(tǒng)計特征在等概率不放回的簡單隨機(jī)抽樣條件下,是總體統(tǒng)計特征的無偏估計,比如樣本均值和總體均值,那么就可以先從整張表中抽出一部分來做近似估計。
sample 函數(shù)中的主要參數(shù)為 n, axis, frac, replace, weights ,前三個分別是指抽樣數(shù)量、抽樣的方向(0為行、1為列)和抽樣比例(0.3則為從總體中抽出30%的樣本)。
replace 和 weights 分別是指是否放回和每個樣本的抽樣相對概率,當(dāng) replace = True 則表示有放回抽樣。例如,對下面構(gòu)造的 df_sample 以 value 值的相對大小為抽樣概率進(jìn)行有放回抽樣,抽樣數(shù)量為3。
df_sample = pd.DataFrame({'id': list('abcde'),
....: 'value': [1, 2, 3, 4, 90]})
....:
df_sample
id value
0 a 1
1 b 2
2 c 3
3 d 4
4 e 90
df_sample.sample(3, replace = True, weights = df_sample.value)
id value
4 e 90
4 e 90
4 e 90
二、多級索引
- 多級索引及其表的結(jié)構(gòu)
為了更加清晰地說明具有多級索引的 DataFrame 結(jié)構(gòu),下面新構(gòu)造一張表,讀者可以忽略這里的構(gòu)造方法,它們將會在第4小節(jié)被更詳細(xì)地講解。
np.random.seed(0)
multi_index = pd.MultiIndex.from_product([list('ABCD'),
....: df.Gender.unique()], names=('School', 'Gender'))
....:
multi_column = pd.MultiIndex.from_product([['Height', 'Weight'],
....: df.Grade.unique()], names=('Indicator', 'Grade'))
....:
df_multi = pd.DataFrame(np.c_[(np.random.randn(8,4)*5 + 163).tolist(),
....: (np.random.randn(8,4)*5 + 65).tolist()],
....: index = multi_index,
....: columns = multi_column).round(1)
....:
df_multi
Indicator Height Weight
Grade Freshman Senior Sophomore Junior Freshman Senior Sophomore Junior
School Gender
A Female 171.8 165.0 167.9 174.2 60.6 55.1 63.3 65.8
Male 172.3 158.1 167.8 162.2 71.2 71.0 63.1 63.5
B Female 162.5 165.1 163.7 170.3 59.8 57.9 56.5 74.8
Male 166.8 163.6 165.2 164.7 62.5 62.8 58.7 68.9
C Female 170.5 162.0 164.6 158.7 56.9 63.9 60.5 66.9
Male 150.2 166.3 167.3 159.3 62.4 59.1 64.9 67.1
D Female 174.3 155.7 163.2 162.1 65.3 66.5 61.8 63.2
Male 170.7 170.3 163.8 164.9 61.6 63.2 60.9 56.4
下圖通過顏色區(qū)分,標(biāo)記了 DataFrame 的結(jié)構(gòu)。與單層索引的表一樣,具備元素值、行索引和列索引三個部分。其中,這里的行索引和列索引都是 MultiIndex 類型,只不過 索引中的一個元素是元組 而不是單層索引中的標(biāo)量。例如,行索引的第四個元素為 ("B", "Male") ,列索引的第二個元素為 ("Height", "Senior") ,這里需要注意,外層連續(xù)出現(xiàn)相同的值時,第一次之后出現(xiàn)的會被隱藏顯示,使結(jié)果的可讀性增強(qiáng)。
與單層索引類似, MultiIndex 也具有名字屬性,圖中的 School 和 Gender 分別對應(yīng)了表的第一層和第二層行索引的名字, Indicator 和 Grade 分別對應(yīng)了第一層和第二層列索引的名字。
索引的名字和值屬性分別可以通過 names 和 values 獲得:
df_multi.index.names
FrozenList(['School', 'Gender'])
df_multi.columns.names
FrozenList(['Indicator', 'Grade'])
df_multi.index.values
array([('A', 'Female'), ('A', 'Male'), ('B', 'Female'), ('B', 'Male'),
('C', 'Female'), ('C', 'Male'), ('D', 'Female'), ('D', 'Male')],
dtype=object)
df_multi.columns.values
array([('Height', 'Freshman'), ('Height', 'Senior'),
('Height', 'Sophomore'), ('Height', 'Junior'),
('Weight', 'Freshman'), ('Weight', 'Senior'),
('Weight', 'Sophomore'), ('Weight', 'Junior')], dtype=object)
如果想要得到某一層的索引,則需要通過 get_level_values 獲得:
df_multi.index.get_level_values(0)
Index(['A', 'A', 'B', 'B', 'C', 'C', 'D', 'D'], dtype='object', name='School')
但對于索引而言,無論是單層還是多層,用戶都無法通過 index_obj[0] = item 的方式來修改元素,也不能通過 index_name[0] = new_name 的方式來修改名字,關(guān)于如何修改這些屬性的話題將在第三節(jié)被討論。
- 多級索引中的loc索引器
熟悉了結(jié)構(gòu)后,現(xiàn)在回到原表,將學(xué)校和年級設(shè)為索引,此時的行為多級索引,列為單級索引,由于默認(rèn)狀態(tài)的列索引不含名字,因此對應(yīng)于剛剛圖中 Indicator 和 Grade 的索引名位置是空缺的。
df_multi = df.set_index(['School', 'Grade'])
df_multi.head()
Name Gender Weight Transfer
School Grade
Shanghai Jiao Tong University Freshman Gaopeng Yang Female 46.0 N
Peking University Freshman Changqiang You Male 70.0 N
Shanghai Jiao Tong University Senior Mei Sun Male 89.0 N
Fudan University Sophomore Xiaojuan Sun Female 41.0 N
Sophomore Gaojuan You Male 74.0 N
由于多級索引中的單個元素以元組為單位,因此之前在第一節(jié)介紹的 loc 和 iloc 方法完全可以照搬,只需把標(biāo)量的位置替換成對應(yīng)的元組,不過在索引前最好對 MultiIndex 進(jìn)行排序以避免性能警告:
df_multi = df_multi.sort_index()
df_multi.loc[('Fudan University', 'Junior')].head()
Name Gender Weight Transfer
School Grade
Fudan University Junior Yanli You Female 48.0 N
Junior Chunqiang Chu Male 72.0 N
Junior Changfeng Lv Male 76.0 N
Junior Yanjuan Lv Female 49.0 NaN
Junior Gaoqiang Zhou Female 43.0 N
df_multi.loc[[('Fudan University', 'Senior'),
....: ('Shanghai Jiao Tong University', 'Freshman')]].head()
....:
Name Gender Weight Transfer
School Grade
Fudan University Senior Chengpeng Zheng Female 38.0 N
Senior Feng Zhou Female 47.0 N
Senior Gaomei Lv Female 34.0 N
Senior Chunli Lv Female 56.0 N
Senior Chengpeng Zhou Male 81.0 N
df_multi.loc[df_multi.Weight > 70].head() # 布爾列表也是可用的
Name Gender Weight Transfer
School Grade
Fudan University Freshman Feng Wang Male 74.0 N
Junior Chunqiang Chu Male 72.0 N
Junior Changfeng Lv Male 76.0 N
Senior Chengpeng Zhou Male 81.0 N
Senior Chengpeng Qian Male 73.0 Y
df_multi.loc[lambda x:('Fudan University','Junior')].head()
Name Gender Weight Transfer
School Grade
Fudan University Junior Yanli You Female 48.0 N
Junior Chunqiang Chu Male 72.0 N
Junior Changfeng Lv Male 76.0 N
Junior Yanjuan Lv Female 49.0 NaN
Junior Gaoqiang Zhou Female 43.0 N
此外,在多級索引中的元組有一種特殊的用法,可以對多層的元素進(jìn)行交叉組合后索引,但同時需要指定 loc 的列,全選則用 : 表示。其中,每一層需要選中的元素用列表存放,傳入 loc 的形式為 [(level_0_list, level_1_list), cols] 。例如,想要得到所有北大和復(fù)旦的大二大三學(xué)生,可以如下寫出:
res = df_multi.loc[(['Peking University', 'Fudan University'],
....: ['Sophomore', 'Junior']), :]
....:
res.head()
Name Gender Weight Transfer
School Grade
Peking University Sophomore Changmei Xu Female 43.0 N
Sophomore Xiaopeng Qin Male NaN N
Sophomore Mei Xu Female 39.0 N
Sophomore Xiaoli Zhou Female 55.0 N
Sophomore Peng Han Female 34.0 NaN
res.shape
(33, 4)
下面的語句和上面類似,但仍然傳入的是元素(這里為元組)的列表,它們的意義是不同的,表示的是選出北大的大三學(xué)生和復(fù)旦的大二學(xué)生:
res = df_multi.loc[[('Peking University', 'Junior'),
....: ('Fudan University', 'Sophomore')]]
....:
res.head()
Name Gender Weight Transfer
School Grade
Peking University Junior Juan Xu Female NaN N
Junior Changjuan You Female 47.0 N
Junior Gaoli Xu Female 48.0 N
Junior Gaoquan Zhou Male 70.0 N
Junior Qiang You Female 56.0 N
res.shape
(16, 4)
- IndexSlice對象
前面介紹的方法,即使在索引不重復(fù)的時候,也只能對元組整體進(jìn)行切片,而不能對每層進(jìn)行切片,也不允許將切片和布爾列表混合使用,引入 IndexSlice 對象就能解決這個問題。 Slice 對象一共有兩種形式,第一種為 loc[idx[,]] 型,第二種為 loc[idx[,],idx[,]] 型,下面將進(jìn)行介紹。為了方便演示,下面構(gòu)造一個 索引不重復(fù)的 DataFrame :
np.random.seed(0)
L1,L2 = ['A','B','C'],['a','b','c']
mul_index1 = pd.MultiIndex.from_product([L1,L2],names=('Upper', 'Lower'))
L3,L4 = ['D','E','F'],['d','e','f']
mul_index2 = pd.MultiIndex.from_product([L3,L4],names=('Big', 'Small'))
df_ex = pd.DataFrame(np.random.randint(-9,10,(9,9)),
....: index=mul_index1,
....: columns=mul_index2)
....:
df_ex
Big D E F
Small d e f d e f d e f
Upper Lower
A a 3 6 -9 -6 -6 -2 0 9 -5
b -3 3 -8 -3 -2 5 8 -4 4
c -1 0 7 -4 6 6 -9 9 -6
B a 8 5 -2 -9 -8 0 -9 1 -6
b 2 9 -7 -9 -9 -5 -4 -3 -1
c 8 6 -5 0 1 -8 -8 -2 0
C a -6 -3 2 5 9 -9 5 -6 3
b 1 2 -5 -3 -5 6 -6 3 -5
c -1 5 6 -6 6 4 7 8 -4
為了使用 silce 對象,先要進(jìn)行定義:
idx = pd.IndexSlice
【a】 loc[idx[,]] 型
這種情況并不能進(jìn)行多層分別切片,前一個 * 表示行的選擇,后一個 * 表示列的選擇,與單純的 loc 是類似的:
df_ex.loc[idx['C':, ('D', 'f'):]]
Big D E F
Small f d e f d e f
Upper Lower
C a 2 5 9 -9 5 -6 3
b -5 -3 -5 6 -6 3 -5
c 6 -6 6 4 7 8 -4
另外,也支持布爾序列的索引:
df_ex.loc[idx[:'A', lambda x:x.sum()>0]] # 列和大于0
Big D F
Small d e e
Upper Lower
A a 3 6 9
b -3 3 -4
c -1 0 9
【b】 loc[idx[,],idx[,]] 型
這種情況能夠分層進(jìn)行切片,前一個 idx 指代的是行索引,后一個是列索引。
df_ex.loc[idx[:'A', 'b':], idx['E':, 'e':]]
Big E F
Small e f e f
Upper Lower
A b -2 5 -4 4
c 6 6 9 -6
- 多級索引的構(gòu)造
前面提到了多級索引表的結(jié)構(gòu)和切片,那么除了使用 set_index 之外,如何自己構(gòu)造多級索引呢?常用的有 from_tuples, from_arrays, from_product 三種方法,它們都是 pd.MultiIndex 對象下的函數(shù)。
from_tuples 指根據(jù)傳入由元組組成的列表進(jìn)行構(gòu)造:
my_tuple = [('a','cat'),('a','dog'),('b','cat'),('b','dog')]
pd.MultiIndex.from_tuples(my_tuple, names=['First','Second'])
MultiIndex([('a', 'cat'),
('a', 'dog'),
('b', 'cat'),
('b', 'dog')],
names=['First', 'Second'])
from_arrays 指根據(jù)傳入列表中,對應(yīng)層的列表進(jìn)行構(gòu)造:
my_array = [list('aabb'), ['cat', 'dog']*2]
pd.MultiIndex.from_arrays(my_array, names=['First','Second'])
MultiIndex([('a', 'cat'),
('a', 'dog'),
('b', 'cat'),
('b', 'dog')],
names=['First', 'Second'])
from_product 指根據(jù)給定多個列表的笛卡爾積進(jìn)行構(gòu)造:
my_list1 = ['a','b']
my_list2 = ['cat','dog']
pd.MultiIndex.from_product([my_list1,
.....: my_list2],
.....: names=['First','Second'])
.....:
MultiIndex([('a', 'cat'),
('a', 'dog'),
('b', 'cat'),
('b', 'dog')],
names=['First', 'Second'])
三、索引的常用方法
- 索引層的交換和刪除
為了方便理解交換的過程,這里構(gòu)造一個三級索引的例子:
np.random.seed(0)
L1,L2,L3 = ['A','B'],['a','b'],['alpha','beta']
mul_index1 = pd.MultiIndex.from_product([L1,L2,L3],
.....: names=('Upper', 'Lower','Extra'))
.....:
L4,L5,L6 = ['C','D'],['c','d'],['cat','dog']
mul_index2 = pd.MultiIndex.from_product([L4,L5,L6],
.....: names=('Big', 'Small', 'Other'))
.....:
df_ex = pd.DataFrame(np.random.randint(-9,10,(8,8)),
.....: index=mul_index1,
.....: columns=mul_index2)
.....:
df_ex
Big C D
Small c d c d
Other cat dog cat dog cat dog cat dog
Upper Lower Extra
A a alpha 3 6 -9 -6 -6 -2 0 9
beta -5 -3 3 -8 -3 -2 5 8
b alpha -4 4 -1 0 7 -4 6 6
beta -9 9 -6 8 5 -2 -9 -8
B a alpha 0 -9 1 -6 2 9 -7 -9
beta -9 -5 -4 -3 -1 8 6 -5
b alpha 0 1 -8 -8 -2 0 -6 -3
beta 2 5 9 -9 5 -6 3 1
索引層的交換由 swaplevel 和 reorder_levels 完成,前者只能交換兩個層,而后者可以交換任意層,兩者都可以指定交換的是軸是哪一個,即行索引或列索引:
df_ex.swaplevel(0,2,axis=1).head() # 列索引的第一層和第三層交換
Other cat dog cat dog cat dog cat dog
Small c c d d c c d d
Big C C C C D D D D
Upper Lower Extra
A a alpha 3 6 -9 -6 -6 -2 0 9
beta -5 -3 3 -8 -3 -2 5 8
b alpha -4 4 -1 0 7 -4 6 6
beta -9 9 -6 8 5 -2 -9 -8
B a alpha 0 -9 1 -6 2 9 -7 -9
df_ex.reorder_levels([2,0,1],axis=0).head() # 列表數(shù)字指代原來索引中的層
Big C D
Small c d c d
Other cat dog cat dog cat dog cat dog
Extra Upper Lower
alpha A a 3 6 -9 -6 -6 -2 0 9
beta A a -5 -3 3 -8 -3 -2 5 8
alpha A b -4 4 -1 0 7 -4 6 6
beta A b -9 9 -6 8 5 -2 -9 -8
alpha B a 0 -9 1 -6 2 9 -7 -9
若想要刪除某一層的索引,可以使用 droplevel 方法:
df_ex.droplevel(1,axis=1)
Big C D
Other cat dog cat dog cat dog cat dog
Upper Lower Extra
A a alpha 3 6 -9 -6 -6 -2 0 9
beta -5 -3 3 -8 -3 -2 5 8
b alpha -4 4 -1 0 7 -4 6 6
beta -9 9 -6 8 5 -2 -9 -8
B a alpha 0 -9 1 -6 2 9 -7 -9
beta -9 -5 -4 -3 -1 8 6 -5
b alpha 0 1 -8 -8 -2 0 -6 -3
beta 2 5 9 -9 5 -6 3 1
df_ex.droplevel([0,1],axis=0)
Big C D
Small c d c d
Other cat dog cat dog cat dog cat dog
Extra
alpha 3 6 -9 -6 -6 -2 0 9
beta -5 -3 3 -8 -3 -2 5 8
alpha -4 4 -1 0 7 -4 6 6
beta -9 9 -6 8 5 -2 -9 -8
alpha 0 -9 1 -6 2 9 -7 -9
beta -9 -5 -4 -3 -1 8 6 -5
alpha 0 1 -8 -8 -2 0 -6 -3
beta 2 5 9 -9 5 -6 3 1
- 索引屬性的修改
通過 rename_axis 可以對索引層的名字進(jìn)行修改,常用的修改方式是傳入字典的映射:
df_ex.rename_axis(index={'Upper':'Changed_row'},
.....: columns={'Other':'Changed_Col'}).head()
.....:
Big C D
Small c d c d
Changed_Col cat dog cat dog cat dog cat dog
Changed_row Lower Extra
A a alpha 3 6 -9 -6 -6 -2 0 9
beta -5 -3 3 -8 -3 -2 5 8
b alpha -4 4 -1 0 7 -4 6 6
beta -9 9 -6 8 5 -2 -9 -8
B a alpha 0 -9 1 -6 2 9 -7 -9
通過 rename 可以對索引的值進(jìn)行修改,如果是多級索引需要指定修改的層號 level :
df_ex.rename(columns={'cat':'not_cat'},
.....: level=2).head()
.....:
Big C D
Small c d c d
Other not_cat dog not_cat dog not_cat dog not_cat dog
Upper Lower Extra
A a alpha 3 6 -9 -6 -6 -2 0 9
beta -5 -3 3 -8 -3 -2 5 8
b alpha -4 4 -1 0 7 -4 6 6
beta -9 9 -6 8 5 -2 -9 -8
B a alpha 0 -9 1 -6 2 9 -7 -9
傳入?yún)?shù)也可以是函數(shù),其輸入值就是索引元素:
df_ex.rename(index=lambda x:str.upper(x),
.....: level=2).head()
.....:
Big C D
Small c d c d
Other cat dog cat dog cat dog cat dog
Upper Lower Extra
A a ALPHA 3 6 -9 -6 -6 -2 0 9
BETA -5 -3 3 -8 -3 -2 5 8
b ALPHA -4 4 -1 0 7 -4 6 6
BETA -9 9 -6 8 5 -2 -9 -8
B a ALPHA 0 -9 1 -6 2 9 -7 -9
對于整個索引的元素替換,可以利用迭代器實現(xiàn):
new_values = iter(list('abcdefgh'))
df_ex.rename(index=lambda x:next(new_values),
.....: level=2)
.....:
Big C D
Small c d c d
Other cat dog cat dog cat dog cat dog
Upper Lower Extra
A a a 3 6 -9 -6 -6 -2 0 9
b -5 -3 3 -8 -3 -2 5 8
b c -4 4 -1 0 7 -4 6 6
d -9 9 -6 8 5 -2 -9 -8
B a e 0 -9 1 -6 2 9 -7 -9
f -9 -5 -4 -3 -1 8 6 -5
b g 0 1 -8 -8 -2 0 -6 -3
h 2 5 9 -9 5 -6 3 1
若想要對某個位置的元素進(jìn)行修改,在單層索引時容易實現(xiàn),即先取出索引的 values 屬性,再給對得到的列表進(jìn)行修改,最后再對 index 對象重新賦值。但是如果是多級索引的話就有些麻煩,一個解決的方案是先把某一層索引臨時轉(zhuǎn)為表的元素,然后再進(jìn)行修改,最后重新設(shè)定為索引,下面一節(jié)將介紹這些操作。
另外一個需要介紹的函數(shù)是 map ,它是定義在 Index 上的方法,與前面 rename 方法中層的函數(shù)式用法是類似的,只不過它傳入的不是層的標(biāo)量值,而是直接傳入索引的元組,這為用戶進(jìn)行跨層的修改提供了遍歷。例如,可以等價地寫出上面的字符串轉(zhuǎn)大寫的操作:
df_temp = df_ex.copy()
new_idx = df_temp.index.map(lambda x: (x[0],
.....: x[1],
.....: str.upper(x[2])))
.....:
df_temp.index = new_idx
df_temp.head()
Big C D
Small c d c d
Other cat dog cat dog cat dog cat dog
Upper Lower Extra
A a ALPHA 3 6 -9 -6 -6 -2 0 9
BETA -5 -3 3 -8 -3 -2 5 8
b ALPHA -4 4 -1 0 7 -4 6 6
BETA -9 9 -6 8 5 -2 -9 -8
B a ALPHA 0 -9 1 -6 2 9 -7 -9
關(guān)于 map 的另一個使用方法是對多級索引的壓縮,這在第四章和第五章的一些操作中是有用的:
df_temp = df_ex.copy()
new_idx = df_temp.index.map(lambda x: (x[0]+'-'+
.....: x[1]+'-'+
.....: x[2]))
.....:
df_temp.index = new_idx
df_temp.head() # 單層索引
Big C D
Small c d c d
Other cat dog cat dog cat dog cat dog
A-a-alpha 3 6 -9 -6 -6 -2 0 9
A-a-beta -5 -3 3 -8 -3 -2 5 8
A-b-alpha -4 4 -1 0 7 -4 6 6
A-b-beta -9 9 -6 8 5 -2 -9 -8
B-a-alpha 0 -9 1 -6 2 9 -7 -9
同時,也可以反向地展開:
new_idx = df_temp.index.map(lambda x:tuple(x.split('-')))
df_temp.index = new_idx
df_temp.head() # 三層索引
Big C D
Small c d c d
Other cat dog cat dog cat dog cat dog
A a alpha 3 6 -9 -6 -6 -2 0 9
beta -5 -3 3 -8 -3 -2 5 8
b alpha -4 4 -1 0 7 -4 6 6
beta -9 9 -6 8 5 -2 -9 -8
B a alpha 0 -9 1 -6 2 9 -7 -9
- 索引的設(shè)置與重置
為了說明本節(jié)的函數(shù),下面構(gòu)造一個新表:
df_new = pd.DataFrame({'A':list('aacd'),
.....: 'B':list('PQRT'),
.....: 'C':[1,2,3,4]})
.....:
df_new
A B C
0 a P 1
1 a Q 2
2 c R 3
3 d T 4
索引的設(shè)置可以使用 set_index 完成,這里的主要參數(shù)是 append ,表示是否來保留原來的索引,直接把新設(shè)定的添加到原索引的內(nèi)層:
df_new.set_index('A')
B C
A
a P 1
a Q 2
c R 3
d T 4
df_new.set_index('A', append=True)
B C
A
0 a P 1
1 a Q 2
2 c R 3
3 d T 4
可以同時指定多個列作為索引:
df_new.set_index(['A', 'B'])
C
A B
a P 1
Q 2
c R 3
d T 4
如果想要添加索引的列沒有出現(xiàn)再其中,那么可以直接在參數(shù)中傳入相應(yīng)的 Series :
my_index = pd.Series(list('WXYZ'), name='D')
df_new = df_new.set_index(['A', my_index])
df_new
B C
A D
a W P 1
X Q 2
c Y R 3
d Z T 4
reset_index 是 set_index 的逆函數(shù),其主要參數(shù)是 drop ,表示是否要把去掉的索引層丟棄,而不是添加到列中:
df_new.reset_index(['D'])
D B C
A
a W P 1
a X Q 2
c Y R 3
d Z T 4
df_new.reset_index(['D'], drop=True)
B C
A
a P 1
a Q 2
c R 3
d T 4
如果重置了所有的索引,那么 pandas 會直接重新生成一個默認(rèn)索引:
df_new.reset_index()
A D B C
0 a W P 1
1 a X Q 2
2 c Y R 3
3 d Z T 4
- 索引的變形
在某些場合下,需要對索引做一些擴(kuò)充或者剔除,更具體地要求是給定一個新的索引,把原表中相應(yīng)的索引對應(yīng)元素填充到新索引構(gòu)成的表中。例如,下面的表中給出了員工信息,需要重新制作一張新的表,要求增加一名員工的同時去掉身高列并增加性別列:
df_reindex = pd.DataFrame({"Weight":[60,70,80],
.....: "Height":[176,180,179]},
.....: index=['1001','1003','1002'])
.....:
df_reindex
Weight Height
1001 60 176
1003 70 180
1002 80 179
df_reindex.reindex(index=['1001','1002','1003','1004'],
.....: columns=['Weight','Gender'])
.....:
Weight Gender
1001 60.0 NaN
1002 80.0 NaN
1003 70.0 NaN
1004 NaN NaN
這種需求常出現(xiàn)在時間序列索引的時間點(diǎn)填充以及 ID 編號的擴(kuò)充。另外,需要注意的是原來表中的數(shù)據(jù)和新表中會根據(jù)索引自動對其,例如原先的1002號位置在1003號之后,而新表中相反,那么 reindex 中會根據(jù)元素對其,與位置無關(guān)。
還有一個與 reindex 功能類似的函數(shù)是 reindex_like ,其功能是仿照傳入的表的索引來進(jìn)行被調(diào)用表索引的變形。例如,現(xiàn)在以及存在一張表具備了目標(biāo)索引的條件,那么上述功能可以如下等價地寫出:
df_existed = pd.DataFrame(index=['1001','1002','1003','1004'],
.....: columns=['Weight','Gender'])
.....:
df_reindex.reindex_like(df_existed)
Weight Gender
1001 60.0 NaN
1002 80.0 NaN
1003 70.0 NaN
1004 NaN NaN
四、索引運(yùn)算
- 集合的運(yùn)算法則
經(jīng)常會有一種利用集合運(yùn)算來取出符合條件行的需求,例如有兩張表 A 和 B ,它們的索引都是員工編號,現(xiàn)在需要篩選出兩表索引交集的所有員工信息,此時通過 Index 上的運(yùn)算操作就很容易實現(xiàn)。
不過在此之前,不妨先復(fù)習(xí)一下常見的四種集合運(yùn)算:
SA.intersection(SB)SA.union(SB)SA.difference(SB)SA.symmetric_difference(SB)=SA∩SB?{x|x∈SAandx∈SB}=SA∪SB?{x|x∈SAorx∈SB}=SA?SB?{x|x∈SAandx?SB}=SA△SB?{x|x∈SA∪SB?SA∩SB}
- 一般的索引運(yùn)算
由于集合的元素是互異的,但是索引中可能有相同的元素,先用 unique 去重后再進(jìn)行運(yùn)算。下面構(gòu)造兩張最為簡單的示例表進(jìn)行演示:
df_set_1 = pd.DataFrame([[0,1],[1,2],[3,4]],
.....: index = pd.Index(['a','b','a'],name='id1'))
.....:
df_set_2 = pd.DataFrame([[4,5],[2,6],[7,1]],
.....: index = pd.Index(['b','b','c'],name='id2'))
.....:
id1, id2 = df_set_1.index.unique(), df_set_2.index.unique()
id1.intersection(id2)
Index(['b'], dtype='object')
id1.union(id2)
Index(['a', 'b', 'c'], dtype='object')
id1.difference(id2)
Index(['a'], dtype='object')
id1.symmetric_difference(id2)
Index(['a', 'c'], dtype='object')
上述的四類運(yùn)算還可以用等價的符號表示代替如下:
id1 & id2
Index(['b'], dtype='object')
id1 | id2
Index(['a', 'b', 'c'], dtype='object')
(id1 ^ id2) & id1
Index(['a'], dtype='object')
id1 ^ id2 # ^符號即對稱差
Index(['a', 'c'], dtype='object')
若兩張表需要做集合運(yùn)算的列并沒有被設(shè)置索引,一種辦法是先轉(zhuǎn)成索引,運(yùn)算后再恢復(fù),另一種方法是利用 isin 函數(shù),例如在重置索引的第一張表中選出id列交集的所在行:
df_set_in_col_1 = df_set_1.reset_index()
df_set_in_col_2 = df_set_2.reset_index()
df_set_in_col_1
id1 0 1
0 a 0 1
1 b 1 2
2 a 3 4
df_set_in_col_2
id2 0 1
0 b 4 5
1 b 2 6
2 c 7 1
df_set_in_col_1[df_set_in_col_1.id1.isin(df_set_in_col_2.id2)]
id1 0 1
1 b 1 2