如何从DataFrame中删除一个列,有一些额外的细节

从pandasDataFrame 中删除一个或多个列是一个相当常见的任务,但事实证明,有许多可能的方法来执行这项任务。我发现这个StackOverflow问题,以及其中的解决方案和讨论提出了许多有趣的话题。值得对细节进行一番挖掘。

首先,从DataFrame 中删除一列的 “正确 “方法是什么?标准的方法是用SQL语言思考并使用drop

import pandas as pd
import numpy as np

df = pd.DataFrame(np.arange(25).reshape((5,5)), columns=list("abcde"))

display(df)

try:
    df.drop('b')
except KeyError as ke:
    print(ke)
复制代码
    a   b   c   d   e
0   0   1   2   3   4
1   5   6   7   8   9
2  10  11  12  13  14
3  15  16  17  18  19
4  20  21  22  23  24
"['b'] not found in axis"
复制代码

等等,什么?为什么会出现错误?这是因为drop 工作的默认轴是行。就像许多pandas方法一样,有不止一种方法可以调用这个方法(有些人觉得这很令人沮丧)。

你可以使用axis=0axis='rows' 删除行,或者使用labels 参数。

df.drop(0)                # drop a row, on axis 0 or 'rows'
df.drop(0, axis=0)        # same
df.drop(0, axis='rows')   # same
df.drop(labels=0)         # same
df.drop(labels=[0])       # same
复制代码
    a   b   c   d   e
1   5   6   7   8   9
2  10  11  12  13  14
3  15  16  17  18  19
4  20  21  22  23  24
复制代码

再说一遍,我们如何删除一列呢?

我们想删除一列,那么这看起来像什么呢?你可以指定axis ,或者使用columns 参数。

df.drop('b', axis=1)         # drop a column
df.drop('b', axis='columns') # same
df.drop(columns='b')         # same
df.drop(columns=['b'])       # same
复制代码
    a   c   d   e
0   0   2   3   4
1   5   7   8   9
2  10  12  13  14
3  15  17  18  19
4  20  22  23  24
复制代码

好了,这就是你如何删除一个列。现在你必须把它赋值给一个新的变量,或者返回到你的旧变量,或者传入inplace=True ,以使变化永久化。

df2 = df.drop('b', axis=1)

print(df2.columns)
print(df.columns)
复制代码
Index(['a', 'c', 'd', 'e'], dtype='object')
Index(['a', 'b', 'c', 'd', 'e'], dtype='object')
复制代码

值得注意的是,通过同时使用indexcolumns 参数,你可以同时使用drop来删除_行和列_,而且你可以传入多个值。

df.drop(index=[0,2], columns=['b','c'])
复制代码
    a   d   e
1   5   8   9
3  15  18  19
4  20  23  24
复制代码

如果你没有drop方法,你基本上可以通过索引获得同样的结果。有很多方法可以完成这个任务,但是一个等价的解决方案是使用.loc 索引器和isin ,同时反转选择。

df.loc[~df.index.isin([0,2]), ~df.columns.isin(['b', 'c'])]
复制代码
    a   d   e
1   5   8   9
3  15  18  19
4  20  23  24
复制代码

如果这些对你来说都没有意义,我建议你阅读我关于在pandas中选择和索引的系列文章,从这里开始。

回到问题上来

回到最初的问题,我们看到还有另一种可用的技术来删除一个列。

del df['a']
df
复制代码
    b   c   d   e
0   1   2   3   4
1   6   7   8   9
2  11  12  13  14
3  16  17  18  19
4  21  22  23  24
复制代码

噗!它就消失了。这就像用inplace=True 来做删除。

那么属性访问呢?

我们也知道,我们可以使用属性访问来_选择_ DataFrame 的列。

df.b
复制代码
0     1
1     6
2    11
3    16
4    21
Name: b, dtype: int64
复制代码

我们可以通过这种方式删除列吗?

del df.b
复制代码
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-10-0dca358a6ef9> in <module>
----> 1 del df.b

AttributeError: b
复制代码

我们不能。在当前的pandas设计中,这不是一个删除列的选项。这在技术上是不可能的吗?为什么del df['b'] 可以,而del df.b 却不行呢?让我们挖掘一下这些细节,看看是否有可能让第二个版本也能工作。

第一个版本可以工作,因为在pandas中,DataFrame 实现了__delitem__ 方法,当你执行del df['b'] 的时候会被调用。但是,del df.b ,有没有办法处理这个问题呢?

首先,让我们做一个简单的类来展示这个类在引擎盖下是如何工作的。我们不做一个真正的DataFrame ,而只是用一个dict ,作为我们的列的容器(它真的可以包含任何东西,我们在这里不做任何索引)。

class StupidFrame:
    def __init__(self, columns):
        self.columns = columns
        
    def __delitem__(self, item):
        del self.columns[item]
        
    def __getitem__(self, item):
        return self.columns[item]
    
    def __setitem__(self, item, val):
        self.columns[item] = val
            
f = StupidFrame({'a': 1, 'b': 2, 'c': 3})
print("StupidFrame value for a:", f['a'])
print("StupidFrame columns: ", f.columns)
del f['b']
f.d = 4
print("StupidFrame columns: ", f.columns)
复制代码
StupidFrame value for a: 1
StupidFrame columns:  {'a': 1, 'b': 2, 'c': 3}
StupidFrame columns:  {'a': 1, 'c': 3}
复制代码

这里有几件事需要注意。首先,我们可以用索引操作符([])访问我们的StupidFrame 中的数据,并使用它来设置、获取和删除项目。当我们把d 分配给我们的框架时,它并没有被添加到我们的列中,因为它只是一个普通的实例属性。如果我们希望能够将列作为属性来处理,我们必须做更多的工作。

所以按照pandas的例子(支持列的属性访问),我们添加了__getattr__ 方法,但我们也将用__setattr__ 方法处理设置,并假装任何属性赋值都是一个 “列”。我们必须直接更新我们的实例字典 (__dict__) 以避免无限递归。

class StupidFrameAttr:
    def __init__(self, columns):
        self.__dict__['columns'] = columns
        
    def __delitem__(self, item):
        del self.__dict__['columns'][item]
        
    def __getitem__(self, item):
        return self.__dict__['columns'][item]
    
    def __setitem__(self, item, val):
        self.__dict__['columns'][item] = val
        
    def __getattr__(self, item):
        if item in self.__dict__['columns']:
            return self.__dict__['columns'][item]
        elif item == 'columns':
            return self.__dict__[item]
        else:
            raise AttributeError
    
    def __setattr__(self, item, val):
        if item != 'columns':
            self.__dict__['columns'][item] = val
        else:
            raise ValueError("Overwriting columns prohibited") 

            
f = StupidFrameAttr({'a': 1, 'b': 2, 'c': 3})
print("StupidFrameAttr value for a", f['a'])
print("StupidFrameAttr columns: ", f.columns)
del f['b']
print("StupidFrameAttr columns: ", f.columns)
print("StupidFrameAttr value for a", f.a)
f.d = 4
print("StupidFrameAttr columns: ", f.columns)
del f['d']
print("StupidFrameAttr columns: ", f.columns)
f.d = 5
print("StupidFrameAttr columns: ", f.columns)
del f.d
复制代码
StupidFrameAttr value for a 1
StupidFrameAttr columns:  {'a': 1, 'b': 2, 'c': 3}
StupidFrameAttr columns:  {'a': 1, 'c': 3}
StupidFrameAttr value for a 1
StupidFrameAttr columns:  {'a': 1, 'c': 3, 'd': 4}
StupidFrameAttr columns:  {'a': 1, 'c': 3}
StupidFrameAttr columns:  {'a': 1, 'c': 3, 'd': 5}
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-12-fd29f59ea01e> in <module>
     39 f.d = 5
     40 print("StupidFrameAttr columns: ", f.columns)
---> 41 del f.d

AttributeError: d
复制代码

我们怎样才能处理删除?

除了使用属性访问的删除之外,一切都可以工作。我们使用数组索引操作符 ([]) 和属性访问来处理设置/获取列。但是如何检测删除呢?这有可能吗?

一种方法是使用__delattr__ 方法,这在数据模型文档中有所描述。如果你在你的类中定义了这个方法,它将被调用而不是直接更新一个实例的属性字典。这就给了我们一个机会,将其重定向到我们的列实例。

class StupidFrameDelAttr(StupidFrameAttr):
    def __delattr__(self, item):
        # trivial implementation using the data model methods
        del self.__dict__['columns'][item]

f = StupidFrameDelAttr({'a': 1, 'b': 2, 'c': 3})
print("StupidFrameDelAttr value for a", f['a'])
print("StupidFrameDelAttr columns: ", f.columns)
del f['b']
print("StupidFrameDelAttr columns: ", f.columns)
print("StupidFrameDelAttr value for a", f.a)
f.d = 4
print("StupidFrameDelAttr columns: ", f.columns)
del f.d 
print("StupidFrameDelAttr columns: ", f.columns)
复制代码
StupidFrameDelAttr value for a 1
StupidFrameDelAttr columns:  {'a': 1, 'b': 2, 'c': 3}
StupidFrameDelAttr columns:  {'a': 1, 'c': 3}
StupidFrameDelAttr value for a 1
StupidFrameDelAttr columns:  {'a': 1, 'c': 3, 'd': 4}
StupidFrameDelAttr columns:  {'a': 1, 'c': 3}
复制代码

现在我并不是说列的属性删除很容易被添加到pandas中,但至少这表明它是可以实现的。在当前的pandas中,删除列最好使用drop

另外,这里值得一提的是,当你在pandas中创建一个新的列时,你并没有把它作为一个属性来分配。为了更好地了解如何正确地创建一个列,你可以看看这篇文章

如果你已经知道了如何在pandas中删除一个列,希望你能多了解一点这个工作。

The postHow to remove a column from a DataFrame, with some extra detailappeared first onwrighters.io.

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享