目录
第一章.预备知识
1、Python基础
1.列表推导式与条件赋值
L = []
def my_func(x):
return 2*x
for i in range(5):
L.append(my_func(i))
L
#[0, 2, 4, 6, 8]
列表表达式还支持多层嵌套,如下面的例子中第一个for
为外层循环,第二个为内层循环:
[m+'_'+n for m in ['a', 'b'] for n in ['c', 'd']]
#['a_c', 'a_d', 'b_c', 'b_d']
2. 匿名函数与map方法
my_func = lambda x: 2*x
my_func(3)
[(lambda x: 2*x)(i) for i in range(5)]
对于上述的这种列表推导式的匿名函数映射,Python
中提供了map
函数来完成,它返回的是一个map
对象,需要通过list
转为列表:
list(map(lambda x: 2*x, range(5)))
对于多个输入值的函数映射,可以通过追加迭代对象实现:
list(map(lambda x, y: str(x)+'_'+y, range(5), list('abcde')))
3. zip对象与enumerate方法
zip
函数能够把多个可迭代对象打包成一个元组构成的可迭代对象,它返回了一个zip
对象,通过tuple
, list
可以得到相应的打包结果:
L1, L2, L3 = list('abc'), list('def'), list('hij')
list(zip(L1, L2, L3))
#[('a', 'd', 'h'), ('b', 'e', 'i'), ('c', 'f', 'j')]
tuple(zip(L1, L2, L3))
#(('a', 'd', 'h'), ('b', 'e', 'i'), ('c', 'f', 'j'))
'''
往往会在循环迭代的时候使用到zip函数:
'''
for i, j, k in zip(L1, L2, L3):
print(i, j, k)
#
a d h
b e i
c f j
'''
enumerate是一种特殊的打包,它可以在迭代时绑定迭代元素的遍历序号:
'''
L = list('abcd')
for index, value in enumerate(L):
print(index, value)
#0 a
1 b
2 c
3 d
'''
用zip对象也能够简单地实现这个功能:
'''
for index, value in zip(range(len(L)), L):
print(index, value)
#0 a
1 b
2 c
3 d
'''
当需要对两个列表建立字典映射时,可以利用zip对象:
'''
dict(zip(L1, L2))
#{'a': 'd', 'b': 'e', 'c': 'f'}
'''
既然有了压缩函数,那么Python也提供了*操作符和zip联合使用来进行解压操作:
'''
zipped = list(zip(L1, L2, L3))
zipped
#[('a', 'd', 'h'), ('b', 'e', 'i'), ('c', 'f', 'j')]
list(zip(*zipped)) # 三个元组分别对应原来的列表
#[('a', 'b', 'c'), ('d', 'e', 'f'), ('h', 'i', 'j')]
二、Numpy基础
1. np数组的构造
...
2. np数组的变形与合并
【a】转置:T
np.zeros((2,3)).T
#array([[0., 0.],
, [0., 0.],
, [0., 0.]])
【b】合并操作:r_
, c_
对于二维数组而言,r_
和c_
分别表示上下合并和左右合并
一维数组和二维数组进行合并时,应当把其视作列向量,在长度匹配的情况下只能够使用左右合并的c_
操作
【c】维度变换:reshape
reshape
能够帮助用户把原数组按照新的维度重新排列。在使用时有两种模式,分别为C
模式和F
模式,分别以逐行和逐列的顺序进行填充读取。
3. np数组的切片与索引
数组的切片模式支持使用slice
类型的start:end:step
切片,还可以直接传入列表指定某个维度的索引进行切片
此外,还可以利用np.ix_
在对应的维度上使用布尔索引,但此时不能使用slice
切片:
target[np.ix_([True, False, True], [True, False, True])]
#array([[0, 2],
, [6, 8]])
4. 常用函数
【a】where
where
是一种条件函数,可以指定满足条件与不满足条件位置对应的填充值:
a = np.array([-1,1,-1,0])
np.where(a>0, a, 5) # 对应位置为True时填充a对应元素,否则填充5
#array([5, 1, 5, 5])
【b】nonzero
, argmax
, argmin
这三个函数返回的都是索引,nonzero
返回非零数的索引,argmax
, argmin
分别返回最大和最小数的索引:
a = np.array([-2,-5,0,1,3,-1])
np.nonzero(a)
#(array([0, 1, 3, 4, 5], dtype=int64),)
a.argmax()
#4
a.argmin()
#1
【c】any
, all
any
指当序列至少 存在一个 True
或非零元素时返回True
,否则返回False
all
指当序列元素 全为 True
或非零元素时返回True
,否则返回False
a = np.array([0,1])
a.any()
#True
a.all()
#False
【d】cumprod
, cumsum
, diff
cumprod
, cumsum
分别表示累乘和累加函数,返回同长度的数组,diff
表示和前一个元素做差,由于第一个元素为缺失值,因此在默认参数情况下,返回长度是原数组减1
【e】 统计函数
常用的统计函数包括max, min, mean, median, std, var, sum, quantile
,其中分位数计算是全局方法,因此不能通过array.quantile
的方法调用:
np.quantile(target, 0.5) # 0.5分位数
但是对于含有缺失值的数组,它们返回的结果也是缺失值,如果需要略过缺失值,必须使用nan*
类型的函数,上述的几个统计函数都有对应的nan*
函数。
最后,需要说明二维Numpy
数组中统计函数的axis
参数,它能够进行某一个维度下的统计特征计算,当axis=0
时结果为列的统计指标,当axis=1
时结果为行的统计指标
5. 广播机制
广播机制用于处理两个不同维度数组之间的操作,这里只讨论不超过两维的数组广播机制。
6. 向量与矩阵的计算
【a】向量内积:dot
【b】向量范数和矩阵范数:np.linalg.norm
在矩阵范数的计算中,最重要的是ord
参数,可选值如下:
ord | norm for matrices | norm for vectors |
---|---|---|
None | Frobenius norm | 2-norm |
'fro' | Frobenius norm | / |
'nuc' | nuclear norm | / |
inf | max(sum(abs(x), axis=1)) | max(abs(x)) |
-inf | min(sum(abs(x), axis=1)) | min(abs(x)) |
0 | / | sum(x != 0) |
1 | max(sum(abs(x), axis=0)) | as below |
-1 | min(sum(abs(x), axis=0)) | as below |
2 | 2-norm (largest sing. value) | as below |
-2 | smallest singular value | as below |
other | / | sum(abs(x)**ord)**(1./ord) |
【c】矩阵乘法:@
[Am×pBp×n]ij=p∑k=1AikBkj
第二章.pandas基础
import numpy as np import pandas as pd
请确认已经安装了xlrd, xlwt, openpyxl
这三个包,其中xlrd
版本不得高于2.0.0
一、文件的读取和写入
1. 文件读取
pandas
可以读取的文件格式有很多,这里主要介绍读取csv, excel, txt
文件。
df_csv = pd.read_csv('../data/my_csv.csv')
df_csv
col1 | col2 | col3 | col4 | col5 | |
---|---|---|---|---|---|
0 | 2 | a | 1.4 | apple | 2020/1/1 |
1 | 3 | b | 3.4 | banana | 2020/1/2 |
2 | 6 | c | 2.5 | orange | 2020/1/5 |
3 | 5 | d | 3.2 | lemon | 2020/1/7 |
df_txt = pd.read_table('../data/my_table.txt')
df_txt
col1 | col2 | col3 | col4 | |
---|---|---|---|---|
0 | 2 | a | 1.4 | apple 2020/1/1 |
1 | 3 | b | 3.4 | banana 2020/1/2 |
2 | 6 | c | 2.5 | orange 2020/1/5 |
3 | 5 | d | 3.2 | lemon 2020/1/7 |
df_excel = pd.read_excel('../data/my_excel.xlsx')
df_excel
col1 | col2 | col3 | col4 | col5 | |
---|---|---|---|---|---|
0 | 2 | a | 1.4 | apple | 2020/1/1 |
1 | 3 | b | 3.4 | banana | 2020/1/2 |
2 | 6 | c | 2.5 | orange | 2020/1/5 |
3 | 5 | d | 3.2 | lemon | 2020/1/7 |
这里有一些常用的公共参数,header=None
表示第一行不作为列名,index_col
表示把某一列或几列作为索引,索引的内容将会在第三章进行详述,usecols
表示读取列的集合,默认读取所有的列,parse_dates
表示需要转化为时间的列,关于时间序列的有关内容将在第十章讲解,nrows
表示读取的数据行数。上面这些参数在上述的三个函数里都可以使用。
pd.read_table('../data/my_table.txt', header=None)
pd.read_csv('../data/my_csv.csv', index_col=['col1', 'col2'])
pd.read_table('../data/my_table.txt', usecols=['col1', 'col2'])
pd.read_csv('../data/my_csv.csv', parse_dates=['col5'])
pd.read_excel('../data/my_excel.xlsx', nrows=2)
在读取txt
文件时,经常遇到分隔符非空格的情况,read_table
有一个分割参数sep
,它使得用户可以自定义分割符号,进行txt
数据的读取。例如,下面的读取的表以||||
为分割:
pd.read_table('../data/my_table_special_sep.txt')
col1 |||| col2 | |
---|---|
0 | TS |||| This is an apple. |
1 | GQ |||| My name is Bob. |
2 | WT |||| Well done! |
3 | PT |||| May I help you? |
上面的结果显然不是理想的,这时可以使用sep
,同时需要指定引擎为python
:
pd.read_table('../data/my_table_special_sep.txt', sep=' \|\|\|\| ', engine='python')
col1 | col2 | |
---|---|---|
0 | TS | This is an apple. |
1 | GQ | My name is Bob. |
2 | WT | Well done! |
3 | PT | May I help you? |
【WARNING】sep
是正则参数
在使用read_table
的时候需要注意,参数sep
中使用的是正则表达式,因此需要对|
进行转义变成\|
,否则无法读取到正确的结果。有关正则表达式的基本内容可以参考第八章或者其他相关资料。
2. 数据写入
一般在数据写入中,最常用的操作是把index
设置为False
,特别当索引没有特殊意义的时候,这样的行为能把索引在保存的时候去除。
df_csv.to_csv('../data/my_csv_saved.csv', index=False)
df_excel.to_excel('../data/my_excel_saved.xlsx', index=False)
pandas
中没有定义to_table
函数,但是to_csv
可以保存为txt
文件,并且允许自定义分隔符,常用制表符\t
分割:
df_txt.to_csv('../data/my_txt_saved.txt', sep='\t', index=False)
如果想要把表格快速转换为markdown
和latex
语言,可以使用to_markdown
和to_latex
函数,此处需要安装tabulate
包。
二、基本数据结构
pandas
中具有两种基本的数据存储结构,存储一维values
的Series
和存储二维values
的DataFrame
,在这两种结构上定义了很多的属性和方法
1. Series
Series
一般由四个部分组成,分别是序列的值data
、索引index
、存储类型dtype
、序列的名字name
。其中,索引也可以指定它的名字,默认为空。
s = pd.Series(data = [100, 'a', {'dic1':5}],
index = pd.Index(['id1', 20, 'third'], name='my_idx'),
dtype = 'object',
name = 'my_name')
s
'''
my_idx
,id1 100
,20 a
,third {'dic1': 5}
,Name: my_name, dtype: object
'''
【NOTE】object
类型
object
代表了一种混合类型,正如上面的例子中存储了整数、字符串以及Python
的字典数据结构。此外,目前pandas
把纯字符串序列也默认认为是一种object
类型的序列,但它也可以用string
类型存储,文本序列的内容会在第八章中讨论。
对于这些属性,可以通过 . 的方式来获取:
索引是pandas
中最重要的概念之一,它将在第三章中被详细地讨论。如果想要取出单个索引对应的值,可以通过[index_item]
可以取出
2. DataFrame¶
DataFrame
在Series
的基础上增加了列索引,一个数据框可以由二维的data
与行列索引来构造:
data = [[1, 'a', 1.2], [2, 'b', 2.2], [3, 'c', 3.2]]
df = pd.DataFrame(data = data,
index = ['row_%d'%i for i in range(3)],
columns=['col_0', 'col_1', 'col_2'])
df
col_0 | col_1 | col_2 | |
---|---|---|---|
row_0 | 1 | a | 1.2 |
row_1 | 2 | b | 2.2 |
row_2 | 3 | c | 3.2 |
但一般而言,更多的时候会采用从列索引名到数据的映射来构造数据框,同时再加上行索引:
df = pd.DataFrame(data = {'col_0': [1,2,3],
'col_1':list('abc'),
'col_2': [1.2, 2.2, 3.2]},
index = ['row_%d'%i for i in range(3)])
df
由于这种映射关系,在DataFrame
中可以用[col_name]
与[col_list]
来取出相应的列与由多个列组成的表,结果分别为Series
和DataFrame
:
与Series
类似,在数据框中同样可以取出相应的属性:
三、常用基本函数
为了进行举例说明,在接下来的部分和其余章节都将会使用一份learn_pandas.csv
的虚拟数据集,它记录了四所学校学生的体测个人信息。
df = pd.read_csv('../data/learn_pandas.csv')
df.columns
'''
Index(['School', 'Grade', 'Name', 'Gender', 'Height', 'Weight', 'Transfer',
, 'Test_Number', 'Test_Date', 'Time_Record'],
, dtype='object')
'''
上述列名依次代表学校、年级、姓名、性别、身高、体重、是否为转系生、体测场次、测试时间、1000米成绩,本章只需使用其中的前七列
df = df[df.columns[:7]]
1. 汇总函数
head, tail
函数分别表示返回表或者序列的前n
行和后n
行,其中n
默认为5:
info, describe
分别返回表的信息概况和表中数值列对应的主要统计量 :
【NOTE】更全面的数据汇总
info, describe
只能实现较少信息的展示,如果想要对一份数据集进行全面且有效的观察,特别是在列较多的情况下,推荐使用pandas-profiling包,它将在第十一章被再次提到。
2. 特征统计函数
在Series
和DataFrame
上定义了许多统计函数,最常见的是sum, mean, median, var, std, max, min
。例如,选出身高和体重列进行演示:
df_demo = df[['Height', 'Weight']]
df_demo.mean()
'''
Height 163.218033
,Weight 55.015873
,dtype: float64
'''
df_demo.max()
'''
Height 193.9
,Weight 89.0
,dtype: float64
'''
此外,需要介绍的是quantile, count, idxmax
这三个函数,它们分别返回的是分位数、非缺失值个数、最大值对应的索引:
上面这些所有的函数,由于操作后返回的是标量,所以又称为聚合函数,它们有一个公共参数axis
,默认为0代表逐列聚合,如果设置为1则表示逐行聚合:
3. 唯一值函数
对序列使用unique
和nunique
可以分别得到其唯一值组成的列表和唯一值的个数:
如果想要观察多个列组合的唯一值,可以使用drop_duplicates
。其中的关键参数是keep
,默认值first
表示每个组合保留第一次出现的所在行,last
表示保留最后一次出现的所在行,False
表示把所有重复组合所在的行剔除。
此外,duplicated
和drop_duplicates
的功能类似,但前者返回了是否为唯一值的布尔列表,其keep
参数与后者一致。其返回的序列,把重复元素设为True
,否则为False
。 drop_duplicates
等价于把duplicated
为True
的对应行剔除。
4. 替换函数
一般而言,替换操作是针对某一个列进行的,因此下面的例子都以Series
举例。pandas
中的替换函数可以归纳为三类:映射替换、逻辑替换、数值替换。其中映射替换包含replace
方法、第八章中的str.replace
方法以及第九章中的cat.codes
方法,此处介绍replace
的用法。
在replace
中,可以通过字典构造,或者传入两个列表来进行替换:
另外,replace
还有一种特殊的方向替换,指定method
参数为ffill
则为用前面一个最近的未被替换的值进行替换,bfill
则使用后面最近的未被替换的值进行替换。从下面的例子可以看到,它们的结果是不同的:
【WARNING】正则替换请使用str.replace
虽然对于replace
而言可以使用正则替换,但是当前版本下对于string
类型的正则替换还存在bug
,因此如有此需求,请选择str.replace
进行替换操作,具体的方式将在第八章中讲解。
【END】
逻辑替换包括了where
和mask
,这两个函数是完全对称的:where
函数在传入条件为False
的对应行进行替换,而mask
在传入条件为True
的对应行进行替换,当不指定替换值时,替换为缺失值。
需要注意的是,传入的条件只需是与被调用的Series
索引一致的布尔序列即可:
5. 排序函数
排序共有两种方式,其一为值排序,其二为索引排序,对应的函数是sort_values
和sort_index
。
为了演示排序函数,下面先利用set_index
方法把年级和姓名两列作为索引,多级索引的内容和索引设置的方法将在第三章进行详细讲解。
对身高进行排序,默认参数ascending=True
为升序:
在排序中,经常遇到多列排序的问题,比如在体重相同的情况下,对身高进行排序,并且保持身高降序排列,体重升序排列:
索引排序的用法和值排序完全一致,只不过元素的值在索引中,此时需要指定索引层的名字或者层号,用参数level
表示。另外,需要注意的是字符串的排列顺序由字母顺序决定。
6. apply方法
apply
方法常用于DataFrame
的行迭代或者列迭代,它的axis
含义与第2小节中的统计聚合函数一致,apply
的参数往往是一个以序列为输入的函数。例如对于.mean()
,使用apply
可以如下地写出:
同样的,可以利用lambda
表达式使得书写简洁,这里的x
就指代被调用的df_demo
表中逐个输入的序列:
若指定axis=1
,那么每次传入函数的就是行元素组成的Series
,其结果与之前的逐行均值结果一致。
这里再举一个例子:mad
函数返回的是一个序列中偏离该序列均值的绝对值大小的均值,例如序列1,3,7,10中,均值为5.25,每一个元素偏离的绝对值为4.25,2.25,1.75,4.75,这个偏离序列的均值为3.25。现在利用apply
计算升高和体重的mad
指标:
【WARNING】谨慎使用apply
¶
得益于传入自定义函数的处理,apply
的自由度很高,但这是以性能为代价的。一般而言,使用pandas
的内置函数处理和apply
来处理同一个任务,其速度会相差较多,因此只有在确实存在自定义需求的情境下才考虑使用apply
。
四、窗口对象
pandas
中有3类窗口,分别是滑动窗口rolling
、扩张窗口expanding
以及指数加权窗口ewm
。需要说明的是,以日期偏置为窗口大小的滑动窗口将在第十章讨论,指数加权窗口见本章练习。
1. 滑窗对象
要使用滑窗函数,就必须先要对一个序列使用.rolling
得到滑窗对象,其最重要的参数为窗口大小window
。
在得到了滑窗对象后,能够使用相应的聚合函数进行计算,需要注意的是窗口包含当前行所在的元素,例如在第四个位置进行均值运算时,应当计算(2+3+4)/3,而不是(1+2+3)/3
此外,还支持使用apply
传入自定义函数,其传入值是对应窗口的Series
,例如上述的均值函数可以等效表示
shift, diff, pct_change
是一组类滑窗函数,它们的公共参数为periods=n
,默认为1,分别表示取向前第n
个元素的值、与向前第n
个元素做差(与Numpy
中不同,后者表示n
阶差分)、与向前第n
个元素相比计算增长率。这里的n
可以为负,表示反方向的类似操作。
将其视作类滑窗函数的原因是,它们的功能可以用窗口大小为n+1
的rolling
方法等价代替:
2. 扩张窗口
扩张窗口又称累计窗口,可以理解为一个动态长度的窗口,其窗口的大小就是从序列开始处到具体操作的对应位置,其使用的聚合函数会作用于这些逐步扩张的窗口上。具体地说,设序列为a1, a2, a3, a4,则其每个位置对应的窗口即[a1]、[a1, a2]、[a1, a2, a3]、[a1, a2, a3, a4]。
第三章 索引
一、索引器
1. 表的列索引
列索引是最常见的索引形式,一般通过[]
来实现。通过[列名]
可以从DataFrame
中取出相应的列,返回值为Series
,例如从表中取出姓名一列:
如果要取出多个列,则可以通过[列名组成的列表]
,其返回值为一个DataFrame
,例如从表中取出性别和姓名两列:
此外,若要取出单列,且列名中不包含空格,则可以用.列名
取出,这和[列名]
是等价的:
2. 序列的行索引
【a】以字符串为索引的Series
如果取出单个索引的对应元素,则可以使用[item]
,若Series
只有单个值对应,则返回这个标量值,如果有多个值对应,则返回一个Series
:
如果想要取出某两个索引之间的元素,并且这两个索引是在整个索引中唯一出现,则可以使用切片,,同时需要注意这里的切片会包含两个端点:
如果前后端点的值重复出现,那么需要经过排序才能使用切片:
【b】以整数为索引的Series
在使用数据的读入函数时,如果不特别指定所对应的列作为索引,那么会生成从0开始的整数索引作为默认索引。当然,任意一组符合长度要求的整数都可以作为索引。
和字符串一样,如果使用[int]
或[int_list]
,则可以取出对应索引元素的值:
如果使用整数切片,则会取出对应索引位置的值,注意这里的整数切片同Python
中的切片一样不包含右端点:
【WARNING】关于索引类型的说明
如果不想陷入麻烦,那么请不要把纯浮点以及任何混合类型(字符串、整数、浮点类型等的混合)作为索引,否则可能会在具体的操作时报错或者返回非预期的结果,并且在实际的数据分析中也不存在这样做的动机。
3. loc索引器
前面讲到了对DataFrame
的列进行选取,下面要讨论其行的选取。对于表而言,有两种索引器,一种是基于元素的loc
索引器,另一种是基于位置的iloc
索引器。
loc
索引器的一般形式是loc[*, *]
,其中第一个*
代表行的选择,第二个*
代表列的选择,如果省略第二个位置写作loc[*]
,这个*
是指行的筛选。其中,*
的位置一共有五类合法对象,分别是:单个元素、元素列表、元素切片、布尔列表以及函数,下面将依次说明。
为了演示相应操作,先利用set_index
方法把Name
列设为索引,关于该函数的其他用法将在多级索引一章介绍。
【a】*
为单个元素
此时,直接取出相应的行或列,如果该元素在索引中重复则结果为DataFrame
,否则为Series
:
【b】*
为元素列表
此时,取出列表中所有元素值对应的行或列:
【c】*
为切片
之前的Series
使用字符串索引时提到,如果是唯一值的起点和终点字符,那么就可以使用切片,并且包含两个端点,如果不唯一则报错:
需要注意的是,如果DataFrame
使用整数索引,其使用整数切片的时候和上面字符串索引的要求一致,都是元素切片,包含端点且起点、终点不允许有重复值。
???????????
【d】*
为布尔列表
在实际的数据处理中,根据条件来筛选行是极其常见的,此处传入loc
的布尔列表与DataFrame
长度相同,且列表为True
的位置所对应的行会被选中,False
则会被剔除。
例如,选出体重超过70kg的学生:
前面所提到的传入元素列表,也可以通过isin
方法返回的布尔列表等价写出,例如选出所有大一和大四的同学信息:
对于复合条件而言,可以用|(或), &(且), ~(取反)
的组合来实现,例如选出复旦大学中体重超过70kg的大四学生,或者北大男生中体重超过80kg的非大四的学生:
【e】*
为函数
这里的函数,必须以前面的四种合法形式之一为返回值,并且函数的输入值为DataFrame
本身。假设仍然是上述复合条件筛选的例子,可以把逻辑写入一个函数中再返回,需要注意的是函数的形式参数x
本质上即为df_demo
:
此外,还支持使用lambda
表达式,其返回值也同样必须是先前提到的四种形式之一:
由于函数无法返回如start: end: step
的切片形式,故返回切片时要用slice
对象进行包装:
最后需要指出的是,对于Series
也可以使用loc
索引,其遵循的原则与DataFrame
中用于行筛选的loc[*]
完全一致,此处不再赘述。
【WARNING】不要使用链式赋值
在对表或者序列赋值时,应当在使用一层索引器后直接进行赋值操作,这样做是由于进行多次索引后赋值是赋在临时返回的copy
副本上的,而没有真正修改元素从而报出SettingWithCopyWarning
警告。例如,下面给出的例子:
4. iloc索引器
iloc
的使用与loc
完全类似,只不过是针对位置进行筛选,在相应的*
位置处一共也有五类合法对象,分别是:整数、整数列表、整数切片、布尔列表以及函数,函数的返回值必须是前面的四类合法对象中的一个,其输入同样也为DataFrame
本身。
在使用布尔列表的时候要特别注意,不能传入Series
而必须传入序列的values
,否则会报错。因此,在使用布尔筛选的时候还是应当优先考虑loc
的方式。
例如,选出体重超过80kg的学生:
对Series
而言同样也可以通过iloc
返回相应位置的值或子序列:
5. query方法
在pandas
中,支持把字符串形式的查询表达式传入query
方法来查询数据,其表达式的执行结果必须返回布尔列表。在进行复杂索引时,由于这种检索方式无需像普通方法一样重复使用DataFrame
的名字来引用列名,一般而言会使代码长度在不降低可读性的前提下有所减少。
例如,将loc
一节中的复合条件查询例子可以如下改写:
在query
表达式中,帮用户注册了所有来自DataFrame
的列名,所有属于该Series
的方法都可以被调用,和正常的函数调用并没有区别,例如查询体重超过均值的学生:
NOTE】query中引用带空格的列名
对于含有空格的列名,需要使用`col name`
的方式进行引用。
【END】
同时,在query
中还注册了若干英语的字面用法,帮助提高可读性,例如:or, and, or, is in, not in
。例如,筛选出男生中不是大一大二的学生:
此外,在字符串中出现与列表的比较时,==
和!=
分别表示元素出现在列表和没有出现在列表,等价于is in
和not in
,例如查询所有大三和大四的学生:
对于query
中的字符串,如果要引用外部变量,只需在变量名前加@
符号。例如,取出体重位于70kg到80kg之间的学生
6. 随机抽样
如果把DataFrame
的每一行看作一个样本,或把每一列看作一个特征,再把整个DataFrame
看作总体,想要对样本或特征进行随机抽样就可以用sample
函数。有时在拿到大型数据集后,想要对统计特征进行计算来了解数据的大致分布,但是这很费时间。同时,由于许多统计特征在等概率不放回的简单随机抽样条件下,是总体统计特征的无偏估计,比如样本均值和总体均值,那么就可以先从整张表中抽出一部分来做近似估计。
sample
函数中的主要参数为n, axis, frac, replace, weights
,前三个分别是指抽样数量、抽样的方向(0为行、1为列)和抽样比例(0.3则为从总体中抽出30%的样本)。
replace
和weights
分别是指是否放回和每个样本的抽样相对概率,当replace = True
则表示有放回抽样。例如,对下面构造的df_sample
以value
值的相对大小为抽样概率进行有放回抽样,抽样数量为3。
二、多级索引¶
1. 多级索引及其表的结构
为了更加清晰地说明具有多级索引的DataFrame
结构,下面新构造一张表,读者可以忽略这里的构造方法,它们将会在第4小节被更详细地讲解。
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 |
下图通过颜色区分,标记了DataFrame
的结构。与单层索引的表一样,具备元素值、行索引和列索引三个部分。其中,这里的行索引和列索引都是MultiIndex
类型,只不过索引中的一个元素是元组而不是单层索引中的标量。例如,行索引的第四个元素为("B", "Male")
,列索引的第二个元素为("Height", "Senior")
,这里需要注意,外层连续出现相同的值时,第一次之后出现的会被隐藏显示,使结果的可读性增强。
与单层索引类似,MultiIndex
也具有名字属性,图中的School
和Gender
分别对应了表的第一层和第二层行索引的名字,Indicator
和Grade
分别对应了第一层和第二层列索引的名字。
索引的名字和值属性分别可以通过names
和values
获得:
但对于索引而言,无论是单层还是多层,用户都无法通过index_obj[0] = item
的方式来修改元素,也不能通过index_name[0] = new_name
的方式来修改名字,关于如何修改这些属性的话题将在第三节被讨论。
2. 多级索引中的loc索引器¶
熟悉了结构后,现在回到原表,将学校和年级设为索引,此时的行为多级索引,列为单级索引,由于默认状态的列索引不含名字,因此对应于刚刚图中Indicator
和Grade
的索引名位置是空缺的。
由于多级索引中的单个元素以元组为单位,因此之前在第一节介绍的 loc
和 iloc
方法完全可以照搬,只需把标量的位置替换成对应的元组。
当传入元组列表或单个元组或返回前二者的函数时,需要先进行索引排序以避免性能警告:
当使用切片时需要注意,在单级索引中只要切片端点元素是唯一的,那么就可以进行切片,但在多级索引中,无论元组在索引中是否重复出现,都必须经过排序才能使用切片,否则报错:
此外,在多级索引中的元组有一种特殊的用法,可以对多层的元素进行交叉组合后索引,但同时需要指定loc
的列,全选则用:
表示。其中,每一层需要选中的元素用列表存放,传入loc
的形式为[(level_0_list, level_1_list), cols]
。例如,想要得到所有北大和复旦的大二大三学生,可以如下写出:
下面的语句和上面类似,但仍然传入的是元素(这里为元组)的列表,它们的意义是不同的,表示的是选出北大的大三学生和复旦的大二学生:
3. IndexSlice对象¶
前面介绍的方法,即使在索引不重复的时候,也只能对元组整体进行切片,而不能对每层进行切片,也不允许将切片和布尔列表混合使用,引入IndexSlice
对象就能解决这个问题。Slice
对象一共有两种形式,第一种为loc[idx[*,*]]
型,第二种为loc[idx[*,*],idx[*,*]]
型,下面将进行介绍。为了方便演示,下面构造一个索引不重复的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
对象,先要进行定义:
【a】loc[idx[*,*]]
型
这种情况并不能进行多层分别切片,前一个*
表示行的选择,后一个*
表示列的选择,与单纯的loc
是类似的:
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 |
另外,也支持布尔序列的索引:
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[*,*]]
型
这种情况能够分层进行切片,前一个idx
指代的是行索引,后一个是列索引。
Big | E | F | |||
---|---|---|---|---|---|
Small | e | f | e | f | |
Upper | Lower | ||||
A | b | -2 | 5 | -4 | 4 |
c | 6 | 6 | 9 | -6 |
但需要注意的是,此时不支持使用函数:
4. 多级索引的构造
前面提到了多级索引表的结构和切片,那么除了使用set_index
之外,如何自己构造多级索引呢?常用的有from_tuples, from_arrays, from_product
三种方法,它们都是pd.MultiIndex
对象下的函数。
from_tuples
指根据传入由元组组成的列表进行构造:
三、索引的常用方法
1. 索引层的交换和删除
为了方便理解交换的过程,这里构造一个三级索引的例子:
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
完成,前者只能交换两个层,而后者可以交换任意层,两者都可以指定交换的是轴是哪一个,即行索引或列索引:
【NOTE】轴之间的索引交换¶
这里只涉及行或列索引内部的交换,不同方向索引之间的交换将在第五章中被讨论。
【END】
若想要删除某一层的索引,可以使用droplevel
方法:
2. 索引属性的修改
通过rename_axis
可以对索引层的名字进行修改,常用的修改方式是传入字典的映射:
通过rename
可以对索引的值进行修改,如果是多级索引需要指定修改的层号level
:
传入参数也可以是函数,其输入值就是索引元素:
【END】
对于整个索引的元素替换,可以利用迭代器实现:
若想要对某个位置的元素进行修改,在单层索引时容易实现,即先取出索引的values
属性,再给对得到的列表进行修改,最后再对index
对象重新赋值。但是如果是多级索引的话就有些麻烦,一个解决的方案是先把某一层索引临时转为表的元素,然后再进行修改,最后重新设定为索引,下面一节将介绍这些操作。
另外一个需要介绍的函数是map
,它是定义在Index
上的方法,与前面rename
方法中层的函数式用法是类似的,只不过它传入的不是层的标量值,而是直接传入索引的元组,这为用户进行跨层的修改提供了遍历。例如,可以等价地写出上面的字符串转大写的操作:
关于map
的另一个使用方法是对多级索引的压缩,这在第四章和第五章的一些操作中是有用的:
同时,也可以反向地展开:
3. 索引的设置与重置
为了说明本节的函数,下面构造一个新表:
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 |
索引的设置可以使用set_index
完成,这里的主要参数是append
,表示是否来保留原来的索引,直接把新设定的添加到原索引的内层
可以同时指定多个列作为索引:
如果想要添加索引的列没有出现在其中,那么可以直接在参数中传入相应的Series
:
reset_index
是set_index
的逆函数,其主要参数是drop
,表示是否要把去掉的索引层丢弃,而不是添加到列中:
如果重置了所有的索引,那么pandas
会直接重新生成一个默认索引:
4. 索引的变形
在某些场合下,需要对索引做一些扩充或者剔除,更具体地要求是给定一个新的索引,把原表中相应的索引对应元素填充到新索引构成的表中。例如,下面的表中给出了员工信息,需要重新制作一张新的表,要求增加一名员工的同时去掉身高列并增加性别列:
这种需求常出现在时间序列索引的时间点填充以及ID
编号的扩充。另外,需要注意的是原来表中的数据和新表中会根据索引自动对齐,例如原先的1002号位置在1003号之后,而新表中相反,那么reindex
中会根据元素对齐,与位置无关。
还有一个与reindex
功能类似的函数是reindex_like
,其功能是仿照传入的表索引来进行被调用表索引的变形。例如,现在已经存在一张表具备了目标索引的条件,那么上述功能可采用下述代码得到:
四、索引运算
1. 集合的运算法则
经常会有一种利用集合运算来取出符合条件行的需求,例如有两张表A
和B
,它们的索引都是员工编号,现在需要筛选出两表索引交集的所有员工信息,此时通过Index
上的运算操作就很容易实现。
不过在此之前,不妨先复习一下常见的四种集合运算:
2. 一般的索引运算
由于集合的元素是互异的,但是索引中可能有相同的元素,先用unique
去重后再进行运算。下面构造两张最为简单的示例表进行演示:
若两张表需要做集合运算的列并没有被设置索引,一种办法是先转成索引,运算后再恢复,另一种方法是利用isin
函数,例如在重置索引的第一张表中选出id列交集的所在行: