大數據【企業級360°全方位用戶畫像】基於RFE模型的挖掘型標籤開發

寫在前面: 博主是一名軟件工程系大數據應用開發專業大二的學生,暱稱來源於《愛麗絲夢遊仙境》中的Alice和自己的暱稱。作爲一名互聯網小白,寫博客一方面是爲了記錄自己的學習歷程,一方面是希望能夠幫助到很多和自己一樣處於起步階段的萌新。由於水平有限,博客中難免會有一些錯誤,有紕漏之處懇請各位大佬不吝賜教!個人小站:http://alices.ibilibili.xyz/ , 博客主頁:https://alice.blog.csdn.net/
儘管當前水平可能不及各位大佬,但我還是希望自己能夠做得更好,因爲一天的生活就是一生的縮影。我希望在最美的年華,做最好的自己

        上一篇博客,已經爲大家介紹了基於RFM(用戶價值模型)的挖掘型標籤開發過程(👉大數據【企業級360°全方位用戶畫像】基於RFM模型的挖掘型標籤開發),本篇博客,我們來學習基於RFE(用戶活躍度模型)的挖掘型標籤開發。

在這裏插入圖片描述


RFE模型引入

        在正式開始實現需求之前,肯定要給各位朋友們解釋下。

        RFE模型可以說是RFM模型的變體 RFE模型基於用戶的普通行爲(非轉化或交易行爲)產生,它跟RFM類似都是使用三個維度做價值評估。

RFE詳解

        RFE 模型是根據會員最近一次訪問時間R( Recency)、訪問頻率 F(Frequency)和頁面互動度 E(Engagements)計算得出的RFE得分。 其中:

  • 最近一次訪問時間 R( Recency): 會員最近一次訪問或到達網站的時間。
  • 訪問頻率 F( Frequency):用戶在特定時間週期內訪問或到達的頻率。
  • 頁面互動度 E( Engagements):互動度的定義可以根據不同企業或行業的交互情況而定,例如可以定義爲頁面 瀏覽時間、瀏覽商品數量、視頻播放數量、點贊數量、轉發數量等

        在RFE模型中,由於不要求用戶發生交易,因此可以做未發生登錄、 註冊等匿名用戶的行爲價值分析, 也可以做實名用戶分析。該模型常用來做用戶活躍分羣或價值區分, 也可用於內容型(例如論壇、新聞、資訊等)企業的會員分析。

        RFM和 RFE模型的實現思路相同, 僅僅是計算指標發生變化。 對於RFE的數據來源, 可以從企業自己監控的用戶行爲日誌獲取,也可以從第三方網站分析工具獲得。

基於RFE模型的實踐應用

        在得到用戶的RFE得分之後, 跟 RFM 類似也可以有兩種應用思路:

        1:基於三個維度值做用戶羣體劃分和解讀,對用戶的活躍度做分析。 RFE得分爲 313 的會員說明其訪問頻率低, 但是每次訪問時的交互都非常不錯, 此時重點要做用戶回訪頻率的提升,例如通過活動邀請、 精準廣告投放、會員活動推薦等提升回訪頻率。

        2:基於RFE的彙總得分評估所有會員的活躍度價值,並可以做活躍度排名; 同時,該得分還可以作爲輸入維 度跟其他維度一起作爲其他數據分析和挖掘模型的輸入變量,爲分析建模提供基礎。

        比如:

  • 6忠誠 (1天內訪問2次及以上,每次訪問頁面不重複)
  • 5活躍 (2天內訪問至少1次)
  • 4迴流 (3天內訪問至少1次)
  • 3新增 (註冊並訪問)
  • 2不活躍 (7天內未訪問)
  • 1流失 (7天以上無訪問)

具體代碼實現

        大家看到這裏,應該都發現了,RFE模型和之前我們介紹過的RFM模型非常類似,只不過一個是用戶價值模型,一個是用戶活躍度模型。

        因爲同爲挖掘型標籤的開發,所以流程上大部分的內容都是相似的,下面博主就不詳細分步驟介紹了😅,關於代碼中有任何的疑惑,可以私信聯繫我喲~

import com.czxy.base.BaseModel
import org.apache.spark.ml.clustering.{KMeans, KMeansModel}
import org.apache.spark.ml.feature.VectorAssembler
import org.apache.spark.sql.expressions.UserDefinedFunction
import org.apache.spark.sql.{Column, DataFrame, SparkSession, functions}

import scala.collection.immutable

/*
 * @Author: Alice菌
 * @Date: 2020/6/24 08:25
 * @Description: 

       用戶的活躍度標籤開發
 */
object RFEModel extends BaseModel{

  override def setAppName: String = "RFEModel"

  override def setFourTagId: String = "176"

  override def getNewTag(spark: SparkSession, fiveTagDF: DataFrame, hbaseDF: DataFrame): DataFrame = {

    // 展示MySQL的五級標籤數據
    fiveTagDF.show()
    //+---+----+
    //| id|rule|
    //+---+----+
    //|177|   1|
    //|178|   2|
    //|179|   3|
    //|180|   4|
    //+---+----+

    // 展示HBase中的數據
    hbaseDF.show(false)
    //+--------------+--------------------+-------------------+
    //|global_user_id|             loc_url|           log_time|
    //+--------------+--------------------+-------------------+
    //|           424|http://m.eshop.co...|2019-08-13 03:03:55|
    //|           619|http://m.eshop.co...|2019-07-29 15:07:41|
    //|           898|http://m.eshop.co...|2019-08-14 09:23:44|
    //|           642|http://www.eshop....|2019-08-11 03:20:17|

    //RFE三個單詞
    //最近一次訪問時間R
    val recencyStr: String = "recency"
    //訪問頻率 F
    val frequencyStr: String = "frequency"
    //頁面互動度 E
    val engagementsStr: String = "engagements"

    // 特徵單詞
    val featureStr: String = "feature"  // 向量
    val predictStr: String = "predict"  // 分類
    // 計算業務數據
    // R(會員最後一次訪問或到達網站的時間)
    // F(用戶在特定時間週期內訪問或到達的頻率)
    // E(頁面的互動度,注意:一個頁面訪問10次,算1次)

    // 引入隱式轉換
    import spark.implicits._
    //引入java 和scala相互轉換
    import scala.collection.JavaConverters._
    //引入sparkSQL的內置函數
    import org.apache.spark.sql.functions._

    /* 分別計算 R F  E  的值 */

    // R 計算  最後一次瀏覽距今的時間
    val getRecency: Column = datediff(current_timestamp(),max("log_time")) as recencyStr

    // F 計算  頁面訪問次數(一個頁面訪問多次,就算多次)
    val getFrequency: Column = count("loc_url") as frequencyStr

    // E 計算 頁面互動度(一個頁面訪問多次,只計算一次)
    val getEngagements: Column = countDistinct("loc_url") as engagementsStr

    val getRFEDF: DataFrame = hbaseDF.groupBy("global_user_id")
      .agg(getRecency, getFrequency, getEngagements)

    getRFEDF.show(false)
    //+--------------+-------+---------+-----------+
    //|global_user_id|recency|frequency|engagements|
    //+--------------+-------+---------+-----------+
    //|296           |312    |380      |227        |
    //|467           |312    |405      |267        |
    //|675           |312    |370      |240        |
    //|691           |312    |387      |244        |


    //現有的RFM 量綱不統一,需要執行歸一化   爲RFM打分
    //計算R的分數
    val getRecencyScore: Column =
      when(col(recencyStr).between(0,15), 5)
        .when(col(recencyStr).between(16,30), 4)
        .when(col(recencyStr).between(31,45), 3)
        .when(col(recencyStr).between(46,60), 2)
        .when(col(recencyStr).gt(60), 1)
        .as(recencyStr)

    //計算F的分數
    val getFrequencyScore: Column =
      when(col(frequencyStr).geq(400), 5)
        .when(col(frequencyStr).between(300,399), 4)
        .when(col(frequencyStr).between(200,299), 3)
        .when(col(frequencyStr).between(100,199), 2)
        .when(col(frequencyStr).leq(99), 1)
        .as(frequencyStr)

    //計算E的分數
    val getEngagementScore: Column =
      when(col(engagementsStr).geq(250), 5)
        .when(col(engagementsStr).between(200,249), 4)
        .when(col(engagementsStr).between(150,199), 3)
        .when(col(engagementsStr).between(50,149), 2)
        .when(col(engagementsStr).leq(49), 1)
        .as(engagementsStr)

    // 計算 RFE 的分數
    val getRFEScoreDF: DataFrame = getRFEDF.select('global_user_id ,getRecencyScore,getFrequencyScore,getEngagementScore)

    getRFEScoreDF.show(false)
    //+--------------+-------+---------+-----------+
    //|global_user_id|recency|frequency|engagements|
    //+--------------+-------+---------+-----------+
    //|296           |1      |4        |4          |
    //|467           |1      |5        |5          |
    //|675           |1      |4        |4          |
    //|691           |1      |4        |4          |
    //|829           |1      |5        |5          |

    // 爲了方便計算,我們將數據轉換成向量
    val RFEFeature: DataFrame = new VectorAssembler()
      .setInputCols(Array(recencyStr, frequencyStr, engagementsStr))
      .setOutputCol(featureStr)
      .transform(getRFEScoreDF)

    RFEFeature.show()
   //+--------------+-------+---------+-----------+-------------+
   //|global_user_id|recency|frequency|engagements|      feature|
   //+--------------+-------+---------+-----------+-------------+
   //|           296|      1|        4|          4|[1.0,4.0,4.0]|
   //|           467|      1|        5|          5|[1.0,5.0,5.0]|
   //|           675|      1|        4|          4|[1.0,4.0,4.0]|
   //|           691|      1|        4|          4|[1.0,4.0,4.0]|

    // 利用KMeans算法,進行數據的分類
    val KMeansModel: KMeansModel = new KMeans()
      .setK(4) // 設置4類
      .setMaxIter(5) // 迭代計算5次
      .setFeaturesCol(featureStr) // 設置特徵數據
      .setPredictionCol(predictStr) // 計算完畢後的標籤結果
      .fit(RFEFeature)


    // 將其轉換成DF
    val KMeansModelDF: DataFrame = KMeansModel.transform(RFEFeature)

    KMeansModelDF.show()
    //+--------------+-------+---------+-----------+-------------+-------+
    //|global_user_id|recency|frequency|engagements|      feature|predict|
    //+--------------+-------+---------+-----------+-------------+-------+
    //|           296|      1|        4|          4|[1.0,4.0,4.0]|      1|
    //|           467|      1|        5|          5|[1.0,5.0,5.0]|      0|
    //|           675|      1|        4|          4|[1.0,4.0,4.0]|      1|
    //|           691|      1|        4|          4|[1.0,4.0,4.0]|      1|

    // 計算用戶的價值
    val clusterCentersSum: immutable.IndexedSeq[(Int, Double)] = for(i <- KMeansModel.clusterCenters.indices) yield (i,KMeansModel.clusterCenters(i).toArray.sum)
    val clusterCentersSumSort: immutable.IndexedSeq[(Int, Double)] = clusterCentersSum.sortBy(_._2).reverse

    clusterCentersSumSort.foreach(println)
    //(0,11.0)
    //(3,10.0)
    //(2,10.0)
    //(1,9.0)

    // 獲取到每種分類以及其對應的索引
    val clusterCenterIndex: immutable.IndexedSeq[(Int, Int)] = for(a <- clusterCentersSumSort.indices) yield (clusterCentersSumSort(a)._1,a)
    clusterCenterIndex.foreach(println)
    //(0,0)
    //(3,1)
    //(2,2)
    //(1,3)


    // 類別的價值從高到低,角標依次展示
    // 將其轉換成DF
    val clusterCenterIndexDF: DataFrame = clusterCenterIndex.toDF(predictStr,"index")
   clusterCenterIndexDF.show()
    //+-------+-----+
    //|predict|index|
    //+-------+-----+
    //|      0|    0|
    //|      3|    1|
    //|      2|    2|
    //|      1|    3|
    //+-------+-----+

    // 開始join
    val JoinDF: DataFrame = fiveTagDF.join(clusterCenterIndexDF,fiveTagDF.col("rule") ===  clusterCenterIndexDF.col("index"))

    JoinDF.show()
    //+---+----+-------+-----+
    //| id|rule|predict|index|
    //+---+----+-------+-----+
    //|177|   0|      0|    0|
    //|178|   1|      3|    1|
    //|179|   2|      2|    2|
    //|180|   3|      1|    3|
    //+---+----+-------+-----+

    val JoinDFS: DataFrame = JoinDF.select(predictStr,"id")

    //fiveTageList
   val fiveTageMap: Map[String, String] = JoinDFS.as[(String,String)].collect().toMap


    //7、獲得數據標籤(udf)
    // 需要自定義UDF函數
    val getRFMTags: UserDefinedFunction = udf((featureOut: String) => {

        fiveTageMap.get(featureOut)
    })

    val CustomerValueTag: DataFrame = KMeansModelDF.select('global_user_id .as("userId"),getRFMTags('predict).as("tagsId"))

    //CustomerValueTag.show(false)
    //|userId|tagsId|
    //+------+------+
    //|296   |180   |
    //|467   |177   |
    //|675   |180   |
    //|691   |180   |
    //|829   |177   |
    //|125   |180   |
    //|451   |180   |
    //|800   |180   |
    //|853   |179   |
    CustomerValueTag

  }


  def main(args: Array[String]): Unit = {
    exec()
  }
}

知識拓展

        這裏問大家一個問題,我們在調用K-Means算法進行聚類計算的時候,需要先設定一個K值,那麼這個K值具體是多少是如何得到的呢?

在這裏插入圖片描述

        就拿本題來說,如果你覺得我們在標籤系統中人爲地劃分成了四類,所以在進行聚類計算的時候,就把K設置成了4的話,那就理解錯了。
在這裏插入圖片描述
        大多數情況下,我們是無法預先確定K值的大小,所以有認真看過之前介紹機器學習常見面試題的朋友(👉關於機器學習的面試題,你又瞭解多少呢?),肯定對於肘部法則有一定的印象。

        手肘法的核心指標是

        集合內誤差平方和:Within Set Sum of Squared Error, WSSSE

        或者叫SSE(sum of the squared errors,誤差平方和),公式爲

在這裏插入圖片描述
        本次所開發的標籤,爲什麼K = 4 呢,接下倆,讓我們用代碼來講道理!

在這裏插入圖片描述
        我們在原有代碼的基礎上,添加上這幾行代碼,然後運行程序,等待結果。

    var  SSE: String =""
    //4  數據分類(不知道哪個類的活躍度高和低)
    for(k<-2  to 9){

      val model: KMeansModel = new KMeans()
        .setK(k)
        .setMaxIter(5)
        .setSeed(10)
        .setFeaturesCol(featureStr)
        .setPredictionCol(predictStr)
        .fit(RFEFeature)

      SSE=k+"-"+model.computeCost(RFEFeature)
      println(SSE)
    }

        友情提示:運行的過程可能非常漫長。爲啥勒,畢竟一次KMeans 計算就夠久的了,而這個循環要計算8次…
在這裏插入圖片描述
        不出所料,經過了N秒之後的等待,終於有了下面的結果。

2-185.03108672935983
3-23.668965517242555
4-0.0
5-0.0
6-0.0
7-0.0
8-0.0
9-0.0

        其實看到這裏,已經很清楚拐點就是在K = 4 的時候了。但是如果你跟我說莫得辦法,看不出來,菌哥還是有妙招!!!

        爲了讓數據更有畫面感,菌哥打開了很久沒上號的Echars😂,爲大家帶來了下面的簡單小圖。
在這裏插入圖片描述
        相信看到這裏,大家一定沒有疑問了吧~

小結

        如果以上過程中出現了任何的紕漏錯誤,煩請大佬們指正😅

        受益的朋友或對大數據技術感興趣的夥伴記得點贊關注支持一波🙏

        希望我們都能在學習的道路上越走越遠😉
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章