数据预处理之一些需要去重的情况处理(usingPython)
在数据分析中,有时候因为一些原因会有重复的记录,因此需要去重。如果重复的那些行是每一列懂相同的,删除多余的行只保留相同行中的一行就可以了,这个在Excel或pandas中都有很容易使用的工具了,例如Excel中就是在菜单栏选择数据->删除重复值,然后选择根据哪些列进行去重就好,pandas中是有drop_duplicates()
函数可以用。
但面对一些复杂一些的需求可能就不是那么容易直接操作了。例如根据特定条件去重、去重时对多行数据进行整合等。特定条件例如不是保留第一条也不是最后一条,而是根据两列存在的某种关系、或者保留其中最大的值、或保留评价列文字最多的行等。下面记录一种我遇到的需求:因为设计原因,用户在购物车下的单每个商品都会占一条记录,但价格只记录当次购物车总价,需要每个这样的单子只保留一条记录,但把商品名称整合起来。
抽象一下,相当于把下面的表df根据uid去重,但是每个uid对应的name整合在一行里(暂且不管date列),从图1的左边变成右边效果:
图1, 去重前后效果示例这个不能直接由drop_duplicates()
,那就写代码自己实现吧,因为是根据uid去重,我的思路是对uid进行循环,把uid相同的聚在一起,在if条件中选择保存的行并把name整合起来,建个新表保存去重后的行,
ndf=pd.DataFrame(columns=df.columns) #根据df的列名建一个空表ndf
uids=set(df['uid'])
for u in uids:
one=df.loc[df['uid']==u] #获取所有uid等于u的行,之后只会保存一行
#在这里写if然后只保留一行,然后concat到ndf上,实现只保留一行
olst=list(one['name']) #或者用set
zero=one.iloc[[0]] #iloc[行号]是series iloc[[行号]]是dataframe
#zero['name']=str(olst)
if len(olst)>1: #等于1的就不用改了
zero['name']=str(olst) #or =''.join(olst)
ndf=pd.concat([ndf,zero]) #把选出来的zero加到ndf里
我是用了一个for循环去遍历的,如果有更优雅的实现欢迎指教呀。
更深入一些,如果没有某一列可以作为主键呢?存在一个表,除name之外,其他的列都相同算重复行,这些列有文本有数值型,但是不能拿其中任何列作主键,实现上面的去重合并name,怎么办?一个个比对是O(n^2),我目前的思路时用除name之外的列合并形成一个字符串型的新列,拿这列做主键,用上面的代码片段。合并之后再删掉之前建的新列保持数据的格式。
附录:
关于python中的drop_duplicates(subset=None, keep='first', inplace=False)
,一些基础的去重需求直接用这个函数就好,它有三个参数:
- subset指定根据哪些列去重,默认是根据所有列,也就是当两行的所有列都一样时满足去重条件;
- keep有三种选择:{‘first’, ‘last’, False},first和last分别对应选重复行中的第一行、最后一行,false是删除所有的重复值,例如上面例子中的df根据name去重且keep填false的话,就只剩name等于d的行了;
-
inplace是指是否应用于原表,通常建议选择默认的参数False,然后写
newdf=df.drop_duplicates(subset=['col1'])
。
例如有个业务场景是对问卷填写数据进行预处理,用户可以多次填写,根据最后一次填写的数据为准,根据同一个用户名和手机号进行去重(假设数据根据时间先后顺序排序了,否则先用sort_values(by=' ')排序),则可以写ndf=df.drop_duplicates(subset=['姓名','手机号'],keep='last')
。进一步了解drop_duplicates()可以参考官方文档。