1.中位数与标准差计算示例【内存优化】
在前一篇博客中,我介绍了一种计算中位数和标准差的方法,但是该方法需要将所有的数据读入内存再进行遍历,不够优化。所以在这里,我们将前一种方法进行优化,
将所有的数值都插入列表会产生大量的重复的元素。一个规避重复元素的方法就是保存元素的计数。
例如:要保存<1,1,1,1,2,2,3,4,5,5,5>可以使用排序好的值到计数的映射来代替:(1->4,2->2,3->1,4->1,5->3).
核心概念还是与前一种方法相同,但是这样可以减少存放到内存的数据量。而且这个方法还有一个优点就是可以使用combiner来聚合map端的计数。
2.数据集
本示例使用Movielens数据集中的u1.base文件,MovieLens数据集是一个用户对电影的评分数据集,在后续的示例中我们将一直使用这个数据集,我会将这个数据集上传到CSDN方便大家下载,文件的格式如下所示:
第1列到第4列分别代表用户ID**,项目ID**,用户对项目的评分,时间戳
1 1 5 874965758
1 2 3 876893171
1 3 4 878542960
1 4 3 876893119
1 5 3 889751712
1 7 4 875071561
1 8 1 875072484
... .... ....
943 1067 2 875501756
943 1074 4 888640250
943 1188 3 888640250
943 1228 3 888640275
943 1330 3 888692465
3.问题
对于给定的用户项目评分数据,确定每个用户评分的中位数、均方差。
4.计算方案(内存优化版)
1)自定义Writab类存储输出数据
public class MedianStdDevImproveTuple implements Writable{
private float stdDev=0;
private float median=0;
private int rate=0;
public void setStdDev(float stdDev){
this.stdDev=stdDev;
}
public double getStdDev(){
return stdDev;
}
public void setMedian(float median){
this.median=median;
}
public double getMedian(){
return median;
}
public void setRate(int rate){
this.rate=rate;
}
public double getRate(){
return rate;
}
@Override
public void readFields(DataInput in) throws IOException {
stdDev=in.readFloat();
median=in.readFloat();
rate=in.readInt();
}
@Override
public void write(DataOutput out) throws IOException {
out.writeFloat(this.stdDev);
out.writeFloat(this.median);
out.writeInt(this.rate);
}
public String toString(){
return "medianValue:"+median+" "+"stdDev:"+stdDev;
}
}
2)mapper代码
public static class MedianStdDevImproveMapper extends Mapper<Object, Text, IntWritable, SortedMapWritable>{
private IntWritable outUserId=new IntWritable();//输出健
private IntWritable outRate=new IntWritable();//输出值
private final LongWritable ONE=new LongWritable(1);
public void map(Object key, Text value ,Context context) throws IOException, InterruptedException{
String[] data=value.toString().split(" ");
if(data.length==4){
//输出的键
outUserId.set(Integer.valueOf(data[0]));
//输出的值,用户的评分
outRate.set(Integer.valueOf(data[2]));
//定义一个Writable类来存放键和值的映射
SortedMapWritable outCommentLength=new SortedMapWritable();
outCommentLength.put(outRate, ONE);
context.write(outUserId, outCommentLength);
}
}
}
2)reducer代码
public static class MedianStdDevImproveReducer extends Reducer<IntWritable, SortedMapWritable, IntWritable, MedianStdDevTuple>{
private MedianStdDevTuple result=new MedianStdDevTuple();
private TreeMap<Integer,Long> RateCounts=new TreeMap<Integer,Long>();
public void reduce(IntWritable key, Iterable<SortedMapWritable> values,Context context) throws IOException, InterruptedException{
float sum=0;
long totalcount=0;
RateCounts.clear();
result.setStdDev(0.0);
result.setMedian(0.0);
for(SortedMapWritable v:values){
for(Entry<WritableComparable,Writable> entry:v.entrySet()){
int rate=((IntWritable)entry.getKey()).get();
long count=((LongWritable)entry.getValue()).get();
totalcount+=count;
sum+=rate*count;
Long storedCount=RateCounts.get(rate);
if(storedCount==null){
//如果storedCount==null说明是第一条数据
RateCounts.put(rate, count);
}else{
//后续依次存入递增的值
RateCounts.put(rate, storedCount+count);
}
}
}
//中位数的位置
long x=2;
long medianIndex= totalcount/x;
long previousCount=0;
long lastCount=0;
int preKey=0;
for(Entry<Integer,Long> entry:RateCounts.entrySet()){
lastCount=previousCount+entry.getValue();
//*首先分为两种情况:
//* 1.总的评分数目是奇数:说明中位数就存在于当前key值对应的范围内,当前key值就是中位数
// * 2.总的评分数目是偶数:1)前后两个数在同一个范围类,则当前key值就是中位数
// * 2)前后两个数不在同一个范围内,只满足其中一个数位于边界,即previousCount==medianIndex,则取前一个key和后一个key的和相除得到中位数
// *
if(previousCount<=medianIndex&&medianIndex<lastCount){
if(totalcount%2==0&&previousCount==medianIndex){
result.setMedian((float)(entry.getKey()+preKey)/2.0f);
}else{
result.setMedian((float)entry.getKey());
}
break;//中位数已经找到,跳出循环
}
previousCount=lastCount;//更新
preKey=entry.getKey();//更新
}
//计算均方差
float mean=sum/totalcount;
float sumOfSquares=0.0f;
for(Entry<Integer, Long> entry:RateCounts.entrySet()){
sumOfSquares+=(entry.getKey()-mean)*(entry.getKey()-mean)*entry.getValue();
}
result.setStdDev((float)(Math.sqrt(sumOfSquares/(totalcount-1))));
context.write(key, result);
}
}
2)Combiner优化代码
public static class MedianStdDevImproveCombiner extends Reducer<IntWritable, SortedMapWritable,IntWritable, SortedMapWritable>{
public void reduce(IntWritable key, Iterable<SortedMapWritable> values,Context context) throws IOException, InterruptedException{
SortedMapWritable outValue=new SortedMapWritable();
for(SortedMapWritable v:values){
for(Entry<WritableComparable, Writable> entry:v.entrySet()){
LongWritable count=(LongWritable)outValue.get(entry.getKey());//获取当前的计数
if(count==null){
outValue.put(entry.getKey(), new LongWritable(((LongWritable)entry.getValue()).get()));
}else{
outValue.put(entry.getKey(),new LongWritable(count.get()+((LongWritable)entry.getValue()).get()));
}
}
}
context.write(key, outValue);
}
}
4.总结
在对有大量重复数据的数据进行存储时,可以采用本例展示的存储数据出现次数的方式来节省存储空间。
本例特别注意:在对输出文件进行设置的时候需要注意,combiner的输入和输出类型需要与map的输出类型相同,不然会出现没有输出的情况。
详情可以参考博客:http://blog.csdn.net/lucktroy/article/details/7957120
感谢该博主分享经验,帮我找出了程序中存在的问题!