科多大数据带你学会轻松的调用Spark中的数据标准化库。
这篇文章的核心,不是在于介绍「数据标准化」,也不是在于实现「Spark调用」,毕竟这些概念大家应该耳濡目染了,至于调用方法一搜一大堆。
省略的内容,参考下面链接:
《数据标准化/归一化》 链接:数据标准化/归一化normalization - CSDN博客
《Spark ML包中的几种归一化方法总结》 链接:Spark ML包中的几种归一化方法总结 | Ron's Blog
Ok,我们在进入文章的重点前,允许我先吐槽一下。
其实我一直以来都不太愿意去直接调用Spark的一些工具包,因为很多数据的「输入」和「输出」都是向量和数组类型,直接使用起来很不灵活。
而且我也觉得DataFrame的功能太多此一举了,很多操作用Hive就可以替代,而且很多分析最好还是用Excel和SPSS去做。
但是没有办法,毕竟有些功能实现是现成的,可以节省很多开发成本,就比如数据预处理中的「标准化」,你就没有必要再单独去开发了。
然而,我们先看一下Spark要做「标准化」的输入数据样式。
// 原原始数据
+---+-----------------+
| id| features |
+---+-----------------+
| 0 |[1.0,0.5,-1.0]|
| 1 | [2.0,1.0,1.0]|
| 2 |[4.0,10.0,2.0]|
+---+-----------------+
看到这,我就不想去用了,除了简单的DataFrame赋值,正常情况下的业务特征都是一张宽表,或者是其他特征工程的组合形式。
那有人会无聊去把数据的存储形式保存为向量型的呢?虽然也可以这样做,但是我觉得不太方便去回顾数据。
无奈之下,我在使用DataFrame和「标准化库」时,做了一个简单的优化,具体如下所示:
// 原始数据
Userid,Feature1,Feature2,Feature3
import sqlContext.implicits._
//需要进行数据标准化的特征(除Userid外)有:
val value = behavData.map(_.split(",")).map(record =>
{
var featureArray:Array[Double] = new Array[Double](3)
val userid = record(0)
val feature = ( for(i
val featureVector = Vectors.dense(feature)
(userid,featureVector)
}
).toDF("userid","featureSet")
这样的话,我就可以直接将「原始数据」转化为Spark标准化库所要求的样式了。
// 转化数据
Userid,[Feature1,Feature2,Feature3]
提醒一下,其他向量类型还不行,必须是import org.apache.spark.mllib.linalg.Vectors;
令人反感的「数据输入」解决了一半,我们再着手「数据输出」,尽量让后期的建模工作顺畅起来。
// 这是其中一种标准化方法的数据输出。
+------+----------------------+-------------------------------------------------------------------+
| id | features | scaledFeatures |
+------+-----------------------+------------------------------------------------------------------+
| 0 | [1.0,0.5,-1.0] | [0.654653670707,0.09352195,-0.654653670] |
| 1 | [2.0,1.0,1.0] | [1.3093073414159544,0.18704390,0.65465] |
| 2 | [4.0,10.0,2.0] | [2.618614682831909,1.87043905,1.309307] |
+-----+-------------------------+-----------------------------------------------------------------+
可能是我真的看不习惯,要说这结果输出的灵活性太差也不为过,所以我又做了一个简单的优化。
//将DataFrame转换成RDD再存储于HDFS上
val resultRDD = inputValue.rdd.map(record =>
{
val ouputResult = new StringBuilder()
ouputResult.append(record(0).toString()).append(",")
//调用字符串StrDealOne函数
StrDealOne(record(1).toString()).split(",").map(records =>
{
ouputResult.append(round(records.toDouble,4)).append(",")
}
)
//调用字符串StrDealTwo函数
StrDealTwo(ouputResult.toString())
}
)
其中
/**
* 字符串处理(替换特殊字符、去掉字符串末尾一位)
*/
def StrDealOne(InputValue:String):String = {
InputValue.replaceAll("\\(","").replaceAll("\\)","").replaceAll("\\[","").replaceAll("\\]","")
}
def StrDealTwo(InputValue:String):String = {
InputValue.substring(0, InputValue.toString().length()-1)
}
简单来说,就是让标准化后的数据恢复最初的Userid,Feature1,Feature2,Feature3格式,方便后期使用。
通过对数据「输入」和「输出」的简单操作,我在后期想将数值型的特征进行标准化时,就能很舒服去调用了。
閱讀更多 浩瀚科技數碼 的文章