部分的に一致するキー列を持つ複数のCSVファイルをマージする

部分的に一致するキー列を持つ複数のCSVファイルをマージする

100個のファイルがあり、csv各ファイルには2つの列が含まれています。最初のものはでtaxonomy、2番目はですcounts。各ファイルには約10,000行があります。各ファイルのデータはtaxonomy部分的にのみ共有され、合計約50,000の固有値があります。taxa1つのファイルから欠落している項目に値が割り当てられたテーブルにマージする必要があります0。結果は、50,000行と101列のテーブルでなければなりませんcsvtsv単純化された例は次のとおりです。ファイル 1( R1.csv):

A,1
B,20
C,30

ファイル 2( R2.csv):

C,1
D,13
E,15
F,19

ファイル3(R3.csv):

A,1
B,4
E,2
G,6
H,8

予想される結果:

Taxa,R1,R2,R3
A,1,0,1
B,20,0,4
C,30,1,0
D,0,13,0
E,0,15,2
F,0,19,0
G,0,0,6
H,0,0,8

bashスクリプトを使ってこれを行う方法を知っていますか?

答え1

私はawkこれを使用して複数のファイルを一度に処理します。

sed 's/,R[1-9]\+\.csv:/,/g' <(awk -v HEADER="$(printf ",%s:" R{1..3}.csv)" -F, '
    { seen[$1]=seen[$1]","FILENAME":"$2; }
    END { print HEADER; for (x in seen) print x seen[x]}' R{1..3}.csv \
|awk -F, 'NR==1{split($0,arr,/,/);next} {SEP=""; fld=1;
    for (x in arr){printf ($0 ~ arr[x])?SEP""$(fld++):",0";SEP=","};print ""}')

出力:

A,1,0,1
B,20,0,4
C,30,1,0
D,0,13,0
E,0,15,2
F,0,19,0
G,0,0,6
H,0,0,8

コード分​​析:

awk -F, '{ seen[$1]=seen[$1]","FILENAME":"$2; }
    END{ print HEADER; for (x in seen) print x seen[x] }' R{1..3}.csv

コードの主要部分は、すべてのファイルの2番目の列をすべて1つに連結し、同じ最初の列を持つファイルに属する値を印刷します。以下は、キーが最初の列で値が追加モードの配列seen名です。,FILENAME:$2

Inは、seen[$1]=seen[$1]","FILENAME":"$2;カンマを印刷し、,その後に現在処理されているFILENAMEファイルを印刷することを意味します。アッ、コロン:の後に2番目の列の値が続きます$2(最初の列が同じ場合)。seen[$1]=...同じキーインデックスに追加され、=seen[$1]...同じキー値に保存されます。

このEND声明は、アッこのブロックはすべてのレコード/行を読み取ると最終的に実行され、forループを使用して配列を繰り返します。ボン印刷して最初とコア値次から。

結果は次のとおりです。

A,R1.csv:1,R3.csv:1
B,R1.csv:20,R3.csv:4
C,R1.csv:30,R2.csv:1
D,R2.csv:13
E,R2.csv:15,R3.csv:2
F,R2.csv:19
G,R3.csv:6
H,R3.csv:8

さて、既存の値がどのファイルから来たのか、どのファイルにこのデータがないのかがわかります。存在しないファイルをデータで埋めるために0シェルコマンドを使用しました。すべてのファイル名を含むヘッダー行の生成そして配信アッ〜のようにHEADER -V可変的な:

awk -v HEADER="$(printf ",%s:" R{1..3}.csv)" ...

HEADER後でこの行を使用します0。現在の入力形式は次のとおりです。

$ awk -v HEADER="$(printf ",%s:" R{1..3}.csv)" -F, '
    { seen[$1]=seen[$1]","FILENAME":"$2; }
    END { print HEADER; for (x in seen) print x seen[x]}' R{1..3}.csv 
,R1.csv:,R2.csv:,R3.csv:
A,R1.csv:1,R3.csv:1
B,R1.csv:20,R3.csv:4
C,R1.csv:30,R2.csv:1
D,R2.csv:13
E,R2.csv:15,R3.csv:2
F,R2.csv:19
G,R3.csv:6
H,R3.csv:8

次に、以下の他のものを使用しました。アッ終了しないファイルデータを埋めるスクリプト、0質問に対する他の回答からこのデータをコピーしました。「列に基づいて書式設定し、不足しているデータを埋める」

... |awk -F, 'NR==1{split($0,arr,/,/);next} {SEP=""; fld=1;
    for (x in arr){printf ($0 ~ arr[x])?SEP""$(fld++):",0";SEP=","};print ""}'

最後に、sed 's/,R[1-9]\+\.csv:/,/g'結果の既存のファイル名を単一のコンマに変更するために使用します,

答え2

見事に使うミラー、あなたはできます

mlr --csv --implicit-csv-header put '$f=FILENAME;$f=sub($f,"\..+","")' then \
label Taxa then \
reshape -s f,2 then \
unsparsify then \
fill-empty -v 0 then \
sort-within-records then \
reorder -f Taxa *.csv

得るために

Taxa,R1,R2,R3
A,1,0,1
B,20,0,4
C,30,1,0
D,0,13,0
E,0,15,2
F,0,19,0
G,0,0,6
H,0,0,8

答え3

これは痛いです。

 join -t, -j1 -a1 -e0 -o auto r1.csv r2.csv > r12a.csv
 join -t, -j1 -a2 -e0 -o auto r1.csv r2.csv > r12b.csv
 sort -u r12?.csv > r12.csv
 join -t, -j1 -a1 -e0 -o auto r12.csv r3.csv > r123a.csv
 join -t, -j1 -a2 -e0 -o auto r12.csv r3.csv > r123b.csv
 sort -u r123{a,b}.csv
  • 最初の接続は、ファイルx()にペアのない値を出力します。-axデフォルトは(-e0-o autoで、接続にゼロを印刷するように指示します。
  • sort -u独自の記録を分類して維持します。

awkコードが読みやすくなるかどうかはわかりません。

答え4

それを処理する方法はいくつかありますコマンドラインのCSV、またはArchemarの答え。しかし、あなたの要件のためにPythonを使用することをお勧めします。私はこのスクリプトをPython 3.5でテストしましたが、トリックを実行するか、少なくとも良い起動を提供する必要があります。

import os,re,argparse
import csv

parser = argparse.ArgumentParser(description='join csvs with rows of the \
        form \w+,[1-9], inserting a zero for a row label if it does not \
        exist.')
parser.add_argument('infiles', type=str, help='infile names', nargs='+')
args = parser.parse_args()

d = {}
file_idx = 0
for infile in args.infiles:
    with open(infile, 'r') as f:
        for line in f:
            parsed_line = re.match('(\w+),([0-9]+)', line)
            if not parsed_line:
                print("line {} not parsed in file {}".format(line, infile))
                continue
            if parsed_line.group(1) in d:
                d[parsed_line.group(1)].append(parsed_line.group(2))
            else:
                l = [0]*(file_idx)
                l.append(parsed_line.group(2))
                d[parsed_line.group(1)]=l
        for k in d:
            if (len(d[k]) == file_idx):
                d[k].append(0)
            if not(len(d[k]) == file_idx+1):
                print("problem with file {}, dict {}, key {}".format(f,d,k))
    file_idx = file_idx + 1

## output time
with open('results.csv','w') as csvfile:
    cwriter = csv.writer(csvfile)
    header = [os.path.splitext(x)[0] for x in args.infiles]
    header.insert(0,'Taxa')
    cwriter.writerow(header)
    for k in sorted(d.keys()):
        d[k].insert(0,k)
        cwriter.writerow(d[k])

関連情報