spark dataframe实战
说明:该文基于spark-2.0+
dataframe介绍
dataframe是dataset的行的集合。
Dataset是分布式数据集合。Dataset是Spark 1.6+中添加的一个新接口,它提供了RDD的很多优点。 (强类型化,使用强大的lambda函数的功能),以及Spark SQL优化执行引擎的优点。数据集可以从JVM对象构建,然后使用函数转换(map,flatMap,filter等)进行操作。数据集API可用于Scala和Java。
Python不支持数据集API。但是由于Python的动态特性,数据集API的许多优点已经可用(即,您可以通过自然的row.columnName名称来访问行的字段)。R的情况是相似的。
一个DataFrame是一个数据集组织成命名列。它在概念上等同于关系数据库中的表或R / Python中的(dataframe)数据框,但是在实现引擎层面有更多的优化。
DataFrame可以从各种各样的源构建,例如:结构化数据文件,Hive中的表,外部数据库或现有的RDD。DataFrame API可用于Scala,Java,Python和R.在Scala和Java中,DataFrame由行数据集表示。在Scala API中,DataFrame只是Dataset [Row]的类型别名。而在Java API中,用户需要使用Dataset 来表示一个DataFrame。
在整篇文档中,我们经常将Scala / Java数据集作为DataFrames。
sparkSession
在spark-2.0以后,引入了sparkSession来对资源进行管理。包括管理spark Context等。创建一个sparkSession的代码如下。
import org.apache.spark.sql.SparkSession
val spark = SparkSession.builder().appName("Spark SQL basic example").config("spark.some.config.option", "some-value").getOrCreate()
// For implicit conversions like converting RDDs to DataFrames
import spark.implicits._
创建Dataframe
通过toDs来创建dataframe
val ds = Seq(1, 2, 3).toDS()
加载文件数据创建
dataframe可以加载各种格式的文件。
下面的例子会通过一个数据文件进行讲解,该文件的内容如下:
$ hadoop fs -cat /user/zxh/csvdata/csvdata
id,name,subject,score
1,n1,s1,10
2,n2,s2,20
3,n3,s3,30
3,n3,s1,20
4,n4,s2,40
5,n5,s3,50
6,n6,s1,60
7,n6,s2,40
8,n8,s3,90
8,n9,s1,30
9,n9,s1,20
9,n9,s2,70
- 加载csv文件
注意:spark是开始创建的sparkSession实体。
val spark = SparkSession.builder().appName("Spark SQL basic example").config("spark.some.config.option", "some-value").getOrCreate()
import spark.implicits._
val df = spark.read.csv("/user/hadoop/csvdata/csvdata")
scala> val df = spark.read.option("header",true).csv("/user/hadoop/csvdata/csvdata")
df: org.apache.spark.sql.DataFrame = [id: string, name: string ... 2 more fields]
scala> df.show()
+---+----+-------+-----+
| id|name|subject|score|
+---+----+-------+-----+
| 1| n1| s1| 10|
| 2| n2| s2| 20|
| 3| n3| s3| 30|
| 3| n3| s1| 20|
| 4| n4| s2| 40|
| 5| n5| s3| 50|
| 6| n6| s1| 60|
| 7| n6| s2| 40|
| 8| n8| s3| 90|
| 8| n9| s1| 30|
| 9| n9| s1| 20|
| 9| n9| s2| 70|
+---+----+-------+-----+
- 加载json文件
$ hadoop fs -cat /user/zxh/jsondata/jsondata
{ "name":"Yin", "address":{ "city":"Columbus", "state":"Ohio" }}
scala> val df = spark.read.json("/user/zxh/jsondata/jsondata")
df: org.apache.spark.sql.DataFrame = [address: struct<city: string, state: string>, name: string]
scala> df.show()
+---------------+----+
| address|name|
+---------------+----+
|[Columbus,Ohio]| Yin|
+---------------+----+
- 加载parquet文件
val peopleDF = spark.read.format("json").load("examples/src/main/resources/people.json")
peopleDF.select("name", "age").write.format("parquet").save("namesAndAges.parquet")
val sqlDF = spark.sql("SELECT * FROM parquet.`examples/src/main/resources/users.parquet`")
val usersDF = spark.read.load("examples/src/main/resources/users.parquet")
usersDF.select("name", "favorite_color").write.save("namesAndFavColors.parquet")
从JDBC加载数据
- 通过JDBC读取数据
// Note: JDBC loading and saving can be achieved via either the load/save or jdbc methods
// Loading data from a JDBC source
val jdbcDF = spark.read
.format("jdbc")
.option("url", "jdbc:postgresql:dbserver")
.option("dbtable", "schema.tablename")
.option("user", "username")
.option("password", "password")
.load()
val connectionProperties = new Properties()
connectionProperties.put("user", "username")
connectionProperties.put("password", "password")
val jdbcDF2 = spark.read
.jdbc("jdbc:postgresql:dbserver", "schema.tablename", connectionProperties)
// Saving data to a JDBC source
jdbcDF.write
.format("jdbc")
.option("url", "jdbc:postgresql:dbserver")
.option("dbtable", "schema.tablename")
.option("user", "username")
.option("password", "password")
.save()
jdbcDF2.write
.jdbc("jdbc:postgresql:dbserver", "schema.tablename", connectionProperties)
// Specifying create table column data types on write
jdbcDF.write
.option("createTableColumnTypes", "name CHAR(64), comments VARCHAR(1024)")
.jdbc("jdbc:postgresql:dbserver", "schema.tablename", connectionProperties)
操作Dataframe
- 为dataframe设置新的字段名(列名)
scala> val newNames = Seq("id1", "name1", "score1")
newNames: Seq[String] = List(id1, name1, score1)
scala> val dfRenamed = df.toDF(newNames: _*)
dfRenamed: org.apache.spark.sql.DataFrame = [id1: string, name1: string ... 1 more field]
scala> dfRenamed.show()
+---+-----+------+
|id1|name1|score1|
+---+-----+------+
| 1| n1| 10|
| 2| n2| 20|
| 3| n3| 30|
| 4| n4| 40|
| 5| n5| 50|
| 6| n6| 60|
| 7| n6| 60|
| 8| n8| 60|
| 8| n9| 60|
| 9| n9| 60|
+---+-----+------+
- 添加一个新列:通过其他列的值来添加
为了添加一列,我们可以使用withColumn函数来
scala> val df2 = df.withColumn("newscore", df("score")+50)
df2: org.apache.spark.sql.DataFrame = [id: string, name: string ... 2 more fields]
scala> df2.show()
+---+----+-----+--------+
| id|name|score|newscore|
+---+----+-----+--------+
| 1| n1| 10| 60.0|
| 2| n2| 20| 70.0|
| 3| n3| 30| 80.0|
| 4| n4| 40| 90.0|
| 5| n5| 50| 100.0|
| 6| n6| 60| 110.0|
| 7| n6| 60| 110.0|
| 8| n8| 60| 110.0|
| 8| n9| 60| 110.0|
| 9| n9| 60| 110.0|
+---+----+-----+--------+
- 条件去重
通过select可以选择要返回的列,通过where函数可以对列进行筛选。在使用distinct函数去重。
scala> val df3 = df.select("name").where($"score">50).distinct()
df3: org.apache.spark.sql.Dataset[org.apache.spark.sql.Row] = [name: string]
scala> df3.show()
+----+
|name|
+----+
| n8|
| n9|
| n6|
+----+
- 根据多列的值去重
可以看到以下代码按name,score这两列的值进行去重。
scala> val df3 = df.select("name", "score").where($"score">50).distinct()
df3: org.apache.spark.sql.Dataset[org.apache.spark.sql.Row] = [name: string, score: string]
scala> df3.show()
+----+-----+
|name|score|
+----+-----+
| n9| 60|
| n8| 60|
| n6| 60|
+----+-----+
- 如何把dataframe的整数列转换成字符串
以下代码把df的col1列的值转换成字符串类型:
var df2 = df.withColumn("col1", df("col1").cast("string"))