ValueError: Some of types cannot be determined by the first 100 rows, please try again with sampling
在spark中试图将RDD转换成DataFrame时,有时会提示ValueError: Some of types cannot be determined by the first 100 rows, please try again with sampling,此时有2种解决方案:一是提高数据采样率(sampling ratio),二是显式声明要创建的DataFrame的数据结构,即schema。
第一种方案具体做法如下:
sqlContext.createDataFrame(rdd, samplingRatio=0.2)
其中的samplingRatio参数就是数据采样率,可以先设置为0.2试试,如果不行,可以继续增加。
其原理在于,RDD中元素的内部结构是未知的、不明确的,也就是说每个元素里面有哪些字段,每个字段是什么类型,这些都是不知道的,而DataFrame则要求对元素的内部结构有完全的知情权。
比如有一个RDD,其中的元素是dict类型,在使用sqlContext.createDataFrame()方法将该RDD转换成DataFrame的时候,如果括号中没有传入schema参数,那么spark会根据RDD中的前N个元素去猜测元素的内部结构,猜出里面都有哪些字段,每个字段是什么数据类型的。至于N是多少,取决于samplingRatio参数的值,如果不设该参数,则默认取前100个元素。上面代码中设置的samplingRatio是0.2,意味着spark将会取RDD中前20%的元素作为样本去推断元素中各个字段的数据类型。假如运气好,这20%的元素中各个字段都是齐备的,并且能根据字段值判断出该字段的类型,像下图这样:
['name':'Tom', 'age':23, 'gender':'M', 'is_married':False]
['name':'Jerry', 'age':31, 'gender':'F', 'is_married':True]
['name':'John', 'age':27, 'gender':'M', 'is_married':False]
那么spark就能根据上图中的这么多样本元素推断出元素内部有name、age、gender、is_married这几个字段,且字段类型分别是String,Int,String,Boolean,那就不会报任何错误。
但是,如果这20%的元素都缺失了某个字段,像下图这样:
['name':'Tom', 'age':23, 'gender':'M']
['name':'Jerry', 'age':31, 'gender':'F']
['name':'John', 'age':27, 'gender':'M']
本来元素中其实有4个字段的,但是抽样的20%数据里面都缺失了is_married字段,这样就会导致spark误以为元素中只有3个字段,然而当真的按照3个字段来处理时,却又发现后面有其他元素不止这3个字段,这就尴尬了,然后spark觉得自己被耍了,就罢工了。
还有一种情况也会导致spark报错,像下图这样:
['name':'Tom', 'age':23, 'gender':'M', 'is_married':None]
['name':'Jerry', 'age':31, 'gender':'F', 'is_married':None]
['name':'John', 'age':27, 'gender':'M', 'is_married':None]
这种情况下,由于抽样的20%的元素中is_married字段值全都是None,导致spark误以为该字段就是None类型的,当按照None处理时,如果发现后面有元素的is_married字段的值是True或者False,然后spark就崩溃了,不是说好了是None类型拉钩上吊一百年不许变吗?你给我来个True和False是个什么意思?
看到这里,智商超群的你可能已经想到,我主动告诉spark每个RDD元素内部结构是什么样的不就行了吗?没错,这就是我说的第二种方案,显式声明schema。在sqlContext.createDataFrame()方法中,有个参数叫schema,这个参数就是用来告诉spark每个RDD元素的结构的。具体做法是这样的:
from pyspark.sql.types import *
schema = StructType([
StructField("column_1", StringType(), True),
StructField("column_2", IntegerType(), True)
.
.
.
])
df = sqlContext.createDataFrame(rdd, schema=schema)
当你显式声明schema并应用到createDataFrame方法中后,就不再需要samplingRatio参数了。实际开发工程中建议使用显式声明schema的方案,这样可以避免出现因奇葩数据导致的错误。