前言
最近在研究pyspark,用到的主要是pyspark的sql模块和ml模块。
既然用到sql模块,便免不了要涉及dataframe。
至于dataframe的基本操作,大家可以自行百度或者必应,很容易上手的啦。
但是坑很多,要慢慢调,要耐心。
上次讲到了pyspark的dataframe如何做词向量,详情参见Pyspark系列笔记–如何在一个pysprk Dataframe上训练word2vec模型,这次就是在做完词向量以后要对dataframe进行拼接时,遇到了各种坑,在此写个博客,以便各位参考。
本次实验环境:
pyspark 1.5.0
python 2.7
Step 1. 继续上次的词向量
from pyspark.ml.feature import Word2Vec
model = Word2Vec(vectorSize=5, seed=42, inputCol="name", outputCol="model").fit(jsonDF)
model.getVectors().show()
结果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/d36d9cb064fbb810ddaafd16102eb4d4.jpeg)
图1
训练完词向量以后,然而我们的目的是把一个句子变成向量啊,在这个例子中,我们是要把下图中的name那一列做成向量。
![](https://i-blog.csdnimg.cn/blog_migrate/25012433a25778dddebd099618aef87c.jpeg)
图2
怎么办呢?别慌。难道训练出模型了还怕变不了句子咋地?spark的开发者那么厉害,怎么可能没有函数呢?
wordVecs = model.transform(sourceDF.select(['name','pid']))
wordVecs.show()
就这样我们就可以愉快的把每一个句子都变成向量了。结果如下:
图3
Step 2. Join到原始的dataFrame中去
一般而言,我们是要将model那一列作为一个新的特征,需要拼接到原始数据的dataframe中去,这就需要用到一个函数join,其作用在pyspark的API中写的很清楚。
参照它的写法,我们可以写成如下形式:
playlistDF = wordVecs.join(sourceDF,wordVecs.name==sourceDF.name).drop(sourceDF.name)
playlistDF.show()
后面为什么要drop对应的name呢,因为如果你是用等号的方式设置’on’参数的话,两个表中的Key列都会增加到新的DataFrame中去,如果你写成如下形式:
wordVecs.join(sourceDF,‘name’)
则不需要drop,因为只会增加一列。
But!!
这样是会有问题的,我们的name是一个list,如图3所示,这样判断相等真的不会有问题吗??
笔者亲自验证,是会有问题的,但是具体原因不明,如果你是parallize以后再createDataframe以后这么相等不会有问题,但是你要是从文件中读入那就会有问题。
Then what can we do?
其实很简单,我们只要用主键去连接Dataframe就没问题啦,我就不信主键还是个list咋地?
wordVecs = model.transform(sourceDF.select(['name','id']))
playlistDF = wordVecs.join(sourceDF,wordVecs.id==sourceDF.id).drop(sourceDF.id)
playlistDF.show()
这里有两个地方需要注意:
1. 你在transform的时候就应该把主键包含进来,不然你再transform以后是找不到主键的,为什么可以包含进来,那是因为我们模型在训练的时候,有inputCol这个属性。
2. 你会神奇的发现,如果你只是做一次这样的join,那是不会出现问题的,如果你连续有两列要拼接,你做了两次这样的join,第二次就会出问题,大概是报一个AnalysisError:resolved attribute …#。大致是因为DataFrame里面会出现重复的列名太多啦,比如说这里,你没有删除name,用id去join,最终得到的DataFrame里面会有两个列都叫name(因为你原始的DataFrame里面也有一个列叫name),最好的做法是:
wordVecs = model.transform(sourceDF.select(['name','id'])).drop('name')
playlistDF = wordVecs.join(sourceDF,wordVecs.id==sourceDF.id).drop(sourceDF.id)
playlistDF.show()
这样可以保证你顺利的进行多次join操作。
最终结果如下: