In excel, I need to count the number of rows based on criteria from values in individual columns, and from the sum of a set of multiple columns.
For example, with the attached image of data, I want to be able to count the number of rows which are "valid" (= when Column A = 1), are over 17 years old (= Column B = >17), and when the sum of columns C to G are equal to zero.
I am currently using countifs function, with individual criteria for columns C to G being zero. However, with my actual data there are many more columns than C to G so the formula becomes too large, they are taking a long time to write and very vulnerable to mistakes.
I have tried using sumproduct but this has the same problem with a long formula.
Formulas I have tried;
=COUNTIFS($A:$A, 1, $B:$B, ">17", $C:$C, 0, $D:$D,0, $E:$E,0, $F:$F,0, $G:$G,0)
=SUMPRODUCT(($A:$A=1)*($B:$B>17)*($C:$C=0)*($D:$D=0)*($E:$E=0)*($F:$F=0)*($G:$G=0))
I know I could create a new column with an If function for the sum of columns C-G first (=IF(SUM(C2:G2)=0, 1, 0)), and then include this as a criteria (new column = 1) within a countifs, but I would like to avoid creating new columns with this data.
I would like to do =COUNTIFS($A:$A, 1, $B:$B, ">17", C:G, SUM(C:G)=0), but sum within countifs is not possible.
Any ideas would be very much appreciated?
Image of data:
Sample data:
| Valid | Age | a | b | c | d | e |
|-------|-----|---|---|---|---|---|
| 1 | 18 | 0 | 0 | 0 | 0 | 0 |
| 0 | 8 | 1 | 0 | 0 | 0 | 1 |
| 0 | 48 | 0 | 1 | 0 | 0 | 1 |
| 1 | 22 | 0 | 0 | 0 | 0 | 0 |
You said that you're unhappy with your COUNTIFS formula, but here's an efficient way to still use COUNTIFS (shorter/easier than VBA) and maintain accuracy.
The formula with your example would be:
=COUNTIFS(A:A,1,B:B,">=18",C:C,,D:D,,E:E,,F:F,,G:G,)
This is likely similar what you were using, but note:
If the criteria is "=0" then no criteria need to be specified between the commas.
If the function applies to the entire column, there is no need to specify row numbers.
In fact, even if there's other values
above/below your data (like headings), they'll be ignored
(unless they
meet all the criteria in the formula, which would be unlikely.)
Want to avoid errors? Make Excel write the formula for you!
If you're concerned about making errors when adjusting the formula to your actual data, let Excel do the work for you with three steps. (First save your workbook.)
Select each column (or a cell from each column) that you want to include in your formula. (To select multiple areas, click the first one and then hold Ctrl while you click the rest.)
Copy the line below by highlighting it and hitting Ctrl+C:
?"=COUNTIFS(A:A,1,B:B,"">=18""";:For Each c In Selection.Columns:?","&Columns(c.Column).Address&",";:Next c:?")"
Hit Alt+F11 then Ctrl+GV then Enter (This immediately runs the VBA code from Step 2.)
The formula will appear on the next line. Just copy/paste into to where you want it!
Example output:
=COUNTIFS(A:A,1,B:B,">=18",$C:$C,,$F:$F,,$G:$G,,$P:$P,,$T:$T,,$AJ:$AJ,,$AC:$AC,)
Edit:
I'm not sure why you want/need to use INDIRECT but it's fine as long as you maintain the same syntax. A simplified example:
I have values in Columns A and B. I want to count rows where A=1 and B=1. My COUNTIFS formula (in D2) would be: =COUNTIFS(A:A,1,B:B,1)
The reason to use INDIRECT could be if, for example, I don't always want this to use Column B -- I want to be able to specify which column to use, in a different cell, maybe D5.
In D5 I enter the text: B:B. Then I change the formula is D2, replacing B:B with INDIRECT(D5).
Since E2 contains the text B:B, the formula is still indirectly referring to B:B, and the result of the formula does not change. If I specify a different column range in E2, the formula will look at the new range.
Important Note:
With SUMIFS, COUNTIFS and AVERAGEIFS, all ranges specified must have the same number of rows and columns as the criteria_range1 argument.
In this formula, that means they must be entire columns.
If you're just trying to refer to a column on a different worksheet, that's fine as long as you use correct syntax, such as: =COUNTIFS(A:A,1,Sheet1!D:D,1) to refer to column D on Sheet1.
If the name of the sheet is contained as text in a cell (D5), but you want the column (D:D) still hard-coded, then your INDIRECT section of the formula would be: INDIRECT(D5&"!D:D")
If the specified worksheet name has a SPACE in it, then you need to surround the name with ' apostophes, in the correct spot:
=COUNTIFS(A:A,1,INDIRECT("'" & D5&"'!D:D"),1)
...which is one of the many reasons spaces and other non-alphanumeric characters should be avoided when naming "anything".
This can easily be done with a custom function using VBA. A custom function is a self programmed function that can be used as a normal function in an Excel cell. the function is called countCorrectEntries and takes a Range as Parameter. So if your table(including header) is located in A1:G5 the function in your target cell would be =countCorrectEntries(A1:G5) What it essentialy does is, it initiallizes a variable countCorrect=0 which is going to count all rows that fullfill all conditions.
The loop:
For i = 2 To UBound(table, 1)
...
Next i
Goes through the rows and checks the conditions that look as follows:
If table(i, columnNumber) <> condition Then
conditionsMet = False
End If
It checks in row i (the iterator) if in a column e.g 1 for first does not meet a certain condition. E.g value is not equal to 1. If that is so, a boolean conditionsMet is set to False and it is not counted as correct.
For your sum problem, I propose the following condition:
'Check sum
tempSum = 0
For j = startColumn To EndColumn
tempSum = tempSum + table(i, j)
Next j
If tempSum > 0 Then
conditionsMet = False
End If
The loop within the loop goes over the columns between startColumn and endColumn e.g 3 and 7 for column C to G and sums the values in tempSum. Then it is checked if the sum is greater 0 and again the conditionsMet is set to False if so.
If after all the conditions are checked the conditionsMet is still True then CountCorrect is incremented by 1.
The following code should work for your problem. Just change the inner loop values for the correct columns and you are all set.
Cheers!
Function countCorrectEntries(Rng As Range)
Dim table As Variant
table = Rng
countCorrect = 0
For i = 2 To UBound(table, 1)
conditionsMet = True
If table(i, 1) <> 1 Then
conditionsMet = False
End If
If table(i, 2) < 18 Then
conditionsMet = False
End If
'Check sum
tempSum = 0
For j = 3 To 7
tempSum = tempSum + table(i, j)
Next j
If tempSum > 0 Then
conditionsMet = False
End If
If conditionsMet = True Then
countCorrect = countCorrect + 1
End If
Next i
countCorrectEntries = countCorrect
End Function
Related
I have a range of numbers and I need to identify if the first number of each cell is repeated anywhere in the corresponding row.
For example, in row 2 below, column 2 and column 3 both start with a 3. I know that if I do =LEFT(TRIM(cell)) to get just the first number but how do I find the rows that have repeated numbers in the row so row 1 isn't marked but row 2 is?
100|600|203|700| |
202|302|301|400|600|
Use a helper column with this as an array formula:
=SUMPRODUCT(--(COLUMN($A1:$E1)<>MATCH(INT($A1:$E1/100),INT($A1:$E1/100),0)))>0
Being an array formula it must be confirmed with Ctrl-Shift-Enter instead of Enter when exiting edit mode. If done correctly then Excel will put {} around the formula.
Consider the following UDF():
Public Function AnyDups(rng As Range) As Boolean
Dim valu() As String
Dim i As Long, L As Long
L = rng.Count
ReDim valu(1 To L)
AnyDups = False
If L = 1 Then Exit Function
i = 1
For Each r In rng
valu(i) = Left(r.Value, 1)
i = i + 1
Next r
For i = 1 To L - 1
v = valu(i)
For j = i + 1 To L
If v = valu(j) Then AnyDups = True
Next j
Next i
End Function
For example:
The code just loops through the possible combinations of left-most characters in the cells.
It should work with either text or numeric data.
One way to do it would be to use this formula as a basis:
=IF(ISERROR(FIND(LEFT(TRIM(A1),1),B1)),FALSE,"Row "& ROW(A1))
Depending on how you want to check your row, you can adapt it. You could either have one formula to check one cell (Lock the A1 reference and drag right) - which would allow you to know where the match is but take more space on the sheet.
Or, if you don't have too many cells to check in each row, you could concatenate all cells in the same formula:
=IF(ISERROR(FIND(LEFT(TRIM(A1),1),B1&C1&D1&E1)),FALSE,"Row "& ROW(A1))
I'm sure Gary's Student will have a more elegant answer though!
My data looks like:
Colums C,D,E,F
Row 1 4,10,40,F
Row 2 4,12,48,F
Row 3 4,14,56,F
Row 4 3,16,48,F
Row 5 1,18,18,F
Row 6 1,20,10,F
Row 7 0,22,0,0
My foucs is on column F. My current array formula in column F is:
=IF(C31=0,VLOOKUP(INDEX($C$30:$C$38,MIN(($C$30:C30*D31)-$E$30:E30)),$C$30:$E$38,1),"F")
In case column C is is equal to 0, I want the formula of column F to return the value of column C,which has the respective minimum difference between column C (allrowsbefore) * column D (currentrow) and E(respective, all rows before). For instance, in row 2 that minimization problem would equal:C1*D2-E1, if C2 would be equal to 0.
However, now i want column F formular to only choose a value of C which is unequal to 0.
Hence, in column F7 i want column F to give me the value 1, as this is a C value which is bigger as 0 and has the respective min difference between (C6*D7)-E6.
(For value 4 of C it would be 32 and for value 3 of C it would be 18).
How and where do i include the if condition, that the value of C chosen has to be bigger as 1, into my existing formula ?
Thanks a lot for helping out, its much appreciated!!! In case i need to split up my existing formula that would be alright, too.
I reread your question more carefully after making my earlier comment, and I think I understand better now. To make sure you get the smallest value greater than 0, you use the following according to this KB article:
=MIN(IF(A1:A10>0,A1:A10))
So your formula in F7 should look like this for the ranges you described:
=IF(C7=0,VLOOKUP(INDEX($C$1:$C$7,MIN((IF($C$1:$C$7>0,$C$1:$C$7)*D6)-$E$1:$E$7)),$C$1:$E$7,1),"F")
EDIT
Okay. I think I finally understand the question. :)
If Cx <> 0, Fx = Cx
If Cx = 0, Fx = the address of the cell in Column C that produces minimum of (C1 * D7 - E1, C2 * D7 - E2, ..., CN * D7 - EN)
From what I know of Excel, I don't think you can do this with a single formula. INDEX and VLOOKUP both look for values in the worksheet, but you're trying to work from the value of a calculation (MIN). Using the array formula to find that value is really smart. But once we get the value (4 in this case), we can't use that to look up the address of the cell in column C that produced that result. I really can't think of any way to do this without a macro. Are you able to do that?
EDIT: Macro Solution
This is a little quick and dirty, but it passed the test. Make sure to save your workbook as a Macro-Enabled workbook. Hit Alt+F11 to open the macro editor. You might need to insert a new module. You can do this by right-clicking the workbook project in the project explorer on the left.
Once you've done this, double-click on the module. You should get a blank white text editor on the right. Paste the following code into that window:
Public Function MinimumC()
Dim rngCurrent As Range
Set rngCurrent = Application.ThisCell
Dim rngMin As Range
Dim minimum As Long
minimum = 100000000
Dim tmp As Long
Dim rngC As Range
Set rngC = ActiveSheet.Range("C1:C" & rngCurrent.Row - 1)
For Each c In rngC.Cells
If c.Value2 <> 0 Then
tmp = c.Value2 * rngCurrent.Offset(0, -2).Value2 - c.Offset(0, 2)
If tmp < minimum Then
minimum = tmp
Set rngMin = c
End If
End If
Next c
MinimumC = rngMin.Value2
End Function
Save the workbook. You can now close the code editor.
Now you can use the following formula in F1 and copy down column F: =IF(C1<>0,C1,MinimumC()).
This will work as long as the value of C1 * Dy - E1 is never greater than 100000000. :)
I've got a spreadsheet full of names and peoples' roles, like the one below:
Role Name Change
1 A Yes
2 A No
5 A N/Ap
1 B Yes
3 B No
2 C Yes
4 C No
I have to come up with a spreadsheet like the one below:
1 2 3 4 5 6
A Yes
B
C
Basically, it should retrieve the information from the first spreadsheet and be layed out clearly on the second one.
There are way too many names and roles to do it manually. VLMOVE won't work and I've tried MATCH and INDEX.
Alternative to #RocketDonkey (but thanks for more complete desired result!) could be to string together Role and Name (say in a column inserted between B & C in Sheet1 [because I think OP wants a separate sheet for the results]):
C2=A1&B2 copied down as required
then use a lookup in Sheet2!B2:
=IFERROR(VLOOKUP(B$1&$A2,Sheet1!$C$2:$D$8,2,FALSE),"")
copied across and down as required.
This assumes the grid for the results (as in question) has been constructed (and that there are 7 rows with data - adjust $8 as necessary otherwise.)
Agree with #Melanie that if you can force your data into a structure that can be interpreted as numbers (1 being yes, 0 being false, for example), Pivot tables are far and away the easiest way (since they will display numbers as the values - not the text). *(see below)
If you want to display arbitrary text, you could try this:
=IF(
SUMPRODUCT(--($A$2:$A$8=F$1),--($B$2:$B$8=$E2),ROW($A$2:$A$8))=0,"",
INDEX(
$A$1:$C$8,
SUMPRODUCT(--($A$2:$A$8=F$1),--($B$2:$B$8=$E2),ROW($A$2:$A$8)),
3))
This checks to see if the SUMPRODUCT of the three columns totals 0 (which will happen when no combo of x/y is matched (like Name: C, Role: 5, for instance), and if so, it returns "". Otherwise, it returns the value in column Value.
*A ‘pivot table option’ would be to represent the Change as a number (eg as formula in D2 copied down). Then create a pivot table from (in the example) A1:D8, with fields as shown. Copy the pivot table to a different sheet with Paste Special/Values (though shown in F11:K15 of same sheet in example). Then in that other sheet select row starting with Name A and as far down as required, Replace -1 with No, 1 with Yes and 0 with N/Ap.
AMENDED
You can use array formulas to reorganize your table, without having to change the its structure. Assuming the data is in the range A2:C8 on Sheet1 and the result table is to be in range A1:G4 on Sheet2, the following formula would be the first entry (role 1 and name A) in the result table.
=IFERROR(INDEX(Sheet1!$A$2:$C$8,MATCH(B$1&$A2,Sheet1!$A$2:$A$8&Sheet1!$B$2:$B$8,0),3),"-")
The MATCH formula returns the row number in which the role/name combination 1A occurs. The INDEX function returns the contents of the cell at the row number found by the MATCH formula and the column number 3, i.e., the Change column of your data table. The IFERROR returns "-" if the role/name combination is not in the data table.
Be sure to enter the formula using the Control-Shift-Enter key combination. Then copy the formula to the remaining cells of the result table.
The data table on Sheet1:
The result table on Sheet2:
Well since there's Excel-VBA tag, thought it would complete the solutions types by adding one in VBA :) The following code is not elegant, in any case you need to use code base, give it a try :)
Code:
Option Explicit
Public Sub sortAndPivot()
Dim d As Object
Dim ws As Worksheet
Dim sourceArray As Variant, pvtArray As Variant, v As Variant
Dim maxRole As Long
Dim i, j, k, m As Integer
Set d = CreateObject("Scripting.Dictionary")
Set ws = Worksheets("Sheet3") '-- set according to your sheet
'-- you could enhance by using an input box to select the range
sourceArray = Application.WorksheetFunction.Transpose(ws.Range("B3:D9").Value)
'-- max role number
maxRole = Application.WorksheetFunction.Max(ws.Range("B3:B9"))
'-- find unique name list
For i = LBound(sourceArray, 2) To UBound(sourceArray, 2)
If Not d.exists(sourceArray(2, i)) Then
d.Add sourceArray(2, i), i
End If
Next i
ReDim pvtArray(d.Count, maxRole)
pvtArray(0, 0) = "Name"
'-- add unique names from dictionary
j = 1
For Each v In d.keys
pvtArray(j, 0) = v
j = j + 1
Next
'-- add unique Role number list
For i = UBound(pvtArray, 2) To LBound(pvtArray) + 1 Step -1
pvtArray(0, i) = i
Next i
'-- sort into the correct positions
For k = LBound(pvtArray, 1) + 1 To UBound(pvtArray, 1)
For m = LBound(pvtArray, 2) + 1 To UBound(pvtArray, 2)
For i = LBound(sourceArray, 2) To UBound(sourceArray, 2)
If pvtArray(k, 0) = sourceArray(2, i) Then
If pvtArray(0, m) = sourceArray(1, i) Then
pvtArray(k, m) = sourceArray(3, i)
End If
End If
Next i
Next m
Next k
'Output the processed array into the Sheet in pivot view.
Range("F2").Resize(UBound(pvtArray) + 1, _
UBound(Application.Transpose(pvtArray))) = pvtArray
Set d = Nothing
End Sub
Results:
There is another way to go about it without VBA. If you create another column that concatenates the first two in the first spreadsheet, like so:
Role Name Change CheckColumn
1 A Yes 1A
2 A No 2A
5 A N/Ap 5A
1 B Yes 1B
3 B No 3B
2 C Yes 2C
4 C No 4C
Then you can use the Offset and Match functions together to find the change in the 2nd sheet. So assuming your data is laid from cell A1, the formula in cell B2 would be:
=iferror(offset(Sheet1!$A$1,match(B$1&$A2,sheet1!$D:$D,0),2),"")
Alternatively, if you put the concatenated column in sheet1 before the role column, you can use vlookup in sheet2, with the formula being:
=iferror(vlookup(B$1&$A2,sheet1!$A:$D,4,false),"")
I am looking for an Excel formula, that can add the results of rows untill it hits an empty cell.
The first row contains a the formula (including value X).
The following rows each contain 3 cells (A,B,C) with values in them.
It is simple True/False ("=IF(AND(X>=A2;X<=B2);C2;0)) that checks if 'X' > is higher than A, and lower than B, and if true, it returns the value of the C.
The formula then looks at the next row and repeats the action, and adds all the returned values to one sum.
ROW1 - A1(formula)
ROW2 - A2(5) - B2(10) - C2(100)
ROW3 - A3(10) - B3(20) - C3(200)
ROW4 - (empty)
ROW2 - If we set X=8, then 8 is > 5, and < 10, and the value 100 is returned.
ROW3 - The action is repeated - this time with the result 0.
ROW4 - The row/cell is empty and the loop stops.
ROW1 - Formula has stopped and shows result (100 + 0 =) 100.
This is exactly why they created the SUMIFS worksheet function.
For the whole column (if there's nothing after the first blank row):
=SUMIFS(C:C, A:A, "<" & X , B:B ,">" & X)
Note: X is the cell that X is located in.
The following will work if you do have stuff after the first blank row:
=SUMIFS(INDIRECT("C2:C" & MATCH(TRUE,INDEX(ISBLANK(A:A),0,0),0)-1),
INDIRECT("A2:A" & MATCH(TRUE,INDEX(ISBLANK(A:A),0,0),0)-1),"<" & M1,
INDIRECT("B2:B" & MATCH(TRUE,INDEX(ISBLANK(A:A),0,0),0)-1),">" & M1)
I'm not at a computer (with excel) right now.
I do know there is a select current region (I think it was mapped to Ctrl-/?) You should be able to use that to detect ends of contiguous regions. If you want only a column of that, use Range.Intersect to limit to a column (e.g. "$D:D" for column D).
This will leverage the power of Excel instead of coding your own.
Have a look at named ranges (Insert/Name, F3 to pick a named range in formula editor). IIRC you could have 'dynamic' ranges (like: automatically detect contiguous areas) under a name as well. I'd have to try how that worked to provide more details
if you're unsure of how many rows there are going to be then you may be better off writing vba to loop through the cells until it finds an empty one
myValue = 8
searchRow = 2
Total = 0
Do While Cells(searchRow, 1).Value <> ""
If (myValue > Cells(searchRow, 1).Value And myValue < Cells(searchRow, 2).Value) Then
Total = Total + Cells(searchRow, 3).Value
End If
searchRow = searchRow + 1
Loop
Cells(1, 1).Value = Total
I've got a spreadsheet like this:
date | 7/1 | 7/2 | 7/3 | 7/4
-----|-----|-----|-----|-----
val | 3 | 5 | 1 | 3
-----|-----|-----|-----|-----
I want to sum the val row, but only up to the current date. So if today were 7/3, the sum would be 3+5+1=9. If today were 7/4, it would be 12.
I figured this out to get the number of columns:
=YEARFRAC(B1,TODAY())*360 // B1 is the first date -- 7/1
but I can't figure out how to tell excel to do the sum:
=SUM(B2:<B+num cols above>2)
Presumably its something to do with references, and lookup, but I'm not really familiar with how those work....
You can use SUMIF:
=SUMIF(A1:E1,"<="&TODAY(),A2:E2)
Assuming your dates are in a1:e1 and your values are in a2:e2.
The OFFSET function should do the job. There's a similar question here. Without knowing the exact layout of your table, I guess your formula would look something like:
=SUM(OFFSET(B2,0,0,1,DAY(TODAY())))
Where DAY(TODAY()) returns the day in the month. This number is then used as the width of the range to SUM over in OFFSET.
I hope that was relatively clear. Good luck.
Put this VBA in a module
Option Explicit
Public Function GetTotal(StartCell As Range) As Integer
Dim i As Integer, j As Integer
i = StartCell.Row
j = StartCell.Column
If Cells(i, j) > Date Then
GetTotal = 0
Exit Function
End If
While Cells(i, j) <> DateAdd("d", 1, Date) 'values up to an including today'
GetTotal = GetTotal + Cells(i + 1, j)
j = j + 1
Wend
End Function
and then use it in a worksheet cell by inserting
=GetTotal([starting date cell])
where [starting date cell] is the cell with the date from which you want the sum. The worksheet cell's value will be the sum
There are three ways to get the sum of the values between the dates.
Using sumifs
=SUMIFS(B2:B15,A2:A15,">="&F3,A2:A15,"<="&F4)
Using sumproduct
=SUMPRODUCT((A2:A15>=F3)*(A2:A15<=F4),B2:B15)
Using offset
=SUM(OFFSET(B1,MATCH(F3,A2:A15,0),0,MATCH(F4,A2:A15,0)-MATCH(F3,A2:A15,0)+1,1))
If Date range is in A column and val range is in B column.