對於許多資料科學家來說,資料操作從始至終就是 Pandas 或 Tidyverse。從理論上講,這樣做沒有任何問題。畢竟,這就是這些工具存在的原因。然而,對於像分隔符轉換這樣的簡單任務,這些工具是大材小用了。
立志掌握命令列應該在每個開發人員的學習清單上,特別是資料科學家。學習 shell 的來龍去脈將無可否認地提高你的生產力。除此之外,命令列還是計算領域的一個重要歷史課程。例如,awk —— 一種資料驅動的指令碼語言。1977 年,在 Brain Kernighan(即傳奇的 K&R 書中 K)的幫助下,awk 首次出現。今天,大約五十年過去了,awk 仍然活躍在每年新出版的書裡面。因此,可以安全地假設對命令列魔法的付出不會很快貶值。
檔案編碼可能會很棘手。現在大部分檔案都是 UTF-8 編碼的。要了解 UTF-8 背後的一些魔力,請檢視這個出色的視訊。儘管如此,有時我們收到的檔案不是這種編碼。這可能引起對改變編碼模式的一些胡亂嘗試。這裡,iconv
是一個拯救者。iconv
是一個簡單的程式,它將獲取採用一種編碼的文字並輸出採用另一種編碼的文字。
# Converting -f (from) latin1 (ISO-8859-1)# -t (to) standard UTF_8iconv -f ISO-8859-1 -t UTF-8 < input.txt > output.txt
實用選項:
iconv -l
列出所有已知編碼iconv -c
默默丟棄無法轉換的字元如果你是一個 Pandas 重度使用者,那麼會很熟悉 head
。通常在處理新資料時,我們想做的第一件事就是了解其內容。這就得啟動 Pandas,讀取資料然後呼叫 df.head()
—— 要說這有點費勁。沒有任何選項的 head
將列印出檔案的前 10 行。head
的真正力量在於乾淨利落的測試操作。例如,如果我們想將檔案的分隔符從逗號更改為管道。一個快速測試將是:head mydata.csv | sed 's/,/|/g'
。
# Prints out first 10 lineshead filename.csv# Print first 3 lineshead -n 3 filename.csv
實用選項:
head -n
列印特定行數head -c
列印特定位元組數tr
類似於翻譯。這個功能強大的實用程式是檔案基礎清理的主力。理想的用例是替換檔案中的分隔符。
# Converting a tab delimited file into commascat tab_delimited.txt | tr "\t" "," comma_delimited.csv
tr
另一個功能是你可以用內建 [:class:]
變數(POSIX 字元類)發揮威力。這些包括了:
[:alnum:]
所有字母和數位[:alpha:]
所有字母[:blank:]
所有水平空白[:cntrl:]
所有控制字元[:digit:]
所有數位[:graph:]
所有可列印字元,但不包括空格[:lower:]
所有小寫字母[:print:]
所有可列印字元,包括空格[:punct:]
所有標點符號[:space:]
所有水平或垂直空白[:upper:]
所有大寫字母[:xdigit:]
所有 16 進位制數位你可以將這些連線在一起以組成強大的程式。以下是一個基本的字數統計程式,可用於檢查 README 是否被濫用。
cat README.md | tr "[:punct:][:space:]" "\n" | tr "[:upper:]" "[:lower:]" | grep . | sort | uniq -c | sort -nr
另一個使用基本正規表示式的例子:
# Converting all upper case letters to lower casecat filename.csv | tr '[A-Z]' '[a-z]'
實用選項:
tr -d
刪除字元tr -s
壓縮字元\b
退格\f
換頁\v
垂直製表符\NNN
八進位制字元單詞計數。它的價值主要來自其 -l
選項,它會給你提供行數。
# Will return number of lines in CSVwc -l gigantic_comma.csv
這個工具可以方便地確認各種命令的輸出。所以,如果我們在轉換檔案中的分隔符之後執行 wc -l
,我們會期待總行數是一樣的,如果不一致,我們就知道有地方出錯了。
實用選項:
wc -c
列印位元組數wc -m
列印字元數wc -L
列印最長行的長度wc -w
列印單詞數量檔案大小的範圍可以很廣。對於有的任務,拆分檔案或許是有好處的,所以使用 split
吧。split
的基本語法是:
# We will split our CSV into new_filename every 500 linessplit -l 500 filename.csv new_filename_# filename.csv# ls output# new_filename_aaa# new_filename_aab# new_filename_aa
它有兩個奇怪的地方是命名約定和缺少副檔名。字尾約定可以通過 -d
標誌變為數位。要新增副檔名,你需要執行以下 find
命令。它將通過附加 .csv
擴充套件名來更改當前目錄中所有檔案的名稱,所以小心了。
find . -type f -exec mv '{}' '{}'.csv \;# ls output# filename.csv.csv# new_filename_aaa.csv# new_filename_aab.csv# new_filename_aac.csv
實用選項:
split -b N
按特定位元組大小分割split -a N
生成長度為 N 的字尾split -x
使用十六進位制字尾上面兩個命令很明顯:它們的作用就是字面意思。這兩者結合起來可以提供最強大的衝擊 (例如,唯一單詞的數量)。這是由於 uniq
只作用於重複的相鄰行。這也是在輸出前進行 sort
的原因。一個有趣的事情是 sort -u
會達到和典型的 sort file.txt | uniq
模式一樣的結果。
sort
對資料科學家來說確實具有潛在的有用能力:能夠根據特定列對整個 CSV 進行排序。
# Sorting a CSV file by the second column alphabeticallysort -t"," -k2,2 filename.csv# Numericallysort -t"," -k2n,2 filename.csv# Reverse ordersort -t"," -k2nr,2 filename.csv
這裡的 -t
選項將逗號指定為分隔符,通常假設分隔符是空格或製表符。此外,-k
選項是為了確定我們的鍵。這裡的語法是 -km,n
,m
作為開始列,n
作為結束列。
實用選項:
sort -f
忽略大小寫sort -r
反向排序sort -R
亂序uniq -c
統計出現次數uniq -d
只列印重複行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
查出第二列中唯一值的數量。
cat filename.csv | cut -d, -f 2 | 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
是一個帶有趣味性功能的特定命令。如果你有兩個需要合併的檔案,並且它們已經排序好了,paste
幫你解決了接下來的步驟。
# names.txtadamjohnzach# jobs.txtlawyeryoutuberdeveloper# Join the two into a CSVpaste -d ',' names.txt jobs.txt > person_data.txt# Outputadam,lawyerjohn,youtuberzach,developer
更多 SQL 式變種,見下文。
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 first_file.txt second_file.txt
標準的 join
是內連線。然而,外連線通過 -a
選項也是可行的。另一個值得一提的技巧是 -q
標誌,如果發現有缺失的欄位,可用於替換值。
# 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
它不是最使用者友好的命令,而是絕望時刻的絕望措施。
實用選項:
join -a
列印不可配對的行join -e
替換丟失的輸入欄位join -j
相當於 -1 FIELD -2 FIELD
grep
即 用正規表示式全域性搜尋並且列印,可能是最有名的命令,並且名副其實。grep
很強大,特別適合在大型程式碼庫中查詢。在資料科學的王國裡,它充當其他命令的提煉機制。雖然它的標準用途也很有價值。
# Recursively search and list all files in directory containing 'word'grep -lr 'word' .# List number of files containing wordgrep -lr 'word' . | wc -l
計算包含單詞或模式的總行數。
grep -c 'some_value' filename.csv# Same thing, but in all files in current directory by file namegrep -c 'some_value' *
對多個值使用“或”運算子: \|
。
grep "first_value\|second_value" filename.csv
實用選項:
alias grep="grep --color=auto"
使 grep 色彩豐富grep -E
使用擴充套件正規表示式grep -w
只匹配整個單詞grep -l
列印匹配的檔名grep -v
非匹配sed
和 awk
是本文中最強大的兩個命令。為簡潔起見,我不打算詳細討論這兩個命令。相反,我將介紹各種能證明其令人印象深刻的力量的命令。如果你想了解更多,這兒就有一本書是關於它們的。
sed
本質上是一個流編輯器。它擅長替換,但也可以用於所有輸出重構。
最基本的 sed
命令由 s/old/new/g
組成。它的意思是搜尋 old
,全域性替換為 new
。 如果沒有 /g
,我們的命令將在 old
第一次出現後終止。
為了快速了解它的功能,我們可以深入了解一個例子。 在以下情景中,你已有以下檔案:
balance,name$1,000,john$2,000,jack
我們可能想要做的第一件事是刪除美元符號。-i
標誌表示原位。''
表示零長度副檔名,從而覆蓋我們的初始檔案。理想情況下,你可以單獨測試,然後輸出到新檔案。
sed -i '' 's/\$//g' data.txt# balance,name# 1,000,john# 2,000,jack
接下來,去除 blance
列的逗號。
sed -i '' 's/\([0-9]\),\([0-9]\)/\1\2/g' data.txt# balance,name# 1000,john# 2000,jack
最後 jack 有一天決定辭職。所以,再見了,我的朋友。
sed -i '' '/jack/d' data.txt# balance,name# 1000,john
正如你所看到的,sed
有很多強大的功能,但樂趣並不止於此。
最好的留在最後。awk
不僅僅是一個簡單的命令:它是一個成熟的語言。在本文中涉及的所有內容中,awk
是目前為止最酷的。如果你感興趣,這裡有很多很棒的資源 —— 看 這裡、這裡 和 這裡。
awk
的常見用例包括:
awk
可以以最原生的形式並行 grep
。
awk '/word/' filename.csv
或者更加神奇:將 grep
和 cut
組合起來。在這裡,對於所有帶我們指定單詞 word
的行,awk
列印第三和第四列,用 tab
分隔。-F,
用於指定切分時的列分隔符為逗號。
awk -F, '/word/ { print $3 "\t" $4 }' filename.csv
awk
內建了許多精巧的變數。比如,NF
—— 欄位數,和 NR
—— 記錄數。要獲取檔案中的第 53 條記錄:
awk -F, 'NR == 53' filename.csv
更多的花招是其基於一個或多個值進行過濾的能力。下面的第一個範例將列印第一列等於給定字串的行的行號和列。
awk -F, ' $1 == "string" { print NR, $0 } ' 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 }' 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}']# Nonconsecutive linesawk '! a[$0]++' filename.csv# More efficientawk '!($0 in a) {a[$0];print}
使用內建函數 gsub()
替換多個值。
awk '{gsub(/scarlet|ruby|puce/, "red"); print}'
這個 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}'
命令列擁有無窮無盡的力量。本文中介紹的命令足以將你從一無所知提升到英雄人物。除了涵蓋的內容之外,還有許多實用程式可以考慮用於日常資料操作。Csvkit、xsv 還有 q 是需要記住的三個。如果你希望更深入地了解命令列資料科學,檢視這本書。它也可以免費線上獲得!