【问题标题】:Remove duplicate rows in CSV comparing data in only two columns with Python删除 CSV 中的重复行,仅使用 Python 比较两列中的数据
【发布时间】:2015-12-23 20:20:12
【问题描述】:

可能有很多方法可以解决这个问题,但归根结底是要点:

我有两个人满为患的数据库,都导出为 csv 文件。其中一个数据库正在退役。 我需要比较每个 csv 文件(或两者的组合版本),并在即将退役的服务器中过滤掉所有非唯一的人。这样我就可以将唯一的人从退役的数据库导入到当前的数据库中。

我只需要比较 FirstName 和 LastName(它们是两个单独的列)。部分问题在于它们不是精确重复的,名称在一个数据库中全部大写,而在另一个数据库中则大写。

这是我将两个 csv 文件合并为一个时的数据示例。所有 CAPS 名称均来自当前数据库(这是 csv 当前的格式):

FirstName,LastName,id,id2,id3
John,Doe,123,432,645
Jacob,Smith,456,372,383
Susy,Saucy,9999,12,8r83
Contractor ,#1,8dh,28j,153s
Testing2,Contrator,7463,99999,0283
JOHN,DOE,999,888,999
SUSY,SAUCY,8373,08j,9023

将被解析为:

Jacob,Smith,456,372,383
Contractor,#1,8dh,28j,153s
Testing2,Contrator,7463,99999,0283

解析其他列是无关紧要的,但显然数据是非常相关的,所以它必须保持不变。 (实际上还有几十个其他列,而不仅仅是三个)。

为了了解我实际上有多少重复,我运行了这个脚本(取自之前的帖子):

with open('1.csv','r') as in_file, open('2.csv','w') as out_file:
    seen = set() # set for fast O(1) amortized lookup
    for line in in_file:
        if line in seen: continue # skip duplicate

        seen.add(line)
        out_file.write(line)

不过对我的需求来说太简单了。

【问题讨论】:

  • 您的重复条目在 other 列中具有不同的值。您如何决定要保留哪些价值观?
  • 制表符分隔吗?
  • wwii - 是的,每个服务器都有不同的加密,所以即使真实值相同,它在数据库中的记录也是不同的。我将保留与当前服务器关联的值(大写名称)。但由于我将仅使用唯一名称附加当前数据库,因此这无关紧要。
  • Padraic C - 目前,它是逗号分隔的。但我可以将其更改为方便的任何内容。
  • 好的,那我们就可以使用csv模块了,我来编辑

标签: python database parsing csv python-3.x


【解决方案1】:

您需要对不区分大小写的名称串联进行操作。例如:

with open('1.csv','r') as in_file, open('2.csv','w') as out_file:
    seen = set() # set for fast O(1) amortized lookup
    for line in in_file:
        field_list = line.split(' ')
        key_name = (field_list[0] + "_" + filed_list[1]).lower()
        if key_name in seen: continue # skip duplicate

        seen.add(key_name)
        out_file.write(line)

【讨论】:

    【解决方案2】:

    您可以为此使用 pandas 包

    import pandas as pd
    import StringIO
    

    将 StringIO 替换为 csv 文件的路径

    df1 = pd.read_table(StringIO.StringIO('''FirstName    LastName    id     id2    id3
    John         Doe         123    432    645
    Jacob        Smith       456    372    383
    Susy         Saucy       9999   12     8r83
    Contractor   #1          8dh    28j    153s
    Testing2     Contrator   7463   99999  0283'''), delim_whitespace=True)
    
    df2 = pd.read_table(StringIO.StringIO('''FirstName    LastName    id     id2    id3
    JOHN         DOE         999    888    999
    SUSY         SAUCY       8373   08j    9023'''), delim_whitespace=True)
    

    将名称连接起来并大写

    df1['name'] = (df1.FirstName + df1.LastName).str.upper()
    df2['name'] = (df2.FirstName + df2.LastName).str.upper()
    

    从 df1 中选择与 df2 中的名称不匹配的行

    df1[~df1.name.isin(df2.name)]
    

    【讨论】:

      【解决方案3】:

      除非你真的想保留一个具有重复值的唯一行,否则使用集合是不好的做:

      with open("test.csv", encoding="utf-8") as f, open("file_out.csv", "w") as out:
          from collections import Counter
          from csv import reader, writer
          wr = writer(out)
          header = next(f) # get header
          # get count of each first/last name pair lowering each string
          counts = Counter((a.lower(), b.lower()) for a, b, *_ in reader(f))
          f.seek(0) # reset counter 
          out.write(next(f))  # write header ?
          # iterate over the file again, only keeping rows which have
          # unique first and second names
          wr.writerows(row for row in reader(f)
                         if counts[row[0].lower(),row[1].lower()] == 1)
      

      输入:

      FirstName,LastName,id,id2,id3
      John,Doe,123,432,645
      Jacob,Smith,456,372,383
      Susy,Saucy,9999,12,8r83
      Contractor,#1,8dh,28j,153s
      Testing2,Contrator,7463,99999,0283
      JOHN,DOE,999,888,999
      SUSY,SAUCY,8373,08j,9023
      

      文件输出:

      FirstName,LastName,id,id2,id3
      Jacob,Smith,456,372,383
      Contractor,#1,8dh,28j,153s
      Testing2,Contrator,7463,99999,0283
      

      counts 计算每个名称在降低后出现的次数。然后我们重置指针并只写入前两列值在整个文件中只出现一次的行。

      或者没有 csv 模块,如果你有名字列可能会更快:

      with open("test.csv") as f, open("file_out.csv","w") as out:
          from collections import Counter
          header = next(f) # get header
          next(f) # skip blank line
          counts = Counter(tuple(map(str.lower,line.split(",", 2)[:2])) for line in f)
          f.seek(0) # back to start of file
          next(f), next(f) # skip again
          out.write(header) # write original header ?
          out.writelines(line for line in  f
                         if counts[map(str.lower,line.split(",", 2)[:2])] == 1)
      

      【讨论】:

      • 逗号分隔以回答您的问题,但如果需要,我可以更改此设置。感谢回复
      • 表头后面真的有空行吗?
      【解决方案4】:
      changed since it is in csv format 
      
          from collections import defaultdict
      
          dd = defaultdict(list)
          d = {}
      
          import re
      
      with open("data") as f:
          for line in f:
              line = line.strip().lower()
              mobj =  re.match('(\w+),(\w+|#\d),(.*)',line)
              firstf, secondf, rest = mobj.groups()
              key = firstf + "_" + secondf
              d[key] = rest
              dd[key].append(rest)
      
      
          for k, v in d.items():
              print(k, v)
      

      输出

      jacob_smith 456,372,383
      
      testing2_contrator 7463,99999,0283
      
      john_doe 999,888,999
      
      susy_saucy 8373,08j,9023
      
      contractor_#1 8dh,28j,153s
      
      jacob_smith 456 372 383
      

      输出

      for k, v in dd.items():
          print(k,v)
      
      
      jacob_smith ['456,372,383']
      
      testing2_contrator ['7463,99999,0283']
      
      john_doe ['123,432,645', '999,888,999']
      
      susy_saucy ['9999,12,8r83', '8373,08j,9023']
      
      contractor_#1 ['8dh,28j,153s']
      

      【讨论】:

        【解决方案5】:

        您可以保留使用集合的想法。只需定义一个返回您感兴趣的函数的函数:

        def name(line):
            line = line.split(',')
            n = ' '.join(line[:2])
            return n.lower()
        

        在不连接两个数据库的情况下,将当前数据库中的名称读入一个集合。

        with open('current.csv') as f:
            next(f)
            current_db = {name(line) for line in f}
        

        检查退役数据库中的名称,如果没有看到,则将其写入。

        with open('decommissioned.csv') as old, open('unique.csv', 'w') as out:
            next(old)
            for line in old:
                if name(line) not in current_db:
                    out.write(line)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-12-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-12-18
          • 2022-01-25
          • 2020-05-11
          相关资源
          最近更新 更多