现假设有一间公司,有四种产品供客户选择(产品型号为1,2,3和4),客户可以同时订阅四种产品中的任意一种或者多种且订阅期最短为一个月,但是不能重复订阅同一型号的产品。所有产品的状态可以划分为20种(1为最好,20为最差),并且所有产品的初始状态为1。产品的状态处于1到18时被认为处于正常区间,而处于19至20时则被认为处于异常状态。这间公司会在每一个月的月末对所有被订阅的产品进行一次检测,现要求利用2021-08-31到2021-12-31的状态记录,统计出不同产品在这段期间出现过由正常转变为异常的比例,以对不同产品的质量进行合理的评估。附件中共有5套数据可供模拟,现以Data2为例进行说明。

根据以上信息,不难归纳出:对于按时间排列的两相邻状态,只要出现以下的36种组合之一,则认为出现一次由正常转变为异常的事件。

对于在2021-08-31时已经被订阅的产品,只需要求出其按时间排列的每两相邻的状态组合,再判断这些状态组合与上图是否存在交集,如果存在则说明在此期间至少出现过一次状态由正常转变为异常的事件,如果交集为空集则说明没有发生过这样的事件。以下以Data2中的AccNo为3的客户为例进行说明,这位客户在2021-08-31时已经订阅了了产品2,而且直到2021-12-31时这位客户仍然在订阅这项服务。

上图说明这位客户订阅的产品2,在2021-08-31到2021-09-30期间,其状态由7转变为19,即发生了一次由正常演变为异常的事件。

为了让计算机察觉到事件的发生,其中一种思路首先需要把状态根据时间的先后进行排列,然后分别对排序后的所有状态进行截尾和截首,之后组合处于同一相对位置的状态,最后把组合好的状态组与之前提及的36种组合进行对比,如果不存在交集则说明没有发生过事件,反之至少发生了一次事件。

对于2021-08-31后订阅的产品,处理的程序稍微有些不同。因为检测是每一月的月末进行一次,这意味着有些被订阅的产品会没有初始的状态记录,所以需要为这些产品人为加入初始状态。以下以Data2中的AccNo为5的客户为例进行说明,这位客户订阅了产品3,而且直到2021-12-31这位客户仍在订阅该服务。因为所有被订阅的产品在最初都处于最好的状态,所以上图说明在2021-09-01到2021-09-30期间,该客户订阅的产品3发生了状态由正常转变为异常的事件。

为了让计算机能察觉事件的发生,在刚才截尾和截首再进行组合的基础上,还需要让1与排序后的第一个状态进行组合,并把这个组合添加至组合好的状态组中。这个新的状态组与之前提及的36种组合进行对比,如果不存在交集则说明没有发生过事件,反之至少发生了一次事件。
以下为完整执行以上思路的代码:
let
InitialDate = #date(2021, 08, 31),
BestStatus = 1,
GoodStatus =
List.Split(
{1..18},
1
),
BadStatus =
List.Buffer(
{
{19},
{20}
}
),
CartesianProduct =
List.Buffer(
List.TransformMany(
GoodStatus,
(x)=˃ BadStatus,
(y, w)=˃ List.Combine({y, w})
)
),
Grouping =
Table.Group(
Dataset,
"ProductCode",
{
"JumpRate",
(x)=˃
let
NestedGroup =
Table.Buffer(
Table.Group(
x,
"AccNo",
{
"IsStable",
(y)=˃
let
Sorting =
Table.Sort(
y,
{
"ReportDate",
Order.Ascending
}
),
MinReportDate =
List.First(
Table.Column(
Sorting,
"ReportDate"
)
),
StatusList =
Table.Column(
Sorting,
"Status"
),
CompoundList =
List.Zip(
{
List.RemoveLastN(
StatusList,
1
),
List.RemoveFirstN(
StatusList,
1
)
}
),
IfThenElse =
if
MinReportDate = InitialDate
then
CompoundList
else
List.Combine(
{
{
{
BestStatus,
StatusList{0}
}
},
CompoundList
}
),
EmpytTest =
List.IsEmpty(
List.Intersect(
{
IfThenElse,
CartesianProduct
}
)
)
in
EmpytTest,
Logical.Type
}
)
),
Full_List = NestedGroup[IsStable],
Nominator = List.Count(Full_List),
Denominator =
List.Count(
List.Select(
Full_List,
(a)=˃ a = false
)
),
Result =
Value.Divide(
Denominator,
Nominator
)
in
Result,
Percentage.Type
}
)
in
Grouping

以上为使用模拟数据Data2对以上代码进行测试的结果。如果使用List.TransformMany()构造状态组,则无需按时间的先后次序对状态进行排列,能减少将近30%运行的时间。由于笔者不太擅长使用文字对List.TransformMany()进行解释,请有兴趣的读者参考附件中的解法2。
这是是一个非常好的交流场所,不持续更新真的是可惜了。
最近看了本新书,有所感悟,之后总结好再和大家分享
大神,附件下载不了了,恳请您有空的时候帮我们上传一下,谢谢。
修好了,比较好奇在现实中这个案例会适用怎样的场景,如果的话麻烦留言