【问题标题】:Loops over lists of Pandas objects exhibit weird behaviorPandas 对象列表上的循环表现出奇怪的行为
【发布时间】:2020-03-08 20:14:03
【问题描述】:

在谈到 Pandas 对象列表及其循环时,我遇到了一个小麻烦。在我正在处理的一些代码中,有一些 pandas 数据帧被放置在一个列表中,因此可以对所有这些数据帧执行操作。

但是,我注意到某些操作,例如创建新列,在“幼稚”的 Python for loops 中工作,而其他操作,例如反转数据帧的顺序,

  1. 需要显式索引,并且
  2. 不影响原始数据帧(仅它们的副本位于 列表)。

我正在寻求帮助,以使我的 MWE 的第二部分像第一部分一样容易地工作,并且首先要深入了解导致这种差异的潜在逻辑。

## Creating data
import pandas as pd
from io import StringIO

data = StringIO(
"""
date;time;random
2019-06-12;19:59:59+00:00;99
2019-06-12;19:59:54+00:00;200
2019-06-12;19:59:52+00:00;65
2019-06-12;19:59:34+00:00;140
"""
               )

df = pd.read_csv(data, sep=";")

print(df)

## Creating list; there is only one dataframe in this list to make the
## code easier to work with, but in actuality I am working with >20 dataframes
df_list = [df]

## First operation - successfully adds new column to both original df and df_list[0]
for dataframe in df_list:
    dataframe['date_time'] = pd.to_datetime(dataframe['date']+' '+dataframe['time'], utc=True)
print(df)
print(df_list[0])

## Second operation - successful only if using explicit indexing over list, first commented segment does nothing;
## using second segment works, but does not effect original df, only df_list[0].

# for dataframe in df_list:
#     dataframe = dataframe.iloc[::-1]
#     dataframe.reset_index(drop=True, inplace=True)

for i in range(len(df_list)):
    df_list[i] = df_list[i].iloc[::-1]
    df_list[i].reset_index(drop=True, inplace=True)

print(df)
print(df_list[0])

【问题讨论】:

  • for in 循环不能通过引用工作。使用enumerate,然后引用df_list[i]
  • df_list 有什么意义,你想在这里做什么?使用 Pandas 时最好尽可能避免显式循环,因此这会引发一些问题。第一个/原始 DataFrame 来自哪里?
  • @AlexanderCécile,我认为使用列表的原因可能不仅仅是一个数据帧,而是一个数据帧列表。
  • @BillChen 这当然有可能,我只是被df_list = [df]弄糊涂了。
  • @BillChen 啊,实际上你可能是对的,我只是注意到他在帖子中说列表中有多个 DataFrame。

标签: python pandas loops dataframe shallow-copy


【解决方案1】:

第一个操作dataframe['date_time']= 表明它是就地操作,而不是赋值

之所以在第二个操作中,第二种方法有效,是因为当您循环遍历一个不使用索引的列表时,您创建了一个与该列表无关的新变量,并将其分配给一个新值。

a = [1,2,3]
for i in a:
    i = 0
print(a)
print(i)

输出是:

[1, 2, 3]
0

因此,在您的情况下,当您 for dataframe in df_list: 时,您会创建一个新变量 dataframe,它引用或指向 df_list 中每个元素的地址。然后,当您将它们分配给反向数据框时,dataframe 引用或指向一个新变量。

这里的问题是您(或我们)混淆了就地操作与分配。

【讨论】:

  • 我还是不明白。 dataframe['date_time'] = pd.to_datetime()dataframe = dataframe.iloc[::-1] 看起来都在设置一些东西。在第一种情况下,它将数据帧的一个方面,一个新列设置为等于某物,在第二种情况下,它将数据帧设置为等于其自身的反转版本。这两种情况看起来都和我完全一样,所以我还是不明白我哪里错了。
  • 当您使用dataframe['date_time'] = 时,您将更改dataframe 本身。这是因为有一个下划线操作,列选择['date_time'] 发生了。该操作由 pandas 提供,是一种就地操作。但是如果你只使用python原生=,这里它只是将引用传递给一个新变量。我希望我可以为您绘制,但尝试使用简单的变量来玩它,我需要一段时间才能意识到这一点,可能会有其他答案比我解释得更好。 :-)
【解决方案2】:

我发现你问题的重点是前提是对浅拷贝元素(df_list[0])的各种操作都会反映在原来的可变实例(df)中,但不包括赋值如此处所述:Python: Assignment vs Shallow Copy vs Deep Copy

让我们看看这个正常的例子:

In [29]: df_list = [df]

In [30]: df_list[0]['date_time'] = pd.to_datetime(df_list[0]['date']+' '+df_list[0]['time'], utc=True)

In [31]: df_list
Out[31]:
[         date            time  random                 date_time
 0  2019-06-12  19:59:59+00:00      99 2019-06-12 19:59:59+00:00
 1  2019-06-12  19:59:54+00:00     200 2019-06-12 19:59:54+00:00
 2  2019-06-12  19:59:52+00:00      65 2019-06-12 19:59:52+00:00
 3  2019-06-12  19:59:34+00:00     140 2019-06-12 19:59:34+00:00]

In [32]: df
Out[32]:
         date            time  random                 date_time
0  2019-06-12  19:59:59+00:00      99 2019-06-12 19:59:59+00:00
1  2019-06-12  19:59:54+00:00     200 2019-06-12 19:59:54+00:00
2  2019-06-12  19:59:52+00:00      65 2019-06-12 19:59:52+00:00
3  2019-06-12  19:59:34+00:00     140 2019-06-12 19:59:34+00:00

它按预期工作。也就是df_list有自己的指针但是df_list[0]df共享同一个指针,那么df会随着df_list[0]的变化而变化。

In [35]: hex(id(df))
Out[35]: '0x7f2c90e8d978'

In [36]: hex(id(df_list[0]))
Out[36]: '0x7f2c90e8d978'

In [37]: hex(id(df_list))
Out[37]: '0x7f2c90d68188'

查看Python变量内存地址的方法:answer to "print memory address of Python variable [duplicate]"

但在以下示例中,我们面临不同的情况。

In [22]: df_list = [df]
In [23]: df_list[0] = df_list[0].iloc[::-1]

In [24]: df_list
Out[24]:
[         date            time  random
 3  2019-06-12  19:59:34+00:00     140
 2  2019-06-12  19:59:52+00:00      65
 1  2019-06-12  19:59:54+00:00     200
 0  2019-06-12  19:59:59+00:00      99]

In [25]: df
Out[25]:
         date            time  random
0  2019-06-12  19:59:59+00:00      99
1  2019-06-12  19:59:54+00:00     200
2  2019-06-12  19:59:52+00:00      65
3  2019-06-12  19:59:34+00:00     140

In [26]: df_list[0]['date_time'] = pd.to_datetime(df_list[0]['date']+' '+df_list[0]['time'], utc=True)
/usr/bin/ipython3:1: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  #! /bin/sh
In [27]: df_list
Out[27]:
[         date            time  random                 date_time
 3  2019-06-12  19:59:34+00:00     140 2019-06-12 19:59:34+00:00
 2  2019-06-12  19:59:52+00:00      65 2019-06-12 19:59:52+00:00
 1  2019-06-12  19:59:54+00:00     200 2019-06-12 19:59:54+00:00
 0  2019-06-12  19:59:59+00:00      99 2019-06-12 19:59:59+00:00]

In [28]: df
Out[28]:
         date            time  random
0  2019-06-12  19:59:59+00:00      99
1  2019-06-12  19:59:54+00:00     200
2  2019-06-12  19:59:52+00:00      65
3  2019-06-12  19:59:34+00:00     140

原因是我们进行了添加和删除项目之类的操作,这意味着我们首先删除了df_list[0],然后添加了一个新的df_list[0](或替换),这两者都不会反映在原始可变对象实例中.

In [40]: hex(id(df_list[0]))
Out[40]: '0x7f2c90d6ea58'

In [41]: hex(id(df))
Out[41]: '0x7f2c90e8d978'

我们可以看到df_list[0] 的指针发生了变化。

让我们看下面这个简单的例子:

In [44]: a = [[1, 2, 3], [4, 5]]

In [45]: b = a[:]

In [46]: a[0] = [0, 0, 0]

In [47]: b
Out[47]: [[1, 2, 3], [4, 5]]

In [48]: a
Out[48]: [[0, 0, 0], [4, 5]]

这可能不是您怀疑的 for 循环引起的,而是分配和浅拷贝之间的差异。 HTH :)

【讨论】:

  • 那么,有没有办法在 Python 中创建一个更复杂的对象列表——这里是 pandas 数据框——并通过所述列表的操作来更改底层对象?
  • @Coolio2654 我认为如果指针保持不变,则可以更改底层对象。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-13
相关资源
最近更新 更多