前言
近几天,在做一个工作相关的内容,就是对企业投资者进行风险评测。我设计了若干个问题,并根据投资者的选项用于评分,但需要提前计算多少种选项的可能性,并计算分数的分布。并根据分数确定相应的分数区间对应的风险等级。
这听上去是个简单的问题,但实际上计算起来颇为复杂,想了不少办法才实现。这里也记录下来,并写成分享的文章可以让大家看一下,说不定可以借鉴。
本次计算主要是使用Python和R。
由于代码比较简单,就不用同步到Github,所有代码在文章里都能获取到。
问题
其实这次的问题很简单:
- 有6个风险评测的问题
- 每个问题有A、B、C三个选项
- 每个问题的分值比重不一样
- 每个选项的分数不一样
- 需要根据投资者的选择答案计算其最终得分
- 根据其得分给出风险等级
但我们要根据所有可能的选择结果确定分数范围,并根据分数范围确定其对应的风险等级。所以问题的计算逻辑就如下了:
- 获取所有的选择答案的组合结果
- 根据组合计算每一个结果的分数
- 根据所有分数确定分数区间和风险等级的对应关系
- 计算投资者的分数
- 确定投资者的风险等级
获取所有组合
我们假设有A、B、C三个选项,对应的分数分别为[25,50,75]:
[A, B, C] = [25, 50, 75] |
总共有6道题,每道题有三个选项,所以答案的组合总共有$3^6=729$个,那么分数的可能性就有729个(实际上应该小于729,因为有不同答案但相同分数)。
我们不需要获取ABC的答案组合,只需要获得分数[25, 50, 75]的组合即可,所以我思考了不超过10s,就放弃了使用Excel,因为这么多年早忘了当初那丁点的VBA知识了,而这个要用Excel计算的话必须使用VBA,所以果断放弃。
那么再思考下要怎么计算,我们知道有6个答案,每个答案的分数是[25, 50, 75]中的一个,然后组成一个组合。所以其答案可能是:
[A, A, B, B, C, A] = [25, 25, 50, 50, 75, 25] |
这样的组合总共有729个,所以要获取所有可能的结果,那么可以使用Python的itertools包中的product就是笛卡尔积来计算所有的可能组合:
import itertools |
s就是所有的答案分数的组合可能,我们查看下组合的数量:
print(len(s)) |
如何计算?
我们已经知道了,每个答案组合有对应的分数,以上面的组合为例:
[A, A, B, B, C, A] = [25, 25, 50, 50, 75, 25] |
计算其分值为:250(随机的分数都能是这个值我服了)。
但我们不能这样简单的计算分值,因为每个问题的分值权重是不一样的,每个问题的重要程度是不一样的,我们预先定义每个问题权重为:
[1, 2, 3, 4, 5, 6] = [20%, 20%, 20%, 20%, 10%, 10%] |
所以每个投资者的分数计算公式为:
$$Score = \sum_{i}^{n=6}score_i \times W_i$$
$score_{i}$即是答案的分值组合,$W_i$为每个答案分值的权重,重新计算的分数结果就是40分。
如果答案组合只有一两个,那么这样计算也就罢了,但我们有729个组合,不可能每个计算出来,至少我们的组合的获取就是要通过工具得出来。怎么计算所有729个组合分数呢,而且当选项扩展至4个甚至5个,问题扩展至数十个,那么答案组合就更多了,怎么办呢?
复习下矩阵计算
在6个问题3个答案的情况有729个组合,每个组合是有6个分数组成,每个分数有对应的权重,我们想到这样一个公式,$A_{m \times n}$ $\times$ $B_{n \times k}$ = $C_{m \times k}$:
$$
\begin{equation}
\begin{bmatrix}
1 & 2 \
3 & 4
\end{bmatrix}
\times
\begin{bmatrix}
1 \
2
\end{bmatrix}
\begin{bmatrix}
5 \
11
\end{bmatrix}
\end{equation}
$$
这是线性代数中所学习的矩阵乘法,那么我们就可以认为,729个组合每个组合为6个分数,所以可以看成一个$729 \times 6$的矩阵$A_{729 \times 6}$,那么权重则是一个$6 \times 1$的矩阵$B_{6 \times 1}$,所有两个矩阵相乘则生成一个矩阵$C_{729 \times 1}$。
那么计算的整个公式就是:
$$
\begin{equation}
\begin{bmatrix}
a_{11} & a_{12} & a_{13} & \cdots & a_{16} \
a_{21} & a_{22} & a_{23} & \cdots & a_{26} \
\vdots & \vdots & \vdots & \ddots & \vdots \
a_{i1} & a_{i2} & a_{i3} & \cdots & a_{ij}
\end{bmatrix}
\times
\begin{bmatrix}
b_{11} \
b_{21} \
\vdots \
b_{61}
\end{bmatrix}
\begin{bmatrix}
c_{11} \
c_{21} \
\vdots \
c_{i1}
\end{bmatrix}
\end{equation}
$$
特别的,$(i, j) = (729, 6)$。
Python计算逻辑和代码
我们考虑整个计算的顺序和逻辑如下:
- 获取所有分数组合
- 将分数组合生成为矩阵
- 构建权重的矩阵
- 分别两个矩阵相乘
- 得到所有分数
- 用R做分数的图形分布
- 根据分布确定不同风险等级的分位数
我们考虑每一个步骤的代码:
- 获取所有分数组合
import itertools |
- 将分数组合生成矩阵
import numpy as np |
- 构建权重的矩阵
w = np.mat([0.2,0.2,0.2,0.2,0.1,0.1]) |
- 矩阵相乘
score = np.matrix.tolist(sm*w.T) % w.T指将矩阵转置为列矩阵 |
这段代码使用了np.matrix.tolist将计算后得到的矩阵直接转换为list列表,后面的第6步和第7步考虑在Python中调用R来实现。
Python调用R分析数据
数据的预处理
首先,需要使用python生成csv文件以供R使用,当然也可以在R中使用rpy2包调用R来分析。
- 使用python生成csv文件:
writer = csv.writer(file("score.csv",'wb')) |
- 在R中导入数据:
rm(list = ls()) |
怎样分析数据?
其实分析的思路挺简单,总共有729个总体值,但实际上计算出来的Score值只有21个,所以我们要考虑每个Score值占总体组合的概率。一般我们考虑CDF(cumulative distribution function)就是累积分布函数:
$$
F(X) = F(x \leq X) =
\begin{cases}
\sum_{i=1}^{x \leq X}P_{x=i}, & \text{x是离散的} \[3ex]
\int_{-\infty}^{X} f(x),dx, & \text{x是连续的}
\end{cases}
$$
所以我们可以在R中画出Score的累积分布函数图。
画累积分布函数图
- 画出累积分布图
par(family='MicrosoftYaHei') |
画出来的图如下:
- 用分位数工具取数
我们假设取[20%, 80%]分位数的值:
y <- quantile(score$Value,c(0.2,0.8)) |
那么,我们可以认为:
分数在[0,25)之间的为谨慎型,当然这一类别是理论值,不会通过问卷计算出来
分数在[25,42.5]之间的为保守型,占20%比例
分数在(42.5,57.5]之间的为谨慎型,占60%比例
分数在(57.5,75]之间的为积极型,占20%比例
结束语
在这里简单的做了一个关于风险评测的计算介绍,由于最近比较忙,所以内容写的也不怎么严谨。简单说就是思路清奇,排版华丽(你知道我说的是反话)。而且最近也没写什么,所以只是恰好碰到问题就随便写了篇,姑且看着吧。
python代码如下:
__author__ = "Eggs" |
R的代码如下:
rm(list = ls()) |
运行的结果如下:
> length(score$Value) |
一个测试:如果我们把题目答案扩展到4个,题目增加为10个,计算的结果如下:
> length(score$Value) |
可见总共的样本超过了100万,我们可以用图形观察如下:
CDF图形:
概率密度图形:
可见样本越多,图形越平滑,而且其分布越接近正态分布(极限中心定理)