【问题标题】:Merge two CSV files based on a data from a column根据列中的数据合并两个 CSV 文件
【发布时间】:2015-01-18 23:10:21
【问题描述】:

我有两个 csv 文件,如下所示。

CSV1

data13      data23      d      main_data1;main_data2      data13         data23
data12      data22      d      main_data1;main_data2      data12         data22
data11      data21      d      main_data1;main_data2      data11         data21
data3       data4       d      main_data2;main_data4      data3          data4
data52      data62      d      main_data3                 data51         data62
data51      data61      d      main_data3                 main_data3     data61
data7       data8       d      main_data4                 data7          data8

CSV2

id1      main_data1      a1      a2      a3
id2      main_data2      b1      b2      b3
id3      main_data3      c1      c2      c3
id4      main_data4      d1      d2      d3
id5      main_data5      e1      e2      e3

现在我的问题是,当两个文件中的一列完全相同时,我知道如何合并两个 CSV 文件。但我的问题有点不同。 CSV1 的第 4 列可以包含 CSV2 的第 2 列。我想得到一个如下的 CSV 文件

FINAL_CSV

id1      main_data1      a1      a2      a3      data13
id2      main_data2      b1      b2      b3      data3
id3      main_data3      c1      c2      c3      main_data3
id4      main_data4      d1      d2      d3      data7
id5      main_data5      e1      e2      e3

where:
1.它匹配两列中的数据,并从第一次出现时获取相应的行并写入 csv 文件。
2. 如果不匹配,它可以将 FINAL_CSV 中的最后一列留空或写入“NA”或类似的任何内容。
3. 当 CSV1 的第 4 列和第 5 列中的数据完全匹配时,它返回该行而不是第一次出现。

我完全不知道如何做到这一点。帮助它的一部分也很好。任何建议都非常感谢。
PS- 我知道 csv 文件中的数据应该用逗号分隔,但为了清楚起见,我更喜欢制表符,尽管实际数据用逗号分隔。

编辑:实际上,'main_data' 可以在 CSV2 的任何列中,而不仅仅是在 column2 中。相同的“main_data”也可以在多行中重复,然后我想获取所有相应的行。

【问题讨论】:

  • 你能澄清一下加入条件吗?例如main_data1 包含在多行 CSV1 中,但输出只包含一次
  • 抱歉,没有仔细阅读,仅第一次出现
  • 这必须在 python 中吗?
  • @Jidder 是的,因为这是我唯一熟悉的语言,R 也很好。但是您还有什么建议?
  • @amy 如果是在 unix 系统上,那么我可以在awk 中提出解决方案

标签: python regex csv merge two-columns


【解决方案1】:

(g)awk 的一种方式。

 awk -F, 'NR==FNR{a[$2]=$0;next}
         {split($4,b,";");x=b[1]}
         (x in a)&&!c[x]++{d[x]=$5}
         ($5 in a){d[$5]=$5}
         END{n=asorti(a,e);for(i=1;i<=n;i++)print a[e[i]]","d[e[i]]}'  CSV1 CSV2

输出

id1,main_data1,a1,a2,a3,data13
id2,main_data2,b1,b2,b3,data3
id3,main_data3,c1,c2,c3,main_data3
id4,main_data4,d1,d2,d3,data7
id5,main_data5,e1,e2,e3,

【讨论】:

  • 如果我说你的 awk 很热会不会很尴尬?
  • 这是否适用于“main_data 可以位于 CSV 的任何列”要求(稍后添加)?
  • @mfitzp nope 没看到。
  • 更多评论会对新手有所帮助...不过感谢您的回答:)
【解决方案2】:

您是否考虑过使用pandas?如果您熟悉 R,那么数据帧应该非常简单。以下为您提供您想要的:

from pandas import merge, read_table

csv1 = read_table('CSV1.csv', sep=r"[;,]", header=None)
csv2 = read_table('CSV2.csv', sep=r"[,]",  header=None)

print csv1
print csv2

请注意,我用逗号替换了制表符,并用分号分隔。到目前为止的输出应该是:

        0       1   2           3           4           5       6
0  data13  data23   d  main_data1  main_data2      data13  data23
1  data12  data22   d  main_data1  main_data2      data12  data22
2  data11  data21   d  main_data1  main_data2      data11  data21
3   data3   data4   d  main_data2  main_data4       data3   data4
4  data52  data62   d  main_data3         NaN      data51  data62
5  data51  data61   d  main_data3         NaN  main_data3  data61
6   data7   data8   d  main_data4         NaN       data7   data8

[7 rows x 7 columns]
     0           1   2   3   4
0  id1  main_data1  a1  a2  a3
1  id2  main_data2  b1  b2  b3
2  id3  main_data3  c1  c2  c3
3  id4  main_data4  d1  d2  d3
4  id5  main_data5  e1  e2  e3

[5 rows x 5 columns]

使用左连接:

kw1 = dict(how='left', \
          left_on=[3,4], \
          right_on=[1,1], \
          suffixes=('l', 'r'))

df1 = merge(csv1, csv2, **kw1)
df1.drop_duplicates(cols=[3], inplace=True)

print df1[[0,7]]

给出合并的第 0 列和第 7 列:

            3       5
0  main_data1  data13
3  main_data2   data3
4  main_data3  data51
6  main_data4   data7

[4 rows x 2 columns]

要根据需要提供输出,请与CSV2 进行另一次合并(这次是外连接):

kw2 = dict(how='outer', \
           left_on=[3], \
           right_on=[1], \
           suffixes=('l', 'r'))

df2 = merge(df1, csv2, **kw2)

print df2[[15,16,17,18,19,8]]

输出:

     0           1   2  3r  4r       5
0  id1  main_data1  a1  a2  a3  data13
1  id2  main_data2  b1  b2  b3   data3
2  id3  main_data3  c1  c2  c3  data51
3  id4  main_data4  d1  d2  d3   data7
4  id5  main_data5  e1  e2  e3     NaN

您不必将**kw 用于关键字参数。我只是用它来使所有东西都水平放置。

我让read_tablemerge 决定列名。如果您自己指定列名,您将获得更好看的输出。

【讨论】:

    【解决方案3】:

    由于合并的条件似乎很复杂,因此将数据加载到数据库中并使用 SQL 可能是值得的。使用内存中的 SQLite,您可以这样做(假设逗号分隔数据)

    import csv
    import sqlite3
    
    def createTable(cursor, rows, tablename):
        tableCreated = False
        for row in rows:
            if not tableCreated:
                sql = "CREATE TABLE %s(ROW INTEGER PRIMARY KEY, " + ", ".join(["c%d" % (i+1) for i in range(len(row))]) + ")"
                cur.execute(sql % tablename)
                tableCreated = True
            sql = "INSERT INTO %s VALUES(NULL, " + ", ".join(["'" + c + "'" for c in row]) + ")"
            cur.execute(sql % tablename)
        conn.commit()
    
    
    conn = sqlite3.connect(":memory:")
    cur = conn.cursor()
    
    for filename, tablename in [(path_to_csv1, "CSV1"), (path_to_csv2, "CSV2")]:
        with open(filename, "r") as f:
            reader = csv.reader(f, delimiter=',')        
            rows = [row for row in reader]
        createTable(cur, rows, tablename)
    

    然后您可以在 SQL 中制定您的联接逻辑。您可以像这样运行查询:

    for row in cur.execute(your_sql_statement):
        print row
    

    以下查询给出所需的输出:

    WITH
    MATCHES AS( -- get all matches
        SELECT      CSV2.*
                    , CSV1.ROW as ROW_1                 
                    , CSV1.C4 as C4_1
                    , CSV1.C5 as C5_1
        FROM        CSV2 
        LEFT JOIN   CSV1 
        ON          CSV1.C4 LIKE '%' || CSV2.C2 || '%'    
    ),
    EXACT AS( -- matches where CSV1.C4 = CSV1.C5
        SELECT      *
        FROM        MATCHES
        WHERE       C4_1 = C5_1
    ),
    MIN_ROW AS( -- CSV1.ROW of first occurence for each CSV2.C1
        SELECT      C1
                    , min(ROW_1) as ROW_1
        FROM        MATCHES
        WHERE       C1 NOT IN (SELECT C1 FROM EXACT)
        GROUP BY    C1, C2, C3, C4, C5                  
    )
    -- use C4=C5 first
    SELECT      *
    FROM        EXACT
    UNION
    -- if match not in exact, use first occurence
    SELECT      MATCHES.*
    FROM        MIN_ROW
    INNER JOIN  MATCHES
    ON          MIN_ROW.C1 = MATCHES.C1
    AND         (MIN_ROW.ROW_1 = MATCHES.ROW_1 OR MIN_ROW.ROW_1 IS NULL)
    ORDER BY    C1
    

    【讨论】:

    • 我很抱歉。不在。这看起来很棒。将尝试并更新您。非常感谢!
    【解决方案4】:

    由于您最初要求为此提供 Python 解决方案,因此我想我会提供一个。发生的最简单的解决方案是首先加载CSV1 并使用它生成一个映射字典,以便在从 CSV2 生成输出时使用。

    如果我正确理解输入文件,则只考虑 ; 左侧的值(如果有的话)。这可以通过使用split(';') 并取元素零来实现。如果没有;,则元素零将是整个字符串。分配给mapper 然后只需要遵循您定义的规则(仅在不存在时添加,except 当第 4 列和第 5 列匹配时)。

    下面的代码产生您请求的输出:

    import csv
    
    mapper = dict()
    with open('CSV1', 'r') as f1:
        reader = csv.reader(f1)
        for row in reader:
            # Column 3 contains the match; but we only want the left-most (before semi-colon)
            i = row[3].split(';')[0]
            # Column 4 contains the target value for output
            t = row[4]
            if i not in mapper:
                mapper[i] = t
            elif row[3] == row[4]:
                mapper[i] = t        
    
    with open('CSV2', 'r') as f2:
        with open('FINAL_CSV', 'wb') as fo:
            reader = csv.reader(f2)
            writer = csv.writer(fo)
            for row in reader:
                if row[1] in mapper:
                    row.append( mapper[ row[1] ] )
                writer.writerow(row)
    

    输出文件:

    id1,main_data1,a1,a2,a3,data13
    id2,main_data2,b1,b2,b3,data3
    id3,main_data3,c1,c2,c3,main_data3
    id4,main_data4,d1,d2,d3,data7
    id5,main_data5,e1,e2,e3
    

    要解决“main_data 可以在 CSV 的任何列中”的修改,请使用以下代码:

    for row in reader:
        for r in row:
            if r in mapper:
                row.append( mapper[ r ] )
                break
    
        writer.writerow(row)
    

    这将搜索 CSV2 的当前行中的每个条目,如果存在匹配项(与原始映射器数据匹配),则将该映射数据附加到该行。然后将像以前一样写入该行。

    【讨论】:

    • 谢谢。这很好用,但我该怎么做才能在文件中的任何位置查找“main_data”,而不仅仅是在 CSV2 的第 2 列中?
    • 您的意思是CSV2中的main_data(地图)数据可以在任何列中吗?如果是这样,您可以迭代行中的项目并检查映射器。那是你要的吗?如果您有一些有用的示例数据,谢谢!
    • 是的,我同意示例数据会很有用
    • 目前最好的答案和所需的EDIT2可以很容易地添加。我只是在elif row[3] == row[4] 周围徘徊:CSV1 是否有可能包含两个col4==col5 案例?在这种情况下,将采用最后一次而不是第一次出现这种情况,这可能不是预期的行为。考虑到这个问题,我建议你,@amy,重新考虑你组织数据的方式。这不是很一致。
    • 谢谢@loli。回复:col4==col5,正如你所说,它将采用最后一次而不是第一次出现,但这是我对要求 3 的解释。“当 CSV1 的第 4 列和第 5 列中的数据完全匹配时,它返回该行而不是第一次出现。”诚然,它没有说明优先顺序,但我认为它必须与规范不同并排在最后!
    猜你喜欢
    • 2019-06-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-28
    • 2021-09-18
    • 2015-01-30
    • 1970-01-01
    相关资源
    最近更新 更多