熊猫df.apply意外地更改了数据帧的位置

时间:2018-09-22 15:10:26

标签: 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'])
>>> 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

我还注意到,如果列dtypes的类型不是“ object”,则上述情况并不正确。为什么apply()在这两种情况下的行为不同?

Python:3.6.5

熊猫:0.23.1

2 个答案:

答案 0 :(得分: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

请注意,熊猫将行或单元格传递给您作为apply的第一个参数的函数,然后将函数的输出存储在您选择的列中。

如果您想逐行修改数据帧,请查看iterrowsloc中最惯用的路由。

答案 1 :(得分:1)

也许晚了,但我认为这可能对提出这个问题的人特别有帮助。

当我们使用 nextLine() 时:

foo

然后在:

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

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

让我们回顾一下幕后发生的事情:

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

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

apply

完整代码

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)