来看看数据分析中相对复杂的去重问题

  • 2019 年 10 月 10 日
  • 筆記

在数据分析中,有时候因为一些原因会有重复的记录,因此需要去重。如果重复的那些行是每一列懂相同的,删除多余的行只保留相同行中的一行就可以了,这个在Excel或pandas中都有很容易使用的工具了,例如Excel中就是在菜单栏选择数据->删除重复值,然后选择根据哪些列进行去重就好,pandas中是有drop_duplicates()函数可以用。 但面对一些复杂一些的需求可能就不是那么容易直接操作了。例如根据特定条件去重、去重时对多行数据进行整合等。特定条件例如不是保留第一条也不是最后一条,而是根据两列存在的某种关系、或者保留其中最大的值、或保留评价列文字最多的行等。下面记录一种我遇到的需求:因为设计原因,用户在购物车下的单每个商品都会占一条记录,但价格只记录当次购物车总价,需要每个这样的单子只保留一条记录,但把商品名称整合起来。

抽象一下,相当于把下面的表df根据uid去重,但是每个uid对应的name整合在一行里(暂且不管date列),从下图中左边的变成右边效果:

去重前后效果示例

这个不能直接由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()可以参考其官方文档。