使用Python数据框架高效地将数百万行数据写入文件

4
我有以下代码片段,可以将CSV读入数据帧,并以符合Redis协议的方式将键值对写入文件,即SET key1 value1。这个代码是零散的,我试图使用多进程,但不确定它的性能(收益)。
CSV大约有600万行,读入数据框非常快(少于2分钟)。输出文件有1200万行(每个输入文件的行数为2)。这需要约50分钟才能完成。我的代码中是否有任何部分可以优化/更改,以使其运行更快?一旦文件完成,将其加载到Redis只需不到90秒。瓶颈确实在于写文件上。我将有几个这样的文件要写,每个文件耗费50-60分钟真的不理想。这个特定的数据集有600万行和10列,主要由字符串组成,有一些浮点列。Redis键是字符串,浮点值是键值对中的Redis值。其他数据集的大小都类似,甚至更大(与行和列有关)。
我正在研究如何将我生成的所有字符串加载到数据帧中,然后使用to_csv()函数将其转储到文件中,但我不确定其性能如何。
filepath = '/path/to/file.csv'

def df_to_file:
    df = pd.read_csv(filepath)
    f = open('output_file', 'w')
    for i in range(len(df.index)):
        if df['col1'].iloc[i] != '':
            key1 = str(df['col1'].iloc[i])+str(df['col4'].iloc[i])+str(df['col5'].iloc[i])+...+str(df['col_n'].iloc[i])
            val1 = df['col_n+1'].iloc[i]

            key1a = str(df['col1'].iloc[i])+str(df['col4'].iloc[i])+str(df['col5'].iloc[i])+...+str(df['col_n'].iloc[i])
            val1a = df['col_n+2'].iloc[i]

            print('SET {0} {1}\nSET {0} {1}'.format(key1, val1, key1a, val1a), file = f)

        if df['col2'].iloc[i] != '':
            key1 = str(df['col2'].iloc[i])+str(df['col4'].iloc[i])+str(df['col5'].iloc[i])+...+str(df['col_n'].iloc[i])
            val1 = df['col_n+1'].iloc[i]

            key1a = str(df['col2'].iloc[i])+str(df['col4'].iloc[i])+str(df['col5'].iloc[i])+...+str(df['col_n'].iloc[i])
            val1a = df['col_n+2'].iloc[i]

            print('SET {0} {1}\nSET {0} {1}'.format(key1, val1, key1a, val1a), file = f)
        if df['col3'].iloc[i] != '':
            key1 = str(df['col3'].iloc[i])+str(df['col4'].iloc[i])+str(df['col5'].iloc[i])+...+str(df['col_n'].iloc[i])
            val1 = df['col_n+1'].iloc[i]

            key1a = str(df['col3'].iloc[i])+str(df['col4'].iloc[i])+str(df['col5'].iloc[i])+...+str(df['col_n'].iloc[i])
            val1a = df['col_n+2'].iloc[i]

            print('SET {0} {1}\nSET {0} {1}'.format(key1, val1, key1a, val1a), file = f)
    f.close()

p = Process(target = df_to_file)
p.start()
p.join() 

不要使用循环,至少不要在循环中使用iloc[i]来提取单行数据,这将会极大地降低性能。除非您提供一个小的代表性输出数据框的示例,否则很难再做更多说明。 - juanpa.arrivillaga
顺便问一下,你用的是哪个版本的Python? - juanpa.arrivillaga
@juanpa.arrivillaga - 这里没有输出数据框架。输入是一个数据框架,输出是文件。这里提到的数字也是真实存在的,而不是假设或推断出来的。使用Python 2.7。 - CodingInCircles
不,我的意思是“举一个你想要输出到文件的数据框的例子”。也就是说,df看起来像什么? - juanpa.arrivillaga
你能展示一下如何从DataFrame中选择key1val1吗? - relay
此外,这些变量 key1, val1 = string1, string2 是从哪里来的?请提供一个 [mcve]。 - juanpa.arrivillaga
1个回答

2
使用类似于df['col1'].loc[...]这样的结构来循环遍历每一行会很慢,ilocloc基于选择器是用于在整个数据框中进行选择,并且与索引对齐相关的许多操作将导致每行的开销很高。相反,仅需使用df.itertuples()来迭代每一行将会更快。

def df_to_file:
    df = pd.read_csv(filepath)
    f = open('output_file', 'wb') # writing in binary mode should be faster, if it is possible without unicode problems
    for row in df.itertuples():
        if row.col1:
            key1, val1 = string1, string2
            key1a, val1a = string1a, string2a
            print('SET {0} {1}\nSET {0} {1}'.format(key1, val1, key1a, val1a), file = f)
        if row.col2:
            key1, val1 = string1, string2
            key1a, val1a = string1a, string2a
            print('SET {0} {1}\nSET {0} {1}'.format(key1, val1, key1a, val1a), file = f)
        if row.col3:
            key1, val1 = string1, string2
            key1a, val1a = string1a, string2a
            print('SET {0} {1}\nSET {0} {1}'.format(key1, val1, key1a, val1a), file = f)
    f.close()

这可能是你可以做的最基本的优化。如果你详细描述你正在做什么,也许可以找到矢量化的解决方案。
此外,不要在multiprocessing中使用以上方法。
另外,如上所述,'SET {0} {1}\nSET {0} {1}'.format(key1, val1, key1a, val1a)将始终相同。如果这些参数不变,则只需在循环外部进行字符串连接一次,并在循环中重复使用整个字符串。
编辑:似乎你不能在上面这样做。然而,考虑到:
这个数据集有600万行和10列,大部分由字符串组成,有几列是浮点数列。Redis键是字符串,浮点值是键值对中的Redis值。
那么只需key1 = ''.join(row.col1, row.col4, row.col5, ...)。不要使用str和+运算符,这非常低效,因为你暗示这些列已经是字符串了。如果你必须对所有这些列调用str,请使用map(str,...)。
最后,如果你真的需要挤出性能,注意row将是命名元组对象,它们是元组,并且你可以使用基于整数的索引而不是基于属性的标签访问,即row[1]而不是row.col1(注意,row[0]将是row.index,即索引),这应该更快(因为你在每次迭代中进行数十次元组索引,并进行数百万次迭代)。

我刚刚编辑了问题,请看一下并更新你的答案(如果需要)。谢谢! - CodingInCircles
@CodingInCircles 请提供一个具体的例子。无论如何,是的,你的字符串拼接非常低效。使用 ''.join 替代 str(x) + str(y) + ... + str(z) - juanpa.arrivillaga
谢谢!我会尝试这些更改并告诉您它的效果如何。 - CodingInCircles
哇!你的建议很有帮助,它指数级加速了!1200万条记录在不到1分钟内被写入!非常感谢你! - CodingInCircles
我在Code Review SE上问了同样的问题,本以为能更快地得到答案,但结果却是在这里得到了。如果您在那里写下这个答案,我可以将其标记为被接受的答案。这是链接:https://codereview.stackexchange.com/questions/187220/write-millions-of-lines-to-a-file-python-dataframes-and-redis。 - CodingInCircles

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接