使用一行代码在pandas数据框中交换选定行的列值的正确语法是什么?
我正在使用 pandas 版本 0.14.1 和 Python 2.7.5,手头有一个包含三列的数据框,比如:
import pandas as pd
d = {'L': ['left', 'right', 'left', 'right', 'left', 'right'],
'R': ['right', 'left', 'right', 'left', 'right', 'left'],
'VALUE': [-1, 1, -1, 1, -1, 1]}
df = pd.DataFrame(d)
idx = (df['VALUE'] == 1)
这个数据框看起来是这样的:
L R VALUE
0 left right -1
1 right left 1
2 left right -1
3 right left 1
4 left right -1
5 right left 1
对于那些 VALUE == 1
的行,我想把左边和右边的列内容互换,这样所有的“左”值就会放到“L”列下面,而“右”值则放到“R”列下面。
我已经在上面定义了 idx
变量,现在只需要再写三行代码,就可以通过使用一个临时变量来轻松实现这个互换,如下所示:
tmp = df.loc[idx,'L']
df.loc[idx,'L'] = df.loc[idx,'R']
df.loc[idx,'R'] = tmp
不过我觉得这个写法看起来有点笨重和不优雅;难道 pandas 不支持更简洁的写法吗?我注意到如果我在数据框的 .loc
属性中交换列的顺序,那么输出就会变成这样:
In [2]: print(df.loc[idx,['R','L']])
R L
1 left right
3 left right
5 left right
这让我觉得我应该能用下面这一行代码来实现和上面一样的互换:
df.loc[idx,['L','R']] = df.loc[idx,['R','L']]
但是当我实际尝试这样做时,什么也没发生——列依然没有互换。就好像 pandas 自动识别出我在赋值语句的右边把列的顺序搞错了,然后自动纠正了这个问题。我想知道有没有办法在 pandas 的赋值语句中禁用这种“列顺序自动纠正”,这样我就可以在不创建不必要的临时变量的情况下实现互换?
4 个回答
在我看来,df.update(df.loc[m].rename({'L': 'R', 'R': 'L'}, axis=1))
是最好的方法。
正如 @cs95 在 这个回答 的评论中提到的,df.update(df.loc[m].rename({'L': 'R', 'R': 'L'}, axis=1))
是可行的。
为什么这个方法更好呢?
因为它同时支持 NumPy 和 DataFrame 的掩码。
d = {'L': ['left', 'right', 'left', 'right', 'left', 'right'],
'R': ['right', 'left', 'right', 'left', 'right', 'left'],
'VALUE': [-1, 1, -1, 1, -1, 1]}
df = pd.DataFrame(d)
df_mask = df['VALUE'] == 1
df.update(df.loc[df_mask].rename({'L': 'R', 'R': 'L'}, axis=1))
df
d = {'L': ['left', 'right', 'left', 'right', 'left', 'right'],
'R': ['right', 'left', 'right', 'left', 'right', 'left'],
'VALUE': [-1, 1, -1, 1, -1, 1]}
df = pd.DataFrame(d)
np_mask = df['VALUE'].values == 1
df.update(df.loc[np_mask].rename({'L': 'R', 'R': 'L'}, axis=1))
df
所以,如果把它当作一个函数来用,用户可以提供灵活的索引方式。
此外,为了安全起见,如果使用 NumPy 索引,它也支持 iloc
而不是 loc
。可惜的是,iloc
在 '1.3.5' 版本中不支持 DataFrame 索引。
d = {'L': ['left', 'right', 'left', 'right', 'left', 'right'],
'R': ['right', 'left', 'right', 'left', 'right', 'left'],
'VALUE': [-1, 1, -1, 1, -1, 1]}
df = pd.DataFrame(d)
np_mask = df['VALUE'].values == 1
df.update(df.iloc[np_mask].rename({'L': 'R', 'R': 'L'}, axis=1))
df
你也可以用 np.select
和 df.where
来实现这个功能,也就是说:
选项 1: np.select
df[['L','R']] = pd.np.select(df['VALUE'] == 1, df[['R','L']].values, df[['L','R']].values)
选项 2: df.where
df[['L','R']] = df[['R','L']].where(df['VALUE'] == 1, df[['L','R']].values)
选项 3: df.mask
df[['L','R']] = df[['L','R']].mask( df['VALUE'] == 1, df[['R','L']].values)
输出结果:
L R VALUE
0 left right -1
1 left right 1
2 left right -1
3 left right 1
4 left right -1
5 left right 1
这里需要注意的关键点是,pandas会自动根据索引和列名来对齐行和列。因此,你需要以某种方式告诉pandas忽略这些列名。有一种方法是像@DSM那样,将数据转换成numpy数组。另一种方法是重命名这些列:
>>> df.loc[idx] = df.loc[idx].rename(columns={'R':'L','L':'R'})
L R VALUE
0 left right -1
1 left right 1
2 left right -1
3 left right 1
4 left right -1
5 left right 1
一种避免列名对齐的方法是直接使用底层的数组,通过 .values
来实现:
In [33]: df
Out[33]:
L R VALUE
0 left right -1
1 right left 1
2 left right -1
3 right left 1
4 left right -1
5 right left 1
In [34]: df.loc[idx,['L','R']] = df.loc[idx,['R','L']].values
In [35]: df
Out[35]:
L R VALUE
0 left right -1
1 left right 1
2 left right -1
3 left right 1
4 left right -1
5 left right 1