第5章 数 据 转 换 5.1基础知识 本节主要介绍一下计算机的3种程序结构及Pandas中常用的for循环语句。 5.1.1程序结构 计算机的程序设计中有3种基本的结构: 顺序结构、分支结构和循环结构。 1. 顺序结构 顺序结构是程序设计中最简单的结构,它的执行顺序是自上而下的,即依次执行,代码如下: import pandas as pd df=pd.read_excel('demo_.xlsx') a = df.nunique() == 5 cols = a[a].index cols 代码依据编写的顺序依次被执行(先定义变量再调用变量),输出的结果如下: Index(['Name','BMI'],dtype='object') 2. 分支结构 分支结构主要是使用if条件语句。其语句是: if关键字后紧跟条件测试表达式。 当语句中存在条件判断时,需要做分支选择。当条件符合时,选择哪个; 当条件不符合时,返回的结果又是哪个。分支结构有单选(if)、二选一(ifelse)、多选一(ifelifelse)。 除了简单的if语句、ifelse、ifelifelse语句,也经常会用到if语句的嵌套。例如: if语句中嵌套ifelse,ifelse中嵌套ifelse; 又例如: for循环中的if语句、while循环中的if语句等。 在每个if或elif语句的后面,加冒号后换行缩进,再开始另外一行代码。缩进编写是Python代码的特色之一。 3. 循环结构 在Python数据分析中,循环语句是必不可少的语句。常见的循环有for循环与while循环。for循环一般用于循环次数确定的场合,例如列表循环、字典循环等,而while循环一般用于循环次数不确定的场合。在Pandas数据分析过程中,使用最多的循环为for循环。 在Pandas的for循环使用过程中,会经常嵌套分支结构,从而形成了类似forif、forifelse、foriffor、forif(break/continue)、forpass等循环结构。 在计算机编程过程中,对于某些很有规律的操作(很容易找到共性的操作),最简单的实现方式就是采用循环。“循环”,其实是操作过程中某些共性的总结。通过代码化循环操作,可以极大地提高工作效率,代码如下: df = pd.read_excel('demo_.xlsx') [i for i in df.Score] 输出的结果如下: ['A', 'A', 'B', 'C', 'B', 'B', 'A'] 当然,循环也是有代价的,因为逐行循环迭代的原因,从而可使计算机的运行速度大受拖累。例如在Pandas中常用的下标索引循环就是一个明显的例子。正如第4章所讲,在Pandas中,NumPy的向量化函数的速度是最快的,如果能用NumPy或Pandas的向量化函数解决的问题,就尽量不要采用循环迭代的方式。 5.1.2循环语句 1. forif循环 在 for 语句后面跟上一个 if 判断语句,用于筛选不符合条件的值,代码如下: df = pd.read_excel('demo_.xlsx') a = df['Score'] #将df['Score']赋值给变量a l = [] for i in a: if not i in l:#筛选出列表中的重复元素 l.append(i)#只有当列表中不存在该值时才会被追加 l 输出的结果如下: ['A', 'B', 'C'] 2. forifelse循环 分支语句中else子句用于执行当其他的条件不满足时的情景,代码如下: df = pd.read_excel('demo_.xlsx',nrows=3) #nrows=3, 前面的3行 for i in df['Score']: if i=='A': print("优")#当df['Score']== 'A'时,显示为"优" elif i=='B': print("良") #当df['Score']== 'B'时,显示为"良" else: print("中") #当df['Score']不为'A'或'B'时,显示为"中" 输出的结果如下: 优 优 良 在Python中,else分支子句可配合for、while等循环语句使用,还能配合try…except语句进行异常处理使用。 3. for…if…for循环 在二维数组遍历过程中,经常会使用双层for循环。如果在循环的过程中需要加上条件判断,则可在 for 语句后面加上一个 if 判断语句,代码如下: df = pd.read_excel('demo_.xlsx') l = [] for i in df['Score']: if i =="A": for j in df['Name']: if j =='Kim': l.append(i+"_"+j) l 输出的结果如下: ['A_Kim', 'A_Kim', 'A_Kim', 'A_Kim', 'A_Kim', 'A_Kim'] 4. for…if…continue,终止本次循环,进入下一次循环 continue对前面的for起作用,用于终止本次循环,进入下一次循环。其应用原理: 跳过符合本次if条件筛选的操作,进入下一次for循环操作,直至结束,代码如下: lt = ['Kim','Jim','Joe','Tom'] for i in lt: if i=='Tom': continue print(i) 输出的结果如下: Kim Jim Joe 5. for…if…break,终止当前循环 break跳出的是for循环,结束当前的循环,代码如下: lt = ['Kim','Jim','Joe','Tom'] for i in lt: if i=='Joe': break print(i) 输出的结果如下: Kim Jim 注意: 在多层循环中,一个break语句只向外跳一层。 6. for…pass,占位 pass只是为了保持程序结构的完整性,不会做任何操作,代码如下: lt = ['Kim','Jim','Joe','Tom'] for i in lt: print(i) else: pass#占位 输出的结果如下: Kim Jim Joe Tom 5.2映射函数 以下是map、apply、applymap这3个函数的异同点比较。 (1) map是Python的原生态函数,apply和applymap是Pandas的函数。 (2) apply与map的作用对象是Series,遍历的是Series中的每个值。applymap的作用对象是DataFrame中的每个元素,遍历的是DataFrame的每个值。 (3) apply与map可以作用于Series,而applymap不可以。 (4) apply与applymap可以通过axis来确定作用的方向,而map不可以。 (5) apply与applymap可以作用于整个DataFrame,而map不可以。 5.2.1map() 语法: map(function,iterable,…)。 结果: 根据提供的函数对指定序列进行映射。 参数: function表示函数; iterable表示一个或多个序列。 说明: map是Python的内置函数。 map()会根据提供的字典或函数,对指定的对象做映射,代码如下: df = pd.read_excel('demo_.xlsx',nrows=3) df["状况"] = df["Score"].map({"A":"优","B":"良","C":"中"}) df 输出的结果如下: DateNameCityAgeWorkYearsWeightBMIScore状况 02020-12-12Joe Beijing 76355618.86A优 12020-12-12KimShanghai 32128521.27A优 22020-12-13JimShenzhen 55237220.89B良 map的应用场景较为广泛,后续章节会有大量的演示案例可供参考。 5.2.2apply() 语法: apply(func,axis=0,broadcast=False,raw=False,reduce=None,args=(),**kwds)。 结果: 函数作为一个对象,可作为参数传递给其他参数,并且可作为函数的返回值。 参数: func表示函数、自定义函数或匿名函数,它是apply最有用的第一参数。axis决定第一参数func起作用的轴方向,默认值为axis=0。 apply()是Pandas中最为灵活、使用频率最高的函数之一。它可以作用于 Series 或者整个 DataFrame,功能也是自动遍历整个 Series 或者 DataFrame。它可以通过axis参数来控制方向,作用于行或者作用于列。后续的章节有大量的apply()的应用案例。 apply()函数都是以Series为单位的,但它可以作用于DataFrame。apply主要有3种应用方式: 第1种是用匿名函数(lambda),第2种是用自定义函数(def),第3种是用函数(例如Python函数、NumPy函数等)。 1. apply作用于Series 1) 匿名函数实现方式 以下是apply的匿名函数的实现方式,代码如下: df = pd.read_excel('demo_.xlsx',nrows=3) df['等级'] = ( df['Score'] .apply(lambda x: "优" if x == 'A' else x) .apply(lambda x: "良" if x == 'B' else x) .apply(lambda x: "中" if x == 'C' else x)) df 输出的结果如下: DateNameCityAgeWorkYearsWeightBMIScore等级 02020-12-12JoeBeijing76355618.86A优 12020-12-12KimShanghai32128521.27A优 22020-12-13JimShenzhen55237220.89B良 在NumPy与Pandas中,二维数据axis默认为0。以下代码是axis=1的应用举例,axis用于控制不同Series间的组间求和,代码如下: df_ = pd.read_excel('demo_.xlsx') df_['求和']= df_.iloc[:,3:7].apply(lambda x: x.sum(),axis=1) df_ 输出的结果如下: DateNameCityAgeWorkYearsWeightBMIScore求和 02020-12-12JoeBeijing763556.018.86A185.86 12020-12-12KimShanghai321285.021.27A150.27 22020-12-13JimShenzhen552372.020.89B170.89 32020-12-13TomNaN8733NaN21.22C141.22 42020-12-14JimGuangzhou934259.020.89B214.89 52020-12-14KimXiamen783665.0NaNB179.00 62020-12-15SamSuzhou653269.022.89A188.89 匿名函数是指没有命名的函数,通常在某些需要函数的场合使用,但这个场合的这个函数一般只使用一次,所以就不需特意为其命名。 注意: 匿名函数中不能出现for或while循环语句,因为匿名函数表达式的返回值只能有一个返回值(匿名函数的参数可以为一个或多个,如果参数为多个,则需要用逗号分开)。 2) 自定义函数的实现方式 以下是自定义函数的实现方式,代码如下: df = pd.read_excel('demo_.xlsx',nrows=3) #取前面的3行数据 def AA(s): if s=='A': return "优" elif s=='B': return "良" elif s=='C': return "中" df['等级']=df['Score'].apply(AA) df 输出的结果如下: DateNameCityAgeWorkYearsWeightBMIScore 02020-12-12JoeBeijing76355618.86A 12020-12-12KimShanghai32128521.27A 22020-12-13JimShenzhen55237220.89B 函数可理解为带名字的代码块,用于完成具体的工作。当需要在程序中多次执行同一个任务时,可以通过事先创建一个函数,然后在使用的过程中实现对这个函数的调用。创建函数(也可以称为定义函数),语法如下: def [函数名]([输入参数]=[默认值]): [代码] return([输出值]) 注意: 对于自定义函数,即使这个函数没有参数,也必须保留空括号()。如果这个函数带有多个参数,则各参数间应使用逗号(“,”)分隔。 2. apply作用于DataFrame 以下是函数(NumPy的通用函数)的实现方式,代码如下: df = pd.read_excel('demo_.xlsx') df.iloc[:,3:7] = df.iloc[:,3:7].apply(np.square) df 输出的结果如下: DateNameCityAgeWorkYearsWeightBMIScore 02020-12-12JoeBeijing577612253136.0355.6996A 12020-12-12KimShanghai10241447225.0452.4129A 22020-12-13JimShenzhen30255295184.0436.3921B 32020-12-13TomNaN75691089NaN450.2884C 42020-12-14JimGuangzhou864917643481.0436.3921B 52020-12-14KimXiamen608412964225.0NaNB 62020-12-15SamSuzhou422510244761.0523.9521A apply的第一参数接收的是函数,这个函数可以是前面所讲的自定义函数或匿名函数,也可以是上面的NumPy函数等。在本例中,np.square函数被当作参数传入。 apply可以通过axis来控制作用的行与列,代码如下: df.iloc[:3,3:].apply(lambda x:x.name+'-'+x.astype(str)) #df.iloc[:3,3:].apply(lambda x:x.index+'-'+x.astype(str),axis=1) #以上两行代码输出的结果是相同的 输出的结果如下: AgeWorkYearsWeightBMIScore 0Age-5776WorkYears-1225Weight-3136.0BMI-355.6996Score-A 1Age-1024WorkYears-144Weight-7225.0BMI-452.4129Score-A 2Age-3025WorkYears-529Weight-5184.0BMI-436.3921Score-B 这里给读者留一个问题,供大家思考: 为什么代码中第1个是axis=0,而另一个是axis=1,明明控制的方向不同,但结果为什么最后却还相同呢? 在apply中允许函数嵌套。例如apply中的lambda嵌套,代码如下: df = pd.read_excel('demo_.xlsx') df.iloc[:,3:7] = (df.iloc[:,3:7] .apply(lambda x: x.apply( lambda y:str(y)+'-隐退'if y>=65 else y) if sum(x)>=185 else x,axis=1) ) df 输出的结果如下: DateNameCityAgeWorkYearsWeightBMIScore 02020-12-12JoeBeijing76.0-隐退35.056.018.86A 12020-12-12KimShanghai32.012.085.021.27A 22020-12-13JimShenzhen55.023.072.020.89B 32020-12-13TomNaN87.033.0NaN21.22C 42020-12-14JimGuangzhou93.0-隐退42.059.020.89B 52020-12-14KimXiamen78.036.065.0NaNB 62020-12-15SamSuzhou65.0-隐退32.069.0-隐退22.89A 在DataFrame中,通过切片(df.iloc[:,3:7])可用新值替换原有的值。 5.2.3applymap() df.applymap(func)为其语法结构,返回的值为DataFrame。 1. 通用函数 第一参数的函数可以是通用函数,代码如下: df = pd.read_excel('demo_.xlsx') df.applymap(str).info() 结果展示如下: RangeIndex: 7 entries, 0 to 6 Data columns (total 8 columns): #Column Non-Null CountDtype ------------------------ 0Date7 non-nullobject 1Name7 non-nullobject 2City7 non-nullobject 3Age7 non-nullobject 4WorkYears7 non-nullobject 5Weight7 non-nullobject 6BMI7 non-nullobject 7Score7 non-nullobject dtypes: object(8) memory usage: 576.0+ Bytes 原来的其他数据类型已全部转换为object类型。 2. 自定义函数 第一参数的函数可以是自定义函数,代码如下: df = pd.read_excel('demo_.xlsx') def AA(x): return "demo_"+str(x) df.applymap(AA) 输出的结果如图51所示。 图51类型转换与文本加工 3. 匿名函数 第一参数的函数也可以是匿名函数,代码如下: df = pd.read_excel('demo_.xlsx') df.applymap(lambda x: x * 10 if isinstance(x, int) and x > 50 else x) 输出的结果如下: DateNameCityAgeWorkYearsWeightBMIScore 02020-12-12JoeBeijing7603556.018.86A 12020-12-12KimShanghai321285.021.27A 22020-12-13JimShenzhen5502372.020.89B 32020-12-13TomNaN87033NaN21.22C 42020-12-14JimGuangzhou9304259.020.89B 52020-12-14KimXiamen7803665.0NaNB 62020-12-15SamSuzhou6503269.022.89A isinstance()是Python的内置函数,用来判断对象是否是已知的类型,类似于type()。它与 type() 的区别: (1) type()不会认为子类是一种父类类型,不考虑继承关系。 (2) isinstance()会认为子类是一种父类类型,考虑继承关系。如果要判断两种类型是否相同,则推荐使用isinstance()。 5.3各类转换 5.3.1数据类型转换 在Pandas中,数据类型的转换在DataFrame层面多用convert_dtypes(),而在Series层面多用astype()。 1. convert_dtypes() convert_dtypes()可以自动推断数据类型并进行转换。对于int、float、datetime类型的数据,全部会自动转换为64位,代码如下: df_ = pd.read_excel('demo_.xlsx').convert_dtypes() 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-nullstring 2 City 6 non-nullstring 3 Age7 non-nullInt64 4 WorkYears7 non-nullInt64 5 Weight 6 non-nullInt64 6 BMI6 non-nullFloat64 7 Score7 non-nullstring dtypes: Float64(1), Int64(3), datetime64[ns](1), string(3) memory usage: 604.0 Bytes 特别说明: 在NumPy中是有str和object区分的,其中dtype('S')对应于str,dtype('O')对应于object,但是,在Pandas中str和object类型都对应于dtype('O')类型,二者间其实在转换后并无实质性区别。Pandas中的object类型对应的就是Python中的str字符类型。 在Pandas中,支持的数据类型有float、int、bool、datetime64、timedelta[ns]、category、object等。例如,float其实是Python内置的数据类型,可以在NumPy及Pandas中直接使用,但在NumPy及Pandas中也可以使用与其对等的数据类型。 在导入数据的过程中可对数据类型进行转换,代码如下: dft = pd.read_excel( 'demo_.xlsx', dtype={ 'Date': 'datetime64', 'Age': 'object', 'WorkYears': 'object', 'Weight': 'str', 'Score': 'category' } ) dft.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-nullobject 4 WorkYears7 non-nullobject 5 Weight 6 non-nullobject 6 BMI6 non-nullfloat64 7 Score7 non-nullcategory dtypes: category(1), datetime64[ns](1), float64(1), object(5) memory usage: 551.0+ Bytes 对比info()方法与dtypes属性的显示,代码如下: dft.dtypes 输出的结果如下: Date datetime64[ns] Nameobject Cityobject Age object WorkYears object Weightobject BMIfloat64 Score category dtype: object 在上面的代码中,.info()是方法,.dtypes是属性。在Pandas中,当初次面对一个DataFrame数据时,经常会用到.info()方法或.dtypes属性来对数据的整体结构及各字段的数据类型进行一个直观的了解。 注意: 当了解的对象是Series时,用的是.dtype属性; 当了解的对象是DataFrame时,使用的是.dtypes属性。 上面的两例在数据导入的过程中对数据类型进行了自动识别或指定。其实,当数据导入或运行之后,仍旧可以对数据类型按需求进行转换,代码如下: def f(x): return x * 6 df['Age'].apply(f, convert_dtype=False) 输出的结果如下: 0456 1192 2330 3522 4558 5468 6390 Name: Age, dtype: object 2. astype() 在Pandas中astype()用于对数据类型进行强制转换。当数据经过强制类型转换后,convert_dtype将不再起作用,代码如下: def f(x): return x * 6 df['Weight'].astype('str').apply(f, convert_dtype=True) #df['Weight'].astype('str').apply(f, convert_dtype=False) #df['Weight'].astype('str').apply(f) #以上3行代码输出的结果是相同的 输出的结果如下: 056.056.056.056.056.056.0 185.085.085.085.085.085.0 272.072.072.072.072.072.0 3nannannannannannan 459.059.059.059.059.059.0 565.065.065.065.065.065.0 669.069.069.069.069.069.0 Name: Weight, dtype: object 在Python中,字符串*n意味着重复n次。 现以df['Age']为例,先将数值强制转换为文本型,然后强制转换为整型,代码如下: df = pd.read_excel('demo_.xlsx') df['Age']=df['Age'].astype(str)#修改某列数据类型 df['Age']=df['Age'].astype(np.int64) 二次转换后的数据类型对比如图52所示。 图52强制转换数据类型 astype()在数据类型的强制转换过程中遵循着“就高不就低”的原则。例如: int32转float64,会新增小数位; float64转int32,会将小数点截掉; string转float64,数值型字符串会被强制转换为浮点型。Python、NumPy、Pandas中的数据类型对照表见表51。 表51数据类型对照表 Pandas中的数据类型NumPy中的数据类型Python中的数据类型用途 objectstring_,unicode_str文本 int64int_,int8_,int16,int32,int64m uint8,uint16,uint32,uint64int整型 float64float_,float16,float32,float64float浮点型 boolbool_boolTrue/False datetime64datetime64[ns]NA日期/时间 timedelta[ns]NANA时间差 categoryNANA类别型文本 关于NumPy中的所有数据类型及详细分类,参阅表51的NumPy数据类型对照表。 以下案例通过.assign()方法在DataFrame中新建列,然后对新建的列用.astype()来强制指定数据类型。相关代码如下: #导入数据,只取Age、WorkYears两列中的前3行数据 df_ = pd.read_excel('demo_.xlsx', usecols=['Age','WorkYears'], nrows=3) #对Age、WorkYears两列进行数据类型指定 (df_.assign( Age=df['Age'].astype(np.int16), WorkYears=df['WorkYears'].astype(np.int16)) ).dtypes 输出的结果如下: Ageint16 WorkYearsint16 dtype: object 下面的例子中,df['City']和df['Name']原先就存在,但df['nCity']原先并不存在,所以就变成了新增列df['nCity'],强制更改df['Name']列,而df['City']列数据类型保持不变,代码如下: df = pd.read_excel('demo_.xlsx', usecols=['Name','City'], nrows=3) (df.assign( nCity=df.City.astype('category'), Name=df.Name.astype('category')) ).info() 输出的结果如下: RangeIndex: 3 entries, 0 to 2 Data columns (total 3 columns): #ColumnNon-Null CountDtype ---------------------- 0 Name3 non-nullcategory 1 City3 non-nullobject 2 nCity 3 non-nullcategory dtypes: category(2), object(1) memory usage: 422.0+ Bytes 除此之后,还可以对groupby()的对象进行强制数据类型转换,代码如下: ( pd.read_excel('demo_.xlsx') .groupby(['City', 'Name'])['Weight'] .mean() .astype(int) ) 输出的结果如下: City Name BeijingJoe 56 GuangzhouJim 59 Shanghai Kim 85 Shenzhen Jim 72 Suzhou Sam 69 Xiamen Kim 65 Name: Weight, dtype: int32 注意: 如果要转换的列存在空值,则为避免报错的可能,须先对空值进行填充fillna(0),再做数据类型的强制转换astype()。 5.3.2数据结构转换 在Pandas中,会经常用到stack()与unstack()做索引的互换。 stack()可将数据的列“旋转”为行,unstack()可将数据的行“旋转”为列,stack()与unstack()为一组逆运算操作。如果不指定旋转的索引级别,stack()与unstack()默认对最内层进行操作(level=-1),这里的-1是指倒数第一层。 stack与unstack是互逆的过程,stack与unstack的区别在于行与列谁放在前面的问题。stack采用行放前列放后的方式,而unstack采用列放前行放后的方式。 注意: df.stack()的返回值为Series,df.unstack()的返回值为DataFrame。 1. stack() 语法: df.stack(level=-1,dropna=True)。 结果: Stack the prescribed level(s) from columns to index。 DataFrame的列标签为单层索引,将其转换为行,代码如下: df = pd.read_excel('demo_.xlsx') df.stack() 结果呈现及图解说明如图53所示。 图53图解stack()工作原理 以上输出的结果与Excel中的Power Query的以下M代码是等效的,代码如下: let 源 = Excel.CurrentWorkbook(){[Name="表1"]}[Content], 逆透视 = Table.UnpivotOtherColumns(源, {}, "属性", "值") in 逆透视 对堆叠后的数据重设索引列,使之数据结构由Series转变为DataFrame,代码如下: df.stack().reset_index() 对比堆叠数据及堆叠后重设索引的数据,如图54所示。 图54对比堆叠数据 图54中DataFrame的列名不直观、不好理解,现对columns重新命名,代码如下: df.stack().reset_index().rename( columns={ 'level_0': '索引', #将'level_0'列改名为'索引' 'level_1': '属性',#将'level_1'列改名为'属性' 0: '值'})#将0列改名为'值' #以下代码与上面的代码输出的结果是一样的 #df.stack().rename_axis(['索引', '属性']).reset_index(name='值') 重命名列前与重命名列后的对比如图55所示。 图55重命名列 2. unstack() 语法: Series.unstack(level=- 1,fill_value=None),返回DataFrame或拆堆后的Series。 经过堆叠(stack)后再拆堆(unstack)的DataFrame,返回的值为原DataFrame,因为stack与unstack是一对互逆操作,代码如下: df = pd.read_excel('demo_.xlsx') df.stack().unstack() 输出的结果如图56所示,为原DataFrame。 图56堆叠与拆堆 unstack()操作会使窄表变成宽表。为了方便理解它的用法,只取(['Name','City','Score'])这三列数据来做观察,代码如下: dfu = pd.read_excel('demo_.xlsx')[['Name', 'City', 'Score']] dfu = dfu.set_index(['Name', 'City']) [:3] dfu 以上代码等效于下面的代码,两者输出的结果完全一致,代码如下: dfu= pd.read_excel('demo_.xlsx', usecols=['Name', 'City', 'Score'], index_col=[0,1], nrows=3) dfu 以上代码的详细语法将在第7章讲解。输出的结果如下: NameCityScore JoeBeijingA KimShanghaiA JimShenzhenB 重设索引,代码如下: dfu.reset_index() 输出的结果如下: NameCityScore 0JoeBeijingA 1KimShanghaiA 2JimShenzhenB 运用unstack()方法,将行值转换为列值,代码如下: dfu.reset_index().unstack() 前面两个步骤的图解说明如图57所示。 图57图解unstack()运行原理(1) 对获取的数据进行拆堆(unstack)操作,代码如下: dfui = pd.read_excel('demo_.xlsx', index_col=[0,1], #指定索引列 usecols=['Name', 'City', 'Score'],#指定需导入的列 nrows=3).unstack('City') #指定数据范围。拆堆操作 dfui 运行过程的原理及结果如图58所示。 图58图解unstack()运行原理(2) 3. melt() 语法: dfl.melt(id_vars,value_vars,var_name,value_name='value',col_level,ignore_index=True,) 结果: 返回值为DataFrame。 作用: 逆透视DataFrame,将宽表变成窄表。 参数: melt()的参数说明如表52所示。 表52melt()的参数说明 参数对象参 数 说 明 id_varstuple、list or ndarray。作为标识符变量使用的列optional value_varstuple、list or ndarray。如果未指定,则使用所有列 var_namescalar(标量)。用于“变量”列的名称。如果没有,则使用' frame.columns.name '或“变量” value_namescalar。默认值用于“值”列的名称default 'value' col_levelint or str。如果列是一个多层索引,那么使用指定层级去meltoptional ignore_indexbool。如果值为True,则忽略原始索引而重新索引。如果值为False,则保留原索引default True 在上述参数中,id是identifier(标识符)的简写; var是variable(变量)的简写; col是column(column)的简写; level是multi_index中的level; index是指DataFrame的index。 DataFrame的melt()操作,代码如下: dfl = pd.read_excel('demo_.xlsx').iloc[:2,:4] dfl dfl.melt() 前面两个步骤的图解说明如图59所示。 图59图解melt()运行原理 DataFrame的melt()与stack()操作比较。逐行运行以下代码: dfl = pd.read_excel('demo_.xlsx').iloc[:2,:4] dfl.melt() dfl.stack() 图解说明df.melt()方法与df.stack()的差异性,如图510所示。 图510比较melt()方法与stack()方法 以下两种运行方式都是允许的,代码如下: dfl = pd.read_excel('demo_.xlsx').iloc[:2,:4] dfl.melt(id_vars='City') #方式一 pd.melt(dfl,'City') #方式二 以上两种方式输出的结果是一样的,输出的结果如下: Cityvariablevalue 0BeijingDate2020-12-1200:00:00 1ShanghaiDate2020-12-1200:00:00 2BeijingNameJoe 3ShanghaiNameKim 4BeijingAge76 5ShanghaiAge32 继续运行的代码如下: pd.melt(dfl,id_vars=['Date'],value_vars=['City','Name']) #'Date'作为分组依据,['City','Name']为属性列 输出的结果如下: Datevariablevalue 02020-12-12CityBeijing 12020-12-12CityShanghai 22020-12-12NameJoe 32020-12-12NameKim 运行的代码如下: pd.melt(dfl,value_vars=['City','Name']) #无任何分组依据,['City','Name']为属性列 输出的结果如下: variablevalue 0CityBeijing 1CityShanghai 2NameJoe 3NameKim 运行的代码如下: #未使用var_name、value_name参数 dfl.melt(id_vars=['City'], value_vars=['Date', 'Name', 'Age']) 继续运行的代码如下: #使用了var_name、value_name参数 dfl.melt(id_vars=['City'], value_vars=['Date', 'Name', 'Age'], var_name='属性', value_name='值') 图解var_name、value_name两参数的使用,对比结果如图511所示。 图511图解var_name、value_name两参数的使用 运行的代码如下: pd.melt(dfl,'City').pivot('City','variable','value').reset_index() 输出的结果如下: variableCityAgeDateName 0Beijing762020-12-12Joe 1Shanghai322020-12-12Kim 4. pivot() 语法: df.pivot(index,columns,values)。 结果: 返回值DataFrame。 对DataFrame的pivot()应用,代码如下: dfl = pd.read_excel('demo_.xlsx').iloc[:2,:4] dfl.pivot("Date","Name","Age") #方法一 #dfl.pivot(index="Date",columns="Name",values="Age") #方法二 #dfl.pivot("Date","Name")["Age"] #方法三 方法一、方法二、方法三输出的结果完全一样。对比Excel的透视表,理解Pandas pivot()的运行原理,如图512所示。 图512pivot()的运行原理 代码如下: dfl = pd.read_excel('demo_.xlsx').iloc[:2,:4] dfl.pivot(index="Date",columns=['City',"Name"],values="Age") 输出的结果如下: CityBeijingShanghai NameJoeKim Date 2020-12-127632 运行的代码如下: dfl.pivot(index="Date",columns='City',values=["Age","Name"]) 输出的结果如下: AgeName CityBeijingShanghaiBeijingShanghai Date 2020-12-127632JoeKim 5. transpose() 在Pandas中,df.transpose()方法与df.T属性的效果是完全一致的。以所选择的数据的最左上角为基点,进行行列转换位置,代码如下: dfl = pd.read_excel('demo_.xlsx').iloc[:2,:4] dfl dfl.transpose() 此做法相当于Excel中的“复制”→“粘贴”→ (粘贴选项)转置。输出的结果如图513所示。 图513数据的转置 相关原理说明,如图514所示。 图514转置的运行原理 5.3.3文本格式转换 1. %运算符 语法: (格式模板)%(值组)。 结果: 返回的值为字符串。 参数: 格式模板,一组或多组以%标志的字符串,如果有两个或两个以上的值,则需要用小括号括起来。值组,即要格式化的字符串或元组。 逐行运行代码: "%s" % "18.86", type("%s" % "18.86") #'%s', 字符串格式 "%d" % 18.86, type("%d" % 18.86) #'%d', 整数格式 "%f" % 18.86, type("%f" % 18.86) #'%d', 浮点格式(默认为小数点后6位) 格式模板中最常用的是s、d、f字符串。输出的结果如下: ('18.86', str) ('18', str) ('18.860000', str) 当字符串格式化处理的场景较为复杂时,可以采用字典格式(对字典中的键key的顺序没有要求,只需键值对应),代码如下: '%(Name)s,%(City)s,%(Age).2f' % {'City':'Beijing', 'Name':'Joe','Age':76} '%(Name)s 来自一线城市 %(City)s,今年%(Age)d岁 体能%(BMI).2f' % \ {"Name": "Joe","City": "Beijing","Age": 76,"BMI": 18.86} 输出的结果如下: 'Joe,Beijing,76.00' 'Joe 来自一线城市 Beijing,今年76岁 体能18.86' 字符的宽度设置,逐行运行代码如下: '%+6s' % 'Joe' '%-6s' % 'Joe' '%+6d' % 18.86 '%-6d' % 18.86 '%06d' % 18.86 '%(Age)+6s, %(BMI)06d' % {"Age":76,"BMI":18.86} 可供选择的参数: +、-、''(空格)、0。其中,+表示右对齐; -表示左对齐; ''表示在正数的左侧填充空格; 0表示填充0。+6表示右对齐,字符宽度为6(当宽度不足时,从左侧添加空格占位)。输出的结果如下: ' Joe' 'Joe ' ' +18' '18' '000018' '76, 000018' 字符的精度设置,逐行运行的代码如下: '%f' % 76 #浮点数的默认精度为6 '%.2f' % 76#小数点后保留2位 '%.4f' % 18.86#小数点后保留4位 '%.4s' % 'Beijing'#取字符串的前4个 输出的结果如下: '76.000000' '76.0000' '18.8600' 'Beij' 2. format()函数 语法: format(value,format_spec)。 参数: value表示要切换的数据; format_spec为格式控制标志,包括fill、align、sign、#和0、width、千位符、precision、type 这8个可选字段,这些字段是可以组合使用的。 逐行运行的代码如下: format(18.86) #等价于str(18.86) # format默认将其他数据类型转换为字符型 format(76,'d') format(18.86,'f') format(76,'>05') format(76,'0>5') format(76/18.86,'%') format(76/18.86,'.2%') 输出的结果如下: '18.86' '76' '18.860000' '00076' '00076' '402.969247%' '402.97%' 3. str.format()方法 语法: {参数序号:格式控制标志}.format(位置参数,关键字参数)。 参数序号: 位置参数或关键字参数传递过来的参数变量,可以为空值。 格式控制标志: 用来控制参数显示时的格式,和format()函数的format_spec参数是一样的。 逐行运行的代码如下: '{}现居住于{},年龄{},体重{}BMI{}'.format('Joe','Beijing',76,56,18.86) '{0},体重{3}BMI{4}现居住于{1},年龄{2}'.format('Joe','Beijing',76,56,18.86) '{0},体重{3}BMI{4:.4f},现居住于{1},年龄{2}'.format('Joe','Beijing',76,56,18.86) '"2012/12/12"登记结果:{0},体重{3}BMI{4}现居住于{1},年龄{2}'.format('Joe','Beijing',76,56,18.86) 输出的结果如下: 'Joe现居住于Beijing,年龄76,体重56BMI18.86' 'Joe,体重56BMI18.86现居住于Beijing,年龄76' 'Joe,体重56BMI18.8600,现居住于Beijing,年龄76' '"2012/12/12"登记结果:Joe,体重56BMI18.86现居住于Beijing,年龄76' 5.3.4style样式转换 语法: 类别 (type)为属性(property),使用时后面不用加括号()。 结果: 返回一个样式对象(Styler object)。 作用: 对特定数据的突出显示、数值的格式化、迷你条形图的使用等,提高数据的“颜值”。可以通过styler.applymap、styler.apply等将样式功能传递到数据中,也可以通过Styler.background_gradient实现数据的热力图功能等(例如: 对seaborn中light_palette的调用)。 注意: df.style输出的是一个Styler对象(不是DataFrame)。 对DataFrame的索引进行隐藏,代码如下: df = pd.read_excel(r"demo_.xlsx") df df.style.hide_index()#隐藏索引 索引列被隐藏,输出的结果如图515所示。 图515隐藏索引列 对DataFrame中指定的列进行隐藏,代码如下: df.style.hide_columns(['City','Date']) #隐藏列 指定的列被隐藏,输出的结果如图516所示。 对DataFrame中的null值用黄色填充,代码如下: dft = pd.read_excel(r"demo_.xlsx").select_dtypes('number') dft dft.style.highlight_null('yellow') 结果如图517所示。 图516隐藏列 图517高亮显示nan值 如果想对手头的数据负数标红,正数及0用黑色标记(由于手头的数据没有负数,所以先将一部分数据处理变成负数),则代码如下: dft = dft.fillna(0).applymap(lambda x: x if x > 50 else x - 60) def AA(val): color = 'red' if val < 0 else 'black' return 'color: %s' % color dft.style.applymap(AA) 输出的结果如图518所示。 图518标示所有负值 设置自定义函数,对DataFrame进行样式设置,代码如下: def BB(s): mx = s == s.max() return ['background-color: yellow' if v else '' for v in mx] dft.style.apply(BB) # 突出显示每列中的最大值 输出的结果如图519所示。 采用链式写法,将以上两个代码自定义函数放在一行语句中,代码如下: dft.style.applymap(AA).apply(BB) #链式写法 输出的结果如图520所示。 图519高亮显示每列的最大值 图520负值标识及高亮显示 对数据进行百分比格式设置,代码如下: dft = pd.read_excel(r"demo_.xlsx").select_dtypes('number') dft dft.style.format("{:.2%}") dft.style.format("{:.2%}", na_rep="-") 运行以上代码,输出的结果对比如图521所示。 图521文本格式设置 采用字典方式,对不同的列采用不同的样式显示,代码如下: dft = pd.read_excel(r"demo_.xlsx").select_dtypes('number') ( dft.style.format( {'Age': "{:0.0f}", 'WorkYears': "{:0.3f}", 'Weight': "${:0.2f}", 'BMI': '{:+.2f}'}) ) 输出的结果如图522所示。 图522对多列数据的不同样式设置 逐行运行以下代码,给指定列的数据添加数据条,代码如下: dft.style.bar(subset=['Age', 'BMI'], color='lightblue') dft.style.bar(subset=['Age', 'BMI'], color='lightblue').set_precision(2) .set_precision(2)语句用于将数据的精度设置为2。输出的结果对比如图523所示。 图523添加数据条 5.4本章回顾 计算机中有顺序结构、分支结构和循环结构3种基本的循环结构,用于处理重复的、有规律的操作。在Pandas中使用最多的是for循环语句。 在数据清洗的ETL(清洗、转换、加载)过程中,本章属于T(转换)环节。在数据的清洗与分析的过程中,经常会用到数据类型、数据结构或数据样式的转换。若转换的过程相对复杂,则可能会在转换语句中用到循环结构。 Pandas的强项在于数据的处理,但它也带有一些简单的数据表格美颜功能。在数据处理的过程中,可通过style属性进行一些较为常见的样式设置(例如: 空值颜色填充、负值颜色标红、数据条等)。