使用一行代码在pandas数据框中交换选定行的列值的正确语法是什么?

37 投票
4 回答
35092 浏览
提问于 2025-04-21 03:34

我正在使用 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 个回答

0

在我看来,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
7

你也可以用 np.selectdf.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
11

这里需要注意的关键点是,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
42

一种避免列名对齐的方法是直接使用底层的数组,通过 .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

撰写回答