Pandas на месте операции в приложении

Я ожидаю странного поведения панд. В следующем коде

import numpy as np
import pandas as pd

def info(df):
    print(f"whole df: {hex(id(df))}")
    print(f"col a   : {hex(id(df['a']))}")
    print(f"col b   : {hex(id(df['b']))}")
    print(f"col c   : {hex(id(df['c']))}")

def _drop(col):
    print(f"called on  : {col.name}")
    print(f"before drop: {hex(id(col))}")
    col[0] = -1    
    col.dropna(inplace=True)
    col[0] = 1
    print(f"after drop : {hex(id(col))}")   


df = pd.DataFrame([[np.nan, 1.2, np.nan],
                   [5.8, np.nan, np.nan]], columns=['a', 'b', 'c'])

info(df)
df.apply(_drop)
info(df)

если я закомментирую строку dropna() или вызову dropna(inplace=False), я получу ожидаемый результат (потому что dropna создает копию, а я изменяю исходную серию):

     a    b    c
 0  1.0  1.0  1.0
 1  5.8  NaN  NaN

Но когда dropna(inplace=True) операция должна выполняться на месте, тем самым изменяя исходный ряд, но результат, который я получаю, таков:

     a    b    c
 0 -1.0 -1.0 -1.0
 1  5.8  NaN  NaN

Однако я ожидаю, что результат будет таким же, как и в предыдущих случаях. Возвращает ли операция dropna клон, даже если операция выполняется? Я использую панды версии 0.23.1.

Изменить. На основе предоставленных ответов я добавил hex(ids()) вызовов для проверки реальных экземпляров. Приведенный выше код напечатал это (значения могут отличаться для вас, но равенство между ними должно быть одинаковым)

whole df   : 0x1f482392f28
col a      : 0x1f482392f60
col b      : 0x1f48452af98
col c      : 0x1f48452ada0
called on  : a
before drop: 0x1f480dcc2e8
after drop : 0x1f480dcc2e8
called on  : b
before drop: 0x1f480dcc2e8
after drop : 0x1f480dcc2e8
called on  : a
before drop: 0x1f480dcc2e8
after drop : 0x1f480dcc2e8
called on  : b
before drop: 0x1f4ffef1ef0
after drop : 0x1f4ffef1ef0
called on  : c
before drop: 0x1f480dcc2e8
after drop : 0x1f480dcc2e8
whole df   : 0x1f482392f28
col a      : 0x1f482392f60
col b      : 0x1f48452af98
col c      : 0x1f48452ada0

Странно, что функция вызывается 2 раза для столбцов a и b, однако docs говорит, что он вызывается дважды только для первого столбца.

Кроме того, шестнадцатеричное значение для второго прохода столбца b отличается. И то, и другое не происходит, если col.drop() опущено.

Шестнадцатеричные значения предполагают, что .apply() создает новую копию столбцов, однако то, как она распространяет значения обратно в исходное df, мне неизвестно.


person Lukáš Wix    schedule 16.07.2018    source источник
comment
Что вы ожидаете от желаемого результата?   -  person jpp    schedule 16.07.2018
comment
Пожалуйста, обратитесь к этому сообщению для понимания inplace. Опубликовать   -  person Mr. J    schedule 16.07.2018
comment
@Mr.J: Этот пост не отвечает на вопрос автора...   -  person Thomas    schedule 16.07.2018
comment
Это интересный вопрос - если вы дважды добавите print(hex(id(col))) к функции, вы увидите, что копирование не выполняется.   -  person Thomas    schedule 16.07.2018
comment
@Thomas, это говорит о том, как работает inplace.   -  person Mr. J    schedule 16.07.2018
comment
@Lukas: Для меня это называется 3x в первом столбце ... Действительно, странное поведение.   -  person Thomas    schedule 17.07.2018


Ответы (2)


Я пытался рассуждать об этом с помощью концепций переменной области, не считал бы это полным ответом, но, возможно, это будет полезно для кого-то другого.

Когда .apply выполняется для каждой серии, соответствующей здесь аргументу col, внутри области действия _drop() строка col[0] = -1 глобально изменяет «первую строку» df и, следовательно, изменяет ее. Когда dropna() вызывается с inplace=True, NaN фактически отбрасываются, но ТОЛЬКО для серии внутри области действия этой функции, она не назначается глобальной df. Несмотря на то, что он перезаписывает переменную col. Еще одно понимание может заключаться в том, что Документы говорят, что .dropna(inplace=True) возвращает None, а _drop() также возвращает None, поскольку нет оператора return.

person AnotherLazyPeon    schedule 16.07.2018
comment
Строка col[0] = -1 глобально не меняет первую строку df, а только первую строку в серии col (это один конкретный элемент в df). Я обновил свой код, и вы можете видеть, что переменная col не изменяется после удаления. Наконец, я не вижу никакой связи с возвращаемым типом, и .dropna(inplace=True), и _drop() изменяют объект на месте, поэтому им не нужен возвращаемый тип. - person Lukáš Wix; 17.07.2018

Возможно, стоит поднять этот вопрос на github pandas/numpy — для меня это выглядит как неожиданное поведение — если вы добавите в функцию оператор return col, ваш код будет работать так, как ожидалось. Это указывает на то, что действительно создается локальная копия. print(hex(id(col))) подтверждает это.

def _drop(col):
    col[0] = -1
    col.dropna(inplace=True)
    col[0] = 1
    return col # <----

df = pd.DataFrame([[np.nan, 1.2, np.nan],
                   [5.8, np.nan, np.nan]], columns=['a', 'b', 'c'])

df.apply(_drop)
person Thomas    schedule 16.07.2018
comment
Да, этот подход работает, однако строка df.apply(_drop) создает новый фрейм данных. Исходный df остается с -1, как в моем вопросе. Это может быть причиной того, что идентификатор отличается. - person Lukáš Wix; 16.07.2018
comment
Как уже упоминалось, я рекомендую поднять вопрос на странице pandas github. Даже если это не ошибка, это определенно неожиданное поведение. - person Thomas; 17.07.2018
comment
Если вы поднимаете вопрос там, пожалуйста, дайте ссылку здесь, мне также будет интересен результат. - person Thomas; 17.07.2018