3.5 Julia 标准库
Julia 拥有 丰富的标准库,每个 Julia 发行版都可以使用这些库。 与截至目前提到的一切相反,例如类型,数据结构和文件系统;在使用特定的模块或函数前, 需要将标准库模块导入到环境中。
这可以通过 using
或 import
实现。 本书将使用 using
导入代码:
using ModuleName
在执行上述操作后,就可以使用 ModuleName
中所有的函数和类型。
3.5.1 日期
了解如何处理日期和时间戳在数据科学中很重要。 正如在 为什么选择 Julia? (Section 2) 节讨论的那样,Python 中的 pandas
使用它自己的 datetime
类型处理日期。 R 语言中 TidyVerse 的 lubridate
包中也是如此,它也定义了自己的 datetime
类型来处理日期。 在 Julia 软件包中,不需要编写自己的日期逻辑,因为 Julia 标准库中有一个名为 Dates
的日期处理模块。
首先加载 Dates
模块到工作空间中:
using Dates
3.5.1.1 Date
and DateTime
Types
Dates
标准库模块有 两种处理日期的类型:
Date
: 表示以天为单位的时间和DateTime
: 表示以毫秒为单位的时间。
构造 Date
和 DateTime
的方法是,向默认构造器传递表示年,月,日,小时等等的整数:
Date(1987) # year
1987-01-01
Date(1987, 9) # year, month
1987-09-01
Date(1987, 9, 13) # year, month, day
1987-09-13
DateTime(1987, 9, 13, 21) # year, month, day, hour
1987-09-13T21:00:00
DateTime(1987, 9, 13, 21, 21) # year, month, day, hour, minute
1987-09-13T21:21:00
好奇的人会发现,1987 年 9 月 13 日 21 点 21 分正是第一作者 Jose 的官方出生时间。
也可以向默认构造器传递 Period
类型。 对于计算机来说,Period
类型是时间的等价表示。 Julia 的 Dates
具有如下的 Period
抽象类型:
subtypes(Period)
DatePeriod
TimePeriod
它被划分为如下的具体类型,并且它们的用法都是不言自明的:
subtypes(DatePeriod)
Day
Month
Quarter
Week
Year
subtypes(TimePeriod)
Hour
Microsecond
Millisecond
Minute
Nanosecond
Second
因此,也能以如下方式构造 Jose 的官方出生时间:
DateTime(Year(1987), Month(9), Day(13), Hour(21), Minute(21))
1987-09-13T21:21:00
3.5.1.2 序列化 Dates
多数情况下,我们不会从零开始构造 Date
或 DateTime
示例。 实际上更可能是 将字符串序列化为 Date
或 DateTime
类型。
Date
和 DateTime
构造器可以接收一个数字字符串和格式字符串。 例如,表示 1987 年 9 月 13 日的字符串 "19870913"
可被序列化为:
Date("19870913", "yyyymmdd")
1987-09-13
注意第二个参数是日期格式的字符串表示。 前四位表示年 y
,后接着的两位表示月 m
, 而最后两位数字表示日 d
.
这也适用于 DateTime
的时间戳:
DateTime("1987-09-13T21:21:00", "yyyy-mm-ddTHH:MM:SS")
1987-09-13T21:21:00
可以在 Julia Dates’ documentation 了解到更多的日期格式。 不同担心需要时常浏览文档,我们在处理日期和时间戳时也是这样。
根据 Julia Dates’ documentation,当只需调用几次时,使用 Date(date_string, format_string)
方法也是可以的。 然而,如果需要处理大量相同格式的日期字符串,那么更高效的方法是先创建 DateFormat
类型,然后传递该类型而不是原始的格式字符串。 然后,先前的例子改为:
format = DateFormat("yyyymmdd")
Date("19870913", format)
1987-09-13
或者,在不损失性能的情况下,使用字符串字面量前缀 dateformat"..."
:
Date("19870913", dateformat"yyyymmdd")
1987-09-13
3.5.1.3 提取日期信息
很容易 从 Date
和 DateTime
对象中提取想要的信息。 首先,创建一个具体日期的实例:
my_birthday = Date("1987-09-13")
1987-09-13
然后可以从 my_birthday
中提取任何想要的信息:
year(my_birthday)
1987
month(my_birthday)
9
day(my_birthday)
13
Julia 的 Dates
模块也提供了 返回值元组的复合函数:
yearmonth(my_birthday)
(1987, 9)
monthday(my_birthday)
(9, 13)
yearmonthday(my_birthday)
(1987, 9, 13)
也能了解该日期是一周的第几天和其他方便的应用:
dayofweek(my_birthday)
7
dayname(my_birthday)
Sunday
dayofweekofmonth(my_birthday)
2
是的,Jose 出生在 9 月的第 2 个星期日。
NOTE: 如下是一个从
Dates
实例中提取工作日的便捷提示。 对dayofweek(your_date) <= 5
使用filter
。 也可以使用 BusinessDays.jl 包进行工作日相关的操作。
3.5.1.4 日期操作
可以对 Dates
实例进行多种 操作 。 例如,可以对一个 Date
或 DateTime
实例增加天数。 请注意,Julia 的 Dates
将自动地对闰年以及 30 天或 31 天的月份执行必要的调整(这称为 日历 算术)。
my_birthday + Day(90)
1987-12-12
我们想加多少天就加多少天:
my_birthday + Day(90) + Month(2) + Year(1)
1989-02-11
可能你想知道:“还能用 Dates
做些什么?还有哪些可用的方法?”,则可以使用 methodswith
检索这些方法。 这里只展示前 20 条结果:
first(methodswith(Date), 20)
[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] 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] 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] Day(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/periods.jl:36
[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] Quarter(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/periods.jl:36
[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] Year(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/periods.jl:36
[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] firstdayofquarter(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/adjusters.jl:157
[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] firstdayofyear(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/adjusters.jl:119
[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] lastdayofquarter(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/adjusters.jl:180
[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] lastdayofyear(dt::Date) in Dates at /opt/hostedtoolcache/julia/1.7.3/x64/share/julia/stdlib/v1.7/Dates/src/adjusters.jl:135
[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] +(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] +(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] +(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 的年龄, 以天为单位:
today() - my_birthday
12836 days
Date
类型的 默认持续时间 是 Day
实例。 而对于 DateTime
类型,默认持续时间是 Millisecond
实例。
DateTime(today()) - DateTime(my_birthday)
1109030400000 milliseconds
3.5.1.5 日期区间
关于 Dates
模块的一个好处是,可以轻松地构造 日期和时间区间。 Julia 足够聪明,因此不用去定义在 Section 3.3.6 中讨论的整个区间类型和操作。 它只需将为 range
定义的函数和操作扩展到 Date
类型。 这就是在 为什么选择 Julia? (Section 2) 中讨论过的多重派发。
例如,假设想要创建一个 Day
区间。 这可以轻松地通过冒号 :
运算符实现:
Date("2021-01-01"):Day(1):Date("2021-01-07")
2021-01-01
2021-01-02
2021-01-03
2021-01-04
2021-01-05
2021-01-06
2021-01-07
使用 Day(1)
作为间隔没有什么特别的, 可以使用 任意的 Period
类型 作为间隔。 比如,使用 3 天作为间隔:
Date("2021-01-01"):Day(3):Date("2021-01-07")
2021-01-01
2021-01-04
2021-01-07
又或者是月份:
Date("2021-01-01"):Month(1):Date("2021-03-01")
2021-01-01
2021-02-01
2021-03-01
注意, 这个区间的类型是内含 Date
和具体 Period
类型的StepRange
,其中 Period
用于作为间隔:
date_interval = Date("2021-01-01"):Month(1):Date("2021-03-01")
typeof(date_interval)
StepRange{Date, Month}
可以使用 collect
函数将它转换为 向量 :
collected_date_interval = collect(date_interval)
2021-01-01
2021-02-01
2021-03-01
并具有全部的可用数组功能,例如索引:
collected_date_interval[end]
2021-03-01
也可以在 Date
向量中实现 日期操作的广播 :
collected_date_interval .+ Day(10)
2021-01-11
2021-02-11
2021-03-11
同理,这些例子也适用于 DateTime
类型。
3.5.2 随机数
Random
模块是另一重要的 Julia 标准库模块。 这个模块的用途是 生成随机数。 Random
是功能丰富的库,如果感兴趣,可以阅读查看 Julia’s Random documentation 了解更多信息。 接下来 只 讨论三个函数: rand
, randn
和 seed!
。
在开始前,首先导入 Random
模块。 先精确地导入想使用的方法:
using Random: seed!
主要有 两个生成随机数的函数:
rand
: 在某种数据结构或类型的 元素 中做随机抽样。randn
: 在标准正态分布(平均值 0 和标准差 1)中做随机抽样。
3.5.2.1 rand
默认情况下,可以不带参数地调用 rand
,那么它就会返回一个位于区间 \([0, 1)\) 的 Float64
随机数,该区间表示随机数的范围是 0 (包含)到 1 (排除)之间:
rand()
0.5948892232969126
可以使用多种方式更新 rand
的参数。 假设,想要获得多个随机数:
rand(3)
[0.6624302616592804, 0.5298519928354318, 0.6566323506396953]
或者,想在不同的区间抽样:
rand(1.0:10.0)
6.0
也可以给区间指定步长,甚至可以在不同的类型间抽样。 这里使用不带点 .
的数字,所以 Julia 会将它们解释为 Int64
而不是 Float64
:
rand(2:2:20)
2
还可以组合和匹配参数:
rand(2:2:20, 3)
[14, 4, 8]
它还支持元素集构成的元组:
rand((42, "Julia", 3.14))
42
也支持数组:
rand([1, 2, 3])
3
还可以是 Dict
:
rand(Dict(:one => 1, :two => 2))
:two => 2
最后要讨论的 rand
参数选项是,使用数字元组指定随机数的维度。 如果执行此操作,那么返回类型将会变为数组。 例如,如下是由 1.0-3.0 间的 Float64
随机数构成的 2x2 矩阵:
rand(1.0:3.0, (2, 2))
2×2 Matrix{Float64}:
3.0 1.0
3.0 2.0
3.5.2.2 randn
randn
遵循与 rand
相同的生成原理,但现在它只返回从 标准正态分布 中生成的随机数。 标准正态分布是平均值为 0 和标准差为 1 的正态分布。 其默认类型为 Float64
,并且只接受 AbstractFloat
或 Complex
的子类型:
randn()
0.15198409671103658
可以仅指定大小:
randn((2, 2))
2×2 Matrix{Float64}:
0.726461 -0.802142
0.887722 1.05791
3.5.2.3 seed!
在 Random
概述的结尾部分, 我们接下来讨论 重现性。 我们经常需要让某些事能够 可复现。 这意味着,随机数生成器要每次 生成相同的随机数序列。 这可以通过 seed!
函数实现:
seed!(123)
rand(3)
[0.521213795535383, 0.5868067574533484, 0.8908786980927811]
seed!(123)
rand(3)
[0.521213795535383, 0.5868067574533484, 0.8908786980927811]
在一些例子中,在脚本开头调用 seed!
是不够好的。 为了避免 rand
或 randn
依赖全局变量,那么可以转而定义一个 seed!
的实例,然后将它传递给 rand
或 randn
的第一个参数。
my_seed = seed!(123)
Random.TaskLocalRNG()
rand(my_seed, 3)
[0.521213795535383, 0.5868067574533484, 0.8908786980927811]
randn(my_seed, 3)
[-0.21766510678354617, 0.4922456865251828, 0.9809798121241488]
NOTE: 请注意,对于不同的版本,这些数字可能会有所不同。 若要在不同的版本获得稳定的随机数流,请使用
StableRNGs.jl
库。
3.5.3 Downloads
最后一个要讨论的 Julia 标准库是 Downloads
模块。 这部分相当简短,因为只关注单个函数 download
。
假设想要 从互联网上下载文件到本地。 这可以用通过 download
函数实现。 最简单情况下,仅仅需要一个参数,那就是文件的 url。 还可以指定第二个参数作为下载文件的输出路径 (不要忘记文件系统的最佳实践!)。 如果不指定第二个参数,默认情况下,Julia 将使用 tempfile
函数创建一个临时文件。
首先导入 Downloads
模块:
using Downloads
例如,下载 JuliaDataScience GitHub 仓库 的 Project.toml
文件。 注意,Downloads
模块并没有导出 download
函数,因此需要使用语法 Module.function
。 默认情况下, 它返回一个包含下载文件本地路径的字符串:
url = "https://raw.githubusercontent.com/JuliaDataScience/JuliaDataScience/main/Project.toml"
my_file = Downloads.download(url) # tempfile() being created
/tmp/jl_rxAWj4
可以使用 readlines
查看下载文件的前四行:
readlines(my_file)[1:4]
4-element Vector{String}:
"name = \"JDS\""
"uuid = \"6c596d62-2771-44f8-8373-3ec4b616ee9d\""
"authors = [\"Jose Storopoli\", \"Rik Huijzer\", \"Lazaro Alonso\"]"
""
NOTE: 对于更复杂的 HTTP 交互过程,例如与 web API 交互,请使用 HTTP.jl 包。
CC BY-NC-SA 4.0 Jose Storopoli, Rik Huijzer, Lazaro Alonso, 刘贵欣 (中文翻译), 田俊 (中文审校)