RFM 用戶價值模型
1 需求
用戶畫像
- 假設(shè)我是一個市場營銷者, 在做一次活動之前, 我可能會思考如下問題
- 誰是我比較有價值的客戶?
- 誰是比較有潛力成為有價值的客戶?
- 誰快要流失了?
- 誰能夠留下來?
- 誰會關(guān)心這次活動?
- 其實上面這些思考, 都圍繞一個主題 價值
-
RFM是一個最常見的用來評估價值的和潛在價值的工具
2 RFM 是什么RFM
- 通過最后一次消費距今時間, 單位時間內(nèi)的消費頻率, 平均消費金額來評估一個人對公司的價值, 可以理解為 RFM 是一個集成的值, 如下
RFM = Rencency(最后一次消費時間), Frequency(消費頻率), Monetary(消費金額) - RFM 模型可以說明如下事實:
- 最近一次購買時間越近, 用戶對促銷越有感
- 購買頻率越高, 對我們滿意度就越高
- 消費金額越大, 越有錢, 越是高消費人群
RFM模型
3 RFM的實際應(yīng)用
RFM實際應(yīng)用
4 高維空間模型
高維空間模型
5 通過打分統(tǒng)一量綱
- R: 1-3天=5分,4-6天=4分,7-9天=3分,10-15天=2分,大于16天=1分
- F: ≥200=5分,150-199=4分,100-149=3分,50-99=2分,1-49=1分
- M: ≥20w=5分,10-19w=4分,5-9w=3分,1-4w=2分,<1w=1分
val rScore: Column = when('r.>=(1).and('r.<=(3)), 5)
.when('r >= 4 and 'r <= 6, 4)
.when('r >= 7 and 'r <= 9, 3)
.when('r >= 10 and 'r <= 15, 2)
.when('r >= 16, 1)
.as("r_score")
val fScore: Column = when('f >= 200, 5)
.when(('f >= 150) && ('f <= 199), 4)
.when((col("f") >= 100) && (col("f") <= 149), 3)
.when((col("f") >= 50) && (col("f") <= 99), 2)
.when((col("f") >= 1) && (col("f") <= 49), 1)
.as("f_score")
val mScore: Column = when(col("m") >= 200000, 5)
.when(col("m").between(100000, 199999), 4)
.when(col("m").between(50000, 99999), 3)
.when(col("m").between(10000, 49999), 2)
.when(col("m") <= 9999, 1)
.as("m_score")
6 模型訓(xùn)練與預(yù)測
- RFMTrainModel 訓(xùn)練模型, 保存模型到 HDFS 中, 調(diào)度周期, 一個月執(zhí)行一次
- RFMPredictModel 預(yù)測模型, 從 HDFS 中讀取聚類模型, 對整個數(shù)據(jù)集進行預(yù)測, 每天一次
def process(source: DataFrame): DataFrame = {
val assembled = assembleDataFrame(source)
val regressor = new KMeans()
.setK(7)
.setSeed(10)
.setMaxIter(10)
.setFeaturesCol("features")
.setPredictionCol("predict")
regressor.fit(assembled).save(MODEL_PATH)
null
}
val assembled = RFMModel.assembleDataFrame(source)
val kmeans = KMeansModel.load(RFMModel.MODEL_PATH)
val predicted = kmeans.transform(assembled)
// 找到 kmeans 生成的組號和 rule 之間的關(guān)系
val sortedCenters: IndexedSeq[(Int, Double)] = kmeans.clusterCenters.indices // IndexedSeq
.map(i => (i, kmeans.clusterCenters(i).toArray.sum))
.sortBy(c => c._2).reverse
val sortedDF = sortedCenters.toDF("index", "totalScore")
RFE 活躍度
- 類似 RFM, 我們使用 RFE 計算用戶的活躍度
- RFE = R (最近一次訪問時間) + F (特定時間內(nèi)訪問頻率) + E (活動數(shù)量)
- R = datediff(date_sub(current_timestamp(),60), max('log_time))
- F = count('loc_url)
- E = countDistinct('loc_url)
- R:0-15天=5分,16-30天=4分,31-45天=3分,46-60天=2分,大于61天=1分
- F:≥400=5分,300-399=4分,200-299=3分,100-199=2分,≤99=1分
- E:≥250=5分,230-249=4分,210-229=3分,200-209=2分,1=1分
PSM 價格敏感度模型
- PSM 用于統(tǒng)計用戶的價格敏感度
- 對于不同級別價格敏感的用戶可以實行不同程度的營銷
1 PSM計算公式
- PSM Score = 優(yōu)惠訂單占比 + (平均優(yōu)惠金額 / 平均每單應(yīng)收) + 優(yōu)惠金額占比
- 優(yōu)惠訂單占比
- 優(yōu)惠訂單 / 總單數(shù)
- 優(yōu)惠訂單 = 優(yōu)惠的訂單數(shù)量 / 總單數(shù)
- 未優(yōu)惠訂單 = 未優(yōu)惠的訂單數(shù)量 / 總單數(shù)
- 平均優(yōu)惠金額
- 總優(yōu)惠金額 / 優(yōu)惠單數(shù)
- 平均每單應(yīng)收
- 總應(yīng)收 / 總單數(shù)
- 優(yōu)惠金額占比
- 總優(yōu)惠金額 / 總應(yīng)收金額
// 應(yīng)收金額
val receivableAmount = ('couponCodeValue + 'orderAmount).cast(DoubleType) as "receivableAmount"
// 優(yōu)惠金額
val discountAmount = 'couponCodeValue.cast(DoubleType) as "discountAmount"
// 實收金額
val practicalAmount = 'orderAmount.cast(DoubleType) as "practicalAmount"
// 是否優(yōu)惠
val state = when(discountAmount =!= 0.0d, 1) // =!=是column的方法
.when(discountAmount === 0.0d, 0)
.as("state")
// 優(yōu)惠訂單數(shù)
val discountCount = sum('state) as "discountCount"
// 訂單總數(shù)
val totalCount = count('state) as "totalCount"
// 優(yōu)惠總額
val totalDiscountAmount = sum('discountAmount) as "totalDiscountAmount"
// 應(yīng)收總額
val totalReceivableAmount = sum('receivableAmount) as "totalReceivableAmount"
// 平均優(yōu)惠金額
val avgDiscountAmount = ('totalDiscountAmount / 'discountCount) as "avgDiscountAmount"
// 平均每單應(yīng)收
val avgReceivableAmount = ('totalReceivableAmount / 'totalCount) as "avgReceivableAmount"
// 優(yōu)惠訂單占比
val discountPercent = ('discountCount / 'totalCount) as "discountPercent"
// 平均優(yōu)惠金額占比
val avgDiscountPercent = (avgDiscountAmount / avgReceivableAmount) as "avgDiscountPercent"
// 優(yōu)惠金額占比
val discountAmountPercent = ('totalDiscountAmount / 'totalReceivableAmount) as "discountAmountPercent"
// 優(yōu)惠訂單占比 + (平均優(yōu)惠金額 / 平均每單應(yīng)收) + 優(yōu)惠金額占比
val psmScore = (discountPercent + (avgDiscountPercent / avgReceivableAmount) + discountAmountPercent) as "psm"
2 聚類算法原理
- 選擇 K 個點作為初始中點
- 計算每個中點到相近點的距離, 將相近的點聚在一類(簇)
- 歐式距離
- 重新計算每個簇的中點
-
重復(fù)迭代上面步驟, 直至不再發(fā)生變化
聚類算法原理
3 確定K - 肘部法則
- 根據(jù)損失函數(shù), 計算每一個 K 的情況下, 總體上的損失
-
繪制圖形, 找到拐點, 就是合適的 K
肘部法則
4 模型訓(xùn)練與迭代計算
val kArray = Array(2, 3, 4, 5, 6, 7, 8)
val wssseMap = kArray.map(f = k => {
val kmeans = new KMeans()
.setK(k)
.setMaxIter(10)
.setPredictionCol("prediction")
.setFeaturesCol("features")
val model: KMeansModel = kmeans.fit(vectored)
import spark.implicits._
// mlLib計算損失函數(shù)
val vestors: Array[OldVector] = model.clusterCenters.map(v => OldVectors.fromML(v))
val libModel: LibKMeansModel = new LibKMeansModel(vestors)
val features = vectored.rdd.map(row => {
val ve = row.getAs[Vector]("features")
val oldVe: OldVector = OldVectors.fromML(ve)
oldVe
})
val wssse: Double = libModel.computeCost(features)
(k, wssse)
}).toMap
分類模型-預(yù)測性別
- 購物性別模型的意義有兩種:
- 通過用戶購物的行為, 預(yù)測用戶性別
- 通過用戶購物的行為, 判定用戶的購物性別偏好
1 預(yù)置標簽,量化屬性
|memberId| color|productType|gender|colorIndex| color| productType|gender|productTypeIndex| features|featuresIndex|
+--------+------+-----------+------+----------+------------------+---------------+------+----------------+-----------+-------------+
| 4|櫻花粉| 智能電視| 1| 14.0|櫻花粉| 智能電視| 1| 13.0|[14.0,13.0]| [14.0,13.0]|
| 4|櫻花粉| 智能電視| 1| 14.0| 藍色| Haier/海爾冰箱| 0| 1.0| [14.0,1.0]| [14.0,1.0]|
val label = when('ogColor.equalTo("櫻花粉")
.or('ogColor.equalTo("白色"))
.or('ogColor.equalTo("香檳色"))
.or('ogColor.equalTo("香檳金"))
.or('productType.equalTo("料理機"))
.or('productType.equalTo("掛燙機"))
.or('productType.equalTo("吸塵器/除螨儀")), 1)
.otherwise(0)
.alias("gender")
2 決策樹算法
-
決策樹是一個監(jiān)督學(xué)習(xí)算法, 需要先對數(shù)據(jù)集人工打上標簽, 此處簡化整體流程, 通過簡單的匹配, 先預(yù)置所需要的標簽
決策樹算法模型
3 算法工程與模型評估
val featureVectorIndexer = new VectorIndexer()
.setInputCol("features")
.setOutputCol("featuresIndex")
.setMaxCategories(3)
val decisionTreeClassifier = new DecisionTreeClassifier()
.setFeaturesCol("featuresIndex")
.setLabelCol("gender")
.setPredictionCol("predict")
.setMaxDepth(5)
.setImpurity("gini")
val pipeline = new Pipeline()
.setStages(Array(colorIndexer, productTypeIndexer, featureAssembler, featureVectorIndexer, decisionTreeClassifier))
val Array(trainData, testData) = source.randomSplit(Array(0.8, 0.2))
val model: PipelineModel = pipeline.fit(trainData)
val pTrain = model.transform(trainData)
val tTrain = model.transform(testData)
val accEvaluator = new MulticlassClassificationEvaluator()
.setPredictionCol("predict")
.setLabelCol("gender")
.setMetricName("accuracy")//精準度