查看原文
其他

Pandas 2.0 主要优势以及代码实现!

原文:Pandas 2.0: A Game-Changer for Data Scientists?  

作者:Miriam Santos

翻译:陈超

四月,官方发布pandas 2.0.0,在数据科学社区内掀起了轩然大波。
由于其广泛的功能性和多功能性,如果没有 import pandas as pd,几乎不可能做到数据操纵,对吧?
现在,请听我说:在过去的几个月里,随着大语言模型的火爆,我不知怎么地漏掉了pandas刚刚经历了一次重大发布的事实!是的,pandas 2.0已经擦亮枪口走来了(What’s new in 2.0.0 (April 3, 2023) — pandas 2.1.0.dev0+1237.gc92b3701ef documentation (pydata.org))!
虽然我没意识到所有的大肆宣传,数据中心的人工智能社区迅速伸出了援手:

截图来自作者 2.0发行版看起来在数据科学社区造成了相当大的影响,很多用户都称赞新版本里的改进。


有趣的事实:你意识到这个发行版用了惊人的3年时间制作的吗?这就是我所说的“对社区的承诺”!

所以pandas 2.0带来了什么?让我们立刻深入看一下!

1.表现,速度以及记忆效率
正如我们所知,pandas是使用numpy建立的,并非有意设计为数据帧库的后端。因为这个原因,pandas的主要局限之一就是较大数据集的内存处理。
在这一版本里,大的改变来自于为pandas数据引入Apache Arrow后端。
从本质上讲,Arrow 是一种标准化的内存中列式数据格式,具有适用于多种编程语言(C、C++、R、Python 等)的可用库。对于Python,有PyArrow,它基于Arrow的C++实现,因此速度很快!
所以,长话短说,PyArrow考虑到了我们以往1点几版本的内存限制,允许我们执行更快、内存更高效的数据操作,尤其对大型数据集来说。
以下是使用Hacker News数据集(大约650 MB)读取没有pyarrow后端的数据与使用pyarrow后端读取数据之间的比较(许可证CC BY-NC-SA 4.0):
%timeit df = pd.read_csv("data/hn.csv")# 12 s ± 304 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)%timeit df_arrow = pd.read_csv("data/hn.csv", engine='pyarrow', dtype_backend='pyarrow')# 329 ms ± 65 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
比较read_csv(): 使用pyarrow后台快了35倍多。作者代码段。

如您所见,使用新的后端使读取数据的速度提高了近 35 倍。其他值得指出的方面:

  • 如果没有 pyarrow 后端,每个列/特征都存储为自己的唯一数据类型:数字特征存储为 int64 或 float64,而字符串值存储为对象;

  • 使用 pyarrow,所有功能都使用 Arrow dtypes:请注意 [pyarrow] 注释和不同类型的数据:int64、float64、字符串、时间戳和双精度:

df = pd.read_csv("data/hn.csv")df.info()

## RangeIndex: 3885799 entries, 0 to 3885798# Data columns (total 8 columns):# # Column Dtype # --- ------ ----- # 0 Object ID int64 # 1 Title object# 2 Post Type object# 3 Author object# 4 Created At object# 5 URL object# 6 Points int64 # 7 Number of Comments float64# dtypes: float64(1), int64(2), object(5)# memory usage: 237.2+ MB

df_arrow = pd.read_csv("data/hn.csv", dtype_backend='pyarrow', engine='pyarrow')df_arrow.info()

## RangeIndex: 3885799 entries, 0 to 3885798# Data columns (total 8 columns):# # Column Dtype # --- ------ ----- # 0 Object ID int64[pyarrow] # 1 Title string[pyarrow] # 2 Post Type string[pyarrow] # 3 Author string[pyarrow] # 4 Created At timestamp[s][pyarrow]# 5 URL string[pyarrow] # 6 Points int64[pyarrow] # 7 Number of Comments double[pyarrow] # dtypes: double[pyarrow](1), int64[pyarrow](2), string[pyarrow](4), timestamp[s][pyarrow](1)# memory usage: 660.2 MB
df.info():调查每个数据帧的 dtype。作者截图。
2.Arrow数据类型和Numpy索引
除了读取数据(这是最简单的情况)之外,您还可以期待一系列其他操作的其他改进,尤其是那些涉及字符串操作的操作,因为 pyarrow 对字符串数据类型的实现非常有效:
%timeit df["Author"].str.startswith('phy')# 851 ms ± 7.89 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit df_arrow["Author"].str.startswith('phy')# 27.9 ms ± 538 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
比较字符串操作:展示Arrow实现的效率。作者代码段。
事实上,Arrow 比 numpy 具有更多(和更好的支持的)数据类型,这些数据类型在科学(数字)范围之外是必需的:日期和时间、持续时间、二进制、小数、列表和地图。浏览 pyarrow 支持的数据类型和 numpy 数据类型之间的等效性实际上可能是一个很好的练习,以便您学习如何利用它们。
现在也可以在索引中保存更多的 numpy 数值类型。传统的 int64、uint64 和 float64 为所有 numpy 数字 dtypes Index 值打开了空间,因此我们可以指定它们的 32 位版本:
pd.Index([1, 2, 3])# Index([1, 2, 3], dtype='int64')

pd.Index([1, 2, 3], dtype=np.int32)# Index([1, 2, 3], dtype='int32')
利用 32 位 numpy 索引,使代码的内存效率更高。作者代码段。
3.更容易处理缺失值
建立在numpy之上使得pandas很难以轻松,灵活的方式处理缺失值,因为numpy不支持某些数据类型的null值。
例如,整数会自动转换为浮点数,这并不理想:
df = pd.read_csv("data/hn.csv")

points = df["Points"]points.isna().sum()# 0

points[0:5]# 0 61# 1 16# 2 7# 3 5# 4 7# Name: Points, dtype: int64

# Setting first position to Nonepoints.iloc[0] = None

points[0:5]# 0 NaN# 1 16.0# 2 7.0# 3 5.0# 4 7.0# Name: Points, dtype: float64
缺失值:转换成浮点数。作者代码段
请注意在引入 singleNone 值后,点如何自动从 int64 更改为 float64。
对于数据流来说,没有什么比错误的排版更糟糕的了,尤其是在以数据为中心的 AI 范式中。
错误的排版直接影响数据准备决策,导致不同数据块之间的不兼容性,即使以静默方式传递,它们也可能损害某些输出无意义结果的操作。
例如,在以数据为中心的人工智能社区(DCAI Community (discord.com)),我们正在围绕用于数据隐私的合成数据(GitHub - Data-Centric-AI-Community/nist-crc-2023: NIST Collaborative Research Cycle on Synthetic Data. Learn about Synthetic Data week by week!)开展一个项目。其中一个功能NOC(number of children,孩子数)具有缺失值,因此在加载数据时会自动转换为浮点数。当将数据作为浮点数传递到生成模型中时,我们可能会得到小数的输出值,例如 2.5——除非你是一个有 2 个孩子、一个新生儿和奇怪的幽默感的数学家,否则有 2.5 个孩子是不行的。
在 pandas 2.0 中,我们可以利用 dtype = 'numpy_nullable',其中缺失值是在没有任何 dtype 更改的情况下考虑的,因此我们可以保留原始数据类型(在本例中为 int64):
df_null = pd.read_csv("data/hn.csv", dtype_backend='numpy_nullable')

points_null = df_null["Points"]points_null.isna().sum()# 0

points_null[0:5]# 0 61# 1 16# 2 7# 3 5# 4 7# Name: Points, dtype: Int64

points_null.iloc[0] = None

points_null[0:5]# 0 # 1 16# 2 7# 3 5# 4 7# Name: Points, dtype: Int64
利用“numpy_nullable”,pandas 2.0可以在不更改原始数据类型的情况下处理缺失值。作者代码段。
这似乎是一个微妙的变化,但这意味着现在pandas本身就可以使用 Arrow 处理缺失值。这使得操作更加高效,因为 pandas 不必实现自己的版本来处理每种数据类型的 null 值。
4.写入时复制优化
Pandas 2.0 还添加了一种新的惰性复制机制,该机制会延迟复制数据帧和系列对象,直到它们被修改。
这意味着在启用写入时复制时,某些方法将返回视图而不是副本,这通过最大限度地减少不必要的数据重复来提高内存效率。
这也意味着在使用链式分配时需要格外小心。
如果启用了写入时复制模式,则链式分配将不起作用,因为它们指向一个临时对象,该对象是索引操作的结果(在写入时复制下的行为类似于副本)。
禁用copy_on_write后,如果更改新数据帧,切片等操作可能会更改原始 df:
pd.options.mode.copy_on_write = False # disable copy-on-write (default in pandas 2.0)

df = pd.read_csv("data/hn.csv")df.head()

# Throws a 'SettingWithCopy' warning# SettingWithCopyWarning:# A value is trying to be set on a copy of a slice from a DataFramedf["Points"][0] = 2000

df.head() # <---- df changes
禁用写入时复制:在链接分配中更改原始数据帧。作者代码段。
启用copy_on_write后,将在分配时创建副本(python - What rules does Pandas use to generate a view vs a copy? - Stack Overflow),因此永远不会更改原始数据帧。Pandas 2.0 会在这些情况下引发 ChainedAssignmentError,以避免无提示错误:
pd.options.mode.copy_on_write = True

df = pd.read_csv("data/hn.csv")df.head()

# Throws a ChainedAssignmentErrordf["Points"][0] = 2000

# ChainedAssignmentError: A value is trying to be set on a copy of a DataFrame# or Series through chained assignment. When using the Copy-on-Write mode,# such chained assignment never works to update the original DataFrame# or Series, because the intermediate object on which we are setting# values always behaves as a copy.# Try using '.loc[row_indexer, col_indexer] = value' instead,# to perform the assignment in a single step.

df.head() # <---- df does not change
启用写入时复制:在链接分配中不会更改原始数据帧。作者代码段。
5.可依赖选项
使用 pip 时,2.0 版让我们可以灵活地安装可选依赖项,这在资源的定制和优化方面是一个加分项。
我们可以根据我们的特定要求定制安装,而无需将磁盘空间花费在我们并不真正需要的东西上。
此外,它节省了许多“依赖性难题”,减少了兼容性问题或与开发环境中可能存在的其他软件包冲突的可能性:       
pip install "pandas[postgresql, aws, spss]>=2.0.0"
安装可依赖选项。作者代码片段。
让我们试用一下!

然而,问题挥之不去:这种热度真的合理吗?我很好奇pandas 2.0是否对我每天使用的一些软件包提供了显著的改进:ydata-profiling,matplotlib,seaborn,scikit-learn。

从这些中,我决定尝试一下 ydata-profiling——它刚刚增加了对 pandas 2.0 的支持,这似乎是社区的必备品!在新版本中,用户可以休息以确保如果他们使用 pandas 2.0,他们的管道不会中断,这是一个主要优势!但除此之外呢?

说实话,ydata-profiling一直是我最喜欢的探索性数据分析工具之一,它也是一个很好的快速基准测试——我这边只有1行代码,但在此之下,它充满了作为数据科学家我需要解决的计算——描述性统计、直方图绘制、分析相关性、 等等。

那么,还有什么比以最小的努力同时测试pyarrow引擎对所有引擎的影响更好的方法呢?
import pandas as pdfrom ydata_profiling import ProfileReport

# Using pandas 1.5.3 and ydata-profiling 4.2.0%timeit df = pd.read_csv("data/hn.csv")# 10.1 s ± 215 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit profile = ProfileReport(df, title="Pandas Profiling Report")# 4.85 ms ± 77.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit profile.to_file("report.html")# 18.5 ms ± 2.02 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

# Using pandas 2.0.2 and ydata-profiling 4.3.1%timeit df_arrow = pd.read_csv("data/hn.csv", engine='pyarrow')# 3.27 s ± 38.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit profile_arrow = ProfileReport(df_arrow, title="Pandas Profiling Report")# 5.24 ms ± 448 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit profile_arrow.to_file("report.html")# 19 ms ± 1.87 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
使用 ydata-profiling进行基准测试。作者代码段。
同样,使用 pyarrow 引擎读取数据肯定更好,尽管创建数据配置文件在速度方面没有显著改变。
然而,差异可能取决于内存效率,为此我们必须进行不同的分析。此外,我们可以进一步调查对数据进行的分析类型:对于某些操作,1.5.2 和 2.0 版本之间的差异似乎可以忽略不计。
但我注意到在这方面可能产生影响的主要事情是 ydata-profiling尚未利用 pyarrow 数据类型。此更新可能会对速度和内存产生重大影响,也是我对未来发展的期望!
结论:性能、灵活性、互操作性!
这个新的 pandas 2.0 版本带来了很大的灵活性和性能优化,并在“引擎盖下”进行了微妙但关键的修改。
也许对于数据操作领域的新手来说,它们并不“华而不实”,但对于那些曾经跳过篮圈来克服以往版本局限性的资深数据科学家来说,它们就像沙漠中的水一样。
总结一下,这些是新版本中引入的主要优势:
  • 性能优化:随着 Apache Arrow 后端的引入、更多的 numpy dtype 索引和写入时复制模式;
  • 增加灵活性和自定义性:允许用户控制可选的依赖项并利用 Apache Arrow 数据类型(包括从一开始的可空性!);
  • 互操作性:也许是新版本的一个不太“广受赞誉”的优势,但影响巨大。由于 Arrow 是独立于语言的,因此内存中的数据不仅可以在基于 Python 构建的程序之间传输,还可以在 R、Spark 和其他使用 Apache Arrow 后端的程序之间传输!

我希望这个总结可以平息你关于pandas 2.0的一些问题,以及它在我们的数据操作任务中的适用性。
推荐阅读  点击标题可跳转

1、Python那些优质可视化工具!

2、Pandas50个高级操作,秀起来!

3、Pandas + ChatGPT:交互式数据分析!

继续滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存