Table.ReorderColumns()

Table.ReorderColumns()可以抽象地概括为function(table as table, columnOrder as list, optional missingField as nullable MissingField.Type) as table,大概的意思为:(1)第一个参数为需要重新整理列次序的表格。(2)第二个参数为串列,里面的元素为需要调整相对位置的列的名称,元素中没有包括的列不参与相对位置的调整。(3)第三个参数的性质为可选,当没有填写时系统默认该参数的值为MissingField.Error,意思为第二个参数的元素中出现了不存在于第一个参数的列时系统报错,还可填写MissingField.UseNull,意思为如果第二参数的元素中含有第一个参数中没有的列时会在第二参数指定的位置中新增所有值为null的列,如果填写的是MissingField.Ignore,第二个参数中所有不存在于第一个参数中的列都会被忽略。Table.ReorderColumns()一般按照使用者的意志对列的相对位置进行调整,但是也可以按照编写的M代码的逻辑进行列的次序重排。

上图为记录10位同学的9个科目的考试成绩的表格(Score),现在需要以总得分为依据对列进行升序排序,如果不使用Table.ReorderColumns(),可以通过以下代码完成排序:

let
    DHeaders = Table.DemoteHeaders( Score ),

    ToCols = Table.ToColumns( DHeaders ) ,

    SkipFirst = List.Skip( ToCols ),

    Sorting=
        List.Sort(
            SkipFirst,
            each
                List.Sum(
                    List.Skip( _ )
                )
        ),

    ToTable=
        Table.FromColumns(
            List.Combine(
                {
                    List.FirstN( ToCols, 1 ),
                    Sorting
                }
            )
        ),

    PHeaders = Table.PromoteHeaders( ToTable ),

    Outcome=
        Table.TransformColumnTypes(
            PHeaders,
            List.Combine(
                {
                    { { "科目", type text } },
                    List.Transform(
                        List.Skip(
                            Table.ColumnNames( PHeaders )
                        ),
                        each { _,  type number }
                    )
                }
            )
        )

in
    Outcome

以上代码中,DHeaders步骤把Score表格的标题降级为首行。然后,ToCols步骤把DHeaders的结果按列分解为{{}..{}}结构的复合串列,该串列中的任一元素对应分解前相对位置相同的列。由于第一列不参与排序,SkipFirst步骤移除了复合串列的第一个元素。接着,Sorting步骤对余下的元素按照总得分进行升序排序。之后,需要把SkipFirst步骤中被移除的第一个元素重新置于排好序的复合串列的头部,结果传入Table.FromColumns就得到首行为标题并且按要求排好序的表格。最后,需要把首行升级为标题并还原丢失的数据类型。如果照搬M爱好者在餐厅星评排序的方法,可以大幅度化简代码:

let
    IndirectSorting=
        List.Zip(
            List.Sort(
                List.Transform(
                    List.Skip( 
                        Table.ColumnNames( Score )
                     ),
                    each
                    { 
                        _,
                        List.Sum(
                            Table.Column( Score, _ )
                        )
                    }
                ),
                each _{ 1 }
            )
        ){ 0 },

    Outcome = Table.ReorderColumns( Score, IndirectSorting )
        
in
    Outcome

西瓜的解法使用了Table.ReorderColumns()对列进行间接排序,首先使用了List.Transform()构造出{{同学名称, 总得分}..{同学名称, 总得分}},然后使用List.Sort()对这个复合串列按照每一个元素中的总得分进行升序排序。最后使用了List.Zip()取出经过排序后的复合串列中每一个元素的同学名称并组成一个新的简单串列,传入Table.ReorderColumns()就完成了排序。如果把第一种解法与西瓜的解法折中一下,排序可以这样完成:

let
    IndirectSorting=
        List.Zip(
            List.Sort(
                List.Skip(
                    Table.ToColumns(
                        Table.DemoteHeaders( Score )
                    )
                ),
                each
                    List.Sum(
                        List.Skip( _ )
                    )
            )
        ){ 0 },

    Outcome = Table.ReorderColumns( Score, IndirectSorting )
in
    Outcome

折中后的办法也是间接排序,首先让Score表格的标题降级为首行,然后以列为单位生成结构为{{}..{}}的复合串列,串列中的每一元素对应标题降级后相对位置相同的列。在排序之前需要把不参与排序的第一个元素移除,Table.ReorderColumns()接收List.Zip()取出的标题名称就完成列位置的重新整理。由于对总得分进行排序等价于对平均分进行排序,利用Table.Profile()的特点,代码可以化简为:

let
    Profiling = Table.Profile( Score ),

    Sorting = Table.Sort( Profiling, "Average" ),

    Outcome = Table.SelectColumns( Score, Sorting[Column] )
in
    Outcome

对Table.Profile()生成的Average列进行升序排序,刚好能得到需要的列的相对位置,把Table.Profile()的Column列传入Table.ReorderColumns()就可以得到需要的排序效果。

附件

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注