数据分析,数据挖掘基本算法
在本系列教程的第1部分中,您学习了如何将单个表从Informix®移动到Spark,如何将整个数据库放入Spark,以及如何为Informix构建特定的方言。
第二部分的重点是分析,并从数据中获得基本见识。 绝对不需要Informix的知识。
企业就绪
您正在使用模仿企业数据库的Informix商店演示数据库。 该公司是一家运动器材批发商,主要销售给妈妈和流行商店。 本教程仅使用一个表:orders表。 订单表在Apache Spark中导入后的架构如下所示。
关于数据库图的小注释:在原始数据库中,有主键,外键,约束,本机数据类型(例如SERIAL
)。 所有这些都转换为Spark的数据类型和简化的结构。 不需要索引,约束或键。
出货时间
您的第一个练习是衡量从客户下订单到发货之间仓库需要多少时间。
当然,使用Spark进行此操作有点像使用核电站为您的手机加油,但是您会在Spark分析世界中逐步发现!
您可以从GitHub下载代码。
摘要并遍历代码
做好准备 第一次导入似乎有点不寻常,因为Spark有很多预定义的功能。 如有必要,org.apache.spark.sql.functions包中的函数列表是有用的参考。
在以下代码中,将datediff
替换为*
以导入所有功能。 但是,对于本教程,仅需要datediff
。 您可能已经知道它的作用。
import static org.apache.spark.sql.functions.datediff;
其余的导入类与第1部分中使用的类相似。 在您遍历代码时,我喜欢共享导入。 如果我不这样做,使用同名来引用类可能会非常混乱。
import java.sql.Connection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;
import org.apache.spark.sql.jdbc.JdbcDialect;
import org.apache.spark.sql.jdbc.JdbcDialects;
import net.jgp.labs.informix2spark.utils.Config;
import net.jgp.labs.informix2spark.utils.ConfigManager;
import net.jgp.labs.informix2spark.utils.InformixJdbcDialect;
import net.jgp.labs.informix2spark.utils.K;
启动应用程序的时间:
public class TimeToShipApp {
public static void main(String[] args) {
TimeToShipApp app = new TimeToShipApp();
app.start();
}
private void start() {
使用Spark的本地模式启动会话:
SparkSession spark = SparkSession
.builder()
.appName("Time to Ship")
.master("local")
.getOrCreate();
即使该示例不需要它,注册Informix方言也是一个好习惯:
JdbcDialect dialect = new InformixJdbcDialect();
JdbcDialects.registerDialect(dialect);
您将获得Informix参数:
Config config = ConfigManager.getConfig(K.INFORMIX);
创建要使用的所有表的列表(并将其添加到数据湖)。 在此示例中,这似乎有些过头,因为我们仅使用一个表,但在其他示例中重复使用了相同的思想(重复可以帮助您学习)。
List<String> tables = new ArrayList<>();
tables.add("orders");
如您所知,映射包含按键索引的值。 每个来自Spark的数据框都存储在一个映射中,其键是表的名称,值是包含数据的数据框。
Map<String, Dataset<Row>> datalake = new HashMap<>();
for (String table : tables) {
System.out.print("Loading table [" + table
+ "] ... ");
Dataset<Row> df = spark.read()
.format("jdbc")
.option("url", config.getJdbcUrl())
.option("dbtable", table)
.option("user", config.getUser())
.option("password", config.getPassword())
.option("driver", config.getDriver())
.load();
datalake.put(table, df);
System.out.println("done.");
}
至此,此执行的结果是:
Loading table [orders] ... done.
We have loaded 1 table(s) in our data lake.
现在您可以利用数据了。 使用ordersDF
数据ordersDF
:
Dataset<Row> ordersDf = datalake.get("orders");
使用withColumn()
方法创建一个新列。 这一新列是datediff()Spark SQL函数的结果 。 该函数有两个参数:结束日期和开始日期。
ordersDf = ordersDf.withColumn(
"time_to_ship",
datediff(ordersDf.col("ship_date"), ordersDf.col("order_date")));
注意:
datediff(ordersDf.col("ship_date"), ordersDf.col("order_date"))
与以下内容不同:
datediff(ordersDf.col("order_date"), ordersDf.col("ship_date"))
您真正要做的是减法。 因此,将最大的数字(或更近的日期)放在第一位; 否则,您将拥有一个负数。
此时,如果您打印数据框的模式,您将看到:
ordersDf.printSchema();
root
|-- order_num: integer (nullable = false)
|-- order_date: date (nullable = true)
|-- customer_num: integer (nullable = false)
|-- ship_instruct: string (nullable = true)
|-- backlog: string (nullable = true)
|-- po_num: string (nullable = true)
|-- ship_date: date (nullable = true)
|-- ship_weight: decimal(8,2) (nullable = true)
|-- ship_charge: decimal(6,2) (nullable = true)
|-- paid_date: date (nullable = true)
|-- time_to_ship: integer (nullable = true)
看到您添加的列? 它称为time_to_ship
,其类型是整数(并且可以为null
)。
如果您查看数据:
ordersDf.show(10);
System.out.println("We have " + ordersDf.count() + " orders");
+---------+----------+------------+--------------------+-------+----------+----------+-----------+-----------+----------+------------+
|order_num|order_date|customer_num| ship_instruct|backlog| po_num| ship_date|ship_weight|ship_charge| paid_date|time_to_ship|
+---------+----------+------------+--------------------+-------+----------+----------+-----------+-----------+----------+------------+
| 1001|2008-05-20| 104|express ...| n|B77836 |2008-06-01| 20.40| 10.00|2008-07-22| 12|
| 1002|2008-05-21| 101|PO on box; delive...| n|9270 |2008-05-26| 50.60| 15.30|2008-06-03| 5|
| 1003|2008-05-22| 104|express ...| n|B77890 |2008-05-23| 35.60| 10.80|2008-06-14| 1|
| 1004|2008-05-22| 106|ring bell twice ...| y|8006 |2008-05-30| 95.80| 19.20| null| 8|
| 1005|2008-05-24| 116|call before deliv...| n|2865 |2008-06-09| 80.80| 16.20|2008-06-21| 16|
| 1006|2008-05-30| 112|after 10 am ...| y|Q13557 | null| 70.80| 14.20| null| null|
| 1007|2008-05-31| 117| null| n|278693 |2008-06-05| 125.90| 25.20| null| 5|
| 1008|2008-06-07| 110|closed Monday ...| y|LZ230 |2008-07-06| 45.60| 13.80|2008-07-21| 29|
| 1009|2008-06-14| 111|next door to groc...| n|4745 |2008-06-21| 20.40| 10.00|2008-08-21| 7|
| 1010|2008-06-17| 115|deliver 776 King ...| n|429Q |2008-06-29| 40.60| 12.30|2008-08-22| 12|
+---------+----------+------------+--------------------+-------+----------+----------+-----------+-----------+----------+------------+
only showing top 10 rows
We have 23 orders
您可以在time_to_ship
列中看到订单#1006具有空值,因为它尚未发货。 当分析发货时间时,您会发现它不是Amazon Prime!
接下来,您将希望摆脱null值,但是数据帧是不可变的,这意味着数据无法更改。 要解决此问题,请创建一个新的数据框并排除您不喜欢的值。 是的,数据框不是SQL表,没有DELETE FROM
。
Dataset<Row> ordersDf2 = ordersDf.filter(
"time_to_ship IS NOT NULL");
ordersDf2.printSchema();
ordersDf2.show(5);
System.out.println("We have " + ordersDf2.count()
+ " delivered orders");
}
}
输出为:
+---------+----------+------------+--------------------+-------+----------+----------+-----------+-----------+----------+------------+
|order_num|order_date|customer_num| ship_instruct|backlog| po_num| ship_date|ship_weight|ship_charge| paid_date|time_to_ship|
+---------+----------+------------+--------------------+-------+----------+----------+-----------+-----------+----------+------------+
| 1001|2008-05-20| 104|express ...| n|B77836 |2008-06-01| 20.40| 10.00|2008-07-22| 12|
| 1002|2008-05-21| 101|PO on box; delive...| n|9270 |2008-05-26| 50.60| 15.30|2008-06-03| 5|
| 1003|2008-05-22| 104|express ...| n|B77890 |2008-05-23| 35.60| 10.80|2008-06-14| 1|
| 1004|2008-05-22| 106|ring bell twice ...| y|8006 |2008-05-30| 95.80| 19.20| null| 8|
| 1005|2008-05-24| 116|call before deliv...| n|2865 |2008-06-09| 80.80| 16.20|2008-06-21| 16|
+---------+----------+------------+--------------------+-------+----------+----------+-----------+-----------+----------+------------+
only showing top 5 rows
We have 22 delivered orders
但是等等,到底发生了什么?
Spark基于惰性评估的概念。 我认为这是当您要孩子做某事时:打扫房间,将衣服放在篮子里,将书放在书架上……通常,这些变化不会发生,除非您说出“ now”(有时声音会稍大一些)。 这是他们等待上手的动作信号。 Spark完全相同。
这里的负责人称为催化剂。 Catalyst是一种优化器,可找到执行转换的最佳方法。 再说一次,想想这个小孩子,他需要从架子上脱下衣服才能放回书本。 在2.2版中,Spark向Catalyst添加了基于成本的优化器,这是IBM的重要贡献。
什么是数据框?
对于Apache Spark,数据框几乎就像关系数据库世界中的表一样。 好吧,差不多...
- 数据是不可变的,这意味着它不会改变。 使用SQL,您可以轻松地
UPDATE
数据并在表中INSERT
新行。 数据框是不同的。 要执行这些操作,您每次必须创建一个新的数据框。 这是您经常要做的事情。 - 像表一样,数据框具有元数据:列具有名称,类型以及它们是否需要值(
nullable
为nullable
属性)。 与表不同,您不会找到索引,外键,复合键或触发器。 - 由于Spark具有分析性,因此添加列和执行转换很容易,而使用RDBMS表则很棘手。
数据框API是开发人员使用的API。 您将比Facebook访问Class Dataset页面更多! 直接从此API,您可以访问列信息,执行联接和联合等等。 (本系列文章的第3部分将探讨此API。)
在Java中,数据帧被实现为Dataset<Row>
。 存储依赖于群集中的分区。 Spark提供了较低级别的API,因此您可以重新分区并了解执行计划,但这不在本文的讨论范围之内。 从Spark 2开始,存储由称为Tungsten的组件处理,该组件比Java / Scala对象更有效。
图2显示了Spark数据帧的API,实现和存储。
你学到了什么
希望您现在对Spark如何存储数据以及如何通过dataframe API更好地了解。 您还了解了内置函数以及在哪里可以找到它们。 最重要的是,您现在具有构建更复杂的分析和机器学习的基础……这就是本系列教程的下一步。
走得更远
- 要在Mac上安装和理解更好的Informix,请阅读“ Mac 10.12上的Informix 12.10,并带有一些Java 8:苹果,咖啡和一个伟大的数据库的故事 ”(自私无耻的插件;我写了这本书)。
- 从我的GitHub存储库下载本教程中的所有代码。 如何使用JDBC将IBM Informix数据传输到Apache Spark 。 别忘了喜欢叉!
- Java参考中的Dataframe API 。
- Spark SQL函数列表 。
- 了解有关钨和低级存储的更多信息 。
翻译自: https://www.ibm.com/developerworks/opensource/library/ba-offloading-informix-data-spark-2/index.html
数据分析,数据挖掘基本算法