【MapReduce】疫情数据清洗处理



疫情数据清洗处理

2020年新冠肺炎对我国社会各方面影响巨大,大数据技术在抗击疫情过程中发挥了巨大作用,尤其在新增、确认等相关病例数据的采集及统计上应用颇广,下面有一份数据是今年1月20-4月29日的全国各省市及国外的疫情数据,请你按照要求使用MapReduce程序完成相关数据预处理。

数据集:

  • 数据字段为:日期、省份、城市、新增确诊、新增出院、新增死亡、消息来源、来源1,来源2,来源3
    在这里插入图片描述

具体要求:

  1、数据转换:请将数据中日期字段格式,替换成日期格式为xxxx年xx月xx日。

  2、数据清洗:以下规则同时进行

  • 规则1 从上述小题中,截取前5个字段。
  • 规则2 过滤出省份为湖北省的数据。
  • 规则3 对5个字段去重,生成新的数据,将结果数据输出到hdfs。

一、数据转换

1.构建Bean类对数据集记录进行封装

  • 构建字段属性:这里我们通过对题干的信息分析,得出最终只需要前5个字段,所以属性仅需要5个即可,这里添加一个valid属性,用于判断后面数据清洗的时候筛选数据计数。
  • 重写toString()方法:作为数据输出格式化,采用StringBuffer类
  • 获取属性的setter、getter方法:后面封装Bean时使用
  • 定义valid的两个方法setValid()、isValid():分别用于设置valid属性值、对数据清洗获取的每条记录清洗判断。
package 疫情数据处理;

public class BeanTest  {
    
    
    // 日期、省份、城市、新增确诊、新增出院、新增死亡
    private String data; // 日期
    private String provience; // 省份
    private String city; // 城市
    private int newAdd;  // 新增确诊
    private int newOut;  // 新增出院
    private boolean valid = true;

    // toString()
    @Override
    public String toString() {
    
    
        // 定义一个字符串缓冲
        StringBuffer buffer = new StringBuffer();
        // 定义合法数据集的统计格式
        buffer.append(this.data);
        buffer.append(",").append(this.provience);
        buffer.append(",").append(this.city);
        buffer.append(",").append(this.newAdd);
        buffer.append(",").append(this.newOut);
        return buffer.toString();
    }

    // set\get()
    public String getData() {
    
    
        return data;
    }

    public void setData(String data) {
    
    
        this.data = data;
    }

    public String getProvience() {
    
    
        return provience;
    }

    public void setProvience(String provience) {
    
    
        this.provience = provience;
    }

    public String getCity() {
    
    
        return city;
    }

    public void setCity(String city) {
    
    
        this.city = city;
    }

    public int getNewAdd() {
    
    
        return newAdd;
    }

    public void setNewAdd(int newAdd) {
    
    
        this.newAdd = newAdd;
    }

    public int getNewOut() {
    
    
        return newOut;
    }

    public void setNewOut(int newOut) {
    
    
        this.newOut = newOut;
    }

    public boolean isValid() {
    
    
        return valid;
    }

    public void setValid(boolean valid) {
    
    
        this.valid = valid;
    }
}

返回顶部


2.使用Map类对数据进行日期格式转换

  • Mapper类的泛型结构为<LongWritable, Text, Text, NullWritable>:前两个固定的文件读取类型,后面是转换格式后的文本输出类型对(key,value),注意这里我并没有有使用到上面的Bean类,因为转换的只有日期字段,所以没有必要各字段分开再去处理,这里我们只需要将日期字段与剩下的字段整体分开就好了。
  • 重写map()方法实现日期字段格式的转换:前面就是获取每一行数据,然后对其进行拆分处理,注意使用split(m,n)这一重载方法,以m为分割符,执行(n-1)次分割操作,也可以理解为最终分割为n块字符串。然后提取出第一块字符串即日期字段,然后对其进行添加年份操作(一定要,不然后面转日期类型的时候会报错)
  • 进行日期格式的转换:这里的主要问题就是题目要求最终转为xxxx年xx月xx日,这里应该是指1月3日最终要转为2020年01月03日的形式(不然直接到上一步就可以结束了),首先我们利用DateFormat类将追加年份的数据转为长格式的日期对象,然后再利用SimpleDateFormat类将日期对象以指定的格式转换为字符串对象,最终完成月份、天数的优化。(日期格式化类的使用参见本人博客:https://blog.csdn.net/qq_45797116/article/details/108558790)
    在这里插入图片描述
  • 将日期格式化好了之后,在与其余的字段字符串整体拼接输出即可~
package 疫情数据处理;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class dateMapData extends Mapper<LongWritable, Text, Text, NullWritable> {
    
    
    Text k = new Text();

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
    
    
        // 1月20日,北京,大兴区,2,0,0,北京市大兴区卫健委,https://m.weibo.cn/2703012010/4462638756717942,,,
        // 1.读取一行数据
        String line = value.toString();
        // 2.拆分
        String[] fields = line.split(",", 2);
        // 3.提取转换日期信息
        String date = fields[0];
        String others = fields[1];
        String time = "2020年" + date;
        // 4.对月、日进行优化
        String str = null;
        try {
    
    
            DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.LONG);
            Date date1 = dateFormat.parse(time);
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日");
            str = simpleDateFormat.format(date1);
        } catch (Exception e){
    
    
            e.printStackTrace();
        }
        // 5.拼接
        String clear = str + "," + others;
        // 写出
        k.set(clear);
        context.write(k, NullWritable.get());
    }
}
package 疫情数据处理;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class dateDriverData {
    
    
    public static void main(String[] args) {
    
    
        Job job;
        Configuration conf = new Configuration();
        try{
    
    
            // 获取job
            job = Job.getInstance(conf);
            // 配置
            job.setMapperClass(dateMapData.class);
            job.setJarByClass(dateDriverData.class);
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(NullWritable.class);
            // 设置reduceTask数
            job.setNumReduceTasks(0);
            // 配置文件输入\输出
            FileInputFormat.setInputPaths(job,new Path("G:\\Projects\\IdeaProject-C\\MapReduce\\src\\main\\java\\疫情数据处理\\data.csv"));
            FileOutputFormat.setOutputPath(job,new Path("G:\\Projects\\IdeaProject-C\\MapReduce\\src\\main\\java\\疫情数据处理\\DateOutPut1"));
            // 提交job
            boolean result = job.waitForCompletion(true);
            System.exit(result? 0:1);
        } catch (Exception e){
    
    
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

返回顶部


二、数据清洗

  • 规则1 从上述小题中,截取前5个字段。
  • 规则2 过滤出省份为湖北省的数据。
  • 规则3 对5个字段去重,生成新的数据,将结果数据输出到hdfs。

1.截取前5个字段

  • 截取前5个字段:通过Bean类的封装属性来完成,只需在读取每条数据的时候对数据进行拆分,提取,分别使用setXxx()进行赋值即可。同样拆分的时候我们不需要完全拆分,最终我们只需要前面5个字段的内容,所以只要拆成6块,提取前5块就行了 — line.split(",",7)

2.提取省份为湖北的数据

  • 过滤省份信息:在之前已经对数据集进行了拆分,所以我们只要找出省份字段的数据,进行“湖北”的字符串比较就行了,同时对其余字段进行空值的判断,去除其余字段为空值的数据(无实际业务意义)。
package 疫情数据处理;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;

public class MapTest extends Mapper<LongWritable, Text,Text, NullWritable> {
    
    
    Text k = new Text();
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
    
    
        // 1月20日,北京,大兴区,2,0,0,北京市大兴区卫健委,https://m.weibo.cn/2703012010/4462638756717942,,,
        // 1.读取一行数据
        String line = value.toString();
        // 2.进行数据过滤
        BeanTest bean = parse(line,context);
        // 判断数据集是否合法
        if (!bean.isValid()){
    
    
            return;
        }
        // 合法直接输出
        k.set(bean.toString());
        context.write(k,NullWritable.get());
    }

    private BeanTest parse(String line, Context context) {
    
    
        BeanTest bean = new BeanTest();
        // 2.拆分
        String[] fields = line.split(",",7);
        // 3.过滤数据
        // 3.2 提取有效数据
        if (fields[1].equals("湖北") && (!fields[0].isEmpty()) && (!fields[2].isEmpty()) && (!fields[3].isEmpty()) && (!fields[4].isEmpty())){
    
    
            //  4.封装数据
            bean.setData(fields[0]);
            bean.setProvience(fields[1]);
            bean.setCity(fields[2]);
            bean.setNewAdd(Integer.parseInt(fields[3]));
            bean.setNewOut(Integer.parseInt(fields[4]));
            context.getCounter("map","true").increment(1);  // 计数器统计符合所有条件最终筛选出的数据数量
        } else {
    
    
            bean.setValid(false);
            context.getCounter("map","false").increment(1); // 计数器统计不符合条件的最终被过滤的数据数量
        }
         return bean;
    }
}

返回顶部


3.数据记录去重

  • 去重:这里我们利用MR的一个优势 — 相同的key会进入同一个ReduceTask进行处理,我们只需要将同一个ReduceTask中的第一条数据输出即可。所以在设置输入端的泛型的时候要注意,这里不使用Bean类型的原因是,我们在上面的parse()过滤处理时,借助了一个中间Bean对象,这使得每读一条数据就会创建一个中间Bean对象,也就是每个实际输出Bean对象都是不同的,内存地址不同,所以 不能进入同一个ReduceTask进行处理。
  • 并且在这里我们使用context.getCounter("reduce","final").increment(1);的计数器进行数据统计。
package 疫情数据处理;

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class ReduceTest extends Reducer<Text, NullWritable,Text,NullWritable> {
    
    
    @Override
    protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
    
    
        // 写出
        context.getCounter("reduce","final").increment(1);
        context.write(key,NullWritable.get());
    }
}

返回顶部


4.最终结果输出

package 疫情数据处理;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class DriverTest {
    
    
    public static void main(String[] args) {
    
    
        Job job;
        Configuration conf = new Configuration();
        try{
    
    
            // 获取job
            job = Job.getInstance(conf);
            // 配置
            job.setMapperClass(MapTest.class);
            job.setReducerClass(ReduceTest.class);
            job.setJarByClass(DriverTest.class);
            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(NullWritable.class);
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(NullWritable.class);
            // 设置reduceTask数
            //job.setNumReduceTasks(0);
            // 配置文件输入\输出
            FileInputFormat.setInputPaths(job,new Path("G:\\Projects\\IdeaProject-C\\MapReduce\\src\\main\\java\\疫情数据处理\\DateOutPut1\\part-m-00000"));
            FileOutputFormat.setOutputPath(job,new Path("G:\\Projects\\IdeaProject-C\\MapReduce\\src\\main\\java\\疫情数据处理\\finalOutPut"));
            // 提交job
            boolean result = job.waitForCompletion(true);
            System.exit(result? 0:1);
        } catch (Exception e){
    
    
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
在这里插入图片描述

返回顶部


三、结果输出保存

1.将结果数据输出到hdfs

在这里插入图片描述
在这里插入图片描述

返回顶部


猜你喜欢

转载自blog.csdn.net/qq_45797116/article/details/114524884
今日推荐