第5章Pandas基本用法 Pandas是一个高性能的数据操作和分析工具。它在NumPy的基础上,提供了一种高效的DataFrame数据结构,使得在Python中进行数据清洗和分析非常快捷。Pandas采用了很多NumPy的代码风格,但最大的不同在于Pandas主要用来处理表格型或异质型数据,而NumPy则相反,它更适合处理同质并且是数值类型的数据。事实上大多数时候,使用Pandas多于NumPy。 通常都使用Anaconda发行版安装Pandas,如果自行安装,可以使用如下命令: pip install pandas conda install pandas python3 -m pip install --upgrade pandas 安装完Pandas后,就可以导入它了,通常会使用下面的惯例进行导入: import pandas as pd 有时候也会将它包含的两个重要数据结构Series和DataFrame也单独导入: from pandas import Series, DataFrame 可以用如下命令查看当前Pandas的版本信息: pd.__version__ Pandas的核心是三大数据结构: Series、DataFrame和Index。绝大多数操作都是围绕这三种结构进行的。 5.1Series Series是一个一维的数组对象,它包含一个值序列和一个对应的索引序列。NumPy的一维数组通过隐式定义的整数索引获取元素值,而Series用一种显式定义的索引与元素关联。显式索引让Series对象拥有更强的能力,索引也不再仅仅是整数,还可以是别的类型,如字符串。索引也不需要连续,也可以重复,自由度非常高。 最基本的生成Series对象的方式是使用Series构造器: In[]: import pandas as pd In[]: s = pd.Series([7,-3,4,-2]) In[]: s Out[]: 07 1-3 24 3-2 dtype: int64 打印时,自动对齐了,看起来比较美观。左边是索引,右边是实际对应的值。默认的索引是0~N-1(N是数据的长度)。可以通过values和index属性分别获取Series对象的值和索引。 In[]: s.dtype Out[]: dtype('int64') In[]: s.values Out[]: array([ 7, -3,4, -2], dtype=int64) In[]: s.index Out[]: RangeIndex(start=0, stop=4, step=1) 可以在创建Series对象的时候指定索引。 In[]: s2 = pd.Series([7,-3,4,-2], index=['d','b','a','c']) In[]: s2 Out[]: d7 b-3 a4 c-2 dtype: int64 In[]: s2.index Out[]: Index(['d', 'b', 'a', 'c'], dtype='object') In[]: pd.Series(5, index=list('abcde')) Out[]: a5 b5 c5 d5 e5 dtype: int64 In[]: pd.Series({2:'a',1:'b',3:'c'}, index=[3,2]) # 通过index筛选结果 Out[]: 3c 2a dtype: object 也可以在后期,直接修改index。 In[]: s Out[]: 07 1-3 24 3-2 dtype: int64 In[]: s.index = ['a','b','c','d'] In[]: s Out[]: a7 b-3 c4 d-2 dtype: int64 类似Python的列表和NumPy的数组,Series也可以通过索引获取对应的值。 In[]: s2 = pd.Series([7,-3,4,-2], index=['d','b','a','c']) In[]: s2['a'] Out[]: 4 In[]: s2[['c','a','d']] Out[]: c-2 a4 d7 dtype: int64 也可以对Seires执行一些类似NumPy的通用函数操作。 In[]: s2[s2>0] Out[]: d7 a4 dtype: int64 In[]: s2*2 Out[]: d14 b-6 a8 c-4 dtype: int64 In[]: import numpy as np In[]: np.exp(s2) Out[]: d1096.633158 b0.049787 a54.598150 c0.135335 dtype: float64 因为索引可以是字符串,所以从某个角度看,Series又比较类似Python的有序字典,因此可以使用in操作。 In[]: 'b' in s2 Out[]: True In[]: 'e'in s2 Out[]: False 同样地,也会想到使用Python的字典来创建Series。 In[]: dic = {'beijing':35000,'shanghai':71000,'guangzhou':16000,'shenzhen':5000} In[]: s3=pd.Series(dic) In[]: s3 Out[]: beijing35000 shanghai71000 guangzhou16000 shenzhen5000 dtype: int64 In[]: s3.keys() # 同样地,具有类似字典的方法 Out[]: Index(['beijing', 'shanghai', 'guangzhou', 'shenzhen'], dtype='object') In[]: s3.items() Out[]: <zip at 0x1a5c2d88c88> In[]: list(s3.items()) Out[]: [('beijing', 35000), ('shanghai', 71000), ('guangzhou', 16000), ('shenzhen', 5000)] In[]: s3['changsha'] = 20300 In[]: city = ['nanjing', 'shanghai','guangzhou','beijing'] In[]: s4=pd.Series(dic, index=city) In[]: s4 Out[]: nanjingNaN shanghai71000.0 guangzhou16000.0 beijing35000.0 dtype: float64 city列表中,多了nanjing,但少了shenzhen。Pandas会依据city中的关键字去dic中查找对应的值,因为dic中没有nanjing,这个值缺失,所以用专门的标记值NaN表示。因为city中没有shenzhen,所以在s4中也不会存在shenzhen这个条目。可以看出,索引很关键,在这里起到了决定性的作用。 在Pandas中,可以使用isnull()和notnull()函数来检查缺失的数据。 In[]: pd.isnull(s4) Out[]: nanjingTrue shanghaiFalse guangzhouFalse beijingFalse dtype: bool In[]: pd.notnull(s4) Out[]: nanjingFalse shanghaiTrue guangzhouTrue beijingTrue dtype: bool In[]: s4.isnull() Out[]: nanjingTrue shanghaiFalse guangzhouFalse beijingFalse dtype: bool 可以为Series对象和其索引设置name属性,这有助于标记识别。 In[]: s4.name = 'people' In[]: s4.index.name= 'city' In[]: s4 Out[]: city nanjingNaN shanghai71000.0 guangzhou16000.0 beijing35000.0 Name: people, dtype: float64 In[]: s4.index Out[]: Index(['nanjing', 'shanghai', 'guangzhou', 'beijing'], dtype='object', name='city') 5.2DataFrame DataFrame是Pandas的核心数据结构,表示的是二维的矩阵数据表,类似关系数据库的结构,每一列可以是不同的值类型,如数值、字符串、布尔值等。DataFrame既有行索引,也有列索引,它可以被看作一个共享相同索引的Series的字典。 5.2.1创建DataFrame对象 In[]: dates = ['2021-01-01','2021-01-02','2021-01-03', '2021-01-04','2021-01-05','2021-01-06'] dates=pd.to_datetime(dates) dates Out[]: DatetimeIndex(['2021-01-01','2021-01-02','2021-01-03', '2021-01-04','2021-01-05','2021-01-06'], dtype='datetime64[ns]',freq=None) In[]: df=pd.DataFrame(np.random.randn(6,4),index=dates,columns=list('ABCD')) df 输出结果如图51所示。 图51创建DataFrame对象 5.2.2查看DataFrame对象 In[]: df.head(3) 前3行输出结果如图52所示。 In[]: df.tail(4) 后4行输出结果如图53所示。 In[]: df.columns 图52查看DataFrame对象前3行 图53查看DataFrame对象后4行 Out[]: Index(['A', 'B', 'C', 'D'], dtype='object') In[]: df.index Out[]: DatetimeIndex(['2021-01-01','2021-01-02','2021-01-03', '2021-01-04','2021-01-05','2021-01-06'], dtype='datetime64[ns]',freq=None) In[]: df.values Out[]: array([[-0.24879283, -0.09034927, 0.18204419, 3.9711095 ], [-0.58373675, -1.64861704, 0.87321137, -0.84888624], [-0.06156446, -0.8777472 , 0.34766089, -0.82914362], [-0.07090626, -0.07836195, -0.36985636, -0.81198838], [ 0.24595941, 2.13098157, 0.32730456, 0.61535036], [-0.75787777, -0.16273068, -0.55719831, -0.58041744]]) In[]: df.describe()# 查看数值数据的详细信息 输出结果如图54所示。 图54数值数据的详细信息1 5.2.3DataFrame对象的索引与切片 1. DataFrame 行与列的单独操作 df[1:3] # 行操作 df['A'] # 列操作 df[['A','C']] # 多列操作 df[df['A']>0] # bool值操作 2. 标签索引与切片 loc利用index的名称获取想要的行(或列)。 df.loc[:,'A'] # 提取A列数据 df.loc[:,'A':'C'] # 提取A~C列数据 df.loc[dates[0:2],'A':'C'] # 提取0、1行的A~C列数据 df.loc[dates[0],'A'] # 提取0行的A列数据 df.at[dates[0],'A'] # 提取0行的A列数据 df.loc[df.loc[:,'A']>0] # 提取A列大于0的行 3. 位置索引与切片 iloc利用index的具体位置(所以它只能是整数型参数)获取想要的行(或列)。 df.iloc[2] # 提取行为2(第3行)的数据 df.iloc[:,2] # 提取列为2(第3列)的数据 df.iloc[[1,4],[2,3]] # 提取行为1、4,列为2、3的数据 df.iloc[1:4,2:4] # 提取行为1~3,列为2、3的数据 df.iloc[3,3] # 提取行为3,列为3的数据 df.iat[3,3] # 提取行为3,列为3的数据 df.loc[:,df.iloc[3]>0] # 提取所有行,列为3的大于0的数据 4. DataFrame的操作 1) 转置 df.T 2) 排序与排名 df.sort_index(axis=0, ascending=False) # 按行标签排序 df.sort_index(axis=1, ascending=False) # 按列标签排序 df.sort_values(by='C') # 按某列排序 df.sort_values(by='2016-01-05',axis=1) # 按某行排序 3) 增加列 s1=pd.Series([1,2,3,4,5,6],index=pd.date_range('20160101',periods=6)) df['E'] = s1 4) 增加行 方式1: df1=pd.DataFrame({'A':[1,2,3],'B':[4,5,6],'C':[7,8,9]},\ index=pd.date_range('20160110',periods=3)) df.append(df1) 方式2: data=np.random.randn(1,4) date=pd.to_datetime('20160107') df.loc[date,]=data 方式3: pd.concat([df,df1],join='inner') 5) 删除操作 df.drop(dates[1:3]) # 删除1、2行的数据,df并没有删除 df.drop('A',axis=1) # 删除A列的数据,注意删除列时,需要指定axis=1 del df['A'] # 删除A列的数据,df并没有删除 df.drop(dates[1:3],inplace=True) # 删除1、2行的数据,df相应行删除 6) 替换操作 df.loc[dates[2],'C'] = 0 df.iloc[0,2] = 0 df.loc[:,'B'] = np.arange(0,len(df)) # 替换B列 df.loc[date,]=data # 替换date所在行的数据 5. DataFrame的运算 1) 简单运算 Series与Series运算: 匹配规则为index与 index,如不能匹配,就用NaN。 In[]: s1=pd.Series([1,2,3],index=list('ABC')) s2=pd.Series([4,5,6],index=list('BCD')) s1+s2 Out[]: ANaN B6.0 C8.0 DNaN dtype: float64 Series与DataFrame运算: 匹配规则为index与 column,如不能匹配,就用NaN,每行元素都进行列操作。 In[]: df1=pd.DataFrame(np.arange(1,13).reshape(3,4), index=list('abc'),columns=list('ABCD')) df1-s1 Out[]: ABCD a0.00.00.0NaN b4.04.04.0NaN c8.08.08.0NaN DataFrame与DataFrame运算: 匹配规则为index与column同时匹配,如不能匹配,就用NaN,每行元素都进行列操作。 In[]: df2=pd.DataFrame(np.arange(1,13).reshape(4,3), index=list('bcde'),columns=list('CDE')) df1*df2 Out[]: ABCDE aNaNNaNNaNNaNNaN bNaNNaN7.016.0NaN cNaNNaN44.060.0NaN dNaNNaNNaNNaNNaN eNaNNaNNaNNaNNaN 2) 函数的应用和映射 函数基本形式为: DataFrame.apply(func,axis=0) In[]: df0 = pd.DataFrame(np.random.rand(6,4), index=pd.date_range('20160101',periods=6), columns=list('ABCD')) df0.apply(max,axis=0) Out[]: A0.945795 B0.695932 C0.923623 D0.594854 dtype: float64 也可以使用自定义函数: f=lambda x: x.max()-x.min() df0.apply(f,axis=1) 6. 数据规整化 1) 使用pd.concat()函数实现简易合并 In[]: ser1 = pd.Series(['A', 'B', 'C'], index=[1, 2, 3]) ser2 = pd.Series(['D', 'E', 'F'], index=[4, 5, 6]) pd.concat([ser1, ser2]) Out[]: 1A 2B 3C 4D 5E 6F dtype: object 2) 合并与连接 (1) 一对一连接。 In[]: df1 = pd.DataFrame({ 'employee' : ['Bob', 'Jake', 'Lisa', 'Sue'], 'group' : ['Accounting', 'Engineering', 'Engineering', 'HR'] }) df2 = pd.DataFrame({ 'employee' : ['Lisa', 'Bob', 'Jake', 'Sue'], 'hire_data' : [2004, 2008, 2012, 2014] }) print(df1); print(df2) Out[]: employeegroup 0BobAccounting 1JakeEngineering 2LisaEngineering 3SueHR employeehire_data 0Lisa2004 1Bob2008 2Jake2012 3Sue2014 In[]: df3 = pd.merge(df1, df2) df3 Out[]: employeegrouphire_data 0BobAccounting2008 1JakeEngineering2012 2LisaEngineering2004 3SueHR2014 (2) 多对一连接。 In[]: df4 = pd.DataFrame({ 'group' : ['Accounting', 'Engineering', 'HR'], 'supervisor' : ['Carly', 'Guido', 'Steve'] }) print(pd.merge(df3, df4)) Out[]: employeegrouphire_data supervisor 0BobAccounting2008Carly 1JakeEngineering2012Guido 2LisaEngineering2004Guido 3SueHR2014Steve (3) 多对多连接。 In[]: df5 = pd.DataFrame({ 'group' : ['Accounting', 'Accounting', 'Engineering', 'Engineering', 'HR', 'HR'], 'skills' : ['math', 'spreadsheets', 'coding', 'linux', 'spreadsheets', 'organization'] }) print(pd.merge(df1, df5)) Out[]: employeegroupskills 0BobAccountingmath 1BobAccountingspreadsheets 2JakeEngineeringcoding 3JakeEngineeringlinux 4LisaEngineeringcoding 5LisaEngineeringlinux 6SueHRspreadsheets 7SueHRorganization (4) 设置数据合并的键。 In[]: print(pd.merge(df1, df2, on='employee')) Out[]: employeegrouphire_data 0BobAccounting2008 1JakeEngineering2012 2LisaEngineering2004 3SueHR2014 In[]: df3 = pd.DataFrame({ 'name': ['Bob', 'Jake', 'Lisa', 'Sue'], 'salary': [70000, 80000, 120000, 90000] }) print(df1) print(df3) print(pd.merge(df1, df3, left_on='employee', right_on='name')) Out[]: employeegroup 0BobAccounting 1JakeEngineering 2LisaEngineering 3SueHR namesalary 0Bob70000 1Jake80000 2Lisa120000 3Sue90000 employeegroupnamesalary 0BobAccountingBob70000 1JakeEngineeringJake80000 2LisaEngineeringLisa120000 3SueHRSue90000 3) 透视表 pivot_table(data,values=None,index=None,columns=None,aggfunc='mean',fill_value=None,margins=False,dropna=True,argins_name='All') index: 透视表的行索引,必要参数,如果想要设置多层次索引,使用列表[ ]。 values: 对目标数据进行筛选,默认是全部数据,可以通过values参数设置想要展示的数据列。 columns: 透视表的列索引,非必要参数,同index使用方式一样。 aggfunc: 对数据聚合时进行的函数操作,默认是求平均值,也可以用sum、count等。 margins: 为True时,会添加行/列的总计,默认对行/列求和。 fill_value: 对空值进行填充。 dropna: 默认开启去重。 margins_name: margins=True 时,设定margins行/列的名称。 In[]: df = pd.DataFrame({"A": ["foo", "foo", "foo", "foo", "foo", "bar", "bar", "bar", "bar"], "B": ["one", "one", "one", "two", "two", "one", "one", "two", "two"], "C": ["small", "large", "large", "small", "small", "large", "small", "small", "large"], "D": [1, 2, 2, 3, 3, 4, 5, 6, 7]}) 图55创建DataFrame对象 输出结果如图55所示。 In[]: table1 = pd.pivot_table(df, values='D', index=['A', 'B'],columns=['C'], aggfunc=np.sum) table1 Out[]: Clargesmall AB barone4.05.0 two7.06.0 fooone4.01.0 twoNaN6.0 In[]: table2 = pd.pivot_table(df, values='D', index=['A', 'B'], aggfunc=np.sum) table2 Out[]: D AB barone9 two13 fooone5 two6 In[]: table3 = pd.pivot_table(df, values='D',columns=['C'], aggfunc=np.sum) table3 Out[]: Clargesmall D1518 In[]: table4 = pd.pivot_table(df, values='D', index=['A', 'B'],columns=['C'], aggfunc=np.sum,margins=True,margins_name='total') table4 Out[]: Clargesmalltotal AB barone4.05.09 two7.06.013 fooone4.01.05 twoNaN6.06 total15.018.033 7. 保存结果 data.to_csv('cleanfile.csv',encoding='utf-8') 5.3应用举例 下面按照数据挖掘中数据处理的步骤,通过一个实际案例——泰坦尼克数据集分析,讲解DataFrame的用法。 数据集各字段意义如下。 PassengerId: 乘客编号。 Survived: 存活情况(1表示存活,0表示死亡)。 Pclass: 客舱等级。 Name: 乘客姓名。 Sex: 性别。 Age: 年龄。 SibSp: 同乘的兄弟姐妹/配偶数。 Parch: 同乘的父母/小孩数。 Ticket: 船票编号。 Fare: 船票价格。 Cabin: 客舱号。 Embarked: 登船港口(出发地点为S表示英国南安普敦; 途经地点为C表示法国瑟堡市,Q表示爱尔兰昆士敦)。 5.3.1数据读取 读取CSV文件格式如下。 pd.read_csv(filepath,encoding,sep,header,names,usecols,index_col,skiprows,nrows…) 参数说明如下: filepath: 文件存储路径,可以用r""进行非转义限定,路径最好是纯英文(文件名也是),不然会经常遇到编码错误的问题,最方便的做法是直接将文件存储在Pandas默认的路径下,直接输入文件名即可。 encoding: Pandas默认编码是UTF8,如果同样读取默认UFT8的TXT或者JSON格式文件,则可以忽略这个参数,如果是CSV文件,且数据中有中文时,则要指定encoding='gbk'。 sep: 指定分隔符形式,CSV文件默认用逗号分隔,可以忽略这个参数,如果是其他分隔方式,则要填写。 header: 指定第一行是否是列名。通常有三种用法: 忽略或header=0(表示数据第一行为列名)以及header=None(表明数据没有列名),常与names搭配使用。 names: 指定列名,通常用一个字符串列表表示,当header=0时,用names可以替换掉数据中的第一行作为列名; 如果header=None,用names可以增加一行作为列名; 如果没有header参数,则用names会增加一行作为列名,原数据的第一行仍然保留。 usecols: 一个字符串列表,可以指定读取的列名。 index_col: 一个字符串列表,指定哪几列作为索引。 skiprows: 跳过多少行再读取数据,通常数据不太干净,需要去除掉表头才会用到。 nrows: 仅读取多少行,后面的处理也都仅限于读取的这些行。 In[]: import pandas as pd import numpy as np df = pd.read_csv("Titanic.csv") df.shape Out[]: (891, 12) In[]: df.head()# 显示前5行 输出结果如图56所示。 图56显示前5行数据 In[]: df.dtypes# 查看数据类型 Out[]: PassengerIdint64 Survivedint64 Pclassint64 Nameobject Sexobject Agefloat64 SibSpint64 Parchint64 Ticketobject Farefloat64 Cabinobject Embarkedobject dtype: object Pandas能够探测到数值类型,因此已经有一些数据存为整数值。当它检测到双精度值时,会自动将其转换为float类型。这里有两个更加实用的命令。 In[]: df.info()# 查看数据信息 Out[]: <class 'pandas.core.frame.DataFrame'> RangeIndex: 891 entries, 0 to 890 Data columns (total 12 columns): PassengerId 891 non-null int64 Survived 891 non-null int64 Pclass 891 non-null int64 Name 891 non-null object Sex 891 non-null object Age 714 non-null float64 SibSp 891 non-null int64 Parch 891 non-null int64 Ticket 891 non-null object Fare 891 non-null float64 Cabin 204 non-null object Embarked 889 non-null object dtypes: float64(2), int64(5), object(5) memory usage: 83.6+ KB 可以直观地知道这里有891项(行),大部分变量都是完整的(891为nonnull)。但实际上Age、Cabin、Embarked列在某处都有空值。 In[]: df.describe()# 查看数值数据的详细信息 输出结果如图57所示。 图57数值数据的详细信息2 Pandas列出了所有数值列,而且快速地算出了均值、标准差、最小值和最大值。非常方便。但要注意的是,在Age中有很多缺失值,Pandas是怎么处理它们的呢?它会遗留下很多空值。 从数据中可以看出,年龄的最大值是80。如果想观察数据中的特定子集,如Pclass和Age列,Pandas使用 a[list]的形式即可。 In[]: df[['Sex','Pclass','Age']] Out[]: SexPclass Age 0 male3 22.0 1 female1 38.0 2 female3 26.0 3 female1 35.0 4 male 3 35.0 5 male 3 NaN ... ... [891 rows x 3 columns] 同样可以通过设置条件来过滤数据。 In[]: df[df['Age']>60].loc[0:100,]# 前100行中年龄大于60岁的记录 输出结果如图58所示。 图58前100行中年龄大于60岁的记录 5.3.2数据清洗 数据清洗主要分为如下三步。 (1) 重复值处理——删除。 (2) 缺失值处理——删除,填充(均值、众数、中位数、前后相邻值)和插值(拉格朗日插值、牛顿插值)。 (3) 异常值处理——进行描述性分析+散点图+箱型图定位异常值,处理方法为删除,将其视为缺失值。 1. 重复值处理 In[]: df.duplicated().value_counts() Out[]: False891 dtype: int64 可以看出,数据集没有重复值。如果DataFrame中存在重复的行或者几行中某几列的值重复,这时候需要去掉重复行。示例如下: data.drop_duplicates(subset=['A','B'],keep='first',inplace=True) 代码中subset对应的值是列名,表示只考虑A和B两列,将这两列对应值相同的行进行去重。默认值为subset=None,表示考虑所有列。keep='first'表示保留第一次出现的重复行,是默认值。keep另外两个取值为'last'和False,分别表示保留最后一次出现的重复行和去除所有重复行。 inplace=True表示直接在原来的DataFrame上删除重复项,而默认值False表示生成一个副本。 2. 缺失值处理 缺失值查找: 先通过isnull()函数看一下是否有空值,结果是有空值的地方显示为True,没有空值的地方显示为False; 再通过isnull().any()直接看每一列是否有空值,只要这一列有一个空值,结果就为True; 如果想具体看哪几行有空值,可以再用df.isnull().values==True来定位。 In[]: df.isnull().any() Out[]: PassengerIdFalse SurvivedFalse PclassFalse NameFalse SexFalse AgeTrue SibSpFalse ParchFalse TicketFalse FareFalse CabinTrue EmbarkedTrue dtype: bool 可以看出,Age、Cabin、Embarked列都有空值。 也可以用另外一种方法查看缺失值情况。 In[]: import missingno missingno.matrix(df,figsize=(30,5)) 输出结果如图59所示。 图59通过missingno查看缺失值情况 下面对缺失值进行处理。 (1) 删除缺失值。 dropna(axis,subset,how,thresh,inplace) 参数说明如下。 axis: 删除行或列,行为0或index,列为1或column,默认为行。 subset: 删除某几列的缺失值,可选,默认为所有列。 how: any或all。any表明只要出现一个缺失值就删除,all表示所有列均为缺失值才删除。 thresh: 缺失值的数量标准,达到这个阈值才会删除。 inplace: 是否直接在原文件中修改。 In[]: df.dropna(how='any', axis=0,inplace=True) df.isnull().any() Out[]: PassengerIdFalse SurvivedFalse PclassFalse NameFalse SexFalse AgeFalse SibSpFalse ParchFalse TicketFalse FareFalse CabinFalse EmbarkedFalse dtype: bool (2) 缺失值填充。 fillna(value,method,{},limit,inplace,axis) 参数说明如下。 value: 可以传入一个字符串或数字替代NaN,值可以是指定的或者平均值、众数或中位数等。 method: 有ffill(用前一个填充)和bfill(用后一个填充)两种。 {}: 可以根据不同的列填充不同的值,列为键,填充值为值。 limit: 限定填充的数量。 inplace: 是否直接在原文件中修改。 axis: 填充的方向,默认为0,按行填充。 接下来对Sex分组,用各组乘客的平均年龄填充各组中的缺失年龄,使用Embarked变量的众数填充缺失值。 In[]: fillna_Titanic = [] for i in df.Sex.unique(): update = df.loc[df.Sex == i,] update.fillna(value = {'Age': df.Age[df.Sex == i].mean()}, inplace = True) fillna_Titanic.append(update) df = pd.concat(fillna_Titanic) df.fillna(value = {'Embarked':df.Embarked.mode()[0]}, inplace=True) df.isnull().sum() # 查看各列是否有空值 Out[]: PassengerId0 Survived0 Pclass0 Name0 Sex0 Age0 SibSp0 Parch0 Ticket0 Fare0 Cabin687 Embarked0 dtype: int64 Cabin客舱号有缺失,PassengerId、Ticket、Cabin与是否获救无关,可以删除。 In[]: data.drop(['PassengerId', 'Ticket','Cabin'],axis=1,inplace=True) 5.3.3数据规整 数据规整是在数据清洗完毕后,将其调整为适合分析的结构,为后续的深入分析做准备,主要分为以下几类。 索引和列名调整: 设定新索引,筛选想要的列,更改列名。 数据排序: 根据索引或列进行排序。 数据格式调整: 更改数据类型,更改数据内容(去除空格标点符号/截取/替换/统一数据单位等),增加用于分析的辅助列。 数据拼接: 行堆叠和列拼接。 数据透视: 行或列维度转换。 1. 将性别转换为数值,便于后续进行分析 In[]: df.loc[df['Sex']=='male','Sex']=0 df.loc[df['Sex']=='female','Sex']=1 或者: df['Sex'] = df['Sex'].map( {'female': 1, 'male': 0} ).astype(int) 2. 将登录港口转换为数值,便于后续分析 In[]: print(df.Embarked.unique()) df.loc[df['Embarked']=='S','Embarked'] = 0 df.loc[df['Embarked']=='C','Embarked'] = 1 df.loc[df['Embarked']=='Q','Embarked'] = 2 Out[]: [0,2,1] In[]: df.describe() 输出结果如图510所示。 图510数值数据的详细信息3 3. 建立透视表 (1) 查看不同性别的存活率。 In[]: table = pd.pivot_table(df, index=["Sex"], values="Survived") print(table) Out[]: Survived Sex 00.188908 10.742038 可见女性的存活率很高。 参数说明如下。 df: 要传入的数据。 index: values to group by in the rows,也就是透视表建立时要根据哪些字段进行分组,这里只依据性别分组。 values: 对哪些字段进行聚合操作,因为这里只关心不同性别下的存活率情况,所以values只需要传入一个值"survived"。 将所有乘客按性别分为男、女两组后,对"survived"字段开始进行聚合,默认的聚合函数是"mean",也就是求每个性别组下所有成员的"survived"的均值,即可分别求出男、女两组各自的平均存活率。 (2) 添加列索引: pclass为客舱级别,共有1、2、3三个级别,1级别最高。 In[]: table = pd.pivot_table(df, index=["Sex"], values="Survived") print(table) Out[]: Pclass123 Sex 00.3688520.1574070.135447 10.9680850.9210530.500000 可以发现,无论是男性还是女性,客舱级别越高,存活率越高; 在各个舱位中,女性的生还概率都远大于男性; 一、二等舱的女性生还率接近,且远大于三等舱; 一等舱的男性生还率大于二、三等舱,二、三等舱的男性生还率接近。 (3) 多级行索引: 添加一个行级分组索引。 In[]: table = pd.pivot_table(df, index=["Sex","Pclass"], values="Survived") print(table) Out[]: Survived Sex Pclass 010.368852 20.157407 30.135447 110.968085 20.921053 30.500000 添加一个行级索引"Pclass"后,现在透视表具有二层行级索引和一层列级索引。仔细观察透视表发现,与上面的“添加一个列级索引”在分组聚合效果上是一样的,都是将每个性别组中的成员再次按照客票级别划分为3个小组。 (4) 将年龄以18岁为界,生成一个和年龄有关的透视表。 In[]: def generate_age_label(row): age = row["Age"] if age < 18: return "minor" else: return "adult" age_labels = df.apply(generate_age_label, axis=1) df['age_labels'] = age_labels table= df.pivot_table(index="age_labels", values="Survived") print(table) Out[]: Survived age_labels adult0.361183 minor0.539823 可以发现,未成年组存活率较高。 (5) 性别(Sex)、客舱级别(Pclass)、登船港口(Embarked)与生还率关系。 In[]: df.pivot_table(values="Survived",index="Sex",columns=["Pclass","Embarked"], aggfunc=np.mean) 输出结果如图511所示。 图511统计结果 可以看出,Sex=0且Embarked=1时,即男性在途经地点1: C=法国瑟堡市 (Cherbourg)登船时,在各个客舱级别中的存活率相对较高。 习题 探索鸢尾花数据。 (1) 将数据集保存为变量iris。 (2) 创建数据框的列名称['sepal_length','sepal_width', 'petal_length', 'petal_width', 'class']。 (3) 数据框中有缺失值吗? (4) 将petal_length列的第10~19行设置为缺失值。 (5) 将petal_length列的缺失值全部替换为1.0。 (6) 删除class列。 (7) 将数据框前3行设置为缺失值。 (8) 删除有缺失值的行。 (9) 重新设置索引。