缺失值
缺失值类型
在pandas中,缺失数据显示为NaN。缺失值有3种表示方法,np.nan , None , pd.NA
1、np.nan
缺失值有个特点,它不等于任何值,连自己都不相等。如果用nan和任何其它值比较都会返回nan
print(np.nan == np.nan) print(type(np.nan)) print(pd.Series([1,2,3]).dtype) # 数据中全为int 那么就是int类型 print(pd.Series([1,np.nan,3]).dtype) # 数据中有一个nan,那么类型变为float
初学者做数据处理遇见object类型会发懵,不知道这是个啥,明明是字符型,导入后就变了,其实是因为缺失值导致的。
除此之外,还要介绍一种针对时间序列的缺失值,它是单独存在的,用NaT表示,是pandas的内置类型,可以视为时间序列版的np.nan,也是与自己不相等
t = pd.Series([pd.Timestamp('20220101')]*3) print(t) t[2]=np.nan print(t)
2、None
还有一种就是None,它要比nan好那么一点,因为它至少自己与自己相等
print(None == None) print(pd.Series([1,None]))
3、NA标量
pandas1.0以后的版本中引入了一个专门表示缺失值的标量pd.NA,它代表空整数、空布尔值、空字符
对于不同数据类型采取不同的缺失值表示会很乱。pd.NA就是为了统一而存在的。pd.NA的目标是提供一个缺失值指示器,可以在各种数据类型中一致使用(而不是np.nan、None或者NaT分情况使用)。
s = pd.Series([1,2],dtype='Int64') print(s) s[1]=pd.NA print(s)
### pd.NA的一些常用算术运算和比较运算的示例: # 加法 print('pd.NA+1:\t',pd.NA+1) # 乘法 print('a*pd.NA:\t','a'*pd.NA) # 以下两种其中结果为1 print('pd.NA ** 0 :\t',pd.NA**0) print('1 ** pd.NA:\t',1**pd.NA) ### 比较运算 print('pd.NA == pd.NA:\t',pd.NA==pd.NA) print('pd.NA < 2.5:\t',pd.NA < 2.5) print('np.add(pd.NA,1):\t',np.add(pd.NA,1))
缺失值处理
上面讲解完了缺失值的类型,现在讲解一下如何处理缺失值
对于缺失值一般有2种处理方式,要么删除,要么填充(用某个值代替缺失值)。缺失值一般分2种,
- 一种是某一列的数据缺失。
- 另一种是整行数据都缺失,即一个空行
首先先来获取缺失值
df = pd.read_excel('16-data/data_test.xlsx') print(df) print('---------------------') print(df.info()) # 查看缺失值的情况 print(df.isnull()) # 缺失值的判断
之后就是对这些缺失值要进行那些处理了,可以进行删除,也可以进行填充
删除缺失值
df. dropna(axis=0, how='any', thresh=None,subset=None,inplace=False)
- axis:{0或’index’,1或columns"}默认为0确定是否删除了包含缺少值的行或列
0或“索引”:删除包含缺少值的行。
1或“列”:删除包含缺少值的列。 - how:{any’,'all},默认为’any’确定是否从DataFrame中删除行或列,至少一个NA或所有NA.
“any”:如果存在任何NA值,请删除该行或列。
“all”:如果所有值都是NA,则删除该行或列。 - thresh: int需要至少非NA值数据个数。
- subset:定义在哪些列中查找缺少的值
- inplace:是否更改源数据
df = pd.DataFrame({'name':['张','三','李','四'], 'toy':[np.nan,'dog','cat','mouse'], 'born':[pd.NaT,pd.Timestamp('1940-04-25'),pd.NaT,pd.Timestamp('2022-01-02')]}) print(df) # 删除至少缺少一个元素的行 print(df.dropna()) # 删除至少缺少一个元素的列 print(df.dropna(axis='columns')) # 删除缺少所有元素的行 print(df.dropna(how='all')) # 仅保留至少有2个非NA值的行 print(df.dropna(thresh=2)) # 定义在那些列中查找缺少的值 print(df.dropna(subset=['toy'])) # 在同一个变量中保留操作数据 print(df.dropna(inplace=True)) print(df)
如果有缺失值,还可以进行缺失值补充操作
用平均值填充,
用众数填充(大多数时候用这个),众数是指一组数据中出现次数最多的那个数据,一组数据可以有多个众数,也可以没有众数
向前填充(用缺失值的上一行对应字段的值填充,比如D3单元格缺失,那么就用D2单元格的值填充)、
向后填充(与向前填充对应)等方式。
df.fillna(value=None,method=None,axis=None,inplace=False,limit=None,downcast=None)
- value:用于填充的值(例如0),或者是一个dict/Series/DataFrame值,指定每个索引(对于一个系列)或列(对于一个数据帧)使用哪个值。不在dictSeries/DataFrame中的值将不会被填充。此值不能是列表。
- method :ffiIl->将上一个有效观察值向前传播 bfill–>将下一个有效观察值向后传播
- axis :用于填充缺失值的轴。
- inplace :是否操作源数据
df = pd. DataFrame([ [np.nan,2,np.nan,0], [3,4,np.nan,1], [np.nan,np.nan,np.nan,np.nan], [np.nan,3,np.nan,4] ], columns=['A','B','C','D'] ) print(df) #将所有NaN元素替换为0 print(df.fillna(0)) # 我们还可以向前或者向后传播非空值 print(df.fillna(method='ffill')) print(df.fillna(method='bfill')) # 将列'A','B','C','D'中的所有NaN元素分别替换为0,1,2,3 values = {'A':0,'B':1,'C':2,'D':3} df.fillna(value=values) # 只替换第一个NaN元素 df.fillna(0,limit=1) # 当使用数据填充时,替换会沿着相同的列名和索引进行 df2 = pd.DataFrame(np.random.rand(4,4),columns=['A','B','C','E']) print(df2) print('--------------') print(df) df.fillna(value=df2)
分组操作
在数据分析中,经常会遇到这样的情况:
根据某一列(或多列)标签把数据划分为不同的组别,然后再对其进行数据分析。
比如,某网站对注册用户的性别或者年龄等进行分组,从而研究出网站用户的画像(特点)。在 Pandas中,要完成数据的分组操作,需要使用groupby()函数,它和 SQL的GROUP BY操作非常相似。
在划分出来的组(group)上应用一些统计函数,从而达到数据分析的目的,比如对分组数据进行聚合、转换,或者过滤。这个过程主要包含以下三步:
- 拆分(Spliting):表示对数据进行分组;
- 应用(Applying):对分组数据应用聚合函数,进行相应计算;
- 合并(Combining):最后汇总计算结果。
在pandas中,实现分组操作的代码很简单,仅需一行代码,在这里,将上面的数据集按照name字段进行划分:
list1 = ['A','B','C'] [list1[x] for x in np.random.randint(0,len(list1),10)] data = pd.DataFrame({ 'name':[list1[x] for x in np.random.randint(0,len(list1),10)], 'age':np.random.randint(5,50,10), 'money':np.random.randint(15,50,10) }) print(data) print('---------') group = data.groupby('name') print(group)
上述代码会返回一个DataFrameGroupBy对象
那这个生成的DataFrameGroupBy是啥呢?
对data进行了groupby后发生了什么?
python所返回的结果是其内存地址,并不利于直观地理解,为了看看group内部究竟是什么,这里把group转换成list的形式来看一看:
print(list(group)) group.groups
转换成列表的形式后,可以看到,列表由三个元组组成,每个元组中,
第一个元素是组别(这里是按照name进行分组,所以最后分为了A,B,C),
第二个元素的是对应组别下的DataFrame,整个过程可以图解如下:
聚合操作
聚合(Aggregation)操作是groupby后非常常见的操作,会写SQL的朋友对此应该是非常熟悉了。聚合操作可以用来求和、均值、最大值、最小值等
例如
min----求最小
max----求最大
count----计数
mean----求平均
median----求中位数
std----求标准差
var----求方差
print(data.groupby('name').agg('mean')) # 显示声明聚合调用mean方法 print(data.groupby('name').mean()) # 直接调用聚合的mean方法
如果想对针对不同的列求不同的值,比如要计算不同公司员工的平均年龄以及薪水的中位数,可以利用字典进行聚合操作的指定:
data.groupby('name').agg({'age':'max','money':'min'})
转换
transform转换值
transform是一种什么数据操作?
和agg有什么区别呢?
为了更好地理解transform和agg的不同,下面从实际的应用场景出发进行对比。在上面的agg中,我们学会了如何求不同name员工的平均money和age,
如果现在需要在原数据集中新增一列avg_money,代表员工所在的公司的平均薪水(相同公司的员工具有一样的平均薪水),该怎么实现呢?
如果按照正常的步骤来计算,需要先求得不同公司的平均薪水,然后按照员工和公司的对应关系填充到对应的位置,不用transform的话
print(data.groupby('name').mean()['money']) avg_money_dict = data.groupby('name')['money'].mean().to_dict() print(avg_money_dict) avg_money = data[['name','money']].groupby('name')['money'].mean().to_dict() print(avg_money) # map()函数可以用于Series对象或DataFrame对象的一列 # 接收函数或字典对象作为参数,返回结果函数或字典映射处理后的值 data['avg_money']=data['name'].map(avg_money) print(data) print('---------------------------') # 如果使用transform的话,只需要一行代码 data['avg_money']=data.groupby('name')['money'].transform('mean') print(data)
还是以图解的方式来看看进行groupby后transform的实现过程(为了更直观展示,
图中加入了name列,实际按照上面的代码只有money列):
图中的大方框是transform和agg所不一样的地方,对agg而言,会计算得到A,B,C公司对应的均值并直接返回,但对transform而言,则会对每一条数据求得相应的结果,同一组内的样本会有相同的值,组内求完均值后会按照原索引的顺序返回结果,如果有不理解的可以拿这张图和agg那张对比一下。
Apply
它相比agg和transform而言更加灵活,能够传入任意自定义的函数,实现复杂的数据操作对于groupby后的apply,以分组后的子DataFrame作为参数传入指定函数的,基本操作单位是DataFrame
假设我现在需要获取各个公司年龄最大的员工的数据,该怎么实现呢?可以用以下代码实现:
def get_oldest_staff(x): # 输入的数据按照age字段进行排序 df = x.sort_values(by='age',ascending=True) # 返回最后一条数据 return df.iloc[-1] oldest_staff = data.groupby('name',as_index=False).apply(get_oldest_staff) print(oldest_staff)
虽然说apply拥有更大的灵活性,但apply的运行效率会比agg和transform更慢。所以,groupby之后能用agg和transform解决的问题还是优先使用这两个方法,实在解决不了了才考虑使用apply进行操作
合并
为了方便维护,一般公司的数据在数据库内都是分表存储的,比如用一个表存储所有用户的基本信息,一个表存储用户的消费情况。所以,在日常的数据处理中,经常需要将两张表拼接起来使用,这样的操作对应到SQL中是join,在Pandas中则是用merge来实现。这就讲一下merge的主要原理。
上面的引入部分说到merge是用来拼接两张表的,那么拼接时自然就需要将用户信息一 一对应地进行拼接,所以进行拼接的两张表需要有一个共同的识别用户的键(key)。总结来说,整个merge的过程就是将信息一 一对应匹配的过程,下面介绍merge的四种类型,分别为inner,left , right和outer 。
pd.merge(left,right,how: str = 'inner',on=None,left_on=None,right_on=None,left_index: bool =
False,right_index: bool = False,sort: bool =False,suffixes=('_x', 'Y'),copy: bool = True,
indicator: bool = False,validate=None,)
先来看一下数据对应的情况,分为一一对应和一对多两种情况
# 一条数据对应一条数据 df1 = pd.DataFrame({ 'userid':['a','b','c','d'], 'age':[12,13,14,15] }) df2 = pd.DataFrame({ 'userid':['a','c'], 'money':[123,543] }) print(pd.merge(df1,df2,on='userid')) # 第一种合并方法 print('------------------------') print(df1.merge(df2,on='userid')) # 第二种方法合并
# 一条数据对应多条数据 df1 = pd.DataFrame({ 'userid':['a','b','c','d'], 'age':[12,13,14,15] }) df2 = pd.DataFrame({ 'userid':['a','c','a','b'], 'money':[123,543,546,123] }) print(df1.merge(df2,on='userid')) print('------------') print(pd.merge(df1,df2,on='userid'))
'left和’right的merge方式其实是类似的,分别被称为左连接和右连接。这两种方法是可以互相转换的,所以在这里放在一起介绍。
‘left’
merge时,以左边表格的键为基准进行配对,如果左边表格中的键在右边不存在,则用缺失值NaN填充。
‘right’
merge时,以右边表格的键为基准进行配对,如果右边表格中的键在左边不存在,则用缺失值NaN填充。
简而言之也就是:左连接,则左表的所有数据都必须显示,右表只是填充对应的数据而已,
右连接则与左连接相反.
什么意思呢?用一个例子来具体解释一下,这是演示的数据
# 左连接 df1 = pd.DataFrame({ 'userid':['a','b','c','d'], 'age':[12,13,14,15] }) df2 = pd.DataFrame({ 'userid':['a','c','e'], 'money':[123,543,546] }) print(df1.merge(df2,on='userid',how='left')) print('------------') print(pd.merge(df1,df2,on='userid',how='left')) # 左连接
# 右连接 df1 = pd.DataFrame({ 'userid':['a','b','c','d'], 'age':[12,13,14,15] }) df2 = pd.DataFrame({ 'userid':['a','c','e'], 'money':[123,543,546] }) print(df1.merge(df2,on='userid',how='right')) print('------------') print(pd.merge(df1,df2,on='userid',how='right')) # 右连接
与此同时还有一个outer外连接,他将展示量表所有的信息,没有对应的则为NaN
# 外连接 df1 = pd.DataFrame({ 'userid':['a','b','c','d'], 'age':[12,13,14,15] }) df2 = pd.DataFrame({ 'userid':['a','c','e'], 'money':[123,543,546] }) print(df1.merge(df2,on='userid',how='outer')) print('------------') print(pd.merge(df1,df2,on='userid',how='outer')) # 外连接