Google 张中无限真实动态范围的平均数组公式

ArrayFormula of Average on Infinite Truly Dynamic Range in Google Sheets

根据示例:

     A       B      C     D     E     F     G     ∞
  |======|=======|=====|=====|=====|=====|=====|=====
1 |      |AVERAGE|     |     |     |     |     |        
  |======|=======|=====|=====|=====|=====|=====|=====
2 | xx 1 |       |   1 |   2 | 0.5 |  10 |     |        
  |======|=======|=====|=====|=====|=====|=====|=====
3 | xx 2 |       |   7 |   1 |     |     |     |       
  |======|=======|=====|=====|=====|=====|=====|=====
4 |      |       |   0 |     |     |     |     |       
  |======|=======|=====|=====|=====|=====|=====|=====
5 | xx 3 |       |   9 |   8 |   7 |   6 |     |       
  |======|=======|=====|=====|=====|=====|=====|=====
6 | xx 4 |       |   0 |   1 |   2 |   1 |     |       
  |======|=======|=====|=====|=====|=====|=====|=====
7 |      |       |   1 |     |   4 |     |     |       
  |======|=======|=====|=====|=====|=====|=====|=====
8 | xx 5 |       |     |     |     |     |     |       
  |======|=======|=====|=====|=====|=====|=====|=====
9 |      |       |     |     |     |     |   5 |           
  |======|=======|=====|=====|=====|=====|=====|=====
∞ |      |       |     |     |     |     |     |       

对于动态术语中的每个有效行(行数未知 & 列数未知),获得 AVERAGE 的最佳方法是什么?

查询

1级:

如果 C2:G 范围内的所有 5 个单元格都具有以下值:

=QUERY(QUERY(C2:G, "select (C+D+E+F+G)/5"), "offset 1", )

如果不是,则跳过行:

如果空单元格被视为零:

=INDEX(QUERY(QUERY({C2:G*1}, "select (Col1+Col2+Col3+Col4+Col5)/5"), "offset 1", ))

我们使用 IFERROR(1/(1/...)) 包装来移除零值:

=INDEX(IFERROR(1/(1/QUERY(QUERY({C2:G*1}, 
 "select (Col1+Col2+Col3+Col4+Col5)/5"), "offset 1", ))))

要使 Col 引用动态,我们可以这样做:

=INDEX(IFERROR(1/(1/QUERY(QUERY({C2:G*1}, 
 "select "&
 "("&JOIN("+", "Col"&ROW(INDIRECT("1:"&COLUMNS(C:G))))&")/"&COLUMNS(C:G)), 
 "offset 1", ))))


2级:

如果空单元格不被视为零且不应跳过:

=INDEX(TRANSPOSE(QUERY(TRANSPOSE(E2:I), 
 "select "&TEXTJOIN(",", 1, IF(A2:A="",,
 "avg(Col"&ROW(A2:A)-ROW(A2)+1&")")))),, 2)

请注意,这是 A 列相关项,因此 A 列中的缺失值将抵消结果

有趣的事实!!我们可以将 avg 换成 maxmin:

将其从 A 列的限制中解放出来并使其适用于任何有效行:

=INDEX(IFERROR(1/(1/TRANSPOSE(QUERY(TRANSPOSE(
 IF(TRIM(TRANSPOSE(QUERY(TRANSPOSE(C2:G),,9^9)))="", C2:G*0, C2:G)), 
 "select "&TEXTJOIN(",", 1, 
 "avg(Col"&ROW(A2:A)-ROW(A2)+1&")"))))),, 2)

如果范围内的 0 不应该被平均,我们可以添加一个小的 IF 语句:

=INDEX(IFERROR(1/(1/TRANSPOSE(QUERY(TRANSPOSE(
 IF(TRIM(TRANSPOSE(QUERY(TRANSPOSE(
 IF(C2:G>0, C2:G, )),,9^9)))="", C2:G*0, 
 IF(C2:G>0, C2:G, ))), 
 "select "&TEXTJOIN(",", 1, 
 "avg(Col"&ROW(A2:A)-ROW(A2)+1&")"))))),, 2)

这里我们使用了so-called “vertical query smash”,它获取给定范围内的所有值并将其集中到一个列中,其中每一行的所有单元格作为副产品与空 space 连接:

=FLATTEN(QUERY(TRANSPOSE(C2:G),,9^9))

除此之外还有“水平查询粉碎”:

=QUERY(C2:G,,9^9)

还有 “终极 360° 双重查询粉碎”,它将范围内的所有单元格放入一个单元格中:

=QUERY(FLATTEN(QUERY(TRANSPOSE(C2:G),,9^9)),,9^9)

最后 “臭名昭著的负向 360° 反向双查询粉碎” 列优先于行:

=QUERY(FLATTEN(QUERY(C2:G,,9^9)),,9^9)

当然所有查询 smash 名称均受版权保护

回到主题...如上所述,范围内每行的所有单元格都与空 space 连在一起,即使是那些空单元格,所以我们遇到了双倍或多倍 spaces 值之间。为了解决这个问题,我们使用 TRIM 并引入一个简单的 IF 语句来为给定范围内的空行分配 0 值,例如。抵消偏移量:


3级:

MMULT是一种重class公式,可以对arrays/matrixes进行加减乘除甚至运行宁总...然而,数据集越大 = 公式计算越慢(因为在 MMULT 中,即使是空行也需要时间来执行 + - × ÷ 操作)...除非我们使用 真正的动态范围 双向无限...

获取具有给定范围值的最后一行:

=INDEX(MAX(IF(TRIM(FLATTEN(QUERY(TRANSPOSE(
 INDIRECT("C2:"&ROWS(A:A))),,9^9)))="",,ROW(A2:A))))

获取具有给定范围值的最后一列:

=INDEX(MAX(IF(TRIM(QUERY(INDIRECT("C2:"&ROWS(A:A)),,9^9))="",,COLUMN(C2:2))))

现在我们可以用一种简单的方式构造它:

=INDIRECT("C2:"&ADDRESS(9, 7))

等同于:

=INDEX(INDIRECT("C2:"&ADDRESS(MAX(IF(TRIM(FLATTEN(QUERY(TRANSPOSE(
 INDIRECT("C2:"&ROWS(A:A))),,9^9)))="",,ROW(A2:A))), 
 MAX(IF(TRIM(QUERY(INDIRECT("C2:"&ROWS(A:A)),,9^9))="",,COLUMN(C2:2))))))

或更短的选择:

=INDEX(INDIRECT("C2:"&ADDRESS(
 MAX((INDIRECT("C2:"&ROWS(A:A))<>"")*ROW(A2:A)), 
 MAX((INDIRECT("C2:"&ROWS(A:A))<>"")*COLUMN(C2:2)))))

因此简化的 MMULT 公式为:

=ARRAYFORMULA(IFERROR(
 MMULT(N(   C2:G9),           ROW(INDIRECT("C1:C"&COLUMNS(C:G)))^0)/
 MMULT(N(IF(C2:G9<>"", 1, )), ROW(INDIRECT("C1:C"&COLUMNS(C:G)))^0)))

如果我们想从范围中排除零值,则公式为:

=ARRAYFORMULA(IFERROR(
 MMULT(N(   C2:G9),         ROW(INDIRECT("C1:C"&COLUMNS(C:G)))^0)/
 MMULT(N(IF(C2:G9>0, 1, )), ROW(INDIRECT("C1:C"&COLUMNS(C:G)))^0)))

4级:

将以上所有内容放在一起使其具有无限动态并仍然限于有效数据集:

=INDEX(IFERROR(
 MMULT(N(   INDIRECT("C2:"&ADDRESS(
 MAX((INDIRECT("C2:"&ROWS(A:A))<>"")*ROW(A2:A)), 
 MAX((INDIRECT("C2:"&ROWS(A:A))<>"")*COLUMN(C2:2))))),           ROW(INDIRECT("C1:C"&
 MAX((INDIRECT("C2:"&ROWS(A:A))<>"")*COLUMN(C2:2))-(COLUMN(C2)-1)))^0)/
 MMULT(N(IF(INDIRECT("C2:"&ADDRESS(
 MAX((INDIRECT("C2:"&ROWS(A:A))<>"")*ROW(A2:A)), 
 MAX((INDIRECT("C2:"&ROWS(A:A))<>"")*COLUMN(C2:2))))<>"", 1, )), ROW(INDIRECT("C1:C"&
 MAX((INDIRECT("C2:"&ROWS(A:A))<>"")*COLUMN(C2:2))-(COLUMN(C2)-1)))^0)))

同样,不包括范围内为零的单元格:


荣誉奖:

等级:

与前面的公式相反的是 运行 MMULT

  • C2:?的总面积(all rows, all columns)而不是
  • 有效区域C2:?(excluding empty rows and columns)避开了0 × 0 = 0
  • 的mass-calculations

包括零:

=INDEX(IFERROR(
 MMULT(   INDIRECT("C2:"&ROWS(C:C))*1,         SEQUENCE(COLUMNS(C2:2))^0)/ 
 MMULT(IF(INDIRECT("C2:"&ROWS(C:C))<>"", 1)*1, SEQUENCE(COLUMNS(C2:2))^0)))

不包括零:

=INDEX(IFERROR(
 MMULT(   INDIRECT("C2:"&ROWS(C:C))*1,       SEQUENCE(COLUMNS(C2:2))^0)/ 
 MMULT(IF(INDIRECT("C2:"&ROWS(C:C))>0, 1)*1, SEQUENCE(COLUMNS(C2:2))^0)))

等级:

对于固定范围 C2:G9MMULT 平均值为:

=INDEX(IFERROR(
 MMULT( C2:G9*1,    FLATTEN(COLUMN(C:G))^0)/ 
 MMULT((C2:G9>0)*1, FLATTEN(COLUMN(C:G))^0)))

=INDEX(IFNA(VLOOKUP(ROW(C2:C), 
 QUERY(SPLIT(FLATTEN(ROW(C2:C)&"×"&C2:J), "×"),
 "select Col1,avg(Col2)
  where Col2 is not null
  group by Col1"), 2, )))

等级:

=INDEX(QUERY(SPLIT(FLATTEN(ROW(C2:C)&"×"&OFFSET(C2,,,9^9, 9^9)), "×"),
 "select avg(Col2) 
  group by Col1  
  label avg(Col2)''"))

不包括零:

=INDEX(QUERY(SPLIT(FLATTEN(ROW(C2:C)&"×"&OFFSET(C2,,,9^9, 9^9)), "×"),
 "select avg(Col2)
  where Col2 <> 0 
  group by Col1  
  label avg(Col2)''"))

包括空单元格:

=INDEX(IFERROR(1/(1/QUERY(SPLIT(FLATTEN(ROW(C2:C)&"×"&OFFSET(C2,,,9^9, 9^9)*1), "×"),
 "select avg(Col2)
  group by Col1  
  label avg(Col2)''"))))

您为此投入了大量时间。我希望人们欣赏它,更希望你这样做是为了其他人,而不是为了你自己。

查看您的最终公式,它们应该产生相同的结果(在 C2:? 中提供数据,如您的示例所示):

在 B2 中(包括零):

=ArrayFormula(IFERROR(MMULT(INDIRECT("C2:"&ROWS(C:C))*1,SEQUENCE(COLUMNS(C1:1),1,1,0))/ MMULT(IF(INDIRECT("C2:"&ROWS(C:C))<>"",1,0),SEQUENCE(COLUMNS(C1:1),1,1,0))))

在 B2 中(不包括零):

=ArrayFormula(IFERROR(MMULT(INDIRECT("C2:"&ROWS(C:C))*1,SEQUENCE(COLUMNS(C1:1),1,1,0))/ MMULT(IF(INDIRECT("C2:"&ROWS(C:C))<>0,1,0),SEQUENCE(COLUMNS(C1:1),1,1,0))))

我会尝试对@player0 的回答做一些补充。我将非常感谢任何关于优化此的评论。


如果数据范围内有很多空行和空列,不妨将它们从 MMULT 中排除。

第 1 步 - 过滤掉空行

我们有一个数据范围:从 C2 到最后一行并一直到最后一列(即 J:J)。我将使用 C2:K,请参阅下面的详细说明。

此公式将为我们提供一组行号,其中至少有一个非空单元格。如果有空行,它也会有一个 0,但是在这个数组中搜索并不重要,或者我们会在重要的时候过滤掉它:

=ARRAYFORMULA(
  UNIQUE(FLATTEN((C2:K <> "") * ROW(C2:K)))
)

因此,为了从数据范围中过滤掉空行,我们使用 FILTER 来检查上面的数组中是否有一行,如果是这样则保留:

=ARRAYFORMULA(
  FILTER(
    C2:K*1,
    MATCH(
      ROW(C2:K),
      UNIQUE(FLATTEN((C2:K <> "") * ROW(C2:K))),
      0
    )
  )
)

第 2 步 - 过滤掉空列

要获得仅包含非空列号的数组,我们可以使用几乎相同的公式:

=ARRAYFORMULA(
  UNIQUE(FLATTEN((C2:K <> "") * SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2))))
)

为什么用SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2))代替COLUMN(C2:K),详见文末

为了过滤掉空列,我们还使用 FILTERMATCH 条件来搜索数组中的列号:

=ARRAYFORMULA(
  FILTER(
    C2:K*1,
    MATCH(
      SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)),
      UNIQUE(FLATTEN((C2:K <> "") * SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)))),
      0
    )
  )
)

为了过滤掉空行和空列,我们只使用两个 FILTER

=ARRAYFORMULA(
  FILTER(
    FILTER(
      C2:K*1,
      MATCH(
        ROW(C2:K),
        UNIQUE(FLATTEN((C2:K <> "") * ROW(C2:K))),
        0
      )
    ),
    MATCH(
      SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)),
      UNIQUE(FLATTEN((C2:K <> "") * SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)))),
      0
    )
  )
)

原始数据范围将在内部变为:

第 3 步 - 执行 MMULT

现在我们可以使用 MMULT 和该数据集来计算平均值:

=ARRAYFORMULA(
  MMULT(
    FILTER(
      FILTER(
        C2:K*1,
        MATCH(
          ROW(C2:K),
          UNIQUE(FLATTEN((C2:K <> "") * ROW(C2:K))),
          0
        )
      ),
      MATCH(
        SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)),
        UNIQUE(FLATTEN((C2:K <> "") * SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)))),
        0
      )
    ),
    SEQUENCE(
      ROWS(
        QUERY(
          UNIQUE(FLATTEN((C2:K <> "") * SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)))),
          "WHERE Col1 <> 0"
        )
      ),
      1,
      1,
      0
    )
  ) /
  MMULT(
    FILTER(
      FILTER(
        (C2:K <> "")*1,
        MATCH(
          ROW(C2:K),
          UNIQUE(FLATTEN((C2:K <> "") * ROW(C2:K))),
          0
        )
      ),
      MATCH(
        SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)),
        UNIQUE(FLATTEN((C2:K <> "") * SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)))),
        0
      )
    ),
    SEQUENCE(
      ROWS(
        QUERY(
          UNIQUE(FLATTEN((C2:K <> "") * SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)))),
          "WHERE Col1 <> 0"
        )
      ),
      1,
      1,
      0
    )
  )
)

原始数据行有点偏差。

第 4 步 - 填写平均值列

为了使平均值与原始数据行一致,我们可以这样使用 VLOOKUP

=ARRAYFORMULA(
  IFNA(VLOOKUP(
    SEQUENCE(MAX((C2:K <> "") * ROW(C2:K)) - 1, 1, ROW(C2)),
    {
      QUERY(UNIQUE(FLATTEN((C2:K <> "") * ROW(C2:K))), "WHERE Col1 <> 0"),
      MMULT(
        ...
      ) /
      MMULT(
        ...
      )
    },
    2,
    0
  ))
)

在哪里

  • SEQUENCE(MAX((C2:K <> "") * ROW(C2:K)) - 1, 1, ROW(C2))是一个行号数组,从第2行到最后none-空行。我们不会用空字符串填充所有行。
  • QUERY(UNIQUE(FLATTEN((C2:K <> "") * ROW(C2:K))), "WHERE Col1 <> 0") 是一个非空行号的数组,0 过滤掉用作搜索的关键字。
  • IFNA 将 return 一个空字符串放在空数据行旁边。

最终公式

综合起来:

=ARRAYFORMULA(
  IFNA(VLOOKUP(
    SEQUENCE(MAX((C2:K <> "") * ROW(C2:K)) - 1, 1, ROW(C2)),
    {
      QUERY(UNIQUE(FLATTEN((C2:K <> "") * ROW(C2:K))), "WHERE Col1 <> 0"),
      MMULT(
        FILTER(
          FILTER(
            C2:K*1,
            MATCH(
              ROW(C2:K),
              UNIQUE(FLATTEN((C2:K <> "") * ROW(C2:K))),
              0
            )
          ),
          MATCH(
            SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)),
            UNIQUE(FLATTEN((C2:K <> "") * SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)))),
            0
          )
        ),
        SEQUENCE(
          ROWS(
            QUERY(
              UNIQUE(FLATTEN((C2:K <> "") * SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)))),
              "WHERE Col1 <> 0"
            )
          ),
          1,
          1,
          0
        )
      ) /
      MMULT(
        FILTER(
          FILTER(
            (C2:K <> "")*1,
            MATCH(
              ROW(C2:K),
              UNIQUE(FLATTEN((C2:K <> "") * ROW(C2:K))),
              0
            )
          ),
          MATCH(
            SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)),
            UNIQUE(FLATTEN((C2:K <> "") * SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)))),
            0
          )
        ),
        SEQUENCE(
          ROWS(
            QUERY(
              UNIQUE(FLATTEN((C2:K <> "") * SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)))),
              "WHERE Col1 <> 0"
            )
          ),
          1,
          1,
          0
        )
      )
    },
    2,
    0
  ))
)


一些细节

    为简洁起见,可以使用
  • INDEX 代替 ARRAYFORMULA(感谢@player0,几个月前教我的),但我喜欢 ARRAYFORMULA.[=133 的明确性=]
  • 为了清楚起见,我使用SEQUENCE 来构造一列或一行1。比如这个
SEQUENCE(
  ROWS(
    QUERY(
      UNIQUE(FLATTEN((C2:K <> "") * SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)))),
      "WHERE Col1 <> 0"
    )
  ),
  1,
  1,
  0
)

可以替换为

SIGN(
  QUERY(
    UNIQUE(FLATTEN((C2:K <> "") * SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)))),
    "WHERE Col1 <> 0"
  )
)

短了一点。 @player0 在这里也展示了一种提高 0:

次方的方法
QUERY(
  UNIQUE(FLATTEN((C2:K <> "") * SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)))),
  "WHERE Col1 <> 0"
)^0

但是(这只是我的猜测)我认为SEQUENCE的内部实现应该比提升到幂的操作更简单。

  • 我使用的范围 C2:K 比 sheet 上实际存在的多一列。它不仅给出了 C2 右侧的所有列的范围以及它下面的所有行,而且还会在 sheet 右侧添加另一列的情况下进行更新:a demo。虽然它不会被突出显示。这个 C2:K 几乎可以完美地替换那些方法:
INDIRECT("C2:" & ROWS(C:C))

OFFSET(C2,,, ROWS(C2:C), COLUMNS(C2:2))
  • 使用C2:K有一个小缺点:=ARRAYFORMULA(COLUMN(C2:K))会return一个列号数组,即使是不存在的,所以我们需要使用=SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)) 相反。

我认为使用 VLOOKUPQUERY.

的行平均有一个简单的答案

这个在B2:

=ARRAYFORMULA(
  IFNA(
    VLOOKUP(
      ROW(B2:B),
      QUERY(
        {
          FLATTEN(ROW(C2:J) + SEQUENCE(1, COLUMNS(C2:J),,)),
          FLATTEN(C2:J)
        },
        "SELECT Col1, AVG(Col2)
         WHERE Col2 IS NOT NULL
         GROUP BY Col1"
      ),
      2,
      0
    )
  )
)

  • 这可以很容易地更改为最大值、最小值、总和、计数 - 只需更改 QUERY 语句中的聚合函数。
  • 同样的方法可以用于按列聚合。
  • FLATTEN(C2:J) 可以改为:
    • FLATTEN(--C2:J) 将空单元格视为 0s;
    • FLATTEN(IFERROR(1/(1/C2:J))) 从平均值中排除 0s。
  • 如果没有中间空行,可以从公式中删除 VLOOKUP,以及从 SELECT 语句中删除 Col1
  • 有一个较短的版本(感谢@MattKing!)没有 VLOOKUPWHERE Col...:
=ARRAYFORMULA(
  QUERY(
    {
      FLATTEN(ROW(C2:J) + SEQUENCE(1, COLUMNS(C2:J),,)),
      FLATTEN(IFERROR(1/(1/C2:J)))
    },
    "SELECT AVG(Col2)
     GROUP BY Col1
     LABEL AVG(Col2) ''"
  )
)

我使用 C2:J 范围,列数最多为 I:I,一些详细信息:

  • 范围 C2:J,比 sheet 上实际存在的多一列。它不仅给出了 C2 右侧的所有列的范围以及它下面的所有行,而且还会在 sheet 右侧添加另一列的情况下进行更新:a demo。虽然它不会被突出显示。这个 C2:J 几乎可以完美地(如果 sheet 上实际上有 ZZZ 列就会出现问题)替换这些方法:
INDIRECT("C2:" & ROWS(C:C))

OFFSET(C2,,, ROWS(C2:C), COLUMNS(C2:2))
  • 使用 C2:J 有一个小缺点:=ARRAYFORMULA(0 * COLUMN(C2:J)) 将 return 一个列号数组,即使对于不存在的列号也是如此(乘以 0),所以我们需要使用 =SEQUENCE(1, COLUMNS(C2:J),,) 来代替。

@player0,有什么想法吗?

更新:我更新了我原来的公式 post。 ROW() 应该始终排在第一位,这样数据中的缺失值才不会影响拆分。

=ARRAYFORMULA(QUERY(SPLIT(FLATTEN(ROW(C2:C)&"|"&OFFSET(C2,,,9^9,9^9)),"|"),"select AVG(Col2) group by Col1 label AVG(Col2)''"))

应该可以,除非我误解了问题。

不需要 vlookups 或 mmults 或过滤器或任何东西。