一、问题及原因
阿猪希望使用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,拥有两个新的列d和e,其中d列与原DataFramedf的a列相同,e列则由df的b列和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'])
|