根据列中的数据合并两个CSV文件

i7uq4tfw  于 2022-12-06  发布在  其他
关注(0)|答案(4)|浏览(132)

我有两个csv文件像下面.
CSV 1型

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

CSV 2型

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文件中的一列完全相同时,我知道如何合并这两个文件。但我的问题有点不同。CSV 1中的第4列可能包含CSV 2中的第2列。我想得到如下所示的CSV文件
最终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.**当CSV 1的第4列和第5列中的数据完全匹配时,它将返回该行,而不是第一个匹配项。

我完全不知道该怎么做。帮助做一部分也很好。任何建议都是非常感谢的。
PS-我知道csv文件中的数据应该用逗号分隔,但为了清晰起见,我更喜欢制表符,尽管实际数据是用逗号分隔的。
编辑:实际上,'main_data'可以在CSV 2的任何列中,而不仅仅是在column 2中。相同的'main_data'也可以在多行中重复,那么我想获得所有相应的行。

llew8vvj

llew8vvj1#

(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,
cidc1ykv

cidc1ykv2#

您是否考虑过使用pandas?如果您熟悉R,那么 Dataframe 应该非常简单。下面是您想要的:

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]]

给出合并的第零列和第七列:

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来决定列名。如果您自己指定列名,则会得到更好看的输出。

voj3qocg

voj3qocg3#

由于合并的条件看起来很复杂,所以将数据加载到数据库中并使用SQL可能是值得的。

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
jmo0nnb3

jmo0nnb34#

由于您最初要求Python提供一个解决方案,我想我会提供一个。最简单的解决方案是首先加载CSV1,然后使用它生成一个Map字典,以便在生成CSV2的输出时使用。
如果我对输入文件的理解正确的话,只有;左边的值(如果有)。这可以通过使用split(';')并取元素零来实现。如果没有;,则元素零将是整个字符串。对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 can be in any column of CSV'修改问题,请使用以下代码:

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

    writer.writerow(row)

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

相关问题