第三章 索引

來源:https://datawhalechina.github.io/joyful-pandas/build/html/目錄/ch3.html

In [1]: import numpy as np

In [2]: import pandas as pd

一、索引器

1. 表的列索引

列索引是最常見的索引形式,一般通過 [] 來實(shí)現(xiàn)。通過 [列名] 可以從 DataFrame 中取出相應(yīng)的列,返回值為 Series ,例如從表中取出姓名一列:

In [3]: df = pd.read_csv('data/learn_pandas.csv',
   ...:                  usecols = ['School', 'Grade', 'Name', 'Gender',
   ...:                             'Weight', 'Transfer'])
   ...: 

In [4]: df['Name'].head()
Out[4]: 
0      Gaopeng Yang
1    Changqiang You
2           Mei Sun
3      Xiaojuan Sun
4       Gaojuan You
Name: Name, dtype: object

如果要取出多個(gè)列,則可以通過 [列名組成的列表] ,其返回值為一個(gè) DataFrame ,例如從表中取出性別和姓名兩列:

In [5]: df[['Gender', 'Name']].head()
Out[5]: 
   Gender            Name
0  Female    Gaopeng Yang
1    Male  Changqiang You
2    Male         Mei Sun
3  Female    Xiaojuan Sun
4    Male     Gaojuan You

此外,若要取出單列,且列名中不包含空格,則可以用 .列名 取出,這和 [列名] 是等價(jià)的:

In [6]: df.Name.head()
Out[6]: 
0      Gaopeng Yang
1    Changqiang You
2           Mei Sun
3      Xiaojuan Sun
4       Gaojuan You
Name: Name, dtype: object

2. 序列的行索引

【a】以字符串為索引的 Series

如果取出單個(gè)索引的對應(yīng)元素,則可以使用 [item] ,若 Series 只有單個(gè)值對應(yīng),則返回這個(gè)標(biāo)量值,如果有多個(gè)值對應(yīng),則返回一個(gè) Series

In [7]: s = pd.Series([1, 2, 3, 4, 5, 6],
   ...:                index=['a', 'b', 'a', 'a', 'a', 'c'])
   ...: 

In [8]: s['a'] #把對應(yīng)的所有元素都取出來
Out[8]: 
a    1
a    3
a    4
a    5
dtype: int64

In [9]: s['b']
Out[9]: 2

如果取出多個(gè)索引的對應(yīng)元素,則可以使用 [items的列表]

In [10]: s[['c', 'b']]
Out[10]: 
c    6
b    2
dtype: int64

如果想要取出某兩個(gè)索引之間的元素,并且這兩個(gè)索引是在整個(gè)索引中唯一出現(xiàn),則可以使用切片,同時(shí)需要注意這里的切片會包含兩個(gè)端點(diǎn):

In [11]: s['c': 'b': -2] #步長是-2,從后往前取
Out[11]: 
c    6
a    4
b    2
dtype: int64

【b】以整數(shù)為索引的 Series

在使用數(shù)據(jù)的讀入函數(shù)時(shí),如果不特別指定所對應(yīng)的列作為索引,那么會生成從0開始的整數(shù)索引作為默認(rèn)索引。當(dāng)然,任意一組符合長度要求的整數(shù)都可以作為索引。

和字符串一樣,如果使用 [int][int_list] ,則可以取出對應(yīng)索引 元素 的值:

In [12]: s = pd.Series(['a', 'b', 'c', 'd', 'e', 'f'],
   ....:               index=[1, 3, 1, 2, 5, 4])
   ....: 

In [13]: s[1]
Out[13]: 
1    a
1    c
dtype: object

In [14]: s[[2,3]] 
Out[14]: 
2    d
3    b
dtype: object

如果使用整數(shù)切片,則會取出對應(yīng)索引 位置 的值,注意這里的整數(shù)切片同 Python 中的切片一樣不包含右端點(diǎn):

In [15]: s[1:-1:2]#1和-1是位置,而不是索引值
Out[15]: 
3    b
2    d
dtype: object

關(guān)于索引類型的說明

如果不想陷入麻煩,那么請不要把純浮點(diǎn)以及任何混合類型(字符串、整數(shù)、浮點(diǎn)類型等的混合)作為索引,否則可能會在具體的操作時(shí)報(bào)錯(cuò)或者返回非預(yù)期的結(jié)果,并且在實(shí)際的數(shù)據(jù)分析中也不存在這樣做的動(dòng)機(jī)。

3. loc索引器

前面講到了對 DataFrame 的列進(jìn)行選取,下面要討論其行的選取。對于表而言,有兩種索引器:

  • 一種是基于 元素 的 loc 索引器

  • 另一種是基于 位置 的 iloc 索引器

loc 索引器的一般形式是 loc[*, *] ,其中第一個(gè) * 代表行的選擇,第二個(gè) * 代表列的選擇,如果省略第二個(gè)位置寫作 loc[*] ,這個(gè) * 是指行的篩選。其中, * 的位置一共有五類合法對象,分別是:單個(gè)元素、元素列表、元素切片、布爾列表以及函數(shù),下面將依次說明。

為了演示相應(yīng)操作,先利用 set_index 方法把 Name 列設(shè)為索引,關(guān)于該函數(shù)的其他用法將在多級索引一章介紹。

In [16]: df_demo = df.set_index('Name')

In [17]: df_demo.head()
Out[17]: 
                                       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】 * 為單個(gè)元素

此時(shí),直接取出相應(yīng)的行或列,如果該元素在索引中重復(fù)則結(jié)果為 DataFrame,否則為 Series

In [18]: df_demo.loc['Qiang Sun'] # 多個(gè)人叫此名字,結(jié)果為 DataFrame
Out[18]: 
                                  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

In [19]: df_demo.loc['Quan Zhao'] # 名字唯一結(jié)果為 Series
Out[19]: 
School      Shanghai Jiao Tong University
Grade                              Junior
Gender                             Female
Weight                                 53
Transfer                                N
Name: Quan Zhao, dtype: object

也可以同時(shí)選擇行和列:

In [20]: df_demo.loc['Qiang Sun', 'School'] # 返回Series
Out[20]: 
Name
Qiang Sun              Tsinghua University
Qiang Sun              Tsinghua University
Qiang Sun    Shanghai Jiao Tong University
Name: School, dtype: object

In [21]: df_demo.loc['Quan Zhao', 'School'] # 返回單個(gè)元素
Out[21]: 'Shanghai Jiao Tong University'

【b】 * 為元素列表

此時(shí),取出列表中所有元素值對應(yīng)的行或列:

In [22]: df_demo.loc[['Qiang Sun','Quan Zhao'], ['School','Gender']]
Out[22]: 
                                  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 使用字符串索引時(shí)提到,如果是唯一值(必須是唯一的)的起點(diǎn)和終點(diǎn)字符,那么就可以使用切片,并且包含兩個(gè)端點(diǎn),如果不唯一則報(bào)錯(cuò):

In [23]: df_demo.loc['Gaojuan You':'Gaoqiang Qian', 'School':'Gender']
Out[23]: 
                                      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ù)切片的時(shí)候和上面字符串索引的要求一致,都是 元素 切片,包含端點(diǎn)且起點(diǎn)、終點(diǎn)不允許有重復(fù)值。

In [24]: df_loc_slice_demo = df_demo.copy()

In [25]: df_loc_slice_demo.index = range(df_demo.shape[0],0,-1)

In [26]: df_loc_slice_demo.loc[5:3]
Out[26]: 
                          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

In [27]: df_loc_slice_demo.loc[3:5] # 沒有返回,說明不是整數(shù)位置切片,因?yàn)樯厦娴乃饕樞蚴?,4,3,所以沒有索引元素是3到5的區(qū)間
Out[27]: 
Empty DataFrame
Columns: [School, Grade, Gender, Weight, Transfer]
Index: []

【d】 * 為布爾列表

在實(shí)際的數(shù)據(jù)處理中,根據(jù)條件來篩選行是極其常見的,此處傳入 loc 的布爾列表與 DataFrame 長度相同,且列表為 True 的位置所對應(yīng)的行會被選中, False 則會被剔除。

例如,選出體重超過70kg的學(xué)生:

In [28]: df_demo.loc[df_demo.Weight>70].head()
Out[28]: 
                                      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 方法返回的布爾列表等價(jià)寫出,例如選出所有大一和大四的同學(xué)信息:

In [29]: df_demo.loc[df_demo.Grade.isin(['Freshman', 'Senior'])].head() #與之前的de_demo.loc['Qiang Sun', 'School']的區(qū)別是不用設(shè)置index了,并且返回的不是series而是dataframe
Out[29]: 
                                       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ù)合條件而言,可以用 |(或), &(且), ~(取反) 的組合來實(shí)現(xiàn),例如選出復(fù)旦大學(xué)中體重超過70kg的大四學(xué)生,或者北大男生中體重超過80kg的非大四的學(xué)生:

In [30]: condition_1_1 = df_demo.School == 'Fudan University'

In [31]: condition_1_2 = df_demo.Grade == 'Senior'

In [32]: condition_1_3 = df_demo.Weight > 70

In [33]: condition_1 = condition_1_1 & condition_1_2 & condition_1_3

In [34]: condition_2_1 = df_demo.School == 'Peking University'

In [35]: condition_2_2 = df_demo.Grade == 'Senior'

In [36]: condition_2_3 = df_demo.Weight > 80

In [37]: condition_2 = condition_2_1 & (~condition_2_2) & condition_2_3

In [38]: df_demo.loc[condition_1 | condition_2]
Out[38]: 
                           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

練一練

select_dtypes 是一個(gè)實(shí)用函數(shù),它能夠從表中選出相應(yīng)類型的列,若要選出所有數(shù)值型的列,只需使用 .select_dtypes('number') ,請利用布爾列表選擇的方法結(jié)合 DataFramedtypes 屬性在 learn_pandas 數(shù)據(jù)集上實(shí)現(xiàn)這個(gè)功能。

df.loc[:,df.dtypes=='float64'] #:可以選擇所有行數(shù)

【e】 * 為函數(shù)

這里的函數(shù),必須以前面的四種合法形式之一為返回值,并且函數(shù)的輸入值為 DataFrame 本身。假設(shè)仍然是上述復(fù)合條件篩選的例子,可以把邏輯寫入一個(gè)函數(shù)中再返回,需要注意的是函數(shù)的形式參數(shù) x 本質(zhì)上即為 df_demo

In [39]: 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
   ....: 

In [40]: df_demo.loc[condition]
Out[40]: 
                           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á)式,其返回值也同樣必須是先前提到的四種形式之一:

In [41]: df_demo.loc[lambda x:'Quan Zhao', lambda x:'Gender']
Out[41]: 'Female'

由于函數(shù)無法返回如 start: end: step 的切片形式,故返回切片時(shí)要用 slice 對象進(jìn)行包裝:

In [42]: df_demo.loc[lambda x: slice('Gaojuan You', 'Gaoqiang Qian')] #結(jié)果和df_demo.loc['Gaojuan You':'Gaoqiang Qian']效果相同
Out[42]: 
                                      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[*] 完全一致,此處不再贅述。

不要使用鏈?zhǔn)劫x值

在對表或者序列賦值時(shí),應(yīng)當(dāng)在使用一層索引器后直接進(jìn)行賦值操作,這樣做是由于進(jìn)行多次索引后賦值是賦在臨時(shí)返回的 copy 副本上的,而沒有真正修改元素從而報(bào)出 SettingWithCopyWarning 警告。例如,下面給出的例子:

In [43]: df_chain = pd.DataFrame([[0,0],[1,0],[-1,0]], columns=list('AB'))

In [44]: df_chain
Out[44]: 
   A  B
0  0  0
1  1  0
2 -1  0

In [45]: import warnings

In [46]: 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
   ....: 

In [47]: 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

In [48]: df_chain
Out[48]: 
   A  B
0  0  0
1  1  0
2 -1  0

In [49]: df_chain.loc[df_chain.A!=0,'B'] = 1

In [50]: df_chain
Out[50]: 
   A  B
0  0  0
1  1  1
2 -1  1

4. iloc索引器

iloc 的使用與 loc 完全類似,只不過是針對位置進(jìn)行篩選,在相應(yīng)的 * 位置處一共也有五類合法對象,分別是:整數(shù)、整數(shù)列表、整數(shù)切片、布爾列表以及函數(shù),函數(shù)的返回值必須是前面的四類合法對象中的一個(gè),其輸入同樣也為 DataFrame 本身。

注意和loc的切片不同在于,不包含結(jié)束端點(diǎn)

In [51]: df_demo.iloc[1, 1] # 第二行第二列
Out[51]: 'Freshman'

In [52]: df_demo.iloc[[0, 1], [0, 1]] # 前兩行前兩列
Out[52]: 
                                       School     Grade
Name                                                   
Gaopeng Yang    Shanghai Jiao Tong University  Freshman
Changqiang You              Peking University  Freshman

In [53]: df_demo.iloc[1: 4, 2:4] # 切片不包含結(jié)束端點(diǎn)
Out[53]: 
                Gender  Weight
Name                          
Changqiang You    Male    70.0
Mei Sun           Male    89.0
Xiaojuan Sun    Female    41.0

In [54]: df_demo.iloc[lambda x: slice(1, 4)] # 傳入切片為返回值的函數(shù)
Out[54]: 
                                       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

在使用布爾列表的時(shí)候要特別注意,不能傳入 Series 而必須傳入序列的 values ,否則會報(bào)錯(cuò)。因此,在使用布爾篩選的時(shí)候還是應(yīng)當(dāng)優(yōu)先考慮 loc 的方式。

例如,選出體重超過80kg的學(xué)生:

In [55]: df_demo.iloc[(df_demo.Weight>80).values].head()
Out[55]: 
                                       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)位置的值或子序列:

In [56]: df_demo.School.iloc[1]
Out[56]: 'Peking University'

In [57]: df_demo.School.iloc[1:5:2]
Out[57]: 
Name
Changqiang You    Peking University
Xiaojuan Sun       Fudan University
Name: School, dtype: object

5. query方法(更方便)

pandas 中,支持把字符串形式的查詢表達(dá)式傳入 query 方法來查詢數(shù)據(jù),其表達(dá)式的執(zhí)行結(jié)果必須返回布爾列表。在進(jìn)行復(fù)雜索引時(shí),由于這種檢索方式無需像普通方法一樣重復(fù)使用 DataFrame 的名字來引用列名,一般而言會使代碼長度在不降低可讀性的前提下有所減少。

例如,將 loc 一節(jié)中的復(fù)合條件查詢例子可以如下改寫:

In [58]: df.query('((School == "Fudan University")&'
   ....:          ' (Grade == "Senior")&'
   ....:          ' (Weight > 70))|'
   ....:          '((School == "Peking University")&'
   ....:          ' (Grade != "Senior")&'
   ....:          ' (Weight > 80))')
   ....: 
Out[58]: 
                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é)生:

In [59]: df.query('Weight > Weight.mean()').head()
Out[59]: 
                           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中引用帶空格的列名

對于含有空格的列名,需要使用 `col name` 的方式進(jìn)行引用。

同時(shí),在 query 中還注冊了若干英語的字面用法,幫助提高可讀性,例如: or, and,or, is in, not in 。例如,篩選出男生中不是大一大二的學(xué)生:

In [60]: df.query('(Grade not in ["Freshman", "Sophomore"]) and'
   ....:          '(Gender == "Male")').head()
   ....: 
Out[60]: 
                           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)與列表的比較時(shí), ==!= 分別表示元素出現(xiàn)在列表和沒有出現(xiàn)在列表,等價(jià)于 is innot in,例如查詢所有大三和大四的學(xué)生:

In [61]: df.query('Grade == ["Junior", "Senior"]').head()
Out[61]: 
                           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é)生:

In [62]: low, high =70, 80 #外部變量

In [63]: df.query('Weight.between(@low, @high)').head()
Out[63]: 
                           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

6. 隨機(jī)抽樣sample

如果把 DataFrame 的每一行看作一個(gè)樣本,或把每一列看作一個(gè)特征,再把整個(gè) DataFrame 看作總體,想要對樣本或特征進(jìn)行隨機(jī)抽樣就可以用 sample 函數(shù)。有時(shí)在拿到大型數(shù)據(jù)集后,想要對統(tǒng)計(jì)特征進(jìn)行計(jì)算來了解數(shù)據(jù)的大致分布,但是這很費(fèi)時(shí)間。同時(shí),由于許多統(tǒng)計(jì)特征在等概率不放回的簡單隨機(jī)抽樣條件下,是總體統(tǒng)計(jì)特征的無偏估計(jì),比如樣本均值和總體均值,那么就可以先從整張表中抽出一部分來做近似估計(jì)。

sample 函數(shù)中的主要參數(shù)為 n, axis, frac, replace, weights ,前三個(gè)分別是指抽樣數(shù)量、抽樣的方向(0為行、1為列)和抽樣比例(0.3則為從總體中抽出30%的樣本)。

replaceweights 分別是指是否放回和每個(gè)樣本的抽樣相對概率,當(dāng) replace = True則表示有放回抽樣。例如,對下面構(gòu)造的 df_samplevalue 值的相對大小為抽樣概率進(jìn)行有放回抽樣,抽樣數(shù)量為3。

In [64]: df_sample = pd.DataFrame({'id': list('abcde'),
   ....:                           'value': [1, 2, 3, 4, 90]})
   ....: 

In [65]: df_sample
Out[65]: 
  id  value
0  a      1
1  b      2
2  c      3
3  d      4
4  e     90

In [66]: df_sample.sample(3, replace = True, weights = df_sample.value)
Out[66]: 
  id  value
4  e     90
4  e     90
4  e     90

二、多級索引

1. 多級索引及其表的結(jié)構(gòu)

為了更加清晰地說明具有多級索引的 DataFrame 結(jié)構(gòu),下面新構(gòu)造一張表,讀者可以忽略這里的構(gòu)造方法,它們將會在第4小節(jié)被更詳細(xì)地講解。

In [67]: np.random.seed(0)

In [68]: multi_index = pd.MultiIndex.from_product([list('ABCD'),
   ....:               df.Gender.unique()], names=('School', 'Gender')) #行索引名稱
   ....: 

In [69]: multi_column = pd.MultiIndex.from_product([['Height', 'Weight'],
   ....:                df.Grade.unique()], names=('Indicator', 'Grade')) #列索引名稱
   ....: 

In [70]: 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)
   ....: 

In [71]: df_multi
Out[71]: 
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)。與單層索引的表一樣,具備元素值、行索引和列索引三個(gè)部分。其中,這里的行索引和列索引都是 MultiIndex 類型,只不過 索引中的一個(gè)元素是元組 而不是單層索引中的標(biāo)量。例如,行索引的第四個(gè)元素為 ("B", "Male") ,列索引的第二個(gè)元素為 ("Height", "Senior") ,這里需要注意,外層連續(xù)出現(xiàn)相同的值時(shí),第一次之后出現(xiàn)的會被隱藏顯示,使結(jié)果的可讀性增強(qiáng)。

與單層索引類似, MultiIndex 也具有名字屬性,圖中的 SchoolGender 分別對應(yīng)了表的第一層和第二層行索引的名字, IndicatorGrade 分別對應(yīng)了第一層和第二層列索引的名字。

索引的名字和值屬性分別可以通過 namesvalues 獲得:

In [72]: df_multi.index.names #行索引的名稱
Out[72]: FrozenList(['School', 'Gender'])

In [73]: df_multi.columns.names #列索引的名稱
Out[73]: FrozenList(['Indicator', 'Grade'])

In [74]: df_multi.index.values #行索引的組合值
Out[74]: 
array([('A', 'Female'), ('A', 'Male'), ('B', 'Female'), ('B', 'Male'),
       ('C', 'Female'), ('C', 'Male'), ('D', 'Female'), ('D', 'Male')],
      dtype=object)

In [75]: df_multi.columns.values #列索引的組合值
Out[75]: 
array([('Height', 'Freshman'), ('Height', 'Senior'),
       ('Height', 'Sophomore'), ('Height', 'Junior'),
       ('Weight', 'Freshman'), ('Weight', 'Senior'),
       ('Weight', 'Sophomore'), ('Weight', 'Junior')], dtype=object)

如果想要得到某一層的索引,則需要通過 get_level_values 獲得:

In [76]: df_multi.index.get_level_values(0)
Out[76]: Index(['A', 'A', 'B', 'B', 'C', 'C', 'D', 'D'], dtype='object', name='School')
    df_multi.index.get_level_values(1)
    '''
Index(['Female', 'Male', 'Female', 'Male', 'Female', 'Male', 'Female', 'Male'], dtype='object', name='Gender')
    '''

但對于索引而言,無論是單層還是多層,用戶都無法通過 index_obj[0] = item 的方式來修改元素,也不能通過 index_name[0] = new_name 的方式來修改名字,關(guān)于如何修改這些屬性的話題將在第三節(jié)被討論。

2. 多級索引中的loc索引器

熟悉了結(jié)構(gòu)后,現(xiàn)在回到原表,將學(xué)校和年級設(shè)為索引,此時(shí)的行是多級索引,列為單級索引,由于默認(rèn)狀態(tài)的列索引不含名字,因此對應(yīng)于剛剛圖中 IndicatorGrade 的索引名位置是空缺的。

In [77]: df_multi = df.set_index(['School', 'Grade'])

In [78]: df_multi.head()
Out[78]: 
                                                   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

由于多級索引中的單個(gè)元素以元組為單位,因此之前在第一節(jié)介紹的 lociloc 方法完全可以照搬,只需把標(biāo)量的位置替換成對應(yīng)的元組,不過在索引前最好對 MultiIndex 進(jìn)行排序以避免性能警告:

In [79]: df_multi = df_multi.sort_index()

In [80]: df_multi.loc[('Fudan University', 'Junior')].head() #把標(biāo)量的位置替換成對應(yīng)的元組
Out[80]: 
                                  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

In [81]: df_multi.loc[[('Fudan University', 'Senior'),
   ....:               ('Shanghai Jiao Tong University', 'Freshman')]].head() #兩個(gè)元組
   ....: 
Out[81]: 
                                    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

In [82]: df_multi.loc[df_multi.Weight > 70].head() # 布爾列表也是可用的
Out[82]: 
                                     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

In [83]: df_multi.loc[lambda x:('Fudan University','Junior')].head()
Out[83]: 
                                  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

練一練

與單層索引類似,若存在重復(fù)元素,則不能使用切片,請去除重復(fù)索引后給出一個(gè)元素切片的例子。

df_multi = df.drop_duplicates(['School','Grade']).set_index(['School', 'Grade']).sort_index() #必須先排序才能用切片
df_multi.loc[('Fudan University', 'Freshman'):('Peking University', 'Senior')]       

此外,在多級索引中的元組有一種特殊的用法,可以對多層的元素進(jìn)行交叉組合后索引,但同時(shí)需要指定 loc 的列,全選則用 : 表示。其中,每一層需要選中的元素用列表存放,傳入 loc 的形式為 [(level_0_list, level_1_list), cols] 。例如,想要得到所有北大和復(fù)旦的大二大三學(xué)生,可以如下寫出:

In [84]: res = df_multi.loc[(['Peking University', 'Fudan University'],
   ....:                     ['Sophomore', 'Junior']), :] #不同索引排列組合,即相當(dāng)于4個(gè)索引類型
   ....: 

In [85]: res.head()
Out[85]: 
                                     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

In [86]: res.shape
Out[86]: (33, 4)

下面的語句和上面類似,但仍然傳入的是元素(這里為元組)的列表,它們的意義是不同的,表示的是選出北大的大三學(xué)生和復(fù)旦的大二學(xué)生:

In [87]: res = df_multi.loc[[('Peking University', 'Junior'),
   ....:                     ('Fudan University', 'Sophomore')]] #只有2個(gè)索引類型
   ....: 

In [88]: res.head()
Out[88]: 
                                   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

In [89]: res.shape
Out[89]: (16, 4)

3. IndexSlice對象(對每層進(jìn)行切片)

前面介紹的方法,即使在索引不重復(fù)的時(shí)候,也只能對元組整體進(jìn)行切片,而不能對每層進(jìn)行切片,也不允許將切片和布爾列表混合使用,引入 IndexSlice 對象就能解決這個(gè)問題。 Slice 對象一共有兩種形式,第一種為 loc[idx[*,*]] 型,第二種為 loc[idx[*,*],idx[*,*]] 型,下面將進(jìn)行介紹。為了方便演示,下面構(gòu)造一個(gè) 索引不重復(fù)的 DataFrame

In [90]: np.random.seed(0)

In [91]: L1,L2 = ['A','B','C'],['a','b','c']

In [92]: mul_index1 = pd.MultiIndex.from_product([L1,L2],names=('Upper', 'Lower'))

In [93]: L3,L4 = ['D','E','F'],['d','e','f']

In [94]: mul_index2 = pd.MultiIndex.from_product([L3,L4],names=('Big', 'Small'))

In [95]: df_ex = pd.DataFrame(np.random.randint(-9,10,(9,9)),
   ....:                     index=mul_index1,
   ....:                     columns=mul_index2)
   ....: 

In [96]: df_ex
Out[96]: 
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)行定義:

In [97]: idx = pd.IndexSlice

【a】 loc[idx[*,*]]

這種情況并不能進(jìn)行多層分別切片,前一個(gè) * 表示行的選擇,后一個(gè) * 表示列的選擇,與單純的 loc 是類似的:

In [98]: df_ex.loc[idx['C':, ('D', 'f'):]]
Out[98]: 
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

另外,也支持布爾序列的索引:

In [99]: df_ex.loc[idx[:'A', lambda x:x.sum()>0]] # 列和大于0
Out[99]: 
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)行切片,前一個(gè) idx 指代的是行索引,后一個(gè)是列索引。

In [100]: df_ex.loc[idx[:'A', 'b':], idx['E':, 'e':]]
Out[100]: 
Big          E     F   
Small        e  f  e  f
Upper Lower            
A     b     -2  5 -4  4
      c      6  6  9 -6

4. 多級索引的構(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)造:

In [101]: my_tuple = [('a','cat'),('a','dog'),('b','cat'),('b','dog')]

In [102]: pd.MultiIndex.from_tuples(my_tuple, names=['First','Second'])
Out[102]: 
MultiIndex([('a', 'cat'),
            ('a', 'dog'),
            ('b', 'cat'),
            ('b', 'dog')],
           names=['First', 'Second'])

from_arrays 指根據(jù)傳入列表中,對應(yīng)層的列表進(jìn)行構(gòu)造:

In [103]: my_array = [list('aabb'), ['cat', 'dog']*2] #數(shù)量要對應(yīng),前面是4個(gè)元素,后面也要是4個(gè)元素

In [104]: pd.MultiIndex.from_arrays(my_array, names=['First','Second'])
Out[104]: 
MultiIndex([('a', 'cat'),
            ('a', 'dog'),
            ('b', 'cat'),
            ('b', 'dog')],
           names=['First', 'Second'])

from_product 指根據(jù)給定多個(gè)列表的笛卡爾積進(jìn)行構(gòu)造:

In [105]: my_list1 = ['a','b']

In [106]: my_list2 = ['cat','dog']

In [107]: pd.MultiIndex.from_product([my_list1,
   .....:                             my_list2],
   .....:                            names=['First','Second'])
   .....: 
Out[107]: 
MultiIndex([('a', 'cat'),
            ('a', 'dog'),
            ('b', 'cat'),
            ('b', 'dog')],
           names=['First', 'Second'])

三、索引的常用方法

1. 索引層的交換和刪除

為了方便理解交換的過程,這里構(gòu)造一個(gè)三級索引的例子:

In [108]: np.random.seed(0)

In [109]: L1,L2,L3 = ['A','B'],['a','b'],['alpha','beta']

In [110]: mul_index1 = pd.MultiIndex.from_product([L1,L2,L3],
   .....:              names=('Upper', 'Lower','Extra'))
   .....: 

In [111]: L4,L5,L6 = ['C','D'],['c','d'],['cat','dog']

In [112]: mul_index2 = pd.MultiIndex.from_product([L4,L5,L6],
   .....:              names=('Big', 'Small', 'Other'))
   .....: 

In [113]: df_ex = pd.DataFrame(np.random.randint(-9,10,(8,8)),
   .....:                         index=mul_index1,
   .....:                         columns=mul_index2)
   .....: 

In [114]: df_ex
Out[114]: 
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

索引層的交換由 swaplevelreorder_levels 完成,前者只能交換兩個(gè)層,而后者可以交換任意層,兩者都可以指定交換的是軸是哪一個(gè),即行索引或列索引:

In [115]: df_ex.swaplevel(0,2,axis=1).head() # 列索引的第一層和第三層交換
Out[115]: 
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

In [116]: df_ex.reorder_levels([2,0,1],axis=0).head() # 列表數(shù)字指代原來索引中的層
Out[116]: 
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

軸之間的索引交換

這里只涉及行或列索引內(nèi)部的交換,不同方向索引之間的交換將在第五章中被討論。

刪除droplevel

若想要?jiǎng)h除某一層的索引,可以使用 droplevel 方法:

In [117]: df_ex.droplevel(1,axis=1)
Out[117]: 
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

In [118]: df_ex.droplevel([0,1],axis=0)
Out[118]: 
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

2. 索引屬性的修改rename_axis, rename

通過 rename_axis 可以對索引層的名字進(jìn)行修改,常用的修改方式是傳入字典的映射:

In [119]: df_ex.rename_axis(index={'Upper':'Changed_row'},
   .....:                   columns={'Other':'Changed_Col'}).head()
   .....: 
Out[119]: 
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

In [120]: df_ex.rename(columns={'cat':'not_cat'},
   .....:              level=2).head()
   .....: 
Out[120]: 
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ù),其輸入值就是索引元素

In [121]: df_ex.rename(index=lambda x:str.upper(x),
   .....:              level=2).head() #level=2是指Extra那列的索引
   .....: 
Out[121]: 
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

練一練

嘗試在 rename_axis 中使用函數(shù)完成與例子中一樣的功能。

df_ex.rename_axis(index=lambda x:x.replace('Upper',"changed_row"), columns=lambda x:x.replace('Other', 'Changed_Col')).head()
或者:
df_ex.rename_axis(index=lambda x: "changed_row" if x == 'Upper' else x, columns=lambda x:x.replace('Other', 'Changed_Col')).head() #if后面一定要有else,不然lambda不知道要返回什么值

對于整個(gè)索引的元素替換,可以利用迭代器實(shí)現(xiàn):

In [122]: new_values = iter(list('abcdefgh'))

In [123]: df_ex.rename(index=lambda x:next(new_values),
   .....:              level=2) #這里替換的是原來的數(shù)列里的alpha,beta等
   .....: 
Out[123]: 
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

若想要對某個(gè)位置的元素進(jìn)行修改,在單層索引時(shí)容易實(shí)現(xiàn),即先取出索引的 values屬性,再給對得到的列表進(jìn)行修改,最后再對 index 對象重新賦值。但是如果是多級索引的話就有些麻煩,一個(gè)解決的方案是先把某一層索引臨時(shí)轉(zhuǎn)為表的元素,然后再進(jìn)行修改,最后重新設(shè)定為索引,下面一節(jié)將介紹這些操作。

map方法的應(yīng)用

另外一個(gè)需要介紹的函數(shù)是 map ,它是定義在 Index 上的方法,與前面 rename 方法中層的函數(shù)式用法是類似的,只不過它傳入的不是層的標(biāo)量值,而是直接傳入索引的元組,這為用戶進(jìn)行跨層的修改提供了遍歷。例如,可以等價(jià)地寫出上面的字符串轉(zhuǎn)大寫的操作:

In [124]: df_temp = df_ex.copy()

In [125]: new_idx = df_temp.index.map(lambda x: (x[0],
   .....:                                        x[1],
   .....:                                        str.upper(x[2])))
   .....: 

In [126]: df_temp.index = new_idx

In [127]: df_temp.head()
Out[127]: 
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 的另一個(gè)使用方法是對多級索引的壓縮,這在第四章和第五章的一些操作中是有用的:

In [128]: df_temp = df_ex.copy()

In [129]: new_idx = df_temp.index.map(lambda x: (x[0]+'-'+
   .....:                                        x[1]+'-'+
   .....:                                        x[2]))
   .....: 

In [130]: df_temp.index = new_idx

In [131]: df_temp.head() # 單層索引
Out[131]: 
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

同時(shí),也可以反向地展開:

In [132]: new_idx = df_temp.index.map(lambda x:tuple(x.split('-')))

In [133]: df_temp.index = new_idx

In [134]: df_temp.head() # 三層索引
Out[134]: 
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

3. 索引的設(shè)置與重置set_index, reset_index

為了說明本節(jié)的函數(shù),下面構(gòu)造一個(gè)新表:

In [135]: df_new = pd.DataFrame({'A':list('aacd'),
   .....:                        'B':list('PQRT'),
   .....:                        'C':[1,2,3,4]})
   .....: 

In [136]: df_new
Out[136]: 
   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)層:

In [137]: df_new.set_index('A')
Out[137]: 
   B  C
A      
a  P  1
a  Q  2
c  R  3
d  T  4

In [138]: df_new.set_index('A', append=True) #保留原來的索引
Out[138]: 
     B  C
  A      
0 a  P  1
1 a  Q  2
2 c  R  3
3 d  T  4

可以同時(shí)指定多個(gè)列作為索引

In [139]: df_new.set_index(['A', 'B'])
Out[139]: 
     C
A B   
a P  1
  Q  2
c R  3
d T  4

如果想要添加索引的列沒有出現(xiàn)再其中,那么可以直接在參數(shù)中傳入相應(yīng)的 Series

In [140]: my_index = pd.Series(list('WXYZ'), name='D')

In [141]: df_new = df_new.set_index(['A', my_index])

In [142]: df_new
Out[142]: 
     B  C
A D      
a W  P  1
  X  Q  2
c Y  R  3
d Z  T  4

reset_indexset_index 的逆函數(shù),其主要參數(shù)是 drop ,表示是否要把去掉的索引層丟棄,而不是添加到列中:

In [143]: df_new.reset_index(['D'])
Out[143]: 
   D  B  C
A         
a  W  P  1
a  X  Q  2
c  Y  R  3
d  Z  T  4

In [144]: df_new.reset_index(['D'], drop=True)
Out[144]: 
   B  C
A      
a  P  1
a  Q  2
c  R  3
d  T  4

如果重置了所有的索引,那么 pandas 會直接重新生成一個(gè)默認(rèn)索引:

In [145]: df_new.reset_index()
Out[145]: 
   A  D  B  C
0  a  W  P  1
1  a  X  Q  2
2  c  Y  R  3
3  d  Z  T  4

4. 索引的變形reindex,reindex_like

在某些場合下,需要對索引做一些擴(kuò)充或者剔除,更具體地要求是給定一個(gè)新的索引,把原表中相應(yīng)的索引對應(yīng)元素填充到新索引構(gòu)成的表中。例如,下面的表中給出了員工信息,需要重新制作一張新的表,要求增加一名員工的同時(shí)去掉身高列并增加性別列:

In [146]: df_reindex = pd.DataFrame({"Weight":[60,70,80],
   .....:                            "Height":[176,180,179]},
   .....:                            index=['1001','1003','1002'])
   .....: 

In [147]: df_reindex
Out[147]: 
      Weight  Height
1001      60     176
1003      70     180
1002      80     179

In [148]: df_reindex.reindex(index=['1001','1002','1003','1004'],
   .....:                    columns=['Weight','Gender'])
   .....: 
Out[148]: 
      Weight  Gender
1001    60.0     NaN
1002    80.0     NaN
1003    70.0     NaN
1004     NaN     NaN

這種需求常出現(xiàn)在時(shí)間序列索引的時(shí)間點(diǎn)填充以及 ID 編號的擴(kuò)充。另外,需要注意的是原來表中的數(shù)據(jù)和新表中會根據(jù)索引自動(dòng)對其,例如原先的1002號位置在1003號之后,而新表中相反,那么 reindex 中會根據(jù)元素對其,與位置無關(guān)。

還有一個(gè)與 reindex 功能類似的函數(shù)是 reindex_like ,其功能是仿照傳入的表的索引來進(jìn)行被調(diào)用表索引的變形。例如,現(xiàn)在以及存在一張表具備了目標(biāo)索引的條件,那么上述功能可以如下等價(jià)地寫出:

In [149]: df_existed = pd.DataFrame(index=['1001','1002','1003','1004'],
   .....:                           columns=['Weight','Gender'])
   .....: 

In [150]: df_reindex.reindex_like(df_existed)
Out[150]: 
      Weight  Gender
1001    60.0     NaN
1002    80.0     NaN
1003    70.0     NaN
1004     NaN     NaN

四、索引運(yùn)算

1. 集合的運(yùn)算法則

經(jīng)常會有一種利用集合運(yùn)算來取出符合條件行的需求,例如有兩張表 AB ,它們的索引都是員工編號,現(xiàn)在需要篩選出兩表索引交集的所有員工信息,此時(shí)通過 Index上的運(yùn)算操作就很容易實(shí)現(xiàn)。

不過在此之前,不妨先復(fù)習(xí)一下常見的四種集合運(yùn)算:

\rm S_A.intersection(S_B) = \rm S_A \cap S_B \Leftrightarrow \rm \{x|x\in S_A\, and\, x\in S_B\}
\rm S_A.union(S_B) = \rm S_A \cup S_B \Leftrightarrow \rm \{x|x\in S_A\, or\, x\in S_B\}
\rm S_A.difference(S_B) = \rm S_A - S_B \Leftrightarrow \rm \{x|x\in S_A\, and\, x\notin S_B\}
\rm S_A.symmetric\_difference(S_B) = \rm S_A\triangle S_B\Leftrightarrow \rm \{x|x\in S_A\cup S_B - S_A\cap S_B\}

2. 一般的索引運(yùn)算

由于集合的元素是互異的,但是索引中可能有相同的元素,先用 unique 去重后再進(jìn)行運(yùn)算。下面構(gòu)造兩張最為簡單的示例表進(jìn)行演示:

In [151]: df_set_1 = pd.DataFrame([[0,1],[1,2],[3,4]],
   .....:                         index = pd.Index(['a','b','a'],name='id1'))
   .....: 

In [152]: df_set_2 = pd.DataFrame([[4,5],[2,6],[7,1]],
   .....:                         index = pd.Index(['b','b','c'],name='id2'))
   .....: 

In [153]: id1, id2 = df_set_1.index.unique(), df_set_2.index.unique()

In [154]: id1.intersection(id2)
Out[154]: Index(['b'], dtype='object')

In [155]: id1.union(id2)
Out[155]: Index(['a', 'b', 'c'], dtype='object')

In [156]: id1.difference(id2)
Out[156]: Index(['a'], dtype='object')

In [157]: id1.symmetric_difference(id2)
Out[157]: Index(['a', 'c'], dtype='object')

上述的四類運(yùn)算還可以用等價(jià)的符號表示代替如下:

In [158]: id1 & id2
Out[158]: Index(['b'], dtype='object')

In [159]: id1 | id2
Out[159]: Index(['a', 'b', 'c'], dtype='object')

In [160]: (id1 ^ id2) & id1
Out[160]: Index(['a'], dtype='object')

In [161]: id1 ^ id2 # ^符號即對稱差
Out[161]: Index(['a', 'c'], dtype='object')

若兩張表需要做集合運(yùn)算的列 并沒有被設(shè)置索引,一種辦法是先轉(zhuǎn)成索引,運(yùn)算后再恢復(fù),另一種方法是利用 isin 函數(shù),例如在重置索引的第一張表中選出id列交集的所在行:

In [162]: df_set_in_col_1 = df_set_1.reset_index()

In [163]: df_set_in_col_2 = df_set_2.reset_index()

In [164]: df_set_in_col_1
Out[164]: 
  id1  0  1
0   a  0  1
1   b  1  2
2   a  3  4

In [165]: df_set_in_col_2
Out[165]: 
  id2  0  1
0   b  4  5
1   b  2  6
2   c  7  1

In [166]: df_set_in_col_1[df_set_in_col_1.id1.isin(df_set_in_col_2.id2)]
Out[166]: 
  id1  0  1
1   b  1  2

五、練習(xí)

Ex1:公司員工數(shù)據(jù)集

現(xiàn)有一份公司員工數(shù)據(jù)集:

In [167]: df = pd.read_csv('data/company.csv')

In [168]: df.head(3)
Out[168]: 
   EmployeeID birthdate_key  age  city_name department      job_title gender
0        1318      1/3/1954   61  Vancouver  Executive            CEO      M
1        1319      1/3/1957   58  Vancouver  Executive      VP Stores      F
2        1320      1/2/1955   60  Vancouver  Executive  Legal Counsel      F
  1. 分別只使用 queryloc 選出年齡不超過四十歲且工作部門為 DairyBakery 的男性。
df.query('(age<=40)&((department=="Dairy")|(department=="Bakery"))&(gender=="M")')
#loc: 注意在[]中使用括號和query的效果是不一樣的,需要單獨(dú)寫條件并組合
condition_1_1 = df.department=="Dairy"
condition_1_2 = df.department=="Bakery"
condition_1 = condition_1_1 | condition_1_2
condition_2 = df.age<=40
condition_3 = df.gender == 'M'
df.loc[condition_1&condition_2&condition_3]
  1. 選出員工 ID 號 為奇數(shù)所在行的第1、第3和倒數(shù)第2列。
df.loc[df.EmployeeID%2==1].iloc[:, [0,2,-2]]
答案:
df.iloc[(df.EmployeeID%2==1).values,[0,2,-2]].head()
  1. 按照以下步驟進(jìn)行索引操作:
  • 把后三列設(shè)為索引后交換內(nèi)外兩層
  • 恢復(fù)中間一層
  • 修改外層索引名為 Gender
  • 用下劃線合并兩層行索引
  • 把行索引拆分為原狀態(tài)
  • 修改索引名為原表名稱
  • 恢復(fù)默認(rèn)索引并將列保持為原表的相對位置
# 把后三列設(shè)為索引后交換內(nèi)外兩層
df_ex = df.set_index(['department','job_title','gender'])
df_ex = df_ex.swaplevel(0,1,axis=0)
# 恢復(fù)中間一層(把中間一層從索引變回到column)
df_ex = df_ex.reset_index(level=1)
# 修改外層索引名為 `Gender`
df_ex =df_ex.rename_axis(index={'department':'gender'})
# 用下劃線合并兩層行索引
new_idx = df_ex.index.map(lambda x: (x[0]+'_'+x[1]))
df_ex.index = new_idx
# 把行索引拆分為原狀態(tài)
new_idx = df_ex.index.map(lambda x:tuple(x.split('_')))
df_ex.index = new_idx
# 修改索引名為原表名稱
df_ex = df_ex.rename_axis(index=['job_title','gender'])
# 恢復(fù)默認(rèn)索引并將列保持為原表的相對位置
df_ex = df_ex.reset_index().reindex(df.columns,axis=1) #要加axis=1,否則會默認(rèn)等于0

Ex2:巧克力數(shù)據(jù)集

現(xiàn)有一份關(guān)于巧克力評價(jià)的數(shù)據(jù)集:

In [169]: df = pd.read_csv('data/chocolate.csv')

In [170]: df.head(3)
Out[170]: 
    Company  Review\nDate Cocoa\nPercent Company\nLocation  Rating
0  A. Morin          2016            63%            France    3.75
1  A. Morin          2015            70%            France    2.75
2  A. Morin          2015            70%            France    3.00
  1. 把列索引名中的 \n 替換為空格。
df.rename(columns=lambda x: x.replace('\n',' '))
  1. 巧克力 Rating 評分為1至5,每0.25分一檔,請選出2.75分及以下且可可含量 Cocoa Percent 高于中位數(shù)的樣本。
df['Cocoa Percent'] = df['Cocoa Percent'].apply(lambda x:float(x[:-1])/100)
df.query('(Rating<3)&(`Cocoa Percent`>`Cocoa Percent`.median())') #注意列名有空格的要用``符號包起來;中位數(shù)是median不是mean
  1. Review DateCompany Location 設(shè)為索引后,選出 Review Date 在2012年之后且 Company Location 不屬于 France, Canada, Amsterdam, Belgium 的樣本。
df.set_index(['Review Date',"Company Location"])
df.query('(`Review Date`>=2012) and (`Company Location` not in ["France", "Canada", "Amsterdam", "Belgium"])')
答案:
idx = pd.IndexSlice
exclude = ['France', 'Canada', 'Amsterdam', 'Belgium']
res = df.set_index(['Review Date', 'Company Location']).sort_index(level=0)
res.loc[idx[2012:,~res.index.get_level_values(1).isin(exclude)],:].head(3)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容