在数组上使用OR逻辑作为Sumproduct

时间:2019-02-13 09:17:32

标签: excel excel-formula worksheet-function sumproduct

我有一个相当大的数据集,我需要将多个条目合并为一个值。我的数据集包含有关两个数据集的组合的数据,每个数据集都使用自己的ID和密钥。

我想到使用像这样的Sumproduct()函数:

=SUMPRODUCT(--('Raw data'!C:C=Landgebruik!A2);--('Raw data'!O:O={20;21;22;23;40});'Raw data'!S:S)

Landgebruik!A2拥有第一个数据集的ID,我需要将其聚合到第二个数据集。

'Raw data'!O:O包含第二个数据集的ID。在上述情况下,当第二个ID的值为以下任何一个值时,我需要对'Raw data'!S:S中的区域求和:{20;21;22;23;40}。 (或逻辑)该列仅包含整数值。

还有其他解决方法,然后为数组中的所有值复制--('Raw data'!O:O=20)吗?

编辑:

我现在使用了变通方法,即:=SUMPRODUCT(--('Raw data'!C:C=Landgebruik!A2);--('Raw data'!O:O=20)+('Raw data'!O:O=20)+('Raw data'!O:O=21)+('Raw data'!O:O=22)+('Raw data'!O:O=23)+('Raw data'!O:O=40);'Raw data'!S:S)。但是我觉得应该有一种更优雅的方法。

7 个答案:

答案 0 :(得分:6)

即使这已经做过数百次了,但也许微软会调换公式之类的东西。

我偏爱Jerry和Me所建议的方法,因为它们很简单又简洁,但是您付出了沉重的性能成本。

汤姆的公式对我来说看起来很丑,但到目前为止最快,比我最初的例子快4倍。我们能够将{}与Tom的公式合并,但是要使其正常工作,我们必须将sumifs函数与sum函数包装在一起。这大大降低了配方的速度,但使其更漂亮。

z32a7ul也有很好的解决方案。我真的很喜欢-的用法,并学会了如何使用| s搜索文本,并且仅搜索该文本。乍一看,我认为它不适用于2323等数字,但确实可以。

模拟示例如下:

A1:A5000中充满了LandgeBruik,

B1:B5000充满了40个

C1:5000中充满了1。


结果:

=SUMPRODUCT((A1:A5000="LandgeBruik")*(B1:B5000={20,21,22,23,40})*C1:C5000)

经过了19.186031秒

59,818,073滴答声

{=SUM(IF(A1:A5000="Landgebruik",1,0)*IF(B1:B5000={20,21,22,23,40},1,0)*C1:C5000)}

经过26.124411秒

81,450,506个滴答声

{=SUM((A1:A5000=""Landgebruik"")*(B1:B5000={20,21,22,23,40})*C1:C5000)}

经过21.111835秒

65,822,330滴答

"=SUMIFS(C1:C5000,B1:B5000,"">=20"",B1:B5000,""<=23"",A1:A5000,""=Landgebruik"")+SUMIFS(C1:C5000,B1:B5000,""=40"",A1:A5000,""=Landgebruik"")"

经过6.732804秒

20,991,490滴答

"=SUM(SUMIFS(C1:C5000,A1:A5000,"Landgebruik",B1:B5000,{21,22,23,24,40}))"

已逝去16.954528秒

52,860,709滴答声

"=SUMPRODUCT(--(A1:A5000=""Landgebruik""),--NOT(ISERROR(FIND(""|""&B1:B5000&""|"",""|20|21|22|23|40|""))),C1:C5000)"

经过11.822379秒

36,859,729个滴答声


方便的类 TimerWin64 用于计时这些时间:

显式选项

Private Declare PtrSafe Function QueryPerformanceCounter Lib "kernel32" (lpPerformanceCount As LongInteger) As Long
Private Declare PtrSafe Function QueryPerformanceFrequency Lib "kernel32" (lpFrequency As LongInteger) As Long

Private Type LongInteger
    First32Bits As Long
    Second32Bits As Long
End Type

Private Type TimerAttributes
    CounterInitial As Double
    CounterNow As Double
    PerformanceFrequency As Double
End Type

Private Const MaxValue_32Bits = 4294967296#
Private this As TimerAttributes

    Private Sub Class_Initialize()
        PerformanceFrequencyLet
    End Sub

        Private Sub PerformanceFrequencyLet()
            Dim TempFrequency As LongInteger
            QueryPerformanceFrequency TempFrequency
            this.PerformanceFrequency = ParseLongInteger(TempFrequency)
        End Sub

    Public Sub CounterInitialLet()
        Dim TempCounterIntital As LongInteger
        QueryPerformanceCounter TempCounterIntital
        this.CounterInitial = ParseLongInteger(TempCounterIntital)
    End Sub

    Public Sub PrintTimeElapsed()
        CounterNowLet
        If CounterInitalIsSet = True Then
            Dim TimeElapsed As Double
            TimeElapsed = (this.CounterNow - this.CounterInitial) / this.PerformanceFrequency
            Debug.Print Format(TimeElapsed, "0.000000"); " seconds elapsed "

            Dim TicksElapsed As Double
            TicksElapsed = (this.CounterNow - this.CounterInitial)
            Debug.Print Format(TicksElapsed, "#,##0"); " ticks"
        End If
    End Sub

        Private Function CounterNowLet()
            Dim TempTimeNow As LongInteger
            QueryPerformanceCounter TempTimeNow
            this.CounterNow = ParseLongInteger(TempTimeNow)
        End Function

        Private Function CounterInitalIsSet() As Boolean
            If this.CounterInitial = 0 Then
                MsgBox "Counter Initial Not Set"
                CounterInitalIsSet = False
            Else
                CounterInitalIsSet = True
            End If
        End Function

        Private Function ParseLongInteger(ByRef LongInteger As LongInteger) As Double
            Dim First32Bits As Double
            First32Bits = LongInteger.First32Bits

            Dim Second32Bits As Double
            Second32Bits = LongInteger.Second32Bits

            If First32Bits < 0 Then First32Bits = First32Bits + MaxValue_32Bits
            If Second32Bits < 0 Then Second32Bits = First32Bits + MaxValue_32Bits

            ParseLongInteger = First32Bits + (MaxValue_32Bits * Second32Bits)
        End Function

以下是用于运行测试的代码:

Option Explicit

Sub testFunctions()

    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
        .DisplayStatusBar = False
        .EnableEvents = False
    End With

    Dim ws As Worksheet
    Set ws = Sheets("Test")

    Dim Index As Long

    Dim Timer As TimerWin64
    Set Timer = New TimerWin64

    Timer.CounterInitialLet

    For Index = 1 To 5000

    Dim rngAddress As String
        rngAddress = "D" & Index
        ws.Range(rngAddress).Formula = "=SUMPRODUCT(--(A1:A5000=""Landgebruik""),--NOT(ISERROR(FIND(""|""&B1:B5000&""|"",""|20|21|22|23|40|""))),C1:C5000)"
    Next Index

    Timer.PrintTimeElapsed

    With Application
        .Calculation = xlCalculationAutomatic
        .ScreenUpdating = True
        .DisplayStatusBar = True
        .EnableEvents = True
    End With
End Sub

答案 1 :(得分:6)

您可以为此使用文本搜索:

--NOT(ISERROR(FIND('Raw data'!O:O,"2021222340")))

但是您必须小心,不要在较长的ID中错误地找到较短的ID,例如如果您要在ID {123,456,789}中进行搜索,则ID中不包含12。因此,像上面这样的简单文本搜索将无法工作。您需要使用分隔符来分隔ID字符串。通常,我将管道字符用于此目的,因为我不记得它在Excel文件的原始文本中出现的任何情况,并且因为它使公式易于阅读:

--NOT(ISERROR(FIND("|"&'Raw data'!O:O&"|","|20|21|22|23|40|")))

示例:

“原始数据”!O:O为20 => | 21 |在| 20 | 21 | 22 | 23 | 40 |

中找到

“原始数据”!O:O为2 => | 2 |在| 20 | 21 | 22 | 23 | 40 |

中找不到

(如果您的ID可能包含竖线字符,则可以使用CHR(1),它是SOH的一个很长的被遗忘的ASCII码,表示标头的开头;当然,它的可读性较低。)

整个公式:

=SUMPRODUCT(--('Raw data'!C:C=Landgebruik!A2),--NOT(ISERROR(FIND("|"&'Raw data'!O:O&"|","|20|21|22|23|40|"))),'Raw data'!S:S)

(对不起,我的Excel使用,而不是;)

答案 2 :(得分:5)

您可以按照注释中所述将其拆分为两个SUMIFS。如果所有值都是整数,则将“原始数据”!O:O与20,21,22和23进行比较与测试> = 20和<= 23相同。值40必须单独完成。

=SUMIFS('Raw Data'!S:S,'Raw Data'!C:C,Landgebruik!A2,'Raw Data'!O:O,">="&20,'Raw Data'!O:O,"<="&23)
+SUMIFS('Raw Data'!S:S,'Raw Data'!C:C,Landgebruik!A2,'Raw Data'!O:O,40)

在我的区域设置

=SUMIFS('Raw Data'!S:S;'Raw Data'!C:C;Landgebruik!A2;'Raw Data'!O:O;">="&20;'Raw Data'!O:O;"<="&23)
+SUMIFS('Raw Data'!S:S;'Raw Data'!C:C;Landgebruik!A2;'Raw Data'!O:O;40)

在您的语言环境中。

这仅在多个条件为连续整数时有效。

速度注意事项

SUMIFS被认为比sumproduct快大约五倍,因此对于大型数据集as demonstrated here

可能是首选

您可能会说,(有效)@ BrakNicku的SUM中的五个SUMIFS的更笼统的建议应该与一个SUMPRODUCT差不多快,但是SUM(SUMIFS)仍然会获胜,因为SUMIFS之类的公式可以处理全列引用比数组公式更有效。

答案 3 :(得分:5)

您可以对当前公式进行一些小的更改;将;更改为*(在特定情况下也不需要--

=SUMPRODUCT(('Raw data'!C:C=Landgebruik!A2)*('Raw data'!O:O={20;21;22;23;40})*'Raw data'!S:S)

那应该可行。


当您将单独的参数提供给SUMPRODUCT时,每个参数的大小必须相同。但是,当您像这样乘以它们时,它将强制求值,并且数组会扩展。

例如,如果采用两个数组5x1和1x5,则会得到一个5x5的结果数组:

enter image description here

答案 4 :(得分:4)

在向OP提出一些澄清要求后,我想对此问题做一个解答,因为英语不是我的主要语言,并且我认为我误会了一些东西。

所以,我为模拟情况所做的工作,用两张纸制成了一个新的工作簿。

一个工作表名为import threading from queue import Queue import requests hist_lock = threading.Lock() def opthist_job(worker,d_thread): headers = { 'Pragma': 'no-cache', 'Accept-Encoding': 'gzip, deflate, br', 'Accept-Language': 'en-US,en;q=0.9', 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36', 'Accept': '*/*', 'Referer': 'https://www.nseindia.com/products/content/derivatives/equities/historical_fo.htm', 'X-Requested-With': 'XMLHttpRequest', 'Connection': 'keep-alive', 'Cache-Control': 'no-cache', } params = d_threading[0] # This is where I need to get the value of key opthistdf = requests.get('https://www.nseindia.com/products/dynaContent/common/productsSymbolMapping.jsp', headers=headers, params=params) with hist_lock: # I am not sure if this is required in this instance. #### Some more functions #### def threader(): while True: worker = q.get() opthist_job(worker) q.task_done() q = Queue() for th in range(len(d_threading.keys())): t=threading.Thread(target=threader) t.daemon = True t.start() ,并在version: 2.1 orbs: aws-s3: circleci/aws-s3@1.0.3 中得到了一个值,我这样做了:

enter image description here

第二张纸名为Landgebruik。我隐藏了一些列以仅使用C,O和S列。在SI列中,输入的值仅等于1。在OI列中,随机值等于A2;在CI列中,随机值是A或B。看起来像这样(请注意,我隐藏了一些列):

enter image description here

问题想对S列中的值求和,但仅当O列等于20或21或22或23 o 40且C列等于Raw data时(在我的测试中,其中的值是字母{20,21,22,23,40}

我们可以使用数组公式来过滤S列中的数据,然后在过滤后对符合要求的值求和。在我的测试中,正确的结果应该是8,因为S列中只有8个值满足C和O列的要求。在图像中,右行以黄色突出显示。

OP已经做到了这一点,但想知道是否存在一个更短/更优雅的公式。

我发现最短的公式是这样的:

Landgebruik!A2
  

这是一个数组公式,因此必须按插入    CTRL + SHIFT + ENTER 否则将无效!

工作原理:

第一个A接受S列中的所有值,并忽略所有与O列中的等效值不等于20或21或22或23或40的值。第二个=SUM(IF($O$2:$O$28={20;21;22;23;40};IF($C$2:$C$28=Landgebruik!$A$2;$S$2:$S$28))) 接受该新数组,并忽略所有值其中C列中的等效项不等于IF。最终数组由函数IF

求和

我已经尽力解释了。我希望您能适应您的需求。

答案 5 :(得分:4)

如果您对性能(计算速度)感兴趣并且不害怕矩阵计算,则可以使用MMULT:

=SUMPRODUCT(--('Raw data'!C:C=Landgebruik!A2),MMULT(--('Raw data'!O:O={20,21,22,23,24}),TRANSPOSE({1,1,1,1,1})),'Raw data'!S:S)

说明:

首先,创建一个1048576×5矩阵,如果'原始数据'中的ID与O:O的第i行相同,则第i行和第j列的值为1。枚举{20,21,22,23,24}中的第j个值,否则为0。

第二,将其乘以1s(因为{20,21,22,23,24}包含五个元素,所以为5 1s),这表示您接受所有五个值。

第三,从上面得到一个向量,如果ID在可接受的值中,则第i个元素为1,否则为0,然后将该向量放在SUMPRODUCT中的其他向量旁边。

(对不起,我的Excel使用','而不是';'。如果要缩短公式,可以写{1; 1; 1; 1; 1}而不是TRANSPOSE({1,1,1 ,1,1})。但是您必须找出Excel用什么代替“;”来分隔行,最有可能是“。”。)

注意:如果您引用的是实际包含值而不是整个列的范围,例如,可能会提高计算速度。原始数据!C1:C123而不是原始数据!C:C。

如果使用Shift + Space Ctrl ++在已包含的最后一行上方插入新行,则公式中的引用将自动更新。另外,您可以将“名称”与特殊公式配合使用,以通过确定最后一个非空单元格来扩大所引用的范围。

更新

我进行了一些测量,以比较这些方法的效率。我使用10000行的随机数据,并对每个公式重新计算了1000次。您可以在第二列中看到经过的时间。

Measurements

在运行此VBA代码以测量时间时,我注释了其他公式:

Public Sub MeasureCalculationTime()
    Dim datStart As Date: datStart = Now

    Dim i As Long: For i = 1 To 1000
        Application.Calculate
    Next i

    Dim datFinish As Date: datFinish = Now
    Dim dblSeconds As Double: dblSeconds = (datFinish - datStart) * 24 * 60 * 60
    Debug.Print "Calculation finished at " & datFinish; " took " & dblSeconds & " seconds"
End Sub

在这种情况下,MMULT并不是最快的。

但是,我想指出这是最灵活的,因为

  1. 您可以将其与开关一起使用:引用单元格区域而不是{1,1,1,1,1},您将可以非常快速地在选择中包括/排除ID。就像您放入A1:A5 {20,21,22,23,24}并紧随其后,放入B1:B5 {1,1,1,1,1}。如果要排除21,则将B2重写为0,如果要包含B2,则将其写回1。

  2. 您可能使用更复杂的条件,在这里您必须比较多个级别。喜欢:

    = SUMPRODUCT(MMULT(-(CarId = CarOwner),-(CarOwner = ListOfJobs),-(ListOfJobs = JobsByDepartment),-(DepartmentIncludedInSelection = 1)),燃料消耗)

注意:上一行只是伪代码,MMULT只有两个参数。

答案 6 :(得分:-1)

这可能有效:

={SUMPRODUCT(--('Raw data'!C:C=Landgebruik!A2);--IFERROR(MATCH('Raw data'!O:O;{20;21;22;23;40};0)>0;0);'Raw data'!S:S)}

这需要作为数组公式输入。