부분적으로 일치하는 키 열이 있는 여러 CSV 파일 병합

부분적으로 일치하는 키 열이 있는 여러 CSV 파일 병합

100개의 파일이 있고 csv각 파일에는 2개의 열이 포함되어 있습니다. 첫 번째는 이고 taxonomy, 두 번째는 입니다 counts. 각 파일에는 약 10,000줄이 있습니다. 각 파일의 데이터는 taxonomy부분적으로만 공유되며 총 약 50,000개의 고유 값이 있습니다. taxa한 파일에서 누락된 항목 에 값이 할당된 테이블로 병합해야 합니다 0. 결과는 50,000개의 행과 101개의 열이 있는 테이블이어야 합니다 csv. tsv단순화된 예는 다음과 같습니다. 파일 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

코드의 주요 부분은 모든 파일의 두 번째 열을 모두 하나로 연결하고 동일한 첫 번째 열이 있는 파일에 속한 값을 인쇄합니다. 다음은 키가 첫 번째 열이고 값이 추가 모드 seen인 배열 이름입니다 .,FILENAME:$2

In은 seen[$1]=seen[$1]","FILENAME":"$2;쉼표를 인쇄하고 ,그 뒤에 현재 처리된 FILENAME파일을 인쇄하는 것을 의미합니다., 콜론 :뒤에 두 번째 열 값이 옵니다 $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

자, 이제 기존 값이 어떤 파일에서 왔는지, 어떤 파일에 이 데이터가 없는지 알 수 있습니다. 존재하지 않는 파일을 data 로 채우기 위해 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이며 연결에 0을 인쇄하도록 지시합니다.
  • 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])

관련 정보