熊猫表演:栏目选择

时间:2019-02-19 13:21:49

标签: pandas

我今天观察到,选择两列或更多列数据帧可能比仅选择一列慢得多。

如果我使用loc或iloc选择多个列,并且使用list传递列名或索引,则与使用iloc的单列或多列选择相比,性能下降了100倍(但未传递任何列表)< / p>

示例:

df = pd.DataFrame(np.random.randn(10**7,10), columns=list('abcdefghij'))

一列选择:

%%timeit -n 100
df['b']
3.17 µs ± 147 ns per loop (mean ± std. dev. of 7 runs, 100 loops each)

%%timeit -n 100
df.iloc[:,1]
66.7 µs ± 5.95 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%%timeit -n 100
df.loc[:,'b']
44.2 µs ± 10.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

两列选择:

%%timeit -n 10
df[['b', 'c']]
96.4 ms ± 788 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10
df.loc[:,['b', 'c']]
99.4 ms ± 4.44 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10
df.iloc[:,[1,2]]
97.6 ms ± 1.79 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

仅此选择可以像预期的那样工作: [编辑]

%%timeit -n 100
df.iloc[:,1:3]
103 µs ± 17.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

机制上有何不同以及为何如此之大?

[编辑]: 正如@ run-out指出的那样,pd.Series的处理似乎比pd.DataFrame快得多,有人知道为什么会这样吗?

另一方面-它不能解释df.iloc[:,[1,2]]df.iloc[:,1:3]之间的区别

2 个答案:

答案 0 :(得分:4)

Pandas作为pandas.Series使用单行或单列,这比在DataFrame体系结构中工作要快。

当您要求时,Pandas可与pandas.Series配合使用

%%timeit -n 10
df['b']
2.31 µs ± 1.59 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

但是,我可以通过将其放在列表中来为同一列调用DataFrame。然后您得到:

%%timeit -n 10
df[['b']]
90.7 ms ± 1.73 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

从上面您可以看到,表现优于DataFrame的是Series。

这是Pandas如何处理列“ b”。

type(df['b'])
pandas.core.series.Series

type(df[['b']])
pandas.core.frame.DataFrame

编辑: 我正在扩展我的答案,因为OP希望更深入地了解为什么pd.series与pd.dataframe的速度如此之大。同样,这也是一个扩展我/我们对基础技术如何工作的理解的好问题。那些有更多专业知识的人请加入。

首先让我们从numpy开始,因为它是熊猫的构建基块。根据pandas的作者以及Python for Data Analysis的作者Wes McKinney的说法,性能在numpy之上超过了python:

This is based partly on performance differences having to do with the
cache hierarchy of the CPU; operations accessing contiguous blocks of memory (e.g.,
summing the rows of a C order array) will generally be the fastest because the mem‐
ory subsystem will buffer the appropriate blocks of memory into the ultrafast L1 or
L2 CPU cache. 

让我们看看这个例子的速度差异。让我们从数据帧的列“ b”中创建一个numpy数组。

a = np.array(df['b'])

现在进行性能测试:

%%timeit -n 10
a

结果是:

32.5 ns ± 28.2 ns per loop (mean ± std. dev. of 7 runs, 10 loops each)

这是在pd.series时间2.31 µs内性能的严重提高。

提高性能的另一个主要原因是numpy索引直接进入了NumPy C扩展,但是当您对Series进行索引时,有很多python东西在工作,而且速度慢得多。 (read this article

让我们看一下为什么这样做的问题:

df.iloc[:,1:3]

明显胜过:

df.iloc[:,[1,2]]

有趣的是,在这种情况下,.loc具有与.iloc相同的性能效果。

我们的第一个大提示是以下代码:

df.iloc[:,1:3] is df.iloc[:,[1,2]]
False

这些给出相同的结果,但是是不同的对象。我已经进行了深入研究,以找出区别。我无法在互联网上或我的书库中找到对此的参考。

看一下源代码,我们可以开始看到一些区别。我指的是indexing.py。

在_iLocIndexer类中,我们可以发现熊猫正在做一些额外的工作,以便在iloc切片中列出。

马上,我们在检查输入时会遇到这两个区别:

if isinstance(key, slice):
            return

vs。

elif is_list_like_indexer(key):
            # check that the key does not exceed the maximum size of the index
            arr = np.array(key)
            l = len(self.obj._get_axis(axis))

            if len(arr) and (arr.max() >= l or arr.min() < -l):
                raise IndexError("positional indexers are out-of-bounds")

这是否足以导致性能下降?我不知道。

尽管.loc稍有不同,但是使用值列表时它也会降低性能。在index.py中查看def _getitem_axis(self,key,axis = None):->在类_LocIndexer(_LocationIndexer)中:

is_list_like_indexer(key)的用于处理列表输入的代码段很长,其中包括很多开销。它包含注释:

# convert various list-like indexers
# to a list of keys
# we will use the *values* of the object
# and NOT the index if its a PandasObject

在处理值列表或整数列表时,肯定会有足够的额外开销,然后直接引导切片导致处理延迟。

其余代码超出了我的工资等级。如果有人可以欣赏和欣赏,那将是非常受欢迎的

答案 1 :(得分:2)

我发现这可能植根于numpy。

numpy有两种索引:

  1. 像a [1:3]一样的基本索引
  2. 像[[1,2]]一样的高级索引

根据文档

高级索引总是返回数据的副本(与 返回视图的基本切片)。

因此,如果您检查

a=df.values
%timeit -n2 a[:,0:3]
%timeit -n2 a[:,[0,1,2]]

您有

The slowest run took 5.06 times longer than the fastest. This could mean that an intermediate result is being cached.
1.57 µs ± 1.3 µs per loop (mean ± std. dev. of 7 runs, 2 loops each)
188 ms ± 2.17 ms per loop (mean ± std. dev. of 7 runs, 2 loops each)

与熊猫数据框非常相似