假设业务员的薪酬与其销售额挂钩,计算薪酬时需要把销售额按如下表格(Info1)划分为多个区间,处在不同区间的销售额的提成率也会发生变化:
现在需要根据以下十位员工的销售额(Info2表格)算出对应的薪酬:
以员工6为例,薪酬的计算过程为
0.03 | * | (36,000 - 0) |
0.1 | * | (144,000 - 36,000) |
0.2 | * | (300,000 - 144,000) |
0.25 | * | (420,000 - 300,000) |
0.3 | * | (654,321 - 420,000) |
通过以上表格,不难发现薪酬的计算需要分为三步。第一步,需要把Info1表格根据上界划分为2组,所有上界小于销售额的归为一组(组1)而剩下的为另一组(组2),加总组1的每一行的上界减去对应下界再乘以提成率。第二步,提取组2的第一行,使销售额与这一行的下界相减再乘以对应的提成率。最后,加总第一步和第二步的结果。
为了简化代码,需要定义以下fnSumproduct()
( Cols as list ) as number ⇒ let Multiplication= List.Sum( List.Transform( List.Zip( Cols ), each List.Product( _ ) ) ) in Multiplication
fnSumproduct()主要用来实现对应项相乘后再加总的过程。
出于简化代码的目的,需要构造fnSubstraction():
( Col1 as list, Col2 as list ) as list ⇒ let Substraction= List.Transform( List.Zip( { Col1, Col2 } ), each Value.Subtract( _{0}, _{1} ) ) in Substraction
fnSubstraction()主要用于两组串列(list)的对应项进行相减。
let Grouping= Table.AddColumn( Info2, "Info1", each List.Buffer( Table.Partition( Info1, "Ceiling", 2, (x) ⇒ Number.From( x › [SalesAmt] ) ) ), type list ), AddCol1= Table.AddColumn( Grouping, "Salary1", each if Table.IsEmpty( [Info1]{0} ) then 0 else fnSumproduct( { fnSubtraction( [Info1]{0}[Ceiling], [Info1]{0}[Floor] ), [Info1]{0}[Rate] } ), type number ), AddCol2= Table.AddColumn( AddCol1, "Salary2", each Value.Multiply( Value.Subtract( [SalesAmt], [Info1]{1}[Floor]{0} ), [Info1]{1}[Rate]{0} ), type number ), AddCol3= Table.AddColumn( AddCol2, "Salary", each [Salary1] + [Salary2], type number ), RemoveCols= Table.SelectColumns( AddCol3, { "Staff", "SalesAmt", "Salary" } ) in RemoveCols
Grouping这一步骤使用了Table.Partition()把Info1表格根据上界划分两组,由于生成的串列(list)在之后的步骤至少使用一次,使用了List.Buffer()提升性能。由于组1可能为空表格,在AddCol1的步骤中需要使用Table.IsEmpty()防错。AddCol1,AddCol2和AddCol3分别对应上文提及的第一步,第二步和第三步。
其实还可以把Info1表格根据下界划分为2组,所有下界小于销售额的行归为一组(组1),剩下的行归类为另一组(组2)。然后,把组1的最后一行的上界替换成销售额,构成一个新的表格。加总这个新的表格的上界减去对应下界再乘以对应提成率就是想要的结果,以下为实现这一个想法的代码:
let AddCol1= Table.AddColumn( Info2, "Info1", each Table.Buffer( Table.SelectRows( Info1, (x) ⇒ x[Floor] ‹ [SalesAmt] ) ), type table ), TransInfo1= Table.FromRecords( Table.TransformRows( AddCol1, (x) ⇒ Record.RenameFields( Record.TransformFields( Record.TransformFields( x, { "Info1", (y) ⇒ Table.Buffer( Table.ReplaceRows( Table.ReverseRows( y ), 0, 1, { Record.TransformFields( Table.Last( y ), { "Ceiling", (z) ⇒ x[SalesAmt] } ) } ) ) } ), { "Info1", (w) ⇒ fnSumproduct( { w[Rate], fnSubtraction( w[Ceiling], w[Floor] ) } ) } ), {"Info1", "Salary"} ) ), type table [Staff=Int64.Type, SalesAmt=number, Salary=number] ) in TransInfo1
由于Table.TransformColumns()是不可以根据同一个表格的其他列对作为自变量的列进行转换,所以为了把每一个组1的最后一行替换为销售额,需要使用Table.FromRecords()和Table.TransformRows()的组合完成替换。
以员工6为例,薪酬的另一种算法为
( 0.03 - 0.3 ) | * | ( 36, 000 - 0 ) |
( 0.1 - 0.3 ) | * | ( 144,000 - 36,000 ) |
( 0.2 - 0.3 ) | * | ( 300,000 - 144,000 ) |
( 0.25 - 0.3 ) | * | ( 420,000 - 300,000 ) |
( 0.3 - 0 ) | * | ( 654,321 - 0 ) |
上表给出的算法也需要把计算过程分为3步。第一步,需要把Info1表格根据上界分为两组,所有上界小于Info1当前行的上界分为一组,这一组每一行的提成率与当前行的提成率相减再乘以对应的上界与下界之差,最后加总所有的乘积得到速算扣除数(Factor)。第二部,需要把Info1表格根据上界分为两组,所有上界大于销售额为一组,提取这一组的最小的提成率与销售额相乘。最后,把第二步的结果减去对应的提成率。
第一步可以通过以下代码实现:
let AddCol1= Table.AddColumn( Info1, "Subtable", each Table.Buffer( Table.SelectRows( Info1, (x) ⇒ x[Ceiling] ‹ [Ceiling] ) ), type table ), AddCol2= Table.AddColumn( AddCol1, "Factor", each if Table.IsEmpty( [Subtable] ) then 0 else fnSumproduct( { fnSubtraction( [Subtable][Ceiling], [Subtable][Floor] ), List.Transform( [Subtable][Rate], (x) ⇒ [Rate] - x ) } ), type number ), RemoveCols = Table.RemoveColumns( AddCol2, {"Subtable"} ) in RemoveCols
第一步主要用于为Info构造一个含有速算扣除因子的计算列, 步骤AddCol1主要用来实现分组的目的,步骤AddCol2主要用于实现当前行的提成率与组1的每一行提成率进行相减的目的。
第二和第三步可以通过以下代码实现,
let AddCol1= Table.AddColumn( Info2, "Subtable", each Table.Buffer( Table.SelectRows( Info2ii, (x) ⇒ x[Ceiling] › [SalesAmt] ) ), type table ), AddCol2= Table.AddColumn( AddCol1, "Salary", each Value.Subtract( Value.Multiply( [Subtable]{0}[Rate], [SalesAmt] ), [Subtable]{0}[Factor] ), type number ), RemoveCol = Table.RemoveColumns( AddCol2,{"Subtable"} ) in RemoveCol
AddCol1主要用于实现分组,AddCol2完成提取分组的第一行的提成率与速算扣除数并完成需要的计算。