命令行数据科学工具笔记

《Data Science at the Command Line》阅读笔记。
用命令行工具来提升效率,还有这种操作.jpg

前言

对于习惯了Windows图形化操作界面的人来说,对于命令行/终端操作界面可能嗤之以鼻。但不可否认的是使用命令行的效率是大于图形界面的。
无意间看到了这本书,觉得里面的一些命令行操作非常实用,也是之前没有接触过的。故记录书中的一些通用的技巧,在很多Linux下面都是同样适用的。
对于涉及到具体的问题,可以诸如Python解决的,就跳过了。这些命令不必烂熟于行,但至少

本书在线阅读的网站是https://www.datascienceatthecommandline.com/
示例和GitHub地址是https://github.com/jeroenjanssens/data-science-at-the-command-line

入门起步

首先你需要一定的GNU/Linux知识,至少会使用Linux系统。那么命令行工具的环境大致可以分为四层:命令(Command Line)->终端(Terminal)->Shell->操作系统(Operating System)。Shell是解释你输入命令的,一般我们在Linux发行版中使用的Shell是bash,当然也有其它的比如大名鼎鼎的zsh。

如何执行命令行工具呢?在终端下输入相应的命令就行了,比如$ pwd就会给出当前的地址。

常见的命令行工具可以分成5种:

  • 二进制可执行文件,一般是由其它编译而成的
  • Shell自带命令,比如cdhelp
  • 可解释脚本,如Python脚本
  • Shell函数,Shell是自带了一些函数的如seqfac
  • 命令别名(alias),你可以把一些很长的命令用别名的方式来简化。

结合使用不同的命令行工具,比如

1
$ seq 100 | grep 3 | wc -l

seq 100的作用是输出1-100的数,那么grep就在输出中找出含有3的项,之后wc -l就是数grep之后输出的行数,那么这里有19个。

重定向输入输出,如$ seq 10 > output.txt就可以把命令行的输出保存到文件中,这在许多调试中是非常有用的。
下面的命令中,echo的-n是不换行,>>是指在文件后接着写(append的方式)

1
2
$ echo -n "Hello" > hello-world.txt
$ echo " World" >> hello-world.txt

接下来看看怎么读取文件,一个常用的命令是cat,如$ cat hello-world.txt | wc -w,其中-w参数是数单词数(words),这条的结果应该是2,因为"Hello World"有两个单词。
当然还有其它的方法,这句和上面的输出是一样的$ < hello-world.txt wc -w,只不过它将hello-word.txt文件直接加载到了wc的输入流里面。或者直接使用wc的参数$ wc -w hello-world.txt,但此时输出会将文件名附在单词数后面。

和文件打交道,常用的命令是移动/剪切mv,复制cp,删除rm。下面整理了最常用的一些命令。
$ mv hello.txt ~/book/ch02/data/将文件移动到文件夹中;
$ cd data切换目录,$ mv hello.txt bye.txt重命名;
$ rm bye.txt删除文件,$ rm -r book/ch02/data/old删除文件夹;
$ cp server.log server.log.bak复制文件,$ mkdir logs新建文件夹

在命令行中寻求帮助,对于每个命令行工具,一般是有相应的说明文档的,比如man cat就可以查看cat工具的说明。下面列举了常见的三种查看帮助文档的方式。
$ man cat | head -n 20使用man命令查看帮助前20行;
$ help cd | head -n 20对于一些工具使用help也是一样的。
$ jq --help大部分工具都有--help这个参数,也一样可以查看帮助

Bash常用快捷键,熟悉了的话能够提高不少效率。

获取数据

解压

在Linux中,我们最常见到的压缩文件后缀名是.tar.gz,.zip.rar文件,对应的解压缩命令是tar,unzipunrar,注意在使用他们之前要确保已经正确安装了这些工具。
比如,$ tar -xzvf data/logs.tar.gz,注意命令的参数是-xzvf,分别对应解压archive、gzip解压、verbose输出、指定file。
书中提供了实用的Bash脚本来解压不同类型的文件,附上地址

转换Excel文件

很多情况下,使用Excel保存的数据文件会存放在一个xlsx文件中,对于命令行工具来说非常不友好,通用一点的数据格式是CSV格式。
这时候in2csv命令就能派上用场了。需要先安装一下sudo pip install csvkit
之后就可以使用命令$ in2csv data/imdb-250.xlsx > data/imdb-250.csv将xlsx文件转换成csv文件了。
使用$ in2csv imdb-250.xlsx | head | cut -c1-80可以查看,但是输出的是原始的逗号分隔文件,非常不友好。为了使显示效果更好,可以使用csvlook的管道。
in2csv data/imdb-250.xlsx | head | csvcut -c Title,Year,Rating | csvlook的效果会好很多。
另一种处理的方式就是使用Libre Office这样的的软件打开,再进行转换。但这样非常不方便,而且你在用服务器的时候使不可能装一个Office套件的。

查询关系数据库

关系型数据库常见的有MySQL,PostgreSQL和SQLite等。使用命令行工具sql2csv可以更方便的使用CSV处理SQL数据库。
如使用$ sql2csv --db 'sqlite:///data/iris.db' --query 'SELECT * FROM iris '\> 'WHERE sepal_length > 7.5'进行query查询操作。

从网上下载数据

使用最多的是cURL工具。
$ curl -s http://www.gutenberg.org/cache/epub/76/pg76.txt | head -n 10,参数-s表示silent模式。
$ curl http://www.gutenberg.org/cache/epub/76/pg76.txt > data/finn.txt将curl下载的内容到文件中,此时不能用-s模式。
$ curl -s http://www.gutenberg.org/cache/epub/76/pg76.txt -o data/finn.txt使用-o参数指定输出文件,此时可以使用-s
$ curl -u username:password ftp://host/file下载FTP上的文件
$ curl -L j.mp/locatbbar当网址是诸如http://bit.ly/*的自动跳转等短网址时,加上-L参数就可以下载跳转之后的网页内容。

通过API请求数据

很多时候我们需要通过一些在线服务提供的API来获取数据。比如我们需要知道某地的天气时,就需要通过一些天气服务提供商提供的网络接口来获取数据,通常返回的数据格式时json形式的。使用最基础的curl就能实现,如$ curl -s http://api.randomuser.me | jq '.'
当我们需要更高级的操作时则需要其它工具,这在后续会涉及到。

创建可复用的命令行工具

其实就是将命令写进一个脚本文件。这在我们需要重复连续执行繁琐的命令的时候会非常有用。当然这里只能介绍一些基本的脚本编写,更复杂的需要阅读Shell脚本编写的相关书籍。

将单行指令转换为Shell脚本

通常要经历下面几个步骤:

  1. 将单行指令复制粘贴到一个文件中
  2. 给文件添加执行权限
  3. 定义一个shebang行(如#!/bin/sh)
  4. 去除固定输入部分
  5. 添加参数
  6. 可选的拓展你的路径

复制单行命令到文件

有一个技巧是可以通过!!来代替上一条执行了的命令。
一般来说我们会将Bash脚本命令放到.sh文件中,表明是shell脚本。我们就可以通过$ bash book/ch04/top-words-1.sh这样的命令来执行Shell脚本了。

添加权限

绝大多数情况下,我们实用vim或者nano编辑器创建的.sh文件是没法直接执行的。因为我们还需要给它赋予可执行权限。那么这时候就要用到更改权限的命令chmod。在终端中执行$ chmod u+x top-words-2.sh,其中u指定是用户(也就是你),+x代表增加执行权限。这样你就可以在终端中直接执行./top-words-2.sh来运行这个脚本了。
通过$ ls -l top-words-2.sh来查看文件具体信息,我们可以看到权限部分由原来的-rw-rw-r--变到了-rwxrw-r--,增加了x,执行权限。

定义shebang

在bash脚本的第一行,我们需要加入#!/usr/bin/env bash来告诉终端是用bash来执行这个脚本。
如果你添加的是#!/usr/bin/python,那么就是用python来执行。

去除固定输入

把bash脚本中的固定输入部分删除,这样我们就可以通过终端把我们需要的输入直接给脚本,而无须每次都改动脚本。
$ cat data/finn.txt | top-words-4.sh,通过管道把数据给shell脚本。

参数化

下面的脚本内容定义了一个变量NUM_WORDS,在使用这个变量的时候需要在前面加$符号,这个变量的内容是$1代表着脚本第一个输入参数,比如./xx.sh 1里的1。

1
2
3
4
#!/usr/bin/env bash
NUM_WORDS="$1"
tr '[:upper:]' '[:lower:]' | grep -oE '\w+' | sort |
uniq -c | sort -nr | head -n $NUM_WORDS

拓展你的PATH

很多情况下,在不同的文件夹中,你也想使用你之前编写好的脚本,这时候把脚本所在路径添加到系统PATH中就能够使你在任何地方访问到。
$ echo $PATH | fold
想要长期的改变系统的环境变量的话,就需要修改.bashrc.profile文件了。

使用Python创建命令行工具

迁移脚本

上面的脚本,可以同样使用Python来实现,下面代码给出了实现。

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env python
import re
import sys
from collections import Counter
num_words = int(sys.argv[1])
text = sys.stdin.read().lower()
words = re.split('\W+', text)
cnt = Counter(words)
for word, count in cnt.most_common(num_words):
print "%7d %s" % (count, word)

这时候我们就可以通过Python脚本来执行我们所需的功能,将数据流输入到脚本并附上参数5。$ < data/76.txt top-words.py 5

使用标准输入来处理流数据

当输入数据流是类似命令行形式而非文件时,你就不可能一次性全部处理掉,故需要一行一行处理。

1
2
3
4
5
6
7
8
#!/usr/bin/env python
from sys import stdin, stdout
while True:
line = stdin.readline()
if not line:
break
stdout.write("%d\n" % int(line)**2)
stdout.flush()

清洗数据

对普通文本操作

过滤行

主要用到的命令行工具是sedawk

基于位置:
生成一个10行的数据用于演示。$ seq -f "Line %g" 10 | tee data/lines
打印前三行:$ < lines head -n 3;$ < lines sed -n '1,3p'$ < lines awk 'NR<=3'
打印末三行:$ < lines tail -n 3
删除前三行:$ < lines tail -n +4$ < lines sed '1,3d'$ < lines sed -n '1,3!p'
删除末三行:$ < lines head -n -3
打印4-6行:$ < lines sed -n '4,6p'$ < lines awk '(NR>=4)&&(NR<=6)'$ < lines head -n 6 | tail -n 3
打印奇数行:$ < lines sed -n '1~2p'$ < lines awk 'NR%2'
打印偶数行:$ < lines sed -n '0~2p'$ < lines awk '(NR+1)%2'

基于模式:
使用grep和正则表达式
$ grep -E '^CHAPTER (.*)\. The' alice.txt,在alice.txt中找出所有以"The"开头的章节

基于随机:
使用sample工具创建数据集的子集
$ seq 1000 | sample -r 1% | jq -c '{line: .}'
可以指定sample的延时,避免数据一下子太快打印出来而产生错误,这在调试你的脚本时非常有用。
$ seq 10000 | sample -r 1% -d 1000 -s 5 | jq -c '{line: .}'

提取值

使用grep的输出到cut工具中
$ grep -i chapter alice.txt | cut -d' ' -f3-

替换与删除

使用tr工具
$ echo 'hello world!' | tr ' ' '_',把空格替换成了下划线
$ echo 'hello world!' | tr -d -c '[a-z]',删除不是a-z的字符
tr也可以用来转换大小写
$ echo 'hello world!' | tr '[a-z]' '[A-Z]',将小写转换成大写
$ echo 'hello world!' | tr '[:lower:]' '[:upper:]',与上面效果相同

操作CSV

使用了三种工具:bodyheadercols

操作HTML和JSON

使用了curlscrapexml2jsonjqjson2csv

管理你的数据工作流

原书使用了Drake工具。因为不太通用,故跳过。

探索数据

用了R语言中大名鼎鼎的ggplot2来进行数据可视化。
在Python中可以使用matplotlib或plotly等可视化工具。

并行管道

普通循环

这个还是挺有用的。
使用bc来计算数学表达式。$ echo "4^2" | bc

1
2
3
4
$ for i in {0..100..2}  
> do
> echo "$i^2" | bc
> done | tail

逐行循环:

1
2
3
4
$ while read line                                       
> do
> echo "Sending invitation to ${line}."
> done < data/emails.txt

也可以从标准输入流(即你的控制台输入)中获取数据
$ while read i; do echo "You typed: $i."; done < /dev/stdin

逐个文件循环:

1
2
3
4
$ for filename in *.csv
> do
> echo "Processing ${filename}."
> done

还可以使用find来更灵活的寻找文件
$ find data -name '*.csv' -exec echo "Processing {}" \;

GNU Parallel

非常好用的一个工具,可以让循环变得简单,并且易于管理。
$ seq 5 | parallel "echo {}^2 | bc"

对数据建模

降维、聚类、回归、分类,机器学习问题。这里不展开了。
可以利用python等更友好的实现这些。

打赏支持一下~