7

我有两个 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

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

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

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

4

4 回答 4

3

(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,
于 2015-01-02T22:24:08.213 回答
3

您是否考虑过使用熊猫?如果您熟悉 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

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

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

于 2015-01-08T14:24:53.700 回答
2

由于合并的条件似乎很复杂,因此将数据加载到数据库中并使用 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
于 2014-11-21T00:23:44.193 回答
2

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

如果我正确理解输入文件,则只考虑;(如果有的话)左边的值。这可以通过使用split(';')元素零来实现。如果没有,;则元素零将是整个字符串。对 then 的分配mapper只需要遵循您定义的规则(仅在尚未存在时添加,除非第 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 的当前行中的每个条目,如果存在匹配项(与原始映射器数据匹配),则将该映射数据附加到该行。然后将像以前一样写入该行。

于 2015-01-02T16:14:40.760 回答