Python expndtw-1.apply意外地更改了在位的数据帧

Python expndtw-1.apply意外地更改了在位的数据帧,python,pandas,dataframe,pandas-apply,Python,Pandas,Dataframe,Pandas Apply,据我所知,pandas.DataFrame.apply不会就地应用更改,我们应该使用其返回对象来持久化任何更改。但是,我发现以下不一致的行为: 为了确保原始df保持不变,让我们应用一个虚拟函数: >>> def foo(row: pd.Series): ... row['b'] = '42' >>> df = pd.DataFrame([('a0','b0'),('a1','b1')], columns=['a', 'b']) >>>

据我所知,pandas.DataFrame.apply不会就地应用更改,我们应该使用其返回对象来持久化任何更改。但是,我发现以下不一致的行为:

为了确保原始df保持不变,让我们应用一个虚拟函数:

>>> def foo(row: pd.Series):
...     row['b'] = '42'

>>> df = pd.DataFrame([('a0','b0'),('a1','b1')], columns=['a', 'b'])
>>> df.apply(foo, axis=1)
>>> df
    a   b
0   a0  b0
1   a1  b1
这与预期的一样。但是,如果我们修改初始化此df的方式,foo将应用更改:

>>> df2 = pd.DataFrame(columns=['a', 'b'])
>>> df2['a'] = ['a0','a1']
>>> df2['b'] = ['b0','b1']
>>> df2.apply(foo, axis=1)
>>> df2
    a   b
0   a0  42
1   a1  42
我还注意到,如果列数据类型不是“object”类型,则上述情况不成立。为什么apply()在这两种上下文中的行为不同

Python:3.6.5


熊猫:0.23.1

有趣的问题!我相信您看到的行为是您使用
apply
方式的产物

正如您正确指出的,
apply
不用于修改数据帧。但是,由于
apply
采用任意函数,因此不能保证应用该函数将是幂等函数,并且不会更改数据帧。在这里,您已经找到了这种行为的一个很好的例子,因为您的函数
foo
尝试修改它所传递的行
apply

使用
apply
修改行可能会导致这些副作用。这不是最好的做法

改为考虑<代码> >应用< /COD>的习惯用法。函数

apply
通常用于创建新列。下面是一个典型使用
apply
的示例,我相信这将引导您远离这个潜在的麻烦领域:

import pandas as pd
# construct df2 just like you did
df2 = pd.DataFrame(columns=['a', 'b'])
df2['a'] = ['a0','b0']
df2['b'] = ['a1','b1']

df2['b_copy'] = df2.apply(lambda row: row['b'], axis=1) # apply to each row
df2['b_replace'] = df2.apply(lambda row: '42', axis=1) 
df2['b_reverse'] = df2['b'].apply(lambda val: val[::-1]) # apply to each value in b column

print(df2)

# output:
#     a   b b_copy b_replace b_reverse
# 0  a0  a1     a1        42        1a
# 1  b0  b1     b1        42        1b
请注意,pandas将一行或一个单元格作为
apply
的第一个参数传递给函数,然后将函数的输出存储在您选择的列中


如果您想逐行修改数据帧,请查看
iterrows
loc
以了解最惯用的路线。

可能会晚些,但我认为这可能对解决此问题的人特别有帮助

当我们使用
foo
时,如下所示:

def foo(row: pd.Series):
    row['b'] = '42'
然后将其用于:

df.apply(foo, axis=1)
我们预计df不会发生任何变化,但会发生。为什么?

让我们回顾一下引擎盖下发生的事情:

apply
函数调用
foo
并将一行传递给它。由于它不是python中特定类型的
类型(如int、float、str等),而是一个对象,因此根据python规则,它是通过引用而不是通过值传递的。因此,它与
apply
函数发送的行完全等效。(值相等,并且两个值都指向同一块ram。) 因此,在
foo
函数中对
的任何更改都将立即更改
——其类型为
pandas.series
,并指向
df.row
驻留的内存块

我们可以重写
foo
(我将其命名为
bar
)函数,以不更改任何就地。(通过深度复制
,这意味着在ram的另一个单元上创建具有相同值的另一行)。这是在
apply
函数中使用
lambda
时经常发生的情况

def bar(row: pd.Series):
    row_temp=row.copy(deep=True)
    row_temp['b'] = '42'
    return row_temp
完整代码

将熊猫作为pd导入
#发生了变化——不像拉姆达
def foo(行:pd.系列):
行['b']='42'
#不要在原地更改df——就像lambda一样工作
def棒(世界其他地区:pd系列):
行临时=行复制(深=真)
行温度['b']='42'
返回行温度
df2=pd.DataFrame(列=['a','b'])
df2['a']=['a0','a1']
df2['b']=['b0','b1']
打印(df2)
#不变
df_b=df2.应用(杆,轴=1)
打印(df2)
#酒吧功能正常
打印(df_b)
打印(df2)
#就地变化
df2.应用(foo,轴=1)
打印(df2)
输出

#df2 before any change
    a   b
0  a0  b0
1  a1  b1

#calling df2.apply(bar, axis=1) not changed df2 inplace
    a   b
0  a0  b0
1  a1  b1

#df_b = df2.apply(bar, axis=1) #bar is working as expected
    a   b
0  a0  42
1  a1  42

#print df2 again to assure it is not changed
    a   b
0  a0  b0
1  a1  b1

#call df2.apply(foo, axis=1) -- as we see foo changed df2 inplace ( to compare with bar)
    a   b
0  a0  42
1  a1  42

您正在
df2['a']
中插入值
['a0','b0']
。但在df2输出中,数据是不同的。为什么?编辑:更新了df2。谢谢@roganjosh和Arihant,结果发现这与你看到的行为无关。好问题:)这真的回答了为什么它不在第一个实例中修改数据帧吗?