- A. H1 ^1 p# P, t1 }
原标题:资源 | 简单快捷的数据处理,数据科学需要注意的命令行
$ e" ~! B8 Y6 S+ n# f, K1 m. b 选自Medium
, y4 J6 Q) C4 Q* R( c6 ~1 g# P" U% y
作者:Kade Killary
2 H& Y \) z8 x! t2 z! v3 s" T7 ] 机器之心编译
{1 U: y% h: y8 M: N' T' F: ]
参与:Nurhachu Null、思源
1 p/ u% l- D) g: t v7 ?( e
对很多数据科学家而言,他们的数据操作经常需要使用 Pandas 或者 Tidyverse。理论上,这个说法没有任何错误,毕竟这就是这些工具存在的原因。然而,对于分隔符转换这样的简单任务而言,这些工具往往是大材小用,我们可以直接使用命令行快速处理。
0 P6 K* A3 w) G+ s" F4 Q
命令行应该是每个开发者都希望掌握的,尤其是数据科学家。熟悉终端的来龙去脉可以毫无疑问地可以让我们变得更加有效率,因此命令行还是计算机技术中的一个很棒的历史课。例如,awk 这个数据驱动的脚本语言是 1977 年在 Brina Kernighan 的帮助下首次出现的,Brina Kernighan 就是 K&R 这本书中的 K。在 50 年后的今天,每年仍然能够出现与 awk 相关的新书。因此,我们可以相对保守地假设:一项针对命令行才能的投资在任何新近的时间内都不会贬值。
+ H' x/ l9 m3 @* N2 }8 n
- R, J* c. h/ p/ @
2 v: l" X+ A7 w6 h3 T% G! B' _ 打开凤凰新闻,查看更多高清图片
, C% O6 L& v/ K. O
# p$ I2 L# M2 L
2 R' ~' A6 ~. o7 |. E! \ 我们将会涉及以下内容
# n* X3 v z2 L
ICONV
( g+ D/ z" G7 |* d* b HEAD
5 d& @2 O: `$ _5 g$ t
TR
3 [1 d- p1 N+ W( u, _2 V6 J& P WC
8 m. W9 c* ~( @
SPLIT
7 Q' p1 V5 e5 m9 T4 {& }# N7 v. R# j! h SORT & UNIQ
! d! M) e( q% E6 Z: i
CUT
$ e, V1 D) F7 d
PASTE
9 f [ B5 }0 f* u: |0 z" Q
JOIN
5 _7 ]9 J2 Y8 m9 w" A8 J
GREP
% ?( G$ g4 r) g& n SED
+ t' Y0 o$ t7 F6 |. o1 b) L! ^
AWK
& F5 V3 L, X) e; n' h/ G ICONV(用来转换文件的编码方式)
0 t, ^/ J; C; K# F
文件编码可能是比较棘手的。现在的多数文件都是 UTF-8 编码,然而有时候我们拿到的文件并不是这个格式的。这可能导致交换编码格式时的一些不靠谱的尝试。这里,iconv 是一个拯救者,它能以一种编码的文本为输入,输出另一种编码的文本。
# Converting -f (from) latin1 (ISO-8859-1)# -t (to) standard UTF_8iconv -f ISO
-8859-1 -t UTF
-8/ e. s A7 V! ?% h$ Z( O0 T k# c+ u6 F; k
< input.txt > output.txt
% ~' ~/ h9 p" B3 e X: s
可选参数:
- r4 f& w3 x" G$ a. \& s. I
iconv -l 列出所有已知的编码字符集合
% x. x7 V" A6 _8 A, h( K3 v# j* U/ Z
iconv -c 忽略不能转换的非法字符,静默地丢弃
0 M4 r2 C' e6 x: f) s5 p HEAD(用于显示文件的开头内容)
4 t, h$ I# o7 s' u D 如果你是一个频繁使用 Pandas 的用户,那么你会比较熟悉 df.head()。默认情况下 head 命令显示文件的前 10 行内容,当然我们也可以选择不同的参数确定打印的行数或字符数。
# Prints out first 10 lines- i* P/ _* Q0 d5 Q+ H
head filename.csv
# Print first 3 lineshead -n
33 m$ b5 }0 J- A# U6 Z
filename.csv
+ O; j: B% p# p* U 可选参数:
" [/ r3 l2 l: x/ `
head -n <数字> 打印特定数目的行数
) f, e4 y, s: N5 g" y
head -c <字符数> 打印特定数目的字符
7 z9 c) z/ O* c* N$ U s/ H TR(对字符进行替换、压缩和删除)
: i+ J+ m$ C- ]5 y( y& @7 ]# d3 I2 G8 P- @ tr 与转译比较类似,它的强大能力是文件清理的主要工具。例如以下交换文件中的分隔符:
# Converting a tab delimited file into commascat tab_delimited.txt | tr
"\\t" "," comma_delimited.csv
tr 的另一个功能是由我们控制的内置 [:class:] 参数,这些用法包括:
# R' F' M4 v% j1 Y9 W [:alnum:] 所有的字母和数字
4 j; l4 k3 c$ L+ C6 E
[:alpha:] 所有的字母
, S ~( G& d3 ~8 S% g/ `& A
[:blank:] 所有的水平空格
% _& M# ~ L5 q/ A7 J [:cntrl:] 所有的控制字符(非打印)
2 p4 D& I& x6 t3 [; P8 H [:digit:] 所有的数字
5 n- o9 ?; `: y% g: ^2 l
[:graph:] 所有的可打印字符,不包含空格
! J6 R" z, r# K/ ~: T' f9 P6 o
[:lower:] 所有的小写字母
& s5 t0 Y! n- s/ v8 q- X; w [:print:] 所有的可打印字符,包含空格
V+ j5 B% D8 p* y
[:punct:] 所有的标点符号
* ~9 y/ j: d$ L [:space:] 所有的水平或垂直空格
' I& u/ y% Y, m. o2 _! ^2 S g [:upper:] 所有的大写字母
- `5 [" q' n4 @- C
[:xdigit:] 所有的十六进制字符
& D6 k6 L G) J/ X; x
我们可以将它们连接在一起组成强大的程序。下面是一个基本的字数统计程序,我们可以用它来检查 README 文档。
cat README.md | tr
"[:punct:][:space:]" "\n" | tr
"[:upper:]" "[:lower:]"
; g, ~; w0 k+ {. W+ I | grep . | sort | uniq -c | sort -nr
; Y8 J: n h; [5 W% K8 H( _ 使用基本正则表达式的另一个例子是:
# Converting all upper case letters to lower casecat filename.csv | tr
[A-Z] [a-z]7 H/ A! k8 I# a
可选参数:
& C* w4 a1 X. Q8 x g( o
tr -d 删除字符
/ N z* C9 Y0 w, H8 Q0 R tr -s 压缩字符(将连续重复的字符用一个字符表示)
, M- m% a* ~ t6 ~* r \b 空格
) F) E7 n: G& x" x \f 换页符
0 @( b5 K% D! Z. M- f/ p \v 垂直制表符
# ` w/ w# D/ V% s \NNN 八进制字符 NNN
1 T! O5 o2 x$ q$ u' r
WC(用来计数的命令)
9 D4 A# Z: Y4 S$ t9 v 它的值主要来自于 -l flag,它会提供文档的行数。
# Will return number of lines in CSVwc -l gigantic_comma.csv
这个工具可以方便地确认各种命令的输出。所以,如果我们转换了文件中的分隔符,那么运行 wc -l 就可以查看总行数是不是相同,不同就是出了问题。
: V& J' k6 |, G8 Z! x& R
可选参数:
! I, Y" ~/ W; x0 E# w( d% { wc -c 打印 Bytes 数目
/ a2 N/ c$ _% |. ^% t wc -m 打印出字符数
0 ^3 J0 z6 \* M) D/ t- C
wc -L 打印出最长行的字符数
( g) n! y' @; C; N wc -w 打印出单词数目
9 J4 u" y- U ]& g2 k, c, m6 X6 W SPLIT(把一个大文件分割成小文件的命令)
1 A" Z! q( e+ R& Y" _- j8 `; ? 文件大小可以使用这个命令大幅度改变。根据任务的不同,分割文件可能会有所帮助,所以就有了 split 命令。split 的基本语法如下:
# We will split our CSV into new_filename every 500 linessplit -l
500+ v1 O" x/ R3 E6 V
filename.csv new_filename_
# filename.csv# ls output# new_filename_aaa# new_filename_aab# new_filename_aac
a9 h. t+ v5 F. c! K 两个怪异的地方是命名约定和文件的扩展名。后缀约定可以通过-d 标志来约定为数字。为了添加文件扩展名,您需要运行下面的 find 命令。它会改变当前路径下的所有文件名,给每个文件后面扩展.csv,所以,谨慎使用。
find . -type f -
exec mv
{} {}
2 U5 B, I- r% ~1 } .csv \;
# ls output# filename.csv.csv# new_filename_aaa.csv# new_filename_aab.csv# new_filename_aac.csv% P* C+ o/ P, A
可选参数:
; ~ Z" u1 ?1 L
split -b 通过确定的字节大小分割
3 _' {- Z6 z. W# Z9 J3 a2 A6 u% [& N split -a 生成长度为 N 的后缀
4 `+ a% |2 W6 w. M! y
split -x 使用十六进制后缀分割
% _( C( q9 S9 d6 S- H
SORT & UNIQ(sort:文件排序;uniq:报告或忽略文件中的重复行,与 sort 结合使用)
1 S+ {/ [$ i. B2 y$ w 这两个命令提供了唯一的单词计数,这是因为 uniq 仅仅在重复的相邻行上运行。因此,这就是在输出之前进行排序的原因。一个有趣的注意事项是:sort -u 会与 sort file.txt | uniq 有着相同的结果。
3 x2 Q0 ^4 L, }- b# p$ f$ M0 t1 p 对于数据科学家而言,排序具是一种潜在有用的能力:即基于特定列对整个 CSV 文件进行排序的能力。
# Sorting a CSV file by the second column alphabetically
. C ]. `/ E( ?" E- Y6 y) m; z sort -t, -k2 filename.csv
# Numerically# [3 Z* ?1 ^/ l8 T
sort -t, -k2n filename.csv
# Reverse ordersort -t, -k2nr filename.csv
这里的-t 选项将逗号作为我们的分隔符,通常会采用空格或者制表符。此外,-k flag 用于指定关键词。
, J! k3 {2 ^; p# } 可选参数:
$ K+ C! d, b1 e4 X0 `( n- R sort -f 忽略大小写
# y+ k7 e! |1 z( Z S sort -r 以相反的顺序排序
- ^/ X7 I. o: V
sort -R 乱序
: O; f! ~$ k* K+ e uniq -c 统计出现的次数
/ j8 p) O& A8 B+ ` uniq -d 仅仅打印重复行
# w/ Q" }, \# f! P
CUT(cut 命令用来显示行中的指定部分,删除文件中指定字段。)
. C0 h" }9 I1 ?2 ?
cut 用于删除列。举例来说,如果我们要删除第一列和第三列,可以使用 cut:
cut -d, -f
1,
3 filename.csv
选择除了第一列之外的每一列:
cut -d, -f
2- filename.csv
与其他命令结合使用的时候,cut 作为一个过滤器:
# Print first 10 lines of column 1 and 3, where "some_string_value" is presenthead filename.csv | grep
"some_string_value" | cut -d, -f
1,
3! _8 N+ L# b% Z0 x8 t" v
找到第二列中某个特定值出现的次数:
cat filename.csv | cut -d, -f
2& k; c( B7 m: r
| sort | uniq | wc -l
# Count occurences of unique values, limiting to first 10 resultscat filename.csv | cut -d, -f
2 | sort | uniq -c | head
PASTE(用于将多个文件按照列队列进行合并)
7 [4 `! ]. p( |8 l
paste 是一个简洁命令,具有一个有趣的功能。如果您有两个需要合并的文件,并且它们已经排序,paste 能够实现这些功能。
# names.txt
, a+ V, ~* O7 K C/ s8 y% L" n# e adam
( |" e7 }; b2 T6 V" r: Y& a! W, X john
& i( ?- e- I$ s" R3 z zach
# jobs.txt8 R* f. B1 [( j
lawyer
, l( m" u4 o5 h# ?+ |) [
youtuber
, R3 ]& K4 T1 s2 L" S6 T( t. Y7 d; r
developer
# Join the two into a CSVpaste -d
,' z, P3 G) H$ u! n1 ]! b& A/ }
names.txt jobs.txt > person_data.txt
# Output$ a+ z% ?; u8 t: B. R( Q0 R- ?
adam,lawyer
. F8 _7 ?/ L# n. ] john,youtuber
zach,developer
更具 SQL 风格的变体,请参见下文。
1 l3 |- U K8 X) x i; N! F! n8 F JOIN(连接并合并文件)
) B% A9 T0 w5 K5 W" N
join 命令是一个简单的、拟正切的 SQL。最大的区别在于 join 将返回所有列,并且只能在一个字段上进行匹配。默认情况下,join 将尝试使用第一列作为匹配键。对于不同的结果,必须使用以下语法:
# Join the first file (-1) by the second column# and the second file (-2) by the firstjoin -t,
-1 2 -2 1
- ^4 x+ c" r; V- {& F2 k& z$ Z first_file.txt second_file.txt
+ z8 w1 u6 g7 D! K! A# p# A9 T; B
标准 join 是内部连接。但是,外部连接也可以通过- a flag 实现。另一个值得注意的现象是- e 标志,如果找到丢失的字段,它可以用来替换值。
# Outer join, replace blanks with NULL in columns 1 and 2# -o which fields to substitute - 0 is key, 1.1 is first column, etc...join -t,
-1 2 -a
1 -a2 -e
NULL -o
0,1.1,2.2 first_file.txt second_file.txt
虽然不是最便于用户使用的命令,但是绝望的时候自有绝望的措施。
, |2 h" X# b, b. l
可选参数:
; ?) {) O# k& I, w2 N' E) J( f; G join -a 打印不能匹配的行
. q5 _( A: Q1 ]/ w- E1 [
join -e 替换丢失的输入字段
9 \) T2 i+ O$ z0 t1 U3 x5 t$ Y join -j 等价于 -1 FIELD -2 FIELD
! U1 @4 p$ r, p3 ^1 _7 `- u
GREP(这是一种强大的文本搜索工具)
! y6 _7 S, P: Y f8 Y. ^; g 全面搜索正则表达式并打印(grep),这很可能是最出名的命令。grep 有很多强大的能力,尤其是在大型代码库中以我们自己的方式寻找字段。在数据科学领域,它充当着其它命令的细化机制。
# Recursively search and list all files in directory containing wordgrep -lr
word& c& V ^( }0 ~9 X
.
# List number of files containing wordgrep -lr
word . | wc -l
统计包含单词/模式的总行数
grep -c
some_value! M4 s; ^0 Z8 b$ J
filename.csv
# Same thing, but in all files in current directory by file namegrep -c
some_value *
使用\|运算子进行多值操作
grep
"first_value\|second_value" filename.csv
可选参数:
0 S/ |' v6 o7 ]! i. u+ x alias grep="grep --color=auto" 使 grep 色彩化
u$ Y8 B0 V7 ~7 X" H/ U
grep -E 使用扩展的正则表达式
. m# W) A% t+ i- |* D1 ^3 @; z& L grep -w 只匹配全字符
- c8 b; i; h# R ]: \ grep -l 打印出匹配的文件名
$ U: P g, L4 o" M& `1 j9 [ grep -v 反转匹配
& b1 [5 O. y5 O) F+ ]
SED(流编辑器)
! U" W+ p. y; o3 [$ b# V; I
sed 是一个逐行运行的流编辑器。它擅长替换,但是也可以用于所有的重构(refactoring)。
5 H$ }" B# z4 f+ a 最基本的 sed 命令包含 s/old/new/g。这指的是搜索旧值,并用新值替换。如果没有/gour 命令,终端将在第一次出现这个值之后停止。
; I9 l) @! B$ U" } 为了快速体验这种能力,让我们来举个例子。若我们有以下文件:
9 Y7 W, R; s4 Y0 P: O0 Z
balance,name
$
1,
000
1 T! L; ^) U7 I( m7 P ,john
$
2,
000,jack
我们想做的第一件事就是去掉美元符号。-i flag 指的是位置,标志指的是零长度的文件扩展名,然后覆盖初始文件。理想情况下,我们可以单独测试其中的每一个,然后输出到新文件。
sed -i
s/\$//g' `$ S" Q0 O% j$ R2 H5 X- H
data.txt
# balance,name# 1,000,john# 2,000,jack
8 N6 I3 ~2 J5 M* q! ] 接下来,我们处理 balance 中的逗号
sed -i
s/\([0-9]\),\([0-9]\)/\1\2/g! w8 Q! v$ o0 N$ P+ r+ n# ^, a1 J" i6 K
data.txt
# balance,name# 1000,john# 2000,jack
) m' S) n1 E3 g/ W AWK(不仅仅是一个命令)
j% F) [4 L; S. {. W: ~6 h1 X, o
awk 不仅仅是一个简单的命令:它是一种成熟的语言。在本文所涉及的所有内容中,awk 是最酷的。如果你发现自己对 awk 印象深刻,也可以找更多的资源。
' S6 V. c- L2 ?5 r5 k awk 的用例包括:
/ s* L# P% U6 [5 U ? 文本处理
0 ~/ v$ ?5 w9 X7 i; L5 C
格式化文本报告
/ B3 g& `) {* R- {; N' Q 执行数学运算
) ^" g) |4 U' x$ ]; ?* g4 N 执行字符串操作
$ T2 ^: c c2 i# `2 D& | 最新版的 awk 可以与 grep 并行使用。
awk
/word/ filename.csv
或者使用一些技巧将 grep 和 cut 结合起来。这里,对于所有我们要查找的 word 行,awk 打印第三列和第四列和分隔符。-F,仅将分隔符改为逗号。
awk -F,
/word/ { print $3 "\t" $4 } filename.csv
awk 内置了许多优秀的变量。例如,NF -字段数,NR -记录数。要在文件中获取第五十三条记录,代码如下:
awk -F,
NR == 53 filename.csv
一个额外的功能是基于一个或多个值进行过滤的能力。下面的第一个示例将打印第一列等于 string 记录的行数和列数。
awk -F,
$1 == "string" { print NR, $0 }
5 _! o5 H9 e7 c) E9 k0 E0 K9 g filename.csv
# Filter based off of numerical value in second columnawk -F,
$2 == 1000 { print NR, $0 } filename.csv
多数值表达式:
# Print line number and columns where column three greater# than 2005 and column five less than one thousandawk -F,
$3 >= 2005 && $5 <= 1000 { print NR, $0 } filename.csv
对第三列求和:
awk -F,
{ x+=$3 } END { print x } filename.csv
对第一列等于『something』的所有行,对它们的第三列求和。
awk -F,
$1 == "something" { x+=$3 } END { print x } filename.csv
得到文件的维度:
awk -F,
END { print NF, NR }# M K" N8 s! b/ o' ]3 d
filename.csv
# Prettier versionawk -F,
BEGIN { print "COLUMNS", "ROWS" }; END { print NF, NR } filename.csv
打印出现两次的行:
awk -F,
++seen[$0] == 2 filename.csv
删除重复的行:
# Consecutive linesawk
a !~ $0; {a=$0}
# b+ }: K: s& ` [" q g( F, i. ` ]
# Nonconsecutive linesawk
! a[$0]++
( y# A# D5 U! X F filename.csv
# More efficientawk
4 l4 S' `+ F2 O5 B) P2 w !($0 in a) {a[$0];print}
6 a$ S: u; d& Z. l/ X* S0 `& x
; ]9 X0 u' k; k3 _) q' L- e+ Q2 R 使用内置函数 gsub() 替换多值:
awk
{gsub(/scarlet|ruby|puce/, "red"); print}& w9 q# p+ z8 K" l% H/ y$ M
这个 awk 命令将合并多个 CSV 文件,忽略文件头,然后将其附加到末尾。
awk
FNR==1 && NR!=1{next;}{print} *.csv > final_file.csv
需要缩减大量文件?awk 可以在 sed 的帮助下处理这个问题。具体而言,这个命令可以基于行数将 一个大文件拆分为多个小文件。
sed
1d;$d filename.csv | awk
NR%NUMBER_OF_LINES==1{x="filename-"++i".csv";}{print > x}# Example: splitting big_data.csv into data_(n).csv every 100,000 linessed
1d;$d big_data.csv | awk
NR%100000==1{x="data_"++i".csv";}{print > x}) g6 C( o& L( g+ {6 y
结语
; e( G4 B$ k* |# ?6 N$ W 命令行拥有无穷无尽的能力。本文中介绍的命令足以让您在短时间内从小白变成高手。除了这些内容之外,还有许多用于日常数据处理的程序需要考虑。如果你想深入了解命令行数据科学,可以多找一些详细的资源。
% n3 Q/ @" J4 S& a+ w4 }9 V2 i) j 原文链接:
$ y ?5 H: ?3 E1 `( \( Q
https://medium.com/@kadek/command-line-tricks-for-data-scientists-c98e0abe5da
$ s& W& V7 [0 E2 W+ T% v# c 本文为机器之心编译,转载请联系本公众号获得授权。
) W' i2 E8 m# V( { ✄------------------------------------------------
9 `! N- {: u: ? 加入机器之心(全职记者/实习生):hr@jiqizhixin.com
. p0 q) H& g$ G- g9 C! o
投稿或寻求报道:content@jiqizhixin.com
% B+ A& r% s( Y& [% h3 {" j 广告&商务合作:bd@jiqizhixin.com
$ v. ]/ ~6 D9 T9 \, a1 b0 b