从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=0
或axis='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')
复制代码
值得注意的是,通过同时使用index
和columns
参数,你可以同时使用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.