來看看數據分析中相對複雜的去重問題

  • 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()可以參考其官方文檔。

Tags: