Julia 编程项目(一)
原文:
annas-archive.org/md5/0086c86218c52c8cc6ad2375a5d3ae02译者:飞龙
前言
Julia 是一种新的编程语言,它结合了性能和生产力,有望改变科学计算以及编程本身。
Julia 选择了现有编程语言的最佳部分,提供了诸如强大的 REPL、表达性语法、Lisp 风格的元编程能力、强大的数值和科学编程库、内置的包管理器、高效的 Unicode 支持,以及可以轻松调用的 C 和 Python 函数等开箱即用的特性。
它具有与 C 语言类似的执行速度,在多核、GPU 和基于云的计算中有着出色的应用。《Julia 编程项目》 使用 Julia v1.0 的支持解释了这一切。
经过六年作为开源项目的发展,Julia 现在随着 v1.0 版本的发布,已经准备好登上舞台。
本书面向对象
数据科学家、统计学家、商业分析师和开发者,如果他们对学习如何使用 Julia 进行数据处理、数据分析以及构建应用程序感兴趣,会发现这本书很有用。本书假设读者具备基本的编程知识。
本书涵盖内容
第一章,开始使用 Julia 编程,介绍了 Julia 语言,涵盖了它是什么以及它的优势。然后,本章将指导您设置一个可工作的 Julia 环境,查看运行 Julia 的各种本地和在线选项。我们将涵盖安装、REPL 和 IDE 选项,以及通过集成包管理器扩展语言的基本知识。
第二章,创建我们的第一个 Julia 应用程序,将展示如何使用 Julia 对 Iris 数据集进行数据分析。我们查看 RDatasets 包,这是一个提供对与 R 语言一起分发的 700 个学习数据集访问权限的包。我们将加载 Iris 数据集,并使用标准的数据分析函数对其进行操作。我们还通过使用 Gadfly 等常见的可视化技术更仔细地查看数据。在这个过程中,我们将涵盖字符串和正则表达式、数字、元组、范围和数组。最后,我们将看到如何使用 CSV、Feather 和 MongoDB 持久化和(重新)加载数据。
第三章,设置维基游戏,介绍了我们的第一个功能齐全的 Julia 项目,一个伪装成流行游戏的维基百科网络爬虫。在第一轮迭代中,我们将构建一个从维基百科获取随机网页的程序。然后我们将学习如何使用 CSS 选择器解析 HTML 响应。我们将利用这一点来介绍诸如函数、元组、字典、异常和条件评估等关键概念。
第四章,构建维基游戏网络爬虫,将在前一章的基础上进行构建,我们将构建一个实现维基游戏要求的维基百科网络爬虫。
第五章,为 Wiki 游戏添加 Web UI,我们将通过添加 Web UI 来完成 Wiki 游戏的开发。我们将构建一个简单的 Web 应用程序,允许玩家开始新游戏,渲染游戏引擎选择的维基百科文章,并在链接的维基百科文章之间导航。UI 还将跟踪并显示当前游戏进度,并确定一个会话为胜利或失败。
第六章,使用 Julia 实现推荐系统,将让您承担一个更具挑战性的示例项目,并构建几个基本的推荐系统。我们将设置一个由 Julia 驱动的监督机器学习系统,并开发一些简单的电影推荐系统。
第七章,推荐系统的机器学习,将向您展示如何使用 Recommender.jl 包实现更强大的推荐系统。我们将使用样本数据集来训练我们的系统,并在学习基于模型的推荐系统时生成书籍推荐。
第八章,利用无监督学习技术,将教会您如何使用 Julia 执行无监督机器学习,即聚类。我们将通过使用旧金山商业注册来实践。我们将使用强大的 DataFrames 包和 Query.jl 来切片和切块数据集,并通过可视化获得更多见解。在这个过程中,我们将了解元编程和 Clustering.jl。
第九章,处理日期、时间和时间序列,是关于日期、时间和时间序列的两章中的第一章。在这里,我们将向您介绍处理日期、时区和时间序列的基础知识。我们将使用 TimeSeries.jl 包和 Plots.jl 来分析时间序列数据,并了解 TimeArray 数据结构。
第十章,时间序列预测,我们将对欧盟失业数据进行分析并预测失业人数。您将学习如何开发预测模型、训练它并生成预测。
第十一章,创建 Julia 包,是最后一章,将指导你开发一个功能齐全的 Julia 包。我们将讨论更高级的包管理功能、单元测试、基准测试和性能技巧,为 Julia 软件添加和生成文档,以及包发布和注册。
为了充分利用本书
假设您熟悉另一种编程语言,因为本书专注于 Julia 的特定内容,而不介绍通用的编程和计算机科学概念。
您需要一个运行 Windows、macOS 或流行的 Linux 版本、并且能够安装和启动程序(命令行、集成开发环境(IDE)、编辑器等)的计算机,以及互联网连接。
下载示例代码文件
您可以从www.packt.com账户下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packt.com/support并注册,以便将文件直接通过电子邮件发送给您。
您可以通过以下步骤下载代码文件:
-
在www.packt.com登录或注册。
-
选择支持选项卡。
-
点击代码下载与勘误。
-
在搜索框中输入书名,并遵循屏幕上的说明。
一旦文件下载完成,请确保使用最新版本的软件解压或提取文件夹:
-
Windows 下的 WinRAR/7-Zip
-
Mac 下的 Zipeg/iZip/UnRarX
-
Linux 下的 7-Zip/PeaZip
本书代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Julia-Programming-Projects。如果代码有更新,它将在现有的 GitHub 仓库中更新。
我们还有其他来自我们丰富图书和视频目录的代码包可供在**github.com/PacktPublishing/**下载。查看它们!
下载彩色图像
我们还提供了一份包含本书中使用的截图/图表彩色图像的 PDF 文件。您可以从这里下载:www.packtpub.com/sites/default/files/downloads/9781788292740_ColorImages.pdf。
使用的约定
本书使用了多种文本约定。
CodeInText: 表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“难度级别已经在Gameplay模块中定义,所以不要忘记声明我们正在using Gameplay。”
代码块按照以下方式设置:
function articleinfo(content)
dom = articledom(content)
(extractcontent(dom.root), extractlinks(dom.root), extracttitle(dom.root), extractimage(dom.root))
end
当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:
@from i in df begin
@where i.Parking_Tax == true
@select i
@collect DataFrame
end
任何命令行输入或输出都按照以下方式编写:
pkg> add PackageName@vX.Y.Z
pkg> add IJulia@v1.14.1
粗体: 表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词在文本中显示如下。以下是一个示例:“但是versicolor和virginica?并不多。”
警告或重要提示显示如下。
技巧和窍门显示如下。
联系我们
欢迎读者反馈。
一般反馈:如果您对本书的任何方面有疑问,请在邮件主题中提及书名,并通过customercare@packtpub.com给我们发邮件。
勘误: 尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,如果您能向我们报告,我们将不胜感激。请访问 www.packt.com/submit-erra…,选择您的书籍,点击勘误提交表单链接,并输入详细信息。
盗版: 如果您在互联网上以任何形式发现了我们作品的非法副本,如果您能提供位置地址或网站名称,我们将不胜感激。请通过发送链接至 copyright@packt.com 与我们联系。
如果您有兴趣成为作者: 如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问 authors.packtpub.com。
评论
请留下评论。一旦您阅读并使用了这本书,为何不在您购买它的网站上留下评论呢?潜在读者可以查看并使用您的客观意见来做出购买决定,Packt 公司可以了解您对我们产品的看法,我们的作者也可以看到他们对书籍的反馈。谢谢!
如需了解 Packt 的更多信息,请访问 packt.com。
第一章:开始使用 Julia 编程
Julia 是一种高级、高性能的动态编程语言,专注于数值计算和通用编程。它相对较新——四位创建者,Jeff Bezanson、Stefan Karpinski、Viral Shah 和 Alan Edelman,于 2009 年着手创建它,2012 年首次公开提及该语言,当时他们发布了一篇博客文章,解释了他们的愿景和目标。2012 年被认为是 Julia 的官方诞生年份,使其仅有六岁。自其首次公开发布以来,Julia 已经收到了来自世界各地数百名科学家、程序员和工程师的代码贡献。它是开源的,源代码可在 GitHub 上找到,并且是拥有近 20,000 个星标(截至写作时,仍在计数)的最受欢迎的仓库之一。备受期待的第一个稳定版本 Julia v1.0 在 2018 年 8 月的伦敦 Julia 大会上发布,这是超过 700 名开源贡献者和数千名包创建者及早期用户的卓越合作的成果。到那时,该语言已经被下载超过两百万次了!
Julia 作为一种全新的替代品出现,用于传统的科学计算语言,这些语言要么是高效的,要么是快速的,但两者都不是。这被称为两种语言问题,其中初始原型代码是用动态、高效的语言(如 R 或 Python)编写的,这允许探索性编码和快速迭代,跳过了耗时的构建和编译时间。但后来,开发者被迫重写他们的程序(或者至少是程序中性能关键的部分),使用编译语言来满足科学计算的高性能要求。
Julia 的创建者认为,软件开发技术已经发展到足以支持一种结合高生产力和高性能的语言。这是他们的宣言,也是他们为 Julia 设定的目标:
"我们希望有一种开源的语言,拥有宽松的许可证。我们希望拥有 C 的速度和 Ruby 的动态性。我们希望有一种同构语言,拥有像 Lisp 一样的真正宏,但又有像 MATLAB 一样明显、熟悉的数学符号。我们希望它对通用编程的可用性像 Python 一样,对统计学的易用性像 R 一样,对字符串处理的自然性像 Perl 一样,对线性代数的强大性像 MATLAB 一样,在粘合程序方面像 shell 一样出色。一种学习起来非常简单,但又能让最严肃的黑客满意的简单语言。我们希望它是交互式的,我们希望它是编译的。"
"(我们提到它应该和 C 一样快吗?)"
看起来可能难以置信,Julia 已经成功满足了所有这些要求,创造了一种易于学习、直观、友好、高效且快速的独特语言。让我们更深入地了解一下所有这些特性。
本章我们将涵盖的主题包括:
-
快速了解 Julia——它是什么,主要功能和优势,以及为什么它可能是您下一个项目的最佳选择
-
如何设置和与本地机器上的 Julia 语言交互
-
最好的 IDE 和编辑器用于高效的 Julia 开发
-
通过了解其强大的 REPL 开始使用 Julia
-
如何使用内置的包管理器
Pkg通过第三方库扩展语言
技术要求
Julia 的包生态系统正在持续发展中,并且每天都有新的包版本发布。大多数时候,这是一个好消息,因为新版本带来了新功能和错误修复。然而,由于许多包仍在测试版(版本 0.x)中,任何新版本都可能引入破坏性更改。因此,书中展示的代码可能会停止工作。为了确保您的代码会产生与书中描述相同的结果,建议使用相同的包版本。以下是本章使用的外部包及其特定版本:
IJulia@v1.14.1
OhMyREPL@v0.4.1
Revise@v0.7.14
为了安装特定版本的包,您需要运行:
pkg> add PackageName@vX.Y.Z
例如:
pkg> add IJulia@v1.14.1
或者,您可以通过下载章节中提供的Project.toml文件,并使用pkg>实例化来安装所有使用的包:
julia> download("https://raw.githubusercontent.com/PacktPublishing/Julia-Programming-Projects/master/Chapter01/Project.toml", "Project.toml")
pkg> activate .
pkg> instantiate
为什么选择 Julia?
简而言之,Julia 确实是一种新型的编程语言,它成功地结合了编译语言的高性能和动态语言的高灵活性,通过一种友好且直观的语法,从开始就让人感觉自然。Julia 是快速的(程序在运行时编译成针对多个平台的高效原生代码),通用的(标准库支持开箱即用的强大编程任务,包括异步 I/O、进程控制、并行和分布式计算、日志记录、性能分析、包管理等等),动态和可选类型的(它是动态类型的,具有可选的类型声明,并附带一个强大的读取-评估-打印循环(REPL)用于交互式和探索性编码)。它也是技术的(擅长数值计算)和可组合的(得益于其丰富的生态系统,这些包被设计成无缝且高性能地协同工作)。
尽管最初它专注于解决高性能数值分析和计算科学的需求,但最近的版本已经将语言定位在通用计算领域,许多专门的函数被从核心移动到专用模块中。因此,它也非常适合客户端和服务器端编程,这得益于其在并发、并行和分布式计算方面的强大能力。
Julia 实现了一种基于参数多态和多重调用的类型系统,它采用垃圾回收机制,使用即时求值,内置强大的正则表达式引擎,并且可以调用 C 和 Fortran 函数而无需粘合代码。
让我们来看看语言最重要的特性,那些使 Julia 突出的部分。如果您正在考虑将 Julia 用于您的下一个项目,您可以使用这个快速清单来对照您的需求。
良好的性能
Julia 性能的关键在于基于 LLVM 的即时编译器(JIT)和一系列战略性的设计决策,这些决策允许编译器生成接近甚至大多数情况下匹配 C 语言性能的代码。
为了让您了解 Julia 在这个方面的位置,官方网站提供了一系列针对其他主流语言的微基准测试(包括 C、Python、R、Java、JavaScript、Fortran、Go、Rust、MATLAB 和 Octave),这些语言实现了计算斐波那契数列、Mandelbrot 集合、快速排序以及其他一些算法。它们旨在评估编译器对常见代码模式(如函数调用、字符串解析、排序、迭代、递归等)的性能。基准测试的图表可在julialang.org/benchmarks/找到,该图表展示了 Julia 在所有测试中的一致性顶级性能。以下图表展示了这一点:
如需了解更多关于测试方法的信息,您可以访问julialang.org/benchmarks/。
简洁、易读且直观的语法
Julia 的创造者从其他语言中精心挑选了最成功的语法元素,目的是生成表达性强、简洁且易于阅读的代码。与 R、MATLAB 和 Python 等语言一样,Julia 提供了强大的表达式性语言结构,用于高级数值计算。它建立在现有数学编程语言的经验之上,同时也借鉴了流行的动态语言,如 Lisp、Perl、Python、Lua 和 Ruby。
为了让您快速了解 Julia 的惯用法,以下是如何打开一个文件、读取它、输出它,然后由 Julia 自动关闭文件的示例:
open(".viminfo") do io
read(io, String) |> println
end
.viminfo file for reading passing io, an IOStream instance, into the underlying code block. The stream is then read into a String that is finally displayed onto the console by piping it into the println function. The code is very readable and easy to understand if you have some coding experience, even if this is your first time looking at Julia code.
这种所谓的 do 语法(以 open 函数后的 do 部分为名)受到了 Ruby 的 blocks 的启发——实际上,它是将匿名函数作为方法参数传递的语法糖。在先前的例子中,它被有效地使用,以简洁地表达一个强大的设计模式,用于安全地处理文件,确保资源不会意外地被留下打开。
这表明了语言设计者对 Julia 的安全性、易用性、表达性、简洁性、易读性和直观性的关注程度。
强大且高效的动态类型系统
Julia 的类型系统是语言的关键特性,它对性能和生产力都有重大影响。类型系统是动态和可选的,这意味着开发者可以选择但不必须向编译器提供类型信息。如果没有提供,Julia 将执行类型推断,即从输入值的类型推断后续值的类型的过程。这是一种非常强大的技术,因为它使程序员从担心类型中解放出来,使他们能够专注于应用程序逻辑,并使学习曲线更加平缓。这对于原型设计和探索性编程特别有用,当事先不知道完整的约束和要求时。
然而,理解和正确使用类型系统提供了重要的性能优势。Julia 允许可选地添加类型信息,这使得可以指明某个值必须是特定类型。这是语言的一个基石,允许高效的函数分发,并促进为不同参数类型自动生成高效、专门的代码。类型系统允许定义丰富的类型层次结构,用户定义的类型与内置类型一样快速且紧凑。
设计用于并行性和分布式计算
如果 70 年代和 80 年代的语言是在有限的 CPU 和 RAM 资源严格要求的约束下设计的,那么 90 年代和 2000 年代的语言则持有乐观的预期,认为这些资源将永远扩展。然而,在过去的十年中,在这方面出现了一些停滞,转向了多 CPU、多核和分布式计算。在这方面,Julia 仅 6 年前才出现,与较老的语言相比,它具有优势,将并行和分布式计算作为其最重要的特性之一。
与其他语言的高效互操作
在采用新语言时,最严重的障碍之一是生态系统需要时间才能赶上——在最初,它无法提供与已经建立的语言质量相当和丰富的库。现在这个问题已经不那么严重了,因为 Julia 受益于一个庞大、热情且持续增长的开发者社区。但能够与其他语言无缝通信是一种非常有效的方式来丰富现有功能,并轻松补充任何缺失的功能。
Julia 具有直接调用 C 和 Fortran 函数的能力(即,无需粘合代码)——这对于科学计算尤为重要,在这些语言中,它们具有强大的存在感和悠久的历史。
可选包通过添加对其他语言编写的函数的调用支持来扩展这一功能,最值得注意的是通过 PyCall 调用 Python,还有其他一些,支持与 Java、C++、MATLAB、Rust 等语言交互。
强大的 REPL 和类似 shell 的功能
REPL 代表一个语言外壳,是一个命令行上的交互式计算机编程环境。Julia 拥有出色的 REPL,支持复杂的代码输入和评估。它包括一些强大的编辑功能,如可搜索的历史记录、自动补全和语法高亮,仅举几例。
它还包含三种特殊模式——shell,允许像在操作系统终端一样执行命令;help,提供在不离开 REPL 的情况下访问文档的功能;以及 pkg,用于安装和管理应用程序依赖项。
更多...
Julia 自带强大的包管理器,可以解决依赖关系,并处理额外包的添加和删除。像大多数现代语言一样,Julia 完全支持 Unicode。最后,它遵循宽松的 MIT 许可协议——它是免费和开源的。
安装 Julia
如果前面的部分让您决定为您的下一个项目使用 Julia,或者至少让您对了解更多感到好奇,那么是时候设置您的 Julia 开发环境了。
Julia 拥有出色的跨平台支持,可以在所有主要操作系统上运行。安装过程很简单——该语言可以在您的本地机器、虚拟机(VM)、Docker 容器或云中的某个服务器上设置。
让我们先看看本地安装选项,针对三大操作系统(Windows、Linux 和 macOS)。您可以自由地直接跳转到适合您的选项。
Windows
Windows 作为一个开发平台已经取得了长足的进步,并且有一些很好的替代方案可以让 Julia 运行起来。
官方 Windows 安装程序
最简单的方法是下载适用于您平台(32 位或 64 位)的 Windows 安装程序,从julialang.org/downloads/。获取.exe文件并运行它。按照标准安装程序进行操作,最后,您将拥有安装为程序的 Julia。双击julia.exe将打开一个带有 Julia REPL 的命令提示符,就像这里所示:
使用 Chocolatey
Chocolatey 是 Windows 的包管理器,类似于 Linux 上的apt或yum,或 Mac 上的brew。如果您还没有它,请按照chocolatey.org上的说明获取。
Chocolatey 拥有最新的 Julia 版本,可以通过以下搜索进行确认:
$ choco search julia
Chocolatey v0.10.11
Julia 1.0.0 [Approved]
1 packages found.
安装过程就像这样简单:
$ choco install julia
Chocolatey v0.10.11
Installing the following packages:
julia
By installing you accept licenses for the packages.
Progress: Downloading Julia 1.0.0... 100%
Julia v1.0.0 [Approved]
Chocolatey installed 1/1 packages.
Windows 子系统对于 Linux
Windows 10 最近新增的功能之一是 Linux 子系统。这允许在 Windows 上直接设置 Linux 开发环境,包括大多数命令行工具、实用程序和应用程序——无需修改,无需运行虚拟机(VM)的开销。
为了能够使用 Linux 子系统,你的 PC 必须运行 Windows 10 周年更新或更高版本的 64 位版本(构建 1607+)。它还需要首先启用——因此以管理员身份打开 PowerShell 并运行以下命令:
$ Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
一旦子系统被启用(可能需要重启计算机),你可以直接从 Windows Store 选择可用的 Linux 版本。在撰写本文时,有五个版本可供选择——Ubuntu、openSUSE、SLES、Debian 和 Kali。
Ubuntu 是 Windows 10 的默认选项,在 Windows Store 中拥有最好的用户评分,所以让我们选择它。它可以从 www.microsoft.com/en-us/store/p/ubuntu/9nblggh4msv6 安装。或者,你只需打开一个命令提示符并输入 $ bash。这将触发 Ubuntu Linux 子系统的安装。
一旦你发现自己处于 Linux 子系统的 shell 提示符,你就可以继续并输入安装 Julia 的命令。对于 Ubuntu,你需要运行以下命令:
$ sudo apt-get install julia
确保确认所需的选择——然后几分钟后,你应该就可以运行 Julia 了。
macOS
在 macOS 上安装 Julia 非常简单。主要有两种选择,取决于你是否更喜欢图形安装程序还是更习惯于在终端提示符前操作。
官方镜像
访问 julialang.org/downloads/ 并查找 macOS 软件包(.dmg)。下载完成后,双击 .dmg 文件,将 Julia 应用程序拖放到 /Applications 文件夹。现在你可以简单地打开 Julia 应用程序——它将启动一个新的终端会话,加载 Julia 环境,如下所示:
Homebrew
Homebrew 是 macOS 上一个知名的包管理器,类似于 Linux 上的 apt 和 yum。虽然安装 Julia 并非必需,但值得设置它,因为它在开发过程中非常有用,因为它可以无缝地安装数据库服务器、库和其他项目组件。
根据在 brew.sh 的说明,可以在终端窗口中运行以下命令来安装:
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
可能需要一段时间,但一旦 Homebrew 安装完成,一个新的命令行工具 brew 将会可用。
最后,使用 $ brew cask install julia 将下载并安装 Julia 的最新版本。在这个过程中,它还会将 julia 二进制文件链接到 /usr/local/bin/julia,这样你就可以通过简单地输入 $ julia 来从命令行与语言交互。
一旦你收到安装成功的确认,你可以运行 $ julia 来启动一个新的 REPL 会话:
Linux 和 FreeBSD
Julia 已经可用在主要 Linux 发行版的软件仓库中,但遗憾的是,这些并不都是最新的。例如,在撰写本文时,Ubuntu 提供的是 v0.4.5,Debian 是 v0.4.7。最佳方法是使用 Julia 下载页上提供的通用 Linux 二进制文件,在julialang.org/downloads/。
请遵循对应您 Linux 发行版的说明,如julialang.org/downloads/platform.html#generic-binaries所示。
Docker
Docker 是一种提供操作系统级别虚拟化抽象额外层的软件技术。简单来说,Docker 设置了像 VM 一样的容器,但无需启动和维护 VM 的开销。您可以在所有主要操作系统上运行 Docker。
Docker 被广泛用作开发和部署策略,因此许多技术都以 Docker 镜像的形式 readily 可用,Julia 也不例外。
首先为您的平台安装 Docker。官方的 Julia 容器可以在 Docker 商店的store.docker.com/images/julia找到。去获取它。
如果您需要帮助设置 Docker 或安装容器,请参阅www.docker.com上的说明。
在命令提示符下,输入$ docker pull julia。一旦 Docker 配置了 Julia 镜像,使用$ docker exec -it --rm julia运行它。这将启动容器并加载一个新的 Julia REPL:
JuliaPro
Julia 编程语言背后的公司 Julia Computing 提供了一种包含所有组件的发行版。它被称为JuliaPro,可以说是立即开始使用 Julia 的最简单方法。它包括编译器、分析器、Juno IDE 以及超过 160 个高质量的精选包,用于绘图、数据可视化、机器学习、数据库等。
您可以在shop.juliacomputing.com/Products/免费下载 JuliaPro(需要注册)。一旦您获得它,请遵循您平台特定的安装过程。完成后,您将拥有开始高效使用 Julia 所需的一切。
付费的企业版也提供了一些额外功能,例如 Excel 集成和支持 SLA。
JuliaBox
最后,还有 JuliaBox (www.juliabox.com),这是 Julia Computing 提供的另一项免费服务。JuliaBox 允许在他们的云中即时运行 Julia Docker 容器。它提供了对 IJulia Jupyter 笔记本(github.com/JuliaLang/IJulia.jl)的访问、与 Google Drive 的文件同步、导入 GitHub 仓库以及许多其他功能。
如果你不太熟悉 Jupyter 笔记本,你可以通过访问 jupyter.org 来了解更多信息。
选择 IDE
在使用编程语言时,IDE 非常重要。一个强大的源代码编辑器、代码补全、以及良好的代码检查器和调试器可以显著影响学习曲线和使用该语言的效率。你会很高兴地了解到,对于 Julia 来说,有一些非常好的 IDE 和编辑器选项——你可能会在这些选项中找到你最喜欢的一个。
IDE 的选择反映了整个语言的实用主义。从选择 LLVM 作为编译器,到提供从其他语言调用函数的高效方式,再到使用 git 和 GitHub 来驱动包管理器,Julia 核心团队采取了“不重复造轮子”的方法。遵循同样的思路,Julia 社区在现有的行业建立编辑器的基础上构建了强大的 IDE,例如 Atom 和 Visual Studio Code。
Juno (Atom)
Juno (junolab.org) 是最先进的 Julia 集成开发环境(IDE),也是 Julia 专业人士首选的默认编辑器。它基于 Atom 编辑器,可以被认为是官方的开发工具,因为它也随之前提到的 JuliaPro 发行版一起分发。
要获取它,你可以从 juliacomputing.com/products/juliapro.html 下载并安装 JuliaPro,或者手动安装 Atom 和所需的插件。
如果你选择手动安装,首先需要从 atom.io 下载 Atom。一旦启动并运行,转到设置面板(你可以使用快捷键 Ctrl/cmd 和 ,),然后转到安装面板。在搜索框中输入 uber-juno 并按 Enter。接下来,点击具有相同名称的包的安装按钮。Atom 将从这里开始,安装所有必需的 Atom 和 Julia 包。
一旦配置完成,IDE 选项将在 Atom 的菜单中可用,在 Packages > Julia 下。也可以从这里启用各种面板,以列出变量、可视化图表或搜索文档。
如需更多信息,请查看 junolab.org 和 github.com/JunoLab/uber-juno/blob/master/setup.md。
Visual Studio Code
Visual Studio Code 是微软的一个跨平台可扩展编辑器。它适用于所有三大平台,可在 code.visualstudio.com 获取。安装后,运行它并从菜单中选择视图 > 扩展或使用快捷键 Shift 和 Ctrl/cmd 和 X。搜索 julia 并从 julialang 安装 Julia 扩展。
Visual Studio Code 中的 Julia 支持(目前)不如 Juno 强大,但如果你更喜欢它,它也能提供极佳的编码体验,包括语法高亮、代码补全、悬停帮助、Julia 代码评估、代码检查、代码导航等功能。Visual Studio Code 比 Atom 更轻快,使用的资源也更少,这使得它在运行在性能较弱的工作站上时成为一个吸引人的选择(尽管 Atom 在最近版本中在这方面有了很大改进)。
扩展可能需要一点帮助来确定它在哪里可以找到 Julia 的二进制文件。如果是这种情况,你会收到一个信息丰富的错误消息,要求你设置julia.executablePath配置选项。这应该指向 julia 的二进制文件,并且取决于你的操作系统和安装 Julia 的方式(有关安装的详细信息,请参阅上一节)。
要设置配置,请转到“首选项”>“设置”(Ctrl/cmd和*,*),然后在右侧面板中,用于覆盖默认设置的面板,添加以下内容:
"julia.executablePath": "/path/to/your/julia/folder/bin/julia"
IJulia (JuliaBox)
我们在上一节中已经提到了 JuliaBox (www.juliabox.com)——它允许在云中创建、编辑和运行 IJulia Jupyter 笔记本。IJulia 也可以安装在本地的开发机器上。
IJulia 是 Jupyter 交互式环境(也被 IPython 使用)的 Julia 语言后端。它允许我们通过 Jupyter/IPython 强大的图形笔记本与 Julia 语言进行交互,该笔记本将代码、格式化文本、数学和多媒体结合在一个文档中。
虽然 IJulia/Jupyter 并不是真正的 IDE,也不是经典的编辑器,但它是一个强大的编辑和执行 Julia 脚本的编程环境,特别受数据科学和科学计算领域的欢迎。让我们花几分钟时间来设置它。
启动一个新的 Julia REPL 并执行以下命令:
julia> using Pkg
julia> Pkg.add("IJulia")
这将安装IJulia包,同时还会添加一个名为Miniconda的必需的最小 Python 和 Jupyter 发行版。这个 Python 发行版是 Julia 专有的(不在你的PATH中)。完成后,继续执行以下命令:
julia> using IJulia
julia> notebook()
这将在你的默认浏览器中打开本地 Jupyter 安装的主页,在localhost:8888/tree。从工具栏中选择新建 > Julia 1.0.0(或你当前运行的版本)来创建一个新的笔记本。你现在可以使用嵌入的可执行 Julia 代码创建丰富的文档。
还有另一种运行 IJulia 作为桌面应用程序的方式,通过Interact。你可以下载它并尝试使用nteract.io/desktop。
如果你刚开始使用 Jupyter,了解它将很有帮助。可以去jupyter.org查看。
你也可以在本书每一章的支持文件仓库中找到每个章节的 IJulia 笔记本。这些笔记本将允许你逐步查看我们编写的代码。例如,你可以在这个章节中找到代码 github.com/PacktPublishing/Julia-Programming-Projects/blob/master/Chapter01/Chapter%201.ipynb。你可以在你的电脑上下载它,并用本地 IJulia 安装打开,或者通过他们的 Google Drive 集成上传到 JuliaBox。
其他选项
上述选择是 Julia 最常见的 IDE 和编辑器选项。但还有一些其他的选择。
对于 vim 爱好者,也有 julia-vim (github.com/JuliaEditorSupport/julia-vim)。
如果你更喜欢使用 Emacs,你会很高兴地知道 Julia 也支持它 github.com/JuliaEditorSupport/julia-emacs。
如果你更愿意使用 JetBrains 提供的 IDE(如 IntelliJ IDEA),你会很高兴地听说有一个插件可用,在 plugins.jetbrains.com/plugin/10413-julia。
最后,还有对 Sublime Text 的支持,可在 github.com/JuliaEditorSupport/Julia-sublime 找到。该插件提供了良好的 Julia 编辑体验,支持语法高亮、代码补全和跳转到定义等功能。
开始使用 Julia
如果你跟随着本章的第一部分,到现在你应该已经有一个功能齐全的本地 Julia 安装,了解如何启动 Julia REPL 会话,并且你的首选 IDE 已经准备好进行编码。如果不是这样,请参考前面的部分。从现在开始,我们将进入正题——是时候编写一些 Julia 代码了!
Julia REPL
我们首先需要了解的是如何使用强大的 REPL。作为一名 Julia 开发者,你将花费大量时间进行探索性编程,与壳和文件系统交互,以及管理包。REPL 将是你的忠实伙伴。了解它将节省你很多时间。
缩写 REPL 代表读取-评估-打印循环。简单来说,它是一种特定语言的壳,一个交互式编码环境,允许输入表达式,评估它们,并输出结果。
REPL 非常有用,因为它们提供了一种简单的方式来与语言交互,尝试想法和原型,促进探索性编程和调试。在数据分析的上下文中,它尤其强大,因为可以快速连接到数据源,加载数据样本,然后进行切片和切块,快速测试不同的假设。
Julia 提供了一个出色的 REPL 体验,具有丰富的功能,涵盖了快速评估 Julia 语句、可搜索的历史记录、自动补全、语法高亮、专用帮助和 shell 模式等,仅举几例。
如果您没有安装有效的 Julia,请参阅安装 Julia部分。
与 REPL 交互
根据您的操作系统和偏好,REPL 可以通过简单地使用无参数的$ julia命令启动,或者通过双击julia可执行文件。
您将看到一个类似这样的屏幕(Julia 版本可能与我不同):
现在,Julia 正在等待我们输入代码,逐行评估。您可以通过检查终端提示符来确认,它说julia>。这被称为julian 模式。让我们试试看。
您可以按照本章支持文件中提供的 IJulia Jupyter 笔记本进行操作。如果您不熟悉 Jupyter 且不知道如何在本地运行它,可以使用 Juliabox (juliabox.com)。您只需创建一个账户,登录,然后从github.com/PacktPublishing/Julia-Programming-Projects/blob/master/Chapter01/Chapter%201.ipynb加载笔记本。
输入以下行,每行输入后按Enter键:
julia> 2+2
julia> 2³
因此,我们可以像使用简单计算器一样使用 Julia。虽然不是很实用,但这只是开始,展示了当我们处理复杂计算时,这种快速输入和反馈循环是多么强大。
println是一个非常有用的函数,它会打印接收到的任何值,并在之后添加一个新行。输入以下代码:
julia> println("Welcome to Julia")
在每一行下面,您应该能看到每个表达式生成的输出。现在您的窗口应该看起来像这样。
julia> 2+2
4
julia> 2³
8
julia> println("Welcome to Julia")
Welcome to Julia
让我们再试一些。REPL 一次解释一行,但所有内容都在一个共同的范围内评估。这意味着我们可以定义变量并在以后引用它们,如下所示:
julia> greeting = "Hello"
"Hello"
看起来很棒!让我们使用greeting变量和println:
julia> println(greting)
ERROR: UndefVarError: greting not defined
哦!这里有个小错误,REPL 迅速返回了一个错误。不是greting,而是greeting。这也告诉我们,Julia 不允许在不正确初始化的情况下使用变量。它只是查找了greting变量,但没有成功——并抛出了一个未定义变量的错误。让我们再次尝试,这次更加小心:
julia> println(greeting)
Hello
好多了!我们可以看到输出:存储在greeting变量中的Hello值。
ans变量
REPL 提供了一些帮助功能,特定于这个交互式环境(在执行 Julia 脚本时不可用)。其中之一是ans变量,由 Julia 自动设置和更新。
如果你输入 julia> 2³——不出所料,你会得到 8。现在输入 julia> ans——你又会得到 8!这是怎么回事?ans 是一个只在 REPL 中存在的特殊变量,它会自动存储最后一个返回的值。当与 REPL 一起工作时,这可以非常有用,但更重要的是,你需要意识到它的存在,以免不小心声明了一个同名变量。否则,你可能会遇到一些非常难以理解的错误,因为你的变量值会不断被覆盖。
提示粘贴
REPL 内置了一个非常强大的功能,称为 提示粘贴。这允许我们复制粘贴并执行包含 julia> 提示和表达式输出的 Julia 代码和代码片段。当粘贴以 julia> 开头的文本时,它会激活。在这种情况下,只有以 julia> 开头的表达式会被解析,其他所有内容都会被忽略。这使得能够粘贴从另一个 REPL 会话或文档中复制出来的代码块,而无需清除提示和输出。
提示粘贴在 IJulia Jupyter 笔记本中不起作用。
要查看此功能的效果,请复制并粘贴以下代码片段,无需修改:
julia> using Dates
julia> Dates.now()
2018-09-02T21:13:03.122
julia> ans
2018-09-02T21:13:03.122
如果一切顺利,两个表达式都应该输出你当前的时间,而不是代码片段中的时间,从而有效地替换代码片段中的结果为 Julia 会话中的结果。
由于默认 Windows 命令提示符的限制,此功能无法使用。
Tab 完成功能
在 Julian、pkg 和 help 模式下,你可以在输入函数的前几个字符后按 Tab 键,以获取所有匹配项的列表:
julia> pri[TAB]
primitive type print print_shortest print_with_color println printstyled
它还可以用来将 LaTeX 数学符号替换为其 Unicode 等效符号。为此,输入一个反斜杠作为第一个字符,然后输入符号的前几个字符,然后按 Tab。这将完成符号的名称,或者如果存在多个匹配名称,将显示一个选项列表。再次按 Tab 在符号的完整名称上将会执行替换:
julia> \pi[TAB]
julia> π
π = 3.1415926535897...
julia> \om[TAB] \omega \ominus
julia> \ome[TAB]
julia> \omega[TAB]
julia> ω
清理 REPL 作用域
Julia 没有空值的概念,所以你实际上无法从内存中释放变量。然而,如果你需要释放一个由变量引用的昂贵资源,你可以将其值替换为类似 0 的东西,之前的值将会自动被垃圾回收。你甚至可以直接通过调用 gc() 来立即调用垃圾回收器。
附加 REPL 模式
Julia REPL 内置了四种操作模式——并且可以根据需要定义附加模式。当前活动模式由其提示表示。在之前的例子中,我们使用了 julian 模式 julia>,它评估输入的表达式。其他三个可用模式是 help、help?>、shell、shell> 和包管理,pkg>。
可以通过在行首输入特定的字符来切换活动模式。提示符将相应地更改,以指示当前模式。模式将保持活动状态,直到当前行被评估,自动切换回 julian(pkg>模式除外,它是粘性的,即它将保持活动状态,直到通过在行首按退格键显式退出)。可以通过删除整行直到提示符变回julia>或按Ctrl + C来退出替代模式,而无需评估表达式。
使用帮助模式访问文档
帮助模式提供访问文档的功能,无需离开 REPL。要访问它,只需在行首输入?。你应该会看到help?>提示符。现在你可以输入文本,Julia 将会搜索匹配的文档条目,如下所示:
julia> ?
help?> println
search: println printstyled print_with_color print print_shortest sprint isprint
println([io::IO], xs...)
Print (using print) xs followed by a newline. If io is not supplied, prints to stdout.
Examples
≡≡≡≡≡≡≡≡≡≡
julia> println("Hello, world")
Hello, world
julia> io = IOBuffer();
julia> println(io, "Hello, world")
julia> String(take!(io))
"Hello, world\n"
在 IJulia 中,通过在输入前加上所需的模式激活器来激活额外的模式。例如,要访问之前println函数的帮助,我们需要输入?println.。
输出支持丰富的格式化,通过 Markdown:
julia> using Profile
help?> Profile.print
结果如以下截图所示:
可以查询更复杂的表达式,包括宏、类型和变量。
例如,help?> @time:
或者help?> IO:
Shell 模式
使用 shell 模式可以切换到类似于系统 shell 的命令行界面,以便直接执行操作系统命令。要进入该模式,请在 julian 提示符的非常开始处输入一个分号;:
julia> ;
输入;后,提示符(就地)变为shell>:
要在 IJulia 中进入 shell 模式并执行 shell 命令,请在命令前加上;,例如;ls。
现在,我们可以直接执行系统范围内的命令,无需将它们包裹在 Julia 代码中。这将列出你的repl_history.jl文件的最后十行。此文件由 Julia 用于记录在 REPL 中执行的命令的历史,因此你的输出将与我不同:
julia> using REPL
shell> tail -n 10 ~/.julia/logs/repl_history.jl
IO
# time: 2018-09-02 21:56:47 CEST
# mode: julia
REPL.find_hist_file()
# time: 2018-09-02 21:58:47 CEST
# mode: shell
tail -n 10 ~/.julia/logs/repl_history.jl
当处于 REPL 模式时,我们可以访问 Julia 的 API,这使得这是一个非常强大的组合。例如,为了以编程方式获取 REPL 历史文件的路径,我们可以使用REPL.find_hist_file()函数,如下所示:
julia> REPL.find_hist_file()
"/Users/adrian/.julia/logs/repl_history.jl"
文件的路径将因人而异。
我们可以在 shell 模式下使用它,通过将命令用$()括起来:
shell> tail -n 10 $(REPL.find_hist_file())
REPL.find_hist_file()
# time: 2018-09-02 21:58:47 CEST
# mode: shell
tail -n 10 ~/.julia/logs/repl_history.jl
# time: 2018-09-02 22:00:03 CEST
# mode: shell
tail -n 10 $(REPL.find_hist_file())
与帮助模式类似,shell 模式可以通过在行首按退格键或输入Ctrl + C来退出,而无需执行任何命令。
在 IJulia 中,可以通过在输入前加上;来执行命令,如下所示:
;tail -n 10 ~/.julia/logs/repl_history.jl
搜索模式
除了帮助和 shell 模式之外,还有两种搜索模式。这些模式不一定是 Julia 特有的,它们是许多*nix 风格编辑应用程序的共同点。
同时按下Ctrl键和R键以启动反向增量搜索。提示符将变为(reverse-i-search)。开始输入你的查询,最近的搜索结果将显示出来。要找到更早的结果,再次按Ctrl + R。
Ctrl + R的对应操作是Ctrl + S,它启动增量搜索。这两个可以一起使用,分别用于移动到上一个或下一个匹配结果。
startup.jl文件
如果你想每次运行 Julia 时自动执行一些代码,你可以将其添加到一个名为startup.jl的特殊文件中。这个文件不是自动创建的,所以你需要自己将它添加到你的 Julia 配置目录中。你添加到其中的任何代码都会在 Julia 启动时运行。让我们用 Julia 来做这个实验,并且练习一下我们学到的东西。
首先,进入 shell 模式并运行以下三个命令:
shell> mkdir $(dirname(REPL.find_hist_file()))/../config
shell> cd $(dirname(REPL.find_hist_file()))/../config
/Users/adrian/.julia/config
shell> touch startup.jl
然后,在 julian 模式下,执行以下操作:
julia> write("startup.jl", "println(\"Welcome to Julia!\")")
28
我们刚才做了什么?在 shell 模式下,我们在历史文件所在目录的上一级创建了一个名为config的新目录。然后我们cd进入新创建的文件夹,在那里我们创建了一个名为startup.jl的新文件。最后,我们让 Julia 向startup.jl文件添加了这一行代码"println(\"Welcome to Julia!\")"。下次我们启动 Julia REPL 时,我们会看到这个欢迎信息。看看这个:
REPL 钩子
还可以在启动 REPL 会话之前定义一个将被自动调用的函数。为了实现这一点,你需要使用atreplinit(f)函数,它将一个单参数函数f注册为在交互式会话中初始化 REPL 界面之前调用。这个函数应该从startup.jl文件内部调用。
假设我们编辑了startup.jl文件,使其现在看起来像这样:
println("Welcome to Julia!")
atreplinit() do (f)
println("And welcome to you too!")
end
我们的 REPL 现在会两次问候我们:
可以使用atreplinit函数与isinteractive一起使用,isinteractive返回一个Boolean true或false值,告诉我们 Julia 是否正在运行一个交互式会话。
退出 REPL
要退出 REPL,你可以输入^ D(Ctrl + D)。但是,这只有在行首(当文本缓冲区为空时)才会起作用。否则,只需输入^C(Ctrl + C)来首先中断(或取消)并清除行。你也可以运行exit(),这将停止当前 Julia 进程的执行。
要查看 REPL 中的完整键绑定列表以及如何自定义它们,你可以阅读官方文档docs.julialang.org/en/v1.0/stdlib/REPL/#Key-bindings-1。
包系统
你的 Julia 安装附带了一个名为Pkg的强大包管理器。它处理所有预期的操作,例如添加和删除包、解决依赖关系、保持已安装包的更新、运行测试,甚至帮助我们发布自己的包。
包通过提供广泛的功能,无缝地扩展了核心语言,发挥着至关重要的作用。让我们看看最重要的包管理功能。
添加一个包
为了让Pkg知道,包必须添加到一个 Julia 可用的注册表中。Pkg支持同时与多个注册表一起工作——包括位于企业防火墙后面的私有注册表。默认情况下,Pkg配置为使用 Julia 的通用注册表,这是一个由 Julia 社区维护的免费和开源包的存储库。
Pkg是一个非常强大的工具,我们将在整本书中广泛使用它。在使用 Julia 进行开发时,包管理是一个常见的任务,因此我们将有多次机会逐步深入了解。现在,我们将迈出第一步,学习如何添加包——我们将通过添加一些强大的新功能到我们的 Julia 设置中来实现这一点。
OhMyREPL
我最喜欢的包之一叫做OhMyREPL。它为 Julia 的 REPL 实现了一些超级高效的功能,最显著的是语法高亮和括号配对。这是一个非常好的补充,使得交互式编码体验更加愉快和高效。
Julia 的Pkg以 GitHub 为中心。创建者将包作为 git 仓库分发,托管在 GitHub 上——甚至通用注册表本身也是一个 GitHub 仓库。OhMyREPL也不例外。在安装它之前,如果你想了解更多信息——使用第三方代码时,这总是一个好主意——你可以在github.com/KristofferC/OhMyREPL.jl查看。
请记住,即使它属于通用注册表,这些包也不提供任何保证,它们不一定经过 Julia 社区检查、验证或认可。然而,有一些常识性的指标可以提供关于包质量的洞察,最值得注意的是星标数量、测试状态以及最新 Julia 版本的兼容性。
为了添加一个包,我们首先需要进入Pkg的 REPL 模式。我们通过在行首输入]来实现这一点:
julia>]
光标将改变以反映我们现在可以管理包了:
(v1.0) pkg>
IJulia 目前不支持pkg>模式,但我们可以通过将它们包裹在pkg"..."中来执行Pkg命令,例如pkg"add OhMyREPL"。
Pkg使用环境的概念,允许我们根据项目定义不同的和独立的包集合。这是一个非常强大且有用的功能,因为它消除了由依赖于同一包的不同版本的项目(所谓的依赖地狱)引起的依赖冲突。
由于我们尚未创建任何项目,Pkg将仅使用默认项目v1.0,由括号中的值指示。这代表您正在运行的 Julia 版本——并且您可能会根据您自己的 Julia 版本得到不同的默认项目。
现在,我们只需继续添加OhMyREPL:
(v1.0) pkg> add OhMyREPL
Updating registry at `~/.julia/registries/General`
Updating git-repo `https://github.com/JuliaRegistries/General.git`
Resolving package versions...
Updating `~/.julia/environments/v1.0/Project.toml`
[5fb14364] + OhMyREPL v0.3.0
Updating `~/.julia/environments/v1.0/Manifest.toml`
[a8cc5b0e] + Crayons v1.0.0
[5fb14364] + OhMyREPL v0.3.0
[0796e94c] + Tokenize v0.5.2
[2a0f44e3] + Base64
[ade2ca70] + Dates
[8ba89e20] + Distributed
[b77e0a4c] + InteractiveUtils
[76f85450] + LibGit2
[8f399da3] + Libdl
[37e2e46d] + LinearAlgebra
[56ddb016] + Logging
[d6f4376e] + Markdown
[44cfe95a] + Pkg
[de0858da] + Printf
[3fa0cd96] + REPL
[9a3f8284] + Random
[ea8e919c] + SHA
[9e88b42a] + Serialization
[6462fe0b] + Sockets
[8dfed614] + Test
[cf7118a7] + UUIDs
[4ec0a83e] + Unicode
上一条命令在 IJulia 中的等效命令是pkg"add OhMyREPL"。
当在新的 Julia 安装上运行pkg> add时,Pkg将克隆 Julia 的通用注册表,并使用它来查找我们请求的包名。尽管我们只明确请求了OhMyREPL,但大多数 Julia 包都有外部依赖项,也需要安装。正如我们所见,我们的包有很多——但它们被Pkg迅速安装了。
自定义包安装
有时候我们可能想使用未添加到通用注册表的包。这通常适用于处于(早期)开发中的包或私有包。对于这种情况,我们可以传递pkg> add存储库的 URL,而不是包名:
(v1.0) pkg> add https://github.com/JuliaLang/Example.jl.git
Cloning git-repo `https://github.com/JuliaLang/Example.jl.git`
Updating git-repo `https://github.com/JuliaLang/Example.jl.git`
Resolving package versions...
Updating `~/.julia/environments/v1.0/Project.toml`
[7876af07] + Example v0.5.1+ #master (https://github.com/JuliaLang/Example.jl.git)
Updating `~/.julia/environments/v1.0/Manifest.toml`
[7876af07] + Example v0.5.1+ #master (https://github.com/JuliaLang/Example.jl.git)
另一个常见场景是我们想安装包存储库的某个分支。这可以通过在包名或 URL 的末尾附加#name_of_the_branch轻松实现:
(v1.0) pkg> add OhMyREPL#master
Cloning git-repo `https://github.com/KristofferC/OhMyREPL.jl.git`
Updating git-repo `https://github.com/KristofferC/OhMyREPL.jl.git`
Resolving package versions...
Installed Crayons ─ v0.5.1
Updating `~/.julia/environments/v1.0/Project.toml`
[5fb14364] ~ OhMyREPL v0.3.0 ⇒ v0.3.0 #master (https://github.com/KristofferC/OhMyREPL.jl.git)
Updating `~/.julia/environments/v1.0/Manifest.toml`
[a8cc5b0e] ↓ Crayons v1.0.0 ⇒ v0.5.1
[5fb14364] ~ OhMyREPL v0.3.0 ⇒ v0.3.0 #master (https://github.com/KristofferC/OhMyREPL.jl.git)
或者,对于未注册的包,使用以下命令:
(v1.0) pkg> add https://github.com/JuliaLang/Example.jl.git#master
如果我们想回到使用已发布的分支,我们需要free这个包:
(v1.0) pkg> free OhMyREPL
Resolving package versions...
Updating `~/.julia/environments/v1.0/Project.toml`
[5fb14364] ~ OhMyREPL v0.3.0 #master (https://github.com/KristofferC/OhMyREPL.jl.git) ⇒ v0.3.0
Updating `~/.julia/environments/v1.0/Manifest.toml`
[a8cc5b0e] ↑ Crayons v0.5.1 ⇒ v1.0.0
[5fb14364] ~ OhMyREPL v0.3.0 #master (https://github.com/KristofferC/OhMyREPL.jl.git) ⇒ v0.3.0
Revise
这很简单,但熟能生巧。让我们再添加一个!这次我们将安装Revise,这是另一个必不可少的包,它通过监控和检测您的 Julia 文件中的更改,并在需要时自动重新加载代码,从而实现流畅的开发工作流程。在Revise之前,加载当前 Julia 进程中的更改是出了名的困难,开发者通常被迫重启 REPL——这是一个耗时且低效的过程。Revise可以消除重启、加载包和等待代码编译的开销。
您可以通过阅读其文档来了解更多关于 Revise 的信息,文档位于timholy.github.io/Revise.jl/latest/。
出乎意料的是,我们只需要再次调用add命令,这次传递Revise作为包名:
(v1.0) pkg> add Revise
Resolving package versions...
Installed Revise ─ v0.7.5
Updating `~/.julia/environments/v1.0/Project.toml`
[295af30f] + Revise v0.7.5
Updating `~/.julia/environments/v1.0/Manifest.toml`
[bac558e1] + OrderedCollections v0.1.0
[295af30f] + Revise v0.7.5
[7b1f6079] + FileWatching
add命令也可以一次接受多个包。我们现在一个接一个地添加它们,为了学习目的,但否则,我们本可以执行(v1.0) pkg> add OhMyREPL Revise。
检查包状态
我们可以通过使用恰如其分的status命令来检查我们项目的状态,以确认操作是否成功:
(v1.0) pkg> status
Status `~/.julia/environments/v1.0/Project.toml`
[7876af07] Example v0.5.1+ #master (https://github.com/JuliaLang/Example.jl.git)
[5fb14364] OhMyREPL v0.3.0
[295af30f] Revise v0.7.5
status命令显示所有已安装的包,包括从左到右的包的 ID 简短版本(称为UUID)、包名和版本号。在适当的情况下,它还会指示我们正在跟踪的分支,例如在Example的情况下,我们处于master分支。
Pkg 还支持一系列快捷方式,如果您想节省一些按键。在这种情况下,st 可以用来代替 status。
使用包
一旦添加了包,为了访问其功能,我们必须将其引入作用域。这就是我们告诉 Julia 我们打算使用它的方法,请求编译器使其对我们可用。为此,首先,我们需要退出 pkg 模式。一旦我们处于 julian 提示符,为了使用 OhMyREPL,我们只需要执行:
julia> using OhMyREPL
[ Info: Precompiling OhMyREPL [5fb14364-9ced-5910-84b2-373655c76a03]
这就是全部——OhMyREPL 现在会自动增强当前的 REPL 会话。要看到它的实际效果,这里是一个 常规 REPL 的样子:
这里是相同的代码,通过 OhMyREPL 增强后的样子:
语法高亮和括号匹配使代码更易读,减少了语法错误。看起来很棒,不是吗?
OhMyREPL 还有更多酷炫的功能——您可以通过查看官方文档了解它们:kristofferc.github.io/OhMyREPL.jl/latest/index.html。
再多一步
OhMyREPL 和 Revise 是出色的开发工具,在所有 Julia 会话中自动加载它们非常有用。这正是 startup.jl 文件存在的原因——现在我们有了一个将其用于实际的机会(尽管我们诚挚的欢迎问候已经足够令人印象深刻了!)。
这里有一个小技巧,让我们开始——Julia 提供了一个 edit 函数,它会在配置的编辑器中打开一个文件。让我们用它来打开 startup.jl 文件:
julia> edit("~/.julia/config/startup.jl")
这将使用默认编辑器打开文件。如果您还没有删除我们之前添加的欢迎信息,现在可以自由地这样做(除非您真的很喜欢它们,在这种情况下,您当然可以保留它们)。现在,Revise 需要在我们想要跟踪的任何其他模块之前使用——所以我们将希望它在文件顶部。至于 OhMyREPL,它可以放在下面。您的 startup.jl 文件应该看起来像这样:
using Revise
using OhMyREPL
保存并关闭编辑器。下次您启动 Julia 时,Revise 和 OhMyREPL 将已经加载。
更新包
Julia 提供了一个繁荣的生态系统,并且包的更新速度非常快。定期使用 pkg> update 检查更新是一个好习惯:
(v1.0) pkg> update
当发出此命令时,Julia 首先检索通用仓库的最新版本,然后检查是否有任何包需要更新。
注意,发出 update 命令将更新所有可用的包。正如我们之前讨论的,当提到 依赖地狱 时,这可能不是最好的做法。在接下来的章节中,我们将看到如何与单个项目一起工作,并按单个应用程序管理依赖项。不过,了解您可以通过传递它们的名称来选择您想要更新的包是很重要的:
(v1.0) pkg> update OhMyREPL Revise
Pkg 还提供了一个预览模式,它将显示运行特定命令时会发生什么,而实际上不会进行任何更改:
(v1.0) pkg> preview update OhMyREPL
(v1.0) pkg> preview add HTTP
pkg> update 的快捷键是 pkg> up。
固定包
有时我们可能想要确保某些包不会被更新。这就是我们“固定”它们的时候:
(v1.0) pkg> pin OhMyREPL
Resolving package versions...
Updating `~/.julia/environments/v1.0/Project.toml`
[5fb14364] ~ OhMyREPL v0.3.0 ⇒ v0.3.0
Updating `~/.julia/environments/v1.0/Manifest.toml`
[5fb14364] ~ OhMyREPL v0.3.0 ⇒ v0.3.0
固定包会标记为 ⚲ 符号——现在在检查状态时也会出现:
(v1.0) pkg> st
Status `~/.julia/environments/v1.0/Project.toml`
[5fb14364] OhMyREPL v0.3.0
[295af30f] Revise v0.7.5
如果我们想取消固定一个包,我们可以使用 pkg> free:
(v1.0) pkg> free OhMyREPL
Updating `~/.julia/environments/v1.0/Project.toml`
[5fb14364] ~ OhMyREPL v0.3.0 ⇒ v0.3.0
Updating `~/.julia/environments/v1.0/Manifest.toml`
[5fb14364] ~ OhMyREPL v0.3.0 ⇒ v0.3.0
(v1.0) pkg> st
Status `~/.julia/environments/v1.0/Project.toml`
[5fb14364] OhMyREPL v0.3.0
[295af30f] Revise v0.7.5
移除包
如果你不再打算使用某些包,你可以删除(或使用 pkg> remove 命令移除它们)。例如,假设我们有以下配置:
(v1.0) pkg> st
Status `~/.julia/environments/v1.0/Project.toml`
[7876af07] Example v0.5.1+ #master (https://github.com/JuliaLang/Example.jl.git)
[5fb14364] OhMyREPL v0.3.0
[295af30f] Revise v0.7.5
我们可以使用以下代码移除 Example 包:
(v1.0) pkg> remove Example
Updating `~/.julia/environments/v1.0/Project.toml`
[7876af07] - Example v0.5.1+ #master (https://github.com/JuliaLang/Example.jl.git)
Updating `~/.julia/environments/v1.0/Manifest.toml`
[7876af07] - Example v0.5.1+ #master ([`github.com/JuliaLang/Example.jl.git`](https://github.com/JuliaLang/Example.jl.git))
当然,它现在已经消失了:
(v1.0) pkg> st
Status `~/.julia/environments/v1.0/Project.toml`
[5fb14364] OhMyREPL v0.3.0
[295af30f] Revise v0.7.5
pkg> remove 的快捷键是 pkg> rm。
除了显式删除不需要的包外,Pkg 还有一个内置的自动清理功能。随着包版本的发展和包依赖关系的变化,一些已安装的包可能会变得过时,并且不再在任何现有项目中使用。Pkg 会记录所有使用过的项目,以便它可以遍历日志并确切地看到哪些项目仍然需要哪些包——从而识别出不再必要的包。这些可以使用 pkg> gc 命令一次性删除:
(v1.0) pkg> gc Active manifests at: `/Users/adrian/.julia/environments/v1.0/Manifest.toml` `/Users/adrian/.julia/environments/v0.7/Manifest.toml` Deleted /Users/adrian/.julia/packages/Acorn/exWWb: 40.852 KiB Deleted /Users/adrian/.julia/packages/BufferedStreams/hCA7W: 102.235 KiB Deleted /Users/adrian/.julia/packages/Crayons/e1SsX: 49.133 KiB Deleted /Users/adrian/.julia/packages/Example/ljaU2: 4.625 KiB Deleted /Users/adrian/.julia/packages/Genie/XOia2: 2.031 MiB Deleted /Users/adrian/.julia/packages/HTTPClient/ZQR55: 37.669 KiB Deleted /Users/adrian/.julia/packages/Homebrew/l8kUw: 277.296 MiB Deleted /Users/adrian/.julia/packages/LibCURL/Qs5og: 11.599 MiB Deleted /Users/adrian/.julia/packages/LibExpat/6jLDP: 127.247 KiB Deleted /Users/adrian/.julia/packages/LibPQ/N7lDU: 134.734 KiB Deleted /Users/adrian/.julia/packages/Libz/zMAun: 80.744 KiB Deleted /Users/adrian/.julia/packages/Nettle/LMDZh: 50.371 KiB
Deleted /Users/adrian/.julia/packages/OhMyREPL/limOC: 448.493 KiB
Deleted /Users/adrian/.julia/packages/WinRPM/rDDZz: 24.925 KiB
Deleted 14 package installations : 292.001 MiB
除了专门的 Pkg REPL 模式外,Julia 还提供了一个强大的 API,用于以编程方式管理包。我们不会涉及它,但如果你想了解它,你可以查看官方文档,链接为 docs.julialang.org/en/latest/stdlib/Pkg/#References-1。
发现包
包发现还不是像它本可以那样简单,但有一些很好的选项。我建议从以下精心挑选的 Julia 包列表开始:github.com/svaksha/Julia.jl。它按领域分组了一个大量的包集合,包括人工智能、生物学、化学、数据库、图形、数据科学、物理学、统计学、超级计算等多个主题。
如果这还不够,你总是可以访问 discourse.julialang.org,在那里 Julia 社区讨论与语言相关的多种主题。你可以搜索和浏览现有的线程,特别是位于 discourse.julialang.org/c/community/packages 的包公告部分。
当然,你总是可以向社区寻求帮助——Julians 非常友好和欢迎,社区投入了大量精力进行管理,以保持讨论文明和建设性。创建新主题和回复需要免费 Discourse 账户。
最后,juliaobserver.com/packages 是一个第三方网站,提供了一种更精致的方式来查找包——它还执行 GitHub 搜索,因此也包括未注册的包。
注册与非注册
尽管我已在之前的段落中提到了这个话题,但我仍想以一个警告来结束对 Pkg 的讨论。一个包是否注册并不一定意味着它在功能或安全性方面经过了审查。它仅仅意味着该包已被创建者提交,并且它满足了一些技术要求,以便被添加到通用注册表中。包源代码可在 GitHub 上找到,就像任何开源软件一样,请确保你了解它做什么,应该如何使用,以及你接受许可条款。
这就结束了我们对包管理的初步讨论。但鉴于这是最常见的任务之一,我们将在未来的章节中反复回到这个话题,我们还将看到一些更高级使用的场景。
摘要
Julia 是一种新的编程语言,它利用了编译技术方面的最新创新,以提供动态编程语言的函数性、易用性和直观语法,同时以 C 的速度运行。其目标之一是消除所谓的两种语言问题——当用户用高级语言(如 R 和 Python)编写代码时,但性能关键部分必须用 C 或 C++ 重新编写。Julia 感觉像是一种动态语言,并提供了与这些语言相关的所有生产力特性。但与此同时,它消除了性能权衡,证明对于原型设计和探索性编程来说足够高效,对于性能关键的应用来说也足够高效。
它的内置包管理器提供了访问 2000 多个第三方库的权限,这些库无缝地扩展了语言,并提供了强大的新功能——我们已经学会了如何利用这些功能。而且如果还不够,Julia 还具有调用其他语言(如 C、Fortran、Python 或 Java)编写的函数的能力,仅举几个例子。
Julia 是免费且开源的(MIT 许可),可以在所有主要的操作系统上部署,包括 Windows、主要的 Linux 发行版和 macOS。它还提供了一些非常好的 IDE 和编辑器选项。
现在我们已经成功设置了我们的开发环境,是时候深入探讨 Julia 的语法了。在下一章中,我们将查看语言的一些基本构建块——定义变量和常量、操作和使用 Strings 和数值类型,以及与 Arrays 一起工作。作为 Julia 生产力的一种证明,这就是我们(连同我们将添加的一些额外包)在 Iris 花卉数据集上进行强大的探索性数据分析所需的一切(together with some extra packages that we'll add)。下一章见!
第二章:创建我们的第一个 Julia 应用程序
现在您已经安装了有效的 Julia 环境,并且您选择的 IDE 已经准备好运行,是时候将它们用于一些有用的任务了。在本章中,您将学习如何将 Julia 应用于数据分析——这是一个语言的核心领域,因此请期待给您留下深刻印象!
我们将学习如何使用 Julia 进行探索性数据分析。在这个过程中,我们将查看 RDatasets,这是一个提供超过 700 个学习数据集访问权限的包。我们将加载其中一个,即 Iris 花卉数据集,并使用标准数据分析函数对其进行操作。然后,我们将通过采用常见的可视化技术更仔细地查看数据。最后,我们将了解如何持久化和(重新)加载数据。
但是,为了做到这一点,我们首先需要查看语言的一些最重要的构建块。
在本章中,我们将涵盖以下主题:
-
声明变量(和常量)
-
处理字符
Strings和正则表达式 -
数字和数值类型
-
我们的第一种 Julia 数据结构——
Tuple、Range和Array -
*使用 Iris 花卉数据集进行探索性数据分析——RDatasets和核心Statistics -
使用
Gadfly快速进行数据可视化 -
*使用CSV和Feather保存和加载数据表 -
与 MongoDB 数据库交互
技术要求
Julia 的包生态系统正在不断发展,并且每天都有新的包版本发布。大多数时候这是一个好消息,因为新版本带来了新功能和错误修复。然而,由于许多包仍在测试版(版本 0.x)中,任何新版本都可能引入破坏性更改。因此,书中展示的代码可能无法正常工作。为了确保您的代码将产生与书中描述相同的结果,建议使用相同的包版本。以下是本章中使用的外部包及其特定版本:
CSV@v0.4.3
DataFrames@v0.15.2
Feather@v0.5.1
Gadfly@v1.0.1
IJulia@v1.14.1
JSON@v0.20.0
RDatasets@v0.6.1
为了安装特定版本的包,您需要运行:
pkg> add PackageName@vX.Y.Z
例如:
pkg> add IJulia@v1.14.1
或者,您也可以通过下载章节中提供的 Project.toml 文件,并使用 pkg> 实例化以下命令来安装所有使用的包:
julia> download("https://raw.githubusercontent.com/PacktPublishing/Julia-Programming-Projects/master/Chapter02/Project.toml", "Project.toml")
pkg> activate .
pkg> instantiate
定义变量
我们在上一章中看到,如何使用 REPL 来执行计算并将结果显示给我们。Julia 甚至通过设置 ans 变量来提供帮助,该变量自动保存最后一个计算值。
但是,如果我们想编写除了最简单的程序之外的内容,我们需要学习如何自己定义变量。在 Julia 中,变量只是一个与值相关联的名称。对变量命名有非常少的限制,并且名称本身没有语义意义(与 Ruby 不同,Ruby 中所有大写的名称被视为常量,语言会根据名称的不同对待变量)。
让我们看看一些例子:
julia> book = "Julia v1.0 By Example"
julia> pi = 3.14
julia> ANSWER = 42
julia> my_first_name = "Adrian"
你可以通过加载本章支持文件中提供的配套 Jupyter/IJulia 笔记本来跟随本章中的示例。
变量的名称是区分大小写的,这意味着ANSWER和answer(以及Answer和aNsWeR)是完全不同的:
julia> answer
ERROR: UndefVarError: answer not defined
也接受 Unicode 名称(UTF-8 编码)作为变量名称:
julia> δ = 130
记住,你可以通过输入反斜杠(\)然后输入符号的名称,然后按Tab键来输入许多 Unicode 数学符号。例如,\pi[Tab]将输出π。
如果你的终端支持,表情符号也可以使用:
julia>  = "apollo 11"
变量的唯一明确禁止的名称是内置 Julia 语句的名称(do、end、try、catch、if和else,以及一些其他名称):
julia> do = 3
ERROR: syntax: invalid "do" syntax
julia> end = "Paris"
ERROR: syntax: unexpected end
尝试访问未定义的变量将导致错误:
julia> MysteryVar
ERROR: UndefVarError: MysteryVar not defined
诚然,这种语言没有强加很多限制,但一套代码风格约定总是有用的——对于一个开源语言来说更是如此。Julia 社区已经提炼了一套编写代码的最佳实践。在变量命名方面,名称应该是小写并且只包含一个单词;单词分隔可以使用下划线(_),但只有当没有它们名称难以阅读时才使用。例如,myvar与total_length_horizontal。
由于读取名称的难度是一个主观问题,我对这种命名风格有点犹豫。我通常更喜欢在单词边界处分离的清晰度。但无论如何,遵循建议总是更好的,因为 Julia API 中的函数名称遵循它。通过遵循相同的约定,你的代码将保持一致性。
常量
常量是一旦声明就不能更改的变量。它们通过在前面加上const关键字来声明:
julia> const firstmonth = "January"
在 Julia 中非常重要的一点是,常量不关心它们的值,而是关心它们的类型。现在讨论 Julia 中的类型还为时过早,所以现在只需说,类型代表我们正在处理的价值类型。例如,"abc"(在双引号内)是String类型,'a'(在单引号内)是Char类型,1000是Int类型(因为它是一个整数)。因此,在 Julia 中,与大多数其他语言不同,只要类型保持不变,我们就可以更改分配给常量的值。例如,我们最初可以决定鸡蛋和牛奶是可接受的餐食选择,并改为vegetarian:
julia> const mealoption = "vegetarian"
如果我们决定改为vegan,我们可以在以后改变主意。Julia 会通过仅发出警告来允许这样做:
julia> mealoption = "vegan"
WARNING: redefining constant mealoption
"vegan"
然而,尝试将mealoption = 2赋值将会导致错误:
julia> mealoption = 2
ERROR: invalid redefinition of constant mealoption
这是有意义的,对吧?谁听说过那种饮食?
然而,细微差别可能比这更微妙,尤其是在处理数字时:
julia> const amount = 10.25
10.25
julia> amount = 10
ERROR: invalid redefinition of constant amount
Julia 不允许这样做,因为从内部来看,10 和 10.00 虽然具有相同的算术值,但它们是不同类型的值(10 是一个整数,而 10.00 是一个 float)。我们将在稍后更详细地了解数值类型,这样一切都会变得清晰:
julia> amount = 10.00
WARNING: redefining constant amount
10.0
因此,我们需要将新值作为 10.00——一个 float 传递,以遵守相同的类型要求。
为什么常量很重要?
这主要关乎性能。常量可以作为全局值特别有用。因为全局变量是长期存在的,并且可以在代码的任何位置和任何时候进行修改,编译器在优化它们时会有困难。如果我们告诉编译器该值是常量,因此该值的类型不会改变,性能问题就可以得到优化。
当然,仅仅因为常量可以缓解由全局变量引起的某些关键性能问题,并不意味着我们被鼓励使用它们。在 Julia 中,像在其他语言中一样,应尽可能避免使用全局值。除了性能问题之外,它们还可能创建难以捕捉和理解的微妙错误。此外,请记住,由于 Julia 允许更改常量的值,意外的修改成为可能。
注释
常见的编程智慧如下:
"代码被阅读的次数远多于被编写的次数,因此要相应地计划。"
代码注释是一种强大的工具,可以使程序在以后更容易理解。在 Julia 中,注释用 # 符号标记。单行注释由 # 表示,并且直到行尾的所有内容都会被编译器忽略。多行注释被 #= ... =# 包围。在开闭注释标签之间的所有内容也会被编译器忽略。以下是一个示例:
julia> #=
Our company charges a fixed
$10 fee per transaction.
=#
const flatfee = 10 # flat fee, per transaction
在前面的代码片段中,我们可以看到多行和单行注释的实际应用。单行注释也可以放在行的开头。
字符串
字符串表示字符序列。我们可以通过在双引号之间包围相应的字符序列来创建字符串,如下所示:
julia> "Measuring programming progress by lines of code is like measuring aircraft building progress by weight."
如果字符串中也包含引号,我们可以通过在它们前面加上反斜杠 \ 来转义这些引号:
julia> "Beta is Latin for \"still doesn't work\"."
三引号字符串
然而,转义可能会变得混乱,所以有一个更好的处理方法——使用三引号 """..."""。
julia> """Beta is Latin for "still doesn't work"."""
在三引号内,不再需要转义单引号。但是,请确保单引号和三引号是分开的——否则编译器会感到困惑:
julia> """Beta is Latin for "still doesn't work""""
syntax: cannot juxtapose string literal
当与多行文本一起使用时,三引号带来了一些额外的特殊功能。首先,如果开头的 """ 后面跟着一个换行符,这个换行符将被从字符串中删除。此外,空白被保留,但字符串将被缩进到最不缩进的行的级别:
julia> """
Hello
Look
Here"""
julia> print(ans)
Hello
Look
Here
Here was removed).
在 Jupyter/IJulia 中看起来是这样的:
长箭头代表一个 Tab(在输出中由 \t 表示),而短箭头是一个空格。请注意,每一行都以一个空格作为开头——但它被移除了。最不缩进的行,即最后一行,被向左移动,移除了所有空白,并以 Here 开头,而其他行上的剩余空白被保留(现在以 Tab 开头)。
字符串拼接
两个或多个字符串可以通过使用星号 * 运算符拼接在一起,形成一个单独的字符串:
julia> "Hello " * "world!" "Hello world!"
或者,我们可以调用 string 函数,传入我们想要拼接的所有单词:
julia> string("Itsy", " ", "Bitsy", " ", "Spider")
"Itsy Bitsy Spider"
拼接也可以与变量很好地配合使用:
julia> username = "Adrian"
julia> greeting = "Good morning"
julia> greeting * ", " * username
"Good morning, Adrian"
然而,同样,我们在处理类型时需要小心(类型是 Julia 的核心,所以这将会是一个经常出现的话题)。拼接仅适用于字符串:
julia> username = 9543794
julia> greeting = "Good morning"
julia> greeting * ", " * username
MethodError: no method matching *(::String, ::Int64)
即使不是所有参数都是字符串,通过调用 string 函数进行拼接也是有效的:
julia> string(greeting, ", ", username)
"Good morning, 9543794"
因此,string 有一个额外的优势,它自动将参数转换为字符串。以下示例也适用:
julia> string(2, " and ", 3)
"2 and 3"
但这不行:
julia> 2 * " and " * 3
ERROR: MethodError: no method matching *(::Int64, ::String)
此外,还有一个 String 方法(首字母大写)。请记住,在 Julia 中名称是区分大小写的,所以 string 和 String 是两件不同的事情。对于大多数用途,我们需要小写的函数 string。如果您想了解 String,可以使用 Julia 的帮助系统来访问其文档。
字符串插值
当创建更长的、更复杂的字符串时,拼接可能会很嘈杂且容易出错。对于这种情况,我们最好使用 $ 符号将变量插值到字符串中:
julia> username = "Adrian"
julia> greeting = "Good morning"
julia> "$greeting, $username"
"Good morning, Adrian"
更复杂的表达式可以通过将其包裹在 $(...) 中进行插值:
julia> "$(uppercase(greeting)), $(reverse(username))"
"GOOD MORNING, nairdA"
这里我们调用了 uppercase 函数,它将字符串中的所有字母转换为大写字母——以及 reverse 函数,它反转单词中字母的顺序。它们的输出随后被插值到字符串中。在 $(...) 边界内,我们可以使用任何我们想要的 Julia 代码。
就像 string 函数一样,插值会负责将值转换为字符串:
julia> "The sum of 1 and 2 is $(1 + 2)"
"The sum of 1 and 2 is 3"
字符串操作
字符串可以被当作字符列表来处理,因此我们可以对它们进行索引——也就是说,访问单词中某个位置的字符:
julia> str = "Nice to see you"
julia> str[1]
'N': ASCII/Unicode U+004e (category Lu: Letter, uppercase)
字符串 Nice to see you 的第一个字符是 N。
Julia 中的索引是 1-based,这意味着列表的第一个元素位于索引 1。如果你之前编程过,这可能会让你感到惊讶,因为大多数编程语言使用 0-based 索引。然而,我向你保证,1-based 索引会让编码体验非常愉快且直接。
Julia 支持具有任意索引的数组,例如,可以从 0 开始编号。然而,任意索引是一个更高级的功能,我们在这里不会涉及。如果您对此好奇,可以查看官方文档docs.julialang.org/en/v1/devdocs/offset-arrays/。
我们也可以通过使用 range 进行索引来提取字符串的一部分(子字符串),提供起始和结束位置:
julia> str[9:11]
"see"
重要的是要注意,通过单个值进行索引返回一个 Char,而通过 range 进行索引返回一个 String(记住,对于 Julia 来说,这是两件完全不同的事情):
julia> str[1:1]
"N"
N 是一个仅由一个字母组成的 String,正如其双引号所示:
julia> str[1]
'N': ASCII/Unicode U+004e (category Lu: Letter, uppercase)
N 是一个 Char,正如单引号所示:
julia> str[1:1] == str[1]
false
它们不相等。
Unicode 和 UTF-8
在 Julia 中,字符串字面量使用 UTF-8 编码。UTF-8 是一种可变宽度编码,这意味着并非所有字符都使用相同数量的字节来表示。例如,ASCII 字符使用单个字节编码——但其他字符可以使用多达四个字节。这意味着并非每个 UTF-8 字符串的字节索引都是对应字符的有效索引。如果您在无效的字节索引处索引字符串,将会抛出错误。以下是我的意思:
julia> str = "Søren Kierkegaard was a Danish Philosopher"
julia> str[1]
'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
我们可以正确地检索索引 1 处的字符:
julia> str[2]
'ø': Unicode U+00f8 (category Ll: Letter, lowercase)
在索引 2 处,我们成功获取了 ø 字符:
julia> str[3]
StringIndexError("Søren Kierkegaard was a Danish Philosopher", 3)
然而,ø 有两个字节,所以索引 3 也被 ø 使用,我们无法访问这个位置的字符串:
julia> str[4]
'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
第三字母 r 在位置 4 被找到。
因此 ø 是一个占用位置 2 和 3 的双字节字符——所以索引 3 是无效的,匹配 ø 的第二个字节。下一个有效索引可以使用 nextind(str, 2) 来计算——但推荐的方式是使用字符迭代(我们将在本章稍后讨论 for 循环):
julia> for s in str
println(s)
end
S
ø
r
e
n
K
... output truncated...
由于可变长度编码,字符串中的字符数不一定与最后一个索引相同(如您所见,第三个字母 r 在索引 4 处):
julia> length(str) 42
julia> str[42] 'e': ASCII/Unicode U+0065 (category Ll: Letter, lowercase)
对于此类情况,Julia 提供了 end 关键字,它可以作为最后一个索引的快捷方式。您可以使用 end 进行算术和其他操作,就像一个普通值一样:
julia> str[end]
'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
julia> str[end-10:end]
"Philosopher"
end 值可以使用 endof(str) 函数进行程序计算。尝试在字符串的界限之外进行索引将导致 BoundsError:
julia> str[end+1]
ERROR: BoundsError: attempt to access "Søren Kierkegaard was a Danish Philosopher"
at index [44]
正则表达式
正则表达式用于在字符串内部进行强大的子字符串模式匹配。它们可以根据模式在字符串中搜索子字符串,然后提取或替换匹配项。Julia 提供了对 Perl 兼容正则表达式的支持。
输入正则表达式的最常见方式是使用所谓的非标准字符串字面量。这些看起来像常规的双引号字符串,但带有特殊的前缀。在正则表达式的例子中,这个前缀是"r"。前缀提供了一种与普通字符串字面量不同的行为。
例如,为了定义一个匹配所有字母的正则字符串,我们可以使用r"[a-zA-Z]*"。
Julia 提供了相当多的非标准字符串字面量——如果我们想的话,我们甚至可以定义自己的。最广泛使用的是正则表达式(r"...")、字节数组字面量(b"...")、版本号字面量(v"...")和包管理命令(pkg"...")。
下面是如何在 Julia 中构建正则表达式——它匹配介于 0 和 9 之间的数字:
julia> reg = r"[0-9]+"
r"[0-9]+"
julia> match(reg, "It was 1970")
RegexMatch("1970")
我们的正则表达式匹配子字符串1970。
我们可以通过使用typeof函数检查其type来确认非标准字符串字面量reg实际上是一个Regex而不是一个普通的String:
julia> typeof(reg)
Regex
这揭示了还有一个Regex构造函数可用:
julia> Regex("[0-9]+")
r"[0-9]+"
这两个构造函数类似:
julia> Regex("[0-9]+") == reg
true
当我们需要使用更复杂的字符串创建正则表达式,可能包括插值或连接时,使用构造函数可能很有用。但通常,r"..."格式更常用。
通过使用一些组合的标志i、m、s和x,可以影响正则表达式的行为。这些修饰符必须放在关闭双引号标记之后:
julia> match(r"it was", "It was 1970") # case-sensitive no match
julia> match(r"it was"i, "It was 1970") # case-insensitive match
RegexMatch("It was")
如您所预期,i执行不区分大小写的模式匹配。如果没有i修饰符,match返回nothing——一个特殊值,在交互式提示符中不打印任何内容,以指示正则表达式不匹配给定的字符串。
这些是可用的修饰符:
-
i—不区分大小写的模式匹配。 -
m—将字符串视为多行。 -
s—将字符串视为单行。 -
x—告诉正则表达式解析器忽略大多数既不是转义也不是在字符类内的空白。您可以使用此功能将正则表达式分成(稍微)更易读的部分。#字符也被视为元字符,引入注释,就像在普通代码中一样。
如果我们只想检查正则表达式或子字符串是否包含在字符串中,而不想提取或替换匹配项,则occursin函数更为简洁:
julia> occursin(r"hello", "It was 1970")
false
julia> occursin(r"19", "It was 1970")
true
当正则表达式匹配时,它返回一个RegexMatch对象。这些对象封装了表达式的匹配方式,包括匹配的子字符串和任何捕获的子字符串:
julia> alice_in_wonderland = "Why, sometimes I've believed as many as six impossible things before breakfast."
julia> m = match(r"(\w+)+", alice_in_wonderland)
RegexMatch("Why", 1="Why")
Why.
我们还可以指定开始搜索的索引:
m = match(r"(\w+)+", alice_in_wonderland, 6)
RegexMatch("sometimes", 1="sometimes")
让我们尝试一个稍微复杂一些的例子:
julia> m = match(r"((\w+)(\s+|\W+))", alice_in_wonderland)
RegexMatch("Why, ", 1="Why, ", 2="Why", 3=", ")
结果的RegexMatch对象m公开以下属性(或在 Julia 的说法中,字段):
-
m.match(Why)包含匹配的整个子字符串。 -
m.captures(一个包含Why、Why和,的字符串数组)表示捕获的子字符串。 -
m.offset,整个匹配开始的偏移量(在我们的例子中是1)。 -
m.offsets,捕获子字符串的偏移量作为整数数组(在我们的例子中是[1, 1, 4])。
Julia 不提供 g 修饰符,用于 贪婪 或 全局 匹配。如果你需要所有匹配项,你可以使用 eachmatch() 函数遍历它们,如下所示:
julia> for m in eachmatch(r"((\w+)(\s+|\W+))", alice_in_wonderland)
println(m)
end
或者,我们可以使用 collect() 函数将所有匹配项放入一个列表中:
julia> collect(eachmatch(r"((\w+)(\s+|\W+))", alice_in_wonderland))
13-element Array{RegexMatch,1}:
RegexMatch("Why, ", 1="Why, ", 2="Why", 3=", ")
RegexMatch("sometimes ", 1="sometimes ", 2="sometimes", 3=" ")
RegexMatch("I'", 1="I'", 2="I", 3="'")
RegexMatch("ve ", 1="ve ", 2="ve", 3=" ")
RegexMatch("believed ", 1="believed ", 2="believed", 3=" ")
RegexMatch("as ", 1="as ", 2="as", 3=" ")
RegexMatch("many ", 1="many ", 2="many", 3=" ")
RegexMatch("as ", 1="as ", 2="as", 3=" ")
RegexMatch("six ", 1="six ", 2="six", 3=" ")
RegexMatch("impossible ", 1="impossible ", 2="impossible", 3=" ")
RegexMatch("things ", 1="things ", 2="things", 3=" ")
RegexMatch("before ", 1="before ", 2="before", 3=" ")
RegexMatch("breakfast.", 1="breakfast.", 2="breakfast", 3=".")
想要了解更多关于正则表达式的信息,请查看官方文档:docs.julialang.org/en/stable/manual/strings/#Regular-Expressions-1。
原始字符串字面量
如果需要定义一个不执行插值或转义的字符串,例如表示可能包含 $ 和 \ 的其他语言的代码,这些字符可能会干扰 Julia 解析器,你可以使用原始字符串。它们使用 raw"..." 构造,并创建包含所包含字符的普通 String 对象,这些字符与输入的完全一致,没有插值或转义:
julia> "This $will error out"
ERROR: UndefVarError: will not defined
在字符串中放置一个 $ 将导致 Julia 执行插值并查找名为 will 的变量:
julia> raw"This $will work"
"This \$will work"
但是,通过使用原始字符串,$ 符号将被忽略(或者更确切地说,自动转义,如输出所示)。
数字
Julia 提供了广泛的原始数字类型,以及完整的算术和位运算符以及标准数学函数。我们有丰富的数字类型层次结构可供使用,其中最通用的是 Number——它定义了两个子类型,Complex 和 Real。相反,Real 有四个子类型——AbstractFloat、Integer、Irrational 和 Rational。最后,Integer 分支为四个其他子类型——BigInt、Bool、Signed 和 Unsigned。
让我们来看看数字最重要的几个类别。
整数
文本整数简单地表示如下:
julia> 42
默认的整数类型,称为 Int,取决于代码执行的系统架构。它可以是 Int32 或 Int64。在我的 64 位系统上,我得到它如下:
julia> typeof(42)
Int64
Int 类型将反映这一点,因为它只是 Int32 或 Int64 的别名:
julia> @show Int
Int = Int64
溢出行为
最小值和最大值由 typemin() 和 typemax() 函数给出:
julia> typemin(Int), typemax(Int)
(-9223372036854775808, 9223372036854775807)
尝试使用超出最小值和最大值定义的边界之外的值不会抛出错误(甚至警告),而是导致环绕行为(意味着它会在另一端跳过):
julia> typemin(Int) - 1
9223372036854775807
julia> typemin(Int) - 1 == typemax(Int)
true
从最小值减去 1 将返回最大值:
julia> typemax(Int) + 1 == typemin(Int)
true
反之亦然——将 1 添加到最大值将返回最小值。
对于处理这些范围之外的值,我们将使用 BigInt 类型:
julia> BigInt(typemax(Int)) + 1
9223372036854775808
这里没有环绕;结果是我们所期望的。
浮点数
浮点数由由点分隔的数值表示:
julia> 3.14
3.14
julia> -1.0
-1.0
julia> 0.25
0.25
julia> .5
0.5
默认情况下,它们是 Float64 值,但可以转换为 Float32:
julia> typeof(1.)
Float64
julia> f32 = Float32(1.)
1.0f0
julia> typeof(f32)
Float32
为了提高可读性,下划线(_)分隔符可以与整数和浮点数一起使用:
julia> 1_000_000, 0.000_000_005
(1000000, 5.0e-9)
有理数
Julia 还提供了有理数类型。这允许我们处理精确的比率,而不是必须处理浮点数固有的精度损失。有理数以它们的分子和分母值表示,用两个正斜杠 // 分隔:
julia> 3//2
3//2
如果没有数据丢失,有理数可以转换为其他类型:
julia> 1//2 + 2//4
1//1
julia> Int(1//1)
1
julia> float(1//3)
0.3333333333333333
julia> Int(1//3)
ERROR: InexactError: Int64(Int64, 1//3)
julia> float(1//3) == 1/3
true
Julia 还包括对复数的支持。我们不会详细讨论它们,但你可以在官方文档中阅读有关该主题的内容,链接为docs.julialang.org/en/v1/manual/complex-and-rational-numbers/#Complex-Numbers-1。
数值运算符
Julia 支持其数值类型的完整范围的算术运算符:
-
+—(一元和二元加) -
-—(一元和二元减) -
*—(乘) -
/—(除) -
\—(倒数除) -
^—(幂) -
%—(余数)
语言还支持每个这些的便捷更新运算符(+=,-=,*=,/=,\=,÷=,%=,和 ^=)。这里它们是野生的:
julia> a = 2
2
julia> a *= 3 # equivalent of a = a * 3
6
julia> a ^= 2 # equivalent of a = a ^ 2
36
julia> a += 4 # equivalent of a = a + 4
40
可以使用以下一组运算符执行数值比较:
-
==—(相等) -
!=或≠—(不等) -
<—(小于) -
<=或≤—(小于等于) -
>—(大于) -
>=或≥—(大于等于)
在 Julia 中,比较也可以链式使用:
julia> 10 > 5 < 6 == 6 >= 3 != 2
true
向量化点操作符
Julia 为每个二元运算符定义了相应的 点 操作。这些操作旨在逐元素与值集合(称为 向量化)一起工作。也就是说,被 点 的运算符应用于集合中的每个元素。
在以下示例中,我们将对 first_five_fib 集合中的每个元素进行平方:
julia> first_five_fib = [1, 1, 2, 3, 5]
5-element Array{Int64,1}:
1
1
2
3
5
julia> first_five_fib .^ 2
5-element Array{Int64,1}:
1
1
4
9
25
在上一个示例中,first_five_fib 没有被修改,返回的结果集合,但还有 点 更新运算符也可用,它们在原地更新值。它们与之前讨论的更新运算符(增加了 点)相匹配。例如,要就地更新 first_five_fib,我们会使用以下代码:
julia> first_five_fib .^= 2
向量化代码是语言的一个重要部分,因为它具有可读性和简洁性,同时也因为它提供了重要的性能优化。更多详情,请查看docs.julialang.org/en/stable/manual/functions/#man-vectorized-1。
还有更多
这一节只是触及了表面。要深入了解 Julia 的数值类型,请阅读官方文档docs.julialang.org/en/stable/manual/mathematical-operations/。
元组
元组是 Julia 中最简单的数据类型和结构之一。它们可以有任意长度,可以包含任何类型的值——但它们是不可变的。一旦创建,元组就不能修改。可以使用字面量元组表示法创建元组,通过在大括号(...)内包裹逗号分隔的值:
(1, 2, 3)
julia> ("a", 4, 12.5)
("a", 4, 12.5)
为了定义一个只有一个元素的元组,我们一定不要忘记尾随的逗号:
julia> (1,)
(1,)
但省略括号是可以的:
julia> 'e', 2
('e', 2)
julia> 1,
(1,)
我们可以索引元组来访问它们的元素:
julia> lang = ("Julia", v"1.0")
("Julia", v"1.0.0")
julia> lang[2]
v"1.0.0"
向量化点操作也适用于元组:
julia> (3,4) .+ (1,1) (4, 5)
命名元组
命名元组表示一个带有标签项的元组。我们可以通过标签或索引访问各个组件:
julia> skills = (language = "Julia", version = v"1.0")
(language = "Julia", version = v"1.0.0")
julia> skills.language
"Julia"
julia> skills[1]
"Julia"
命名元组可以非常强大,因为它们类似于完整对象,但限制在于它们是不可变的。
范围
我们在之前学习如何索引字符串时已经看到了范围。它们可以像以下这样简单:
julia> r = 1:20
1:20
与之前的集合一样,我们可以对范围进行索引:
julia> abc = 'a':'z'
'a':1:'z'
julia> abc[10]
'j': ASCII/Unicode U+006a (category Ll: Letter, lowercase)
julia> abc[end]
'z': ASCII/Unicode U+007a (category Ll: Letter, lowercase)
可以使用展开运算符"..."将范围展开为其对应的值。例如,我们可以将其展开成元组:
julia> (1:20...,)
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)
我们还可以将其展开成列表:
julia> [1:20...]
20-element Array{Int64,1}
对于元组也是如此,它们还可以被展开成列表,以及其他东西:[(1,2,3)...]。
我们可以看到,范围默认以增量为一的步长。我们可以通过传递一个可选的步长参数来改变它。以下是一个从0到20且步长为五的范围的例子:
julia> (0:5:20...,)
(0, 5, 10, 15, 20)
现在我们的值从5到5。
这也打开了以负步长递减顺序前进的可能性:
julia> (20:-5:-20...,)
(20, 15, 10, 5, 0, -5, -10, -15, -20)
范围不仅限于整数——您之前已经看到了chars的范围;这些是floats的范围:
julia> (0.5:10)
0.5:1.0:9.5
julia> (0.5:10...,)
(0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5)
我们还可以使用collect函数将范围扩展到列表(数组)中:
julia> collect(0.5:0.5:10)
20-element Array{Float64,1}
数组
数组是一种数据结构(以及相应的类型),它表示一个有序的元素集合。更具体地说,在 Julia 中,数组是一个存储在多维网格中的对象的集合。
数组可以有任意数量的维度,由它们的类型和维度数定义——Array{Type, Dimensions}。
一维数组,也称为向量,可以使用数组字面量表示法轻松定义,即方括号[...]:
julia> [1, 2, 3]
3-element Array{Int64,1}:
1
2
3
您还可以约束元素的类型:
julia> Float32[1, 2, 3, 4]
4-element Array{Float32,1}:
1.0
2.0
3.0
4.0
二维数组(也称为矩阵)可以使用相同的数组字面量表示法初始化,但这次不需要逗号:
julia> [1 2 3 4]
1×4 Array{Int64,2}:
1 2 3 4
我们可以使用分号添加更多行:
julia> [1 2 3; 4 5 6; 7 8 9]
3×3 Array{Int64,2}:
1 2 3
4 5 6
7 8 9
Julia 附带了许多函数,可以构造和初始化具有不同值的数组,例如zeroes、ones、trues、falses、similar、rand、fill等。以下是一些实际应用的例子:
julia> zeros(Int, 2)
2-element Array{Int64,1}:
0
0
julia> zeros(Float64, 3)
3-element Array{Float64,1}:
0.0
0.0
0.0
julia> ones(2)
2-element Array{Float64,1}:
1.0
1.0
julia> ones(Int, 2)
2-element Array{Int64,1}:
1
1
julia> ones(Int, 3, 4)
3×4 Array{Int64,2}:
1 1 1 1
1 1 1 1
1 1 1 1
julia> trues(2)
2-element BitArray{1}:
true
true
julia> rand(Int, 4, 2)
4×2 Array{Int64,2}:
9141724849782088627 6682031028895615978
-3827856130755187476 -1731760524632072533
-3369983903467340663 -7550830795386270701
-3159829068325670125 1153092130078644307
julia> rand(Char, 3, 2)
3×2 Array{Char,2}:
'\U63e7a' '\Ub8723'
'\Uda56f' 
'\U7b7fd' '\U5f749'
julia> fill(42, 2, 3)
2×3 Array{Int64,2}:
42 42 42
42 42 42
Array元素可以通过它们的索引访问,为每个维度传递一个值:
julia> arr1d = rand(5) 5-element Array{Float64,1}: 0.845359 0.0758361 0.379544 0.382333 0.240184
julia> arr1d[5]
0.240184
julia> arr2d = rand(5,2)
5×2 Array{Float64,2}:
0.838952 0.312295
0.800917 0.253152
0.480604 0.49218
0.716717 0.889667
0.703998 0.773618
julia> arr2d[4, 1]
0.7167165812985592
我们还可以传递一个冒号(:)来选择整个维度内的所有索引——或者一个范围来定义子选择:
julia> arr2d = rand(5,5)
5×5 Array{Float64,2}:
0.618041 0.887638 0.633995 0.868588 0.19461
0.400213 0.699705 0.719709 0.328922 0.326825
0.322572 0.807488 0.866489 0.960801 0.476889
0.716221 0.504356 0.206264 0.600758 0.843445
0.705491 0.0334613 0.240025 0.235351 0.740302
这就是我们选择行1到3和列3到5的方式:
julia> arr2d[1:3, 3:5]
3×3 Array{Float64,2}:
0.633995 0.868588 0.19461
0.719709 0.328922 0.326825
0.866489 0.960801 0.476889
单独的冒号:代表所有——所以这里我们选择了行和列3到5:
julia> arr2d[:, 3:5]
5×3 Array{Float64,2}:
0.633995 0.868588 0.19461
0.719709 0.328922 0.326825
0.866489 0.960801 0.476889
0.206264 0.600758 0.843445
0.240025 0.235351 0.740302
另一个选项是布尔数组Array来选择其true索引处的元素。在这里,我们选择了对应于true值的行和列3到5:
julia> arr2d[[true, false, true, true, false], 3:5]
3×3 Array{Float64,2}:
0.633995 0.868588 0.19461
0.866489 0.960801 0.476889
0.206264 0.600758 0.843445
与数组索引类似,我们也可以将值赋给选定的项:
julia> arr2d[1, 1] = 0.0
julia> arr2d[[true, false, true, true, false], 3:5] = ones(3, 3)
julia> arr2d
5×5 Array{Float64,2}:
0.0 0.641646 1.0 1.0 1.0
0.750895 0.842909 0.818378 0.484694 0.661247
0.938833 0.193142 1.0 1.0 1.0
0.195541 0.338319 1.0 1.0 1.0
0.546298 0.920886 0.720724 0.0529883 0.238986
迭代
遍历数组的最简单方法是使用for构造:
for element in yourarray
# do something with element
end
这里有一个例子:
julia> for person in ["Alison", "James", "Cohen"]
println("Hello $person")
end
Hello Alison
Hello James
Hello Cohen
如果你还需要在迭代时获取索引,Julia 提供了eachindex(yourarray)迭代器:
julia> people = ["Alison", "James", "Cohen"]
3-element Array{String,1}:
"Alison"
"James"
"Cohen"
julia> for i in eachindex(people)
println("$i. $(people[i])")
end
1\. Alison
2\. James
3\. Cohen
修改数组
我们可以使用push!函数向集合的末尾添加更多元素:
julia> arr = [1, 2, 3]
3-element Array{Int64,1}:
1
2
3
julia> push!(arr, 4)
4-element Array{Int64,1}:
1
2
3
4
julia> push!(arr, 5, 6, 7)
7-element Array{Int64,1}:
1
2
3
4
5
6
7
注意push!函数结尾的感叹号!。这在 Julia 中是一个完全合法的函数名。这是一个约定,用来警告该函数是修改性的——也就是说,它将修改传递给它的数据,而不是返回一个新值。
我们可以使用pop!从数组的末尾删除元素:
julia> pop!(arr)
7
julia> arr
6-element Array{Int64,1}:
1
2
3
4
5
6
调用pop!函数已经移除了arr的最后一个元素并返回了它。
如果我们要删除除了最后一个元素之外的其他元素,我们可以使用deleteat!函数,表示要删除的索引:
julia> deleteat!(arr, 3)
5-element Array{Int64,1}:
1
2
4
5
6
最后,关于修改数组有一个警告。在 Julia 中,数组是通过引用传递给函数的。这意味着原始数组被发送作为各种修改函数的参数,而不是它的副本。小心不要意外地做出不想要的修改。同样,当将数组赋给变量时,会创建一个新的引用,但数据不会被复制。所以例如:
julia> arr = [1,2,3]
3-element Array{Int64,1}:
1
2
3
julia> arr2 = arr
3-element Array{Int64,1}:
1
2
3
现在我们从arr2中移除一个元素:
julia> pop!(arr2)
3
因此,arr2看起来是这样的:
julia> arr2
2-element Array{Int64,1}:
1
2
但我们的原始数组也被修改了:
julia> arr
2-element Array{Int64,1}:
1
2
将arr赋给arr2并不会将arr的值复制到arr2中,它只创建了一个新的绑定(一个新的名称),指向原始的arr数组。要创建具有相同值的单独数组,我们需要使用copy函数:
julia> arr
2-element Array{Int64,1}:
1
2
julia> arr2 = copy(arr)
2-element Array{Int64,1}:
1
2
现在,如果我们从复制的数组中移除一个元素:
julia> pop!(arr2)
2
我们原始的数组没有改变:
julia> arr
2-element Array{Int64,1}:
1
2
只有副本被修改了:
julia> arr2
1-element Array{Int64,1}:
1
集合推导
数组推导提供了一种非常强大的构建数组的方法。它类似于之前讨论的数组字面量表示法,但不同之处在于我们不是传递实际值,而是使用对可迭代对象的计算。
一个例子会使其更清楚:
julia> [x += 1 for x = 1:5]
10-element Array{Int64,1}:
2
3
4
5
6
这可以读作——对于范围1到5内的每个元素x,计算x+1并将结果值放入数组中。
就像普通的数组字面量一样,我们可以约束类型:
julia> Float64[x+=1 for x = 1:5]
5-element Array{Float64,1}:
2.0
3.0
4.0
5.0
6.0
类似地,我们可以创建多维数组:
julia> [x += y for x = 1:5, y = 11:15]
5×5 Array{Int64,2}:
12 13 14 15 16
13 14 15 16 17
14 15 16 17 18
15 16 17 18 19
16 17 18 19 20
可以使用if关键字对集合进行过滤:
julia> [x += 1 for x = 1:10 if x/2 > 3]
4-element Array{Int64,1}:
8
9
10
11
在这种情况下,我们只保留了x/2大于3的值。
生成器
但是,当它们用于创建生成器时,集合的强大功能被激活了。生成器可以被迭代以按需产生值,而不是分配一个数组并在事先存储所有值。你将在下一秒看到这意味着什么。
生成器定义的方式与数组推导式相同,但没有方括号:
julia> (x+=1 for x = 1:10)
Base.Generator{UnitRange{Int64},##41#42}(#41, 1:10)
它们允许我们与可能无限大的集合一起工作。检查以下示例,我们想要打印出从一到一百万的数字,其立方小于或等于1_000:
julia> for i in [x³ for x=1:1_000_000]
i >= 1_000 && break
println(i)
end
1
8
27
64
125
216
343
512
729
这个计算使用了大量的资源,因为理解创建了一个包含 1 百万个项目的完整数组,尽管我们只迭代了它的前九个元素。
我们可以通过使用方便的@time构造函数来基准测试代码:
@time for i in [x³ for x=1:1_000_000]
i >= 1_000 && break
println(i)
end
0.035739 seconds (58.46 k allocations: 10.493 MiB)
超过 10 MB 的内存和近 60,000 次分配。与使用生成器相比:
@time for i in (x³ for x=1:1_000_000)
i >= 1_000 && break
println(i)
end
0.019681 seconds (16.63 k allocations: 898.414 KiB)
不到 1 MB 和分配次数的四分之一。如果我们从 1 百万增加到 10 亿,差异将更加明显:
julia> @time for i in [x³ for x=1:1_000_000_000]
i >= 1_000 && break
println(i)
end
1
8
27
64
125
216
343
512
729
10.405833 seconds (58.48 k allocations: 7.453 GiB, 3.41% gc time)
超过 10 秒和 7 GB 的内存使用!
另一方面,生成器几乎以恒定的时间运行:
julia> @time for i in (x³ for x=1:1_000_000_000)
i >= 1_000 && break
println(i)
end
1
8
27
64
125
216
343
512
729
0.020068 seconds (16.63 k allocations: 897.945 KiB
使用 Julia 进行数据探索分析
现在你已经很好地理解了 Julia 的基础知识,我们可以将这个知识应用到我们的第一个项目中。我们将首先通过数据探索分析(EDA)来应用爱丽丝花数据集。
如果你已经对数据分析有经验,你可能之前已经使用过爱丽丝花数据集。如果是这样,那太好了!你将熟悉数据以及在你(之前)选择的语言中如何做事,现在可以专注于 Julia 的方式。
相反,如果你第一次听说爱丽丝花数据集,无需担心。这个数据集被认为是数据科学的Hello World——我们将使用 Julia 强大的工具箱来仔细研究它。享受吧!
爱丽丝花数据集
也称为费舍尔的爱丽丝花数据集,它最初由英国统计学家和生物学家罗纳德·费舍尔在 1936 年介绍。该数据集由三个品种的爱丽丝花(Iris setosa、Iris virginica 和 Iris versicolor)的 50 个样本组成。有时它被称为安德森的爱丽丝花数据集,因为埃德加·安德森收集了这些数据。测量了四个特征——萼片和花瓣的长度和宽度(以厘米为单位)。
使用RDatasets包
寻找用于学习、教学和统计软件开发的高质量数据可能具有挑战性。这就是为什么该行业实际上已经标准化了超过 10,000 个高质量数据集的使用。这些数据集最初是与统计软件环境 R 一起分发的。因此,它们被恰当地命名为RDatasets。
爱丽丝花数据集是本集合的一部分。有多种方式可以下载它,但最方便的方式是通过RDatasets包。这个包为 Julia 用户提供了方便的方式来实验 R 中大多数标准数据集,或者包含在 R 最受欢迎的包中。听起来很棒;让我们添加它。
首先,切换到包管理模式:
julia> ]
pkg> add RDatasets
一旦添加了包,让我们告诉 Julia 我们想要使用它:
julia> using RDatasets
我们可以通过调用 RDatasets.datasets() 来查看包含的数据集。它返回一个包含 RDatasets 中所有 700 多个数据集的列表。它包括数据包的详细信息、数据集的名称、标题(或信息)、行数和列数。以下是前 20 行:
julia> RDatasets.datasets()
输出如下:
你可以看到数据集是 Package 的一部分——我们可以用它来过滤。Iris 花数据集是 datasets 包的一部分。
现在我们只需要加载数据:
julia> iris = dataset("datasets", "iris")
输出如下:
返回值是一个包含 150 行和五列的 DataFrame 对象——SepalLength(花萼长度)、SepalWidth(花萼宽度)、PetalLength(花瓣长度)、PetalWidth(花瓣宽度)和 Species(物种),以及一个自动添加的名为 Row 的 id 列。
Dataframes 是 Julia 处理表格数据的 de facto 标准。它们是 Julia 数据分析工具集的关键部分,我们将在下一章中详细讨论它们。现在,只需说,正如你在前面的例子中看到的那样,它代表了一种类似于表格或电子表格的数据结构。
你可以使用以下方式编程地检索列名:
julia> names(iris)
5-element Array{Symbol,1}:
:SepalLength
:SepalWidth
:PetalLength
:PetalWidth
:Species
要检查大小,请使用以下方法:
julia> size(iris)
(150, 5)
结果是一个与行数和列数相匹配的元组 (rows, cols)。是的,正如已经确立的,150 行跨越 5 列。
让我们看看数据:
julia> head(iris)
输出如下:
head 函数显示前六行。可选地,它接受第二个参数来指定行数:head(iris, 10)。还有一个它的双胞胎函数 tail(),它将显示 DataFrame 的底部行:
julia> tail(iris, 10)
输出如下:
关于数据集中存在的物种,我们在头部行中看到 setosa,在底部看到 virginica。然而,根据数据的描述,我们应该有三个物种。让我们按 Species 分组请求行数:
julia> by(iris, :Species, nrow)
输出如下:
by 函数接受三个参数——数据集、列名和一个分组函数——在这个例子中,nrow,它计算行数。我们可以看到第三种物种是 versicolor,对于每种物种,我们都有 50 条记录。
我敢肯定你一定想知道,在前面的例子中,为什么列名前面有一个冒号 ":"。这是一个 Symbol。当我们学习到元编程时,我们将更详细地讨论符号。现在,你只需将符号视为标识符或标签即可。
使用简单统计来更好地理解我们的数据
现在,我们已经清楚地了解了数据的结构以及集合中包含的内容,我们可以通过查看一些基本统计信息来更好地理解。
为了让我们开始,让我们调用 describe 函数:
julia> describe(iris)
输出如下:
此函数总结了 iris DataFrame 的列。如果列包含数值数据(如 SepalLength),它将计算最小值、中位数、平均值和最大值。还包括缺失值和唯一值的数量。最后一列报告存储在行中的数据类型。
一些其他统计信息也是可用的,包括第 25 百分位和第 75 百分位,以及第一个和最后一个值。我们可以通过传递一个额外的 stats 参数来请求它们,该参数是一个符号数组的格式:
julia> describe(iris, stats=[:q25, :q75, :first, :last])
输出如下:
接受任何组合的统计标签。这些都是所有选项——:mean、:std、:min、:q25、:median、:q75、:max、:eltype、:nunique、:first、:last 和 :nmissing。
为了获取所有统计信息,接受特殊的 :all 值:
julia> describe(iris, stats=:all)
输出如下:
我们也可以通过使用 Julia 的 Statistics 包单独计算这些值。例如,要计算 SepalLength 列的平均值,我们将执行以下操作:
julia> using Statistics
julia> mean(iris[:SepalLength])
5.843333333333334
在这个例子中,我们使用 iris[:SepalLength] 来选择整个列。结果,毫不意外,与相应的 describe() 返回值相同。
以类似的方式,我们可以计算 median():
julia> median(iris[:SepalLength])
5.8
还有更多(很多)内容,例如,例如,标准差 std():
julia> std(iris[:SepalLength])
0.828066127977863
或者,我们可以使用 Statistics 包中的另一个函数 cor(),在简单的脚本中帮助我们了解值之间的相关性:
julia> for x in names(iris)[1:end-1]
for y in names(iris)[1:end-1]
println("$x \t $y \t $(cor(iris[x], iris[y]))")
end
println("-------------------------------------------")
end
执行此代码片段将产生以下输出:
SepalLength SepalLength 1.0
SepalLength SepalWidth -0.11756978413300191
SepalLength PetalLength 0.8717537758865831
SepalLength PetalWidth 0.8179411262715759
------------------------------------------------------------
SepalWidth SepalLength -0.11756978413300191
SepalWidth SepalWidth 1.0
SepalWidth PetalLength -0.42844010433053953
SepalWidth PetalWidth -0.3661259325364388
------------------------------------------------------------
PetalLength SepalLength 0.8717537758865831
PetalLength SepalWidth -0.42844010433053953
PetalLength PetalLength 1.0
PetalLength PetalWidth 0.9628654314027963
------------------------------------------------------------
PetalWidth SepalLength 0.8179411262715759
PetalWidth SepalWidth -0.3661259325364388
PetalWidth PetalLength 0.9628654314027963
PetalWidth PetalWidth 1.0
------------------------------------------------------------
脚本遍历数据集的每一列,除了 Species(最后一列,不是数值类型),并生成一个基本的关联表。该表显示 SepalLength 与 PetalLength(87.17%)、SepalLength 与 PetalWidth(81.79%)、以及 PetalLength 与 PetalWidth(96.28%)之间存在强烈的正相关。SepalLength 与 SepalWidth 之间没有强烈的关联。
我们可以使用相同的脚本,但这次使用 cov() 函数来计算数据集中值的协方差:
julia> for x in names(iris)[1:end-1]
for y in names(iris)[1:end-1]
println("$x \t $y \t $(cov(iris[x], iris[y]))")
end
println("--------------------------------------------")
end
此代码将生成以下输出:
SepalLength SepalLength 0.6856935123042507
SepalLength SepalWidth -0.04243400447427293
SepalLength PetalLength 1.2743154362416105
SepalLength PetalWidth 0.5162706935123043
-------------------------------------------------------
SepalWidth SepalLength -0.04243400447427293
SepalWidth SepalWidth 0.189979418344519
SepalWidth PetalLength -0.3296563758389262
SepalWidth PetalWidth -0.12163937360178968
-------------------------------------------------------
PetalLength SepalLength 1.2743154362416105
PetalLength SepalWidth -0.3296563758389262
PetalLength PetalLength 3.1162778523489933
PetalLength PetalWidth 1.2956093959731543
-------------------------------------------------------
PetalWidth SepalLength 0.5162706935123043
PetalWidth SepalWidth -0.12163937360178968
PetalWidth PetalLength 1.2956093959731543
PetalWidth PetalWidth 0.5810062639821031
-------------------------------------------------------
输出说明了 SepalLength 与 PetalLength 和 PetalWidth 正相关,而与 SepalWidth 负相关。SepalWidth 与所有其他值负相关。
接下来,如果我们想要一个随机数据样本,我们可以这样请求:
julia> rand(iris[:SepalLength])
7.4
可选地,我们可以传递要采样的值的数量:
julia> rand(iris[:SepalLength], 5)
5-element Array{Float64,1}:
6.9
5.8
6.7
5.0
5.6
我们可以使用以下方法将某一列转换为数组:
julia> sepallength = Array(iris[:SepalLength])
150-element Array{Float64,1}:
5.1
4.9
4.7
4.6
5.0
# ... output truncated ...
或者,我们可以将整个 DataFrame 转换为矩阵:
julia> irisarr = convert(Array, iris[:,:])
150×5 Array{Any,2}:
5.1 3.5 1.4 0.2 CategoricalString{UInt8} "setosa"
4.9 3.0 1.4 0.2 CategoricalString{UInt8} "setosa"
4.7 3.2 1.3 0.2 CategoricalString{UInt8} "setosa"
4.6 3.1 1.5 0.2 CategoricalString{UInt8} "setosa"
5.0 3.6 1.4 0.2 CategoricalString{UInt8} "setosa"
# ... output truncated ...
可视化鸢尾花数据
可视化是探索性数据分析中的强大工具,帮助我们识别仅通过查看数字难以发现的模式。Julia 提供了访问一些出色的绘图包的途径,这些包非常容易设置和使用。
我们将通过使用 Gadfly 创建的一些图表来举例说明。
我们将首先通过pkg> add "Gadfly"添加 Gadfly,然后继续使用julia> using Gadfly。这将使 Gadfly 的plot()方法生效。现在,让我们找到一些有趣的数据来进行可视化。
在上一节中,我们已经确定SepalLength和PetalLength之间存在强烈的协变关系。让我们绘制这些数据:
julia> plot(iris, x=:SepalLength, y=:PetalLength, color=:Species)
在撰写本文时,Gadfly 对 Julia v1 的支持仍然不完整。如果情况仍然如此,可以使用不稳定但可工作的 Gadfly 版本安装——pkg> add Compose#master, Gadfly#master, Hexagon。
执行plot()函数将生成以下图形:
果然,该图将表明对于 Iris versicolor 和 Iris virginica,SepalLength和PetalLength是共同变化的。对于 Iris setosa,这并不那么明显,因为PetalLength基本保持不变,而萼片长度在增长。
箱线图将确认相同的结果;Iris setosa 的萼片长度变化很小:
julia> plot(iris, x=:Species, y=:PetalLength, Geom.boxplot)
我们绘制值的样子如下:
我有一种感觉,直方图将更好地说明PetalLength的分布:
julia> plot(iris, x=:PetalLength, color=:Species, Geom.histogram)
使用PetalLength生成直方图会产生以下结果:
如果我们将PetalWidth值可视化为直方图,我们会注意到类似的模式:
julia> plot(iris, x=:PetalWidth, color=:Species, Geom.histogram)
输出如下:
绘制三种物种的花瓣宽度和高度图,现在应该能强烈表明,例如,我们可以根据这两个值成功地将鸢尾花属的 Iris setosa 进行分类:
julia> plot(iris, x=:PetalWidth, y=:PetalLength, color=:Species)
输出如下:
加载和保存我们的数据
Julia 自带了出色的读取和存储数据的工具。鉴于其专注于数据科学和科学计算,对表格文件格式(CSV,TSV)的支持是一流的。
让我们从我们的初始数据集中提取一些数据,并使用它来练习从各种后端进行持久化和检索。
我们可以通过定义相应的列和行来引用DataFrame的某个部分。例如,我们可以定义一个新的DataFrame,它仅由PetalLength和PetalWidth列以及前三个行组成:
julia> iris[1:3, [:PetalLength, :PetalWidth]]
3×2 DataFrames.DataFrame
│ Row │ PetalLength │ PetalWidth │
├─────┼─────────────┼────────────┤
│ 1 │ 1.4 │ 0.2 │
│ 2 │ 1.4 │ 0.2 │
│ 3 │ 1.3 │ 0.2 │
通用索引符号是dataframe[rows, cols],其中rows可以是数字、范围或boolean值的Array,其中true表示该行应被包含:
julia> iris[trues(150), [:PetalLength, :PetalWidth]]
150 rows since trues(150) constructs an array of 150 elements that are all initialized as true. The same logic applies to cols, with the added benefit that they can also be accessed by name.
带着这些知识,让我们从原始数据集中抽取一个样本。它将包括大约 10% 的初始数据,以及 PetalLength、PetalWidth 和 Species 列:
julia> test_data = iris[rand(150) .<= 0.1, [:PetalLength, :PetalWidth, :Species]]
10×3 DataFrames.DataFrame
│ Row │ PetalLength │ PetalWidth │ Species │
├─────┼─────────────┼────────────┼──────────────┤
│ 1 │ 1.1 │ 0.1 │ "setosa" │
│ 2 │ 1.9 │ 0.4 │ "setosa" │
│ 3 │ 4.6 │ 1.3 │ "versicolor" │
│ 4 │ 5.0 │ 1.7 │ "versicolor" │
│ 5 │ 3.7 │ 1.0 │ "versicolor" │
│ 6 │ 4.7 │ 1.5 │ "versicolor" │
│ 7 │ 4.6 │ 1.4 │ "versicolor" │
│ 8 │ 6.1 │ 2.5 │ "virginica" │
│ 9 │ 6.9 │ 2.3 │ "virginica" │
│ 10 │ 6.7 │ 2.0 │ "virginica" │
这里发生了什么?这段代码的秘密在于 rand(150) .<= 0.1。它做了很多事情——首先,它生成一个介于 0 和 1 之间的随机 Float 值数组;然后,它逐元素比较数组与 0.1(代表 1 的 10%);最后,生成的 Boolean 数组用于从数据集中过滤出相应的行。Julia 的强大和简洁真的令人印象深刻!
在我的情况下,结果是包含前面 10 行的 DataFrame,但你的数据可能会有所不同,因为我们正在选择随机行(而且你也不一定有 exactly 10 行)。
使用表格文件格式进行保存和加载
我们可以使用 CSV 包轻松地将这些数据保存到表格文件格式(CSV、TSV 等之一)的文件中。我们首先需要添加它,然后调用 write 方法:
pkg> add CSV
julia> using CSV
julia> CSV.write("test_data.csv", test_data)
同样容易,我们可以使用相应的 CSV.read 函数从表格文件格式中读取数据:
julia> td = CSV.read("test_data.csv")
10×3 DataFrames.DataFrame
│ Row │ PetalLength │ PetalWidth │ Species │
├─────┼─────────────┼────────────┼──────────────┤
│ 1 │ 1.1 │ 0.1 │ "setosa" │
│ 2 │ 1.9 │ 0.4 │ "setosa" │
│ 3 │ 4.6 │ 1.3 │ "versicolor" │
│ 4 │ 5.0 │ 1.7 │ "versicolor" │
│ 5 │ 3.7 │ 1.0 │ "versicolor" │
│ 6 │ 4.7 │ 1.5 │ "versicolor" │
│ 7 │ 4.6 │ 1.4 │ "versicolor" │
│ 8 │ 6.1 │ 2.5 │ "virginica" │
│ 9 │ 6.9 │ 2.3 │ "virginica" │
│ 10 │ 6.7 │ 2.0 │ "virginica" │
仅指定文件扩展名就足以让 Julia 理解如何处理文档(CSV、TSV),无论是写入还是读取。
使用 Feather 文件
Feather 是一种专门为存储数据框而设计的二进制文件格式。它快速、轻量级且语言无关。该项目最初启动是为了使 R 和 Python 之间交换数据框成为可能。很快,其他语言也添加了对它的支持,包括 Julia。
对 Feather 文件的支持不是默认提供的,但可以通过同名的包获得。让我们继续添加它并将其纳入作用域:
pkg> add Feather
julia> using Feather
现在,保存我们的 DataFrame 只需调用 Feather.write:
julia> Feather.write("test_data.feather", test_data)
接下来,让我们尝试反向操作并重新加载我们的 Feather 文件。我们将使用对应的 read 函数:
julia> Feather.read("test_data.feather")
10×3 DataFrames.DataFrame
│ Row │ PetalLength │ PetalWidth │ Species │
├─────┼─────────────┼────────────┼──────────────┤
│ 1 │ 1.1 │ 0.1 │ "setosa" │
│ 2 │ 1.9 │ 0.4 │ "setosa" │
│ 3 │ 4.6 │ 1.3 │ "versicolor" │
│ 4 │ 5.0 │ 1.7 │ "versicolor" │
│ 5 │ 3.7 │ 1.0 │ "versicolor" │
│ 6 │ 4.7 │ 1.5 │ "versicolor" │
│ 7 │ 4.6 │ 1.4 │ "versicolor" │
│ 8 │ 6.1 │ 2.5 │ "virginica" │
│ 9 │ 6.9 │ 2.3 │ "virginica" │
│ 10 │ 6.7 │ 2.0 │ "virginica" │
是的,这正是我们的样本数据!
为了与其他语言提供兼容性,Feather 格式对列的数据类型施加了一些限制。你可以在包的官方文档中了解更多关于 Feather 的信息:juliadata.github.io/Feather.jl/latest/index.html。
使用 MongoDB 进行保存和加载
在关闭这一章之前,让我们看看如何使用 NoSQL 后端来持久化和检索我们的数据。别担心,我们将在接下来的章节中广泛介绍与关系型数据库的交互。
为了继续本章内容,你需要一个可工作的 MongoDB 安装。你可以从官方网站下载并安装适用于你操作系统的正确版本,网址为www.mongodb.com/download-center?jmp=nav#community。我将使用一个通过 Docker 的 Kitematic(可在github.com/docker/kitematic/releases下载)安装并启动的 Docker 镜像。
接下来,我们需要确保添加Mongo包。该包还依赖于LibBSON,它将自动添加。LibBSON用于处理BSON,即二进制 JSON,类似于 JSON 的文档的二进制编码序列化。在此期间,让我们也添加JSON包;我们将需要它。我相信你现在知道如何做——如果不的话,这里有一个提醒:
pkg> add Mongo, JSON
在撰写本文时,Mongo.jl 对 Julia v1 的支持仍在进行中。此代码使用 Julia v0.6 进行了测试。
简单!让我们让 Julia 知道我们将使用所有这些包:
julia> using Mongo, LibBSON, JSON
现在我们已经准备好连接到 MongoDB:
julia> client = MongoClient()
一旦成功连接,我们就可以在db数据库中引用dataframes集合:
julia> storage = MongoCollection(client, "db", "dataframes")
Julia 的 MongoDB 接口使用字典(在 Julia 中称为Dict的数据结构)与服务器通信。我们将在下一章中更详细地了解dicts。现在,我们只需要将我们的DataFrame转换为这样的Dict。最简单的方法是使用JSON包按顺序序列化和反序列化DataFrame。它生成一个很好的结构,我们可以稍后使用它来重建我们的DataFrame:
julia> datadict = JSON.parse(JSON.json(test_data))
提前思考,为了使未来的数据检索更简单,让我们在我们的字典中添加一个标识符:
julia> datadict["id"] = "iris_test_data"
现在我们可以将其插入 Mongo 数据库中:
julia> insert(storage, datadict)
为了检索它,我们只需使用之前配置的“id”字段查询 Mongo 数据库:
Julia> data_from_mongo = first(find(storage, query("id" => "iris_test_data")))
我们得到一个BSONObject,我们需要将其转换回DataFrame。别担心,这很简单。首先,我们创建一个空的DataFrame:
julia> df_from_mongo = DataFrame()
0×0 DataFrames.DataFrame
然后我们使用从 Mongo 检索到的数据填充它:
for i in 1:length(data_from_mongo["columns"])
df_from_mongo[Symbol(data_from_mongo["colindex"]["names"][i])] =
Array(data_from_mongo["columns"][i])
end
julia> df_from_mongo
10×3 DataFrames.DataFrame
│ Row │ PetalLength │ PetalWidth │ Species │
├─────┼─────────────┼────────────┼──────────────┤
│ 1 │ 1.1 │ 0.1 │ "setosa" │
│ 2 │ 1.9 │ 0.4 │ "setosa" │
│ 3 │ 4.6 │ 1.3 │ "versicolor" │
│ 4 │ 5.0 │ 1.7 │ "versicolor" │
│ 5 │ 3.7 │ 1.0 │ "versicolor" │
│ 6 │ 4.7 │ 1.5 │ "versicolor" │
│ 7 │ 4.6 │ 1.4 │ "versicolor" │
│ 8 │ 6.1 │ 2.5 │ "virginica" │
│ 9 │ 6.9 │ 2.3 │ "virginica" │
│ 10 │ 6.7 │ 2.0 │ "virginica" │
就这样!我们的数据已经重新加载到DataFrame中。
摘要
Julia 直观的语法使得学习曲线变得平缓。可选的类型和丰富的缩写构造函数使得代码可读、无噪声,而大量的第三方包使得访问、操作、可视化、绘图和保存数据变得轻而易举。
只需学习 Julia 的基本数据结构和一些相关函数,再加上其强大的数据处理工具集,我们就能够实现高效的数据分析工作流程,并从 Iris 花朵数据集中提取有价值的见解。这正是我们使用 Julia 进行高效探索性数据分析所需的一切。
在下一章中,我们将继续我们的旅程,学习如何构建一个网络爬虫。网络挖掘,即从网络中提取信息的过程,是数据挖掘的重要组成部分,也是数据获取的关键环节。在构建网络挖掘软件时,Julia 是一个极佳的选择,这不仅因为它内置的性能和快速原型设计功能,还因为其提供了覆盖从 HTTP 客户端到 DOM 解析再到文本分析的强大库。