PySpark中RDD与DataFrame

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhufenghao/article/details/80712480

1. 弹性数据集RDD

RDD是一个抽象的分布式数据集合,它提供了一系列转化操作(例如基本的map()flatMap()filter(),类集合操作union()intersection()subtract())和行动操作(例如collect()count()take()top()reduce()foreach())。可以说,RDD是非常灵活的数据集合,其中可以存放类型相同或者互异的数据,同时可以指定任何自己期望的函数对其中的数据进行处理。
创建一个RDD:

# 从list中创建
rdd = sc.parallelize([1, '2', (3, 4), ['5', '6']])
# 从文件中读取
rdd = sc.textFile('\path\to\file')

还有一类RDD是key-value Pair RDD,即规定RDD每个元素都是一个二元组,其中第一个值是key,第二个值为value,key一般选取RDD中每个元素的一个字段。
创建一个Pair RDD:

# 创建一个普通RDD
rdd = sc.parallelize([('a', 1, 2), ('b', 3, 4), ('c', 5, 6)])
# 提取每个元素的第一个元素作为key剩余元素作为value创建Pair RDD
pair_rdd = rdd.map(lambda x: (x[0], x[1:]))

可以看到Pair RDD实质上仍然是一个普通的RDD,那为什么它要单独拿出来讲呢?
这是因为,Pair RDD由于有key的存在,与普通的RDD相比更加格式化,这种特性就会给Pair RDD赋予一些特殊的操作,例如groupByKey()可以将具有相同key进行分组,其结果仍然得到Pair RDD,然后利用mapValues()对相同key的value进行函数计算;reduceByKey()countByKey()sortByKey()等一系列“ByKey()”操作同理。
另外,两个Pair RDD具有像SQL一样的连接操作,例如两个Pair RDD进行join()后,具有相同key的元素的value会被放在一个元组里,key不相同的元素会被舍弃。leftOuterJoin()rightOuterJoin()fullOuterJoin()等操作同理。

2. Spark SQL中的DataFrame

Pair RDD已经被一定程度的格式化了,它的每个元素会具有key,但是value仍然具有很大的灵活性。DataFrame是一种完全格式化的数据集合,和数据库中的的概念比较接近,它每列数据必须具有相同的数据类型。也正是由于DataFrame知道数据集合所有的类型信息,DataFrame可以进行列处理优化而获得比RDD更优的性能。
在内部实现上,DataFrame是由Row对象为元素组成的集合,每个Row对象存储DataFrame的一行,Row对象中记录每个域=>值的映射,因而Row可以被看做是一个结构体类型。可以通过创建多个tuple/listdictRow然后构建DataFrame。
注:用dict构建DataFrame已经废弃了,推荐用Row

# 创建list的list
lists = [['a', 1], ['b', 2]]
# 构建具有默认生成的列_1、_2的DataFrame
dataframe = spark.createDataFrame(lists)

# 创建dict的list
dicts = [{'col1':'a', 'col2':1}, {'col1':'b', 'col2':2}]
# 构建具有列col1、col2的DataFrame
dataframe = spark.createDataFrame(dicts)

# 创建Row的list
rows = [Row(col1='a', col2=1), Row(col1='b', col2=2)]
# 构建具有列col1、col2的DataFrame
dataframe = spark.createDataFrame(rows)

虽然DataFrame被完全格式化了,但是其中每列可以存储的类型仍然是非常丰富的,包括基本的数据类型、list、tuple、dict和Row,这也就意味着所有的复杂数据类型都可以相互嵌套,从而解除了完全格式化的限制。例如,你可以在一列中存储list类型,而每行list按需存储不定长的数据。
那么,RDD和DataFrame还有哪些使用上的区别呢?

  • RDD:没有列名称,只能使用数字来索引;具有map()reduce()等方法并可指定任意函数进行计算;
  • DataFrame:一定有列名称(即使是默认生成的),可以通过.col_name或者['col_name']来索引列;具有表的相关操作(例如select()filter()where()join),但是没有map()reduce()等方法。

3. RDD转换为DataFrame

什么样的RDD可以转换为DataFrame?
RDD灵活性很大,并不是所有RDD都能转换为DataFrame,而那些每个元素具有一定相似格式的时候才可以。

为什么RDD需要转换为DataFrame?
当RDD进行类似的相应操作时,都需要指定相应的函数,转换为DataFrame书写更简单,并且执行效率高。

怎么样将RDD转换为DataFrame?
就像之前的例子一样,可以利用

dataframe = spark.createDataFrame(rdd, schema=None, samplingRatio=None)

来将RDD转换为DataFrame,其中的参数设置需要注意:
schema:DataFrame各列类型信息,在提前知道RDD所有类型信息时设定。例如

schema = StructType([StructField('col1', StringType()),
         StructField('col2', IntegerType())])

samplingRatio:推测各列类型信息的采样比例,在未知RDD所有类型信息时,spark需要根据一定的数据量进行类型推测;默认情况下,spark会抽取前100的RDD进行推测,之后在真正将RDD转换为DataFrame时如果遇到类型信息不符会报错 Some of types cannot be determined by the first 100 rows, please try again with sampling 。同理采样比例较低,推测类型信息也可能错误。

4. DataFrame转换为RDD

有时候DataFrame的相关操作不能处理一些问题,例如需要对一些数据利用指定的函数进行计算时,就需要将DataFrame转换为RDD。DataFrame可以直接利用.rdd获取对应的RDD对象,此RDD对象的每个元素使用Row对象来表示,每列值会成为Row对象的一个域=>值映射。例如

dataframe = spark.createDataFrame([Row(col1='a', col2=1), Row(col1='b', col2=2)])
>>> 
+----+----+
|col1|col2|
+----+----+
|   a|   1|
|   b|   2|
+----+----+

rdd = dataframe.rdd
>>> [Row(col1=u'a', col2=1), Row(col1=u'b', col2=2)]

DataFrame转化后的RDD如果需要和一般形式的RDD进行操作(例如join),还需要做索引将数值从Row中取出,比如转化为Pair RDD可以这样操作

rdd = rdd.map(lambda x: [x[0], x[1:]])
>>> [[u'a', (1,)], [u'b', (2,)]]

注意:DataFrame转化的RDD可能包含Row(col1='a'),它和'a'是不同的对象,所以如果与一般的RDD进行join,还需要索引Row取出数值。

猜你喜欢

转载自blog.csdn.net/zhufenghao/article/details/80712480