用户画像第四章(企业级360°用户画像_标签开发_挖掘标签_价格敏感度模型-PSM)

价格敏感度模型-PSM
2.3.PSM模型引入
2.3.1.大数据杀熟

http://www.sohu.com/a/240094402_374686
http://www.qianjia.com/zhike/html/2019-10/11_13453.html
https://cloud.tencent.com/developer/article/1450512

2.3.2.PSM模型在网游中的运用
PSM(Price Sensitivity Measurement)模型是在70年代由Van Westendrop所创建,其目的在于衡量目标用户对不同价格的满意及接受程度,了解其认为合适的产品价格,从而得到产品价格的可接受范围。PSM的定价是从消费者接受程度的角度来进行的,既考虑了消费者的主观意愿,又兼顾了企业追求最大利益的需求。此外,其价格测试过程完全基于所取购买对象的自然反应,没有涉及到任何竞争对手的信息。虽然缺少竞品信息是PSM的缺陷所在,由于每个网络游戏均自成一个虚拟的社会体系,一般来说,其中每个道具或服务的销售均没有竞品(除非开发组自己开发了类似的道具或服务,产生了内部竞争),从这个角度来说,PSM模型比较适合用于网游中的道具或服务的定价。此外,该模型简洁明了,操作简单,使用非常方便。
2.3.2.1.PSM模型实施具体步骤
第一步:通过定性研究,设计出能够涵盖产品可能的价格区间的价格梯度表。该步骤通常对某一产品或服务追问被访者4个问题,并据此获得价格梯度表。梯度表的价格范围要涵盖所有可能的价格点,最低和最高价格一般要求低于或高出可能的市场价格的三倍以上。
(1)便宜的价格:对您而言什么价格该道具/服务是很划算,肯定会购买的?
(2)太便宜的价格:低到什么价格,您觉得该道具/服务会因为大家都可以随便用,而对自己失去吸引力(或对游戏造成一些不良影响)?
(3)贵的价格:您觉得“有点高,但自己能接受”的价格是多少?
(4)太贵的价格:价格高到什么程度,您肯定会放弃购买?
第二步:取一定数量有代表性的样本,被访者在价格梯度表上做出四项选择:有点低但可以接受的价格,太低而不会接受的价格,有点高但可以接受的价格,太高而不会接受的价格。
第三步:对所获得的样本数据绘制累计百分比曲线图,四条曲线的交点得出产品的合适价格区间以及最优定价点和次优定价点。
用户画像第四章(企业级360°用户画像_标签开发_挖掘标签_价格敏感度模型-PSM)_第1张图片
用户画像第四章(企业级360°用户画像_标签开发_挖掘标签_价格敏感度模型-PSM)_第2张图片
用户画像第四章(企业级360°用户画像_标签开发_挖掘标签_价格敏感度模型-PSM)_第3张图片
2.3.2.2.PSM模型的缺陷及解决
存在的问题
第一:只考虑到了消费者的接受率,忽视了消费者的购买能力,即只追求最大的目标人群数。但事实上,即使消费者觉得价格合理,受限于购买力等因素,也无法购买。
第二:研究中消费者可能出于各种因素(比如让价格更低能让自己收益,出于面子问题而抬高自己能接受的价格等)有意或无意地抬高或压低其接受的价格。由于消费者知道虚拟世界中的产品(道具或服务)没有成本,其压低价格的可能性较高。
第三:没有考虑价格变化导致的购买意愿(销量)变化。
解决方案
第一:为了避免购买力的影响, 问卷或访谈研究中要强调“定这个价格,以自己目前的情况是否会购买”,而非仅仅去客观判断该产品值多少钱。
第二:为了解决玩家抬高或压低价格的问题,可以增大样本量,预期随机误差可以相互抵消。
第三:仅仅从曲线获得最优价格,受到玩家压低或抬高价格的影响较大。由于该误差可能是系统误差,对此,可以用所获得的价格区间设计不同的价格方案,然后设计组间实验设计,每个参与研究的消费者只接触其中一种或几种价格方案,并对该价格方案下是否购买及购买数量做出决策,通过计算那种价格方案下玩家消费金钱量最高来分析出最佳价格方案。如下表。
第四:通过前一条中提到的组间实验设计,可以计算出不同价格下玩家购买意愿的变化,从而得知价格调整会对整体收益带来的影响。此外,价格接受比例还可以作为消费者对某价格满意度的指标,用于计算某价格下企业该产品的良性收益。
注意,我们的上述对策部分基于统计学和实验心理学理论,部分基于我们工作中的实践,欢迎大家讨论和优化。
用户画像第四章(企业级360°用户画像_标签开发_挖掘标签_价格敏感度模型-PSM)_第4张图片
2.3.3.2.确定了功能/服务,该如何为其定价
不是每个功能的出现都是为了实现用户更好的操作而存在的,比如购物车、收藏夹之类的功能。还有一些功能的存在是为了能够赚钱的!是不是很直接!是不是说到了很多人的心里去!比如说卖东西寄快递,卖家愿意给你送货上门,为你提供这个功能虽然是为了用户体验更好,说到底还是起码不赚钱但不亏本的。那么快递费定价多少合适?(我就是举个例子,不要告诉我快递费多少钱是快递公司说的算).
用户画像第四章(企业级360°用户画像_标签开发_挖掘标签_价格敏感度模型-PSM)_第5张图片
代码实现:

package cn.itcast.up.ml

import cn.itcast.up.base.BaseModel2
import cn.itcast.up.common.HDFSUtils
import org.apache.spark.ml.clustering.{KMeans, KMeansModel}
import org.apache.spark.ml.feature.VectorAssembler
import org.apache.spark.sql._

import scala.collection.{immutable, mutable}

/**
  * Author itcast
  * Date 2019/10/28 21:01
  * Desc 价格敏感度模型Price Sensitivity Meter
  * 有时在实际业务中,会把用户分为3-5类,
  * 比如分为极度敏感、较敏感、一般敏感、较不敏感、极度不敏感。
  * 然后将每类的聚类中心值与实际业务所需的其他指标结合,最终确定人群类别,判断在不同需求下是否触达或怎样触达。
  * 比如电商要通过满减优惠推广一新品牌的麦片,
  * 此时可优先选择优惠敏感且对麦片有消费偏好的用户进行精准推送,
  * 至于优惠敏感但日常对麦片无偏好的用户可暂时不进行推送或减小推送力度,
  * 优惠不敏感且对麦片无偏好的用户可选择不进行推送。
  * 可见,在实际操作中,技术指标评价外,还应结合业务需要,才能使模型达到理想效果。
  * //价格敏感度模型
  * //ra:receivableAmount 应收金额
  * //da:discountAmount 优惠金额
  * //pa:practicalAmount 实收金额
  *
  * //tdon 优惠订单数
  * //ton  总订单总数
  *
  * //ada 平均优惠金额
  * //ara 平均每单应收
  *
  * //tda 优惠总金额
  * //tra 应收总金额
  *
  * //tdonr 优惠订单占比(优惠订单数 / 订单总数)
  * //adar  平均优惠金额占比(平均优惠金额 / 平均每单应收金额)
  * //tdar  优惠金额占比(优惠总金额 / 订单总金额)
  *
  * //psm = 优惠订单占比 + 平均优惠金额占比 + 优惠总金额占比
  * //psmScore = tdonr + adar + tdar
  *
  * //>=1        极度敏感
  * //0.4~1      比较敏感
  * //0.1~0.3    一般敏感
  * //0          不太敏感
  * //<0         极度不敏感
  */
object PSMModel extends BaseModel2{
  def main(args: Array[String]): Unit = {
    execute()
  }

  /**
    * 获取标签id(即模型id,该方法应该在编写不同模型时进行实现)
    * @return
    */
  override def getTagID(): Int = 50

  /**
    * 开始计算
    *inType=HBase##zkHosts=192.168.10.20##zkPort=2181##
    * hbaseTable=tbl_orders##family=detail##selectFields=memberId,orderSn,orderAmount,couponCodeValue
    * @param fiveDF  MySQL中的5级规则 id,rule
    * @param hbaseDF 根据selectFields查询出来的HBase中的数据
    * @return userid,tagIds
    */
  override def compute(fiveDF: DataFrame, hbaseDF: DataFrame): DataFrame = {
    import spark.implicits._
    import scala.collection.JavaConversions._
    import org.apache.spark.sql.functions._

    //fiveDF.show()
    //fiveDF.printSchema()

    //hbaseDF.show(10,false)
    //hbaseDF.printSchema()
    //0.定义常量

    // psmScore = tdonr + adar + tdar
    // >=1      =   极度敏感
    // 0.4~1     =   比较敏感
    // 0.1~0.3   =   一般敏感
    // 0        =   不太敏感
    // <0       =   极度不敏感
    val psmScoreStr: String = "psmScore"
    val featureStr: String = "feature"
    val predictStr: String = "predict"


    //1.按用户id进行聚合获取用户价格敏感度模型PSM
    //ra:receivableAmount 应收金额
    val raColumn: Column = ('couponCodeValue + 'orderAmount) as "ra"
    //da:discountAmount 优惠金额
    val daColumn: Column = 'couponCodeValue as "da"
    //pa:practicalAmount 实收金额
    val paColumn: Column = 'orderAmount as "pa"
    //state 优惠订单为1, 非优惠订单为0
    val stateColumn: Column = functions
      .when('couponCodeValue =!= 0.0D, 1)
      .when('couponCodeValue === 0.0d, 0)
      .as("state")

    val tempDF: DataFrame = hbaseDF.select('memberId as "userId", raColumn, daColumn, paColumn, stateColumn)
    //tempDF.show(10)
 
    //tdon 优惠订单数
    val tdon: Column = sum('state) as "tdon"
    //ton 总订单总数
    val ton: Column  = count('state) as "ton"
    //tda 优惠总金额
    val tda: Column  = sum('da) as "tda"
    //tra 应收总金额
    val tra: Column = sum('ra) as "tra"

    //ada 平均优惠金额 === ('tda / 'tdon) //tdon优惠订单数可能为0
    //ara 平均每单应收 === ('tra / 'ton)

    //tdonr 优惠订单占比(优惠订单数 / 订单总数)===('tdon / 'ton)
    //adar 平均优惠金额占比(平均优惠金额 / 平均每单应收金额)=== ('ada / 'ara) === ('tda / 'tdon) / ('tra / 'ton)
    //tdar 优惠金额占比(优惠总金额 / 订单总金额)=== ('tda / 'tra)
    val psmColumn: Column  = (('tdon / 'ton) + (('tda / 'tdon) / ('tra / 'ton)) + ('tda / 'tra)) as psmScoreStr

    val psmScoreResult: DataFrame = tempDF.groupBy('userId)
      .agg(tdon, ton, tda, tra)
      .select('userId, psmColumn)
      .filter('psmScore.isNotNull)
    psmScoreResult.show(10)
  

    //3.聚类
    //为方便后续模型进行特征输入,需要部分列的数据转换为特征向量,并统一命名,VectorAssembler类就可以完成这一任务。
    //VectorAssembler是一个transformer,将多列数据转化为单列的向量列
    val vectorDF: DataFrame = new VectorAssembler()
      .setInputCols(Array(psmScoreStr))
      .setOutputCol(featureStr)
      .transform(psmScoreResult)//.na.drop
    vectorDF.show(10)

    val ks: List[Int] = List(2,3,4,5,6,7,8)
    //集合内误差平方和:Within Set Sum of Squared Error, WSSSE
    //对于那些无法预先知道K值的情况,可以通过WSSSE的计算构建出 K-WSSSE 间的相关关系,从而确定K的值,
    //一般来说,最优的K值即是 K-WSSSE 曲线拐点Elbow的位置
    //当然,对于某些情况来说,我们还需要考虑K值的语义可解释性,而不仅仅是教条地参考WSSSE曲线
    /*val WSSSEMap: mutable.Map[Int, Double] = mutable.Map[Int,Double]()
    for(k<- ks){
      val kMeans: KMeans = new KMeans()
        .setK(k)
        .setMaxIter(20)//最大迭代次数
        .setFeaturesCol(featureStr) //特征列
        .setPredictionCol(predictStr)//预测结果列
      val model: KMeansModel  = kMeans.fit(vectorDF)
      val WSSSE: Double = model.computeCost(vectorDF)
      WSSSEMap.put(k,WSSSE)
    }
    println("训练出来的K对应的WSSSE如下:")
    println(WSSSEMap)
    //训练出来的K对应的WSSSE如下:
    //Map(8 -> 0.1514833272915443, 2 -> 1.2676013396202386, 5 -> 0.3205627518783058, 4 -> 0.4336353113133625, 7 -> 0.16381158476261476, 3 -> 0.8254005284535515, 6 -> 0.20131609978279763)
    val mined: (Int, Double) = WSSSEMap.minBy(_._2)
    val minK: Int = mined._1
    println("训练出来的WSSSE值最小的K是:"+minK)
    //一般来说,同样的迭代次数和算法跑的次数,这个值越小代表聚类的效果越好。
    //但是在实际情况下,我们还要考虑到聚类结果的可解释性,不能一味的选择使 computeCost 结果值最小的那个 K
    */

    //5.使用WSSSE值最小的K创建模型
    val kMeans: KMeans = new KMeans()
      .setK(5)
      .setMaxIter(20)//最大迭代次数
      .setFeaturesCol(featureStr) //特征列
      .setPredictionCol(predictStr)//预测结果列

    //4.训练模型
    var model: KMeansModel = null
    val path = "/model/PSMModel/"
    if (HDFSUtils.getInstance().exists(path)){
      model = KMeansModel.load(path)
    }else{
      model = kMeans.fit(vectorDF)
      model.save(path)
    }

    //5.预测
    val result: DataFrame = model.transform(vectorDF)
    result.show(10)
   

    //问题: 每一个簇的ID是无序的,但是我们将分类簇和rule进行对应的时候,需要有序
    //7.按质心排序,质心大,该类用户价值大
    //[(质心id, 质心值)]
    val center: immutable.IndexedSeq[(Int, Double)] = for(i <- model.clusterCenters.indices) yield (i, model.clusterCenters(i).toArray.sum)
    val sortedCenter: immutable.IndexedSeq[(Int, Double)] = center.sortBy(_._2).reverse
    sortedCenter.foreach(println)
    //[(质心id, rule值)]
    val centerIdAndRule: immutable.IndexedSeq[(Int, Int)] = for(i <- sortedCenter.indices) yield (sortedCenter(i)._1, i+1)
    centerIdAndRule.foreach(println)

    val predictRuleDF: DataFrame = centerIdAndRule.toDF(predictStr,"rule")
    predictRuleDF.show()

    //8.将rule和5级规则进行匹配
    val ruleTagDF: DataFrame = fiveDF.as[(Long,String)].map(t=>(t._2,t._1)).toDF("rule","tag")
    ruleTagDF.show()

    val predictRuleTagDF: DataFrame = predictRuleDF.join(ruleTagDF,"rule")
    predictRuleTagDF.show()

    //Map[predict, tag]
    val map: Map[Long, Long] = predictRuleTagDF.as[(String,Long,Long)].map(t=>(t._2,t._3)).collect().toMap
    val predict2tag = functions.udf((predict:Long)=>{
      val tag = map(predict)
      tag
    })

    //9.predict->tag
    val newDF: DataFrame = result.select($"userId",predict2tag($"predict").as("tagIds"))
    newDF.show(10)
    newDF
  }
}

你可能感兴趣的:(用户画像)