http://joyfulpandas.datawhale.club/Content/index.html
第三章 索引
In [1]: import numpy as np
In [2]: import pandas as pd
一、索引器
1. 表的列索引
列索引是最常见的索引形式,一般通过 [] 来实现。通过 [列名] 可以从 DataFrame 中取出相应的列,返回值为 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
如果要取出多个列,则可以通过 [列名组成的列表] ,其返回值为一个 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
此外,若要取出单列,且列名中不包含空格,则可以用 .列名 取出,这和 [列名] 是等价的:
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
如果取出单个索引的对应元素,则可以使用 [item] ,若 Series 只有单个值对应,则返回这个标量值,如果有多个值对应,则返回一个 Series:
In [7]: s = pd.Series([1, 2, 3, 4, 5, 6],
...: index=['a', 'b', 'a', 'a', 'a', 'c'])
...:
In [8]: s['a']
Out[8]:
a 1
a 3
a 4
a 5
dtype: int64
In [9]: s['b']
Out[9]: 2
如果取出多个索引的对应元素,则可以使用 [items的列表] :
In [10]: s[['c', 'b']]
Out[10]:
c 6
b 2
dtype: int64
如果想要取出某两个索引之间的元素,并且这两个索引是在整个索引中唯一出现,则可以使用切片,同时需要注意这里的切片会包含两个端点:
In [11]: s['c': 'b': -2]
Out[11]:
c 6
a 4
b 2
dtype: int64
如果前后端点的值存在重复,即非唯一值,那么需要经过排序才能使用切片:
In [12]: try:
....: s['a': 'b']
....: except Exception as e:
....: Err_Msg = e
....:
In [13]: Err_Msg
Out[13]: KeyError("Cannot get left slice bound for non-unique label: 'a'")
In [14]: s.sort_index()['a': 'b']
Out[14]:
a 1
a 3
a 4
a 5
b 2
dtype: int64
【b】以整数为索引的 Series
在使用数据的读入函数时,如果不特别指定所对应的列作为索引,那么会生成从0开始的整数索引作为默认索引。当然,任意一组符合长度要求的整数都可以作为索引。
和字符串一样,如果使用 [int] 或 [int_list] ,则可以取出对应索引 元素 的值:
In [15]: s = pd.Series(['a', 'b', 'c', 'd', 'e', 'f'],
....: index=[1, 3, 1, 2, 5, 4])
....:
In [16]: s[1]
Out[16]:
1 a
1 c
dtype: object
In [17]: s[[2,3]]
Out[17]:
2 d
3 b
dtype: object
如果使用整数切片,则会取出对应索引 位置 的值,注意这里的整数切片同 Python 中的切片一样不包含右端点:
In [18]: s[1: -1:2]
Out[18]:
3 b
2 d
dtype: object
关于索引类型的说明
如果不想陷入麻烦,那么请不要把纯浮点以及任何混合类型(字符串、整数、浮点类型等的混合)作为索引,否则可能会在具体的操作时报错或者返回非预期的结果,并且在实际的数据分析中也不存在这样做的动机。
3. loc索引器
前面讲到了对 DataFrame 的列进行选取,下面要讨论其行的选取。对于表而言,有两种索引器,一种是基于 元素 的 loc 索引器,另一种是基于 位置 的 iloc 索引器。
loc 索引器的一般形式是 loc [ * , * ] ,其中第一个 * 代表行的选择,第二个 * 代表列的选择,如果省略第二个位置写作 loc[*] ,这个 * 是指行的筛选 。其中, * 的位置一共有五类合法对象,分别是:单个元素、元素列表、元素切片、布尔列表以及函数,下面将依次说明。
为了演示相应操作,先利用 set_index 方法把 Name 列设为索引,关于该函数的其他用法将在多级索引一章介绍。
In [19]: df_demo = df.set_index('Name')
In [20]: df_demo.head()
Out[20]:
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】 * 为单个元素
此时,直接取出相应的行或列,如果该元素在索引中重复则结果为 DataFrame,否则为 Series :
In [21]: df_demo.loc['Qiang Sun'] # 多个人叫此名字
Out[21]:
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 [22]: df_demo.loc['Quan Zhao'] # 名字唯一
Out[22]:
School Shanghai Jiao Tong University
Grade Junior
Gender Female
Weight 53.0
Transfer N
Name: Quan Zhao, dtype: object
也可以同时选择行和列:
In [23]: df_demo.loc['Qiang Sun', 'School'] # 返回Series
Out[23]:
Name
Qiang Sun Tsinghua University
Qiang Sun Tsinghua University
Qiang Sun Shanghai Jiao Tong University
Name: School, dtype: object
In [24]: df_demo.loc['Quan Zhao', 'School'] # 返回单个元素
Out[24]: 'Shanghai Jiao Tong University'
【b】 * 为元素列表
此时,取出列表中所有元素值对应的行或列:
In [25]: df_demo.loc[['Qiang Sun','Quan Zhao'], ['School','Gender']]
Out[25]:
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 使用字符串索引时提到,如果是唯一值的起点和终点字符,那么就可以使用切片,并且包含两个端点,如果不唯一则报错:
In [26]: df_demo.loc['Gaojuan You':'Gaoqiang Qian', 'School':'Gender']
Out[26]:
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 使用整数索引,其使用整数切片的时候和上面字符串索引的要求一致,都是 元素 切片,包含端点且起点、终点不允许有重复值。
In [27]: df_loc_slice_demo = df_demo.copy()
In [28]: df_loc_slice_demo.index = range(df_demo.shape[0],0,-1)
In [29]: df_loc_slice_demo.loc[5:3]
Out[29]:
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 [30]: df_loc_slice_demo.loc[3:5] # 没有返回,说明不是整数位置切片
Out[30]:
Empty DataFrame
Columns: [School, Grade, Gender, Weight, Transfer]
Index: []
【d】 * 为布尔列表
在实际的数据处理中,根据条件来筛选行是极其常见的,此处传入 loc 的布尔列表与 DataFrame 长度相同,且列表为 True 的位置所对应的行会被选中, False 则会被剔除。
例如,选出体重超过70kg的学生:
In [31]: df_demo.loc[df_demo.Weight>70].head()
Out[31]:
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 方法返回的布尔列表等价写出,例如选出所有大一和大四的同学信息:
In [32]: df_demo.loc[df_demo.Grade.isin(['Freshman', 'Senior'])].head()
Out[32]:
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
对于复合条件而言,可以用 |(或), &(且), ~(取反) 的组合来实现,例如选出复旦大学中体重超过70kg的大四学生,或者北大男生中体重超过80kg的非大四的学生:
In [33]: condition_1_1 = df_demo.School == 'Fudan University'
In [34]: condition_1_2 = df_demo.Grade == 'Senior'
In [35]: condition_1_3 = df_demo.Weight > 70
In [36]: condition_1 = condition_1_1 & condition_1_2 & condition_1_3
In [37]: condition_2_1 = df_demo.School == 'Peking University'
In [38]: condition_2_2 = df_demo.Grade == 'Senior'
In [39]: condition_2_3 = df_demo.Weight > 80
In [40]: condition_2 = condition_2_1 & (~condition_2_2) & condition_2_3
In [41]: df_demo.loc[condition_1 | condition_2]
Out[41]:
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】 * 为函数
这里的函数,必须以前面的四种合法形式之一为返回值,并且函数的输入值为 DataFrame 本身。假设仍然是上述复合条件筛选的例子,可以把逻辑写入一个函数中再返回,需要注意的是函数的形式参数 x 本质上即为 df_demo :
In [42]: 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 [43]: df_demo.loc[condition]
Out[43]:
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 表达式,其返回值也同样必须是先前提到的四种形式之一:
In [44]: df_demo.loc[lambda x:'Quan Zhao', lambda x:'Gender']
Out[44]: 'Female'
由于函数无法返回如 start: end: step 的切片形式,故返回切片时要用 slice 对象进行包装:
In [45]: df_demo.loc[lambda x: slice('Gaojuan You', 'Gaoqiang Qian')]
Out[45]:
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[*] 完全一致,此处不再赘述。
不要使用链式赋值
在对表或者序列赋值时,应当在使用一层索引器后直接进行赋值操作,这样做是由于进行多次索引后赋值是赋在临时返回的 copy 副本上的,而没有真正修改元素从而报出 SettingWithCopyWarning 警告。例如,下面给出的例子:
In [46]: df_chain = pd.DataFrame([[0,0],[1,0],[-1,0]], columns=list('AB'))
In [47]: df_chain
Out[47]:
A B
0 0 0
1 1 0
2 -1 0
In [48]: import warnings
In [49]: with warnings.catch_warnings():
....: warnings.filterwarnings('error')
....: try:
....: df_chain[df_chain.A!=0].B = 1 # 使用方括号列索引后,再使用点的列索引
....: except Warning as w:
....: Warning_Msg = w
....:
In [50]: 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 [51]: df_chain
Out[51]:
A B
0 0 0
1 1 0
2 -1 0
In [52]: df_chain.loc[df_chain.A!=0,'B'] = 1
In [53]: df_chain
Out[53]:
A B
0 0 0
1 1 1
2 -1 1
4. iloc索引器
**iloc 的使用与 loc 完全类似,只不过是针对位置进行筛选,**在相应的 * 位置处一共也有五类合法对象,分别是:整数、整数列表、整数切片、布尔列表以及函数,函数的返回值必须是前面的四类合法对象中的一个,其输入同样也为 DataFrame 本身。
In [54]: df_demo.iloc[1, 1] # 第二行第二列
Out[54]: 'Freshman'
In [55]: df_demo.iloc[[0, 1], [0, 1]] # 前两行前两列
Out[55]:
School Grade
Name
Gaopeng Yang Shanghai Jiao Tong University Freshman
Changqiang You Peking University Freshman
In [56]: df_demo.iloc[1: 4, 2:4] # 切片不包含结束端点
Out[56]:
Gender Weight
Name
Changqiang You Male 70.0
Mei Sun Male 89.0
Xiaojuan Sun Female 41.0
In [57]: df_demo.iloc[lambda x: slice(1, 4)] # 传入切片为返回值的函数
Out[57]:
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 ,否则会报错。因此,在使用布尔筛选的时候还是应当优先考虑 loc 的方式。
例如,选出体重超过80kg的学生:
In [58]: df_demo.iloc[(df_demo.Weight>80).values].head()
Out[58]:
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 返回相应位置的值或子序列:
In [59]: df_demo.School.iloc[1]
Out[59]: 'Peking University'
In [60]: df_demo.School.iloc[1:5:2]
Out[60]:
Name
Changqiang You Peking University
Xiaojuan Sun Fudan University
Name: School, dtype: object
df.loc[ ]和df.iloc[ ]的区别
- loc利用index的名称,来获取想要的行(或列)。(名称导向的这个特点,使得df[df.loc[‘col_name’] == ‘condition’, ‘col_name’] = value_1成立。
- iloc利用index的具体位置(所以它只能是整数型参数),来获取想要的行(或列)。
5. query方法
在 pandas 中,支持把字符串形式的查询表达式传入 query 方法来查询数据,其表达式的执行结果必须返回布尔列表。在进行复杂索引时,由于这种检索方式无需像普通方法一样重复使用 DataFrame 的名字来引用列名,一般而言会使代码长度在不降低可读性的前提下有所减少。
例如,将 loc 一节中的复合条件查询例子可以如下改写:
In [61]: df.query('((School == "Fudan University")&'
....: ' (Grade == "Senior")&'
....: ' (Weight > 70))|'
....: '((School == "Peking University")&'
....: ' (Grade != "Senior")&'
....: ' (Weight > 80))')
....:
Out[61]:
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 表达式中,帮用户注册了所有来自 DataFrame 的列名,所有属于该 Series 的方法都可以被调用,和正常的函数调用并没有区别,例如查询体重超过均值的学生:
In [62]: df.query('Weight > Weight.mean()').head()
Out[62]:
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
的方式进行引用。
同时,在 query 中还注册了若干英语的字面用法,帮助提高可读性,例如: or, and, or, is in, not in 。例如,筛选出男生中不是大一大二的学生:
In [63]: df.query('(Grade not in ["Freshman", "Sophomore"]) and'
....: '(Gender == "Male")').head()
....:
Out[63]:
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
此外,在字符串中出现与列表的比较时, == 和 != 分别表示元素出现在列表和没有出现在列表,等价于 is in 和 not in,例如查询所有大三和大四的学生:
In [64]: df.query('Grade == ["Junior", "Senior"]').head()
Out[64]:
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之间的学生:
In [65]: low, high =70, 80
In [66]: df.query('Weight.between(@low, @high)').head()
Out[66]:
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
6. 随机抽样
如果把 DataFrame 的每一行看作一个样本,或把每一列看作一个特征,再把整个 DataFrame 看作总体,想要对样本或特征进行随机抽样就可以用 sample 函数。有时在拿到大型数据集后,想要对统计特征进行计算来了解数据的大致分布,但是这很费时间。同时,由于许多统计特征在等概率不放回的简单随机抽样条件下,是总体统计特征的无偏估计,比如样本均值和总体均值,那么就可以先从整张表中抽出一部分来做近似估计。
sample 函数中的主要参数为 n, axis, frac, replace, weights ,前三个分别是指抽样数量、抽样的方向(0为行、1为列)和抽样比例(0.3则为从总体中抽出30%的样本)。
replace 和 weights 分别是指是否放回和每个样本的抽样相对概率,当 replace = True 则表示有放回抽样。例如,对下面构造的 df_sample 以 value 值的相对大小为抽样概率进行有放回抽样,抽样数量为3。
In [67]: df_sample = pd.DataFrame({'id': list('abcde'),
....: 'value': [1, 2, 3, 4, 90]})
....:
In [68]: df_sample
Out[68]:
id value
0 a 1
1 b 2
2 c 3
3 d 4
4 e 90
In [69]: df_sample.sample(3, replace = True, weights = df_sample.value)
Out[69]:
id value
4 e 90
4 e 90
4 e 90