老齐教室

从DataFrame中删除列

在操作数据的时候,DataFrame对象中删除一个或多个列是常见的操作,并且实现方法较多,然而这中间有很多细节值得关注。

首先,一般被认为是“正确”的方法,是使用DataFramedrop方法,之所以这种方法被认为是标准的方法,可能是收到了SQL语句中使用drop实现删除操作的影响。

1
2
3
4
5
6
7
8
9
10
11
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)
1
2
3
4
5
6
7
    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方法中,默认是删除行。

如果用axis=0axis='rows',都表示展出行,也可用labels参数删除行。

1
2
3
4
5
6
7
8
9
10
11
12
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参数,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
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

这样就删除了一列,注意,删除之后,返回了新的对象,这意味着,你可以用一个新的变量引用删除后得到的结果。如果要改变原有的DataFrame,可以增加一个参数inplace=True

1
2
3
4
5
6
7
8
df2 = df.drop('b', axis=1)

print(df2.columns)
print(df.columns)

# result
Index(['a', 'c', 'd', 'e'], dtype='object')
Index(['a', 'b', 'c', 'd', 'e'], dtype='object')

同样值得注意的是,你可以通过同时使用indexcolumns,同时删除行和列,并且你可以传入多个值,即删除多行或者多列。

1
2
3
4
5
6
7
df.drop(index=[0,2], columns=['b','c'])

# result
a d e
1 5 8 9
3 15 18 19
4 20 23 24

如果不使用drop方法,还可以通过索引实现同样的操作。有多种方式,这里列举一种,如下所示,用.loc.isin并取反。

1
2
3
4
5
6
7
df.loc[~df.index.isin([0,2]), ~df.columns.isin(['b', 'c'])]

# result
a d e
1 5 8 9
3 15 18 19
4 20 23 24

If none of that makes sense to you, I would suggest reading through my series on selecting and indexing in pandas, starting here.

如果这些对你来说都不是很清楚,建议参阅《跟老齐学Python:数据分析》中对此的详细说明。

另外的方法

除了上面演示的方法之外,还有别的方法可以删除列。

1
2
3
4
5
6
7
8
9
10
del df['a']
df

# result
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

原来的df['a']没了,这就如同前面用drop方法时参数中使用了inplace=True一样,原地修改。

但是,不要认为del就能百试百灵,它会让你有迷茫的时候。

我们知道,如果用类似df.b这样访问属性的形式,也能得到DataFrame对象的列,虽然这种方法我不是很提倡使用,但很多数据科学的民工都这么干。

1
2
3
4
5
6
7
8
9
df.b

# result
0 1
1 6
2 11
3 16
4 21
Name: b, dtype: int64

这么干,如果仅仅是查看,也无所谓,但是:

1
2
3
4
5
6
7
8
9
del df.b

# result
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-10-0dca358a6ef9> in <module>
----> 1 del df.b

AttributeError: b

这就报错了。是不是很迷惑,为什del df['b']奏效,而del df.b无效?

这就是接下来要研究的了。必须通过对细节的剖析,才能搞清楚问题的根源。

首先,del df['b']有效,是因为DataFrame对象中实现了__delitem__方法,在执行del df['b']时会调用该方法。但是del df.b呢,有没有调用此方法呢?

为此,可以定义一个简单的类,这里暂用dict作为保存数据的容器,当然,这个类不是真正的DataFrame

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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)

# result
StupidFrame value for a: 1
StupidFrame columns: {'a': 1, 'b': 2, 'c': 3}
StupidFrame columns: {'a': 1, 'c': 3}

认真观察上面的操作和StupidFrame代码,如果用[]对所创建的实例进行数据操作,可以实现删除、赋值、读取等。但是,当我们执行f.d = 4的操作时,并没有在StupidFrame中所创建的columns属性中增加键为d的键值对,而是为实例f增加了一个普通属性,名称是d

因此,如果要让f.df['d']等效,还必须要在StupidFrame类中添加 __getattr__ 方法,并使用__setattr__方法来处理设置问题(关于这两个方法的使用,请参阅《Python大学实用教程》中的详细介绍)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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

# result
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__方法,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)

# result
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}

现在,就理解了前面使用del删除DataFrame对象属性的方法出问题的根源了。当然,并不是说DataFrame对象的类就是上面那样的,而是用上面的方式简要说明了一下原因。

所以,在Pandas中要删除DataFrame的列,最好是用对象的drop方法。

另外,特别提醒,如果要创建新的列,也不要用df.column_name的方法,这也容易出问题。

参考文献

[1]. https://www.wrighters.io/how-to-remove-a-column-from-a-dataframe/

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

关注微信公众号,读文章、听课程,提升技能