Arrow 拥有一个丰富的类型系统,其中包含许多 R 数据类型的直接对应类型,以及许多在 R 中没有对应类型的类型。本文介绍了 Arrow 类型系统,将其与 R 数据类型进行了比较,并概述了将数据从 Arrow 传输到 R 时使用的默认映射。在文章的末尾,有两个查找表:一个描述默认的“R 到 Arrow”类型映射,另一个描述“Arrow 到 R”映射。
示例说明
为了说明需要进行的转换,请考虑当使用dplyr::glimpse()
检查starwars
数据的原始格式(作为 R 中的数据框)时输出结果与将其首先转换为 Arrow 表格(通过调用arrow_table()
)后输出结果之间的差异。
## Rows: 87
## Columns: 14
## $ name <chr> "Luke Skywalker", "C-3PO", "R2-D2", "Darth Vader", "Leia Or~
## $ height <int> 172, 167, 96, 202, 150, 178, 165, 97, 183, 182, 188, 180, 2~
## $ mass <dbl> 77.0, 75.0, 32.0, 136.0, 49.0, 120.0, 75.0, 32.0, 84.0, 77.~
## $ hair_color <chr> "blond", NA, NA, "none", "brown", "brown, grey", "brown", N~
## $ skin_color <chr> "fair", "gold", "white, blue", "white", "light", "light", "~
## $ eye_color <chr> "blue", "yellow", "red", "yellow", "brown", "blue", "blue",~
## $ birth_year <dbl> 19.0, 112.0, 33.0, 41.9, 19.0, 52.0, 47.0, NA, 24.0, 57.0, ~
## $ sex <chr> "male", "none", "none", "male", "female", "male", "female",~
## $ gender <chr> "masculine", "masculine", "masculine", "masculine", "femini~
## $ homeworld <chr> "Tatooine", "Tatooine", "Naboo", "Tatooine", "Alderaan", "T~
## $ species <chr> "Human", "Droid", "Droid", "Human", "Human", "Human", "Huma~
## $ films <list> <"A New Hope", "The Empire Strikes Back", "Return of the J~
## $ vehicles <list> <"Snowspeeder", "Imperial Speeder Bike">, <>, <>, <>, "Imp~
## $ starships <list> <"X-wing", "Imperial shuttle">, <>, <>, "TIE Advanced x1",~
glimpse(arrow_table(starwars))
## Table
## 87 rows x 14 columns
## $ name <string> "Luke Skywalker", "C-3PO", "R2-D2", "Darth Vader", "Leia~
## $ height <int32> 172, 167, 96, 202, 150, 178, 165, 97, 183, 182, 188, 180~
## $ mass <double> 77.0, 75.0, 32.0, 136.0, 49.0, 120.0, 75.0, 32.0, 84.0, ~
## $ hair_color <string> "blond", NA, NA, "none", "brown", "brown, grey", "brown"~
## $ skin_color <string> "fair", "gold", "white, blue", "white", "light", "light"~
## $ eye_color <string> "blue", "yellow", "red", "yellow", "brown", "blue", "blu~
## $ birth_year <double> 19.0, 112.0, 33.0, 41.9, 19.0, 52.0, 47.0, NA, 24.0, 57.~
## $ sex <string> "male", "none", "none", "male", "female", "male", "femal~
## $ gender <string> "masculine", "masculine", "masculine", "masculine", "fem~
## $ homeworld <string> "Tatooine", "Tatooine", "Naboo", "Tatooine", "Alderaan",~
## $ species <string> "Human", "Droid", "Droid", "Human", "Human", "Human", "H~
## $ films <list<...>> <"A New Hope", "The Empire Strikes Back", "Return of the~
## $ vehicles <list<...>> <"Snowspeeder", "Imperial Speeder Bike">, <>, <>, <>, "I~
## $ starships <list<...>> <"X-wing", "Imperial shuttle">, <>, <>, "TIE Advanced x1~
## Call `print()` for full schema details
表示的数据本质上是相同的,但是列的数据类型描述已更改。例如
-
name
在数据框中被标记为<chr>
(字符向量);在 Arrow 表格中被标记为<string>
(字符串类型,也称为 utf8 类型) -
height
在数据框中被标记为<int>
(整数向量);在 Arrow 表格中被标记为<int32>
(32 位有符号整数) -
mass
在数据框中被标记为<dbl>
(数值向量);在 Arrow 表格中被标记为<double>
(64 位浮点数)
其中一些差异纯粹是表面的:R 中的整数实际上是 32 位有符号整数,因此 Arrow 和 R 中的基础数据类型是彼此的直接对应类型。在其他情况下,差异纯粹是关于实现的:Arrow 和 R 有不同的方式来存储字符串向量,但在较高的抽象级别上,R 字符类型和 Arrow 字符串类型可以被视为直接对应类型。然而,在某些情况下,没有明显的对应类型:虽然 Arrow 有 POSIXct(时间戳类型)的对应类型,但它没有 POSIXlt 的对应类型;相反,虽然 R 可以表示 32 位有符号整数,但它没有 64 位无符号整数的等效类型。
当 arrow 包在 R 数据和 Arrow 数据之间进行转换时,它将首先检查是否提供了模式 - 请参阅schema()
以获取更多信息 - 如果没有可用模式,它将尝试通过遵循默认映射来猜测适当的类型。本文末尾提供了这些映射的完整列表,但下图显示了最常见的情况。
在此图像中,黑色框表示 R 数据类型,浅蓝色框表示 Arrow 数据类型。方向箭头指定转换(例如,逻辑 R 类型和布尔 Arrow 类型之间的双向箭头表示逻辑 R 转换为 Arrow 布尔值,反之亦然)。实线表示此转换规则始终为默认值;虚线表示它仅在某些情况下适用(规则和特殊情况在下面描述)。
逻辑/布尔类型
Arrow 和 R 都使用三值逻辑。在 R 中,逻辑值可以是TRUE
或FALSE
,其中NA
用于表示缺失数据。在 Arrow 中,相应的布尔类型可以取值true
、false
或null
,如下所示。
chunked_array(c(TRUE, FALSE, NA), type = boolean()) # default
## ChunkedArray
## <bool>
## [
## [
## true,
## false,
## null
## ]
## ]
在此示例中,不必严格设置type = boolean()
,因为 arrow 中的默认行为是将 R 逻辑向量转换为 Arrow 布尔值,反之亦然。但是,为了清楚起见,我们将在本文中明确指定数据类型。我们还将使用chunked_array()
从 R 对象创建 Arrow 数据,并使用as.vector()
从 Arrow 对象创建 R 数据,但如果我们使用其他方法,也可以获得类似的结果。
整数类型
基本 R 本身仅支持一种整数类型,使用 32 位表示 -2147483648 到 2147483647 之间的有符号数,尽管 R 也可以通过bit64
包支持 64 位整数。Arrow 从 C++ 中继承了有符号和无符号整数类型,分别有 8 位、16 位、32 位和 64 位版本。
描述 | 数据类型函数 | 最小值 | 最大值 |
---|---|---|---|
8 位无符号 | uint8() |
0 | 255 |
16 位无符号 | uint16() |
0 | 65535 |
32 位无符号 | uint32() |
0 | 4294967295 |
64 位无符号 | uint64() |
0 | 18446744073709551615 |
8 位有符号 | int8() |
-128 | 127 |
16 位有符号 | int16() |
-32768 | 32767 |
32 位有符号 | int32() |
-2147483648 | 2147483647 |
64 位有符号 | int64() |
-9223372036854775808 | 9223372036854775807 |
默认情况下,arrow 将 R 整数转换为 Arrow 中的 int32 类型,但您可以通过显式指定其他整数类型来覆盖此设置。
chunked_array(c(10L, 3L, 200L), type = int32()) # default
## ChunkedArray
## <int32>
## [
## [
## 10,
## 3,
## 200
## ]
## ]
chunked_array(c(10L, 3L, 200L), type = int64())
## ChunkedArray
## <int64>
## [
## [
## 10,
## 3,
## 200
## ]
## ]
如果 R 中的值不属于相应 Arrow 类型的允许范围内,arrow 将抛出错误。
chunked_array(c(10L, 3L, 200L), type = int8())
## Error: Invalid: value outside of range
从 Arrow 转换为 R 时,整数类型始终转换为 R 整数,除非以下例外情况之一适用。
- 如果 Arrow uint32 或 uint64 的值超出 R 整数允许的范围,则结果将是 R 中的数值向量。
- 如果 Arrow int64 变量的值超出 R 整数允许的范围,则结果将是 R 中的
bit64::integer64
向量。 - 如果用户设置
options(arrow.int64_downcast = FALSE)
,则无论值如何,Arrow int64 类型始终在 R 中产生bit64::integer64
向量。
浮点数值类型
R 有一种双精度(64 位)数值类型,默认情况下它转换为 Arrow 64 位浮点类型。Arrow 支持单精度(32 位)和双精度(64 位)浮点数,分别使用float32()
和float64()
数据类型函数指定。这两个类型在 R 中都转换为双精度数。示例如下所示。
chunked_array(c(0.1, 0.2, 0.3), type = float64()) # default
## ChunkedArray
## <double>
## [
## [
## 0.1,
## 0.2,
## 0.3
## ]
## ]
chunked_array(c(0.1, 0.2, 0.3), type = float32())
## ChunkedArray
## <float>
## [
## [
## 0.1,
## 0.2,
## 0.3
## ]
## ]
arrow_double <- chunked_array(c(0.1, 0.2, 0.3), type = float64())
as.vector(arrow_double)
## [1] 0.1 0.2 0.3
请注意,Arrow 规范还允许半精度(16 位)浮点数,但这些尚未实现。
定点十进制类型
Arrow 还包含decimal()
数据类型,其中数值以十进制格式而不是二进制格式指定。Arrow 中的十进制数有两种类型,128 位版本和 256 位版本,但在大多数情况下,用户应该能够使用更通用的decimal()
数据类型函数,而不是特定的decimal128()
和decimal256()
函数。
Arrow 中的十进制类型是定精度数(而不是浮点数),这意味着需要显式指定precision
和scale
参数。
-
precision
指定要存储的有效位数。 -
scale
指定应在小数点后存储的位数。如果设置scale = 2
,则将在小数点后精确存储两位数字。如果设置scale = 0
,则值将四舍五入到最接近的整数。也允许使用负刻度(在处理极大数字时很有用),因此scale = -2
将值存储到最接近的 100。
由于 R 本身没有创建十进制类型的方法,因此下面的示例有点绕弯。首先,我们将一些浮点数创建为分块数组,然后在 Arrow 中将这些浮点数显式转换为十进制类型。这是可能的,因为分块数组对象拥有cast()
方法。
arrow_floating <- chunked_array(c(.01, .1, 1, 10, 100))
arrow_decimals <- arrow_floating$cast(decimal(precision = 5, scale = 2))
arrow_decimals
## ChunkedArray
## <decimal128(5, 2)>
## [
## [
## 0.01,
## 0.10,
## 1.00,
## 10.00,
## 100.00
## ]
## ]
尽管在 R 中未使用,但十进制类型在必须避免浮点运算中出现的问题时非常有用。
字符串/字符类型
R 使用单个字符类型来表示字符串,而 Arrow 有两种类型。在 Arrow C++ 库中,这些类型称为字符串和 large_strings,但为了避免在 arrow R 包中产生歧义,它们使用utf8()
和large_utf8()
数据类型函数定义。这两种 Arrow 类型之间的区别对于 R 用户来说不太可能很重要,尽管在关于数据对象布局的文章中讨论了这种差异。
默认行为是将 R 字符向量转换为 utf8/字符串类型,并将这两种 Arrow 类型都转换为 R 字符向量。
strings <- chunked_array(c("oh", "well", "whatever"))
strings
## ChunkedArray
## <string>
## [
## [
## "oh",
## "well",
## "whatever"
## ]
## ]
as.vector(strings)
## [1] "oh" "well" "whatever"
因子/字典类型
R 因子的 Arrow 对应类型是字典类型。因子转换为字典,反之亦然。为了说明这一点,让我们在 R 中创建一个小型因子对象。
## [1] cat dog pig dog
## Levels: cat dog pig
转换为 Arrow 后,将生成以下字典。
dict <- chunked_array(fct, type = dictionary())
dict
## ChunkedArray
## <dictionary<values=string, indices=int32>>
## [
##
## -- dictionary:
## [
## "cat",
## "dog",
## "pig"
## ]
## -- indices:
## [
## 0,
## 1,
## 2,
## 1
## ]
## ]
转换回 R 后,我们将恢复原始因子。
as.vector(dict)
## [1] cat dog pig dog
## Levels: cat dog pig
Arrow 字典比 R 因子稍微灵活一些:字典中的值不必一定是字符串,但因子中的标签必须是字符串。因此,Arrow 字典中的非字符串值在转换为 R 时将强制转换为字符串。
日期类型
在 R 中,日期通常使用 Date 类表示。在内部,Date 对象是数值类型,其值计算自 Unix 纪元(1970 年 1 月 1 日)开始的天数。Arrow 提供了两种可用于表示日期的数据类型:date32 类型和 date64 类型。date32 类型类似于 R 中的 Date 类:在内部,它存储一个 32 位整数,该整数计算自 1970 年 1 月 1 日开始的天数。arrow 中的默认设置是将 R Date 对象转换为 Arrow date32 类型。
## [1] "1989-06-15" "1991-09-24" "1993-09-13"
nirvana_32 <- chunked_array(nirvana_album_dates, type = date32()) # default
nirvana_32
## ChunkedArray
## <date32[day]>
## [
## [
## 1989-06-15,
## 1991-09-24,
## 1993-09-13
## ]
## ]
Arrow 还提供了一种更高精度的 date64 类型,其中日期表示为一个 64 位整数,该整数编码自 1970-01-01 00:00 UTC 开始的毫秒数。
nirvana_64 <- chunked_array(nirvana_album_dates, type = date64())
nirvana_64
## ChunkedArray
## <date64[ms]>
## [
## [
## 1989-06-15,
## 1991-09-24,
## 1993-09-13
## ]
## ]
从 Arrow 转换为 R 的方式有所不同。在内部,date32 类型与 R Date 非常相似,因此这些对象在 R 中转换为 Date。
## [1] "Date"
但是,由于 date64 类型被指定为毫秒级精度,因此它们在 R 中转换为 POSIXct 时间,以避免可能丢失相关信息。
## [1] "POSIXct" "POSIXt"
时间/时间戳类型
在 R 中,有两个类用于表示日期和时间信息,POSIXct 和 POSIXlt。Arrow 只有一个:时间戳类型。Arrow 时间戳与 POSIXct 类大致类似。在内部,POSIXct 对象将日期表示为一个数值变量,该变量存储自 1970-01-01 00:00 UTC 开始的秒数。在内部,Arrow 时间戳是一个 64 位整数,计算自 1970-01-01 00:00 UTC 开始的毫秒数。
Arrow 和 R 都支持时区信息,但在打印的对象中以不同的方式显示。在 R 中,本地时间与时区名称一起打印。
sydney_newyear <- as.POSIXct("2000-01-01 00:01", tz = "Australia/Sydney")
sydney_newyear
## [1] "2000-01-01 00:01:00 AEDT"
转换为 Arrow 后,此 POSIXct 对象将成为 Arrow 时间戳对象。但是,在打印时,时间点始终以 UTC 而不是本地时间显示。
sydney_newyear_arrow <- chunked_array(sydney_newyear, type = timestamp())
sydney_newyear_arrow
## ChunkedArray
## <timestamp[s]>
## [
## [
## 1999-12-31 13:01:00
## ]
## ]
但是,时区信息不会丢失,我们可以通过将sydney_newyear_arrow
对象转换回 R POSIXct 对象轻松地看到这一点。
as.vector(sydney_newyear_arrow)
## [1] "1999-12-31 13:01:00 UTC"
对于 POSIXlt 对象,行为有所不同。在内部,POSIXlt 对象是一个列表,它根据各种与人类相关字段指定“本地时间”。Arrow 中没有与此类似的类,因此默认行为是将其转换为 Arrow 列表。
一天中的时间类型
基本 R 没有类来表示独立于日期的一天的时刻(即,无法在不提及特定日期的情况下指定“下午 3 点”),但可以使用hms
包来实现。在内部,hms 对象始终存储为自 00:00:00 开始的秒数。
Arrow 有两种用于此目的的数据类型。对于 time32 类型,数据存储为 32 位整数,该整数被解释为自 00:00:00 开始的秒数或毫秒数。请注意以下区别。
time_of_day <- hms::hms(56, 34, 12)
chunked_array(time_of_day, type = time32(unit = "s"))
## ChunkedArray
## <time32[s]>
## [
## [
## 12:34:56
## ]
## ]
chunked_array(time_of_day, type = time32(unit = "ms"))
## ChunkedArray
## <time32[ms]>
## [
## [
## 12:34:56.000
## ]
## ]
time64 对象类似,但使用 64 位整数存储一天中的时间,并且可以更精确地表示时间。可以选择微秒 (unit = "us"
) 或纳秒 (unit = "ns"
),如下所示。
chunked_array(time_of_day, type = time64(unit = "us"))
## ChunkedArray
## <time64[us]>
## [
## [
## 12:34:56.000000
## ]
## ]
chunked_array(time_of_day, type = time64(unit = "ns"))
## ChunkedArray
## <time64[ns]>
## [
## [
## 12:34:56.000000000
## ]
## ]
Arrow 中的所有版本的 time32 和 time64 对象都转换为 R 中的 hms 时间。
持续时间类型
时间长度在 R 中表示为 difftime 对象。Arrow 中的类似数据类型是持续时间类型。持续时间类型存储为 64 位整数,可以表示秒数(默认值,unit = "s"
)、毫秒数 (unit = "ms"
)、微秒数 (unit = "us"
) 或纳秒数 (unit = "ns"
)。为了说明这一点,我们将在 R 中创建一个对应于 278 秒的 difftime。
len <- as.difftime(278, unit = "secs")
len
## Time difference of 278 secs
转换为 Arrow 后如下所示。
chunked_array(len, type = duration(unit = "s")) # default
## ChunkedArray
## <duration[s]>
## [
## [
## 278
## ]
## ]
chunked_array(len, type = duration(unit = "ns"))
## ChunkedArray
## <duration[ns]>
## [
## [
## 278000000000
## ]
## ]
无论基础单位如何,Arrow 中的持续时间对象都转换为 R 中的 difftime 对象。
默认转换列表
以上讨论涵盖了最常见的情况。本节中的两个表格提供了 arrow 在 R 数据类型和 Arrow 数据类型之间进行转换的更完整列表。在这些表格中,带有-
的条目当前未实现。
从 R 到 Arrow 的转换
原始 R 类型 | 转换后的 Arrow 类型 |
---|---|
逻辑 | 布尔 |
整数 | int32 |
双精度(“数值”) | float64 1 |
字符 | utf8 2 |
因子 | 字典 |
原始 | uint8 |
日期 | date32 |
POSIXct | 时间戳 |
POSIXlt | 结构 |
数据框 | 结构 |
列表 3 | 列表 |
bit64::integer64 | int64 |
hms::hms | time32 |
difftime | 持续时间 |
vctrs::vctrs_unspecified | 空 |
1: float64
和 double
是 Arrow C++ 中相同概念和数据类型;但是,只有float64()
在 arrow 中用作函数,因为double()
函数已存在于基本 R 中。
2: 如果字符向量的字符串超过 2GB,它将转换为large_utf8
Arrow 类型。
3: 只有所有元素类型都相同列表才能转换为 Arrow 列表类型(它是某种类型的“列表”)。
从 Arrow 到 R 的转换
原始 Arrow 类型 | 转换后的 R 类型 |
---|---|
布尔 | 逻辑 |
int8 | 整数 |
int16 | 整数 |
int32 | 整数 |
int64 | integer 1 |
uint8 | 整数 |
uint16 | 整数 |
uint32 | integer 1 |
uint64 | integer 1 |
float16 | - 2 |
float32 | double |
float64 | double |
utf8 | 字符 |
large_utf8 | 字符 |
binary | arrow_binary 3 |
large_binary | arrow_large_binary 3 |
fixed_size_binary | arrow_fixed_size_binary 3 |
date32 | 日期 |
date64 | POSIXct |
time32 | hms::hms |
time64 | hms::hms |
时间戳 | POSIXct |
持续时间 | difftime |
decimal | double |
字典 | factor 4 |
列表 | arrow_list 5 |
large_list | arrow_large_list 5 |
fixed_size_list | arrow_fixed_size_list 5 |
结构 | 数据框 |
空 | vctrs::vctrs_unspecified |
map | arrow_list 5 |
union | - 2 |
1: 这些整数类型可能包含超出 R 的 integer
类型范围(32 位有符号整数)的值。当它们超出范围时,uint32
和 uint64
会转换为 double
(“numeric”),而 int64
会转换为 bit64::integer64
。可以通过设置 options(arrow.int64_downcast = FALSE)
来禁用此转换(以便 int64
始终生成 bit64::integer64
向量)。
2: 一些 Arrow 数据类型目前没有 R 等价类型,如果转换为或通过模式映射到它们,则会引发错误。
3: arrow*_binary
类实现为原始向量的列表。
4: 由于 R 因子的限制,如果 Arrow dictionary
值不是字符串,则在转换为 R 时会被强制转换为字符串。
5: arrow*_list
类实现为 vctrs_list_of
的子类,其 ptype
属性设置为空数组的值类型转换为的内容。