ArrayFormula среднего значения в бесконечном действительно динамическом диапазоне в 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 для каждой допустимой строки в динамическом смысле терминов (неизвестное количество строк и неизвестное количество столбцов)?



если вы оказались здесь случайно для бегущей / кумулятивной / скользящей средней, см.: https://stackoverflow.com/a/59120993/5632629

только что понял, что вы сами спросили об этом. 😀 Спасибо, что поделились!

kishkin 25.12.2020 14:45
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
1 859
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Ответ принят как подходящий

ЗАПРОС

1-й уровень:

если все 5 ячеек в диапазоне C2:G имеют значения:

=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 на max или min:

чтобы освободить его от ограничения столбца 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)

здесь мы использовали так называемое «вертикальное разбиение запроса», которое берет все значения в заданном диапазоне и концентрирует их в одном столбце, где все ячейки в каждой строке соединяются с пустым пространством в качестве побочного продукта:

=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)

все имена запросов, конечно, защищены авторским правом

вернемся к теме ... как упоминалось выше, все ячейки в строке в диапазоне соединены пустым пространством, даже те, которые пусты, поэтому мы получили ситуацию, когда мы получаем двойные или несколько пробелов между значениями. чтобы исправить это, мы используем TRIM и вводим простой оператор IF для присвоения 0 значений пустым строкам в заданном диапазоне, например. для противодействия смещению:


МУЛЬТИ

Уровень 3:

MMULT - это своего рода формула тяжелого класса, которая может выполнять сложение, вычитание, умножение, деление, даже промежуточный итог по массивам/матрицам... однако, чем больше набор данных, тем медленнее вычисление формулы (потому что в 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)))

опять же, не включая ячейки с нулями в диапазоне:


лямбда

уровень 5:

с 20 сентября 2022 года мы можем использовать новые функции, упрощающие работу:

  • МАКЕРРЕЙ
  • УМЕНЬШАТЬ
  • БЫРОВ
  • БИКОЛ
  • СКАНИРОВАНИЕ
  • КАРТА
  • лямбда

поэтому, чтобы сразу перейти к закрытому диапазону, мы можем взять среднее значение, например:

=IFERROR(BYROW(C2:G9, LAMBDA(x, AVERAGE(x))))

и чтобы получить среднее значение по столбцам, мы просто заменяем BYROW на BYCOL. теперь, чтобы сделать диапазон открытым и по-настоящему динамичным, мы можем изменить приведенную выше формулу следующим образом:

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

мы можем сделать это короче на 12 символов, например:

=IFERROR(BYROW(INDEX(OFFSET(C2,,,
 MAX((INDIRECT("C2:"&ROWS(C:C))<>"")*ROW(C2:C)), 
 MAX((INDIRECT("C2:"&ROWS(C:C))<>"")*COLUMN(C2:2)))), LAMBDA(x, AVERAGE(x))))

чтобы исключить нули из вывода:

=INDEX(IFERROR(1/(1/BYROW(OFFSET(C2,,,
 MAX((INDIRECT("C2:"&ROWS(C:C))<>"")*ROW(C2:C)), 
 MAX((INDIRECT("C2:"&ROWS(C:C))<>"")*COLUMN(C2:2))), LAMBDA(x, AVERAGE(x))))))

чтобы исключить нули из ввода:

=INDEX(IFERROR(1/(1/BYROW(OFFSET(C2,,,
 MAX((INDIRECT("C2:"&ROWS(C:C))<>"")*ROW(C2:C)), 
 MAX((INDIRECT("C2:"&ROWS(C:C))<>"")*COLUMN(C2:2))), LAMBDA(x, AVERAGEIF(x, ">0"))))))

или если пустые ячейки следует рассматривать как нули:

=INDEX(IFERROR(1/(1/BYROW(1*OFFSET(C2,,,
 MAX((INDIRECT("C2:"&ROWS(C:C))<>"")*ROW(C2:C)), 
 MAX((INDIRECT("C2:"&ROWS(C:C))<>"")*COLUMN(C2:2))), LAMBDA(x, AVERAGE(x))))))

также стоит упомянуть ограничение BYROW в ~ 99990 строк


почетные упоминания:

@Erik Tyler уровень:

полной противоположностью предыдущей формулы будет запуск MMULT на

  • общая площадь C2:?(all rows, all columns) вместо
  • допустимая область C2:?(excluding empty rows and columns), что позволяет избежать массовых вычислений 0 × 0 = 0

включая нули:

=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)))

@kishkin уровень:

для фиксированного диапазона C2:G9 среднее MMULT будет:

=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, )))

@MattKing уровень:

=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)''"))))

Несколько вопросов, если у вас есть время о =INDEX(MAX(IF(TRIM(FLATTEN(QUERY(TRANSPOSE( INDIRECT("C2:"&ROWS(A:A))),,9^9))) = "",,ROW(A2:A)))). 1. INDIRECT(...) чем-то лучше, чем OFFSET(C2, 0, 0, ROWS(C2:C), COLUMNS(C2:2))? 2. FLATTEN здесь просто транспонирует результат QUERY, почему бы не использовать TRANSPOSE?

kishkin 24.12.2020 17:40

@кишкин конечно. FLATTEN имеет меньше символов, чем TRANSPOSE :D то же самое с INDEX против ARRYFORMULA. как МэттКинг упомянул несколько недель назад, FLATTEN навсегда останется в GS, так что теперь это официальный эффект. что касается OFFSET(...), это тоже длиннее, чем INDIRECT(...) - для этого нет другой причины. это просто короткая альтернатива

player0 24.12.2020 23:32

спасибо за пояснения! И отличный ответ во всех смыслах! Несколько замечаний: в IF нет необходимости в IF(C2:G9>0, 1)*1 (и им подобных), можно просто (C2:G9>0)*1; 2. Вы не отфильтровываете внутренние пустые столбцы и строки, если они есть :). Можно сделать с VLOOKUP(ROW(B2:B), {non_empty_row_numbers, avg_on_totally_filtered_out_range}, 2, 0). Или это слишком?

kishkin 25.12.2020 11:19

@kishkin действительно, это имеет смысл. а для VLOOKUP... это не приходило мне в голову... Я был больше сосредоточен на поиске внешних границ. в любом случае, это интересный подход, но мне интересно, как он будет работать с точки зрения скорости на каком-то огромном наборе данных с более чем 20 000 строк. На днях обязательно протестирую.

player0 25.12.2020 16:40

"потрясающий двойной запрос на 360°" X_X лол

Osm 21.09.2022 23:37

Ты тратишь на это кучу времени. Я надеюсь, что люди оценят это, тем более, что вы сделали это для всех остальных, а не для себя.

Глядя на ваши окончательные формулы, они должны давать те же результаты (укажите данные в 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))))

хотя это интересное сокращение размера формулы, вычисленные матрицы будут включать общую площадь C2:?(all rows, all columns) вместо действительной площади C2:?(excluding empty rows and columns), например. избегая массовых вычислений 0 × 0 = 0

player0 24.12.2020 16:51

Выполнение любой из сеток из 5000 строк по 50 столбцов занимает менее секунды для расчета. Вероятность того, что люди будут пытаться вычислить такой большой диапазон чисел, очень мала. И, конечно же, можно «обуздать» применимое поле, добавив один или два символа (например, изменив C1:1 на C1:M1, C:C на C2:C500 и т. д.), чтобы отразить их фактический максимум. известный диапазон для обработки. Таким образом, в реальном мире они будут работать с дополнениями к формулам или без них.

Erik Tyler 24.12.2020 18:01

Тем не менее, я рассматриваю все эти формулы как возможность для людей учиться и пробовать новые вещи, а не как «лучшие практики». И ваша тщательная работа по объяснению функций, почему и почему будет иметь большое значение, если люди захотят потратить время на их разбор, потому что вы дали отличный контекст.

Erik Tyler 24.12.2020 18:03

согласованный. к сожалению, через год это будет около 200 просмотров xD

player0 25.12.2020 15:35

Я попытаюсь сделать небольшое дополнение к ответу @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), смотрите подробности в конце.

Чтобы отфильтровать пустые столбцы, мы также используем FILTER с условием MATCH для поиска номеров столбцов в нашем массиве:

=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
    )
  )
)

И чтобы отфильтровать пустые строки и пустые столбцы, мы просто используем два FILTERs:

=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-й до последней непустой. Мы не будем заполнять все строки пустыми строками.
  • QUERY(UNIQUE(FLATTEN((C2:K <> "") * ROW(C2:K))), "WHERE Col1 <> 0") — это массив непустых номеров строк с отфильтрованным 0, используемым в качестве ключей для поиска.
  • IFNA вернет пустую строку для размещения рядом с пустой строкой данных.

ОКОНЧАТЕЛЬНАЯ ФОРМУЛА

Собираем все вместе:

=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.
  • Я использую 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, который на один столбец больше, чем на самом деле существует на листе. Он не только дает диапазон всех столбцов справа от C2 и всех строк от него, но и обновляется в случае добавления другого столбца справа от листа: демо. Хотя это и не подчёркивается. Этот C2:K может почти идеально (будет проблема, если на листе действительно есть ZZZ столбец) заменить эти подходы:
INDIRECT("C2:" & ROWS(C:C))

OFFSET(C2,,, ROWS(C2:C), COLUMNS(C2:2))
  • В использовании C2:K есть небольшой недостаток: =ARRAYFORMULA(COLUMN(C2:K)) вернет массив номеров столбцов даже для несуществующих, поэтому вместо этого нам нужно использовать =SEQUENCE(1, COLUMNS(C2:K), COLUMN(C2)).

Я думаю, что есть простой ответ для среднего по строкам с использованием VLOOKUP и QUERY.

Это в 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
    )
  )
)

  • Это можно легко изменить для max, min, sum, count — просто измените функцию агрегирования внутри оператора QUERY.
  • Тот же подход можно использовать для агрегации по столбцам.
  • FLATTEN(C2:J) можно изменить на:
    • FLATTEN(--C2:J) рассматривать пустые ячейки как 0s;
    • FLATTEN(IFERROR(1/(1/C2:J))), чтобы исключить 0s из среднего.
  • Если нет промежуточных пустых строк, VLOOKUP можно убрать из формулы, а Col1 из оператора SELECT.
  • Есть более короткая версия (спасибо @MattKing!) без VLOOKUP и WHERE 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, который на один столбец больше, чем существует на листе. Он не только дает диапазон всех столбцов справа от C2 и всех строк от него, но и обновляется в случае добавления другого столбца справа от листа: демо. Хотя это и не подчёркивается. Этот C2:J может почти идеально (будет проблема, если на листе действительно есть ZZZ столбец) заменить эти подходы:
INDIRECT("C2:" & ROWS(C:C))

OFFSET(C2,,, ROWS(C2:C), COLUMNS(C2:2))
  • В использовании C2:J есть небольшой недостаток: =ARRAYFORMULA(0 * COLUMN(C2:J)) вернет массив номеров столбцов даже для несуществующих (умноженный на 0), поэтому вместо этого нам нужно использовать =SEQUENCE(1, COLUMNS(C2:J),,).

@player0, есть мысли по этому поводу?

мне это действительно нравится

player0 04.01.2021 14:30

Я сделал его примерно на 30 символов короче и без особой математики, если хотите проверить: docs.google.com/spreadsheets/d/…

player0 04.01.2021 16:26

@player0 Отлично! Удалять все \sвесело! :) И кстати \s включает \n.

kishkin 04.01.2021 23:17

@player0 Я использую математику (вы имеете в виду + и SEQUENCE, верно?), чтобы быть явным, и я стараюсь избегать разделения соединения (ограничение 50 000 символов, нельзя использовать ♥ в моих данных). Также есть соседняя более сложная проблема, которую я решил с помощью MOD и SEQUENCE, не уверен в более простом решении. Сумма по строкам с шагом столбца, 2-й лист в A3, если интересно.

kishkin 04.01.2021 23:24

Ограничение 50 КБ для JOIN, TEXTJOIN, TRIM, REGEXREPLACE, SUBSTITUTE и т. д., но не в том случае, если вы используете & или QUERY — в таких случаях ограничений нет. да, для математической части... иногда кажется, что математика замедляет вычисления на чрезвычайно огромных наборах данных, даже если они такие простые, как 0 * 1. интересное использование мода вы там получили

player0 04.01.2021 23:35

@kishkin как OFFSET(C2,,,9^9,9^9) для бесконечных диапазонов 2d вместо необходимости ссылаться на столбцы() и строки()

MattKing 16.02.2021 17:01

@MattKing тебе не нравится C2:J вместо OFFSET(C2,,,9^9,9^9)? :) Также спасибо за идею не использовать WHERE, что избавляет от необходимости использовать VLOOKUP.

kishkin 17.02.2021 12:49

ОБНОВЛЕНИЕ: я обновил формулу из моего исходного сообщения. 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 или фильтрах или чем-то еще.

хороший. хотя это сместит окончательный вывод, если какая-то строка содержит полностью пустые ячейки. Кроме того, мне нужно было расширить его с помощью where Col2 is not null, потому что первое значение было перепутано - i.stack.imgur.com/9EScK.png

player0 16.02.2021 22:47

@ player0 он не будет компенсировать окончательный вывод со всеми пустыми ячейками в целой строке. Я протестировал его кучу. Единственное, что требуется, это чтобы большинство значений не были пустыми.

MattKing 16.02.2021 23:44

и все дело в том, чтобы НЕ использовать «где Col2 не равен нулю»

MattKing 16.02.2021 23:45

Я скопировал формулу из вашего ответа: i.stack.imgur.com/RA5Hj.png так как вы видите ее смещение на 1-3 строки, первое значение неверно, и большинство ячеек не пустой. красный столбец A содержит стандартные перетаскиваемые AVERAGE(C2:2) эффекты для сравнения. что я напутал?

player0 17.02.2021 00:16

@MattKing, ты забыл несколько запятых в конце SPLIT. В противном случае (в случае пустых исходных ячеек данных) вы иногда получаете номера строк в 1-м столбце, потому что SPLIT удаляет пустые результаты. i.imgur.com/xECBRWs.png

kishkin 17.02.2021 12:42

@kishkin а, ты пишешь. Когда я впервые сделал это, я сначала написал номер строки, поэтому мне не нужны были эти лишние запятые. (потому что ROW() никогда не бывает пустым) Сейчас я изменю свой ответ. Надеюсь, это также прояснит ситуацию для player0.

MattKing 18.02.2021 16:22

@player0 обновленная формула. Я забыл, что вам нужно, чтобы ROW() был первым. Попробуйте снова

MattKing 18.02.2021 16:24

@player0 получу ли я свой "уровень" в исходном посте :D ?

MattKing 18.02.2021 21:21

конечно, вы будете :D это неизбежно :)

player0 19.02.2021 01:36

Теперь это проще с BYROW:

=BYROW(C2:G,LAMBDA(r, AVERAGE(r)))

Кусок пирога. Очень просто

Другие вопросы по теме