Nano Hash - криптовалюты, майнинг, программирование

VBA - столбец суммирующего массива с условиями - как excel sumif

Я хотел бы суммировать столбец в массиве на основе нескольких условий. Если бы данные были в Excel, я бы использовал формулу =SUMIFS.

Пример набора данных в двумерном массиве, который у меня есть:

ID1     ID2     ID3     Value
0       1       1       4
0       2       2       5
1       3       2       6
0       1       1       3
0       1       0       2

Я хотел бы суммировать столбец значений на основе следующих условий:

ID1=0
ID2=1
ID3=1

Следовательно, строки 1 и 4 соответствуют этому критерию, и, следовательно, ответ будет 7 (4+3).

Как бы я построил это в VBA.

Обратите внимание, что идентификаторы могут быть бесконечными и могут быть строками, поэтому я не могу установить ID=0 в цикле.


  • Вы пробовали вложенные For + Ifs? 03.10.2013
  • покажите код, как построен ваш массив. Это массив вариантов VBA или данные в электронной таблице? 03.10.2013
  • Ну, технически ваш идентификатор не может быть бесконечным. Может быть много, МНОГО записей, но в какой-то момент вы нажмете на знак остановки. 04.10.2013

Ответы:


1

Просто небольшое предупреждение о скорости!

Я полагаю, что вопрос касается 2D-массива, а не excel.range, потому что цикл в диапазоне excel очень медленный (действителен, только если у вас много данных , но я уверен, что это обычный случай, если вы планируете использовать макрос VBA ;-))

Раньше я страдал от медлительности диапазона, пока не нашел несколько ссылок, сообщающих об этой проблеме (для примера с 10000 ячеек один пользователь сообщает 9,7 сегмента против 0,16 сегмента, используя 2D-массив!!). Ссылки ниже. Я рекомендую всегда использовать 2D-массив, просто, чисто и быстро!

См. дополнительные тесты производительности в:

Поэтому, если вы хотите обработать большой объем данных, код ответа Якуба следует немного изменить, чтобы получить мощность 2D-массива:

Public Function sumIfMultipleConditionsMet2(rng As Range, ParamArray conditions() As Variant) As Double
    Dim conditionCount As Long: conditionCount = UBound(conditions) + 1
    Dim summedColumnIndex As Long: summedColumnIndex = conditionCount + 1
    Dim currentRow As Range
    Dim result As Double: result = 0 'Changed from Long to Double
    Dim i As Long

    If rng.Columns.Count <> conditionCount + 1 Then
        Err.Raise 17, , "Invalid range passed"
    End If        

    Dim conditionsMet As Boolean

    'USING AN ARRAY INSTEAD OF A RANGE
    Dim arr As Variant
    arr = rng.Value 'Copy the range to an array
    Dim r As Long

    For r = LBound(arr, 1) To UBound(arr, 1)  'OLD: For Each currentRow In rng.Rows
        conditionsMet = True
        For i = LBound(conditions) To UBound(conditions)
            ' cells collection is indexed from 1, the array from 0
            ' OLD: conditionsMet = conditionsMet And (currentRow.Cells(1, i + 1).Value = conditions(i))
            conditionsMet = conditionsMet And (arr(r, i + 1) = conditions(i))
        Next i

        If conditionsMet Then
            'OLD: result = result + currentRow.Cells(1, summedColumnIndex).Value
            result = result + arr(r, summedColumnIndex)
        End If
    Next r

    sumIfMultipleConditionsMet2 = result
End Function

Используйте его так же, как Якуб показал в своем ответе:

debug.Print sumIfMultipleConditionsMet2(Range("A1:D50000"), 0, 1, 1)

Надеюсь, вам понравится!

С уважением, Андрес


PS: Если вы хотите пойти дальше, вот еще несколько советов по ускорению работы в Excel. Надеюсь, вам понравится!

04.10.2013

2

Вы можете использовать функцию paramArray, чтобы получить более обобщенную версию функции sumif. Например:

Public Function sumIfMultipleConditionsMet(rng As range, ParamArray conditions() As Variant) As Long
Dim conditionCount As Long: conditionCount = UBound(conditions) + 1
Dim summedColumnIndex As Long: summedColumnIndex = conditionCount + 1
Dim currentRow As range
Dim result As Long: result = 0
Dim i As Long

If rng.Columns.Count <> conditionCount + 1 Then
    Err.Raise 17, , "Invalid range passed"
End If


Dim conditionsMet As Boolean

For Each currentRow In rng.Rows
    conditionsMet = True

    For i = LBound(conditions) To UBound(conditions)
        ' cells collection is indexed from 1, the array from 0
        conditionsMet = conditionsMet And (currentRow.Cells(1, i + 1).Value = conditions(i))
    Next i

    If conditionsMet Then
        result = result + currentRow.Cells(1, summedColumnIndex).Value
    End If
Next

sumIfMultipleConditionsMet = result
End Function

Тогда вы можете использовать его следующим образом:

debug.Print sumIfMultipleConditionsMet(Range("A1:D5"), 0, 1, 1)
03.10.2013
  • дополнительные сведения о paramArray доступны здесь 03.10.2013

  • 3

    Хорошо, вы сказали, что у вас есть двумерный массив (не диапазон Excel), но точная форма массива не была указана. Поэтому я должен предположить, что ваш 2D-массив называется "arr" и имеет форму: arr(c,r) as variant , где r используется для доступа к строкам и c для столбцов (1 для "ID1", 2 для "ID2", 3 для " ID3" и 4 для "Значения"). (См. "примечание 1" и "примечание 2" для получения дополнительных разъяснений, если вы не понимаете идею).

    Тогда вам просто нужно сделать небольшой цикл:

    tot = 0
    For i = LBound(arr, 2) To UBound(arr, 2) ' The "2" in the second paramenter is
                                             ' for getting the lower and upper bound
                                             ' of the "2nd" dimention of the array
        If arr(1, i) = A And arr(2, i) = B And arr(3, i) = C Then
            tot = tot + arr(4, i)
        End If
    Next i
    

    В переменной tot будет сумма, которую вы пытались вычислить. Легкий??

    Если вы хотите деформировать предыдущую функцию, вы можете использовать:

    Public Function SumIfMyArray(arr As Variant, A As Variant, _
                                 B As Variant, C As Variant) As Double
        Dim i as Long
        Dim tot As Double
        tot = 0
        For i = LBound(arr, 2) To UBound(arr, 2) 
            If arr(1, i) = A And arr(2, i) = B And arr(3, i) = C Then
                tot = tot + arr(4, i) 'Adding the filtered value
            End If
        Next i
    
        SumIfMyArray = tot 'Returning the calculated sum
    
    End Function
    

    Используйте его как: Debug.Print SumIfMyArray(YouArr, 1, 1, 1). Надеюсь это поможет.

    БОЛЕЕ СЛОЖНЫЙ (НО ГИБКИЙ):

    Теперь, если вы хотите иметь очень общую функцию, которая поддерживает разные критерии и в то же время быть гибкой со столбцами, вы можете использовать приведенный ниже код (обратите внимание, я использую ParamArray, как и в другом ответе). На самом деле функция может использовать массив в форме arr(c,r) (эта форма массива проще для добавления дополнительных строк с инструкцией redim) и вторая в форме arr(r,c) (эта форма массива проще, если вы скопируете диапазон excel с помощью arr=range("A1:D5") ).

    Private Function SumIfConditionsMetArray(ColToAdd As Long, Arr As Variant, _
                           TypeArrayIsRC As Boolean, _
                           ParamArray Criteria() As Variant) As Double
        ' Returns:     The sum of values from a column where
        '              the row match the criteria.
        ' Parameters:
        ' 1) Arr:      An array in the form of arr(row,col) (
        '              (like the array passed by an excel range)
        ' 2) ColToAdd: Index of column you want to add.
        ' 3) TypeArrayIsRC: 'True' if the array passed if in the
        '              form of arr(Row,Column) or 'False' if
        '              the array is in the form arr(Column,Row).
        '              Note that passing an range as
        '              arr=range("A1:B3").value , then "true"
        '              should be used!
        ' 4) Criteria: a list of criteria you want to use for
        '              filtering, if you want to skip a column
        '              from the criteria use "Null" in the
        '              parameter list.
        '
        ' Example: Debug.Print SumIfConditionsMetArray(4, data, true, 9, null, 5)
        '          (It means: sum column 4 of data where 1st column
        '                     match "9" and 3rd column match "5".
        '                     The 2nd column was skipped because of null)
    
        Dim tot As Double
        Dim CountCol As Long
        Dim r As Long, c As Long
        Dim conditionsMet As Boolean
        Dim cExtra As Long
        Dim DimRow As Long, DimCol As Long
    
        If TypeArrayIsRC Then
            DimRow = 1: DimCol = 2
        Else
            DimRow = 2: DimCol = 1
        End If
    
        'Some checking...
        If ColToAdd < LBound(Arr, DimCol) Or ColToAdd > UBound(Arr, DimCol) Then
            Err.Raise vbError + 9, , "Error in function SumIfConditionsMetArray. ColToAdd is out of the range."
        End If
    
        'Correction in case of different array bases..
        cExtra = LBound(Arr, DimCol) - LBound(Criteria)  'In case the lower bound were different...
    
        'Limit the last column to check
        CountCol = UBound(Criteria)
        If CountCol > UBound(Arr, DimCol) - cExtra Then
            'Not raising an error, just skip out the extra parameters!
            '(Put err.raise if you want an error instead)
            CountCol = UBound(Arr, DimCol) - cExtra
        End If
    
        On Error GoTo errInFunction
    
        '''' LOOP ''''
        Dim A As Long
        Dim B As Long
        tot = 0
        For r = LBound(Arr, DimRow) To UBound(Arr, DimRow)
            If TypeArrayIsRC Then
                A = r
            Else
                B = r
            End If
            conditionsMet = True
            For c = LBound(Criteria) To CountCol
                If Not IsNull(Criteria(c)) Then
                    If TypeArrayIsRC Then
                        B = c + cExtra
                    Else
                        A = c + cExtra
                    End If
                    If Arr(A, B) <> Criteria(c) Then
                        conditionsMet = False 'Creteria not met
                    End If
                End If
            Next c
            If TypeArrayIsRC Then
                B = ColToAdd
            Else
                A = ColToAdd
            End If
            If conditionsMet Then
                tot = tot + Arr(A, B) 'Adding the value
            End If
        Next r
    
        SumIfConditionsMetArray = tot 'Returning the calculated sum
        Exit Function
        ''' END '''
    errInFunction:
        Err.Raise Err.Number, , "Error in function SumIfConditionsMetArray. Check the parameters are inside the bounds."
    End Function
    

    Это немного сложнее, но гораздо более гибко. Вы можете использовать его с диапазоном как:

    Dim MyArr as variant
    MyArr = ActiveSheet.range("A1:G10").Value  ' Note: use ".Value" at end  
                                               ' and not start with "Set" 
    Debug.Print SumIfConditionsMetArray(4, MyArr, True, 100,  null, 100)
    ' This will add the value of the 4th column, were the row 
    ' has 100 in the first column and 100 in the 3rd column. 
    

    Надеясь, что это поможет с вашим вопросом.

    С уважением, Андрес


    ** Примечание 1 ** Имея массив в форме arr(c,r), вы можете получить доступ к любому элементу, указав координаты внутри круглых скобок. Например, если вы хотите получить доступ к значению 4-го столбца 2-й строки, вам нужно ввести код arr(4,2), и вы получите значение 5 (при условии, что вы тестируете тот же пример своего вопроса. Проверьте его в своей первой таблице) .

    ** Примечание 2 ** У меня есть причина для arr(c,r) вместо arr(r,c). Причина в том, что гораздо проще добавить больше строк с помощью инструкции redim, если у вас есть координата строки в последней позиции. Но если ваш 2D-массив исходит из диапазона Excel (используя, например, что-то вроде arr = range("A3:D6").value), то будет лучше перевернуть позиции r и c в коде.

    03.10.2013
  • +1 за ваше решение и четкое объяснение. Мне немного больше нравится решение Якуба, но все же, даже если это был диапазон excel, вы могли бы -excel/18481730#18481730">легко вставить в массив. 04.10.2013
  • Спасибо! и я согласен, что вы можете легко скопировать диапазон в 2D-массив (это то, что я показываю последний блок кода!) Просто предупреждение: если вы повторяете ячейку excel за ячейкой excel, это очень медленно. Вы должны выполнять итерацию (создавать цикл) только в 2D-массиве. Не шучу, попробуйте 5 столбцов x 1000 строк. Диапазон чтения (A1:E1000).cell(x,y) в 100 раз медленнее, чем однократное копирование в 2D-массив и повторение по нему. 04.10.2013
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..