(7)在多維度組合下,如何計算某個維度組合裏的不重複記錄的條數
以數據文件 c.txt 爲例:
1
2
3
4
5
6
7
|
[root@localhost pig]$
cat c.txt a 1 2 3 4.2 9.8 100 a 3 0 5 3.5 2.1 200 b 7 9 9 - - 300 a 7 9 9 2.6 6.2 300 a 1 2 5 7.7 5.9 200 a 1 2 3 1.4 0.2 500 |
問題:如何計算在第2、3、4列的所有維度組合下,最後一列不重複的記錄分別有多少條?例如,第2、3、4列有一個維度組合是(1,2,3),在這個維度維度下,最後一列有兩種值:100 和 500,因此不重複的記錄數爲2。同理可求得其他的記錄條數。
pig代碼及輸出結果如下:
1
2
3
4
5
6
7
8
|
grunt> A = LOAD
'c.txt' AS (col1:chararray, col2:int, col3:int, col4:int, col5:double, col6:double, col7:int); grunt> B = GROUP A BY (col2, col3, col4); grunt> C = FOREACH B {D = DISTINCT A.col7; GENERATE group, COUNT(D);}; grunt> DUMP C; ((1,2,3),2) ((1,2,5),1) ((3,0,5),1) ((7,9,9),1) |
我們來看看每一步分別生成了什麼樣的數據:
①LOAD不用說了,就是加載數據;
②GROUP也不用說了,和前文所說的一樣。GROUP之後得到了這樣的數據:
1
2
3
4
5
|
grunt> DUMP B; ((1,2,3),{(a,1,2,3,4.2,9.8,100),(a,1,2,3,1.4,0.2,500)}) ((1,2,5),{(a,1,2,5,7.7,5.9,200)}) ((3,0,5),{(a,3,0,5,3.5,2.1,200)}) ((7,9,9),{(b,7,9,9,,,300),(a,7,9,9,2.6,6.2,300)}) |
其實到這裏,我們肉眼就可以看出來最後要求的結果是什麼了,當然,必須要由pig代碼來完成,要不然怎麼應對海量數據?
文章來源:http://www.codelast.com/
③這裏的 FOREACH 與前面有點不一樣,第一次看到這種寫法,肯定會覺得很奇怪。先看一下用於去重的DISTINCT關鍵字的說明:
Removes duplicate tuples in a relation.
然後再解釋一下:FOREACH 是對B的每一行進行遍歷,其中,B的每一行裏含有一個包(bag),每一個包中含有若干元組(tuple)A,因此,FOREACH 後面的大括號裏的操作,其實是對所謂的“內部包”(inner bag)的操作(詳情請參看FOREACH的說明),在這裏,我們指定了對A的col7這一列進行去重,去重的結果被命名爲D,然後再對D計數(COUNT),就得到了我們想要的結果。
④輸出結果數據,與前文所述的差不多。
這樣就達成了我們的目的。從總體上說,剛接觸pig不久的人會覺得這些寫法怪怪的,就是扭不過來,但是要堅持,時間長了,連倒影也會讓你覺得是正的了。
(8)如何將關係(relation)轉換爲標量(scalar)
在前文中,我們要統計符合某些條件的數據的條數,使用了COUNT函數來計算,但在COUNT之後,我們得到的還是一個關係(relation),而不是一個標量的數字,如何把一個關係轉換爲標量,從而可以在後續處理中便於使用呢?
具體請看這個鏈接。
(9)pig中如何使用shell進行輔助數據處理
pig中可以嵌套使用shell進行輔助處理,下面,就以一個實際的例子來說明。
假設我們在某一步pig處理後,得到了類似於下面 b.txt 中的數據:
1
2
3
4
|
[root@localhost pig]$
cat b.txt 1 5 98 = 7 34 8 6 3 2 62 0 6 = 65 |
問題:如何將數據中第4列中的“=”符號全部替換爲9999?
pig代碼及輸出結果如下:
1
2
3
4
5
6
|
grunt> A = LOAD
'b.txt' AS (col1:int, col2:int, col3:int, col4:chararray, col5:int); grunt> B = STREAM A THROUGH ` awk
'{if($4 == "=") print $1"\t"$2"\t"$3"\t9999\t"$5; else print $0}' `; grunt> DUMP B; (1,5,98,9999,7) (34,8,6,3,2) (62,0,6,9999,65) |
我們來看看這段代碼是如何做到的:
①加載數據,這個沒什麼好說的。
②通過“STREAM … THROUGH …”的方式,我們可以調用一個shell語句,用該shell語句對A的每一行數據進行處理。此處的shell邏輯爲:當某一行數據的第4列爲“=”符號時,將其替換爲“9999”;否則就照原樣輸出這一行。
③輸出B,可見結果正確。
(10)向pig腳本中傳入參數
假設你的pig腳本輸出的文件是通過外部參數指定的,則此參數不能寫死,需要傳入。在pig中,使用傳入的參數如下所示:
1
|
STORE A INTO
'$output_dir' ; |
則這個“output_dir”就是個傳入的參數。在調用這個pig腳本的shell腳本中,我們可以這樣傳入參數:
1
|
pig -param output_dir= "/home/my_ourput_dir/"
my_pig_script.pig |
這裏傳入的參數“output_dir”的值爲“/home/my_output_dir/”。
文章來源:http://www.codelast.com/
(11)就算是同樣一段pig代碼,多次計算所得的結果也有可能是不同的
例如用AVG函數來計算平均值時,同樣一段pig代碼,多次計算所得的結果中,小數點的最後幾位也有可能是不相同的(當然也有可能相同),大概是因爲精度的原因吧。不過,一般來說小數點的最後幾位已經不重要了。例如我對一個數據集進行處理後,小數點後13位纔開始有不同,這樣的精度完全足夠了。
(12)如何編寫及使用自定義函數(UDF)
首先給出一個鏈接:Pig 0.8.1 API,還有Pig UDF Manual。這兩個文檔能提供很多有用的參考。
自定義函數有何用?這裏以一個極其簡單的例子來說明一下:
假設你有如下數據:
1
2
3
4
5
|
[root@localhost pig]$ cat a.txt uidk 12 3 hfd 132 99 bbN 463 231 UFD 13 10 |
現在你要將第二列的值先+500,再-300,然後再÷2.6,那麼我們可以這樣寫:
1
2
3
4
5
6
7
|
grunt> A = LOAD
'a.txt' AS (col1:chararray, col2: double , col3: int ); grunt> B = FOREACH A GENERATE col1, (col2 + 500 - 300)/2.6, col3; grunt> DUMP B; (uidk,81.53846153846153,3) (hfd,127.6923076923077,99) (bbN,255.0,231) (UFD,81.92307692307692,10) |
我們看到,對第二列進行了 (col2 + 500 – 300)/2.6 這樣的計算。麻煩不?或許這點小意思沒什麼。但是,如果有比這複雜得多的處理,每次你需要輸入多少pig代碼呢?我們希望有這樣一個函數,可以讓第二行pig代碼簡化如下:
1
|
grunt> B = FOREACH A GENERATE col1, com.codelast.MyUDF(col2), col3; |
這樣的話,對於我們經常使用的操作,豈不是很方便?
pig的UDF(user-defined function)就是拿來做這個的。
文章來源:http://www.codelast.com/
下面,就以IntelliJ這個IDE爲例(其實用什麼IDE倒無所謂,大同小異吧),說明我們如何實現這樣一個功能。
①新建一個新工程,在工程下創建“lib”目錄,然後把pig安裝包中的“pig-0.8.1-core.jar”文件放置到此lib目錄下,然後在“Project Structure→Libraries”下添加(點擊“+”號)一個庫,就命名爲“lib”,然後點擊右側的“Attach Classes”按鈕,選擇pig-0.8.1-core.jar文件,再點擊下方的“Apply”按鈕應用此更改。這樣做之後,你就可以在IDE的編輯器中實現輸入代碼時看到智能提示了。
此外,你還需要用同樣的方法,將一堆Hadoop的jar包添加到工程中,包括以下文件:
1
2
3
4
5
|
hadoop-XXX-ant.jar hadoop-XXX-core.jar hadoop-XXX-examples.jar hadoop-XXX- test .jar hadoop-XXX-tools.jar |
其中,XXX是版本號。
如果沒有這些文件,你在編譯jar包的時候會報錯。
文章來源:http://www.codelast.com/
②跟我一起,在工程目錄下的 src/com/coldelast/ 目錄下創建Java源代碼文件 MyUDF.java,其內容如下:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package
com.codelast; import
java.io.IOException; import
org.apache.pig.EvalFunc; import
org.apache.pig.data.Tuple; /** * Author: Darran Zhang @ codelast.com * Date: 2011-09-29 */ public
class MyUDF extends
EvalFunc<Double> { @Override public
Double exec(Tuple input) throws
IOException { if
(input == null
|| input.size() == 0 ) { return
null ; } try
{ Double val = (Double) input.get( 0 ); val = (val +
500 - 300 ) /
2.6 ; return
val; }
catch (Exception e) { throw
new IOException(e.getMessage()); } } } |
在上面的代碼中,input.get(0)是獲取UDF的第一個參數(可以向UDF傳入多個參數);同理,如果你的UDF接受兩個參數(例如一個求和的UDF),那麼input.get(1)可以取到第二個參數。
然後編寫build.xml(相當於C++裏面的Makefile),用ant來編譯、打包此工程——這裏就不把冗長的build.xml寫上來了,而且這也不是關鍵,沒有太多意義。
文章來源:http://www.codelast.com/
③假定編譯、打包得到的jar包名爲cl.jar,我們到這裏幾乎已經完成了大部分工作。下面就看看如何在pig中調用我們剛編寫的自定義函數了。
1
2
3
4
5
6
7
8
|
grunt> REGISTER cl.jar; grunt> A = LOAD
'a.txt' AS (col1:chararray, col2: double , col3: int ); grunt> B = FOREACH A GENERATE col1, com.codelast.MyUDF(col2), col3; grunt> DUMP B; (uidk,81.53846153846153,3) (hfd,127.6923076923077,99) (bbN,255.0,231) (UFD,81.92307692307692,10) |
注:第一句是註冊你編寫的UDF,使用前必須先註冊。
從結果可見,我們實現了預定的效果。
UDF大有用途!
注意:對如果你的UDF返回一個標量類型(類似於我上面的例子),那麼pig就可以使用反射(reflection)來識別出返回類型。如果你的UDF返回的是一個包(bag)或一個元組(tuple),並且你希望pig能理解包(bag)或元組(tuple)的內容的話,那麼你就要實現outputSchema方法,否則後果很不好。具體可看這個鏈接的說明。
(13)什麼是聚合函數(Aggregate Function)
在pig中,聚合函數就是那些接受一個輸入包(bag),返回一個標量(scalar)值的函數。COUNT函數就是一個例子。
(14)COGROUP做了什麼
與GROUP操作符一樣,COGROUP也是用來分組的,不同的是,COGROUP可以按多個關係中的字段進行分組。
還是以一個實例來說明,假設有以下兩個數據文件:
01
02
03
04
05
06
07
08
09
10
|
[root@localhost pig]$
cat a.txt uidk 12 3 hfd 132 99 bbN 463 231 UFD 13 10 [root@localhost pig]$
cat b.txt 908 uidk 888 345 hfd 557 28790 re 00000 |
現在我們用pig做如下操作及得到的結果爲:
1
2
3
4
5
6
7
8
9
|
grunt> A = LOAD
'a.txt' AS
(acol1:chararray, acol2: int , acol3: int ); grunt> B = LOAD
'b.txt' AS
(bcol1: int , bcol2:chararray, bcol3: int ); grunt> C = COGROUP A
BY acol1, B
BY bcol2; grunt> DUMP C; (re,{},{(28790,re,0)}) (UFD,{(UFD,13,10)},{}) (bbN,{(bbN,463,231)},{}) (hfd,{(hfd,132,99)},{(345,hfd,557)}) (uidk,{(uidk,12,3)},{(908,uidk,888)}) |
每一行輸出的第一項都是分組的key,第二項和第三項分別都是一個包(bag),其中,第二項是根據前面的key找到的A中的數據包,第三項是根據前面的key找到的B中的數據包。
來看看第一行輸出:“re”作爲group的key時,其找不到對應的A中的數據,因此第二項就是一個空的包“{}”,“re”這個key在B中找到了對應的數據(28790 re 00000),因此第三項就是包{(28790,re,0)}。
其他輸出數據也類似。
(15)安裝pig後,運行pig命令時提示“Cannot find hadoop configurations in classpath”等錯誤的解決辦法
pig安裝好後,運行pig命令時提示以下錯誤:
ERROR org.apache.pig.Main – ERROR 4010: Cannot find hadoop configurations in classpath (neither hadoop-site.xml nor core-site.xml was found in the classpath).If you plan to use local mode, please put -x local option in command line
顯而易見,提示找不到與hadoop相關的配置文件。所以我們需要把hadoop安裝目錄下的“conf”子目錄添加到系統環境變量PATH中:
修改 /etc/profile 文件,添加:
1
2
3
4
|
export
HADOOP_HOME= /usr/local/hadoop export
PIG_CLASSPATH=$HADOOP_HOME /conf PATH=$JAVA_HOME /bin :$HADOOP_HOME /bin :$PIG_CLASSPATH:$PATH |
然後重新加載 /etc/profile 文件:
1
|
source
/etc/profile |
文章來源:http://www.codelast.com/
(16)piggybank是什麼東西
Pig also hosts a UDF repository called piggybank that allows users to share UDFs that they have written.
說白了就是Apache把大家寫的自定義函數放在一塊兒,起了個名字,就叫做piggybank。你可以把它理解爲一個SVN代碼倉庫。具體請看這裏。
(17)UDF的構造函數會被調用幾次
你可能會想在UDF的構造函數中做一些初始化的工作,例如創建一些文件,等等。但是你不能假設UDF的構造函數只被調用一次,因此,如果你要在構造函數中做一些只能做一次的工作,你就要當心了——可能會導致錯誤。