根据另一列的值设置Pandas列的值

141
我需要在Pandas数据框中基于另一列的值设置一个列的值。这是逻辑:
if df['c1'] == 'Value':
    df['c2'] = 10
else:
    df['c2'] = df['c3']

我无法让它按照我期望的那样工作,这就是简单地创建一个包含新值的列(或更改现有列的值:两种方式都适用于我)。

如果我尝试运行上面的代码,或者将其编写为函数并使用apply方法,我会得到以下结果:

ValueError: 系列的真值是模糊的。 使用a.empty,a.bool(),a.item(),a.any()或a.all()。

10个回答

210

一个实现这个的方法是使用.loc索引。

示例

在没有示例数据框的情况下,我将在这里编写一个:

import numpy as np
import pandas as pd

df = pd.DataFrame({'c1': list('abcdefg')})
df.loc[5, 'c1'] = 'Value'

>>> df
      c1
0      a
1      b
2      c
3      d
4      e
5  Value
6      g

假设您想要创建一个新的列 c2,它与 c1 相同,除非 c1Value,那么您希望将其分配给 10:

首先,您可以创建一个新的列 c2,并将其设置为与 c1 相同,使用以下两行代码中的任意一行(它们本质上执行相同的操作):

df = df.assign(c2 = df['c1'])
# OR:
df['c2'] = df['c1']

然后,使用.loc找到所有c1等于'Value'的索引,并在这些索引处将所需值赋给c2

df.loc[df['c1'] == 'Value', 'c2'] = 10

最终结果如下:

>>> df
      c1  c2
0      a   a
1      b   b
2      c   c
3      d   d
4      e   e
5  Value  10
6      g   g

如果按照您在问题中提到的想法,有时您可能只想替换已有列中的值,而不是创建新列,则可以跳过创建列这一步,执行以下操作:

df['c1'].loc[df['c1'] == 'Value'] = 10
# or:
df.loc[df['c1'] == 'Value', 'c1'] = 10
给你:
>>> df
      c1
0      a
1      b
2      c
3      d
4      e
5     10
6      g

12
第二个解决方案对我来说很有效。我没有意识到可以像SQL中的WHERE语句一样使用.loc。很合理。谢谢! - NLR
1
我认为你需要将所有需要更新值的列放入一个列表中,然后循环遍历该列表并更改其中的列名参数。 - Joe
1
当我使用类似于df.loc[df['c1'] == 'Value', 'c1'] = 10的语句进行赋值时,即使我使用了.loc[]语法,我也会收到可怕的SettingWithCopyWarning警告。 迄今为止,这还不是我的问题,但是我发现很奇怪,尽管使用了建议的.loc[]方法,我仍然会收到警告。 有什么解决方法吗?这是在pandas 1.2.3,numpy 1.18.5和python 3.7.10中。 - Darren
@Darren,在我的经验中,SettingWithCopyWarning最常见的原因是在视图上尝试分配值。你尝试修改的df是否可能是较大数据框的子集?在这样的视图上使用上述方法会引发警告。值得一提的是,很难预测哪些操作会导致pandas中的视图(请参见此处)。 - sacuL
1
假设我有一个int列,如果其值大于1000,则想将其值除以1000。使用第一种选项df['c1'].loc[df['c1'] > 1000] = df['c1'].loc[df['c1'] > 1000]/1000,我得到了SettingWithCopyWarning警告。然而,使用第二种选项df['c1'].loc[df['c1'] > 1000, 'c1'],我不会收到该警告。 - Averell
显示剩余3条评论

81
您可以使用np.where()基于指定条件设置值:
#df
   c1  c2  c3
0   4   2   1
1   8   7   9
2   1   5   8
3   3   3   5
4   3   6   8

根据您的条件,现在更改列 ['c2'] 中的值(或设置值)。

df['c2'] = np.where(df.c1 == 8,'X', df.c3)

   c1  c2  c3
0   4   1   1
1   8   X   9
2   1   8   8
3   3   5   5
4   3   8   8

如果我想保留所有原始列,该怎么办? - mLstudent33
2
@mLstudent33,使用df['newColName'] = ...时,你要使用一个数据框中不存在的列名来创建一个新列,假设newColName还不存在。 - DJK
1
比其他解决方案更优越,因为(a)由于术语顺序的不确定性较小,更易读;(b)它更具未来性,因为更容易修改以考虑多个列,并且(c)它很快,没有在lambda中解释的代码。 - Contango
1
@DJK 我猜你代码中的 c2 是指输出表格中的 c4?你最好纠正其中一个。 - abu

42

尝试:

df['c2'] = df['c1'].apply(lambda x: 10 if x == 'Value' else x)

感谢@AlexanderHughes。我的原始帖子有一个错别字:实际上需要考虑三列,所以这个解决方案行不通。 - NLR
7
应为 df.apply(lambda x: 10 if x['c1'] == 'Value' else x['c3'], axis=1) - DJK
9
处理大型数据集时可能会出现性能问题。使用df.apply()的速度较慢。 - ErnestScribbler
我也在寻找同样的解决方案,最终发现使用lambda函数和dataframe可以解决问题。我的代码如下:'ard['Hr'] = ard.apply(lambda x: x['Hr']+1 if x['Mi'] >= 45 and x['Mi'] < 60 else x['Hr'],axis=1)'。 - Runawaygeek

23

请注意波浪号(~)可以反转选择,这里使用了Pandas方法(比使用if/else更快)。

df.loc[(df['c1'] == 'Value'), 'c2'] = 10
df.loc[~(df['c1'] == 'Value'), 'c2'] = df['c3']

7

我建议分两步来完成:

# set fixed value to 'c2' where the condition is met
df.loc[df['c1'] == 'Value', 'c2'] = 10

# copy value from 'c3' to 'c2' where the condition is NOT met
df.loc[df['c1'] != 'Value', 'c2'] = df[df['c1'] != 'Value', 'c3']

这个非常令人困惑,你能展示一些表格来澄清吗? - mLstudent33

6
你可以使用 pandas.DataFrame.mask 函数添加尽可能多的条件:
data = {'a': [1,2,3,4,5], 'b': [6,8,9,10,11]}

d = pd.DataFrame.from_dict(data, orient='columns')
c = {'c1': (2, 'Value1'), 'c2': (3, 'Value2'), 'c3': (5, d['b'])}

d['new'] = np.nan
for value in c.values():
    d['new'].mask(d['a'] == value[0], value[1], inplace=True)

d['new'] = d['new'].fillna('Else')
d

输出:

    a   b   new
0   1   6   Else
1   2   8   Value1
2   3   9   Value2
3   4   10  Else
4   5   11  11

4

我认为 Series.map()非常易读和高效,例如:

df["c2"] = df["c1"].map(lambda x: 10 if x == 'Value' else x)

我喜欢它,因为如果条件逻辑变得更加复杂,您可以将其移动到函数中,并只传递该函数而不是lambda表达式。
如果您需要基于多个列来确定条件逻辑,您可以像其他人建议的那样使用DataFrame.apply()

1

如果您的数据框较小/中等大小,请尝试使用df.apply()。

df['c2'] = df.apply(lambda x: 10 if x['c1'] == 'Value' else x['c1'], axis = 1)

否则,如果您有一个大的数据框,请按照上面评论中提到的切片技术进行操作。

0
很多提供的答案都很好!只想再加一点。在最受赞同的答案的基础上,使用.loc,如果你有一个值列表要检查,你可以修改这行代码:
df['c1'].loc[df['c1'] == 'Value'] = 10

df['c1'].loc[df['c1'].isin(['Value1','Value2','Value3'])] = 10 

请注意,如答案中所解释的那样,这将替换列中的值。

-1
我有一个大数据集,使用 .loc[] 方法太慢了,所以我找到了矢量化的方法来处理。记住可以将列设置为逻辑运算符,这样就可以做到:

file['Flag'] = (file['Claim_Amount'] > 0)

这会得到一个布尔值,我想要的是这个,但是你可以将它乘以1等等来生成一个整数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接