Подтвердить что ты не робот

Объединение столбцов из нескольких файлов CSV в один файл

У меня есть куча CSV файлов (только два в примере ниже). Каждый файл CSV имеет 6 столбцов. Я хочу зайти в каждый файл CSV, скопировать первые два столбца и добавить их в качестве новых столбцов в существующий файл CSV.

До сих пор я:

import csv

f = open('combined.csv')
data = [item for item in csv.reader(f)]
f.close()

for x in range(1,3): #example has 2 csv files, this will be automated
    n=0
    while n<2:
        f=open(str(x)+".csv")
        new_column=[item[n] for item in csv.reader(f)]
        f.close()
        #print d

        new_data = []

        for i, item in enumerate(data):
            try:
                item.append(new_column[i])
                print i
            except IndexError, e:
                item.append("")
            new_data.append(item)

        f = open('combined.csv', 'w')
        csv.writer(f).writerows(new_data)
        f.close()
        n=n+1

Это работает, это не очень, но это работает. Однако у меня есть три незначительных раздражения:

  • Я открываю каждый файл CSV дважды (один раз для каждого столбца), который вряд ли является элегантным

  • Когда я печатаю файл combined.csv, он печатает пустую строку после каждой строки?

  • Я должен предоставить файл combined.csv, который содержит по крайней мере столько строк в нем, как самый большой файл, который у меня может быть. Так как я действительно не знаю, что это за число, это что-то отстойное

Как всегда, любая помощь очень ценится!!

В соответствии с запросом: 1.csv выглядит как (mock data)

1,a
2,b
3,c
4,d

2.csv выглядит как

5,e
6,f
7,g
8,h
9,i

файл comb.csv должен выглядеть как

1,a,5,e
2,b,6,f
3,c,7,g
4,d,8,h
,,9,i
4b9b3361

Ответ 1

import csv
import itertools as IT

filenames = ['1.csv', '2.csv']
handles = [open(filename, 'rb') for filename in filenames]    
readers = [csv.reader(f, delimiter=',') for f in handles]

with  open('combined.csv', 'wb') as h:
    writer = csv.writer(h, delimiter=',', lineterminator='\n', )
    for rows in IT.izip_longest(*readers, fillvalue=['']*2):
        combined_row = []
        for row in rows:
            row = row[:2] # select the columns you want
            if len(row) == 2:
                combined_row.extend(row)
            else:
                combined.extend(['']*2)
        writer.writerow(combined_row)

for f in handles:
    f.close()

Линия for rows in IT.izip_longest(*readers, fillvalue=['']*2): может быть понято с помощью примера:

In [1]: import itertools as IT

In [2]: readers = [(1,2,3), ('a','b','c','d'), (10,20,30,40)]

In [3]: list(IT.izip_longest(readers[0], readers[1], readers[2]))
Out[3]: [(1, 'a', 10), (2, 'b', 20), (3, 'c', 30), (None, 'd', 40)]

Как вы можете видеть, IT.izip_longest ведет себя очень похоже на zip, за исключением того, что он не останавливается до тех пор, пока не будет уничтожен самый длинный истребитель, Он по умолчанию заполняет отсутствующие элементы с помощью None.

Теперь, что произойдет, если в readers было более 3 элементов? Мы хотели бы написать

list(IT.izip_longest(readers[0], readers[1], readers[2], ...))

но это было трудоемким, и если бы мы не знали len(readers) заранее, мы бы даже не смогли заменить многоточие (...) чем-то явным.

У Python есть решение для этого: синтаксис звездочки (иначе говоря, распаковка):

In [4]: list(IT.izip_longest(*readers))
Out[4]: [(1, 'a', 10), (2, 'b', 20), (3, 'c', 30), (None, 'd', 40)]

Обратите внимание, что результат Out[4] идентичен результату Out[3].

*readers сообщает Python распаковывать элементы в readers и отправлять их как отдельные аргументы в IT.izip_longest. Так Python позволяет отправлять произвольное количество аргументов функции.

Ответ 2

В эти дни кажется почти обязательным для кого-то дать pandas решение любой проблемы обработки данных в Python. Итак, здесь мое:

import pandas as pd

to_merge = ['{}.csv'.format(i) for i in range(4)]
dfs = []
for filename in to_merge:
    # read the csv, making sure the first two columns are str
    df = pd.read_csv(filename, header=None, converters={0: str, 1: str})
    # throw away all but the first two columns
    df = df.ix[:,:1]
    # change the column names so they won't collide during concatenation
    df.columns = [filename + str(cname) for cname in df.columns]
    dfs.append(df)

# concatenate them horizontally
merged = pd.concat(dfs,axis=1)
# write it out
merged.to_csv("merged.csv", header=None, index=None)

который для файлов

~/coding/pand/merge$ cat 0.csv 
0,a,6,5,3,7
~/coding/pand/merge$ cat 1.csv 
1,b,7,6,7,0
2,c,0,1,8,7
3,d,6,8,4,5
4,e,8,4,2,4
~/coding/pand/merge$ cat 2.csv 
5,f,6,2,9,1
6,g,0,3,2,7
7,h,6,5,1,9
~/coding/pand/merge$ cat 3.csv 
8,i,9,1,7,1
9,j,0,9,3,9

дает

In [21]: !cat merged.csv
0,a,1,b,5,f,8,i
,,2,c,6,g,9,j
,,3,d,7,h,,
,,4,e,,,,

In [22]: pd.read_csv("merged.csv", header=None)
Out[22]: 
    0    1  2  3   4    5   6    7
0   0    a  1  b   5    f   8    i
1 NaN  NaN  2  c   6    g   9    j
2 NaN  NaN  3  d   7    h NaN  NaN
3 NaN  NaN  4  e NaN  NaN NaN  NaN

который я считаю правильным выравниванием.

Ответ 3

Вот программа, которую я написал, чтобы решить вашу проблему. Он создает класс, который содержит информацию о каждом файле CSV для чтения, включая те столбцы, которые вы хотите от него. Тогда есть просто список файлов CSV для чтения, и строка читается из каждого.

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

Кроме того, этой программе требуется только одна строка за раз в памяти. Таким образом, он может обрабатывать даже большие CSV файлы, не требуя большой памяти.

Первоначально у меня было фиктивное значение -1 для отсутствующих данных. Теперь я вижу, что вы добавили пример, и вы просто не хотите значения. Я изменил программу на использование -1 для использования пустой строки, когда нет данных.

Одной из целей дизайна было сделать это возможным. Прямо сейчас вам нужны первые два столбца, но что, если вам понадобится столбцы 0, 3 и 7 из одного из файлов? Поэтому каждый файл имеет список с столбцами, которые нужно взять.

Я действительно не писал код для переименования выходного файла в исходное имя файла, но это легко добавить.

В идеале вся эта вещь была бы завершена в класс, где вы можете повторять экземпляр класса и возвращать одну строку, собранную вместе, используя столбцы из всех входных файлов. Я не занимал дополнительное время, чтобы сделать это, но если вы будете использовать это в долгосрочной перспективе, вы, возможно, захотите это сделать. Кроме того, я никогда не беспокоился о закрытии любого из входных файлов, так как я полагаю, что программа закончится после того, как мы напишем выходной файл, и тогда все будет закрыто; но в идеале мы должны закрыть все файлы после их использования!

import csv

fname_in = "combined.csv"
fname_out = "combined.tmp"

lst_other_fnames = [str(x) + ".csv" for x in range(1, 3)]

no_data = ''
def _no_data_list(columns):
    return [no_data for _ in columns]

class DataCsvFile(object):
    def __init__(self, fname, columns=None):
        self.fname = fname
        self.f = open(fname)
        self.reader = csv.reader(self.f)
        self.columns = columns
        self.done = False
    def next_columns(self):
        if self.done:
            return _no_data_list(self.columns)

        try:
            item = next(self.reader)
        except StopIteration:
            self.done = True
            return _no_data_list(self.columns)

        return [item[i] for i in self.columns]

# want all columns from original file
data_csv_files = [DataCsvFile(fname_in, range(5))]

# build list of filenames and columns: want first two columns from each
data_csv_files.extend(DataCsvFile(fname, range(2)) for fname in lst_other_fnames)


with open(fname_out, "w") as out_f:
    writer = csv.writer(out_f)

    while True:
        values = []
        for df in data_csv_files:
            columns = df.next_columns()
            values.extend(columns)
        if not all(df.done for df in data_csv_files):
            writer.writerow(values)
        else:
            break

Ответ 4

Вот пример (я использую string io вместо файлов для простоты, но это не существенно):

a = u"""
1,a
2,b
3,c
4,d
"""
b = u"""
5,e
6,f
7,g
8,h
9,i
"""
c = u"""
11,x
12,y
13,z
"""

import io, csv, itertools

data = []
expand = lambda it, size: it + [[''] * len(it[0])] * size

for f in [a, b, c]:
    with io.StringIO(f.strip()) as fp:
        d = list(csv.reader(fp))
        t = len(d) - len(data)
        data = d if not data else [
            x + y for x, y in itertools.izip_longest(
                expand(data, t), expand(d, -t))]

for r in data:
    print ','.join(r)    

# 1,a,5,e,11,x
# 2,b,6,f,12,y
# 3,c,7,g,13,z
# 4,d,8,h,,
# ,,9,i,,

с реальными файлами (с именем 1.csv, 2.csv и т.д.) основной цикл будет выглядеть следующим образом:

for n in range(...):
    with open(str(n) + '.csv') as fp:
        d = list(csv.reader(fp))
        t = len(d) - len(data)
        data = d if not data else [
            x + y for x, y in itertools.izip_longest(
                expand(data, t), expand(d, -t))]