【问题标题】:How do I transpose/pivot a csv file with python *without* loading the whole file into memory?如何在不*将整个文件加载到内存的情况下使用 python 转置/旋转 csv 文件?
【发布时间】:2011-08-23 05:13:22
【问题描述】:

对于我的一个数据分析管道,我最终生成了许多单独的 CSV 文件。我想转置它们,连接它们,然后再次转置它们。但是数据量很大,全部加载到内存中并不实用。

【问题讨论】:

  • 你在使用numpy吗?有多少数据?
  • 只是为了确定,你所说的连接是什么意思?如果要连接 A 和 B,您是否希望第一行是 a11,a12,..,a1n,b11,b12,..b1n?
  • 所以你想将它们逐行连接?
  • 您的列/字段是否有固定的宽度/大小?

标签: python csv


【解决方案1】:

连接两个 csv 文件中的数据行(如果您是这个意思)而不将它们都加载到内存中是一个相对简单且快速的操作:只需从每个文件中读取一行,然后将它们连接在一起,然后将其写入输出文件,重复直到所有输入数据用完为止。

在 csv 文件中转置数据而不是将整个内容读入内存本质上是一个慢得多的过程,因为它需要多次重新读取整个输入文件,每次只从一个文件中提取数据它包含的列。如果这是一个可以接受(或必要)的折衷方案,这基本上是使用内置 csv 模块完成的方法:

import csv

input_filename = 'input.csv'
output_filename = 'output.csv'

with open(output_filename, 'wb') as outputf:
    writer = csv.writer(outputf)
    with open(input_filename, 'rb') as inputf:
        # determine number of columns in input file by counting those in its first row
        # number of cols in input file determines number of rows in output file
        numcols = len(csv.reader(inputf).next())
        # read entire input file multiple times, extracting one column from each row
        for col_index in xrange(numcols):
            # write all of column data as a single row of the output file
            inputf.seek(0)  # rewind file for each pass
            writer.writerow(tuple(row[col_index] for row in csv.reader(inputf)))

【讨论】:

  • 如果列/字段有固定的宽度/大小,那么他可以使用 seek 而不是多次循环文件。
  • @tommy.carstensen:对于固定宽度字段的特殊情况,不清楚对每个字段或行进行查找是否会比仅对整个文件进行一次查找更快一开始。此外,对于固定宽度的行,将我的answer 调整到另一个基于使用struct 模块而不是csv 模块解析文件的问题似乎会更快。 然而,OP 对固定宽度字段只字未提。
  • 您的其他答案是一个很好的参考。感谢分享。
  • 我在尝试使用上面的 csv 解决方案时收到此错误: Traceback(最近一次调用最后一次):文件“python.py”,第 16 行,在 writer.writerow(tuple(row [col_index] for row in csv.reader(inputf))) 文件“python.py”,第 16 行,在 writer.writerow(tuple(row[col_index] for row in csv.reader(inputf))) _csv .Error:字段大于字段限制(131072)
  • @tommy.carstensen:我在发布代码之前对其进行了测试,验证它是否有效。听起来您可能有一个格式错误的输入文件,其中缺少字段或其他内容之间的分隔符。您应该能够打印生成器表达式返回的每个项目,并确定文件中发生这种情况的位置。
【解决方案2】:

以下代码模拟读取两个 csv 文件。第一个有两个 行

[1,2,1]
[3,4,1]

第二个

[7,8,2]
[9,10.2].

结果是两行

[1,2,1,7,8,2]
[3,4,1,9,10,2]

这就是你想要的吗?

def source1():
    for i in [ [1,2, 1] ,[3,4, 1]] : yield i

def source2():
    for i in [ [7,8,2] ,[9,10,2]] : yield i

def join(*sources):
    while True:
        row = []
        for s in sources:
            row.extend(s.next())
        yield row

for row in join(source1(), source2()):
    print row

在您的情况下,您必须用 csv 文件迭代器替换对 source1() 和 source2() 的调用。

【讨论】:

    【解决方案3】:

    当字段具有固定宽度时,这是一个可行的解决方案:

    import sys
    import os
    
    
    def main():
    
        path_in = sys.argv[-1]
        path_out = os.path.basename(path_in)+'.transposed'
    
        with open(path_in) as fd_in:
            line = fd_in.readline()
            l = line.split()
            field_width = int(len(line)/len(l))
    
        file_size = os.path.getsize(path_in)
        cols2 = rows1 = line_count = int(file_size/len(line))
        rows2 = cols1 = len(l)
    
        with open(path_in) as fd_in, open(path_out, 'w') as fd_out:
            for row in range(rows2):
                for col in range(cols2-1):
                    fd_in.seek(col*len(line)+row*field_width)
                    fd_out.write('{} '.format(fd_in.read(field_width-1)))
                fd_in.seek((col+1)*len(line)+row*field_width)
                fd_out.write('{}\n'.format(fd_in.read(field_width-1)))
    
        return
    
    
    if __name__ == '__main__':
        main()
    

    如果字段没有固定宽度,这是一个可行的解决方案:

    import sys
    import os
    
    
    def main():
    
        path_in = sys.argv[-1]
        path_out = os.path.basename(path_in)+'.transposed'
        separator = ' '
    
        d_seek = {}
        with open(path_in) as fd_in:
            i = 0
            while True:
                tell = fd_in.tell()
                if fd_in.readline() == '':
                    break
                d_seek[i] = tell
                i += 1
        cols2 = rows1 = i
    
        with open(path_in) as fd_in:
            line = fd_in.readline()
        rows2 = cols1 = len(line.split(separator))
        del line
    
        with open(path_in) as fd_in, open(path_out, 'w') as fd_out:
            for row2 in range(rows2):
                for row1 in range(rows1):
                    fd_in.seek(d_seek[row1])
                    j = 0
                    s = ''
                    while True:
                        char = fd_in.read(1)
                        j += 1
                        if char == separator or char == '\n':
                            break
                        s += char
                    d_seek[row1] += len(s)+1
                    if row1+1 < rows1:
                        fd_out.write('{} '.format(s))
                    else:
                        fd_out.write('{}\n'.format(s))
    
        return
    
    
    if __name__ == '__main__':
        main()
    

    【讨论】:

      【解决方案4】:

      另一个简短的 Pythonic 解决方案。我用它来转置 15,000,000 x 12,000 的 CSV。它是快速而纯正的python。您需要做的其他一切都是微不足道的,这绝对是最难的部分。

      Github 链接:https://gist.github.com/arose13/facfb91b609d453f3ad840417faa503a

          def transpose_csv_out_of_core(csv_path, output_csv_path='transposed.csv', delimiter=','):
          """
          On my laptop it can transpose at ~375,000 lines a sec
      
          :param csv_path: 
          :param output_csv_path: 
          :param delimiter: 
          :return: 
          """
          import csv
      
          transposed_iterator = zip(*csv.reader(open(csv_path)))
          with open(output_csv_path, 'w') as out:
              for row in transposed_iterator:
                  out.write(delimiter.join(row) + '\n')
      

      【讨论】:

        【解决方案5】:

        使用生成器,例如

        from itertools import izip
        
        file1 = open("test", "r")
        file2 = open("test2", "r")
        
        def lazy(file):
            for line in file:
                #do something with the line
                yield line
        
        for lines in izip(lazy(file1), lazy(file2)):
            print lines
        

        http://wiki.python.org/moin/Generators

        编辑:你可以使用 CSV 模块来解析它,我也意识到文件对象的 readlines() 方法不是惰性的,所以你必须使用 for line in file 模式。

        【讨论】:

        • 你可以去 "import csv" 然后去 f1,f2=csv.reader(open('f1.txt','rb'),delimiter=',',quotechar='"' ),csv.reader(open('f2.txt','rb'),delimiter=',',quotechar='"')
        • 是的,我真的不怎么用 CSV 文件,所以我不知道 python 有什么库。
        • csv 模块使用简单。它为你做分裂。例如如果您的 csv 文件使用 line1: firstname,lastname,scores 然后 line2: bob,smith,"98,95,23,99" 的引号字符,那么对于 line2 它将返回 ['bob','smith','99 ,95,23,99']
        • 此外,如果 file1 具有与 file2 相同的字段/列名,则在连接时您将有重复的列名。 (例如,这可能会在插入 mysql 表时导致错误)
        • 这里不需要惰性函数,因为file()遵循迭代器协议。
        猜你喜欢
        • 2011-10-15
        • 2014-09-15
        • 1970-01-01
        • 1970-01-01
        • 2014-03-26
        • 1970-01-01
        • 2013-07-07
        • 1970-01-01
        相关资源
        最近更新 更多