我有两个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'也可以在多行中重复,然后我想获得所有相应的行。
答案 0 :(得分: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,
答案 1 :(得分:3)
您是否考虑过使用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_table
和merge
决定列名。如果您自己分配列名,您将获得更好的输出。
答案 2 :(得分: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