【问题标题】:Is there a way to copy only the structure (not the data) of a Pandas DataFrame?有没有办法只复制 Pandas DataFrame 的结构(而不​​是数据)?
【发布时间】:2015-02-12 14:03:51
【问题描述】:

我从某个地方收到了一个 DataFrame,并想创建另一个具有相同数量和名称的列和行(索引)的 DataFrame。例如,假设原始数据框创建为

import pandas as pd
df1 = pd.DataFrame([[11,12],[21,22]], columns=['c1','c2'], index=['i1','i2'])

我通过明确定义列和名称来复制结构:

df2 = pd.DataFrame(columns=df1.columns, index=df1.index)    

我不想复制数据,否则我可以写df2 = df1.copy()。换句话说,在创建 df2 之后,它必须只包含 NaN 元素:

In [1]: df1
Out[1]: 
    c1  c2
i1  11  12
i2  21  22

In [2]: df2
Out[2]: 
     c1   c2
i1  NaN  NaN
i2  NaN  NaN

有没有更惯用的方法?

【问题讨论】:

  • 我认为没有比您尝试过的更好的方法了,您也可以使用您想要的结构创建一个空的 df 并复制它,在我看来这确实不如性能在单个操作中使用您想要的数据和结构创建 df
  • 感谢 EdChum 的建议,但这段代码从其他地方接收原始 DataFrame。我将编辑我的问题以使其更清楚。
  • 我仍然认为没有更好的方法,而且您的意图很清楚,因为您没有将数据传递给构造函数

标签: python pandas dataframe


【解决方案1】:

要保留列类型,您可以使用 astype 方法, 喜欢pd.DataFrame(columns=df1.columns).astype(df1.dtypes)

import pandas as pd

df1 = pd.DataFrame(
    [
        [11, 12, 'Alice'],
        [21, 22, 'Bob']
    ],
    columns=['c1', 'c2', 'c3'],
    index=['i1', 'i2']
)

df2 = pd.DataFrame(columns=df1.columns).astype(df1.dtypes)
print(df2.shape)
print(df2.dtypes)

输出:

(0, 3)
c1     int64
c2     int64
c3    object
dtype: object

【讨论】:

【解决方案2】:

将 df 结构复制到 df2 的简单方法是:

df2 = pd.DataFrame(columns=df.columns)

【讨论】:

    【解决方案3】:

    不完全回答这个问题,但对于通过搜索引擎来到这里的人来说是一个类似的问题

    我的案例是创建没有数据且没有索引的数据框的副本。可以通过执行以下操作来实现这一点。这将维护列的 dtypes。

    empty_copy = df.drop(df.index)
    

    【讨论】:

      【解决方案4】:

      我知道这是一个老问题,但我想我会加两分钱。

      def df_cols_like(df):
          """
          Returns an empty data frame with the same column names and types as df
          """
          df2 = pd.DataFrame({i[0]: pd.Series(dtype=i[1])
                              for i in df.dtypes.iteritems()},
                             columns=df.dtypes.index)
          return df2
      

      这种方法以输入数据框dfdf.dtypes 属性为中心,即pd.Seriespd.DataFrame 由空的 pd.Series 对象的字典构成,该对象使用输入列名命名,列顺序取自输入 df

      【讨论】:

        【解决方案5】:

        这在 pandas 0.22 中对我有用: df2 = pd.DataFrame(index=df.index.delete(slice(None)), columns=df.columns)

        转换类型: df2 = df2.astype(df.dtypes)

        delete(slice(None)) 如果您不想保留索引的值。

        【讨论】:

          【解决方案6】:

          您可以通过notna() 简单地mask

          df1 = pd.DataFrame([[11, 12], [21, 22]], columns=['c1', 'c2'], index=['i1', 'i2'])
          
          df2 = df1.mask(df1.notna())
          
              c1  c2
          i1 NaN NaN
          i2 NaN NaN
          

          【讨论】:

            【解决方案7】:

            一个简单的替代方案——首先将原始数据帧 (df1) 中的基本结构或索引和具有数据类型的列复制到 df2 中

            df2 = df1.iloc[0:0]
            

            然后用空行填充您的数据框 - 需要调整伪代码以更好地匹配您的实际结构:

            s = pd.Series([Nan,Nan,Nan], index=['Col1', 'Col2', 'Col3'])
            

            遍历 df1 中的行

            df2 = df2.append(s)
            

            【讨论】:

            • df1.iloc[0:0] 更改为df1.iloc[0:0].copy(),因为如果不这样做,您仍然在使用对df1 的引用。这可以防止在执行df2.rename(columns={"c1": "z1"}, inplace=True) 之类的操作时出现警告SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame
            • 这里是最简单的选择。
            【解决方案8】:

            这是reindex_like 的工作。从原文开始:

            df1 = pd.DataFrame([[11, 12], [21, 22]], columns=['c1', 'c2'], index=['i1', 'i2'])
            

            构造一个空的DataFrame并像df1一样重新索引它:

            pd.DataFrame().reindex_like(df1)
            Out: 
                c1  c2
            i1 NaN NaN
            i2 NaN NaN   
            

            【讨论】:

            • 没见过reindex_like,很高兴看到新的东西
            • 谢谢,这是一个很好的解决方案。它不仅保留列名和行名,还保留列的类型,而无需显式复制这些属性。另外,我认为它非常有效,因为它从一个空的 DataFrame 开始,然后是一个调整大小的操作。
            • 这比显式解决方案快得多。在我的情况下是 20 倍。
            • 与@bmello 不同,当我在我的DataFrame 上使用datetime64[ns]int64object 执行pd.DataFrame().reindex_like(df1).dtypes 时,所有内容都会变成float64。 Pandas 版本0.24.2
            • 这确实 not 保留了 dtypes。使用:new_df = dataframe.copy(deep=False); new_df.astype(dataframe.dtypes.to_dict()).
            【解决方案9】:

            version 0.18 of pandas, the DataFrame constructor 中,没有用于创建数据框的选项,例如另一个使用 NaN 而不是值的数据框。

            你使用df2 = pd.DataFrame(columns=df1.columns, index=df1.index)的代码是最合乎逻辑的方式,唯一的改进方法是把你正在做的事情拼写更多,就是添加data=None,这样其他编码人员就可以直接看到你故意离开从您正在创建的这个新 DataFrame 中取出数据。

            TLDR:所以我的建议是:

            显式优于隐式

            df2 = pd.DataFrame(data=None, columns=df1.columns, index=df1.index)
            

            很像你的,但更清楚。

            【讨论】:

            • 这不会保留列类型。有什么建议吗?
            • @dbc 我怀疑 pandas 尊重具有空数据集的 dtypes,但我更新了我的答案以匹配我的最佳猜测。我还不得不说,pandas 并没有强制执行 dtypes,我感觉你可能认为设置它们会提供一些额外的可用性。 dtypes 是一个 numpy 的东西,只要它适合 numpy,它就会在 pandas 的控制之外自动更改。
            • 不幸的是,pd.DataFrame 在其构造函数中只接受一种默认 dtype。没有它,并设置data=None,所有列都是 dtype 对象。这很重要,因为我最终想调用df2.to_sql,它不能很好地处理对象列。当然,我可以在 to_sql 调用中手动设置每个 dtype,但如果它在结构复制期间来自 df1 会更好。
            • @dbc 是的,你是对的,pandas 在构造上只需要一个 dtype。一般来说,我建议不要基于 dtypes 构建 SQL 表,因为 dtypes 携带的信息远远少于正确定义的 SQL 表所需的信息。
            • 我同意,我使用 SQL create 语句构建表,然后使用df.to_sql(..., if_exists='append', index=False)。但是,当我对使用此结构复制方法创建的 df 执行此操作时,由于 object dtype,插入失败。我的解决方法是在每一列上手动调用pd.to_numeric(等)。我希望有一个更通用的方法,但它看起来不像。
            【解决方案10】:

            让我们从一些示例数据开始

            In [1]: import pandas as pd
            
            In [2]: df = pd.DataFrame([[1, 'a'], [2, 'b'], [3, 'c']],
               ...:                   columns=['num', 'char'])
            
            In [3]: df
            Out[3]: 
               num char
            0    1    a
            1    2    b
            2    3    c
            
            In [4]: df.dtypes
            Out[4]: 
            num      int64
            char    object
            dtype: object
            

            现在让我们使用原始DataFrame 的列进行简单的DataFrame 初始化,但不提供数据:

            In [5]: empty_copy_1 = pd.DataFrame(data=None, columns=df.columns)
            
            In [6]: empty_copy_1
            Out[6]: 
            Empty DataFrame
            Columns: [num, char]
            Index: []
            
            In [7]: empty_copy_1.dtypes
            Out[7]: 
            num     object
            char    object
            dtype: object
            

            如您所见,列数据类型与我们原来的DataFrame 中的不同。

            所以,如果你想保留列dtype...

            如果要保留列数据类型,则需要一次构造DataFrame 一个Series

            In [8]: empty_copy_2 = pd.DataFrame.from_items([
               ...:     (name, pd.Series(data=None, dtype=series.dtype))
               ...:     for name, series in df.iteritems()])
            
            In [9]: empty_copy_2
            Out[9]: 
            Empty DataFrame
            Columns: [num, char]
            Index: []
            
            In [10]: empty_copy_2.dtypes
            Out[10]: 
            num      int64
            char    object
            dtype: object
            

            【讨论】:

            • 需要指出的是,为数据帧中的空列设置 dtype 不会以任何方式改变数据帧的行为。
            • 它可能不会改变 pandas 框架内的行为。但是可能会设置消费者代码来检查数据类型或列名,在这些情况下,您希望代码不必以不同方式编写或包含 if 子句以用于 len(df) == 0 的情况
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-09-13
            • 2022-01-24
            • 1970-01-01
            • 2021-04-29
            • 2012-06-30
            • 1970-01-01
            相关资源
            最近更新 更多