【问题标题】:How to reversibly store and load a Pandas dataframe to/from disk如何可逆地将 Pandas 数据帧存储到磁盘或从磁盘加载
【发布时间】:2013-06-10 12:32:23
【问题描述】:

现在我每次运行脚本时都会导入一个相当大的CSV 作为数据框。是否有一个好的解决方案可以在两次运行之间保持该数据帧始终可用,这样我就不必花费所有时间等待脚本运行?

【问题讨论】:

  • 是的,这是我对使用 Python 的主要抱怨之一 - 没有简单的方法来保存和检索数据帧。在这方面,R 和 SAS 对用户更加友好。

标签: python pandas dataframe


【解决方案1】:

最简单的方法是使用to_picklepickle它:

df.to_pickle(file_name)  # where to save it, usually as a .pkl

然后您可以使用以下方法将其加载回来:

df = pd.read_pickle(file_name)

注意:在 0.11.1 之前,saveload 是执行此操作的唯一方法(现在它们已被弃用,分别支持 to_pickleread_pickle)。 p>


另一个流行的选择是使用HDF5 (pytables),它为大型数据集提供very fast 访问时间:

import pandas as pd
store = pd.HDFStore('store.h5')

store['df'] = df  # save it
store['df']  # load it

cookbook 中讨论了更多高级策略。


从 0.13 开始,还有 msgpack 可能更好地实现互操作性,作为 JSON 的更快替代方案,或者如果您有 python 对象/文本大量数据(请参阅 this question)。

【讨论】:

  • @geekazoid 如果数据需要在加载后进行转换(即字符串/对象到 datetime64),则需要在加载保存的 csv 后再次执行此操作,从而导致性能损失。 pickle 将数据帧保存在当前状态,因此数据及其格式被保留。这可能会导致性能大幅提升。
  • pickle 和 HDFStore 都不能保存超过 8GB 的​​数据帧。有替代品吗?
  • @user1700890 尝试从随机数据(文本和数组)生成并发布一个新问题。我不认为这可能是正确的/怀疑我们遗漏了一些东西。新问题会引起更多关注,但请尝试包含/生成可重现的 DataFrame :)
  • @YixingLiu 你可以在事后改变模式stackoverflow.com/a/16249655/1240268
  • 只是此回复的更新。自 Pandas v1.2 起,HDFStore 方法已重命名为 to_hdf
【解决方案2】:

虽然已经有一些答案,但我发现了一个很好的比较,他们尝试了几种序列化 Pandas DataFrames 的方法:Efficiently Store Pandas DataFrames

他们比较:

  • pickle:原始 ASCII 数据格式
  • cPickle,一个 C 库
  • pickle-p2:使用较新的二进制格式
  • json:标准库 json 库
  • json-no-index:类似于 json,但没有索引
  • msgpack:二进制 JSON 替代方案
  • CSV
  • hdfstore:HDF5 存储格式

在他们的实验中,他们序列化了一个包含 1,000,000 行的 DataFrame,其中两列分别测试:一列是文本数据,另一列是数字。他们的免责声明说:

您不应相信以下内容可以概括为您的数据。您应该查看自己的数据并自己运行基准测试

他们引用的测试的源代码是可用的online。由于这段代码不能直接工作,我做了一些小的改动,你可以在这里得到:serialize.py 我得到了以下结果:

他们还提到,通过将文本数据转换为categorical 数据,序列化速度要快得多。在他们的测试中大约快 10 倍(另见测试代码)。

编辑:pickle 比 CSV 更高的时间可以通过使用的数据格式来解释。默认情况下,pickle 使用可打印的 ASCII 表示,它会生成更大的数据集。然而,从图中可以看出,使用较新的二进制数据格式(版本 2,pickle-p2)的 pickle 加载时间要短得多。

其他一些参考资料:

【讨论】:

  • 我更新了我的答案来解释你的问题。总结一下:默认情况下,pickle 以 ASCII 格式存储数据。
  • 啊,谢谢你的解释!请注意,pandas DataFrame .to_pickle 似乎正在使用 pkl.HIGHEST_PROTOCOL (应该是 2)
  • 似乎上面链接的博客(Efficiently Store Pandas DataFrames 已被删除。我自己比较了.to_pickle()(使用二进制存储)和.to_hdf()(不压缩)。目标是速度,HDF 的文件大小为 11x Pickle,加载时间为 5x Pickle。我的数据是约 5k 个文件,每行约 7k 行 x 6 列,大部分是数字。
  • 页面依然存在,只需要去掉尾部斜杠即可:Efficiently Store Pandas DataFrames
  • @Mike Williamson,在我的测试中,pickle 的加载速度比 HDF 快 5 倍,并且占用了 1/11 的磁盘空间(即 hdf 的磁盘空间大了 11 倍,加载时间也增加了 5 倍)像泡菜一样的磁盘)。这一切都在 python 3 和 pandas 0.22.0 上。
【解决方案3】:

如果我理解正确,您已经在使用pandas.read_csv(),但希望加快开发过程,这样您就不必在每次编辑脚本时都加载文件,对吗?我有一些建议:

  1. 在进行开发时,您可以使用 pandas.read_csv(..., nrows=1000) 仅加载 CSV 文件的一部分以仅加载表的最高位

  2. 使用ipython 进行交互式会话,以便在编辑和重新加载脚本时将 pandas 表保存在内存中。

  3. 将 csv 转换为 HDF5 table

  4. 更新使用DataFrame.to_feather()pd.read_feather()以超快的R兼容feather二进制格式存储数据(在我手中,比pandas.to_pickle()稍快在数字数据上,在字符串数据上更快)。

您可能还对 stackoverflow 上的 this answer 感兴趣。

【讨论】:

  • 你知道为什么to_feather 可以很好地处理字符串数据吗?我在我的数字数据帧上对 to_pickleto_feature 进行了基准测试,pickle 的速度大约快了 3 倍。
  • @zyxue 好问题,老实说我没怎么玩过羽毛的东西,所以我没有答案
【解决方案4】:

泡菜效果很好!

import pandas as pd
df.to_pickle('123.pkl')    #to save the dataframe, df to 123.pkl
df1 = pd.read_pickle('123.pkl') #to load 123.pkl back to the dataframe df

【讨论】:

  • 请注意,生成的文件不是 csv 文件,也许最好使用@Andy Haydens 回答中建议的扩展名.pkl
【解决方案5】:

您可以使用羽毛格式的文件。它非常快。

df.to_feather('filename.ft')

【讨论】:

  • 然后R 可以使用feather 库直接使用数据。
【解决方案6】:

如前所述,有不同的选项和文件格式(HDF5JSONCSVparquetSQL)来存储数据框。但是,pickle 不是一等公民(取决于您的设置),因为:

  1. pickle 是潜在的安全风险。形成Python documentation for pickle:

警告 pickle 模块不能防止错误或 恶意构造的数据。永远不要解开从 不受信任或未经身份验证的来源。

  1. pickle 很慢。查找 herehere 基准。

根据您的设置/使用情况,这两个限制都不适用,但我不建议将 pickle 作为 pandas 数据帧的默认持久性。

【讨论】:

    【解决方案7】:

    Pandas DataFrames 具有 to_pickle 函数,这对于保存 DataFrame 很有用:

    import pandas as pd
    
    a = pd.DataFrame({'A':[0,1,0,1,0],'B':[True, True, False, False, False]})
    print a
    #    A      B
    # 0  0   True
    # 1  1   True
    # 2  0  False
    # 3  1  False
    # 4  0  False
    
    a.to_pickle('my_file.pkl')
    
    b = pd.read_pickle('my_file.pkl')
    print b
    #    A      B
    # 0  0   True
    # 1  1   True
    # 2  0  False
    # 3  1  False
    # 4  0  False
    

    【讨论】:

      【解决方案8】:

      Numpy 文件格式对于数值数据来说非常快

      我更喜欢使用 numpy 文件,因为它们快速且易于使用。 这是保存和加载具有 1 列 100 万点的数据框的简单基准。

      import numpy as np
      import pandas as pd
      
      num_dict = {'voltage': np.random.rand(1000000)}
      num_df = pd.DataFrame(num_dict)
      

      使用ipython的%%timeit魔术函数

      %%timeit
      with open('num.npy', 'wb') as np_file:
          np.save(np_file, num_df)
      

      输出是

      100 loops, best of 3: 5.97 ms per loop
      

      将数据加载回数据框

      %%timeit
      with open('num.npy', 'rb') as np_file:
          data = np.load(np_file)
      
      data_df = pd.DataFrame(data)
      

      输出是

      100 loops, best of 3: 5.12 ms per loop
      

      还不错!

      缺点

      如果您使用 python 2 保存 numpy 文件,然后尝试使用 python 3 打开(反之亦然),则会出现问题。

      【讨论】:

      • 请注意,此解决方案将删除所有列名并将所有整数数据更改为浮点数:(
      【解决方案9】:

      to_pickle() 的另一个非常新鲜的测试。

      我总共有 25.csv 文件要处理,最终的 dataframe 包含大约 2M 个项目。

      (注意:除了加载 .csv 文件外,我还操作了一些数据并通过新列扩展数据框。)

      遍历所有 25 .csv files 并创建数据框大约需要 14 sec

      pkl 文件加载整个数据帧所需的时间少于1 sec

      【讨论】:

        【解决方案10】:

        https://docs.python.org/3/library/pickle.html

        pickle 协议格式:

        协议版本 0 是原始的“人类可读”协议,向后兼容早期版本的 Python。

        协议版本 1 是一种旧的二进制格式,它也与早期版本的 Python 兼容。

        协议版本 2 是在 Python 2.3 中引入的。它提供了更有效的新型类的酸洗。有关协议 2 带来的改进的信息,请参阅 PEP 307。

        在 Python 3.0 中添加了协议版本 3。它对字节对象有明确的支持,并且不能被 Python 2.x 解压。这是默认协议,当需要与其他 Python 3 版本兼容时推荐使用的协议。

        在 Python 3.4 中添加了协议版本 4。它增加了对非常大的对象、腌制更多种类的对象以及一些数据格式优化的支持。有关协议 4 带来的改进的信息,请参阅 PEP 3154。

        【讨论】:

          【解决方案11】:

          Arctic 是用于 Pandas、numpy 和其他数字数据的高性能数据存储。它位于 MongoDB 之上。也许对于 OP 来说有点矫枉过正,但对于其他偶然发现这篇文章的人来说,值得一提

          【讨论】:

            【解决方案12】:

            pyarrow 跨版本兼容性

            整体迁移到 pyarrow/feather(来自 pandas/msgpack 的弃用警告)。但是我对 pyarrow 有一个挑战,transient in specification 用 pyarrow 0.15.1 序列化的数据不能用 0.16.0 ARROW-7961 反序列化。我正在使用序列化来使用redis,所以必须使用二进制编码。

            我重新测试了各种选项(使用 jupyter notebook)

            import sys, pickle, zlib, warnings, io
            class foocls:
                def pyarrow(out): return pa.serialize(out).to_buffer().to_pybytes()
                def msgpack(out): return out.to_msgpack()
                def pickle(out): return pickle.dumps(out)
                def feather(out): return out.to_feather(io.BytesIO())
                def parquet(out): return out.to_parquet(io.BytesIO())
            
            warnings.filterwarnings("ignore")
            for c in foocls.__dict__.values():
                sbreak = True
                try:
                    c(out)
                    print(c.__name__, "before serialization", sys.getsizeof(out))
                    print(c.__name__, sys.getsizeof(c(out)))
                    %timeit -n 50 c(out)
                    print(c.__name__, "zlib", sys.getsizeof(zlib.compress(c(out))))
                    %timeit -n 50 zlib.compress(c(out))
                except TypeError as e:
                    if "not callable" in str(e): sbreak = False
                    else: raise
                except (ValueError) as e: print(c.__name__, "ERROR", e)
                finally: 
                    if sbreak: print("=+=" * 30)        
            warnings.filterwarnings("default")
            

            我的数据框有以下结果(在out jupyter 变量中)

            pyarrow before serialization 533366
            pyarrow 120805
            1.03 ms ± 43.9 µs per loop (mean ± std. dev. of 7 runs, 50 loops each)
            pyarrow zlib 20517
            2.78 ms ± 81.8 µs per loop (mean ± std. dev. of 7 runs, 50 loops each)
            =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=
            msgpack before serialization 533366
            msgpack 109039
            1.74 ms ± 72.8 µs per loop (mean ± std. dev. of 7 runs, 50 loops each)
            msgpack zlib 16639
            3.05 ms ± 71.7 µs per loop (mean ± std. dev. of 7 runs, 50 loops each)
            =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=
            pickle before serialization 533366
            pickle 142121
            733 µs ± 38.3 µs per loop (mean ± std. dev. of 7 runs, 50 loops each)
            pickle zlib 29477
            3.81 ms ± 60.4 µs per loop (mean ± std. dev. of 7 runs, 50 loops each)
            =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=
            feather ERROR feather does not support serializing a non-default index for the index; you can .reset_index() to make the index into column(s)
            =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=
            parquet ERROR Nested column branch had multiple children: struct<x: double, y: double>
            =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=
            

            羽毛和镶木地板不适用于我的数据框。我将继续使用 pyarrow。不过我会补充泡菜(不压缩)。写入缓存存储 pyarrow 和 pickle 序列化表单时。如果 pyarrow 反序列化失败,则从缓存回退读取到 pickle 时。

            【讨论】:

            • 这不能回答问题
            【解决方案13】:

            这里有很多很棒且足够的答案,但我想发布一个我在 Kaggle 上使用的测试,其中大 df 由不同的 pandas 兼容格式保存和读取:

            https://www.kaggle.com/pedrocouto39/fast-reading-w-pickle-feather-parquet-jay

            我不是这个问题的作者或作者的朋友,但是,当我读到这个问题时,我认为值得一提。

            CSV:1min 42s Pickle:4.45s Feather:4.35s Parquet:8.31s Jay:8.12ms 或 0.0812 秒(超快!)

            【讨论】:

            • 小心!这种比较不公平! Jay 文件被读取为数据表帧而不是熊猫数据帧。 Datatable 支持内存不足的数据集,我怀疑数据实际上还没有被读取。
            猜你喜欢
            • 1970-01-01
            • 2018-08-03
            • 2012-10-12
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-11-07
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多