一、问题及原因

  阿猪希望使用Pandas的apply方法对原先的DataFrame进行运算从而得到一个新的DataFrame,新的DataFrame与原先的DataFrame拥有不同的列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pandas

df = pandas.DataFrame({
'a': [1, 2, 3, 4, 5],
'b': [10, 20, 30, 40, 50],
'c': [50, 60, 70, 80, 90]
})

def func(row):
return pandas.Series({'d': row['a'], 'e': row['b'] + row['c']})

df_new = df.apply(func, axis=1)
df_new = df_new.reset_index(drop=True)

print(df_new)

  上边的示例代码是一个简化后的原型,它会创建一个新的DataFramedf_new,拥有两个新的列de,其中d列与原DataFramedfa列相同,e列则由dfb列和c相加而得。

  当df不为空时,可以得到预期的结果:

1
2
3
4
5
6
   d    e
0 1 60
1 2 80
2 3 100
3 4 120
4 5 140

  但是当将df的值改为pandas.DataFrame(columns = ['a', 'b', 'c'])时(即定义了列的空DataFrame),运行代码后返回的结果并不是预期的

Empty DataFrame
Columns: [d, e]
Index: []

  而是直接返回了df的值:

Empty DataFrame
Columns: [a, b, c]
Index: []

  经过网上一番搜索和调试,原来问题出在apply方法处理空DataFrame的逻辑上。当对df使用apply方法时,Pandas会调用pandas.core.apply.frame_apply

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class FrameApply(NDFrameApply):
# 此处省略N行代码

def apply(self) -> DataFrame | Series:
# 此处省略N行代码

# all empty
if len(self.columns) == 0 and len(self.index) == 0:
return self.apply_empty_result()

# 此处省略N行代码

# one axis empty
elif not all(self.obj.shape):
return self.apply_empty_result()

  这里会对df的行和列进行检查。如果df的行或/和列为空,则不会继续运行后续的代码,而是直接返回apply_empty_result的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class FrameApply(NDFrameApply):
# 此处省略N行代码

def apply_empty_result(self):

# 此处省略N行代码

if self.result_type not in ["reduce", None]:
return self.obj.copy()

# we may need to infer
should_reduce = self.result_type == "reduce"

# 此处省略N行代码

if should_reduce:

# 此处省略N行代码

else:
return self.obj.copy()

  在apply_empty_result中,如果apply的result_type参数的值不是reduce或者默认值None,则会直接复制df并返回,从而导致运行示例代码后直接返回了df的值。

二、解决方法

1、在使用apply方法之前进行条件判断

  示例代码如下:

1
2
3
4
5
if len(df) == 0:
df_new = pandas.DataFrame(columns = ['d', 'e'])
else:
df_new = df.apply(func, axis=1)
df_new = df_new.reset_index(drop=True)

2、在返回的结果中修改列索引

  示例代码如下:

1
2
df_new = df.apply(func, axis=1)
df_new = df_new.reindex(columns=['d', 'e'])