(6)把数据当作“元组”(tuple)来加载
还是假设有如下数据:
1
2
3
4
5
6
7
|
a 1 2 3 4.2 9.8
a 3 0 5 3.5 2.1
b 7 9 9 - -
a 7 9 9 2.6 6.2
a 1 2 5 7.7 5.9
a 1 2 3 1.4 0.2
|
如果我们按照以下方式来加载数据:
1
|
A =
LOAD
'a.txt'
AS
(col1:chararray, col2:
int
, col3:
int
, col4:
int
, col5:
double
, col6:
double
);
|
那么得到的A的数据结构为:
1
2
|
grunt> DESCRIBE A;
A: {col1: chararray,col2:
int
,col3:
int
,col4:
int
,col5:
double
,col6:
double
}
|
如果你要把A当作一个元组(tuple)来加载:
1
|
A =
LOAD
'a.txt'
AS
(T : tuple (col1:chararray, col2:
int
, col3:
int
, col4:
int
, col5:
double
, col6:
double
));
|
也就是想要得到这样的数据结构:
1
2
|
grunt> DESCRIBE A;
A: {T: (col1: chararray,col2:
int
,col3:
int
,col4:
int
,col5:
double
,col6:
double
)}
|
那么,上面的方法将得到一个空的A:
1
2
3
4
5
6
7
|
grunt> DUMP A;
()
()
()
()
()
()
|
那是因为数据文件a.txt的结构不适合于这样加载成元组(tuple)。
文章来源:http://www.codelast.com/
如果有数据文件b.txt:
1
2
3
4
5
6
7
|
(a,1,2,3,4.2,9.8)
(a,3,0,5,3.5,2.1)
(b,7,9,9,-,-)
(a,7,9,9,2.6,6.2)
(a,1,2,5,7.7,5.9)
(a,1,2,3,1.4,0.2)
|
则使用上面所说的加载方法及结果为:
1
2
3
4
5
6
7
8
|
grunt> A = LOAD
'b.txt'
AS (T : tuple (col1:chararray, col2:int, col3:int, col4:int, col5:double, col6:double));
grunt> DUMP A;
((a,1,2,3,4.2,9.8))
((a,3,0,5,3.5,2.1))
((b,7,9,9,,))
((a,7,9,9,2.6,6.2))
((a,1,2,5,7.7,5.9))
((a,1,2,3,1.4,0.2))
|
可见,加载的数据的结构确实被定义成了元组(tuple)。
(7)在多维度组合下,如何计算某个维度组合里的不重复记录的条数
以数据文件 c.txt 为例:
1
2
3
4
5
6
7
|
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 与前面有点不一样,这就是所谓的“嵌套的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
|
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)
请看这个链接:《Apache Pig中文教程(进阶)》
(13)什么是聚合函数(Aggregate Function)
在pig中,聚合函数就是那些接受一个输入包(bag),返回一个标量(scalar)值的函数。COUNT函数就是一个例子。
(14)COGROUP做了什么
与GROUP操作符一样,COGROUP也是用来分组的,不同的是,COGROUP可以按多个关系中的字段进行分组。
还是以一个实例来说明,假设有以下两个数据文件:
01
02
03
04
05
06
07
08
09
10
|
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
|
(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的构造函数只被调用一次,因此,如果你要在构造函数中做一些只能做一次的工作,你就要当心了——可能会导致错误。
(18)LOAD数据时,如何一次LOAD多个目录下的数据
例如,我要LOAD两个HDFS目录下的数据:/abc/2010 和 /abc/2011,则我们可以这样写LOAD语句:
1
|
A =
LOAD
'/abc/201{0,1}'
;
|
(19)怎样自己写一个UDF中的加载函数(load function)
请看这个链接:《Apache Pig中文教程(进阶)》
(20)重载(overloading)一个UDF
请看这个链接:《Apache Pig中文教程(进阶)》。
(21)pig运行不起来,提示“org.apache.hadoop.ipc.Client - Retrying connect to server:
请看这个链接:《Apache Pig中文教程(进阶)》
(22)用含有null的字段来GROUP,结果会如何
假设有数据文件 a.txt 内容为:
1
2
3
4
|
1 2 5
1 3
1 3
6 9 8
|
其中,每两列数据之间是用tab分割的,第二行的第2列、第三行的第3列没有内容(也就是说,加载到Pig里之后,对应的数据会变成null),如果把这些数据按第1、第2列来GROUP的话,第1、2列中含有null的行会被忽略吗?
来做一下试验:
1
2
3
|
A =
LOAD
'a.txt'
AS
(col1:
int
, col2:
int
, col3:
int
);
B =
GROUP
A
BY
(col1, col2);
DUMP B;
|
输出结果为:
1
2
3
4
|
((1,2),{(1,2,5)})
((1,3),{(1,3,)})
((1,),{(1,,3)})
((6,9),{(6,9,8)})
|
从上面的结果(第三行)可见,原数据中第1、2列里含有null的行也被计入在内了,也就是说,GROUP操作是不会忽略null的,这与COUNT有所不同(见本文前面的部分)。
(23)如何统计数据中某些字段的组合有多少种
假设有如下数据:
1
2
3
4
5
|
[root@localhost]
# cat a.txt
1 3 4 7
1 3 5 4
2 7 0 5
9 8 6 6
|
现在我们要统计第1、2列的不同组合有多少种,对本例来说,组合有三种:
1
2
3
|
1 3
2 7
9 8
|
也就是说我们要的答案是3。
用Pig怎么计算?
文章来源:http://www.codelast.com/
先写出全部的Pig代码:
1
2
3
4
5
|
A =
LOAD
'a.txt'
AS
(col1:
int
, col2:
int
, col3:
int
, col4:
int
);
B =
GROUP
A
BY
(col1, col2);
C =
GROUP
B
ALL
;
D = FOREACH C GENERATE
COUNT
(B);
DUMP D;
|
然后再来看看这些代码是如何计算出上面的结果的:
①第一行代码加载数据,没什么好说的。
②第二行代码,得到第1、2列数据的所有组合。B的数据结构为:
1
2
|
grunt> DESCRIBE B;
B: {
group
: (col1:
int
,col2:
int
),A: {col1:
int
,col2:
int
,col3:
int
,col4:
int
}}
|
把B DUMP出来,得到:
1
2
3
|
((1,3),{(1,3,4,7),(1,3,5,4)})
((2,7),{(2,7,0,5)})
((9,8),{(9,8,6,6)})
|
非常明显,(1,3),(2,7),(9,8)的所有组合已经被排列出来了,这里得到了若干行数据。下一步我们要做的就是统计这样的数据一共有多少行,也就得到了第1、2列的组合有多少组。
③第三和第四行代码,就实现了统计数据行数的功能。参考本文前面部分的“怎样统计数据行数”一节。就明白这两句代码是什么意思了。
这里需要特别说明的是:
a)为什么倒数第二句代码中是COUNT(B),而不是COUNT(group)?
我们是对C进行FOREACH,所以要先看看C的数据结构:
1
2
|
grunt> DESCRIBE C;
C: {
group
: chararray,B: {
group
: (col1:
int
,col2:
int
),A: {col1:
int
,col2:
int
,col3:
int
,col4:
int
}}}
|
可见,你可以把C想像成一个map的结构,key是一个group,value是一个包(bag),它的名字是B,这个包中有N个元素,每一个元素都对应到②中所说的一行。根据②的分析,我们就是要统计B中元素的个数,因此,这里当然就是COUNT(B)了。
b)COUNT函数的作用是统计一个包(bag)中的元素的个数:
COUNT
Computes the number of elements in a bag.
从C的数据结构看,B是一个bag,所以COUNT函数是可以用于它的。
如果你试图把COUNT应用于一个非bag的数据结构上,会发生错误,例如:
1
|
java.lang.ClassCastException: org.apache.pig.data.BinSedesTuple cannot be cast to org.apache.pig.data.DataBag
|
这是把Tuple传给COUNT函数时发生的错误。
(24)两个整型数相除,如何转换为浮点型,从而得到正确的结果
这个问题其实很傻,或许不用说你也知道了:假设有int a = 3 和 int b = 2两个数,在大多数编程语言里,a/b得到的是1,想得到正确结果1.5的话,需要转换为float再计算。在Pig中其实和这种情况一样,下面就拿几行数据来做个实验:
1
2
3
|
[root@localhost ~]# cat a.txt
3 2
4 5
|
在Pig中:
1
2
3
4
5
|
grunt> A =
LOAD
'a.txt'
AS
(col1:
int
, col2:
int
);
grunt> B = FOREACH A GENERATE col1/col2;
grunt> DUMP B;
(1)
(0)
|
可见,不加类型转换的计算结果是取整之后的值。
那么,转换一下试试:
1
2
3
4
5
|
grunt> A =
LOAD
'a.txt'
AS
(col1:
int
, col2:
int
);
grunt> B = FOREACH A GENERATE (
float
)(col1/col2);
grunt> DUMP B;
(1.0)
(0.0)
|
这样转换还是不行的,这与大多数编程语言的结果一致——它只是把取整之后的数再转换为浮点数,因此当然是不行的。
文章来源:http://www.codelast.com/
正确的做法应该是:
1
2
3
4
5
|
grunt> A =
LOAD
'a.txt'
AS
(col1:
int
, col2:
int
);
grunt> B = FOREACH A GENERATE (
float
)col1/col2;
grunt> DUMP B;
(1.5)
(0.8)
|
或者这样也行:
1
2
3
4
5
|
grunt> A =
LOAD
'a.txt'
AS
(col1:
int
, col2:
int
);
grunt> B = FOREACH A GENERATE col1/(
float
)col2;
grunt> DUMP B;
(1.5)
(0.8)
|
这与我们的通常做法是一致的,因此,你要做除法运算的时候,需要注意这一点。
(25)UNION的一个例子
假设有两个数据文件为:
1
2
3
4
5
6
7
8
|
[root@localhost ~]
# cat 1.txt
0 3
1 5
0 8
[root@localhost ~]
# cat 2.txt
1 6
0 9
|
现在要求出:在第一列相同的情况下,第二列的和分别为多少?
例如,第一列为 1 的时候,第二列有5和6两个值,和为11。同理,第一列为0的时候,第二列的和为 3+8+9=20。
计算此问题的Pig代码如下:
1
2
3
4
5
6
|
A =
LOAD
'1.txt'
AS
(a:
int
, b:
int
);
B =
LOAD
'2.txt'
AS
(c:
int
, d:
int
);
C =
UNION
A, B;
D =
GROUP
C
BY
$0;
E = FOREACH D GENERATE FLATTEN(
group
),
SUM
(C.$1);
DUMP E;
|
输出为:
1
2
|
(0,20)
(1,11)
|
文章来源:http://www.codelast.com/
我们来看看每一步分别做了什么:
①第1行、第2行代码分别加载数据到关系A、B中,没什么好说的。
②第3行代码,将关系A、B合并起来了。合并后的数据结构为:
1
2
|
grunt> DESCRIBE C;
C: {a:
int
,b:
int
}
|
其数据为:
1
2
3
4
5
6
|
grunt> DUMP C;
(0,3)
(1,5)
(0,8)
(1,6)
(0,9)
|
③第4行代码按第1列(即$0)进行分组,分组后的数据结构为:
1
2
|
grunt> DESCRIBE D;
D: {
group
:
int
,C: {a:
int
,b:
int
}}
|
其数据为:
1
2
3
|
grunt> DUMP D;
(0,{(0,9),(0,3),(0,8)})
(1,{(1,5),(1,6)})
|
④最后一行代码,遍历D,将D中每一行里的所有bag(即C)的第2列(即$1)进行累加,就得到了我们要的结果。