2014-11-20 112 views
5

我有兩個csv文件,如下所示。根據列中的數據合併兩個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 

其中:
從兩列中的數據相匹配,並從第一次出現得到相應的行,並寫入csv文件。
2.當沒有匹配時,它可以將FINAL_CSV中的最後一列留空或寫入'NA'或任何此類。
3.當CSV1的第4列和第5列中的數據完全匹配時,它將返回該行而不是第一次出現。

我完全失去了如何做到這一點。幫助它的一部分也很好。任何建議,高度讚賞。
PS-我知道來自csv文件的數據應該用逗號分隔,但爲了清楚起見,我偏好製表符,儘管實際數據由逗號分隔。

編輯:實際上,'main_data'可以在CSV2中的任何列中,而不是在列2中。同樣的'main_data'也可以在多行中重複,然後我想獲得所有相應的行。

+0

你能說清楚連接條件嗎?例如main_data1包含在CSV1的幾行中,但輸出僅包含一次 – 2014-11-21 00:16:16

+0

對不起,沒有仔細閱讀,第一次發生只有 – 2014-11-21 00:38:16

+0

這是否必須在python? – 2015-01-02 15:36:09

回答

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 
+0

我很抱歉。不在。這看起來不錯。將嘗試它並更新你。非常感謝你! – abn 2014-11-21 02:24:18

2

由於您最初要求一個Python解決這個我想我會提供一個。發生的最簡單的解決方案是首先加載CSV1並使用它生成映射字典以在從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可以在CSV的任何列」修飾使用以下代碼:

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

    writer.writerow(row) 

這將搜索CSV2的當前行中的每個條目,並且如果匹配(第e原始映射器數據)將該映射數據追加到該行。然後該行將如前所述寫入。

+0

謝謝。這很好,但我能做些什麼來尋找可能在文件中的任何位置的'main_data',而不僅僅是CSV2的第2列? – abn 2015-01-02 16:27:19

+0

您的意思是CSV2中的'main_data'(地圖)數據可能位於任何列中?如果是這樣,您可以遍歷行中的項目並檢查映射器。那是你要的嗎?如果您有一些有用的示例數據,謝謝! – mfitzp 2015-01-02 21:32:25

+0

是的,我同意示例數據將是有用的 – jamylak 2015-01-04 14:54:42

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, 
+2

如果我說你的awk很熱,會不會很尷尬? – Korbonits 2015-01-07 20:50:46

+0

這個'main_data可以在CSV的任何列'的要求(稍後添加)的工作? – mfitzp 2015-01-09 23:14:16

+0

@mfitzp nope沒有看到。 – 2015-01-09 23:58:12

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

使合併的零級和第七列:

  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決定列名稱。如果你自己分配列名,你會看到更好的輸出。

相關問題