Apache Arrow
经典的Pandas DataFrame将数据存储在Numpy数组中。而在Polars中,数据被存储在Arrow表中。
我们可以通过调用to_arrow来查看这个Arrow表------这是一个廉价的操作,因为它只是查看底层数据。
python
df.to_arrow()
pyarrow.Table
survived: int64
pclass: int64
sex: large_string
age: double
sibsp: int64
parch: int64
fare: double
embarked: large_string
class: large_string
who: large_string
adult_male: bool
deck: large_string
embark_town: large_string
alive: large_string
alone: bool
----
survived: [[0,1,1,1,0,...,0,1,0,1,0]]
pclass: [[3,1,3,1,3,...,2,1,3,1,3]]
sex: [["male","female","female","female","male",...,"male","female","female","male","male"]]
age: [[22,38,26,35,35,...,27,19,null,26,32]]
sibsp: [[1,1,0,1,0,...,0,0,1,0,0]]
parch: [[0,0,0,0,0,...,0,0,2,0,0]]
fare: [[7.25,71.2833,7.925,53.1,8.05,...,13,30,23.45,30,7.75]]
embarked: [["S","C","S","S","S",...,"S","S","S","C","Q"]]
class: [["Third","First","Third","First","Third",...,"Second","First","Third","First","Third"]]
who: [["man","woman","woman","woman","man",...,"man","woman","woman","man","man"]]
...
Arrow表是Arrow数组的集合------这些是一维向量,是基本的数据存储。我们可以通过在Series上调用to_arrow来查看某列的Arrow数组。
python
df["age"].to_arrow()
[
22,
38,
26,
35,
35,
null,
54,
2,
27,
14,
...
33,
22,
28,
25,
39,
27,
19,
null,
26,
32
]
Apache Arrow是什么?
Apache Arrow是一个开源的跨语言项目,用于在内存中存储表格数据。
Apache Arrow 是一个跨平台的开发库,用于在内存中高效处理大型数据集。它最初由 Databricks 开发,并于 2016 年捐赠给 Apache 软件基金会。Arrow 的主要目标是在不同的系统之间提供高性能的数据交换和处理。
以下是 Apache Arrow 的一些关键特点:
-
列式存储:Arrow 使用列式存储格式,这意味着每一列数据都连续存储在内存中。这种格式非常适合批量数据处理,因为它能够利用现代 CPU 的缓存行为来提高数据访问效率。
-
零拷贝读取:Arrow 支持零拷贝读取,这意味着数据可以在不复制的情况下被多个进程共享。这有助于减少数据传输过程中的开销。
-
类型安全:Arrow 数据结构是类型安全的,这意味着数据类型在内存中是明确指定的。这对于避免运行时错误和提高性能非常重要。
-
跨语言兼容性:Arrow 支持多种编程语言,包括 C++、Java、Python、JavaScript、Rust 和更多。这意味着使用不同语言编写的程序可以共享相同的数据格式,而不需要额外的转换步骤。
-
高性能矢量化操作:Arrow 提供了用于矢量化操作的库函数,这些函数可以在整个数据集上并行执行,从而显著提高数据处理的速度。
-
支持多种数据源:Arrow 可以与多种数据源集成,包括关系数据库、NoSQL 存储系统、文件系统等。
-
Parquet 文件格式集成:Arrow 与 Parquet 文件格式紧密集成,使得 Arrow 可以高效地读写 Parquet 文件。
-
用于大数据处理的生态系统集成:Arrow 被多个大数据处理框架和工具所采用,例如 Apache Spark、Pandas、Dask、Vaex、Polars 和 Flink。
Apache Arrow是:
- 一个关于数据如何在内存中表示的规范
- 一组在不同语言中实现该规范的库
Polars使用了Rust库[Arrow2]中的Arrow规范实现。
为什么Polars使用Apache Arrow?
当人们意识到专为科学计算设计的Numpy数组并不是表格数据的最佳数据存储方式时,Apache Arrow项目应运而生。
Apache Arrow 在数据科学和大数据领域变得越来越流行,因为它提高了数据处理的效率,并简化了不同系统之间的数据交换。
Arrow允许:
- 无需复制即可共享数据(称为"零拷贝")
- 更快的向量化计算
- 分块处理大于内存的数据
- 缺失数据的一致表示
总体而言,由于Arrow,Polars可以更快且更少地使用内存来处理数据。
Apache Arrow的缺点是什么?
Arrow的设计优化了对一维列的操作,而Numpy的设计优化了对多维数组的操作。这种权衡意味着与Numpy相比,使用Arrow数据进行某些类型的操作会更慢:
- 转换数据帧
- 在数据帧上进行矩阵乘法/线性代数运算
对于这种需要按行和列访问数据的用例,转换为Numpy数组可能更快(请参阅关于转换的讲座)。
那么Polars DataFrame和Arrow数据之间的关系是什么?
Polars DataFrame持有对Arrow表的引用,该表又持有对Arrow数组的引用。我们可以将Polars DataFrame视为一个轻量级对象,它指向轻量级的Arrow表,该表又指向重量级的Arrow数组(重量级是因为它们持有实际数据)。
这种分离的结构意味着我们可以对便宜的DataFrame包装器进行更改,而不复制(或复制最小量)任何数据,因为所有实际数据都存储在Arrow数组中。这种设计允许Polars提供快速且内存高效的数据处理功能。
python
df_shape = (1_000_000,100)
df_polars = pl.DataFrame(
np.random.standard_normal(df_shape)
)
df_polars.shape
删除列
我们将查看从Polars DataFrame中删除一个列需要多长时间。我们使用IPython的timeit模块来比较性能(我们将在课程后面的部分中学习更多关于timeit的知识)。
python
%%timeit -n1 -r3
df_polars.drop("column_0")
Polars执行此操作非常快(比传统的Pandas快得多)。这是因为Polars只是创建了一个新的DataFrame对象(这是一个廉价的操作),该对象指向除了column_0之外的所有Arrow数组。基本上,Polars只是遍历列名列表来执行此操作!
重命名列
当我们更改DataFrame的某些不影响列中实际数据的部分时,会产生类似的效果。例如,如果我们重命名一个列...
python
%%timeit -n1 -r3
df_polars.rename({"column_0":"a"})
Polars再次以非常快的速度完成此操作,因为它只是更新列名并检查列名是否仍然唯一。
克隆DataFrame
或者,如果我们通过克隆来创建一个新的DataFrame...
python
%%timeit -n1 -r3
df_polars.clone()
在这种情况下,Polars创建了一个新的DataFrame对象,它指向相同的Arrow表。
更新克隆的DataFrame
尽管新的和旧的DataFrame最初指向相同的Arrow表,但我们不需要担心对其中一个所做的更改会影响另一个。
如果我们对其中一个DataFrame(比如新的DataFrame)中的值进行修改,那么新的DataFrame将:
- 将已更改列中的数据复制到新的Arrow数组中
- 创建一个新的Arrow表,该表指向更新后的Arrow数组以及未更改的Arrow数组
因此,现在我们有了:
- 两个DataFrame,它们指向:
- 两个Arrow表,它们指向:
- 对于未更改的列,是相同的Arrow数组;对于已更改的列,是不同的Arrow数组
通过这种方式,我们创建了一个新的DataFrame,但只需要复制已更改列中的数据。在这个示例中,我们更改了第一行中的第一个值,我们可以看到对新的DataFrame的更改不会影响旧的DataFrame。
python
df_polars2 = df_polars.clone()
df_polars2[0,0] = 1000
df_polars2[0,0]
在原始的DataFrame中,我们仍然保留着原始的值。
python
df_polars[0,0]
练习
在练习中,您将加深以下内容的理解:
-
- 获取DataFrame的数据类型(dtypes)
-
- 获取Series的数据类型(dtypes)
练习 1
这个DataFrame的数据类型(dtypes)是什么?
python
df = pl.DataFrame({'a':[0,1,2],'b':[0,1,2.0]})
df<blank>
练习 2
通过选择df的a列来创建一个Series
python
df = pl.DataFrame({'a':[0,1,2],'b':[0,1,2.0]})
df<blank>
a的数据类型是什么?
b的数据类型是什么?
解决方案
练习 1 的解决方案
这个DataFrame的数据类型(dtypes)是什么?
python
df = pl.DataFrame({'a':[0,1,2],'b':[0,1,2.0]})
df.schema
练习 2 的解决方案
通过选择df的a列来创建一个Series
python
df = pl.DataFrame({'a':[0,1,2],'b':[0,1,2.0]})
s = df["a"]
s
"s
"有一个 64 位整数数据类型(dtype)
python
s2 = df["b"]
s2
s2
有一个 64 位浮点数据类型(dtype)