pandas df.apply 意外更改数据框
pandas df.apply unexpectedly changes dataframe inplace
根据我的理解,pandas.DataFrame.apply 不会就地应用更改,我们应该使用它的 return 对象来保存任何更改。但是,我发现了以下不一致的行为:
为了确保原始 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
Pandas: 0.23.1
有趣的问题!我相信您看到的行为是您使用 apply
.
方式的产物
正如您正确指出的那样,apply
不适用于修改数据框。但是,由于 apply
采用任意函数,因此不能保证应用该函数是幂等的并且不会更改数据帧。在这里,您找到了该行为的一个很好的例子,因为您的函数 foo
试图修改它由 apply
.
传递的行
使用 apply
修改行可能会导致这些副作用。这不是最佳做法。
相反,请考虑 apply
的这种惯用方法。函数 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 中的特定 types
类型(如 int、float、str 等),而是一个对象,因此根据 python 规则,它不是通过引用传递的按价值。所以它完全等同于 apply
函数发送的行。(值相等并且都指向同一个 ram 块。)
因此,对 foo
函数中 row
的任何更改都会更改 row
- 它的类型是 pandas.series
并且指向 df.row
所在的内存块 -立即。
我们可以重写 foo
(我将其命名为 bar
)函数以不更改任何内容 inplace。 (通过深度复制 row
这意味着在 ram 的另一个单元格上创建具有相同值的另一行)。这就是我们在 apply
函数中使用 lambda
时真正发生的事情。
def bar(row: pd.Series):
row_temp=row.copy(deep=True)
row_temp['b'] = '42'
return row_temp
完整代码
import pandas as pd
#Changes df in place -- not like lamda
def foo(row: pd.Series):
row['b'] = '42'
#Do not change df inplace -- works like lambda
def bar(row: pd.Series):
row_temp = row.copy(deep=True)
row_temp['b'] = '42'
return row_temp
df2 = pd.DataFrame(columns=['a', 'b'])
df2['a'] = ['a0', 'a1']
df2['b'] = ['b0', 'b1']
print(df2)
# No change inplace
df_b = df2.apply(bar, axis=1)
print(df2)
# bar function works
print(df_b)
print(df2)
# Changes inplace
df2.apply(foo, axis=1)
print(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
根据我的理解,pandas.DataFrame.apply 不会就地应用更改,我们应该使用它的 return 对象来保存任何更改。但是,我发现了以下不一致的行为:
为了确保原始 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
Pandas: 0.23.1
有趣的问题!我相信您看到的行为是您使用 apply
.
正如您正确指出的那样,apply
不适用于修改数据框。但是,由于 apply
采用任意函数,因此不能保证应用该函数是幂等的并且不会更改数据帧。在这里,您找到了该行为的一个很好的例子,因为您的函数 foo
试图修改它由 apply
.
使用 apply
修改行可能会导致这些副作用。这不是最佳做法。
相反,请考虑 apply
的这种惯用方法。函数 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 中的特定 types
类型(如 int、float、str 等),而是一个对象,因此根据 python 规则,它不是通过引用传递的按价值。所以它完全等同于 apply
函数发送的行。(值相等并且都指向同一个 ram 块。)
因此,对 foo
函数中 row
的任何更改都会更改 row
- 它的类型是 pandas.series
并且指向 df.row
所在的内存块 -立即。
我们可以重写 foo
(我将其命名为 bar
)函数以不更改任何内容 inplace。 (通过深度复制 row
这意味着在 ram 的另一个单元格上创建具有相同值的另一行)。这就是我们在 apply
函数中使用 lambda
时真正发生的事情。
def bar(row: pd.Series):
row_temp=row.copy(deep=True)
row_temp['b'] = '42'
return row_temp
完整代码
import pandas as pd
#Changes df in place -- not like lamda
def foo(row: pd.Series):
row['b'] = '42'
#Do not change df inplace -- works like lambda
def bar(row: pd.Series):
row_temp = row.copy(deep=True)
row_temp['b'] = '42'
return row_temp
df2 = pd.DataFrame(columns=['a', 'b'])
df2['a'] = ['a0', 'a1']
df2['b'] = ['b0', 'b1']
print(df2)
# No change inplace
df_b = df2.apply(bar, axis=1)
print(df2)
# bar function works
print(df_b)
print(df2)
# Changes inplace
df2.apply(foo, axis=1)
print(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