文章目录
疫情数据清洗处理
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