【问题标题】:Pandas: "Returning a view versus a copy" warning when constructing a new dataframe in a loopPandas:在循环中构建新数据框时出现“返回视图与副本”警告
【发布时间】:2019-12-20 17:59:35
【问题描述】:

假设我有一个包含两个日期时间列的数据框,我想分析它们之间的区别:

import pandas as pd

csv = [
         ['2019-08-03 00:00:00', '2019-08-01 15:00:00', 4],
         ['2019-08-03 00:00:00', '2019-08-01 10:00:00', 6],
         ['2019-08-03 00:00:00', '2019-08-01 16:00:00', 8],
         ['2019-08-04 00:00:00', '2019-08-02 19:00:00', 3],
         ['2019-08-04 00:00:00', '2019-08-02 13:00:00', 4],
         ['2019-08-04 00:00:00', '2019-08-02 11:00:00', 5]
]

df = pd.DataFrame(csv, columns=['delivery_date', 'dispatch_date', 'order_size'])
df['delivery_date'] = pd.to_datetime(df['delivery_date'])
df['dispatch_date'] = pd.to_datetime(df['dispatch_date'])
df['transit_time'] = (df['delivery_date']-df['dispatch_date'])
df = df.set_index(['delivery_date','transit_time'])

好的,现在我们有了类似的东西:

                                    dispatch_date  order_size
delivery_date transit_time                                   
2019-08-03    1 days 09:00:00 2019-08-01 15:00:00           4
              1 days 14:00:00 2019-08-01 10:00:00           6
              1 days 08:00:00 2019-08-01 16:00:00           8
2019-08-04    1 days 05:00:00 2019-08-02 19:00:00           3
              1 days 11:00:00 2019-08-02 13:00:00           4
              1 days 13:00:00 2019-08-02 11:00:00           5

例如,对于每个交货日期,我想知道哪个交货最快(交货时间最短)。我想将结果保存到一个新的数据框中,其中包含原始数据框中的所有列。所以我这样迭代:

delivery_dates = df.index.get_level_values(0).unique()
df_ouput = pd.DataFrame()

for date in delivery_dates:    
    df_analyzed = df.loc[(date, )].sort_index()
    df_result = df_analyzed.iloc[[df_analyzed.index.get_loc(0, method='nearest')]]    
    df_result.loc[:,'delivery_date'] = date
    df_ouput = df_ouput.append(df_result)

df_ouput = df_ouput.reset_index().set_index(['delivery_date'])

而且结果是正确的:

                 transit_time       dispatch_date  order_size
delivery_date                                                
2019-08-03    1 days 08:00:00 2019-08-01 16:00:00           8
2019-08-04    1 days 05:00:00 2019-08-02 19:00:00           3

但我收到警告:

试图在 DataFrame 的切片副本上设置一个值。尝试 使用 .loc[row_indexer,col_indexer] = value 代替请参阅注意事项 文档: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy

我不知道为什么,因为我已经在使用“.loc”方法进行赋值:

df_result.loc[:,'delivery_date'] = date

但我无法摆脱警告,所以我来到了这个罕见的解决方案:

delivery_dates = df.index.get_level_values(0).unique()
df_ouput = pd.DataFrame()

for date in delivery_dates:    
    df_analyzed = df.loc[(date, )].sort_index()
    df_result = df_analyzed.iloc[[df_analyzed.index.get_loc(0, method='nearest')]]    
    df_result_2 = df_result.copy()
    df_result_2.loc[:,'delivery_date'] = date
    df_ouput = df_ouput.append(df_result_2)

df_ouput = df_ouput.reset_index().set_index(['delivery_date'])

如果进行复制,则不会显示警告。但为什么?有没有更好的方法来做我想做的事?

【问题讨论】:

标签: python pandas


【解决方案1】:

您的解决方案应该更改为copy 进行过滤:

delivery_dates = df.index.get_level_values(0).unique()
df_ouput = pd.DataFrame()

for date in delivery_dates:    
    df_analyzed = df.loc[date].sort_index()
    df_result = df_analyzed.iloc[[df_analyzed.index.get_loc(0, method='nearest')]].copy()    
    df_result['delivery_date'] = date
    df_ouput = df_ouput.append(df_result)

df_ouput = df_ouput.reset_index().set_index(['delivery_date'])
print (df_ouput)
                 transit_time       dispatch_date  order_size
delivery_date                                                
2019-08-03    1 days 08:00:00 2019-08-01 16:00:00           8
2019-08-04    1 days 05:00:00 2019-08-02 19:00:00           3

GroupBy.apply中的自定义函数更好的解决方案:

def f(x):
    x = x.sort_index(level=1)
    s = x.iloc[[x.index.get_level_values(1).get_loc(0, method='nearest')]]
    return s

df = df.groupby(level=0).apply(f).reset_index(level=0, drop=True)
print (df)
                                    dispatch_date  order_size
delivery_date transit_time                                   
2019-08-03    1 days 08:00:00 2019-08-01 16:00:00           8
2019-08-04    1 days 05:00:00 2019-08-02 19:00:00           3

或者:

def f(x):
    x = x.sort_index(level=1)
    s = x.iloc[[x.index.get_level_values(1).get_loc(0, method='nearest')]]
    return s

df = df.groupby(level=0, group_keys=False).apply(f)
print (df)
                                    dispatch_date  order_size
delivery_date transit_time                                   
2019-08-03    1 days 08:00:00 2019-08-01 16:00:00           8
2019-08-04    1 days 05:00:00 2019-08-02 19:00:00           3

如果理解得好:

df = df.sort_index()
df = df[~df.index.get_level_values(0).duplicated()]
print (df)
                                    dispatch_date  order_size
delivery_date transit_time                                   
2019-08-03    1 days 08:00:00 2019-08-01 16:00:00           8
2019-08-04    1 days 05:00:00 2019-08-02 19:00:00           3

【讨论】:

  • 使用def f(x) 方法的不错的解决方案。谢谢你。但是,我不明白为什么GroupBy.apply() 重复第一个索引,然后你必须做reset_index 至于我的方法,我仍然不明白为什么你需要使用.copy()。据我了解,df_analyzed.iloc[] 已经返回一个新的数据帧,您将其存储为df_result。那你为什么要复制呢?
  • @eliteA92 - 因为它为每个组返回新的 DataFrame,所以添加了 group_keys=False 的新解决方案以避免它。
  • 我明白了,谢谢。至于我的方法,我仍然不明白为什么需要使用 .copy()。据我了解,df_analyzed.iloc[] 已经返回一个新的数据帧,您将其存储为 df_result。那你为什么要复制呢?
  • @eliteA92 - 我认为不是,也许最好的解释是here
【解决方案2】:

获取请求输出的非常简单的代码

OP 试图回答问题的方式非常复杂。在这种情况下,设置索引只会让一切变得更加困难。

首先,我们创建数据框。无需设置索引。

import pandas as pd

csv = [
         ['2019-08-03 00:00:00', '2019-08-01 15:00:00', 4],
         ['2019-08-03 00:00:00', '2019-08-01 10:00:00', 6],
         ['2019-08-03 00:00:00', '2019-08-01 16:00:00', 8],
         ['2019-08-04 00:00:00', '2019-08-02 19:00:00', 3],
         ['2019-08-04 00:00:00', '2019-08-02 13:00:00', 4],
         ['2019-08-04 00:00:00', '2019-08-02 11:00:00', 5]
]

df = pd.DataFrame(csv, columns=['delivery_date', 'dispatch_date', 'order_size'])
df['delivery_date'] = pd.to_datetime(df['delivery_date'])
df['dispatch_date'] = pd.to_datetime(df['dispatch_date'])
df['transit_time'] = (df['delivery_date']-df['dispatch_date'])
# df = df.set_index(['delivery_date','transit_time']) # <------- Just remove this!

之后,需要的操作是:

df_ouput = df.loc[df.groupby('delivery_date').transit_time.idxmin()] # <--- Only these operations are needed!

# The next line is just in case you want to set delivery_date as your index
df_ouput = df_ouput.reset_index(drop=True).set_index('delivery_date')

print(df_ouput)
                    dispatch_date  order_size    transit_time
delivery_date                                                
2019-08-03    2019-08-01 16:00:00           8 1 days 08:00:00
2019-08-04    2019-08-02 19:00:00           3 1 days 05:00:00

groupby 方法允许根据某些条件拆分 DataFrame 中的行。在这种情况下,我们正在拆分行,因为它们具有相同的 delivery_date 值。然后,idxminGroupBy objects 的一种方法,它给出了给定轴上最小值的索引(请参阅idxminhere 的文档)。我把这个想法从这个answer带到了问题Pandas GroupBy and select rows with the minimum value in a specific column,这完全是同一类问题。

请注意,我们在对df 进行切片时使用了loc,并为索引提供了一个数值。

df_output = df.loc[df.groupby('delivery_date').transit_time.idxmin()]

这是有效的,因为在我们的例子中df 的索引由整数值组成,因为我们让DataFrame 默认创建索引。


引发SettingWithCopyWarning的原因

我将通过首先构建论点来构建最终答案的逻辑。我剖析了产生警告的部分,产生警告的行是:

for date in delivery_dates:    
    df_analyzed = df.loc[(date, )].sort_index()
    df_result = df_analyzed.iloc[[df_analyzed.index.get_loc(0, method='nearest')]]    
    df_result.loc[:,'delivery_date'] = date # <------- THIS IS THE LINE!
    df_ouput = df_ouput.append(df_result)

我寻找使用 pandas 的 DataFrames 创建新列的方法:检查 here

当使用DataFrame.assign() 时:

delivery_dates = df.index.get_level_values(0).unique()
df_ouput = pd.DataFrame()

for date in delivery_dates:    
    df_analyzed = df.loc[(date, )].sort_index()
    df_result = df_analyzed.iloc[[df_analyzed.index.get_loc(timedelta(0, 0, 0), method='nearest')]]
    # Assigning the value using the assign method doesn't raise the warning.
    df_result = df_result.assign(delivery_date = [date])
    df_ouput = df_ouput.append(df_result)

df_ouput = df_ouput.reset_index().set_index(['delivery_date'])

我没有收到任何警告。

从问题How to deal with SettingWithCopyWarning in Pandas中引用answer

创建SettingWithCopyWarning 是为了标记潜在的混淆 “链式”分配

为什么会引发警告的解释是,您使用的代码涉及可能令人困惑的“链式”赋值。更具体地说,可能令人困惑的“链式”分配来自您在中所做的两个切片:

    # Most likely the potentially confusing "chained" assignments from from
    # these two lines
    df_result = df_analyzed.iloc[[df_analyzed.index.get_loc(0, method='nearest')]]    
    df_result.loc[:,'delivery_date'] = date

最有可能的是,导致警告的行正在创建对象的副本并尝试为该副本分配值。这可能会使您想要更改的数据保持不变。


最后的想法

确定为什么这些可能会造成混淆的“链式”分配通常很困难。例如,在另一个 answerHow to deal with SettingWithCopyWarning in Pandas 中,它说:

链式赋值的问题在于,通常很难预测返回的是视图还是副本,因此,当您尝试将值重新赋值时,这很大程度上会成为一个问题。 p>

在处理pandas 中的作业时,我们应始终使用最不模糊的方法,以避免出现错误或出现此警告。

【讨论】:

    猜你喜欢
    • 2021-05-25
    • 1970-01-01
    • 1970-01-01
    • 2014-04-28
    • 1970-01-01
    • 2012-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多