【问题标题】:What is the quickest way to ensure a specific column is last (or first) in a dataframe确保特定列在数据框中的最后(或第一)的最快方法是什么
【发布时间】:2016-11-30 19:23:17
【问题描述】:

给定df

df = pd.DataFrame(np.arange(8).reshape(2, 4), columns=list('abcd'))

假设我需要将列 'b' 放在最后。我可以这样做:

df[['a', 'c', 'd', 'b']]

但是确保给定列位于末尾的最有效方法是什么?

这就是我一直在做的事情。别人会怎么做?

def put_me_last(df, column):
    return pd.concat([df.drop(column, axis=1), df[column]], axis=1)

put_me_last(df, 'b')


计时结果

结论 mfripp 是赢家。似乎reindex_axis[] 效率提高很多。这是非常好的信息。

代码

from string import lowercase

df_small = pd.DataFrame(np.arange(8).reshape(2, 4), columns=list('abcd'))
df_large = pd.DataFrame(np.arange(1000000).reshape(10000, 100),
                        columns=pd.MultiIndex.from_product([list(lowercase[:-1]), ['One', 'Two', 'Three', 'Four']]))


def pir1(df, column):
    return pd.concat([df.drop(column, axis=1), df[column]], axis=1)

def pir2(df, column):
    if df.columns[-1] == column:
        return df
    else:
        pos = df.columns.values.__eq__('b').argmax()
        return df[np.roll(df.columns, len(df.columns) - 1 - pos)]

def pir3(df, column):
    if df.columns[-1] == column:
        return df
    else:
        pos = df.columns.values.__eq__('b').argmax()
        cols = df.columns.values
        np.concatenate([cols[:pos], cols[1+pos:], cols[[pos]]])
        return df[np.concatenate([cols[:pos], cols[1+pos:], cols[[pos]]])]

def pir4(df, column):
    if df.columns[-1] == column:
        return df
    else:
        return df[np.roll(df.columns.drop(column).insert(0, column), -1)]

def carsten1(df, column):
    cols = list(df)
    if cols[-1] == column:
        return df
    else:
        return pd.concat([df.drop(column, axis=1), df[column]], axis=1)

def carsten2(df, column):
    cols = list(df)
    if cols[-1] == column:
        return df
    else:
        idx = cols.index(column)
        new_cols = cols[:idx] + cols[idx + 1:] + [column]
        return df[new_cols]

def mfripp1(df, column):
    new_cols = [c for c in df.columns if c != column] + [column]
    return df[new_cols]

def mfripp2(df, column):
    new_cols = [c for c in df.columns if c != column] + [column]
    return df.reindex_axis(new_cols, axis='columns', copy=False)

def ptrj1(df, column):
    return df.reindex(columns=df.columns.drop(column).append(pd.Index([column])))

def shivsn1(df, column):
    column_list=list(df)
    column_list.remove(column)
    column_list.append(column)
    return df[column_list]

def merlin1(df, column):
    return df[df.columns.drop(["b"]).insert(99999, 'b')]


list_of_funcs = [pir1, pir2, pir3, pir4, carsten1, carsten2, mfripp1, mfripp2, ptrj1, shivsn1]

def test_pml(df, pml):
    for c in df.columns:
        pml(df, c)

summary = pd.DataFrame([], [f.__name__ for f in list_of_funcs], ['Small', 'Large'])

for f in list_of_funcs:
    summary.at[f.__name__, 'Small'] = timeit(lambda: test_pml(df_small, f), number=100)
    summary.at[f.__name__, 'Large'] = timeit(lambda: test_pml(df_large, f), number=10)

【问题讨论】:

  • 尝试将您的备选方案与timeit 模块进行比较。
  • @mhawke 我计划这样做。这就是我将如何决定答案。
  • 那么到目前为止,您的 2 个替代方案中哪个更快?
  • 我正在做测试。我必须随机化列顺序并在不同大小的数据集上测试许多试验。今晚我要一个。
  • @mhawke 到目前为止,唯一的答案只有在该列已经是最后一列时才会受益。在随机设置中,它将获得 1 / len(columns) 的时间。根据列的数量,收益可能不会超过检查成本。无论如何,我会建立一个适当的测试。

标签: python pandas


【解决方案1】:

嗯,第一个(并且,根据您的用例,最有效的)优化是首先确保您不必重新排列 DataSet。如果您想成为最后一列的列已经在它的位置,那么您可以原封不动地返回 df。试试这个:

def put_me_last2(df, column):
    if list(df)[-1] == column:
        return df
    else: return pd.concat([df.drop(column, axis=1), df[column]], axis=1)

我已经用 800 万个条目而不是您示例中的 8 个条目进行了尝试,当我要求列 b 作为最后一个时,速度大致相同,而当我要求列时,速度大约是 300 倍(500us 对 150ms)希望最后一列是d(即没有重新排序的情况)。

如果您有很多列或通常想要重新排列列,这对您没有帮助,但也无妨。

更新:

我找到了一种更快的方法:不要删除并重新添加列,而是将df[cols] 与所需的列列表一起使用。给了我大约 40% 的加速(90 毫秒 vs 150 毫秒,800 万个条目)。

def put_me_last3(df, column):
    cols = list(df)
    if cols[-1] == column:
        return df
    else:
        idx = cols.index(column)
        new_cols = cols[:idx] + cols[idx + 1:] + [column]
        return df[new_cols]

【讨论】:

  • 我将其编辑为if df.columns[-1] == column:。但是,是的,这是一个很好的提示。
  • 两者的工作方式相同。其中一个可能快几微秒。如果您对其进行测试,请将结果添加到我的答案或您的问题中,我会感兴趣的。我添加了一个额外的方法来提高 40% 的速度。
【解决方案2】:

我会重新排列列列表,而不是删除和附加其中之一:

import pandas as pd
import numpy as np

df = pd.DataFrame(np.arange(8).reshape(2, 4), columns=list('abcd'))

def put_me_last(df, column):
    return pd.concat([df.drop(column, axis=1), df[column]], axis=1)

def put_me_last_fast(df, column):
    new_cols = [c for c in df.columns if c != column] + [column]
    return df[new_cols]

def put_me_last_faster(df, column):
    new_cols = [c for c in df.columns if c != column] + [column]
    return df.reindex_axis(new_cols, axis='columns', copy=False)

时间安排(在 iPython 中):

%timeit put_me_last(df, 'b')
# 1000 loops, best of 3: 741 µs per loop

%timeit put_me_last_fast(df, 'b')
# 1000 loops, best of 3: 295 µs per loop

%timeit put_me_last_faster(df, 'b')
# 1000 loops, best of 3: 239 µs per loop

%timeit put_me_last_faster(df, 'd')  # not changing order
# 1000 loops, best of 3: 125 µs per loop

注意:您可以使用下面的行来定义 new_cols,但它比上面使用的要慢 80 倍(2 µs vs 160 µs)

new_cols = df.columns.drop(column).insert(-1, column)

另请注意:如果您经常尝试将一列移动到已经存在的末尾,您可以通过添加这一点将这些情况的时间缩短到 1 µs 以下,正如@Carsten 所述:

if df.columns[-1] == column:
    return df

【讨论】:

    【解决方案3】:

    这个怎么样:

    df.reindex(columns=df.columns.drop(col).append(pd.Index([col])))
    

    .append([col]) 不起作用 - 可能是一个错误。编辑:使用.append(pd.Index([col]) 可能是附加的最安全的选项。)

    测试评论:如果您计划使用 timeit 进行测试,请尝试在较大的 df(例如 1e4 行或更多行)上运行它,并且可能使用 -n1 -r1 以防止缓存。

    【讨论】:

      【解决方案4】:

      从这里开始:

       df.columns
       Index([u'a', u'b', u'c', u'd'], dtype='object')
      

      不要这样做,看起来像一个错误。

       df.columns.drop(["b"]).insert(-1, 'b')
       Index([u'a', u'c', u'b', u'd'], dtype='object')
      
       df.columns.drop(["b"]).insert(-1, 'x')
       Index([u'a', u'c', u'x', u'd'], dtype='object')
      

      工作周围:

       df.columns.drop(["b"]).insert(99999, 'b')
       Index([u'a', u'c', u'd', u'b'], dtype='object')
      

      【讨论】:

      • IMO,这不是错误,而是 Python 的 list.insert() 方法的标准行为。试试这个:df.columns.drop('b').insert(len(df.columns)-1, 'b')
      • @Merlin:好点。它的设计是这样的,但是如果您希望在索引的末尾插入一个项目,那就令人惊讶了。很烦人的是 df.Index.insert() 像 list.insert() 一样工作,但是 df.Index.append() 不像 list.append() 那样工作。这意味着没有完全自然的方法可以将项目添加到索引的末尾(必须使用大数字或检索索引的长度)。
      【解决方案5】:

      虽然不是最快的:

      def put_me_last(df,column):
          column_list=list(df)
          column_list.remove(column)
          column_list.append(column)
          return df[column_list]  
      
      
      
      %timeit put_me_last(df,'b')
      1000 loops, best of 3: 391 µs per loop
      

      【讨论】:

        猜你喜欢
        • 2019-11-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-28
        • 2021-07-09
        • 2014-07-21
        相关资源
        最近更新 更多