logo一言堂

用 Julia 做数据分析

据说,这是个大数据的时代。当然,我们普通人接触真正大数据的机会不多,但总要学一两招数据分析,否则也不好出来见人不是。假如你有一堆数据,通常在一个CSV文件里,你需要把数据处理一下,做一些图表,找一些规律,说明一些现象,你可能会用 excel 来做这件事,这没问题。

但假如这原始数据文件有几十上百个字段,几百万行呢?傻眼了吧。

这时候你需要真正强悍的家伙事儿。请看: Julia

什么是Julia

Julia是最近十年内出现的新型编程语言。通俗地说,它兼具 Matlab 的科学计算功能,和 R 的统计计算功能,语法更现代化,而且非常快. 这主要是因为:

  • 它是基于LLVM的编译到底执行。
  • 它是真正通用的编程环境,并不局限于计算
  • 它有相当复杂的自动并行计算功能,充分利用你电脑的多个核心

假如你曾用过Matlab,Octave 或 R,但不是花过大量心血,还愿意尝试新鲜事物的话,不管你是专业从事数据分析处理,还是像我一样的业余爱好者,我建议你了解一下,跟我一起开始一段简短的旅程。

安装

当然,第一步是安装。请从官网下载安装。过程相当标准化,没什么好说的。然后,你就得到了一个命令行界面:

Dereks-MacBook-Pro:~ derek$ julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.5.1 (2020-08-25)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia>

你还可以尝试集成IDE Juno, 这是一个基于 Atom 的集成环境。我大致试用了一下,还不错,但对我个人意义不是特别大。当然我不是一个典型用户,大家不妨多尝试一下。

大型数据文件处理

现在回到我们开始的问题。我们有一个相当大的数据文件需要处理分析。Julia直接提供了相应的程序包,就是 DataFrames, 对应复杂类型的大型数据表格处理,和 CSV, 负责CSV文件的读取到 DataFrame 和从 DataFrame 输出到CSV文件。这些程序包都可以在julia命令行下安装:

julia> using Pkg
julia> Pkg.add("DataFrames")
julia> Pkg.add("CSV")
julia> using DataFrames, CSV

一大堆安装编译的输出之后,这两个包就安装好可以用了。

第一步是读入我们的数据文件:

df = CSV.read("/Users/derek/Downloads/Ridershare_data_triplevel_Sep2019_week1_nofilter.csv")

简单吧。这个文件有两百万行,但十秒钟之内就读入,生成成DataFrame了:

1978890×26 DataFrame. Omitted printing of 22 columns
│ Row     │ Column1 │ Trip_ID                                  │ Trip_Day │ Is_Weekend │
│         │ Int64   │ String                                   │ String   │ Bool       │
├─────────┼─────────┼──────────────────────────────────────────┼──────────┼────────────┤
│ 1       │ 0       │ 0003eb321a18a94794a1f7953738569808fa38e5 │ Sunday   │ 1          │
│ 2       │ 3874    │ ac3456ae1d86578e2ec442ff9d4f1d980af9b2cf │ Sunday   │ 1          │
│ 3       │ 3873    │ ac30915385127ec33af27d30a92a8462d6c2c671 │ Sunday   │ 1          │
│ 4       │ 3872    │ ac2ef27a1089d302638bbe39afb5c5462fea0235 │ Sunday   │ 1          │
│ 5       │ 3871    │ ac1d012abf015bcb969e76710b3d1a29089a3a12 │ Sunday   │ 1          │

第二步是删掉我们不用的列。同样很简单:

df = df[:, [:Trip_Start_Timestamp, :Trip_End_Timestamp, :Pickup_Census_Tract, :Dropoff_Census_Tract]]
1978890×4 DataFrame. Omitted printing of 1 columns
│ Row     │ Trip_Start_Timestamp │ Trip_End_Timestamp     │ Pickup_Census_Tract │
│         │ String               │ String                 │ Int64?              │
├─────────┼──────────────────────┼────────────────────────┼─────────────────────┤
│ 1       │ 2019-09-01           │ 09/01/2019 12:15:00 AM │ missing             │
│ 2       │ 2019-09-01           │ 09/01/2019 12:15:00 AM │ 17031081402         │
│ 3       │ 2019-09-01           │ 09/01/2019 12:15:00 AM │ 17031071300         │
│ 4       │ 2019-09-01           │ 09/01/2019 12:15:00 AM │ 17031330100         │
│ 5       │ 2019-09-01           │ 09/01/2019 12:15:00 AM │ 17031842000         │

第三步是删掉数据不完整的行。原始文件通常都是有瑕疵的,需要一定的数据清洁。这在julia里非常简单:

julia> df = DataFrames.dropmissing(df)
1307900×4 DataFrame. Omitted printing of 1 columns
│ Row     │ Trip_Start_Timestamp │ Trip_End_Timestamp     │ Pickup_Census_Tract │
│         │ String               │ String                 │ Int64               │
├─────────┼──────────────────────┼────────────────────────┼─────────────────────┤
│ 1       │ 2019-09-01           │ 09/01/2019 12:15:00 AM │ 17031081402         │
│ 2       │ 2019-09-01           │ 09/01/2019 12:15:00 AM │ 17031071300         │
│ 3       │ 2019-09-01           │ 09/01/2019 12:15:00 AM │ 17031330100         │
│ 4       │ 2019-09-01           │ 09/01/2019 12:15:00 AM │ 17031842000         │
│ 5       │ 2019-09-01           │ 09/01/2019 12:15:00 AM │ 17031839100         │

第四步是对列做一定数据处理。这其实需要写一点程序,但外围就是这样的:

julia> df = DataFrames.select(df,
           :Trip_Start_Timestamp => ByRow(ts2integer) => :start,
           :Trip_Start_Timestamp => ByRow(ts2integer) => :end,
           :Pickup_Census_Tract => :pickup,
           :Dropoff_Census_Tract => :dropoff)
1307900×4 DataFrame
│ Row     │ start │ end   │ pickup      │ dropoff     │
│         │ Int64 │ Int64 │ Int64       │ Int64       │
├─────────┼───────┼───────┼─────────────┼─────────────┤
│ 1       │ 0     │ 0     │ 17031081402 │ 17031320600 │
│ 2       │ 0     │ 0     │ 17031071300 │ 17031080300 │
│ 3       │ 0     │ 0     │ 17031330100 │ 17031841100 │
│ 4       │ 0     │ 0     │ 17031842000 │ 17031063200 │
│ 5       │ 0     │ 0     │ 17031839100 │ 17031833000 │

以上 ts2integer 函数是我自己根据数据格式和我的需要写的,这里就不贴了。

再往后,就是你自己的数据处理和统计分析,取决于你个人的想法。你需要最基础的画图工具包:

julia> Pkg.add("Plots")
julia> using Plots

我画的一个简单曲线如下:

julia plot
julia plot

批评

说了一堆优点,现在说缺点。

脉络不清晰

实话说,我看了这两天,Julia 语法的脉络我还没摸出来。Julia 既可以当 OOP 语言用,也可以当 FP 语言用,但二者都只能做到八成。这也是可以理解的,毕竟 OOP 和 FP 不能共存. 我感觉它太过求全求大,所以难以找到脉络。我个人学东西总希望先找到核心思想,再用核心思想去套细节,但在这里可能还要依赖于盲人摸象式学习。

文档无重点

Julia 在线文档不少,但可惜的是缺乏主次之分。好的文档能让新手上来不用全部看过就先能用上,解决一些简单问题。细节可以过后细细研究。但 Julia 的核心库文档还是以堆砌事实为主。更由于上一条本来脉络就不是很清晰,一些很简单的事情我在偌大文档里也难以找到答案,不得已借助谷歌搜索或 Stack Overflow 来寻求答案。在这点上,我前一段主力学习的 elixir 文档就好的多。

总结

Julia 已经不是新生事物。它已经有八年的历史,成熟的社区,和一个相当擅长的领域。从高阶来看, Matlab和 R 的 99% 的使用场景都可以被取代;从低阶应用来说,使用 Python 等通用脚本语言来做数值计算和统计分析也变得没有实际意义了。

Julia 是 100% 的自由软件。普通人,用普通家用电脑,已经可以做 GB 级别相当复杂的数据分析,只要你愿意花功夫。

颤抖吧,凡人。专业和业余的鸿沟正在消失。