导读:生活中的选择行为无处不在,数据分析师面对的商业场景也存在大量的用户选择问题。系统、科学地研究用户选择问题,得到选择行为背后的客观规律并基于这些规律提出业务优化策略,这些能力对于数据分析师非常重要且极具价值。
本文将结合示例,讲解选择行为的经济学理论和计量分析模型,详细介绍用户选择行为的分析方法论。
作者:周银河
来源:大数据DT(ID:hzdashuju)
01 引子:以出行为例剖析选择行为的具体逻辑
下面以选择出行方式为例,剖析选择行为的具体逻辑,为后面的学习做好铺垫。
1. 出行选择的场景还原
出行就是“在某时从A点到达B点”,这一行为主要面临的选择是“以什么方式前往”,回想一下我们平时做出行选择时,是否有如下参考信息浮现在脑海。
  • 可以选择的交通方式有哪些?
  • 同程的人多不多?
  • 需要在什么时间到达?
  • 出行预算是多少?
  • 公共交通的便捷程度?
  • 出行方式是否受天气影响?
通常,我们会带着这些疑问打开出行类App看看各类交通方式的花费、耗时及路线,可能还会打开天气App看看未来一段时间是否下雨、是否有严重的雾霾,如图1-1所示。
▲图1-1 打开App查看出行路线和天气
2. 出行选择的决策逻辑
接下来,我们通过一个更加具体的案例说明出行选择的决策逻辑:有200个家庭要进行家庭旅行,每个家庭的情况不同(包括出行人数、目的地、家庭年收入等),每个家庭都会在飞机、火车、长途巴士及自驾车中选择一种作为出行方式。
不同的家庭会有不同的选择,在选择的表象下有着相似的决策逻辑。我们尝试置身于这个场景中,在大脑里构建一张类似图1-2的打分表
出行方式的属性可以主要归结为行程外(等车)耗时、行程中耗时、行程花费、舒适性等,确定这些出行方式的属性后,再结合自身属性(家庭收入、出行人数等),对每个选项进行定性/定量的排序,得到最适合自己的选择结果。
▲图1-2 旅行出行方式打分表
在选择的过程中,如果某个因素发生变化,就有可能对选择结果产生影响。例如:其他因素保持不变,由于航空公司促销,机票价格比火车票还便宜,你的选择是不是会从火车改为飞机呢?再假设,临行前你收获一笔超过预期的奖金,可支配的现金增多,是不是也会从火车改为飞机呢?
回忆一下我们生活中其他方面的选择,其实也秉持类似的方式。经济学家、心理学家经过长期研究,发现人类个体间的“选择之道”存在较高相似性,对这些相似性加以总结就形成了一系列选择行为的经济学理论。
这些长期沉淀下来的理论对于数据分析师来说是非常有价值的,它不仅能帮助我们从本质上理解相关计量选择模型的原理,还能在对业务方进行分析阐述时有理论背书,下面我们开始学习选择行为的经济学理论。
02 选择行为的经济学理论
选择行为主要有两个经济学派别,分别是理性人选择和行为经济学。尽管行为经济学在某些方面对理性人选择提出了挑战,但理性人选择仍然是群体选择行为分析的主流理论框架。本文后续内容均基于理性人选择。
1. 理性人选择理论
理性人选择是指经济决策的主体是充满理智的,他们对于所处环境具有完备的知识,能够找到实现目标的所有备选方案,有稳定且清晰的偏好,拥有很强的计算能力,能预测每种方案的选择后果,并依据某种衡量标准从这些方案中做出最优选择,选择的唯一目标是自身经济利益最大化。
结合上文的出行案例,我们先来解释什么是理性人选择
当我们选择出行方式时,首先确认每种交通方式的重要属性(行程外耗时、行程中耗时、行程花费、舒适性)、自身属性(家庭收入、出行人数)、客观因素(天气);然后基于这些信息为每个方案计算一个偏好值并排序;最终选择偏好值最大的选项。
如果选择了自驾车,那么说明综合多种因素,自驾是最能获得满足感的出行方式 。
2. 效用理论
消费者内心的满足感其实可以用一个经济学的词汇来表示,即“效用”。依照每种选择方案的“效用”排序进行选择的过程叫作“效用最大化”,这就是理性人选择理论最常用的准则,学术上的描述是当消费者面对一系列备选商品的时候,他们会清楚地计算每个商品的效用,并严格将所有商品按照效用排序,选择效用最大化的商品。
读到这里你也许会有疑问,尽管我们认同选择时确实是基于理性人选择理论,但如此抽象的理论怎样才能在实际的数据分析中发挥作用呢?哪怕知道了影响选择行为的因素,也无法得出效用的计算公式。此时,我们需要继续学习揭示性偏好理论。
3. 揭示性偏好理论
揭示性偏好理论由美国经济学家保罗·安东尼·萨缪尔森提出。该理论表明:可以结合消费者历史消费行为分析消费者偏好,通过统计分析的方式得到相关因素的量化影响。该理论有以下两个重要假设
  1. 消费者在进行实际消费行为时,若从备选方案中选择了一个选项,即为首选选项,则该选项效用是最大的。
  2. 在给定的消费者预算、商品价格等因素不变的情况下,如果消费者购买了某种产品,那么他将始终做出相同的选择。
在该理论提出之初,包含的影响因素有消费者预算、商品价格以及其他商品或消费者属性。对这些因素进行归纳和拓展,再结合上述假设,形成了离散选择模型的模型框架。
03 离散选择模型
了解了必要的理论知识后,我们开始学习离散选择模型(Discrete Choice Models,DCM)。
DCM不是单一模型,而是一个模型簇,它包含了一系列应对不同选择场景的模型,例如逻辑回归(Logistics Regression,LR)、多项Logit模型(Multinomial Logit Model,MNL)及嵌套Logit模型(Nested Logit Model,NL Model)等。
如图1-3所示,DCM主要包括5个部分,分别是决策者(决策者属性)、备选项集合、备选项属性、决策准则和选择结果,数学表达形式如下。
选择结果 = F(决策者, 备选项集合, 备选项属性)
其中,F是决策准则,即效用最大化准则。模型最终实现的功能是在给定决策者, 备选项集合, 备选项属性后,基于效用最大化准则,得到选择结果。
▲图1-3 离散选择模型的元素及结构
回到旅行出行方式选择的案例中,我们对例子中的元素进行映射。
  • 决策者:一次选择行为的主体(决策者属性包括家庭收入、出行人数、天气)。
  • 备选项集合:飞机、火车、长途巴士、自驾车(不同决策者的备选项集合可以不同)。
  • 备选项属性:行程外耗时、行程中耗时、行程花费、舒适性(不同备选项的属性也可以不同)。
  • 选择准则:效用的最大化准则。
  • 选择结果:备选项中的一个选项(每个选择过程均存在选择结果)。
04 案例分析:使用逻辑回归分析自驾选择问题
基于前文的介绍,相信读者已经迫不及待使用MNL或NL模型进行建模分析了,这里先从LR的实操讲起。LR是目前应用最广泛的可解释二分类模型之一,深入了解LR对我们的日常工作有很大帮助。
通过对案例数据进行一定的处理,可以得到一份满足LR模型要求的宽格式数据。具体数据描述如下所示,场景逻辑如图1-5所示。
  • OBS_ID离散,选择行为ID
  • HINC连续家庭收入
  • PSIZE连续 or 离散出行人数
  • TTME_AIR连续站点等待时间(飞机) 
  • TTME_TRAIN连续站点等待时间(火车)
  • TTME_BUS连续站点等待时间(长途巴士)
  • INVC_AIR连续金钱成本(飞机)
  • INVC_TRAIN连续金钱成本(火车)
  • INVC_BUS连续金钱成本(长途巴士)
  • INVC_CAR连续金钱成本(自驾)
  • INVT_AIR连续行程中 -时间成本(飞机)
  • INVT_TRAIN连续行程中 -时间成本(火车)
  • INVT_BUS连续行程中 -时间成本(长途巴士)
  • INVT_CAR连续行程中 -时间成本(自驾)
  • y离散是否选择自驾
▲图1-5 LR的场景逻辑示意图
了解数据形式后,开始进行具体的模型搭建工作。
第1步:软件包引入,数据读取
重要的软件包在代码的备注中,如代码清单1-3所示。
  • 代码清单1-3 软件包引入及数据读取
import
 numpy 
as
 np                     
# 引入基础软件包numpy
import
 pandas 
as
 pd                    
# 引入基础软件包pandas
import
 statsmodels.api 
as
 sm  
# 引入Logistic regression软件包statsmodels
from
 sklearn.model_selection 
import
 train_test_split 
# 引入训练集/测试集构造工具包
from
 sklearn 
import
 metrics            
# 引入模型评价指标AUC计算工具包
import
 matplotlib.pyplot 
as
 plt        
# 引入绘图软件包
import
 scipy                           
# 引入scipy软件包完成卡方检验
# 数据读入
data_path = 
'wide_data.csv'
raw_data = pd.read_table(data_path, sep=
','
, header=
0
)
第2步:数据预处理
数据预处理工作对于任何模型搭建都是必要的,这里结合LR及后续将介绍的MNL、NL模型的特点着重讲3个数据预处理的要点:①不要存在缺失值;②每一列均为为数值型;③多枚举值离散变量入模前要进行哑变量处理,如代码清单1-4所示。
  • 代码清单1-4 数据预处理
# 1. 缺失值探查&简单处理
model_data.info()                      
# 查看每一列的数据类型和数值缺失情况
# | RangeIndex: 210 entries, 0 to 209
# | Data columns (total 9 columns):
# | ...
# | HINC              210 non-null int64
# | ...
model_data = model_data.dropna()       
# 缺失值处理——删除
model_data = model_data.fillna(
0
)      
# 缺失值处理——填充(零、均值、中位数、预测值等)

# 2. 数值型核查(连续变量应为int64或float数据类型)
# 若上一步中存在应为连续数值变量的字段为object,则执行下列代码,这里假设'HINC'存在为字符串'null'的值。
import
 re                               
# 正则表达式工具包
float_patten = 
'^(-?\\d+)(\\.\\d+)?$'# 定义浮点数正则patten
float_re = re.compile(float_patten)     
# 编译
model_data[
'HINC'
][model_data[
'HINC'
].apply(
lambda
 x : 
'not_float'if
 float_re.match(str(x)) == 
Noneelse'float'
) == 
'not_float'
# 查看非浮点型数据
# | 2    null
# | Name: distance, dtype: object
model_data = model_data[model_data[
'HINC'
] != 
'null'
]

model_data[
'HINC'
] = model_data[
'HINC'
].astype(float)
第3步:单变量分析
在建模之前需要先对每个自变量进行单变量分析,确定是否纳入模型,变量分为离散变量和连续变量两种,其分析方式也有所不同。
对于离散变量,我们使用k-1自由度的卡方检验,其中k为离散变量的值个数;对于连续变量,比较简单的分析方法是直接对单变量进行逻辑回归,查看回归系数的显著性,根据AUC分析自变量对y的解释能力。
保留显著的自变量进入到后续的操作,如代码清单1-5所示。
  • 代码清单1-5 单变量分析
# 离散变量分析
crosstab = pd.crosstab( model_data[
'y'
],model_data[
'PSIZE'
])

p=scipy.stats.chi2_contingency(crosstab)[
1
]

print(
"PSIZE:"
,p)

# PSIZE: 0.0024577358937625327

# 连续变量分析
logistic = sm.Logit(model_data[
'y'
],model_data[
'INVT_CAR'
]).fit()

p = logistic.pvalues[
'INVT_CAR'
]

y_predict = logistic.predict(model_data[
'INVT_CAR'
])

AUC = metrics.roc_auc_score(model_data[
'y'
],y_predict)

result = 
'INVT_CAR:'
+str(p)+
'  AUC:'
+str(AUC)

print(result)

# INVT_CAR:2.971604856310474e-09  AUC:0.6242563699629587
第4步:共线性检验
由于LR模型是一种广义线性模型,变量间严重的共线性会对参数估计的准确性及泛化能力产生影响,因此需要对自变量间的共线性进行分析。
若vif值大于10,可认为变量间具有很强的共线性,需要进行相应的处理,最简单的处理方式就是进行自变量剔除,保留单变量分析中AUC最大的变量。共线性检验示例如代码清单1-6所示。
  • 代码清单1-6 共线性检验
from
 statsmodels.stats.outliers_influence 
import
 variance_inflation_factor 

#共线性诊断包
X = raw_data[[ 
'INVT_AIR'
'INVT_TRAIN'
,
'INVT_BUS'
'INVT_CAR'
]]

vif = pd.DataFrame()

vif[
'VIF Factor'
] = [variance_inflation_factor(X.values, i) 
for
 i 
in
 range(X.shape[
1
])]

vif[
'features'
] = X.columns

print(
'================多重共线性=============='
)

print(vif)

# | 0   14.229424    INVT_AIR
# | 1   72.782420  INVT_TRAIN
# | 2   80.279742    INVT_BUS
# | 3   35.003438    INVT_CAR
第5步:模型搭建
这里需要注意的是,对于3值及以上的离散变量要进行哑变量处理(需要记住去掉的枚举值),并且增加截距项Intercept,同时进行训练集和测试集的拆分(目的是防止模型的过拟合,确定分析结论可以泛化),代码如清单1-7所示。
  • 代码清单1-7 搭建LR模型
# 建模数据构造
X = model_data[[ 
'HINC'
,
'PSIZE'
,
'TTME_TRAIN'
 , 
'INVC_CAR'
]]

y = raw_data[
'y'
]

# 哑变量处理
dummies = pd.get_dummies(X[
'PSIZE'
], drop_first=
False
)

dummies.columns = [ 
'PSIZE'
+
'_'
+str(x) 
for
 x 
in
 dummies.columns.values]

X = pd.concat([X, dummies], axis=
1
)

X = X.drop(
'PSIZE'
,axis=
1
)   
# 删去原离散变量
X = X.drop(
'PSIZE_4'
,axis=
1
# 删去过于稀疏字段
X = X.drop(
'PSIZE_5'
,axis=
1
# 删去过于稀疏字段
X = X.drop(
'PSIZE_6'
,axis=
1
# 删去过于稀疏字段
X[
'Intercept'
] = 
1# 增加截距项
# 训练集与测试集的比例为80%和20%
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size = 
0.8
, random_state=
1234
)

# 建模
logistic = sm.Logit(y_train,X_train).fit()

print(logistic.summary2())

# 重要返回信息
# | ------------------------------------------------------------------
# |                Coef.   Std.Err.     z     P>|z|    [0.025   0.975]
# | ------------------------------------------------------------------
# | HINC           0.0264    0.0100   2.6477  0.0081   0.0068   0.0459
# | TTME_TRAIN     0.0389    0.0195   1.9916  0.0464   0.0006   0.0772
# | INVC_CAR      -0.0512    0.0204  -2.5103  0.0121  -0.0913  -0.0112
# | PSIZE_1       -0.3077    0.7317  -0.4206  0.6741  -1.7419   1.1264
# | PSIZE_2       -1.0800    0.6417  -1.6829  0.0924  -2.3378   0.1778
# | PSIZE_3       -0.7585    0.7582  -1.0004  0.3171  -2.2444   0.7275
# | Intercept     -1.8879    1.1138  -1.6951  0.0901  -4.0708   0.2950
# | =================================================================
# 模型评价
print(
"========训练集AUC========"
)

y_train_predict = logistic.predict(X_train)

print(metrics.roc_auc_score(y_train,y_train_predict))

print(
"========测试集AUC========"
)

y_test_predict = logistic.predict(X_test)

print(metrics.roc_auc_score(y_test,y_test_predict))

# | ========训练集AUC========
# | 0.7533854166666667
# | ========测试集AUC========
# | 0.6510263929618768
第6步:模型修正
可以看到由于不显著变量的影响,模型的测试集AUC与训练集AUC存在较大差异,我们需要对不显著变量进行剔除。可以看到,新建模型的拟合优度尚可(AUC接近0.75),且自变量显著(p < 0.05),可以进行后续解读,如代码清单1-8所示。
  • 代码清单1-8 修正LR模型
X = X.drop(
'PSIZE_1'
,axis=
1

X = X.drop(
'PSIZE_2'
,axis=
1

X = X.drop(
'PSIZE_3'
,axis=
1
)

# 训练集与测试集的比例为80%和20%
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size = 
0.8
, random_state=
1234
)

# 建模
logistic = sm.Logit(y_train,X_train).fit()

print(logistic.summary2())

# 重要返回信息
# | ------------------------------------------------------------------
# |                Coef.   Std.Err.     z     P>|z|    [0.025   0.975]
# | ------------------------------------------------------------------
# | HINC           0.0266    0.0096   2.7731  0.0056   0.0078   0.0454
# | TTME_TRAIN     0.0335    0.0161   2.0838  0.0372   0.0020   0.0650
# | INVC_CAR      -0.0450    0.0168  -2.6805  0.0074  -0.0778  -0.0121
# | Intercept     -2.3486    0.8275  -2.8384  0.0045  -3.9704  -0.7269
# | =================================================================
print(
"========训练集AUC========"
)

y_train_predict = logistic.predict(X_train)

print(metrics.roc_auc_score(y_train,y_train_predict))

print(
"========测试集AUC========"
)

y_test_predict = logistic.predict(X_test)

print(metrics.roc_auc_score(y_test,y_test_predict))

# | ========训练集AUC========
# | 0.7344618055555555
# | ========测试集AUC========
# | 0.7419354838709677
第7步:模型解读
DCM模型解读的对象可以分为概率(probability)和几率(odds)。在本例中,概率为“选择自驾的概率”,几率为“选择自驾的概率/不选择自驾的概率”限于模型的数学性质,无法直接从模型参数中快速得到概率,需要经过一定计算,这部分会在复杂的MNL及NL模型中介绍。
得益于LR的数学性质,分析师可以基于模型参数直接对几率进行解读(这一点类似于线性回归)。模型解读的话术为“在其他条件保持不变的情况下,某因素增长一个单位(或属性a相对属性b),几率会变化(增长或降低)多少”,计算公式如下。
  • 连续变量:odd(xi+1)/odd(xi)-1=exp(βi)-1
  • 离散变量:odd(xj=1)/odd(xj=0)-1=exp(βj)-1
例如,根据模型可知,在其他条件保持不变的情况下,家庭收入增长1个单位,选择自驾的odds会变化,exp(β"HINC" )-1=exp(0.0266)-1=0.027,即增加0.027倍。
在其他条件保持不变的情况下,自驾成本上升1个单位,选择自驾的odds会变化exp(β"INVC_CAR" )-1=exp(-0.0450)-1=-0.044,即下降0.044倍。
关于作者:周银河,现任腾讯数据科学家,曾任滴滴数据科学家,清华大学商学院及哥伦比亚大学商学院商业分析硕士项目指导嘉宾。拥有丰富的数据分析、统计建模及实验设计经验。
本文摘编自数据科学工程实践:用户行为分析与建模、A/B实验、SQLFlow》,经出版方授权发布。
延伸阅读数据科学工程实践
点击上图了解及购买

转载请联系微信:DoctorData
推荐语:这是一本将数据科学三要素一一商业理解、量化模型、数据技术全面打通的实战性著作,是来自腾讯、滴滴、快手等一线互联网企业的数据科学家、数据分析师和算法工程师的经验总结,得到了SQLFlow创始人以及腾讯、网易、快手、贝壳找房、谷歌等企业的专家一致好评和推荐。 
更多精彩👇
在公众号对话框输入以下关键词
查看更多优质内容!
PPT | 读书 | 书单 | 硬核 | 干货|讲明白|神操作
大数据 | 云计算 | 数据库 | Python | 爬虫 | 可视化
AI | 人工智能 | 机器学习 | 深度学习 | NLP
5G|中台 | 用户画像1024 | 数学 | 算法 数字孪生
据统计,99%的大咖都关注了这个公众号
👇
继续阅读
阅读原文