【问题标题】:why should I make a copy of a data frame in pandas为什么我要在 pandas 中复制数据框
【发布时间】:2015-02-24 17:11:26
【问题描述】:

从父数据帧中选择子数据帧时,我注意到一些程序员使用.copy() 方法复制数据帧。例如,

X = my_dataframe[features_list].copy()

...而不仅仅是

X = my_dataframe[features_list]

他们为什么要复制数据框?如果我不制作副本会怎样?

【问题讨论】:

  • 我猜他们正在采取额外的预防措施来不修改源数据框。可能没有必要,但是当您以交互方式将某些东西放在一起时,安全总比后悔好。
  • 我认为这不是一个给出否定答案的愚蠢问题。

标签: python pandas chained-assignment


【解决方案1】:

Pandas Deep copy 保持初始 DataFrame 不变。

当您想要规范化 DataFrame 并希望保持初始 df 不变时,此功能特别有用。 例如:

df = pd.DataFrame(np.arange(20).reshape(2,10))

然后你规范化数据:

# Using Sklearn MinMaxSacaler method
scaler = preprocessing.MinMaxScaler()

然后你根据第一个创建一个新的 df 并希望第一个保持不变, 你必须使用 .copy() 方法

new_df = pd.DataFrame(df).copy() # Deep Copy
for i in range(10):
    pd_features[i] = scaler.fit_transform(unnormal_pd_features[i].values.reshape(-1,1))

否则你原来的df也会改变。

【讨论】:

    【解决方案2】:

    假设您有如下数据框

    df1
         A    B    C    D
    4 -1.0 -1.0 -1.0 -1.0
    5 -1.0 -1.0 -1.0 -1.0
    6 -1.0 -1.0 -1.0 -1.0
    6 -1.0 -1.0 -1.0 -1.0
    

    当您想创建另一个与df1 相同的df2 时,不带copy

    df2=df1
    df2
         A    B    C    D
    4 -1.0 -1.0 -1.0 -1.0
    5 -1.0 -1.0 -1.0 -1.0
    6 -1.0 -1.0 -1.0 -1.0
    6 -1.0 -1.0 -1.0 -1.0
    

    并且只想修改df2值如下

    df2.iloc[0,0]='changed'
    
    df2
             A    B    C    D
    4  changed -1.0 -1.0 -1.0
    5       -1 -1.0 -1.0 -1.0
    6       -1 -1.0 -1.0 -1.0
    6       -1 -1.0 -1.0 -1.0
    

    同时df1也发生了变化

    df1
             A    B    C    D
    4  changed -1.0 -1.0 -1.0
    5       -1 -1.0 -1.0 -1.0
    6       -1 -1.0 -1.0 -1.0
    6       -1 -1.0 -1.0 -1.0
    

    由于两个df同一个object,我们可以使用id来检查

    id(df1)
    140367679979600
    id(df2)
    140367679979600
    

    所以它们作为同一个对象,一个改变另一个也将传递相同的值。


    如果我们添加copy,现在df1df2 被视为不同的object,如果我们对其中一个进行相同的更改,另一个不会更改。

    df2=df1.copy()
    id(df1)
    140367679979600
    id(df2)
    140367674641232
    
    df1.iloc[0,0]='changedback'
    df2
             A    B    C    D
    4  changed -1.0 -1.0 -1.0
    5       -1 -1.0 -1.0 -1.0
    6       -1 -1.0 -1.0 -1.0
    6       -1 -1.0 -1.0 -1.0
    

    值得一提的是,当您对原始数据框进行子集化时,添加副本也是安全的,以避免SettingWithCopyWarning

    【讨论】:

      【解决方案3】:

      主要目的是避免链式索引并消除SettingWithCopyWarning

      这里的链式索引类似于dfc['A'][0] = 111

      文档说在Returning a view versus a copy 中应该避免使用链式索引。以下是该文档中稍作修改的示例:

      In [1]: import pandas as pd
      
      In [2]: dfc = pd.DataFrame({'A':['aaa','bbb','ccc'],'B':[1,2,3]})
      
      In [3]: dfc
      Out[3]:
          A   B
      0   aaa 1
      1   bbb 2
      2   ccc 3
      
      In [4]: aColumn = dfc['A']
      
      In [5]: aColumn[0] = 111
      SettingWithCopyWarning: 
      A value is trying to be set on a copy of a slice from a DataFrame
      
      In [6]: dfc
      Out[6]:
          A   B
      0   111 1
      1   bbb 2
      2   ccc 3
      

      这里aColumn 是一个视图,而不是原始DataFrame 的副本,因此修改aColumn 将导致原始dfc 也被修改。接下来,如果我们先索引该行:

      In [7]: zero_row = dfc.loc[0]
      
      In [8]: zero_row['A'] = 222
      SettingWithCopyWarning: 
      A value is trying to be set on a copy of a slice from a DataFrame
      
      In [9]: dfc
      Out[9]:
          A   B
      0   111 1
      1   bbb 2
      2   ccc 3
      

      这次zero_row是一个副本,所以原来的dfc没有修改。

      从上面这两个例子中,我们看到是否要更改原始 DataFrame 是模棱两可的。如果您编写如下内容,这尤其危险:

      In [10]: dfc.loc[0]['A'] = 333
      SettingWithCopyWarning: 
      A value is trying to be set on a copy of a slice from a DataFrame
      
      In [11]: dfc
      Out[11]:
          A   B
      0   111 1
      1   bbb 2
      2   ccc 3
      

      这一次完全没用。这里我们想更改dfc,但我们实际上修改了一个中间值dfc.loc[0],它是一个副本,并被立即丢弃。很难预测像dfc.loc[0]dfc['A']这样的中间值是视图还是副本,因此无法保证原始DataFrame是否会更新。这就是为什么应该避免链式索引,pandas 会为这种链式索引更新生成SettingWithCopyWarning

      现在使用.copy()。要消除警告,请制作副本以明确表达您的意图:

      In [12]: zero_row_copy = dfc.loc[0].copy()
      
      In [13]: zero_row_copy['A'] = 444 # This time no warning
      

      由于您正在修改副本,因此您知道原始的 dfc 永远不会改变,您也不会期望它会改变。您的期望与行为匹配,然后SettingWithCopyWarning 消失。

      注意,如果您确实要修改原始DataFrame,文档建议您使用loc

      In [14]: dfc.loc[0,'A'] = 555
      
      In [15]: dfc
      Out[15]:
          A   B
      0   555 1
      1   bbb 2
      2   ccc 3
      

      【讨论】:

      • 不错的答案。之前我没有注意到 pandas 会发出关于“尝试在切片的副本上设置”的警告,即使对象是视图而不是副本也是如此。 aColumn 的第一个例子让我很惊讶。
      【解决方案4】:

      一般来说,处理副本比处理原始数据框更安全,除非您知道不再需要原始数据框并希望继续处理经过处理的版本。通常情况下,您仍然可以将原始数据框与操作版本等进行比较。因此,大多数人在最后进行复制和合并。

      【讨论】:

        【解决方案5】:

        有必要提到返回副本或视图取决于索引类型。

        熊猫文档说:

        返回视图与副本

        关于何时返回数据视图的规则完全是 依赖于 NumPy。每当标签数组或布尔向量 参与索引操作,结果将是一个副本。 使用单个标签/标量索引和切片,例如df.ix[3:6] 或 df.ix[:, 'A'],会返回一个视图。

        【解决方案6】:

        因为如果您不制作副本,那么即使您将 dataFrame 分配给不同的名称,索引仍然可以在其他地方进行操作。

        例如:

        df2 = df
        func1(df2)
        func2(df)
        

        func1可以通过修改df2来修改df,所以要避免:

        df2 = df.copy()
        func1(df2)
        func2(df)
        

        【讨论】:

        • 等等等等,你能解释一下为什么会这样吗?没有意义。
        • 这是因为在第一个示例中,`df2 = df, both variables reference the same DataFrame instance. So any changes made to df` 或df2 将针对同一个对象实例。而在df2 = df.copy() 中创建了第二个对象实例,是第一个对象实例的副本,但现在dfdf2 引用了不同的对象实例,并且将对它们各自的DataFrame 实例进行任何更改。
        【解决方案7】:

        这扩展了保罗的回答。在 Pandas 中,索引 DataFrame 会返回对初始 DataFrame 的引用。因此,更改子集将更改初始 DataFrame。因此,如果要确保初始 DataFrame 不应该更改,则需要使用副本。考虑以下代码:

        df = DataFrame({'x': [1,2]})
        df_sub = df[0:1]
        df_sub.x = -1
        print(df)
        

        你会得到:

        x
        0 -1
        1  2
        

        相比之下,以下保持 df 不变:

        df_sub_copy = df[0:1].copy()
        df_sub_copy.x = -1
        

        【讨论】:

        猜你喜欢
        • 2019-05-14
        • 1970-01-01
        • 2022-01-17
        • 2019-12-28
        相关资源
        最近更新 更多