Excel: Count same number values in noncontiguous range - excel

I'm looking for the most elegant way to count the same number values in a noncontiguous range (I'll refer to it as just 'range'). This is the range:
=$C$2:$C$31,$E$2:$E$31,$G$2:$G$31,$I$2:$I$31,$K$2:$K$31,$M$2:$M$31,$O$2:$O$31,$Q$2:$Q$31,$S$2:$S$7
These are the parameters:
The range contains non-adjacent columns.
The columns differ in height.
The cells in the range are either empty or contain integers.
I'm checking for how many cells equal '1', how many equal '2' etc. in the range. (Not in one go, but in seperate formulas).
I've used a named range to reference the range. I'd really like to use this named range in the formula, in one way or another.
I hope I've given you enough info... Thanks in advance!

I agree with Kartik that a VBA solution is required. However the solution offered is a little inefficient in that it loops over every cell in the ranged passed into the function. It also limits the key parameter to a range reference, and can only count up to 32767 matches. Here's an alternative addresses these shortcomings
Function CountIf_N(rng As Range, key As Variant) As Variant
Dim r As Range
Dim count As Long
count = 0
For Each r In rng.Areas
count = count + WorksheetFunction.CountIfs(r, key)
Next
CountIf_N = count
End Function
Note: assumes Excel 07 or later. If using with an ealier version replace CountIfs with CountIf

One approach is to use excel built in function Countif, but it won't work with non-contigous range. The other way (the easy way) will be to use VBA to create your own custom function, and then use it in excel.
I've presented that technique here.
Goto visual basic editor in excel by pressing Alt+F11, in the project window insert a new module and paste the below code:
Function countif_n(rng As Range, key As Range) As Integer
Dim count As Integer
count = 0
For Each cell In rng
If cell.Value = key.Value Then
count = count + 1
End If
Next cell
countif_n = count
End Function
Here rng is your non-contigous range, and key represent the "range"(cell) which contains the value you want to count. For eg., to check for 1 enter 1 in any cell lets suppose "F2", and your non-contigous range is "testrange"
Then use the above function by entering the following in any blank cell:
=countif_n(testrange, F2)

Although COUNTIF can't handle non-contiguous ranges some functions can, for example RANK and COUNT so for a range called Range this formula will give the number of instances of a number in Z2 within Range
=IFERROR(COUNT(Range)-SUM(RANK(Z2,Range,{1,0}))+2,0)
assumes Excel 2007 or later but can be amended to work in earlier versions

This doesn't quite work if there's stuff below S7 that can't be counted, but you may be able to modify. It also doesn't incorporate the named range.
=SUM(IF(MOD(COLUMN(A2:S31),2)=0,IF(A2:S31=2,1,0)))
This example counts the number of 2's.
This needs to be array-entered with ctrl-shift-enter. It's based on the fact that you're counting in every other column, at least in your example. Also, although you mention the columns are different heights, it looks like all except S are the same height. So maybe there's a way to work around that.

Related

How can CountIf accept a structured reference column as criteria VBA-wise?

I'm trying to use structured references to the current columns the same as CountIf does for my UDF function. While
=COUNTIF(Data[Team];Overview[Team])
works, my new function
=CONCATENATEIF(Data[Team];Overview[Team];Data[Data])
doesn't work, since the Overview[Team] criteria Range can't be cast to a single value which is [#This Row].
I tried to change the parameter "criteria" As String as well as different methods. Calling
=CONCATENATEIF(Data[Team];Overview[#Team];Data[Data])
with "#" works as intended. But CountIf can handle [#Team], [Team] and normal ranges like [A1:A4]. So how they do it?
Public Function CONCATENATEIF(check_range As Range, criteria As Range, data_range As Range) As Variant
Dim mydic As Object
Dim L As Long
Set mydic = CreateObject("Scripting.Dictionary")
For L = 1 To check_range.Count
If check_range(L) = criteria Then
mydic(L) = data_range(L)
End If
Next
CONCATENATEIF= Join(mydic.items, ", ")
End Function
What cast does criteria need to work like CountIf's criteria? How can i transform the structured Reference [Team] to [#Team] vba-wise, so it selects the same row, where the Formular is used later.
The table for the problem (sadly can't embed images yet)
COUNTIF works due to inferred reference¹.
If you put a bunch of values in column A and then use =INDEX(A:A, , ) (Index(<column_A>, <all_rows>, <all_columns>)) in an unused column to the right of the data then the result will be from the common row in column A. Since you haven't provided a specific row reference where a single cell reference is expected, the associated (or inferred) row is used. This is why COUNTIF works; it is using an inferred reference from the Overview[Team] column to reference a single cell for criteria; e.g. the cell in Overview[Team] that is on the same row as the formula (also known as Overview[#Team]).
The VBA code is not using an inferred reference. It is referencing the whole column of Overview[Team] where it needs a single cell for criteria (e.g. Overview[#Team]).
You could try to artificially parse the column of criteria down to a single cell with something like Application.Caller.Row or you could just use Overview[#Team] as the criteria like it was intended.
¹ I hope I got that term right. I use it so little that I have a hard time remembering the correct term sometimes.

How can Excel VBA range variables be tested for references to entire columns..?

What ways are there to test an Excel VBA range variable for references to entire columns?
I'm using Excel 2007 VBA, iterating through Range variables with For-Each loops. The ranges are passed into the function as parameters. References to individual cells, ranges of cells, and entire rows are fine.
For instance, these are okiedokie:
Range("A1") 'One cell
Range("A1:D4") 'Range of cells.
Range("10:20") 'Entire rows 10 through 20.
But if any of the ranges have references to entire columns, it will drag the function down to a screeching halt. For instance, these are not okiedokie, and they need to be tested for and avoided:
Range("A:A")
Range("A:Z")
Range("AA:ZZ")
There are a few ways I've throught of to do this, each of them plausible but with weaknesses. The code contains loops which are used for searching through cells in worksheets with many thousands of rows, so speed is critical.
Here are three ways I can think of, but I'd like to know if there are others..?
The simplest & fastest method is to count the rows. If Range(x).Rows.Count=1048576, that's the maximum number of rows in a worksheet. However, this wouldn't work if the actual number of rows turned out to be exactly that number, or if by some wild chance there were multiple overlapping areas/ranges
that all added up to that number. Both unlikely, but possible. Also, if the version of Excel changes, so might that number, thus rendering the code broken.
Use a RegEx match against the text of Range.Address(False,False) with a pattern such as ([A-Z]{1,3}):([A-Z]{1,3}). I think this would be a medium on the speed scale.
Use VBA loops, If-Then, and string functions such as InStr() and Mid() to pick at the text of Range.Address(False,False). I think this would be the slowest possible way to do it.
You could test if the range is a reference to a column by checking the Range.Address against the Range.EntireColumn.Address like this:
If Range("AA:ZZ").Address = Range("AA:ZZ").EntireColumn.Address Then
'This returns True
End If
If Range("AA1:ZZ4").Address = Range("AA1:ZZ4").EntireColumn.Address Then
'This returns False
End If
Not sure I understand the question completely but this might work for you:
Public Sub Test()
Debug.Print RowCheck(ThisWorkbook.Worksheets("Sheet1").Range("A1:A10"))
End Sub
Public Function RowCheck(InputRange As Range)
Dim u As Long 'used number of rows
Dim x As Long 'max number of rows for any column
Dim r As Long 'number of rows based on input range
With InputRange
u = Cells(Rows.Count, .Columns(1).Column).End(xlUp).Row
r = .Rows.Count
x = Rows.Count
End With
If r = x And u < r Then
RowCheck = "A bad column reference provided"
Else
RowCheck = "This is a valid reference"
End If
End Function
Ok, after reading everyone's suggestions, I realized that no matter what I do, any Range objects passed to my function might include either an entire column reference or any combination of overlapping Range references that result in an entire column being selected.
But in translation, that means...all rows in the data, aka the UsedRange. It's possible with a large amount of data the UsedRange may actually hit the last row at 1048576. And any combination of Range references passed to my Function might result in a huge area that does cover an entire column, all the way to the maximum row.
Of course the likelihood of that happening is very low, but I do like to cover all bases in my code. But the key to this puzzle is UsedRange. This creates a "synthetic maximum last row". If the GrandRange, for lack of a better name, covers all rows in the UsedRange, then my function has nothing to do and no data to return. And so a simple IF-Then-Exit should give me the solution I was looking for:
If Intersect(UsedRange,LeGrandeRange).Rows.Count = UsedRange.Rows.Count Then
'All rows in `UsedRange` are affected.
'Nothing to do.
Exit Function
Else
'Do everything here.
'Then exit normally.
...
...
...
Endif

Sum numbers in one cell that contains line break - Excel

I have been searching for few hours and I couldnt find any solution for this problem.
As you can see on the image I have 3 columns and 2 rows. My goal is to sum the cost row (it has line breaks) or split equipment and cost columns into 3 smaller rows. Is this possible?
Something like this in VBA will work:
Function SumLines(ByVal str As String) As Long
Dim arr() As String
arr = Split(str, Chr(10))
For a = 0 To UBound(arr)
SumLines = SumLines + CLng(arr(a))
Next
End Function
However, this will only work if you don't have any characters other than digits and Chr(10)s (new lines).
You then use this in your worksheet, e.g.:
=SumLines(A1)
Unfortunately there's not a particularly clean way to achieve this (to my knowledge). Here are a couple of things you could try though:
Method 1 - worksheet solution
If your first cost cell is in A1 then place the cursor in cell B1 and create a new named range using the following formula:
=EVALUATE(SUBSTITUTE(A1,CHAR(10),"+"))
You can then type the name of the named range into cell B1 and you'll get the sum as expected. Unfortunately you have to create a named range for this because EVALUATE isn't available as a worksheet function - only a named range function and also available in VBA.
I called my named range "eval". You can drag this formula down and it will fill down, always evaluating the cell to it's left.
Method 2 - VBA solution
You can use some simple VBA. Paste this into a new module and then use this formula on the worksheet like this:
=SumAlt(A1)
will return 600 in your example, if A1 contained your 100 200 300
Function SumAlt(s As String) As Long
SumAlt = Evaluate(Replace(s, Chr(10), "+"))
End Function

EXCEL matching elements in a discontinuous range

I want to be able to create a list of matching elements from two columns in different worksheets.
Let me provide a mock example:
I have two lists of elements, in this case boys names and girls names. Those will be placed in different sheets. In the figure below, for simplicity, they are placed in different columns (discontinuos range). I want the formula to generate the list in cells A3:A14 (list all elements in the discontinuous range which match containing the text "jo".
I've learned that the best way to provide such discontinuous range to a formula is by defining a named range.
So far, what I got is:
{IF(
MATCH(
INDEX(Named_Range,
SMALL(IF(Named_Range="*"&$A$3&"*",
ROW(Named_Range)-ROW(INDEX(Named_Range,1,1))+1),ROW()-1)),
'(Names)'!C:C,
0),
INDEX(Named_Range,
SMALL(IF(Named_Range="*"&$A$3&"*",
ROW(Named_Range)-ROW(INDEX(Named_Range,1,1))+1),
ROW()-1)),
"")
}
Named_Range is declared as
=D9:D13,F3:F6
The small function returns the nth element matching the criteria. It should also MATCH one of the names in (Names)!C:C. If so, I get the value of that cell. For the sake of simplicity, consider that '(Names)'!C:C contains all names, so it will always be true.
This formula worked for me using a single column as the range. However, I cannot evaluate the formula using the discontinuous range without Excel crashing, so I'm looking for a piece of advice on how to get it right.
Thanks.
Perhaps the best way to deal with a discontinuous range is to make it continuous...?
Your problem can be simplified a lot with a small VBA function. This function takes a discontinuous named range and returns a single continuous column containing all the same values. That way you can use your INDEX/MATCH/SMALL etc formulas in the normal way
Function Arrange(rng As Range) As Variant
Dim temp As Variant
Dim i As Long
Dim r As Range
ReDim temp(1 To rng.Cells.Count)
i = 1
For Each r In rng
temp(i) = r.Value
i = i + 1
Next r
Arrange = Application.Transpose(temp)
End Function
After you have added this code to a new module in your VBA editor you will be able to use it like this on the worksheet:
=INDEX(ARRANGE(Named_Range),1)
To get the first element, for example.

Search a column to match data in another column and count the number of occurences

I've searched the web but I can't find anything specific for this.
I have column A as below, I need to search this column and find out how many occurrences there are of the data in column B, in the example below there a 4 (70011x3 + 70014x1).
A B
h323:70011 70011
70007 70012
70011 70013
h323:70014 70014
sip:70011#domain.com 70015
What formula would I need to use in Excel?
Thanks in advance
... I don't know if there's a simple way to do this with a single worksheet function, but I could think of 2 methods I would use to accomplish this (and would love to see if any person could figure out how to do this in a single, simple, worksheet function I couldn't think of).
Either way, my solutions would be:
1) Use Array Formulas:
This would look as follows:
A B C
h323:70011 70011 {=SUM(--ISNUMBER(FIND(B1,$A$1:$A$5,1)))}
70007 70012 {=SUM(--ISNUMBER(FIND(B2,$A$1:$A$5,1)))}
...etc
Note that for this solution, you have to type it in as an array formula (Hit ctrl+shift+enter) at the end, but when you drag down column C and sum it up, you will get the correct total.
The challenge with this one is that it doesn't really give you the answer in one cell.
2) Use a Custom VBA Function (My Preferred method):
You can write your own VBA function to do this VERY EASILY.
The one I created was:
Function MyFunc(CriteriaRng As Range, SearchRange As Range) As Double
Dim CriteriaCl As Range
Dim SearchCl As Range
Dim RunningTotal As Double: RunningTotal = 0
For Each CriteriaCl In CriteriaRng
For Each SearchCl In SearchRange
If InStr(1, SearchCl.Value, CriteriaCl.Value) > 0 Then RunningTotal = RunningTotal + 1
Next SearchCl
Next CriteriaCl
MyFunc = RunningTotal
End Function
All you can then do is in any cell type in =myfunc(B1:B5,A1:A5) and you will get the right answer.
Hope this helps!
the function COUNTIF takes wildcards.
with excel 2007, you can simply reference the whole column:
=COUNTIF(A:A,"*"&B1&"*")
with 2003 or lower, you have to put the range in as cell references
=COUNTIF(A1:A2000,"*"&B1&"*")
note that numbers will have to be formatted as text, or you can add in the countif to cover numbers
=COUNTIF(A:A,"*"&B1&"*")+COUNTIF(A:A,B1)
You can then use a SUM on the column to get the total.

Resources