【问题标题】:Pandas append perfomance concat/append using "larger" DataFramesPandas 使用“更大”的 DataFrames 追加性能 concat/append
【发布时间】:2015-08-06 16:06:41
【问题描述】:

问题:我将数据存储在 csv 文件中,其中包含以下列 data/id/value。我有 15 个文件,每个文件包含大约 10-20mio 行。每个 csv 文件涵盖一个不同的时期,因此时间索引不重叠,但列是(新的 id 不时输入,旧的消失)。我最初所做的是在没有枢轴调用的情况下运行脚本,但后来我在本地机器上遇到了内存问题(只有 8GB)。由于每个文件中有很多冗余,pivot 起初似乎是一个不错的出路(大约减少 2/3 数据),但现在性能开始发挥作用。如果我运行以下脚本,concat 函数将“永远”运行(我总是打断一段时间后手动到目前为止(2h>))。 Concat/append 似乎在大小方面有限制(我大约有 10000-20000 列),或者我在这里错过了什么?有什么建议吗?

import pandas as pd
path = 'D:\\'
data = pd.DataFrame()
#loop through list of raw file names
for file in raw_files:
    data_tmp = pd.read_csv(path + file, engine='c',
                           compression='gzip',
                           low_memory=False,
                           usecols=['date', 'Value', 'ID'])
    data_tmp = data_tmp.pivot(index='date', columns='ID',
                              values='Value')

    data = pd.concat([data,data_tmp])
    del data_tmp

编辑 I:澄清一下,每个 csv 文件有大约 10-20mio 行和三列,在应用透视后,这减少到大约 2000 行,但导致 10000 列。

我可以通过简单地将完整的 id 集拆分为子集并基于每个子集运行所需的计算来解决内存问题,因为它们对于每个 id 都是独立的。我知道这让我重新加载相同的文件 n 次,其中 n 是使用的子集的数量,但这仍然是合理的快。我仍然想知道为什么 append 没有执行。

编辑二:我试图通过模拟重新创建文件结构,它尽可能接近实际的数据结构。我希望很清楚,我没有花太多时间最小化模拟时间,但它在我的机器上运行得相当快。

import string
import random
import pandas as pd
import numpy as np
import math

# Settings :-------------------------------
num_ids = 20000
start_ids = 4000
num_files = 10
id_interval = int((num_ids-start_ids)/num_files)
len_ids = 9
start_date = '1960-01-01'
end_date = '2014-12-31'
run_to_file = 2
# ------------------------------------------

# Simulation column IDs
id_list = []
# ensure unique elements are of size >num_ids
for x in range(num_ids + round(num_ids*0.1)):
    id_list.append(''.join(
        random.choice(string.ascii_uppercase + string.digits) for _
        in range(len_ids)))
id_list = set(id_list)
id_list = list(id_list)[:num_ids]

time_index = pd.bdate_range(start_date,end_date,freq='D')
chunk_size =  math.ceil(len(time_index)/num_files)

data = []
#  Simulate files
for file in range(0, run_to_file):
    tmp_time = time_index[file * chunk_size:(file + 1) * chunk_size]
    # TODO not all cases cover, make sure ints are obtained
    tmp_ids = id_list[file * id_interval:
        start_ids + (file + 1) * id_interval]

    tmp_data = pd.DataFrame(np.random.standard_normal(
        (len(tmp_time), len(tmp_ids))), index=tmp_time,
        columns=tmp_ids)

    tmp_file = tmp_data.stack().sortlevel(1).reset_index()
    # final simulated data structure of the parsed csv file
    tmp_file = tmp_file.rename(columns={'level_0': 'Date', 'level_1':
                                        'ID', 0: 'Value'})

    # comment/uncomment if pivot takes place on aggregate level or not
    tmp_file = tmp_file.pivot(index='Date', columns='ID',
                              values='Value')
    data.append(tmp_file)

data = pd.concat(data)
# comment/uncomment if pivot takes place on aggregate level or not
# data = data.pivot(index='Date', columns='ID', values='Value')

【问题讨论】:

  • 如果你在一个循环中追加或连接(并且循环是不可避免的),你至少应该将data_tmp追加到一个列表中,然后在循环之后一次性连接它们@ 987654324@
  • 在这种情况下不会产生很大的性能影响,我认为问题的出现是因为数据框的大小(col 和 row 维度)
  • 它会(因为它会阻止复制不断增长的数据帧 15 次),如果它能够运行由于内存。但我并没有完全关注那里。你说你有 15 倍的 10-20mio 行和 10000-20000 列,这怎么可能适合 8GB 内存? (快速计算表明至少在 TB 中)
  • 为了清楚起见,原始的 csv 文件大约有 10mio 行和 3 列。然后 Pivot 通过将日期/id 信息唯一地存储在行和列索引中来减少冗余。然而,它最终有大约 10000-20000 列。
  • 当我只附加两个更大的透视表时,问题实际上已经出现了。如果我在一个列表中收集这两个数据帧(如建议的那样),连接它们然后只旋转它是相当快的(由于内存限制,不能对完整的文件列表执行此操作),但是如果我首先旋转每个表然后附加/连接它未定义地运行。

标签: python pandas


【解决方案1】:

使用您可重现的示例代码,我确实可以确认只有两个数据帧的 concat 需要很长时间。但是,如果您首先对齐它们(使列名相等),那么连接非常快:

In [94]: df1, df2 = data[0], data[1]

In [95]: %timeit pd.concat([df1, df2])
1 loops, best of 3: 18min 8s per loop

In [99]: %%timeit
   ....: df1b, df2b = df1.align(df2, axis=1)
   ....: pd.concat([df1b, df2b])
   ....:
1 loops, best of 3: 686 ms per loop

两种方法的结果是一样的。
对齐相当于:

common_columns = df1.columns.union(df2.columns)
df1b = df1.reindex(columns=common_columns)
df2b = df2.reindex(columns=common_columns)

因此,当必须处理完整的数据帧列表时,这可能是更容易使用的方法。

pd.concat 比较慢的原因是它做的更多。例如。当列名不相等时,它会检查每一列是否必须向上转换 dtype 或不保存 NaN 值(通过对齐列名来引入)。通过调整自己,你可以跳过这个。但在这种情况下,您肯定拥有所有相同的 dtype,这没问题。
这么慢也让我感到惊讶,但我会就此提出一个问题。

【讨论】:

  • 谢谢。我想到了两件事:首先,当您将 id_list 替换为整数索引时(只需添加 id_list = range(0,num_ids) 而不是模拟的 id,它会产生巨大的性能影响。其次,我意识到我在原始数据中也有一些错误代码,所以dtypes 是对象类型,清理数据并仅使用浮点数也会增加更大的因素.您建议的对齐也会提高性能,但只有当您使用整数列时列或字符串类型不再那么多时.也许有人验证了这一发现,在 pandas 中可能值得改进
  • 你有机会看看我的评论吗?
【解决方案2】:

总结,取决于设置的三个关键性能驱动因素:

1) 连接两个数据帧时确保数据类型相同

2) 尽可能使用基于整数的列名

3) 使用基于字符串的列时,请确保按照 joris 的建议在调用 concat 之前使用 align 方法

【讨论】:

    【解决方案3】:

    正如@joris 提到的,您应该将所有数据透视表附加到一个列表中,然后一次性将它们全部连接起来。这是对您的代码的建议修改:

    dfs = []
    for file in raw_files:
        data_tmp = pd.read_csv(path + file, engine='c',
                               compression='gzip',
                               low_memory=False,
                               usecols=['date', 'Value', 'ID'])
        data_tmp = data_tmp.pivot(index='date', columns='ID',
                                  values='Value')
        dfs.append(data_tmp)
    del data_tmp
    data = pd.concat(dfs)
    

    【讨论】:

      猜你喜欢
      • 2015-03-11
      • 2020-08-09
      • 2014-05-21
      • 2022-09-23
      • 1970-01-01
      • 2015-08-03
      • 2016-08-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多