Программно нарезать кадр данных Pandas на месте

У меня есть куча фреймов данных, которые я пытаюсь нарезать и присвоить исходным именам. Но я обнаружил, что есть проблема с пространством имен. Ниже то, что у меня есть.

import pandas as pd
import numpy as np

df_a = pd.DataFrame(np.random.rand(14,2), columns = list('XY'))
df_b = pd.DataFrame(np.random.rand(14,2), columns = list('XY'))

mylist =[df_a, df_b]

def truncate_before(list_of_dfts, idx):
    for dfts in list_of_dfts:
        dfts = dfts[idx:]
        print(dfts.head)

truncate_before(mylist, 11)
print(df_a)

В операторах печати в функции truncate_before отображаются 3 строки, соответствующие 11-й, 12-й и 13-й строкам. Но окончательный оператор печати показывает строки с 0-й по 13-ю.

Таким образом, вне функции он возвращается к исходным кадрам данных. У меня сложилось впечатление, что Python передает аргументы по ссылке. Что мне не хватает?


person Spinor8    schedule 30.01.2016    source источник


Ответы (1)


In truncate_before:

def truncate_before(list_of_dfts, idx):
    for dfts in list_of_dfts:
        dfts = dfts[idx:]
        print(dfts.head)

for-loop создает локальную переменную dfts, которая ссылается на кадры данных в list_of_dfts. Но

        dfts = dfts[idx:]

переназначает новое значение для dfts. Это не изменяет DataFrame в list_of_dfts.

См. Факты и мифы об именах и значениях Python, где подробно объясняется, как имена переменных связываются со значениями. и какие операции изменяют значения по сравнению с привязкой новых значений к именам переменных.

Вот несколько альтернатив:

Измените список на месте

def truncate_before(list_of_dfts, idx):
    list_of_dfts[:] = [dfts[idx:] for dfts in list_of_dfts]
    for dfts in list_of_dfts:
        print(dfts.head)

поскольку присваивание list_of_dfts[:] (которое вызывает list_of_dfts.__setitem__) изменяет содержимое list_of_dfts на месте.


import numpy as np
import pandas as pd

df_a = pd.DataFrame(np.random.rand(14,2), columns = list('XY'))
df_b = pd.DataFrame(np.random.rand(14,2), columns = list('XY'))

mylist = [df_a, df_b]

def truncate_before(list_of_dfts, idx):
    list_of_dfts[:] = [dfts[idx:] for dfts in list_of_dfts]

print(mylist[0])
truncate_before(mylist, 11)
print(mylist[0])

показывает, что mylist[0] было усечено. Однако обратите внимание, что df_a по-прежнему ссылается на исходный DataFrame.


Вернуть список и переназначить результат mylist или df_a, df_b

Использование возвращаемых значений может сделать ненужным изменение mylist на месте.

Чтобы переназначить глобальные переменные df_a, df_b на новые значения, вы можете заставить truncate_before возвращать список фреймов данных и переназначить df_a и df_b возвращаемому значению:

def truncate_before(list_of_dfts, idx):
    return [dfts[idx:] for dfts in list_of_dfts]

mylist = truncate_before(mylist, 11)   # or
# df_a, df_b = truncate_before(mylist, 11) # or
# mylist = df_a, df_b = truncate_before(mylist, 11)  

Но обратите внимание, что, вероятно, нехорошо обращаться к DataFrames через mylist, df_a и df_b, поскольку, как показывает приведенный выше пример, значения не остаются скоординированными автоматически. Использование mylist должно быть достаточным.


Используйте метод DataFrame с параметром inplace, например df.drop

dfts.dropinplace=True) изменяет сам dfts:

import numpy as np
import pandas as pd

df_a = pd.DataFrame(np.random.rand(14,2), columns = list('XY'))
df_b = pd.DataFrame(np.random.rand(14,2), columns = list('XY'))

mylist = [df_a, df_b]

def truncate_before(list_of_dfts, idx):
    for dfts in list_of_dfts:
        dfts.drop(dfts.index[:idx], inplace=True)

truncate_before(mylist, 11)
print(mylist[0])
print(df_a)

При изменении dfts на месте оба значения в mylist и df_a и df_b изменяются одновременно.

Обратите внимание, что dfts.drop удаляет строки на основе значения метки индекса. Таким образом, приведенное выше полагается (предполагается), что dfts.index уникален. Если dfts.index не уникально, dfts.drop может содержать больше строк, чем idx строк. Например,

df = pd.DataFrame([1,2], index=['A', 'A'])
df.drop(['A'], inplace=True)

удаляет обе строки, делая df пустым DataFrame.

Обратите также внимание на это предупреждение от основного разработчика Pandas относительно использования inplace:

Мое личное мнение: я никогда не использую операции на месте. Синтаксис труднее читать и не дает никаких преимуществ.

Вероятно, это связано с тем, что под капотом dfts.drop создается новый фрейм данных, а затем вызывается частный метод _update_inplace для присвоения новых данных старому фрейму данных:

def _update_inplace(self, result, verify_is_copy=True):
    """
    replace self internals with result.
    ...
    """
    self._reset_cache()
    self._clear_item_cache()
    self._data = getattr(result,'_data',result)
    self._maybe_update_cacher(verify_is_copy=verify_is_copy)

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

person unutbu    schedule 30.01.2016
comment
Хорошо, mylist был конструкцией для группировки всех отдельных фреймов данных. Я ничего не могу сделать с исходными кадрами данных? Конечно, я мог бы просто сделать это один за другим. df_a = df_a[idx:] и т. д. Но неплохо было бы и программным путем. Позвольте мне дочитать статью, которую вы рекомендовали. - person Spinor8; 30.01.2016
comment
df.drop(..., inplace=True) действительно изменяет df на месте, но из-за того, как операции на месте реализованы в Pandas, нет никакого реального преимущества в этом по сравнению с более простым переназначением имен переменных. Лично я предпочитаю функции, которые возвращают значения, а не функции, которые изменяют значения, поскольку в первом случае синтаксис присваивания дает совершенно ясное представление о том, что изменяется. - person unutbu; 30.01.2016