第3章 Pandas入门 NumPy作为Python科学计算的基础软件包,它的功能非常强大,但它不支持异构列表数据。什么是异构?简单地说就是指一个整体中包含不同的成分的特性,即这个整体由多个不同的成分构成。 在实际工作及生活中,使用的数据大多数是二维异构列表数据。二维异构列表数据是指在一个二维数据结构中允许不同的列拥有不同的数据类型。Pandas正是为处理此类数据而生的,它能够灵活、高效地处理和SQL、Excel电子表格类似的二维异构列表数据,使Pandas迅速成为Python的核心数据分析支持库。 以下是Pandas的特色及NumPy与Pandas的区别说明。 1. Pandas的特色 Pandas是Python生态环境下非常重要的数据分析包。它是一个开源的、有开源协议的库。正因为有了它的存在,基于Python的数据分析才大放异彩,为世人瞩目。 Pandas吸纳了NumPy中的很多精华,同时又能支持图表和混杂数据运算。这是强于NumPy的地方。因为NumPy数组中只能支持单一的某种数据类型(例如: 整型或浮点型)。Pandas是基于NumPy构建的数据分析包,但包含了比ndarray更高级的数据结构和操作工具。正是因为series与DataFrame的存在,才使Pandas在进行数据分析时,十分便捷与高效。 2. NumPy 和Pandas 的区别 NumPy生成的是ndarray数组,而Pandas则可基于NumPy生成两种对象(Series,DataFrame)。 Series是一维数组,它能保存不同种类的数据类型(字符串、boolean值、数字等),而NumPy只能存储同类型数据。DataFrame是二维的表格型数据结构,DataFrame 的每一列都是一个Series。 3.1Series Series的中文含义是“次序、顺序、连续”,在后面的所有章节会直接采用英文Series。DataFrame(结构)与Series(结构)都有一个Index,Index也是Pandas的数据结构之一。 注意: 在Pandas中,DataFrame、Series、Index这3种数据结构的首字母均须大写。 3.1.1Series基础知识 1. Series结构 Series是由一组同类型的数据和一组与数据对应的Index(标签)所组成的。也就是说: Series(结构)=Index(结构)+ data(类似于一组数组的数据结构),它是Pandas的核心结构之一,也是DataFrame结构的基础。Series的语法说明如下: pd.Series( data=None, #传入数组,可迭代对象、字典或标量值 index=None, #以数组或列表形式传入自定义索引,若不传值,则按系统默认值 dtype=None,#指定数据类型。如果为None,则默认为object name=None, #自定义series的名字,默认为none copy=False, #一个布尔值。如果值为True,则复制输入的数据data fastpath=False, ) 作用: 通过对各类数据结构转换后,生成Pandas的Series数据结构。 注意: 创建Series是用pd.Series()实现的,而不是用df.Series()。 pd.Series()语法的图解说明如图31所示。 图31图解说明pd.Series() 注意: 当指定索引时,索引的长度一定要与Series中的data长度相等,否则会报错。 当输入的是pd.Series(np.array([1,2,3,]),index=['A行','B行','C行','D行'])时,会报错,如图32所示。 图32报错提示 以下是pd.Series()的代码演示: >>> import pandas as pd >>> #1. Series的索引默认从0开始,步长为1的整型序列 >>> print(pd.Series(data=np.array([1,2,3]))) 01 12 23 dtype: int32 >>> print(pd.Series(np.array([1,2,3]))) 01 12 23 dtype: int32 >>> print(pd.Series(np.array([1,2,3]), index=['A行', 'B行', 'C行'])) A行1 B行2 C行3 dtype: int32 >>> #2. 当指定索引时,Series会按指定的索引进行排列 >>> print(pd.Series(np.array([1,2,3]), ...index=['A行', 'B行', 'C行'], ...dtype=float)) A行1.0 B行2.0 C行3.0 dtype: float64 >>> #3. 为Series命名是允许的,默认为none >>> print( pd.Series(np.array([1,2,3]), ...index=['A行', 'B行', 'C行'], ...dtype=float, ...name='第1个Series')) A行1.0 B行2.0 C行3.0 Name: 第1个Series, dtype: float64 >>> #4. 将copy参数设置为True, 注意True的首字母为大写 >>> print( pd.Series(np.array([1,2,3]), ...index=['A行', 'B行', 'C行'], ...dtype=float, ...name='第1个Series', ...copy=True)) A行1.0 B行2.0 C行3.0 Name: 第1个Series, dtype: float64 注意: 在Python中,True与False的首字母必须大写,否则会报错。 2. Series与NumPy的初步比对 NumPy是Pandas的基础及重要依赖项,所以在Pandas中随时可以找到NumPy的“身影”。首先导入NumPy及Pandas的第三方库,代码如下: import numpy as np import pandas as pd Pandas与NumPy的一些属性对比,如图33所示。 图33Pandas与NumPy的属性对比 在图33中,Series.values()与NumPy的array存在关联性。np.array()相关的属性,例如: dtype、shape、size、ndim、nBytes、T等属性均适用于Series或DataFrame。当然,Pandas在ndarray的基础上,也新增了不少新的属性,例如: index、 memory_usage、hasnans、empty、flags、set_flags、name等。 3.1.2Series的构建 Series是一个类似于一维数组的对象,可以把它理解为一组带索引的NumPy一维数组。常见的创建的方式有列表转Series、字典转Series、ndarray转Series。在创建的过程中可能同时要考虑是否须特别指定index、是否须设置数据类型等。 一维数组与Series之间最大的区别在于: Series附带索引列(Index)。Series的索引所附带的信息作为一个有效的标识而经常用到(例如: 表间关系型的应用),这是单纯的一维数组所不具备的。 1. 列表(List)转Series 以下列举了List转Series的5种场景,代码如下: #场景一 >>> pd.Series(list('456')) 04 15 26 dtype: object #场景二 >>> pd.Series(['a','b','c'],index=[0,1,2]) 0a 1b 2c dtype: object #场景三 >>> pd.Series(index=list('0a2b'), data=np.arange(4)) 00 a1 22 b3 dtype: int32 #场景四 >>> pd.Series([1,2,3]) 01 12 23 dtype: int64 #场景五 >>> pd.Series([1, 2, 3, 8], index=[5,2,3,1]) 51 22 33 18 dtype: int64 下面的这几种情形需特别注意,代码如下: #list*n >>> pd.Series([2]*3) 02 12 22 dtype: int64 #list*n >>> pd.Series([1,2,3]*3) 01 12 23 31 42 53 61 72 83 dtype: int64 #Series*n >>> pd.Series([1,2,3,])*3 03 16 29 dtype: int64 在pd.Series([2]*3)与pd.Series([1,2,3]*3)中,它们的*3是以列表[2]和[1,2,3]为对象的。在Python中,使用数字n乘以一个序列会生成新的序列。新序列的内容为原来序列被重复n次的结果。例: [2]*3的结果为[2,2,2]; [1,2,3]结果为[1,2,3,1,2,3,1,2,3]。 pd.Series([1,2,3,])*3中,它的*3是对Series进行操作,而pd.Series().values其实是一个NumPy数组,而数组是具备广播功能的,所以值为np.array([3,6,9])。 2. 元组(Tuple)转Series 元组转Series,代码如下: >>> pd.Series((1,2,3)) 01 12 23 dtype: int64 3. 字典(Dist)转Series 在Python中,字典是放在花括号{}内的一系列键值对; 在字典中,想存放多少个键值对取决于需求。在字典中,每个键都与一个值相关联,键与值之间用冒号分隔,而键值对之间用逗号分隔。这些对应的值可以是“数字、字符串及其他”,代码如下: >>> pd.Series({ ... "Joe":'Beijing', ... "Kim":'Shanghai', ... "Jim":'Guangzhou' ... }) Joe Beijing KimShanghai JimGuangzhou dtype: object 4. ndarray转Series 以下列举了4种应用场景,后3种侧重于Index的指定,代码如下: #场景一 >>> pd.Series(np.array((1,2,3))) 01 12 23 dtype: int32 #场景二 >>> pd.Series(np.arange(4,9),index=list('abcde')) a4 b5 c6 d7 e8 dtype: int32 #场景三 >>> pd.Series(np.arange(4,9),index=np.arange(6,11)) 6 4 7 5 8 6 9 7 108 dtype: int32 #场景四 >>> pd.Series(np.linspace(0,9,5),['A','B','C','D','E']) A0.00 B2.25 C4.50 D6.75 E9.00 dtype: float64 5. 常量转Series 常量转Series,代码如下: >>> pd.Series(5,index=np.arange(4)) 05 15 25 35 dtype: int64 3.1.3Series的常用转换方法 在Pandas中,以下转换方法适用于Series及DataFrame。 1. astype() astype()用于对Series中的数据类型进行强制转换,代码如下: >>> s=pd.Series([1, 2, 3],dtype='int32') >>> s.dtype dtype('int32') >>> s.astype('uint32').dtype dtype('uint32') 2. convert_dtypes() convert_dtypes()用于对DataFrame或Series中的数据类型进行强制转换,代码如下: >>> s = pd.Series([1, 2, 3], dtype=np.dtype("float32")) >>> s.convert_dtypes() 01 12 23 dtype: Int64 通过convert_dtypes(),自动转换到最可能的数据类型。这个功能在DataFrame的数据导入过程中会经常用到。需要说明的是: 在NumPy中,当未做数据的子类型指定时,整型数据多数情况下为32位; 经convert_dtypes()转换后,数据类型默认为64位。在NumPy和Pandas中,浮点型的数据类型默认为float64。 3. to_x() 在Pandas中,to_x()系列有pd.to_x()和df.to_()两大类。常用的有df.to_numpy()、df.to_list()、pd.to_period()、pd.to_timestamp()、pd.to_numeric()等,代码如下: >>> pd.Series(pd.Categorical(['a', 'b', 'a', 'c'])).to_numpy() array(['a', 'b', 'a', 'c'], dtype=object) >>> pd.Series(np.array([1, 2, 3], dtype='int')).to_numpy(dtype=object) array([1, 2, 3], dtype=object) >>> pd.Series(np.array([1, 2, 3])).to_list() [1, 2, 3] Series与DataFrame的to_x()系列中还有很多,例如: to_excel()、to_csv()、to_sql()、to_json()、to_dist()等,这里面的很多方法在接触DataFrame时会经常使用到。 在Pandas中,read与to是一对黄金搭档。读取文件的方法以pd.read_x()为主,而写入的方法以df.to_x()为主,如表31所示。 表31Pandas中常用的读取与写入方法 数 据 类 型描述符读 取 方 法写 入 方 法 文本CSVpd.read_csv()df.to_csv() 文本JSONpd.read_json()df.to_json() 文本HTMLpd.read_csv()df.to_csv() 文本剪切板pd.read_clipboard()df.to_clipboard() 二进制Excelpd.read_excel()df.to_excel() 二进制HDF5pd.read_hdf()df.to_hdf() 二进制PKLpd.read_pickle()df.to_pickle() SQLSQLpd.read_sql()df.to_sql() 3.1.4Series的“十八招” 在中国的传统武术中有“十八般武艺”之说,现在一般多用来比喻各种技能。Pandas也有它的“十八般武艺”,例如: 筛选、删除、保留、填充、修改、转换、排序、计算等。 本节所罗列的十八招(含文本处理的“一招九式”)为Pandas的“十八般武艺”。以下均以Series对象做演示,其目的是让读者先对Pandas的常见功能有个大体、直观的了解,达到“纲举目张”的效果。以下十八招来源于笔者日常数据分析的使用流程及经验的累积,同时又兼顾了理解的容易性及招数的实战性。以下是十八招的着力点。 (1) 在行与列的优先序方面,优先处理列。如果知道导出的数据哪一个区间段的列是所需的,完全可以采取切片的方式; 如果列名是有规律的,则可以采用filter或其他函数的方式,选取所需的列; 如果整列为空的,则可以采取drop方法。 (2) 对行进行筛选。行筛选的方式主要有指定前/后所保留的行数、条件筛选(例如: any/all、isin/between等)、比较条件筛选(例如: >、<、=等)等。 (3) 行列处理后的值可能需要进行二次清洗工作。例如: 删除(空值、重复值、异常值等); 如果某些空值是不能删除的,就需要按指定规则对其进行填充。 (4) 转换环节。首先,对文本进行转换,抽取复合型文本中的数字,对特定文本进行拆分与合并,然后,要对数据进行转换,进入对应的四则运算及格式转换。 (5) 对规整的数据进行各式各样的处理(例如: 排序、分组、聚合),也可以在此基础上进行挖掘与图形化呈现。 第1招: 筛选 filter()是一个Python的内置函数,用于过滤掉不符合条件的元素,代码如下: >>> s = pd.Series([1,2,3],index=['A','B1','B2']) >>> s.filter(like='B') B12 B23 dtype: int64 filter()是一个很好用且很有用的数据筛选方法。它里面有items、like、regex这3种互为冲突的选择模式(也就是说: 三者只能选一),用于处理不同的选择模式。其中,items适用于listlike,like适用于str,regex适用于正则。 (1) item参数的用法。在DataFrame中,items中的listlike用于按名字筛选列。 s. filter(items=['B1','B2'])与s.filter(like='B')等效。 例: df.filter(items=['Name','City']),用于筛选df中的Name、City两列。 (2) like参数的用法。在filter中,like参数可以与axis参数一起使用。 例: df.set_index('Name').filter(like='im',axis=0),筛选出索引中含im的所有名字。 (3) regex参数的用法。regex参数可以与axis参数一起使用,regex代表的是正则。 df.set_index('Name').filter(regex='im$',axis=0),筛选出索引中含im的所有名字。 df.set_index('Name').filter(regex='e$',axis=1),筛选出列名中以e结尾的所有字段。 除了filter()方法,Pandas中还有select_dtypes()方法,此方法用于列筛选,依据指定的数据类型选择对应的列。需要注意的是: 在Series中查看数据类型用dtype,但在DataFrame中查看数据类型则用dtypes,代码如下: >>> pd.Series([1,2,3],index=['A','B1','B2']).dtype dtype('int64') 注意: Series中没有类似select_dtype()之类的方法。 第2招: 保留 1. head() head( )函数只能读取前5行数据,也可另行指定前几行。head(2)为前2行,代码如下: >>> s = pd.Series([1,2,3],index=['A','B1','B2']) >>> s.head(2) A 1 B12 dtype: int64 2. tail() tail()方法默认显示数据集的最后5行,也可另行指定后几行,代码如下: >>> s.tail(2) B12 B23 dtype: int64 尽管本书一直在用小数据做演示,但在实际数据处理分析时,肯定会碰到大数据。如果只想查看前几行,则可以用.head()方法; 如果想看最后几行,则可以用.tail()方法。 3. 索引与切片 以下是常见的3种下标索引方式,代码如下: >>> s = pd.Series(np.random.normal(size=5)) >>> s[0] 0.608344392668116 >>> s.loc[0] 0.608344392668116 >>> s.iloc[0] 0.608344392668116 布尔索引也是常用的一种索引方式,代码如下: >>> s= pd.Series(np.arange(3)) >>> s[s>1] 22 dtype: int32 以下是切片的应用举例,代码如下: #索引与切片 >>> s = pd.Series(np.random.normal(size=5)) >>> s[:3] 0-0.304613 11.067088 2-1.260125 dtype: float64 第3招: 判断 在计算机语言中,判断的返回值只有两种: 是(True)或否(False)。在计算机语言中,以is开头的函数多为信息函数,其返回值为True或False。 1. isnull() df.isnull()用于检查数据是否有缺失; df.notnull()用于判断是否未缺失; df.isnull()与df.notnull()互为逻辑的对立面,代码如下: >>> s = pd.Series(['R','G','B'],index=[1,3,np.nan]) >>> s.isna() 1.0False 3.0False NaNFalse dtype: bool >>> s.isnull() 1.0False 3.0False NaNFalse dtype: bool >>> s.notna() #等价于s.notnull() 1.0True 3.0True NaNTrue dtype: bool >>> s.notna().sum() 3 通过缺失值检测(isna或notna)后生成布尔值,返回值为True或False。 2. isin() isin()用于查看Series中是否包含某个字符串,返回布尔值,代码如下: >>> s = pd.Series(['R',np.nan,'G'],index=np.arange(3)) >>> s.isin(['R', 'G', 'B', 'nan',36]) 0 True 1False 2 True dtype: bool >>> s = pd.Series([2, 0,np.nan]) >>> s.between(1, 4,inclusive=False) 0 True 1False 2False dtype: bool isin()用于接受一个列表,并且判断该列中元素是否在列表中。 3. 比较 在Pandas中相同长度的Series之间,组间比较是允许的,代码如下: >>> a = pd.Series([1, 2, np.nan, 5], index=['a', 'b', 'd', 'e']) >>> b = pd.Series([0, 4, np.nan, 5], index=['a', 'b', 'd', 'f']) >>> a.lt(b, fill_value=0) aFalse b True dFalse eFalse f True dtype: bool Pandas中比较运算及数值运算所对应的方法,如表32所示。 表32比较运算及数值运算所对应的方法 运 算 类 别运算符方 法 名 称 比较运算<、>、<=、>=、==、!=.lt()、.gt()、.le()、.ge()、.eq()、.ne() 数值运算+、-、*、/、//、%、**.add、.sub、.mul、.div、.floordiv、.mod、.pow Series与DataFrame中常用的6个比较运算符对应的英文全称及中文含义如表33所示。 表33常用的6个比较运算符 方法对应的运算符英文全称中文含义 .lt()greater than大于 .le()<=less than or equal to小于或等于 .ge()>=greater than or equal to大于或等于 .eq()==equal to等于 .ne()!=not equal to不等于 4. any与all any()用于当Series中只要有一个值满足条件时其返回值为True; all()用于当Series中所有值均满足条件时其返回值为True,代码如下: >>> s_ = pd.Series([9,10,11,12]) >>> s_ [s_>8].all() True >>> s_ [s_>12].any() False 第4招: 删除 当Series存在缺失值时,依据指定的方式进行删除,代码如下: >>> s = pd.Series(['R','G','R', np.nan],index=[1,'A',np.nan,5]) >>> s 1R AG NaNR 5NaN dtype: object >>> s.drop(labels=['A',np.nan]) 1R 5NaN dtype: object >>> s.dropna(how='any') 1R AG NaNR dtype: object 当Series的(axis=0)和DataFrame的(行方向axis=0,列方向axis=1)存在缺失值时,会按指定的axis有针对性地删除空值(how='all',所在方向的所有数据都为缺失值时; how='any',所在方向只要有数据为缺失值时)。 除了用how='all'或how='any',还可以采用thresh=n的方式,例如: thresh=3,当数据所在方向的有效值低于3时,删除该行或该列的数据。 第5招: 去重 drop_duplicate()方法是用于去除特定列下面的重复行,代码如下: >>> s = pd.Series(['R','G','R', np.nan],index=[1,'A',np.nan,5]) >>> s 1R AG NaNR 5NaN dtype: object >>> s_=s.drop_duplicates(keep='last') >>> s_ AG NaNR 5NaN dtype: object >>> s_.drop_duplicates(keep=False, inplace=True) >>> s_ AG NaNR 5NaN dtype: object >>> s 1R AG NaNR 5NaN dtype: object keep有first、last、False三个选项,默认为first; 参数False表示删除所有重复值。inplace参数的默认值为False,inplace=True代表就地删除(会直接影响数据源)。 第6招: 填充 填充的模式主要有bfill(或backfill)、ffill(或pad)、None这3种,代码如下: >>> s = pd.Series(['R','G','B'],index=[1,3,5]) >>> s 1R 3G 5B dtype: object >>> s=s.reindex(np.arange(0,7),method='ffill') >>> s 0NaN 1R 2R 3G 4G 5B 6B dtype: object 在Pandas中:可通过method='ffill'或method='pad'实现自动向下空值填充,而自动向上空值填充则可通过method='bfill'或method='backfill'实现。 填充模式的图解说明如图34所示。 图34填充模式 第7招: 修改 在Pandas中奉行的是无则新增,有则修改的原则。对于原有数据的重新赋值则意味着修改原有的数据,代码如下: >>> s = pd.Series(['R',np.nan,'G'],index=np.arange(3)) >>> s[5]=83 >>> s 0R 1NaN 2G 583 dtype: object >>> s[len(s)]=69 #按索引顺序在最后增加 >>> s 0R 1NaN 2G 583 469 dtype: object >>> s.add_prefix('idx_') idx_0R idx_1NaN idx_2G idx_583 idx_469 dtype: object >>> s.add_suffix('_idx') 0_idxR 1_idxNaN 2_idxG 5_idx83 4_idx69 dtype: object 第8招: 追加 append()方法用于在列表末尾添加新的对象,代码如下: >>> s1 = pd.Series([1,2,3]) >>> s2 = pd.Series([2]*3) >>> s1.append(s2) 01 12 23 02 12 22 dtype: int64 第9招: 变形 经过变形与转换,数据结构会发生改变。常用于数据结构转换的函数有stack()、unstack()、swaplevel()、ravel()等,代码如下: >>> s = pd.Series( ... [1, 2, 3, 4], ... index=pd.MultiIndex.from_product([ ...['A', 'B'],['a', 'b']])) >>> s Aa1 b2 Ba3 b4 dtype: int64 >>> s.unstack(level=0) AB a13 b24 >>> s.unstack(level=-1) ab A12 B34 >>> s.ravel() array([1, 2, 3, 4], dtype=int64) >>> s.reorder_levels([1,0]) aA1 bA2 aB3 bB4 dtype: int64 >>> s.swaplevel() aA1 bA2 aB3 bB4 dtype: int64 第10招: 文本 字符串是Python中最常用的数据类型之一。相同字符串内或不同字符串间可以进行字符串运算或格式转换,代码如下: >>> s = pd.Series(['wo', np.nan, 'Wo De', 'Zuguo']) >>> s.str.upper() 0 WO 1NaN 2WO DE 3ZUGUO dtype: object >>> s.str.title() 0 Wo 1NaN 2Wo De 3Zuguo dtype: object Pandas中的文本操作是一个很值得深入探索的主题,Pandas中的文本操作一般以Series为单位,通过Series的str属性来完成。通过Series.str属性的转换操作,可用于完成一般的字符大小写转换、空值删除等日常性操作,也可用于拼接(cat)、 连接(join)、拆分(split)、包含(contains)、匹配(match)、查找(find)、提取(extract)、替换(replace)、重复(repeat)等更多的复杂操作。在这些操作方法中,有些支持正则应用。 第1式: find(查找) find不支持正则,返回的是索引位置。findall支持正则,返回的是一个列表,代码如下: >>> s = pd.Series(['a1', 'b2', 'c3']) >>> s.str.find('a') 00 1-1 2-1 dtype: int64 >>> s.str.findall('a1') 0[a1] 1[] 2[] dtype: object >>> s.str.findall('a1').str[0] 0 a1 1NaN 2NaN dtype: object 第2式: join(连接) 组内与组间字符串连接的应用举例,代码如下: >>> pd.Series(['Kim','Jim','Joe']).str.join('-') 0K-i-m 1J-i-m 2J-o-e dtype: object >>> pd.Series([['Kim','Jim','Joe'],['SH','SZ','BJ']]).str.join('-') 0Kim-Jim-Joe 1SH-SZ-BJ dtype: object s.str.join()只有一个参数(分隔符sep=),返回的值是Series。 第3式: cat(拼接) str.cat()方法主要有两个重要参数,sep=' '用于指定分隔符,na_rep=' '用于指定空值,代码如下: >>> s = pd.Series(['wo', np.nan, 'Wo De', 'Zuguo']) >>> s.str.cat(sep=' ') 'wo Wo De Zuguo' >>> s.str.cat(sep=' ', na_rep='?') 'wo ? Wo De Zuguo' >>> t = pd.Series(['d', 'a', 'e', 'c'], index=[3, 0, 4, 2]) >>> s.str.cat(t, join='left',sep=' ', na_rep='-') 0 wo a 1- - 2Wo De c 3Zuguo d dtype: object 第4式: repeat(重复) str.repeat(repeats)用于复制字符串。参数repeats可为整型或向量。整型表示每个字符串都复制相同的次数,向量则是可以设置每个元素重复的次数,代码如下: >>> s = pd.Series(['a', 'b', 'c']) >>> s 0a 1b 2c dtype: object >>> s.str.repeat(repeats=2) 0aa 1bb 2cc dtype: object >>> s.str.repeat(repeats=[1, 2, 3]) 0a 1bb 2ccc dtype: object 第5式: contains(包含) str.contains()相当于SQL中的like,用于字符串的模糊筛选,代码如下: >>> s= pd.Series(['wo', 'ai','wo', 'de', 'zuguo']) >>> s.str.contains('o', regex=False) 0 True 1False 2 True 3False 4 True dtype: bool 判断字符是否有包含关系,经常用在数据筛选中,支持正则。 第6式: match(匹配) str.match()的语法为str.match(pat,case,flags,na),用于确定Series对象中的每个字符串是否与正则表达式匹配,代码如下: >>> s = pd.Series(['a1', 'b2', 'c3']) >>> s.str.match('a') 0 True 1False 2False dtype: bool >>> s.str.match('a1') 0 True 1False 2False dtype: bool 参数pat是具有捕获组的正则表达式模式; 参数case用于区分大小写; 参数flags可为re.I、re.G、re.M; na为缺失值的填充方式。 第7式: replace(替换) str.replace()的语法为str.replace(pat,repl,regex),用于字符串的替换。参数pat为查找的内容,一般为正则表达式; repl为要替的字符串,代码如下: >>> (pd.Series( ...['wo','ai','wo','de','zuguo']) ....str.replace('w.', 'Wo', ...regex=True)) 0 Wo 1 ai 2 Wo 3 de 4zuguo dtype: object >>> (pd.Series( ...['wo','ai','wo','de','zuguo']) ....str.replace('w.', 'Wo', ...regex=False)) 0 wo 1 ai 2 wo 3 de 4zuguo dtype: object 可用于文本替换,支持正则。当不想使用正则功能时,可以使用参数regex=False来关闭。 第8式: split(拆分) str.split()的语法结构为str.split(pat,n,expand),用于字符串的拆分,类似于Excel中的拆分列功能。pat为字符串或正则表达式,n默认为-1,expand默认值为True,代码如下: >>> pd.Series(['wo ai wo de zuguo']).str.split(" ") 0[wo, ai, wo, de, zuguo] dtype: object >>> pd.Series(['wo ai wo de zuguo']).str.split(n=4,expand=True) 0 1 2 34 0woaiwodezuguo 第9式: exact(提取) str.extract()的语法为str.extract(pat,flags,expand),用于从字符数据中抽取匹配的数据。pat参数为字符串或正则表达式,代码如下: >>> s = pd.Series(['a1', 'b2', 'c3']) >>> s.str.extract(r'([abc])(\d)') 01 0a1 1b2 2c3 >>> s.str.extract(r'([ab])?(\d)') 01 0a1 1b2 2NaN3 >>> s.str.extract(r'[abc](\d)', expand=True) 0 01 12 23 >>> s.str.extract(r'[abc](\d)', expand=False) 01 12 23 dtype: object str.extract可以利用正则将文本中的数据提取出来,从而形成单独的列,支持正则。如果expand参数为True,则返回一个DataFrame,不管是一列还是多列,当expand参数为False且只有一列时才会返回一个 Series/Index。 第11招: 映射 apply函数是Pandas中自由度最高的一个函数,它的语法结构为df.apply(func,axis,...,**kwds),代码如下: >>> s = pd.Series([1, 2, 3]) >>> def sq(x): ...return (0.98*x) ** 2 s.apply(sq) 00.9604 13.8416 28.6436 dtype: float64 >>> s = pd.Series([[1, 2, 3],[4,5,6]]) >>> s.apply(np.max) 03 16 dtype: int64 除apply之外,后续还会运用较多的映射类函数,如applymap、map函数及相关联的transform、pipe函数。 第12招: 排序 sort_values()是Pandas中使用频率较高的一种排序方法,代码如下: >>> s = pd.Series([1, 2, np.nan, 5]) >>> s 01.0 12.0 2NaN 35.0 dtype: float64 >>> s.sort_values(ascending=True) 01.0 12.0 35.0 2NaN dtype: float64 >>> s.sort_values(ascending=True, ...na_position='first') 2NaN 01.0 12.0 35.0 dtype: float64 它的语法结构为DataFrame.sort_values(by,axis=0,ascending=True,inplace=False,kind='quicksort',na_position='last',ignore_index=False,key=None)。 sort_values()的ascending参数,默认升序为True,降序则为False。如果是列表,则需与by所指定的列表数量相同,指明每一列的排序方式。 sort_values()的na_position参数,有first与last两种选择模式,默认值为last。当指定的排序列有nan值时,nan值放在序列中第1个或最后一个。 第13招: 计算 加、减、乘、除四则运算是使用频率最高的运算,Pandas支持不同Series间的组间运算,可以采用运算符方式,也可以用函数的方式进行计算,代码如下: >>> s1 = pd.Series([1,2,3]) >>> s2 = pd.Series([2]*3) >>> s3 = s1+s2*4 >>> s3 0 9 110 211 dtype: int64 >>> s4 = pd.Series(666, index=[ 1,2,'C']) >>> s4 1666 2666 C666 dtype: int64 >>> s3 + s4 0NaN 1676.0 2677.0 CNaN dtype: float64 >>> s3.add(s4,fill_value=0) 09.0 1676.0 2677.0 C666.0 dtype: float64 当两个Series间不存在空值时,无论是采用运算符方式还是采用函数方式其输出的结果都是一致的,但当两个Series间有一个Series存在空值或Index不一致时,则直接运算后的结果为nan,可以在add()及其他函数中用fill_value=0解决此问题。 以上代码的图解说明如图35所示。 图35位与字节 Pandas中,更多的运算函数如表34所示。 表34Pandas中的运算函数 add(),radd() sub(),rsub() mul(),rmul() div(),rdiv(),truediv(),rtruediv(),floordiv(),rfloordiv() mod(),rmod() pow(),rpow() combine(),combine_first() round() product() dot() 上面的radd()、rsub()等首字母为r的函数,其中的r代表的是reverse(反转、使次序颠倒)的意思。例如: pd.Series([1,2,3]).div(0),结果全为inf; pd.Series([1,2,3]).rdiv(0),结果全为0.0。其中的差别在于div(0)中0是除数,rdiv(0) 中0是被除数。除数为0,结果为无穷大; 被除数为0(除数不为0时),结果为0。 第14招: 描述 df.describe()用以完成统计学中的描述性统计分析,用于观测数据的整体趋势,代码如下: >>> s=pd.Series([1,2,3]) >>> s.describe() count3.0 mean 2.0 std1.0 min1.0 25%1.5 50%2.0 75%2.5 max3.0 dtype: float64 与上述描述性统计分析相对应的函数还有很多。 (1) 与趋势相关的(离中、趋中): corr()、cov()、kurt()、max()、mean()、median()、min()、mod()、sem()、skew()、std()、var()、kurtosis()、count()、quantile()、nlargest()、nsmallest()、nunique()、value_counts()等。 (2) 与累计相关的: sum()、cummax()、cummin()、cumsum()、cumprod()等。 (3) 与布尔相关的: is_unique()、is_monotonic()、is_monotonic_increasing()、is_monotonic_decreasing()等。 第15招: 聚合 第1式: groupby() Pandas的groupby()功能十分强悍与好用,代码如下: >>> s = pd.Series([1,3,9,2,5], ... index=['Kim', 'Jim', 'Joe', 'Tom','Sam'], name='ename') >>> s Kim1 Jim3 Joe9 Tom2 Sam5 Name: ename, dtype: int64 >>> s.groupby(["a", "b", "a", "b",'b']).mean() a5.000000 b3.333333 Name: ename, dtype: float64 >>> s.groupby(level=0).mean() Jim3 Joe9 Kim1 Sam5 Tom2 Name: ename, dtype: int64 >>> s.groupby(s > 4).mean() ename False2 True7 Name: ename, dtype: int64 它的语法结构为DataFrame.groupby(by=None,axis=0,level=None,as_index=True,sort=True,group_keys=True,squeeze=False,**kwargs)。 第2式: agg() agg是aggregate的简写。在Pandas中,df.groupby.agg()与df.groupby.aggregate()是完全等效的。应用agg()方法后,可以一次性使用多种汇总方式,可以针对不同的列采用不同的汇总方式,可以支持函数的多种写法,代码如下: >>> s=pd.Series([1,2,3]) >>> s 01 12 23 dtype: int64 >>> s.agg('min') 1 >>> s.agg(['min', 'max']) min1 max3 dtype: int64 >>> s.aggregate('min') 1 >>> s.aggregate(['min', 'max']) min1 max3 dtype: int64 >>> s.aggregate(['min', 'max']) == s.agg(['min', 'max']) minTrue maxTrue dtype: bool 第16招: 日期 Pandas继承了NumPy库和datetime库中与时间相关的模块,能更高效地处理时间序列数据,代码如下: >>> ts = pd.Series( ... pd.date_range('2021-03-01', ... periods=4, freq='2M')) >>> ts 0 2021-03-31 1 2021-05-31 2 2021-07-31 3 2021-09-30 dtype: datetime64[ns] >>> ts = pd.DataFrame( ... pd.date_range('2021-03-01', ... periods=4, freq='2M')).set_index(0) ts.index DatetimeIndex(['2021-03-31', '2021-05-31', '2021-07-31', '2021-09-30'], dtype='datetime64[ns]', name=0, freq=None) 第17招: 时间 时间索引同样可用于序列索引及时间偏移等相关操作,代码如下: >>> ts = pd.Series( index= ... pd.date_range('2021-03-01', ... periods=4, freq='36T'), ... data=[1,2,3,4]) >>> ts.between_time('1:10', '2:45') 2021-03-01 01:12:003 2021-03-01 01:48:004 Freq: 36T, dtype: int64 >>> ts.shift(periods=1, freq='H') 2021-03-01 01:00:001 2021-03-01 01:36:002 2021-03-01 02:12:003 2021-03-01 02:48:004 Freq: 36T, dtype: int64 第18招: 图表 在Pandas内可直接调用plot()方法,代码如下: s = pd.Series([1, 2, 3,4,5,6]) def sq(x): return (0.98*x) ** 3 s.apply(sq).plot() 输出的图形如图36所示。 图36折线图 以上十八招可以串起来理解: 整体的数据分析是由“清洗、运算、挖掘”三部分完成的。当接触到一个新数据时可以按以下步骤处理。 第1步: 识别数据类型是否存在空值或异常值等。对空值行统计、填充或删除; 对异常值进行去重或按条件筛选、切片等,完成相关清洗工作。 第2步: 以目标为导向,对数据进行计算处理与结构转换,完成相关数据的运算工作。 第3步: 对数据进行描述性分析、探索性挖掘、图形化交互与挖掘工作。 虽然上面列出了这么多招式,但是要强调的是,所有的数据分析与挖掘必须以熟悉业务及明确需求为前提。毕竟,数据分析与挖掘的本质是数据乘以业务,所有(与数据相关的)招式就好比工具,工具一旦离开了它的(业务)应用场景,其(数据分析与挖掘)效果是很难预判的。 3.2DataFrame 3.2.1DataFrame基础知识 DataFrame 是一个表格型的数据结构,它含有一组有序的列。它的每一列可以是不同的值类型,但同一列必须是相同的值类型(数值、日期、object类型)。其中object类型可以保存任何Python对象,例如字符串等。 DataFrame 既有行索引,又有列索引。它可以被看作n个Series组成的字典(共用同一个行索引)。也就是说: DataFrame(结构)=Index(结构)+ data(类似于二组数组的数据结构)。以下是DataFrame的语法说明,代码如下: pd.DataFrame( data = None, #要传入的数据,必选参数 #data可为ndarray、series、map、lists、dist、常量和另外一个DataFrame index = None, #行索引,可选参数 #默认值np.arange(n),即0,1,2,3… columns = None, #列索引,可选参数 #默认为np.arange(n),即0,1,2,3… dtype = None, #每列的数据类型,可选参数 copy = False, #从input输入中复制数据 ) 作用: 通过对各类数据输入或结构转换后,生成Pandas的DataFrame数据结构。 DataFrame的结构如图37所示。 图37DataFrame数据结构 Pandas的DataFrame是与Excel、SQL等类似的表格型数据结构,由index、columns、data三部分组成。其中,DataFrame的data部分与NumPy的ndarray是一致的。 在DataFrame中,axis=0与axis="index"是等价的,axis=1与axis="columns"是等价的。例如: 在df.iloc[:,3:7].sum(1)中,sum(1)的1代表的就是axis=1的方向,这是一种简写方式。 3.2.2创建 1. 文件导入生成 pd.read_excel(io,sheet_name=0,...)默认打开的sheet_name是导入的Excel对象中的第1个电子表格,可以省略不写,代码如下: pd.read_excel('demo_.xlsx') 输出的结果如下: DateNameCityAgeWorkYearsWeightBMIScore 02020-12-12JoeBeijing763556.018.86A 12020-12-12KimShanghai321285.021.27A 22020-12-13JimShenzhen552372.020.89B 32020-12-13TomNaN8733NaN21.22C 42020-12-14JimGuangzhou934259.020.89B 52020-12-14KimXiamen783665.0NaNB 62020-12-15SamSuzhou653269.022.89 A 为了让读者聚焦于语法及便于理解返回的值,本书的大部分代码演示围绕着上面这7行8列的数据展开。 pd.read_excel(): 作用: 将Excel读到Pandas的DataFrame。 说明: Pandas的核心在于数据分析,而不是数据文件的读取与写入,但是,从外部文件中读写数据,仍属于Pandas的重要组成部分。Pandas提供了很多API,以支持对外部数据(Excel、 CSV、SQL、JSON、HTML、Picklle、HDF等)的读写。在日常工作中,使用最多的是pd.read_excel()和pd.read_csv()两种方式。 以下是pd.read_excel()的语法说明: pd.read_excel( io, #相关Excel文件的存储路径 sheet_name=0, #要读取的工作表名称 header=0, #用哪一行作为列名 names=None, #自定义最终的列名 index_col=None, #用作索引的列 usecols=None, #需要读取哪些列 squeeze=False, #当数据仅包含一列 dtype=None, #指定的数据类型 …… ) 更多的参数说明与解析,会在第7章进行讲解。 2. Series创建 在Series构建或转换为DataFrame的过程中,可以有多种构建或转换方式。 1) 单个Series创建 Series创建DataFrame,代码如下: pd.Series([32,55,65],name="Age").to_frame() #将一个Seires转换为只有一列的表格 输出的结果如下: Age 032 155 265 2) 多个Series创建 以下是由多个Series合并为一个DataFrame的应用,代码如下: >>> s1=pd.Series(['Kim','Jim','Sam']) >>> s2=pd.Series((32,55,65)) >>> s3=pd.Series(('Shanghai','Shenzhen','Suzhou')) >>> pd.DataFrame(zip(s1,s2,s3), ... columns=['Name','Age','City']) >>> #分别将不同的Series写在zip中以便转换与创建 NameAgeCity 0Kim32Shanghai 1Jim55Shenzhen 2Sam65Suzhou >>> pd.DataFrame(zip(*[s1,s2,s3]), ... columns=['Name','Age','City']) >>> #多个Series在容器中的转换与创建 NameAgeCity 0Kim32Shanghai 1Jim55Shenzhen 2Sam65Suzhou 3. 字典创建 以下是通过字典数据创建Dataframe的应用,代码如下: pd.DataFrame({ "Name":["Kim",'Jim', 'Sam'], "Age":[32,55,65], "City":["Shanghai",'Shenzhen','Suzhou'] }) 输出的结果如下: NameAgeCity 0Kim 32Shanghai 1Jim 55Shenzhen 2Sam 65Suzhou 4. (二维)列表创建 以下是通过二维列表创建Dataframe的应用,代码如下: df = pd.DataFrame([ ["Kim",'Jim','Sam'], [32,55,65], ["Shanghai",'Shenzhen','Suzhou']], index=['Name','Age','City']) df.T 输出的结果如下: Name AgeCity 0Kim32Shanghai 1Jim55Shenzhen 2Sam65Suzhou 5. 元组创建 以下是通过元组创建DataFrame的应用,代码如下: pd.DataFrame( data=( ('Kim',32,'Shanghai'), ('Jim',55,'Shenzhen'), ('Sam',65,'Suzhou')), columns=['Name','Age','City'] ) 输出的结果如下: NameAge City 0Kim 32 Shanghai 1Jim 55Shenzhen 2Sam 65 Suzhou 3.2.3DataFrame相关知识 1. to_x()回顾 在Series中,有astype()、convert_dtypes()、to_x()等转换方法。在DataFrame中,有to_numpy()、to_dict()、to_string()等转换方法。 代码如下: >>> df = pd.read_excel('demo_.xlsx').head(3) >>> df.to_numpy() array([[Timestamp('2020-12-12 00:00:00'), 'Joe', 'Beijing', 76, 35, 56.0, 18.86, 'A'], [Timestamp('2020-12-12 00:00:00'), 'Kim', 'Shanghai', 32, 12, 85.0, 21.27, 'A'], [Timestamp('2020-12-13 00:00:00'), 'Jim', 'Shenzhen', 55, 23, 72.0, 20.89, 'B']], dtype=object) >>> df.to_dict() {'Date': {0: Timestamp('2020-12-12 00:00:00'), 1: Timestamp('2020-12-12 00:00:00'), 2: Timestamp('2020-12-13 00:00:00')}, 'Name': {0: 'Joe', 1: 'Kim', 2: 'Jim'}, 'City': {0: 'Beijing', 1: 'Shanghai', 2: 'Shenzhen'}, 'Age': {0: 76, 1: 32, 2: 55}, 'WorkYears': {0: 35, 1: 12, 2: 23}, 'Weight': {0: 56.0, 1: 85.0, 2: 72.0}, 'BMI': {0: 18.86, 1: 21.27, 2: 20.89}, 'Score': {0: 'A', 1: 'A', 2: 'B'}} 从输出的结果来看: to_numpy()生成的是ndarray对象,to_dict()生成的是dict(字典)对象,而to_string()生成的是str对象。 如何部分截取DataFrame中的内容,如图38所示。 图38部分截取DataFrame中的内容 代码如下: >>> pd.DataFrame(df.iloc[1:, 3:].to_dict()) AgeWorkYearsWeightBMIScore 1321285.021.27A 2552372.020.89B >>> pd.DataFrame(df.to_numpy()[1:, 3:], ...columns=['Age', 'WorkYears', 'Weight', 'BMI', 'Score']) AgeWorkYearsWeightBMIScore 032128521.27A 155237220.89B 关于df.to_x()方法,在使用过程中稍留心会发现有20多种。能够轻松驾驭数据结构间的相互转换将使数据分析变得更为灵活高效,代码如下: >>> pd.Series( ... df['Name'].to_numpy(), ... index=df['City']) City Beijing Joe ShanghaiKim ShenzhenJim dtype: object >>> pd.DataFrame( ... pd.Series(df['Name'].to_numpy(),index=df['City']), ... columns=["Name"] ... ) #注意: "Name"外面的[]不可省,否则会报错 Name City Beijing Joe ShanghaiKim ShenzhenJim 2. index()相关 1) index_col设置 导入时可直接设置索引列,代码如下: >>> pd.read_excel('demo_.xlsx',index_col='City').head(2) DateNameAgeWorkYearsWeightBMIScore City Beijing2020-12-12Joe 7635 56.018.86 A Shanghai 2020-12-12Kim3212 85.021.27 A 2) set_index与reset_index() 先导入数据再设置索引列,代码如下: pd.read_excel('demo_.xlsx').set_index("City") 结果如图39所示。 图39设置索引列 取消原有索引列,重置并将多列设置为索引列,代码如下: df.reset_index().set_index(['Name', 'City'], drop=False) 结果如图310所示。 图310重设索引列并保留原索引列 DataFrame的set_index函数会将其一个或多个列转换为行索引,并创建一个新的DataFrame。默认情况下,那些列会从DataFrame中移除,但也可以用drop=False将其保留下来。 3. 属性 DataFrame中的部分属性如图311所示。 图311DataFrame的属性 如果想查看各列的数据类型,可以用dtypes属性(注意: 查看Series的数据类型可采用dtype,而查看DataFrame的数据类型采用的是dtypes); 如果想知道DataFrame中数据类型的总数,则可以在dtypes属性后再加上value_counts()方法。演示代码如下: >>> df = pd.read_excel("demo_.xlsx") >>> df.dtypes Date datetime64[ns] Nameobject Cityobject Ageint64 WorkYearsint64 Weight float64 BMIfloat64 Score object dtype: object >>> df.dtypes.value_counts() object 3 int642 float642 datetime64[ns]1 dtype: int64 >>> df.Date.dtype dtype('>> df.info() RangeIndex: 7 entries, 0 to 6 Data columns (total 8 columns): #Column Non-Null CountDtype --------- ------------------- 0 Date 7 non-nulldatetime64[ns] 1 Name 7 non-nullobject 2 City 6 non-nullobject 3 Age7 non-nullint64 4 WorkYears7 non-nullint64 5 Weight 6 non-nullfloat64 6 BMI6 non-nullfloat64 7 Score7 non-nullobject dtypes: datetime64[ns](1), float64(2), int64(2), object(3) memory usage: 576.0+ Bytes 从图311可以发现: 这个DataFrame共由7行8列组成(列名有Date和Name等),这8列中: float64有2列,int64有2列,object有4列(float64、int64、object为数据类型,可以用.dtype属性查看)。例如: 输入df.Date.dtype,输出为dtype('O'),直接查看的是df[' Date ']列的数据属性。 3.3本章回顾 Pandas在数据处理与分析及图形化呈现方面,功能十分强大。当然,Pandas之所以能有如此强大的功能与其有着丰富的方法与属性分不开。 十八般武艺并非局限18种武艺,十八般武艺只是众多武艺的一个概说。以上列出的十八招及文本操作的一招九式,也只是Pandas库里丰富的方法与属性应用的一个缩影。掌握好十八招是Pandas入门的必经之路,这中间少不了对语法及参数的掌握及反复使用所形成的肌肉记忆,最后逐步形成自己的知识体系并相互融会贯通,让Pandas真正实现由了解到强大再过渡到真正应用上的强大。 Pandas本身就好比一个有着器灵存在的上品神器,它具备力量法则、空间法则、时间法则等加持于一身,使之具备越阶作战的能力。具备众多的API及与第三方库无缝链接,使之力量备受加持,是力量法则的体现,即管理学所讲的“赋能”。众多应用场景中的各类花式的数据的筛选与转换,是空间法则的体现。Pandas中强大的时间序列功能,通过各类频率的转换、重采样、时间窗口等功能,让时间拉长、变短、偏移等,是时间法则的体现。同时,它拥有攻击属性和防护属性于一体,攻击属性体现在其强大的数据分析与挖掘能力,防护属性体现在其灵活的数据批量获取与存储、数据的结构与类型转换等。 面对如此功能强大的上品神器,读者就是它的新认主人,是否打算立马炼化它?总之,纸上得来终觉浅,绝知此事须躬行。