在做数据探索性分析的时候,有几个比较重要的数值,,它们能简要的概括数据的分布情况,它们包括分位数、均值、最值等。
在R语言中,有个summary函数,可以返回这些数据摘要
本文所使用的数据集以鸢尾花数据集为例
summary(iris)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
Min. :4.300 Min. :2.000 Min. :1.000 Min. :0.100 setosa :50
1st Qu.:5.100 1st Qu.:2.800 1st Qu.:1.600 1st Qu.:0.300 versicolor:50
Median :5.800 Median :3.000 Median :4.350 Median :1.300 virginica :50
Mean :5.843 Mean :3.057 Mean :3.758 Mean :1.199
3rd Qu.:6.400 3rd Qu.:3.300 3rd Qu.:5.100 3rd Qu.:1.800
Max. :7.900 Max. :4.400 Max. :6.900 Max. :2.500
在spark中也有类似的函数 describe(),但是该函数并没有返回关于分位数的信息
// 加载鸢尾花数据集
val irisDF = spark.read
.options(Map("header" -> "true",
"nullValue" -> "?",
"inferSchema" -> "true"))
.csv(inputFile)
// dataframe 调用describe 函数 ,因为 “class”列不是数值型的数据,所以在此需要去掉
val describe = irisDF.drop("class").describe()
describe.show()
结果如下
spark 的 “summary” 只返回了 计数、均值、方差、最值,因为中值和分位数在大数据上的计算成本很高,两个值都需要先对数据排序再计算。
+-------+------------------+-------------------+------------------+------------------+
|summary| sepalLength| sepalWidth| petalLength| petalWidth|
+-------+------------------+-------------------+------------------+------------------+
| count| 150| 150| 150| 150|
| mean| 5.843333333333335| 3.057333333333334|3.7580000000000027| 1.199333333333334|
| stddev|0.8280661279778637|0.43586628493669793|1.7652982332594662|0.7622376689603467|
| min| 4.3| 2.0| 1.0| 0.1|
| max| 7.9| 4.4| 6.9| 2.5|
+-------+------------------+-------------------+------------------+------------------+
在spark2.0之后,提供了 approxQuantile 方法可以计算分位数,用法如下
//第一个参数 列的名称,可以是单独的列名,也可以是列名数组
//第二个参数 分位数的值数组 。
//最后一个参数 表示错误率,数值在 0-1之间 值越小精确度越高
val summary1 = irisDF.stat.approxQuantile(Array("sepalLength", "sepalWidth"),
Array(0.25, 0.5, 0.75), 0)
val summary2 = irisDF.stat.approxQuantile("sepalLength", Array(0.25, 0.5, 0.75), 0)
// summary2 返回的结果是Array(5.1 ,5.8 ,6.4),与R语言的结果一致
为了能让结果更直观我们做进一步的处理,并计算了四分位差IQR
// 常用的几个数值型变量类型
val numType = Array(DoubleType, IntegerType, LongType, DecimalType, FloatType)
val tuples = irisDF.schema.map(sf => {
(sf.name, sf.dataType)
}).filter(tp => {
numType.contains(tp._2)
}).map(scm => {
val quantiles = irisDF.stat.approxQuantile(scm._1, Array(0.25, 0.5, 0.75), 0)
val col = scm._1
(col, quantiles)
})
val quantileDF = tuples
.toDF("attribute", "quantiles")
.withColumn("q1", $"quantiles".getItem(0))
.withColumn("median", $"quantiles".getItem(0))
.withColumn("q3", $"quantiles".getItem(2))
.withColumn("IOR", $"q3" - $"q1") // 计算四分位差
.drop("quantiles")
quantileDF.show()
结果
+-----------+---+------+---+------------------+
| attribute| q1|median| q3| IOR|
+-----------+---+------+---+------------------+
|sepalLength|5.1| 5.1|6.4|1.3000000000000007|
| sepalWidth|2.8| 2.8|3.3| 0.5|
|petalLength|1.6| 1.6|5.1|3.4999999999999996|
| petalWidth|0.3| 0.3|1.8| 1.5|
+-----------+---+------+---+------------------+
我们队之前describe() 的计算结果也做进一步变换
val schema = describe.schema
val longForm = describe.flatMap(row => {
val metric = row.getString(0)
(1 until row.length).map(i => {
(metric, schema(i).name, row.getString(i).toDouble)
})
}) .toDF("summary", "attribute", "values")
// 列联表
val dataset: DataFrame = longForm.groupBy($"attribute")
.pivot("summary").agg(first("values"))
最后再将两个计算结果join到一起,就可以得到由spark 完成的“summary”结果啦
quantileDF.join(dataset, "attribute").show()
+-----------+---+------+---+------------------+-----+---+------------------+---+-------------------+
| attribute| q1|median| q3| IOR|count|max| mean|min| stddev|
+-----------+---+------+---+------------------+-----+---+------------------+---+-------------------+
|petalLength|1.6| 1.6|5.1|3.4999999999999996|150.0|6.9|3.7580000000000027|1.0| 1.7652982332594662|
| sepalWidth|2.8| 2.8|3.3| 0.5|150.0|4.4| 3.057333333333334|2.0|0.43586628493669793|
|sepalLength|5.1| 5.1|6.4|1.3000000000000007|150.0|7.9| 5.843333333333335|4.3| 0.8280661279778637|
| petalWidth|0.3| 0.3|1.8| 1.5|150.0|2.5| 1.199333333333334|0.1| 0.7622376689603467|
+-----------+---+------+---+------------------+-----+---+------------------+---+-------------------+