当前位置: 首页 > 面试题库 >

创建大型Pandas DataFrame:预分配vs追加vs concat

扈瑞
2023-03-14
问题内容

逐块构建大型数据帧时,Pandas的性能令我感到困惑。在Numpy中,通过预分配一个大的空数组然后填充值,我们(几乎)总是可以看到更好的性能。据我了解,这是由于Numpy立即获取其所需的所有内存,而不是每次append操作都必须重新分配内存。

在Pandas中,通过使用该df = df.append(temp)模式,我似乎获得了更好的性能。

这是一个带有计时的例子。Timer该类的定义如下。如您所见,我发现预分配比使用append!慢大约10倍。使用np.empty适当的dtype值预分配数据帧有很大帮助,但是该append方法仍然是最快的。

import numpy as np
from numpy.random import rand
import pandas as pd

from timer import Timer

# Some constants
num_dfs = 10  # Number of random dataframes to generate
n_rows = 2500
n_cols = 40
n_reps = 100  # Number of repetitions for timing

# Generate a list of num_dfs dataframes of random values
df_list = [pd.DataFrame(rand(n_rows*n_cols).reshape((n_rows, n_cols)), columns=np.arange(n_cols)) for i in np.arange(num_dfs)]

##
# Define two methods of growing a large dataframe
##

# Method 1 - append dataframes
def method1():
    out_df1 = pd.DataFrame(columns=np.arange(4))
    for df in df_list:
        out_df1 = out_df1.append(df, ignore_index=True)
    return out_df1

def method2():
# # Create an empty dataframe that is big enough to hold all the dataframes in df_list
out_df2 = pd.DataFrame(columns=np.arange(n_cols), index=np.arange(num_dfs*n_rows))
#EDIT_1: Set the dtypes of each column
for ix, col in enumerate(out_df2.columns):
    out_df2[col] = out_df2[col].astype(df_list[0].dtypes[ix])
# Fill in the values
for ix, df in enumerate(df_list):
    out_df2.iloc[ix*n_rows:(ix+1)*n_rows, :] = df.values
return out_df2

# EDIT_2: 
# Method 3 - preallocate dataframe with np.empty data of appropriate type
def method3():
    # Create fake data array
    data = np.transpose(np.array([np.empty(n_rows*num_dfs, dtype=dt) for dt in df_list[0].dtypes]))
    # Create placeholder dataframe
    out_df3 = pd.DataFrame(data)
    # Fill in the real values
    for ix, df in enumerate(df_list):
        out_df3.iloc[ix*n_rows:(ix+1)*n_rows, :] = df.values
    return out_df3

##
# Time both methods
##

# Time Method 1
times_1 = np.empty(n_reps)
for i in np.arange(n_reps):
    with Timer() as t:
       df1 = method1()
    times_1[i] = t.secs
print 'Total time for %d repetitions of Method 1: %f [sec]' % (n_reps, np.sum(times_1))
print 'Best time: %f' % (np.min(times_1))
print 'Mean time: %f' % (np.mean(times_1))

#>>  Total time for 100 repetitions of Method 1: 2.928296 [sec]
#>>  Best time: 0.028532
#>>  Mean time: 0.029283

# Time Method 2
times_2 = np.empty(n_reps)
for i in np.arange(n_reps):
    with Timer() as t:
        df2 = method2()
    times_2[i] = t.secs
print 'Total time for %d repetitions of Method 2: %f [sec]' % (n_reps, np.sum(times_2))
print 'Best time: %f' % (np.min(times_2))
print 'Mean time: %f' % (np.mean(times_2))

#>>  Total time for 100 repetitions of Method 2: 32.143247 [sec]
#>>  Best time: 0.315075
#>>  Mean time: 0.321432

# Time Method 3
times_3 = np.empty(n_reps)
for i in np.arange(n_reps):
    with Timer() as t:
        df3 = method3()
    times_3[i] = t.secs
print 'Total time for %d repetitions of Method 3: %f [sec]' % (n_reps, np.sum(times_3))
print 'Best time: %f' % (np.min(times_3))
print 'Mean time: %f' % (np.mean(times_3))

#>>  Total time for 100 repetitions of Method 3: 6.577038 [sec]
#>>  Best time: 0.063437
#>>  Mean time: 0.065770

我非常Timer感谢Huy Nguyen:

# credit: http://www.huyng.com/posts/python-performance-analysis/

import time

class Timer(object):
    def __init__(self, verbose=False):
        self.verbose = verbose

    def __enter__(self):
        self.start = time.clock()
        return self

    def __exit__(self, *args):
        self.end = time.clock()
        self.secs = self.end - self.start
        self.msecs = self.secs * 1000  # millisecs
        if self.verbose:
            print 'elapsed time: %f ms' % self.msecs

如果您仍在关注,我有两个问题:

1)为什么该append方法更快?(注意:对于非常小的数据帧,即n_rows = 40,实际上速度较慢)。

2)用块构建大型数据框的最有效方法是什么?(就我而言,这些块都是大型的csv文件)。

谢谢你的帮助!

EDIT_1:在我的真实世界项目中,这些列具有不同的dtype。因此pd.DataFrame(.... dtype=some_type),按照BrenBarn的建议,我无法使用技巧来提高预分配的性能。dtype参数将所有列强制为相同的dtype。问题[4464]](https://github.com/pydata/pandas/issues/4464)

method2()在代码中添加了几行,以更改dtypes逐列以匹配输入数据帧。此操作很昂贵,并且在写入行块时抵消了具有适当dtypes的好处。

EDIT_2:尝试使用占位符array预分配数据框np.empty(... dtyp=some_type)。根据@Joris的建议。


问题答案:

您的基准实际上太小,无法显示出真正的差异。追加,每次复制,因此您实际上是在复制大小为N的存储空间N
*(N-1)次。随着数据框大小的增加,这效率极低。在很小的框架内,这当然可能无关紧要。但是,如果您有任何实际尺寸,这很重要。这在此处的文档中特别指出,尽管有点警告。

In [97]: df = DataFrame(np.random.randn(100000,20))

In [98]: df['B'] = 'foo'

In [99]: df['C'] = pd.Timestamp('20130101')

In [103]: df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 100000 entries, 0 to 99999
Data columns (total 22 columns):
0     100000 non-null float64
1     100000 non-null float64
2     100000 non-null float64
3     100000 non-null float64
4     100000 non-null float64
5     100000 non-null float64
6     100000 non-null float64
7     100000 non-null float64
8     100000 non-null float64
9     100000 non-null float64
10    100000 non-null float64
11    100000 non-null float64
12    100000 non-null float64
13    100000 non-null float64
14    100000 non-null float64
15    100000 non-null float64
16    100000 non-null float64
17    100000 non-null float64
18    100000 non-null float64
19    100000 non-null float64
B     100000 non-null object
C     100000 non-null datetime64[ns]
dtypes: datetime64[ns](1), float64(20), object(1)
memory usage: 17.5+ MB

追加中

In [85]: def f1():
   ....:     result = df
   ....:     for i in range(9):
   ....:         result = result.append(df)
   ....:     return result
   ....:

康卡特

In [86]: def f2():
   ....:     result = []
   ....:     for i in range(10):
   ....:         result.append(df)
   ....:     return pd.concat(result)
   ....:

In [100]: f1().equals(f2())
Out[100]: True

In [101]: %timeit f1()
1 loops, best of 3: 1.66 s per loop

In [102]: %timeit f2()
1 loops, best of 3: 220 ms per loop

请注意,我什至都不会尝试预分配。它有些复杂,特别是因为您要处理多个dtypes(例如,您 可以
制作一个巨大的框架并且简单.loc并且可以工作)。但是pd.concat只是简单,可靠和快速。

并从上方选择尺寸

In [104]: df = DataFrame(np.random.randn(2500,40))

In [105]: %timeit f1()
10 loops, best of 3: 33.1 ms per loop

In [106]: %timeit f2()
100 loops, best of 3: 4.23 ms per loop


 类似资料:
  • 问题内容: 我在div页上: 并在jQuery中: 这为我产生了: 我想在表中收到此信息: 我在做: 但这为我产生了: 为什么?我怎样才能正确地做到这一点? 问题答案: 这行: 附加到不是新的。 有几种方法: 但是,通过上述方法,添加样式和动态地进行处理变得不那么容易管理。 但是,如何做到这一点,却几乎可以实现您的期望: 希望这会有所帮助。

  • 问题内容: 我已经编写了基本的python代码段,以首先在列表中插入值,然后反转它们。我发现insert和append方法之间的执行速度存在巨大差异。 片段1: 执行此操作所需的时间: 片段2: 执行时间: 我希望代码片段2的性能比snippet1好得多,因为我直接通过在前面插入数字来执行反向操作。但是所花费的时间却相反。我不明白为什么后一种方法需要更多时间来执行,即使该方法看起来更优雅。有人对此

  • 我在HTML正文中有一个名为RecipeContainer的div。我正在尝试使用一个API来根据用户的关键字搜索菜谱。最初,我在一个HTML中有6个DIV,它们被填充在我的javascript中,并使用CSS样式,但我认为这不再是一个有效的解决方案。 下面是我当前的代码,它试图将功能转移到完全在JS中创建的每个菜谱的平铺中: 这是每当用户点击Submit时执行的循环。实际上,我有两个问题与此相关

  • 问题内容: 为了使切片追加操作更快,我们需要分配足够的容量。有两种附加切片的方法,下面是代码: 结果是: BenchmarkSliceAppend-4 200000000 7.87 ns / op 8 B / op 0 allocs / op BenchmarkSliceSet-4 300000000 5.76 ns / op 8 B / op 比我快,我想知道为什么吗? 问题答案: 只需将值分配

  • 如果语句更多地依赖于分支预测,而v表查找更多地依赖分支目标预测,那么

  • 我有一本空字典。名称:它将具有值为列表的键。 从一个单独的迭代中,我获得一个键(例如:)和一个项(一个元组),以放置在的值列表中。 如果此键已存在,我想附加此项。如果这个键不存在,我想用一个空列表创建它,然后附加到它,或者只是用一个元组创建它。 将来,当这个键再次出现时,由于它存在,我希望再次追加该值。 我的代码包括: 获取密钥和值。 查看中是否存在非密钥。 如果不创建: 之后: 这是做这件事的方