Слияние данных на основе сопоставления первого столбца в Python

В настоящее время у меня есть два набора файлов данных, которые выглядят так:

Файл 1:

test1 ba ab cd dh gf
test2 fa ab cd dh gf
test3 rt ty er wq ee
test4 er rt sf sd sa

и в файле 2:

test1 123 344 123
test1 234 567 787
test1 221 344 566
test3 456 121 677

Я хотел бы объединить файлы, основанные на строках в первом столбце (чтобы "тесты" совпадали)

вот так:

test1 ba ab cd dh gf 123 344 123
test1 ba ab cd dh gf 234 567 787
test1 ba ab cd dh gf 221 344 566
test3 rt ty er wq ee 456 121 677

У меня есть этот код

def combineFiles(file1,file2,outfile):
      def read_file(file):
         data = {}
         for line in csv.reader(file):
            data[line[0]] = line[1:]
         return data
      with open(file1, 'r') as f1, open(file2, 'r') as f2:
         data1 = read_file(f1)
         data2 = read_file(f2)
         with open(outfile, 'w') as out:
            wtr= csv.writer(out)
            for key in data1.keys():
               try:
                  wtr.writerow(((key), ','.join(data1[key]), ','.join(data2[key])))
               except KeyError:
                  pass

Однако результат в конечном итоге выглядит следующим образом:

test1 ba ab cd dh gf 123 344 123
test3 er rt sf sd sa 456 121 677

Может кто-нибудь помочь мне с тем, как сделать вывод, чтобы test1 можно было распечатать все три раза?

Очень признателен

Ответ 1

Хотя я бы порекомендовал подход Брэда Соломона, так как он довольно лаконичен, вам просто нужно внести небольшие изменения в ваш код.

Поскольку ваш второй файл имеет последнее слово, вам просто нужно создать словарь для первого файла. Затем вы можете записать выходной файл, как вы читаете из второго файла, выбирая значения из словаря data1 по ходу дела:

with open(file1, 'r') as f1, open(file2, 'r') as f2:
    data1 = read_file(f1)
    with open(outfile, 'w') as out:
        wtr = csv.writer(out, delimiter=' ')
        for line in csv.reader(f2, delimiter=' '):
            # only write if there is a corresponding line in file1
            if line[0] in data1:
                # as you write, get the corresponding file1 data
                wtr.writerow(line[0:] + data1[line[0]] + line[1:])

Ответ 2

Вы могли бы хотеть, чтобы дать панду библиотеке попробовать; это облегчает такие случаи:

>>> import pandas as pd
>>> pd.merge(df1, df2, on='testnum', how='inner')
  testnum 1_x 2_x 3_x   4   5  1_y  2_y  3_y
0   test1  ba  ab  cd  dh  gf  123  344  123
1   test1  ba  ab  cd  dh  gf  234  567  787
2   test1  ba  ab  cd  dh  gf  221  344  566
3   test3  rt  ty  er  wq  ee  456  121  677

Предполагается, что столбец test называется "testnum".

>>> df1
  testnum   1   2   3   4   5
0   test1  ba  ab  cd  dh  gf
1   test2  fa  ab  cd  dh  gf
2   test3  rt  ty  er  wq  ee
3   test4  er  rt  sf  sd  sa

>>> df2
  testnum    1    2    3
0   test1  123  344  123
1   test1  234  567  787
2   test1  221  344  566
3   test3  456  121  677

Вы прочитали бы это с помощью pd.read_csv().

Ответ 3

Проблема в том, что вы перезаписываете ключи в строке

data[line[0]] = line[1:]

Поскольку ваши файлы имеют неуникальные "ключи", вы можете попробовать вручную сделать их уникальными, используя enumerate:

for ind, line in enumerate(csv.reader(file)):
    unique_key = ''.join([line[0], "_", str(ind)])
    data[unique_key] = line[1:]

Позже, когда вы объединяете результат, вы можете удалить ключи после подчеркивания:

wtr.writerow(((key.split("_")[0], ','.join(data1[key]), ','.join(data2[key])))

На мой вкус, все это очень неуклюже. Если ваша цель - читать, манипулировать и записывать данные в и из CSV файлов, я бы порекомендовал изучить панды, так как этот код можно написать в несколько строк с использованием DataFrames (см. Ответ Брэда Соломона).

Ответ 4

Вы можете попытаться собрать свои элементы в отдельную collections.defaultdict(), а затем получить декартово произведение пересекающихся строк с помощью itertools.product():

from collections import defaultdict
from itertools import product

def collect_rows(file):
    collection = defaultdict(list)

    for line in file:
        col1, *rest = line.split()
        collection[col1].append(rest)

    return collection

with open("file1.txt") as f1, open("file2.txt") as f2, open("output.txt", "w") as out:
    f1_collection = collect_rows(f1)
    f2_collection = collect_rows(f2)

    # Ordered intersection, no need to sort
    set_2 = set(f2_collection)
    intersection = [key for key in f1_collection if key in set_2]

    for key in intersection:
        for x, y in product(f1_collection[key], f2_collection[key]):
            out.write("%s\n" % " ".join([key] + x + y))

Который дает следующий output.txt:

test1 ba ab cd dh gf 123 344 123
test1 ba ab cd dh gf 234 567 787
test1 ba ab cd dh gf 221 344 566
test3 rt ty er wq ee 456 121 677

Примечание: вероятно, легче следовать подходу Брэда Соломона к Пандам, поскольку это можно сделать одной командой.