【问题标题】:Combining 2 .csv files by common column按公共列组合 2 个 .csv 文件
【发布时间】:2010-10-24 00:45:49
【问题描述】:

我有两个 .csv 文件,其中文件 1 的第一行是:

MPID,Title,Description,Model,Category ID,Category Description,Subcategory ID,Subcategory Description,Manufacturer ID,Manufacturer Description,URL,Manufacturer (Brand) URL,Image URL,AR Price,Price,Ship Price,Stock,Condition

文件 2 的第一行:

Regular Price,Sale Price,Manufacturer Name,Model Number,Retailer Category,Buy URL,Product Name,Availability,Shipping Cost,Condition,MPID,Image URL,UPC,Description

然后每个文件的其余部分都填充了信息。

如您所见,两个文件都有一个名为 MPID 的公共字段(文件 1:col 1,文件 2:col 9,其中第一个 col 是 col 1)。

我想创建一个新文件,通过查看此列将这两个文件组合起来(如:如果两个文件中都有一个 MPID,那么在新文件中,这个 MPID 将与它的两个行一起出现来自文件 1 及其来自文件 2 的行)。如果一个 MPID 只出现在一个文件中,那么它也应该进入这个组合文件。

文件没有以任何方式排序。

如何在带有 shell 脚本或 python 的 debian 机器上执行此操作?

谢谢。

编辑:除了分隔字段的文件外,两个文件都没有逗号。

【问题讨论】:

标签: python shell join csv debian


【解决方案1】:
sort -t , -k index1 file1 > sorted1
sort -t , -k index2 file2 > sorted2
join -t , -1 index1 -2 index2 -a 1 -a 2 sorted1 sorted2

【讨论】:

  • 当心引号! sort 和 join 都不服从任何引用
  • 为了稍微扩展 Javier 的评论,CSV 格式允许列通过双引号来包含文字逗号。 one,"two,more two","three, ""more, still more three""" 行包含三个 CSV 列,它们显示引用的逗号,甚至在第三列的转义引号内显示​​引用的逗号。 (也存在具有不同转义机制的不同 CSV 方言。)唯一正确的解决方案是使用实际的 CSV 解析器,例如 Python 的 csv 模块。
【解决方案2】:

这是经典的“关系连接”问题。

你有几种算法。

  • 嵌套循环。您从一个文件中读取以选择“主”记录。您阅读了整个其他文件,找到了与主文件匹配的所有“详细”记录。这是个坏主意。

  • 排序合并。您根据公共密钥将每个文件排序为临时副本。然后,您可以通过从 master 读取然后从详细信息中读取所有匹配的行并写入合并的记录来合并这两个文件。

  • 查找。您将其中一个文件完全读入内存中的字典,由键字段索引。这对于详细文件来说可能很棘手,每个键都有多个子文件。然后你读取另一个文件并在字典中查找匹配的记录。

其中,排序合并通常是最快的。这完全使用 unix sort 命令完成。

查找实现

import csv
import collections

index = collections.defaultdict(list)

file1= open( "someFile", "rb" )
rdr= csv.DictReader( file1 )
for row in rdr:
    index[row['MPID']].append( row )
file1.close()

file2= open( "anotherFile", "rb" )
rdr= csv.DictReader( file2 )
for row in rdr:
    print row, index[row['MPID']]
file2.close()

【讨论】:

  • 我相信从 Python 2.2 开始,DictReader 没有实现 getitem。这样做是出于速度原因。所以代码: index[rdr['MPID']].append( row ) 将失败并出现 AttributeError: DictReader instance has no attribute 'getitem'
  • @uman:DictReader 的结果是一流的dict 对象。所有dict 方法都完好无损。
  • @S.Lott:您的代码可能不正确。我对 Python 非常生疏,但是用您的代码尝试 MPID 的示例会导致 AttributeError。请参阅:pastebin.com/VJgSfA3u有人知道如何在 cmets 中嵌入格式吗?
  • @uman:与其在 cmets 中做太多事情,不如用你不喜欢的代码打开一个全新的新问题。
  • 否 - 这样做会使响应与提示它的问题分离。我没有对您的回复投反对票,因为其余部分都是正确的。但是你的代码错了。
【解决方案3】:

您需要查看 shell 中的 join 命令。您还需要对数据进行排序,并且可能会丢失第一行。如果任何数据包含逗号,则整个过程将失败。或者,您将需要使用 CSV 敏感的过程来处理数据,该过程引入了不同的字段分隔符(可能是 control-A),您可以使用它来明确地拆分字段。

另一种方法是使用 Python 将两个文件读入一对字典(在公共列上键入),然后使用循环覆盖两个字典中较小的一个中的所有元素,寻找匹配的值在另一个。 (这是基本的嵌套循环查询处理。)

【讨论】:

  • 加入效果很好;但是输入文件必须按key排序。它也无法读取任意 csv 文件。特别是,引用字段中的逗号将移动该记录的所有字段编号
  • @Javier:同意 - 这就是为什么我更新了我的答案,即使没有看到你的评论(这可能是在我编辑的同时准备)。
【解决方案4】:

您似乎正在尝试在 shell 脚本中执行此操作,这通常使用 SQL Server 完成。是否可以将 SQL 用于该任务?例如,您可以将这两个文件导入 mysql,然后创建一个连接,然后将其导出为 CSV。

【讨论】:

  • 这是严重的矫枉过正,除非您的数据对于 join 或 Awk 而言太大,并且/或者您需要对同一数据执行多个联接。
【解决方案5】:

您可以查看我的 FOSS 项目 CSVfix,这是一个用于处理 CSV 文件的流编辑器。除了其他功能外,它还支持连接,并且不需要使用脚本。

【讨论】:

    【解决方案6】:

    对于基于一个或多个公共列合并多个文件(甚至 > 2),python 中最好和最有效的方法之一是使用“brewery”。您甚至可以指定需要考虑合并哪些字段以及需要保存哪些字段。

    import brewery
    from brewery
    import ds
    import sys
    
    sources = [
        {"file": "grants_2008.csv",
         "fields": ["receiver", "amount", "date"]},
        {"file": "grants_2009.csv",
         "fields": ["id", "receiver", "amount", "contract_number", "date"]},
        {"file": "grants_2010.csv",
         "fields": ["receiver", "subject", "requested_amount", "amount", "date"]}
    ]
    

    创建所有字段的列表并添加文件名以存储有关数据记录来源的信息。通过源定义并收集字段:

    for source in sources:
        for field in source["fields"]:
            if field not in all_fields:
    
    out = ds.CSVDataTarget("merged.csv")
    out.fields = brewery.FieldList(all_fields)
    out.initialize()
    
    for source in sources:
    
        path = source["file"]
    
    # Initialize data source: skip reading of headers
    # use XLSDataSource for XLS files
    # We ignore the fields in the header, because we have set-up fields
    # previously. We need to skip the header row.
    
        src = ds.CSVDataSource(path,read_header=False,skip_rows=1)
    
        src.fields = ds.FieldList(source["fields"])
    
        src.initialize()
    
    
        for record in src.records():
    
       # Add file reference into ouput - to know where the row comes from
        record["file"] = path
    
            out.append(record)
    
    # Close the source stream
    
        src.finalize()
    
    
    cat merged.csv | brewery pipe pretty_printer
    

    【讨论】:

    猜你喜欢
    • 2022-11-22
    • 2019-08-14
    • 2021-03-08
    • 2015-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-25
    • 2016-01-16
    相关资源
    最近更新 更多