返回

Pandas DataFrame 数据筛选与 ValueError 错误详解

python

Pandas DataFrame 数据筛选与 "ValueError: The truth value of a Series is ambiguous" 错误详解

最近在处理一个 DataFrame 时碰到了一个问题。简单来说,我有一个 DataFrame,其中一列是数字,另一列是字符串('Pos' 或 'Neg'),我想根据字符串列的内容来决定数字列的符号。

我最初的代码是这样的:

import pandas as pd
import numpy as np

# 模拟数据
data = {'num_1': [0.788043, -1.745502, 0.035905, 1.306768, -0.034413, -1.228146],
        'W': ['Neg', 'Neg', 'Pos', 'Pos', 'Neg', 'Pos']}
df_T = pd.DataFrame(data)
print(df_T)

# if df_T['W'] == 'Neg':
#    df_T['eval'] = abs (df_T('num_1') * -1) 
# elif df_T['W'] == 'Pos':
#      df_T['eval'] = abs (df_T('num_1'))
# else:
#     df_T ['eval'] = 0
# print (df_T)

代码注释掉了,是因为运行时报了如下错误:

ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

这错误看着挺唬人,实际上是啥原因呢? 下面咱们来分析一下。

一、 问题原因:Pandas 的 Series 比较

问题的根源在于 if df_T['W'] == 'Neg': 这一行。 这里,df_T['W'] 是一个 Pandas Series,而 'Neg' 是一个字符串。 当你用 == 比较一个 Series 和一个标量(单一值)时,Pandas 会逐元素进行比较,返回一个布尔值 Series,像这样:

0     True
1     True
2    False
3    False
4     True
5    False
Name: W, dtype: bool

这个布尔 Series 代表了 df_T['W'] 中每个元素是否等于 'Neg'。 但问题是,if 语句需要一个单一的布尔值(TrueFalse)来决定执行哪个分支,而不能直接处理一个布尔 Series。 这就是错误信息 "The truth value of a Series is ambiguous" 的含义:Pandas 不知道你想表达的是所有元素都为 True,还是至少有一个元素为 True,又或是其他?

二、解决方案

有好几种方法可以解决这个问题,这里介绍三种常用的。

2.1 .apply() 方法与 匿名函数 (lambda)

.apply() 方法可以将一个函数应用到 Series 的每个元素上。 我们可以用一个 lambda 匿名函数来判断 'W' 列的值,并相应地修改 'num_1' 列的符号。

import pandas as pd
import numpy as np

# 模拟数据
data = {'num_1': [0.788043, -1.745502, 0.035905, 1.306768, -0.034413, -1.228146],
        'W': ['Neg', 'Neg', 'Pos', 'Pos', 'Neg', 'Pos']}
df_T = pd.DataFrame(data)

df_T['eval'] = df_T.apply(lambda row: -abs(row['num_1']) if row['W'] == 'Neg' else abs(row['num_1']), axis=1)
print(df_T)

代码解释:

  1. df_T.apply(..., axis=1).apply() 默认是按列操作的,axis=1 表示按行操作。
  2. lambda row: ...:定义一个匿名函数,row 代表 DataFrame 的每一行。
  3. -abs(row['num_1']) if row['W'] == 'Neg' else abs(row['num_1']):这是一个条件表达式。如果 row['W'] 是 'Neg',就取 row['num_1'] 的绝对值的负数;否则,取 row['num_1'] 的绝对值。

这种方式清晰地表达了我们对每一行的操作。

2.2 .loc[] 索引器

.loc[] 索引器可以用来选择 DataFrame 的特定行和列。 我们可以用它来分别处理 'W' 列为 'Neg' 和 'Pos' 的行。

import pandas as pd
import numpy as np

# 模拟数据
data = {'num_1': [0.788043, -1.745502, 0.035905, 1.306768, -0.034413, -1.228146],
        'W': ['Neg', 'Neg', 'Pos', 'Pos', 'Neg', 'Pos']}
df_T = pd.DataFrame(data)

df_T['eval'] = 0  # 初始化'eval'列
df_T.loc[df_T['W'] == 'Neg', 'eval'] = -abs(df_T['num_1'])
df_T.loc[df_T['W'] == 'Pos', 'eval'] = abs(df_T['num_1'])
print(df_T)

代码解释:

  1. df_T['eval'] = 0:先创建一个 'eval' 列,并用 0 填充。
  2. df_T.loc[df_T['W'] == 'Neg', 'eval'] = -abs(df_T['num_1']):选择 'W' 列等于 'Neg' 的行,并将这些行的 'eval' 列设置为 'num_1' 列的绝对值的负数。
  3. df_T.loc[df_T['W'] == 'Pos', 'eval'] = abs(df_T['num_1']):选择 'W' 列等于 'Pos' 的行,并将这些行的 'eval' 列设置为 'num_1' 列的绝对值。

这种方式通过条件筛选直接定位到要修改的行,非常直观。

2.3 使用 np.where()

np.where() 是 NumPy 库中的一个函数,它类似于 Excel 中的 IF 函数。它可以根据一个条件返回两个不同的值。

import pandas as pd
import numpy as np

# 模拟数据
data = {'num_1': [0.788043, -1.745502, 0.035905, 1.306768, -0.034413, -1.228146],
        'W': ['Neg', 'Neg', 'Pos', 'Pos', 'Neg', 'Pos']}
df_T = pd.DataFrame(data)

df_T['eval'] = np.where(df_T['W'] == 'Neg', -abs(df_T['num_1']), abs(df_T['num_1']))
print(df_T)

代码解释:

  1. np.where(df_T['W'] == 'Neg', -abs(df_T['num_1']), abs(df_T['num_1']))
    • 第一个参数是一个条件(df_T['W'] == 'Neg'),它返回一个布尔 Series。
    • 第二个参数是条件为 True 时返回的值(-abs(df_T['num_1']))。
    • 第三个参数是条件为 False 时返回的值(abs(df_T['num_1']))。

np.where() 将条件、True 值和 False 值整合在一个函数中,代码非常简洁。

2.4 进阶: 多条件情况

以上例子是比较简单的, 如果有更多条件呢? 我们可以组合上述方法,写出非常精炼的代码。假设除了 'Pos' 和 'Neg','W' 列还有其他值,我们想把其他值的对应'num_1'保留为原值。 可以这样:

import pandas as pd
import numpy as np

#模拟数据 增加了Other
data = {'num_1': [0.788043, -1.745502, 0.035905, 1.306768, -0.034413, -1.228146, 0.5],
        'W': ['Neg', 'Neg', 'Pos', 'Pos', 'Neg', 'Pos', 'Other']}
df_T = pd.DataFrame(data)

df_T['eval'] = np.where(df_T['W'] == 'Neg', -abs(df_T['num_1']), 
                      np.where(df_T['W'] == 'Pos', abs(df_T['num_1']), df_T['num_1']))
print(df_T)

这段代码用嵌套的np.where()实现了多条件判断,比写多个if-elif-else更简明。

或者,用 .loc[]也可以实现, 多条件用& (且) 和 |(或)连接即可:

import pandas as pd
import numpy as np

#模拟数据 增加了Other
data = {'num_1': [0.788043, -1.745502, 0.035905, 1.306768, -0.034413, -1.228146, 0.5],
        'W': ['Neg', 'Neg', 'Pos', 'Pos', 'Neg', 'Pos', 'Other']}
df_T = pd.DataFrame(data)
df_T['eval'] = df_T['num_1'] # 保留原值

df_T.loc[df_T['W'] == 'Neg', 'eval'] = -abs(df_T['num_1'])
df_T.loc[df_T['W'] == 'Pos', 'eval'] = abs(df_T['num_1'])

print(df_T)

三. 总结一下

  • 在 Pandas 中进行条件判断,要注意 Series 与标量的比较。
  • .apply() 结合 lambda 函数可以灵活处理每一行的数据。
  • .loc[] 索引器可以方便地选取和修改特定行。
  • np.where() 可以根据条件快速进行向量化赋值。
  • 处理复杂问题时,要善于选择合适的工具。

希望这篇对你有帮助!