Python 在DataFrame中反转列顺序的最大复杂性是什么?
假设我有一个熊猫数据框,有m行和n列。我们还可以说,我想反转列的顺序,这可以通过以下代码完成:Python 在DataFrame中反转列顺序的最大复杂性是什么?,python,algorithm,pandas,numpy,big-o,Python,Algorithm,Pandas,Numpy,Big O,假设我有一个熊猫数据框,有m行和n列。我们还可以说,我想反转列的顺序,这可以通过以下代码完成: df_reversed = df[df.columns[::-1]] 这个操作的最大复杂性是什么?我假设这取决于列的数量,但也取决于行的数量吗?我不知道Pandas是如何实现这一点的,但我确实进行了经验测试。我运行了以下代码(在Jupyter笔记本中)来测试操作的速度: def get_dummy_df(n): return pd.DataFrame({'a': [1,2]*n, 'b':
df_reversed = df[df.columns[::-1]]
这个操作的最大复杂性是什么?我假设这取决于列的数量,但也取决于行的数量吗?我不知道Pandas是如何实现这一点的,但我确实进行了经验测试。我运行了以下代码(在Jupyter笔记本中)来测试操作的速度:
def get_dummy_df(n):
return pd.DataFrame({'a': [1,2]*n, 'b': [4,5]*n, 'c': [7,8]*n})
df = get_dummy_df(100)
print df.shape
%timeit df_r = df[df.columns[::-1]]
df = get_dummy_df(1000)
print df.shape
%timeit df_r = df[df.columns[::-1]]
df = get_dummy_df(10000)
print df.shape
%timeit df_r = df[df.columns[::-1]]
df = get_dummy_df(100000)
print df.shape
%timeit df_r = df[df.columns[::-1]]
df = get_dummy_df(1000000)
print df.shape
%timeit df_r = df[df.columns[::-1]]
df = get_dummy_df(10000000)
print df.shape
%timeit df_r = df[df.columns[::-1]]
结果是:
(200, 3)
1000 loops, best of 3: 419 µs per loop
(2000, 3)
1000 loops, best of 3: 425 µs per loop
(20000, 3)
1000 loops, best of 3: 498 µs per loop
(200000, 3)
100 loops, best of 3: 2.66 ms per loop
(2000000, 3)
10 loops, best of 3: 25.2 ms per loop
(20000000, 3)
1 loop, best of 3: 207 ms per loop
如您所见,在前3种情况下,操作的开销占用了大部分时间(400-500µs),但从第4种情况开始,所需时间开始与数据量成比例,每次都以数量级增加
所以,假设n也有一个比例,似乎我们在处理O(m*n)我使用
big\u O
拟合库进行了一次实证测试
注:所有测试均在6个数量级(即
从行
到10
与常量10^6
的大小3列
从列
到10
与常量10^6
的大小行
数据帧
中的列
反向操作。列[:-1]
复杂度为
O(n^3)
其中n是行数
O(n^3)
其中n是列数
big\u o()
pip安装big\u o
代码
import big_o
import pandas as pd
import numpy as np
SWEAP_LOG10 = 6
COLUMNS = 3
ROWS = 10
def build_df(rows, columns):
# To isolated the creation of the DataFrame from the inversion operation.
narray = np.zeros(rows*columns).reshape(rows, columns)
df = pd.DataFrame(narray)
return df
def flip_columns(df):
return df[df.columns[::-1]]
def get_row_df(n, m=COLUMNS):
return build_df(1*10**n, m)
def get_column_df(n, m=ROWS):
return build_df(m, 1*10**n)
# infer the big_o on columns[::-1] operation vs. rows
best, others = big_o.big_o(flip_columns, get_row_df, min_n=1, max_n=SWEAP_LOG10,n_measures=SWEAP_LOG10, n_repeats=10)
# print results
print('Measuring .columns[::-1] complexity against rapid increase in # rows')
print('-'*80 + '\nBig O() fits: {}\n'.format(best) + '-'*80)
for class_, residual in others.items():
print('{:<60s} (res: {:.2G})'.format(str(class_), residual))
print('-'*80)
# infer the big_o on columns[::-1] operation vs. columns
best, others = big_o.big_o(flip_columns, get_column_df, min_n=1, max_n=SWEAP_LOG10,n_measures=SWEAP_LOG10, n_repeats=10)
# print results
print()
print('Measuring .columns[::-1] complexity against rapid increase in # columns')
print('-'*80 + '\nBig O() fits: {}\n'.format(best) + '-'*80)
for class_, residual in others.items():
print('{:<60s} (res: {:.2G})'.format(str(class_), residual))
print('-'*80)
大O复杂度(从0.24开始)是m*n
,其中m
是列数,n
是行数。注意,这是在使用数据帧时使用的。uu getitem_uuu
方法(aka[]
)和索引(
)
下面是一个有用的堆栈跟踪:
<ipython-input-4-3162cae03863>(2)<module>()
1 columns = df.columns[::-1]
----> 2 df_reversed = df[columns]
pandas/core/frame.py(2682)__getitem__()
2681 # either boolean or fancy integer index
-> 2682 return self._getitem_array(key)
2683 elif isinstance(key, DataFrame):
pandas/core/frame.py(2727)_getitem_array()
2726 indexer = self.loc._convert_to_indexer(key, axis=1)
-> 2727 return self._take(indexer, axis=1)
2728
pandas/core/generic.py(2789)_take()
2788 axis=self._get_block_manager_axis(axis),
-> 2789 verify=True)
2790 result = self._constructor(new_data).__finalize__(self)
pandas/core/internals.py(4539)take()
4538 return self.reindex_indexer(new_axis=new_labels, indexer=indexer,
-> 4539 axis=axis, allow_dups=True)
4540
pandas/core/internals.py(4421)reindex_indexer()
4420 new_blocks = self._slice_take_blocks_ax0(indexer,
-> 4421 fill_tuple=(fill_value,))
4422 else:
pandas/core/internals.py(1254)take_nd()
1253 new_values = algos.take_nd(values, indexer, axis=axis,
-> 1254 allow_fill=False)
1255 else:
> pandas/core/algorithms.py(1658)take_nd()
1657 import ipdb; ipdb.set_trace()
-> 1658 func = _get_take_nd_function(arr.ndim, arr.dtype, out.dtype, axis=axis,
1659 mask_info=mask_info)
1660 func(arr, indexer, out, fill_value)
请注意,在上述模板函数中,有一个使用
memmove
的路径(这是本例中采用的路径,因为我们正在从int64
映射到int64
,并且输出的维度与我们切换索引时的维度相同)。请注意,与它必须复制的字节数成比例,尽管可能比直接写入索引快。self注意:在设置此类测试时,选择最低数量级:)会使笔记本电脑崩溃。如果要提高性能,请使用切片df.iloc[:,:-1]
,它返回一个视图,因此实际上应该是自由的,而不是df[df.columns[:-1]]
在后者中建立索引时创建副本。@Divakar,作为一般规则,这是否仅适用于iloc
,还是loc
也返回视图?可能超出了单个注释的范围,但我也感兴趣的是,为什么通过df[col_list]
直接索引应该返回一个副本(这是设计选择/副作用/有任何好处吗?)@divakar如果我返回一个视图,我还可以对它进行操作,然后再次颠倒列的顺序,并以应用操作的原始数据帧结束吗?@TimHoldsworth一旦进行操作,就创建一个副本。你忽略了小数量级的开销,是的,从技术上讲,如果是O(n),O(n^3)也适用,“但是它不是很有用。”物理学家,你能详细说明一下小数量级的开销吗?另一个答案做得很好。你只需拟合有限数量的点,低尾巴就把你甩了。另一个答案实际上是看大O。我严重怀疑pandas中的任何基本操作都是O(n^3)。在调用\uuu getitem\uuuuu
和调用cython函数后,我继续添加了一些示例,这些示例显示了更多的上下文,而cython函数最终大部分时间都花在了大值上。\uuuu getitem\uuuu
中的逻辑并不总是直观的,但我发现这个GH问题有助于解释不同输入在引擎盖下的作用。谢谢,这对解释我们看到的行为有很大帮助。
<ipython-input-4-3162cae03863>(2)<module>()
1 columns = df.columns[::-1]
----> 2 df_reversed = df[columns]
pandas/core/frame.py(2682)__getitem__()
2681 # either boolean or fancy integer index
-> 2682 return self._getitem_array(key)
2683 elif isinstance(key, DataFrame):
pandas/core/frame.py(2727)_getitem_array()
2726 indexer = self.loc._convert_to_indexer(key, axis=1)
-> 2727 return self._take(indexer, axis=1)
2728
pandas/core/generic.py(2789)_take()
2788 axis=self._get_block_manager_axis(axis),
-> 2789 verify=True)
2790 result = self._constructor(new_data).__finalize__(self)
pandas/core/internals.py(4539)take()
4538 return self.reindex_indexer(new_axis=new_labels, indexer=indexer,
-> 4539 axis=axis, allow_dups=True)
4540
pandas/core/internals.py(4421)reindex_indexer()
4420 new_blocks = self._slice_take_blocks_ax0(indexer,
-> 4421 fill_tuple=(fill_value,))
4422 else:
pandas/core/internals.py(1254)take_nd()
1253 new_values = algos.take_nd(values, indexer, axis=axis,
-> 1254 allow_fill=False)
1255 else:
> pandas/core/algorithms.py(1658)take_nd()
1657 import ipdb; ipdb.set_trace()
-> 1658 func = _get_take_nd_function(arr.ndim, arr.dtype, out.dtype, axis=axis,
1659 mask_info=mask_info)
1660 func(arr, indexer, out, fill_value)
inner_take_2d_axis0_template = """\
cdef:
Py_ssize_t i, j, k, n, idx
%(c_type_out)s fv
n = len(indexer)
k = values.shape[1]
fv = fill_value
IF %(can_copy)s:
cdef:
%(c_type_out)s *v
%(c_type_out)s *o
#GH3130
if (values.strides[1] == out.strides[1] and
values.strides[1] == sizeof(%(c_type_out)s) and
sizeof(%(c_type_out)s) * n >= 256):
for i from 0 <= i < n:
idx = indexer[i]
if idx == -1:
for j from 0 <= j < k:
out[i, j] = fv
else:
v = &values[idx, 0]
o = &out[i, 0]
memmove(o, v, <size_t>(sizeof(%(c_type_out)s) * k))
return
for i from 0 <= i < n:
idx = indexer[i]
if idx == -1:
for j from 0 <= j < k:
out[i, j] = fv
else:
for j from 0 <= j < k:
out[i, j] = %(preval)svalues[idx, j]%(postval)s
"""