摘要:欲練資料神功,必先揮刀……,嗯,先紮好馬步吧!編寫SQL語句,是資料統計分析最基本的能力了。覺得SQL的自定義功能太弱了,或者你覺得就算是Hive呼叫外部指令碼也麻煩了,那麼我們上當前最熱的Spark
00 引言
2016就要來了,避不及,躲不開。新一年來之前,還是有一件值得高興的事情,那便是年終獎了。
公司大了,什麼樣的人都有。嗯嗯……,說錯了,是人大了,什麼樣的公司都進。嗯嗯……,還是不對。是公司大了,員工多了,要統計每個員工每年寫的程式碼數量。以此來分配年終獎了。
假設有如下資料示例,第一列為員工的ID,第二列為年份,第三列為程式碼數。
126882,2005,5
126882,2013,16
127305,2010,2
127305,2014,29
128194,2012,1
128194,2013,161
欲練資料神功,必先揮刀……,嗯,先紮好馬步吧。下面的7個步驟,是練好資料基本功的一些方法。
01 MySQL版本
編寫SQL語句,是資料統計分析最基本的能力了。一般統計分析中,掌握好join語句分析即可,其它資料庫相關的備份、恢復、儲存過程之類的,基本上很少用到。
用一個join來實現需求,取出資料:
select a.*
from table as a join table as b on a.id=b.id
where a.count>b.count;
本例中是自己和自己join,最簡單的inner join,將兩個表中按id相同的行進行關聯起來,最後按條件進行篩選需要的資料即可。需要注意,MySQL中的臨時表,是沒有辦法自己join自己的。
02 Bash版本
我們通常會以SQL為常用工具,如果SQL不能滿足你的需求,或者實現起來比較麻煩,你可以匯出成csv格式的文字檔案。
Shell命令比通常想像的要強大,用好Shell命令,很多時候也可以方便的處理問題。
join -t',' -1 1 -2 1 data.csv data.csv | awk -F',' '$3>$5{print $1,$4,$5}'
這條命令看起來估計算最簡潔了的,但還是用了兩個命令。join也是一個非常神奇的命令,可以完成關聯式資料庫的join功能,包括left join,right join,outer join,inner join。
本例中指定了連線的欄位,引數“-1 1 -2 1”分別指定第一個、第二個檔案的第1個欄位作為關聯欄位。將連線的資料使用awk進行簡單的過濾處理。join命令相當於SQL中的join的功能,而附帶的awk相當於SQL中的where條件,進行資料篩選。
03 Awk版本
在shell命令列下,還有一個強大的資料處理工具:AWK,強大到能獨立完成很多資料處理和分析的任務,後面會有單獨的篇章來介紹,請持續關注。
#!/usr/local/bin/awk -f
BEGIN{FS=","}
{
id = int($1)
year = int($2)
count = int($3)
print id,year,count
if(yc[id]["count"] < count){
yc[id]["year"] = year
yc[id]["count"] = count
}
}
END{
for(x in yc){
printf("%s,%s,%s\n", x, yc[x]["year"], yc[x]["count"])
}
}
上面一段簡單的Awk程式碼,把Awk的一些基本概念都用上了。也算是“麻雀雖小,五臟俱全”了。涉及Awk的三段式程式碼結構,陣列與賦值,條件判斷與迴圈等程式設計基礎概念。
因為awk是按行讀入檔案,因此我們的思想就是將當前的最大的值儲存起來,再讀入下一行,如果比當前最大值大,就更新,否則繼續讀入一行。
處理檔案檔案的方式,自然與資料庫的join思想不一樣,但你需要習慣這種方式,因為這種處理檔案檔案的方式,也是很多NoSQL的處理方式。
04 Python版本
前面幾篇文章都安利了Python,處理這種簡單的統計,我們也可以用Python來試試。
import sys
last_id = None
for line in sys.stdin:
idx, year, count = line.strip().split(',')
if idx == last_id:
if count > most_count:
most_year, most_count = year, count
else:
if last_id:
print '%s,%s,%s' %(last_id, most_year, most_count)
last_id = idx
most_count = 0
if idx == last_id:
print '%s,%s,%s' %(last_id, most_year, most_count)
處理的方式還是一樣,按行讀取檔案並儲存和記錄,但邏輯實現起來感覺稍微有點繞而已。沒有用陣列或字典之類的來儲存資料。
還需要注意,這個程式是需要對檔案進行按id排序的,因為程式碼處理的是連續的行,並且假定相同的id是在連續的行上。
當然,你肯定會說,這個程式碼寫得有些雜亂,不符合通常的思路。之所以寫成這樣,是因為我們後面在分散式環境中還要用。
05 Hive版本
也許你會想,如果檔案很大,很大,很大(重要的說三遍嗎?),那麼如何處理呢?馬上就2016了,那麼你聽過安利嗎?哦不對,是大資料,一個已經被說到爛透了的詞。單機不能滿足你的需求,那麼使用分散式。
假設Facebook有20億使用者,統計每個使用者在每天中,各自發的訊息的最多的那天和傳送的條數,假設所有使用者,每天都發訊息。20億使用者,按Facebook上線10年算,3600天,共72000億條記錄,夠大了吧!分別找出每個使用者發訊息最多的那天和發訊息的次數。
且來看看,由Facebook開源出來的Hive資料倉儲,如何處理!
-- 見MySQL版本
你沒有看錯,我也沒有騙你,還真是和MySQL用同樣的程式碼。當然,Hive有自己的優化之類的,暫時先不管。
這個地方,有個前提,你只需要把那72000億條資料,存放到HDFS檔案系統上,然後建立一個外部表和HDFS檔案進行關聯,然後輸入和MySQL同樣的語句,Hive引擎會自然將SQL語句轉換為下層的map-reduce程式碼執行。
重要的是,你的Hadoop叢集有多強大,這個Hive語句就能達到多強大。還不用自己寫map-reduce程式,就是分析師最熟悉的SQL語句。
如果你覺得Hive也是SQL語句,有些自定義的函式或者方法比較麻煩,那麼Hive還可以呼叫外部的指令碼,只要是可執行指令碼都行:python、ruby、bash、scala、java、lisp隨便你愛好。
06 Spark版本
如果你覺得用Hive太Low了,跟不上時代的步伐了。或者,你覺得SQL的自定義功能太弱了,或者你覺得就算是呼叫外部指令碼也很麻煩,那麼我們上當前最熱的Spark。
from pyspark import SparkContext
sc = SparkContext()
data = sc.textFile('data.csv')
data = data.map(lambda x: x.split(',')).map(lambda x: (x[0], (x[1], int(x[2])))).groupByKey().mapValues(lambda value: sorted(value, lambda x, y: cmp(x[1], y[1]), reverse=True)[0])
for item in data.collect():
print '%s,%s,%s' % (item[0], item[1][0], item[1][1])
Spark支援幾種程式設計介面,Scala、Java、Python,最近也開始支援R了。
上面雖然連續用了好幾個map,但原理卻非常簡單,和python的map功能類似。唯一用了一個groupByKey功能,將相同的id聚合在一起,剩下的屬性放在一個列表裡面。對這個列表進行排序,取count最多的次數和年份,最後輸出。
邏輯夠簡單,程式碼也夠簡潔。Spark強大的便利利益於Scala強大的資料結構與資料處理能力。
07 map-reduce版本
如果你追求完全的原生,或者追求完全的可控性。但又不熟悉Java程式碼,那麼還是可以用Python來寫map-reduce程式。
# mapper.py見python版本
# reducer.py見python版本
又一個大騙子!
通過Hadoop的Streaming介面來進行呼叫,只需要自定義mapper和reducer程式即可。上面的mapper和reducer可以直接用純粹Python的單機版本。
輸入是一些id,year,count行,輸出還是同樣的資料結構。只是在程式中,把當前這個程式的輸入中,每個id最多的count和year找出來了。資料量已經減少了,每個id只會保留最大的一條資料。
分散式最基本的原理就是資料分塊,在map階段,對每個塊的資料呼叫mapper程式,求出當前塊裡面每個id的最大count和year找出來。把這些輸出作為reducer的輸入,再求一次最大值,那麼找出的便是全域性的最大值。
08 結尾
資料分析基本功,按上面七個方面,紮好了馬步,離資料神功第一層也不遠了。
不要興奮,也許會突然冒出來一個小姑娘,告訴你說:切,上面的功能我用Excel也可以完美的實現。
當然可以了,聰明如你,Excel還能實現比這強大得多的功能。
理論上來說,上面所有工具都能完成任何統計分析需求。只是不同的地方,實現的方式各有不同,有的複雜,有的簡單,有的快,有的慢而已。選擇你覺得最簡單的方式,搞定任務即可。