3.5 Julia 标准库

Julia 拥有 丰富的标准库,每个 Julia 发行版都可以使用这些库。 与截至目前提到的一切相反,例如类型,数据结构和文件系统;在使用特定的模块或函数前, 需要将标准库模块导入到环境中

这可以通过 usingimport实现。 本书将使用 using 导入代码:

  1. using ModuleName

在执行上述操作后,就可以使用 ModuleName 中所有的函数和类型。

3.5.1 日期

了解如何处理日期和时间戳在数据科学中很重要。 正如在 为什么选择 Julia? (Section 2) 节讨论的那样,Python 中的 pandas 使用它自己的 datetime 类型处理日期。 R 语言中 TidyVerse 的 lubridate 包中也是如此,它也定义了自己的 datetime 类型来处理日期。 在 Julia 软件包中,不需要编写自己的日期逻辑,因为 Julia 标准库中有一个名为 Dates 的日期处理模块。

首先加载 Dates 模块到工作空间中:

  1. using Dates

3.5.1.1 Date and DateTime Types

Dates 标准库模块有 两种处理日期的类型:

  1. Date: 表示以天为单位的时间和
  2. DateTime: 表示以毫秒为单位的时间。

构造 DateDateTime 的方法是,向默认构造器传递表示年,月,日,小时等等的整数:

  1. Date(1987) # year
  1. 1987-01-01
  1. Date(1987, 9) # year, month
  1. 1987-09-01
  1. Date(1987, 9, 13) # year, month, day
  1. 1987-09-13
  1. DateTime(1987, 9, 13, 21) # year, month, day, hour
  1. 1987-09-13T21:00:00
  1. DateTime(1987, 9, 13, 21, 21) # year, month, day, hour, minute
  1. 1987-09-13T21:21:00

好奇的人会发现,1987 年 9 月 13 日 21 点 21 分正是第一作者 Jose 的官方出生时间。

也可以向默认构造器传递 Period 类型。 对于计算机来说,Period 类型是时间的等价表示。 Julia 的 Dates 具有如下的 Period 抽象类型:

  1. subtypes(Period)
  1. DatePeriod
  1. TimePeriod

它被划分为如下的具体类型,并且它们的用法都是不言自明的:

  1. subtypes(DatePeriod)
  1. Day
  1. Month
  1. Quarter
  1. Week
  1. Year
  1. subtypes(TimePeriod)
  1. Hour
  1. Microsecond
  1. Millisecond
  1. Minute
  1. Nanosecond
  1. Second

因此,也能以如下方式构造 Jose 的官方出生时间:

  1. DateTime(Year(1987), Month(9), Day(13), Hour(21), Minute(21))
  1. 1987-09-13T21:21:00

3.5.1.2 序列化 Dates

多数情况下,我们不会从零开始构造 DateDateTime 示例。 实际上更可能是 将字符串序列化为 DateDateTime 类型

DateDateTime 构造器可以接收一个数字字符串和格式字符串。 例如,表示 1987 年 9 月 13 日的字符串 "19870913" 可被序列化为:

  1. Date("19870913", "yyyymmdd")
  1. 1987-09-13

注意第二个参数是日期格式的字符串表示。 前四位表示年 y,后接着的两位表示月 m, 而最后两位数字表示日 d.

这也适用于 DateTime 的时间戳:

  1. DateTime("1987-09-13T21:21:00", "yyyy-mm-ddTHH:MM:SS")
  1. 1987-09-13T21:21:00

可以在 Julia Dates’ documentation 了解到更多的日期格式。 不同担心需要时常浏览文档,我们在处理日期和时间戳时也是这样。

根据 Julia Dates’ documentation,当只需调用几次时,使用 Date(date_string, format_string) 方法也是可以的。 然而,如果需要处理大量相同格式的日期字符串,那么更高效的方法是先创建 DateFormat 类型,然后传递该类型而不是原始的格式字符串。 然后,先前的例子改为:

  1. format = DateFormat("yyyymmdd")
  2. Date("19870913", format)
  1. 1987-09-13

或者,在不损失性能的情况下,使用字符串字面量前缀 dateformat"..."

  1. Date("19870913", dateformat"yyyymmdd")
  1. 1987-09-13

3.5.1.3 提取日期信息

很容易 DateDateTime 对象中提取想要的信息。 首先,创建一个具体日期的实例:

  1. my_birthday = Date("1987-09-13")
  1. 1987-09-13

然后可以从 my_birthday 中提取任何想要的信息:

  1. year(my_birthday)
  1. 1987
  1. month(my_birthday)
  1. 9
  1. day(my_birthday)
  1. 13

Julia 的 Dates 模块也提供了 返回值元组的复合函数

  1. yearmonth(my_birthday)
  1. (1987, 9)
  1. monthday(my_birthday)
  1. (9, 13)
  1. yearmonthday(my_birthday)
  1. (1987, 9, 13)

也能了解该日期是一周的第几天和其他方便的应用:

  1. dayofweek(my_birthday)
  1. 7
  1. dayname(my_birthday)
  1. Sunday
  1. dayofweekofmonth(my_birthday)
  1. 2

是的,Jose 出生在 9 月的第 2 个星期日。

NOTE: 如下是一个从 Dates 实例中提取工作日的便捷提示。 对 dayofweek(your_date) <= 5 使用 filter。 也可以使用 BusinessDays.jl 包进行工作日相关的操作。

3.5.1.4 日期操作

可以对 Dates 实例进行多种 操作 。 例如,可以对一个 DateDateTime 实例增加天数。 请注意,Julia 的 Dates 将自动地对闰年以及 30 天或 31 天的月份执行必要的调整(这称为 日历 算术)。

  1. my_birthday + Day(90)
  1. 1987-12-12

我们想加多少天就加多少天:

  1. my_birthday + Day(90) + Month(2) + Year(1)
  1. 1989-02-11

可能你想知道:“还能用 Dates 做些什么?还有哪些可用的方法?”,则可以使用 methodswith 检索这些方法。 这里只展示前 20 条结果:

  1. first(methodswith(Date), 20)
  1. [1] show(io::IO, dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/io.jl:736
  2. [2] show(io::IO, ::MIME{Symbol("text/plain")}, dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/io.jl:734
  3. [3] DateTime(dt::Date, t::Time) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/types.jl:403
  4. [4] Day(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/periods.jl:36
  5. [5] Month(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/periods.jl:36
  6. [6] Quarter(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/periods.jl:36
  7. [7] Week(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/periods.jl:36
  8. [8] Year(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/periods.jl:36
  9. [9] firstdayofmonth(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/adjusters.jl:84
  10. [10] firstdayofquarter(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/adjusters.jl:157
  11. [11] firstdayofweek(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/adjusters.jl:52
  12. [12] firstdayofyear(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/adjusters.jl:119
  13. [13] lastdayofmonth(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/adjusters.jl:100
  14. [14] lastdayofquarter(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/adjusters.jl:180
  15. [15] lastdayofweek(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/adjusters.jl:68
  16. [16] lastdayofyear(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/adjusters.jl:135
  17. [17] +(dt::Date, t::Time) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/arithmetic.jl:19
  18. [18] +(dt::Date, y::Year) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/arithmetic.jl:27
  19. [19] +(dt::Date, z::Month) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/arithmetic.jl:54
  20. [20] +(x::Date, y::Quarter) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/arithmetic.jl:73

由上可知,也能使用加 + 和减 - 运算符。 让我们看看 Jose 的年龄, 以天为单位:

  1. today() - my_birthday
  1. 12836 days

Date 类型的 默认持续时间Day 实例。 而对于 DateTime 类型,默认持续时间是 Millisecond 实例。

  1. DateTime(today()) - DateTime(my_birthday)
  1. 1109030400000 milliseconds

3.5.1.5 日期区间

关于 Dates 模块的一个好处是,可以轻松地构造 日期和时间区间。 Julia 足够聪明,因此不用去定义在 Section 3.3.6 中讨论的整个区间类型和操作。 它只需将为 range 定义的函数和操作扩展到 Date 类型。 这就是在 为什么选择 Julia? (Section 2) 中讨论过的多重派发。

例如,假设想要创建一个 Day 区间。 这可以轻松地通过冒号 : 运算符实现:

  1. Date("2021-01-01"):Day(1):Date("2021-01-07")
  1. 2021-01-01
  1. 2021-01-02
  1. 2021-01-03
  1. 2021-01-04
  1. 2021-01-05
  1. 2021-01-06
  1. 2021-01-07

使用 Day(1) 作为间隔没有什么特别的, 可以使用 任意的 Period 类型 作为间隔。 比如,使用 3 天作为间隔:

  1. Date("2021-01-01"):Day(3):Date("2021-01-07")
  1. 2021-01-01
  1. 2021-01-04
  1. 2021-01-07

又或者是月份:

  1. Date("2021-01-01"):Month(1):Date("2021-03-01")
  1. 2021-01-01
  1. 2021-02-01
  1. 2021-03-01

注意, 这个区间的类型是内含 Date 和具体 Period 类型的StepRange,其中 Period 用于作为间隔:

  1. date_interval = Date("2021-01-01"):Month(1):Date("2021-03-01")
  2. typeof(date_interval)
  1. StepRange{Date, Month}

可以使用 collect 函数将它转换为 向量

  1. collected_date_interval = collect(date_interval)
  1. 2021-01-01
  1. 2021-02-01
  1. 2021-03-01

并具有全部的可用数组功能,例如索引:

  1. collected_date_interval[end]
  1. 2021-03-01

也可以在 Date 向量中实现 日期操作的广播

  1. collected_date_interval .+ Day(10)
  1. 2021-01-11
  1. 2021-02-11
  1. 2021-03-11

同理,这些例子也适用于 DateTime 类型。

3.5.2 随机数

Random 模块是另一重要的 Julia 标准库模块。 这个模块的用途是 生成随机数Random 是功能丰富的库,如果感兴趣,可以阅读查看 Julia’s Random documentation 了解更多信息。 接下来 讨论三个函数: randrandnseed!

在开始前,首先导入 Random 模块。 先精确地导入想使用的方法:

  1. using Random: seed!

主要有 两个生成随机数的函数

  • rand: 在某种数据结构或类型的 元素 中做随机抽样。
  • randn: 在标准正态分布(平均值 0 和标准差 1)中做随机抽样。

3.5.2.1 rand

默认情况下,可以不带参数地调用 rand ,那么它就会返回一个位于区间 \([0, 1)\) 的 Float64 随机数,该区间表示随机数的范围是 0 (包含)到 1 (排除)之间:

  1. rand()
  1. 0.5948892232969126

可以使用多种方式更新 rand 的参数。 假设,想要获得多个随机数:

  1. rand(3)
  1. [0.6624302616592804, 0.5298519928354318, 0.6566323506396953]

或者,想在不同的区间抽样:

  1. rand(1.0:10.0)
  1. 6.0

也可以给区间指定步长,甚至可以在不同的类型间抽样。 这里使用不带点 . 的数字,所以 Julia 会将它们解释为 Int64 而不是 Float64

  1. rand(2:2:20)
  1. 2

还可以组合和匹配参数:

  1. rand(2:2:20, 3)
  1. [14, 4, 8]

它还支持元素集构成的元组:

  1. rand((42, "Julia", 3.14))
  1. 42

也支持数组:

  1. rand([1, 2, 3])
  1. 3

还可以是 Dict

  1. rand(Dict(:one => 1, :two => 2))
  1. :two => 2

最后要讨论的 rand 参数选项是,使用数字元组指定随机数的维度。 如果执行此操作,那么返回类型将会变为数组。 例如,如下是由 1.0-3.0 间的 Float64 随机数构成的 2x2 矩阵:

  1. rand(1.0:3.0, (2, 2))
  1. 2×2 Matrix{Float64}:
  2. 3.0 1.0
  3. 3.0 2.0

3.5.2.2 randn

randn 遵循与 rand 相同的生成原理,但现在它只返回从 标准正态分布 中生成的随机数。 标准正态分布是平均值为 0 和标准差为 1 的正态分布。 其默认类型为 Float64,并且只接受 AbstractFloatComplex 的子类型:

  1. randn()
  1. 0.15198409671103658

可以仅指定大小:

  1. randn((2, 2))
  1. 2×2 Matrix{Float64}:
  2. 0.726461 -0.802142
  3. 0.887722 1.05791

3.5.2.3 seed!

Random 概述的结尾部分, 我们接下来讨论 重现性。 我们经常需要让某些事能够 可复现。 这意味着,随机数生成器要每次 生成相同的随机数序列。 这可以通过 seed! 函数实现:

  1. seed!(123)
  2. rand(3)
  1. [0.521213795535383, 0.5868067574533484, 0.8908786980927811]
  1. seed!(123)
  2. rand(3)
  1. [0.521213795535383, 0.5868067574533484, 0.8908786980927811]

在一些例子中,在脚本开头调用 seed! 是不够好的。 为了避免 randrandn 依赖全局变量,那么可以转而定义一个 seed! 的实例,然后将它传递给 randrandn 的第一个参数。

  1. my_seed = seed!(123)
  1. Random.TaskLocalRNG()
  1. rand(my_seed, 3)
  1. [0.521213795535383, 0.5868067574533484, 0.8908786980927811]
  1. randn(my_seed, 3)
  1. [-0.21766510678354617, 0.4922456865251828, 0.9809798121241488]

NOTE: 请注意,对于不同的版本,这些数字可能会有所不同。 若要在不同的版本获得稳定的随机数流,请使用 StableRNGs.jl 库。

3.5.3 Downloads

最后一个要讨论的 Julia 标准库是 Downloads 模块。 这部分相当简短,因为只关注单个函数 download

假设想要 从互联网上下载文件到本地。 这可以用通过 download 函数实现。 最简单情况下,仅仅需要一个参数,那就是文件的 url。 还可以指定第二个参数作为下载文件的输出路径 (不要忘记文件系统的最佳实践!)。 如果不指定第二个参数,默认情况下,Julia 将使用 tempfile 函数创建一个临时文件。

首先导入 Downloads 模块:

  1. using Downloads

例如,下载 JuliaDataScience GitHub 仓库Project.toml 文件。 注意,Downloads 模块并没有导出 download 函数,因此需要使用语法 Module.function 。 默认情况下, 它返回一个包含下载文件本地路径的字符串:

  1. url = "https://raw.githubusercontent.com/JuliaDataScience/JuliaDataScience/main/Project.toml"
  2. my_file = Downloads.download(url) # tempfile() being created
  1. /tmp/jl_rxAWj4

可以使用 readlines 查看下载文件的前四行:

  1. readlines(my_file)[1:4]
  1. 4-element Vector{String}:
  2. "name = \"JDS\""
  3. "uuid = \"6c596d62-2771-44f8-8373-3ec4b616ee9d\""
  4. "authors = [\"Jose Storopoli\", \"Rik Huijzer\", \"Lazaro Alonso\"]"
  5. ""

NOTE: 对于更复杂的 HTTP 交互过程,例如与 web API 交互,请使用 HTTP.jl 包。

CC BY-NC-SA 4.0 Jose Storopoli, Rik Huijzer, Lazaro Alonso, 刘贵欣 (中文翻译), 田俊 (中文审校)