Python 为什么len在数据帧上比在底层numpy阵列上更高效?

Python 为什么len在数据帧上比在底层numpy阵列上更高效?,python,pandas,numpy,Python,Pandas,Numpy,我注意到在数据帧上使用len比在底层numpy数组上使用len要快得多。我不明白为什么。通过shape访问相同的信息也没有任何帮助。这与我尝试获取列数和行数更相关。我总是在争论该用哪种方法 我把下面的实验放在一起,很明显我将在数据帧上使用len。但是有人能解释为什么吗 from timeit import timeit import pandas as pd import numpy as np ns = np.power(10, np.arange(6)) results = pd.Data

我注意到在数据帧上使用
len
比在底层numpy数组上使用
len
要快得多。我不明白为什么。通过
shape
访问相同的信息也没有任何帮助。这与我尝试获取列数和行数更相关。我总是在争论该用哪种方法

我把下面的实验放在一起,很明显我将在数据帧上使用
len
。但是有人能解释为什么吗

from timeit import timeit
import pandas as pd
import numpy as np

ns = np.power(10, np.arange(6))
results = pd.DataFrame(
    columns=ns,
    index=pd.MultiIndex.from_product(
        [['len', 'len(values)', 'shape'],
         ns]))
dfs = {(n, m): pd.DataFrame(np.zeros((n, m))) for n in ns for m in ns}

for n, m in dfs.keys():
    df = dfs[(n, m)]
    results.loc[('len', n), m] = timeit('len(df)', 'from __main__ import df', number=10000)
    results.loc[('len(values)', n), m] = timeit('len(df.values)', 'from __main__ import df', number=10000)
    results.loc[('shape', n), m] = timeit('df.values.shape', 'from __main__ import df', number=10000)


fig, axes = plt.subplots(2, 3, figsize=(9, 6), sharex=True, sharey=True)
for i, (m, col) in enumerate(results.iteritems()):
    r, c = i // 3, i % 3
    col.unstack(0).plot.bar(ax=axes[r, c], title=m)

如果您查看
pd.DataFrame
\uuu len\uuuu
,它们实际上只是调用
len(df.index)

对于
范围索引
,这是一个非常快速的操作,因为它只是索引对象中存储的值的减法和除法:

return max(0, -(-(self._stop - self._start) // self._step))

我怀疑如果您使用非范围索引进行测试,时间上的差异会更相似。如果是这样的话,我可能会尝试修改您必须查看的内容


编辑:经过快速检查后,即使使用标准的
索引,速度差异似乎仍然保持不变,因此那里肯定还有其他一些优化。

从各种方法来看,主要原因是构建numpy数组
df.values
占用了大部分时间


len(df)
df.shape
这两个都很快,因为它们本质上是

len(df.index._data)

其中,
\u data
是一个
numpy.ndarray
。因此,使用
df.shape
的速度应该是
len(df)
的一半,因为它可以同时查找
df.index
df.columns
(类型都是
pd.index


len(df.values)
df.values.shape
假设您已经提取了
vals=df.values
。然后

In [1]: df = pd.DataFrame(np.random.rand(1000, 10), columns=range(10))

In [2]: vals = df.values

In [3]: %timeit len(vals)
10000000 loops, best of 3: 35.4 ns per loop

In [4]: %timeit vals.shape
10000000 loops, best of 3: 51.7 ns per loop
与之相比:

In [5]: %timeit len(df.values)
100000 loops, best of 3: 3.55 µs per loop
因此,瓶颈不是
len
,而是如何构建
df.values
。如果检查pandas.DataFrame.values()
,您会发现(大致相当)方法:


请注意,
df.\u data
是一个
pandas.core.internal.BlockManager
,而不是
numpy.ndarray

您应该发现第四个选项
df.shape
的时间大约是
len(df)的两倍
。可能不是
len
shape
相对较慢,而是
值的步骤相对较慢。获取现有numpy数组的
len
shape
应该很快,因为这些只是数组的属性。很高兴知道!我(错误地)认为,
df.\u data
会导致
ndarray
@RandyC是的,我也是这样认为的,直到我刚才查看了源代码。
In [5]: %timeit len(df.values)
100000 loops, best of 3: 3.55 µs per loop
def values(self):
    return self.as_matrix()

def as_matrix(self, columns=None):
    self._consolidate_inplace()
    if self._AXIS_REVERSED:
        return self._data.as_matrix(columns).T

    if len(self._data.blocks) == 0:
        return np.empty(self._data.shape, dtype=float)

    if columns is not None:
        mgr = self._data.reindex_axis(columns, axis=0)
    else:
        mgr = self._data

    if self._data._is_single_block or not self._data.is_mixed_type:
        return mgr.blocks[0].get_values()
    else:
        dtype = _interleaved_dtype(self.blocks)
        result = np.empty(self.shape, dtype=dtype)
        if result.shape[0] == 0:
            return result

        itemmask = np.zeros(self.shape[0])
        for blk in self.blocks:
            rl = blk.mgr_locs
            result[rl.indexer] = blk.get_values(dtype)
            itemmask[rl.indexer] = 1

        # vvv here is your final array assuming you actually have data
        return result 

def _consolidate_inplace(self):
    def f():
        if self._data.is_consolidated():
            return self._data

        bm = self._data.__class__(self._data.blocks, self._data.axes)
        bm._is_consolidated = False
        bm._consolidate_inplace()
        return bm
    self._protect_consolidate(f)

def _protect_consolidate(self, f):
    blocks_before = len(self._data.blocks)
    result = f()
    if len(self._data.blocks) != blocks_before:
        if i is not None:
            self._item_cache.pop(i, None)
        else:
            self._item_cache.clear()
    return result