I'm working on some rostering functions in Excel, with the basis being that we have a limited number of people that will need to be rostered-on for work, covering shifts at multiple sites
When planning the rosters, we use a simple set of coloured cells to fill-in for when people are on dayshift, nightshift, not-rostered, annual leave, etc. Given that there are multiple sites we plan for I need to have a quick formula that checks whether that individual has been rostered-on for simultaneous shifts at multiple sites (obviously not possible), so we can easily identify when there is a conflict in the planning stage. The formula needs to return a TRUE, or value (eg. count >1 means more than one assignment for that person) so that I can use either conditional formatting or VBA to highlight a cell/the row and draw attention to the conflict
What I've tried doing is summing/counting cells that are coloured with a few VBA methods:
Function ISFILLED(MyCell As Range)
If MyCell.Interior.colorIndex > 0 Then
Result = True
Else
Result = False
End If
ISFILLED = Result
End Function
and/or:
Function ColorFunction(rColor As Range, rRange As Range, Optional SUM As Boolean)
Dim rCell As Range
Dim lCol As Long
Dim vResult
lCol = rColor.Interior.ColorIndex
If SUM = True Then
For Each rCell In rRange
If rCell.Interior.ColorIndex = lCol Then
vResult = WorksheetFunction.SUM(rCell, vResult)
End If
Next rCell
Else
For Each rCell In rRange
If rCell.Interior.ColorIndex = lCol Then
vResult = 1 + vResult
End If
Next rCell
End If
ColorFunction = vResult
End Function
These both work fine on their own but I'm unable to combine this with a SUMIFS/COUNTIFS style function to only count the number of cells that are coloured when that individual's name appears against that assignment.
If you have a look in the sample image from the roster, you can see that Joe Bloggs 4 has been assigned a shift at both Site 1 and Site 2 on the 21/05/2014. What I'm after is essentially to count number of coloured cells on row, if the individuals name matching the criteria is against those cells. For this example if would be
=COUNTIFS(C8:AQ8, "Joe Bloggs 4", C12:AQ12, *Cell is Coloured*)
Colour of the cell doesn't matter (hence the first function ISFILLED is better as i don't need a reference cell for the fill), as it's just a sense-check.
Appreciate any help/pointers, as it is, I'm stuck!
You won't be able to use SUMIFS and COUNTIFS for this. You need something along these lines:
Function IsFilledArr(rng As Range) As Variant
Dim i As Long, j As Long
Dim v As Variant
ReDim v(1 To rng.Rows.Count, 1 To rng.Columns.Count)
For i = 1 To rng.Rows.Count
For j = 1 To rng.Columns.Count
v(i, j) = rng.Cells(i, j).Interior.ColorIndex > 0
Next j
Next i
IsFilledArr = v
End Function
This UDF can be called as an array formula, i.e. select a range of cells, type the formula in the top-left cell (while the whole range is still selected), and then press Ctrl-Shift-Enter. Then you can operate on the returned array, e.g. SUM it.
Example usage:
where the -- double minus converts Boolean TRUE/FALSE values (which are not summable) into 1's and 0's (which are).
I'll let you adapt this to suit your particular requirements.
EDIT: You want more:
calculate the number of filled cells in column C, where there was an adjacent filled cell in column B - in this case C4 would be the only cell that met that criteria.
This formula will do the trick (entered as an array formula):
=SUM(IF(IsFilledArr(B2:B7)+IsFilledArr(C2:C7)>1,1))
Note the use of SUM(IF(...,1)) to mimic COUNT. That is the way to do it when you're dealing with array formulas.
Read more here: http://www.cpearson.com/excel/ArrayFormulas.aspx
You asked for help/pointers; this should hopefully be more than enough to get you unstuck. You can adapt this approach to meet whatever you exact needs are.
Related
I'm attempting to use a COUNTIF-like function in Excel 2016 to total up a series of cells by their background color... 3 different colors (green, yellow, red) representing 3 different 'states' (first largest, second largest, third largest). I managed to get it working by using this VBA coding:
Function Countcolour(rng As Range, colour As Range) As Long
Dim c As Range
Application.Volatile
For Each c In rng
If c.Interior.ColorIndex = colour.Interior.ColorIndex Then
Countcolour = Countcolour + 1
End If
Next
End Function
However, this particular code doesn't take into account conditional formatting.
So for example, I try to conditionally format a set of data to highlight its first largest value green, second largest yellow, third largest red. I use this VBA function in another block to get a count of all the green highlights. However, it doesn't pick up on the background color of the cell because of the conditional formatting.
I'm sure I'm missing something obvious... I feel like the first part of the If condition should be some form of c.FormatCondition.Interior, but I've tried variations on the theme with no success.
Thanks in advance for any help that can be provided!
This is something many people try to do including me.
There are some useful codes on the internet like http://www.cpearson.com/excel/CFColors.htm
but after searching a lot I found only ONE with the answer
get conditional formatted color with vba in excel and it works!
The solution is easy:
you can use
.cells(1,1).displayformat.interior.color
or
.cells(1,1).displayformat.interior.colorIndex
Example:
Function CountColor(ByRef rng As Range, ByRef color As Long) As Variant
Dim total: total = 0
If (Not (rng Is Nothing)) Then
Dim element As Variant
For Each element In rng
total = total - (element.DisplayFormat.Interior.color = color) ' MINUS because TRUE evaluates to -1
Next element
End If
CountColor = total
End Function
Sub test_countColors()
With Sheet1
Dim rng As Range
Dim color As Long
Dim total As Long
Set rng = Range(.Cells(1, 1), .Cells(3500, 55)) ' Or anay other range
color = 8438015 ' Or any other color
total = CountColor(rng, color)
MsgBox "Total= " & total
End With
End Sub
As you can see in the image that "Col B" has same number multiple times. For ex: "1" is four times, "2" is three times, and so on. However, all these numbers correspond to a specific number from "Col A". What I am trying to do is get the the column I have highlighted in orange and yellow. You can clearly see what I have done. What I need is a excel function that does it for me. This is just a sample. I have dataset with million data points, and I can't type all that.
Thanks!!
Formula for cell E2
=IFERROR(SMALL(IF($B:$B=$D2,ROW($B:$B)-1),COLUMN(A:A)),"")
Entered as an Array Formula (Enter with Ctrl-Shift-Enter rather than just Enter)
Copy accross for as many cells as you wish
Note: this formula is quite slow. A well designed UDF will be faster.
One way to solve this with a UDF is
Function MultiLookup(Val As Variant, rItems As Range, rLookup As Range, Index As Long) As Variant
Dim vItems As Variant
Dim i As Long, n As Long
With rItems
If IsEmpty(.Cells(.Count)) Then
Set rItems = Range(.Cells(1, 1), .Cells(.Count).End(xlUp))
End If
End With
vItems = rItems
n = 0
For i = 1 To UBound(vItems, 1)
If vItems(i, 1) = Val Then
n = n + 1
If n = Index Then
MultiLookup = rLookup.Cells(i, 1)
Exit Function
End If
End If
Next
MultiLookup = vbNullString
End Function
Use like this, for cell E2
=MultiLookup($D2,$B:$B,$A:$A,COLUMN(A:A))
Again, copy accross for as many cells as use wish
Here is another non UDF solution, that works fast. Please note that it uses the AGGREGATE-Function, which is only available since Excel-2010.
Put this in E2 and drag across.
=INDEX($A$2:$A$1000,AGGREGATE(15,6,Row($1:$1000)/($B$2:$B$1000=$D2),COLUMN(A:A)))
You can also wrap this formula with a IFERROR to make sure you don't get the #VALUE! Error.
A PivotTable, with it's body copied into D2 and then blanks removed should be quick:
Currently I have a medical spread-sheet with a list of clients that we have serviced. We have 8 different clinical categories which are denoted by different acronyms - HV,SV,CV,WV,CC,OV,TS and GS.
A client can receive multiple therapies i.e. HV,SV,CV - in the background we have a counter mechanism which would increment each of these records by 1.The formula used for this counter is:
=(LEN('Parent Sheet'!F25)-LEN(SUBSTITUTE('Parent Sheet'!F25,'Parent Sheet'!$P$4,"")))/LEN('Parent Sheet'!$P$4)
At the bottom of the sheet we then have a sum which ads up all the treatments that occurred for that week.
Now the tricky part about this is that we have almost a year's worth of data in this sheet but the summing formulas are set as: SUM(COLUMN 6: COLUMN 53) but due to a need to increase the entries beyond this limit, we have to adjust the sum formula. We have 300 SUM Formulas adding up each of the 8 Criteria items and assigning them to the HV,SV,SC,WV etc. counters.
Would we have to adjust this manually one by one or is there a easier way of doing this?
Thank you very much!
To me, I think you should change the sheet layout a little, create a User Defined Function (UDF) and alter the formulas in your Sum rows for efficient row/column adding (to make use of Excel's formula fill). The only issue is that you need to save this as a Macro-Enabled file.
What you need to change in the formulas is to utilize $ to restrict changes in column and rows when the formula fill takes place.
To illustrate in an example, consider:
Assuming the first data starts at row 6, and no more than row 15 (you can use the idea of another data gap on the top). Alter the Sum row titles to begin with the abbreviation then create a UDF like below:
Option Explicit
' The oRngType refers to a cell where the abbreviation is stored
' The oRngCount refers to cells that the abbreviation is to be counted
' Say "HV" is stored in $C16, and the cells to count for HV is D$6:D$15,
' then the sum of HV for that date (D16) is calculated by formula
' `=CountType($C16, D$6:D$15)`
Function CountType(ByRef oRngType As Range, ByRef oRngCount) As Long
Dim oRngVal As Variant, oVal As Variant, oTmp As Variant, sLookFor As String, count As Long
sLookFor = Left(oRngType.Value, 2)
oRngVal = oRngCount.Value ' Load all the values onto memory
count = 0
For Each oVal In oRngVal
If Not IsEmpty(oVal) Then
For Each oTmp In Split(oVal, ",")
If InStr(1, oTmp, sLookFor, vbTextCompare) > 0 Then count = count + 1
Next
End If
Next
CountType = count
End Function
Formulas in the sheet:
Columns to sum are fixed to rows 6 to 15 and Type to lookup is fixed to Column C
D16 | =CountType($C16,D$6:D$15)
D17 | =CountType($C17,D$6:D$15)
...
E16 | =CountType($C16,E$6:E$15)
E17 | =CountType($C17,E$6:E$15)
The way I created the UDF is to lookup and count appearances of a cell value (first argument) within a range of cells (second argument). So you can use it to count a type of treatment for a big range of cells (column G).
Now if you add many columns after F, you just need to use the AutoFill and the appropriate rows and columns will be there.
You can also create another VBA Sub to add rows and columns and formulas for you, but that's a different question.
It's isn't a great idea to have 300 sum formulas.
Name your data range and include that inside the SUM formula. So each time the NAMED data range expands, the sum gets calculated based on that. Here's how to create a dynamic named rnage.
Sorry I just saw your comment. Following is a simple/crude VBA snippet.
Range("B3:F12") is rangeValue; Range("C18") is rngTotal.
Option Explicit
Sub SumAll()
Dim WS As Worksheet
Dim rngSum As Range
Dim rngData As Range
Dim rowCount As Integer
Dim colCount As Integer
Dim i As Integer
Dim varSum As Variant
'assuming that your said mechanism increases the data range by 1 row
Set WS = ThisWorkbook.Sheets("Sheet2")
Set rngData = WS.Range("valueRange")
Set rngSum = WS.Range("rngTotal")
colCount = rngData.Columns.Count
'to take the newly added row (by your internal mechanism) into consideration
rowCount = rngData.Rows.Count + 1
ReDim varSum(0 To colCount)
For i = 0 To UBound(varSum, 1)
varSum(i) = Application.Sum(rngData.Resize(rowCount, 1).Offset(, i))
Next i
'transpose variant array with totals to sheet range
rngSum.Resize(colCount, 1).Value = Application.Transpose(varSum)
'release objects in the memory
Set rngSum = Nothing
Set rngData = Nothing
Set WS = Nothing
Set varSum = Nothing
End Sub
Screen:
You can use named ranges as suggested by bonCodigo or you could use find and replace or you can insert the columns within the data range and Excel will update the formula for you automatically.
I have a sheet of data and am attempting to check column E10 TO I610 to see if the values in there are more than 11538 and the value in cell J5 is "weekly". If the conditions are true, add the values that are more than 11538 and multiply them by 8.4. How do I go about doing this?
Not too strong with vba so please bear with me.
If schedType = "Weekly" And Range("E10,I610").Value > 11538 Then
Range("H6").Value = "WOW"
ElseIf schedType = "Monthly" Then
Range("H6").Value = "10"
End If
I tried the above way to achieve what I want. Though the code above wont do the exact calculations im after, its just a test. Like I said, I'm attempting to search the range E10 to I610 for any values greater than 11538, then total them and finally find 8.4% of the total.
Its a bit complicated and any assistance is greatly appreciated.
This doesn't work for a lot of reasons, not the least of which is this:
Range("E10,I610").Value
For starters, Range("E10,I610") is a range of only two cells, you guessed it: E10 and I10. Use a colon to create a continuous range object, Range("E10:I610"). Furthermore, the .Value property of a multi-cell range will always, only return the value in the top, left cell.
So, since the value of E10 was not > 11538, the first If statement returns False, and the rest of your code within that block is omitted.
Then, it will continues to fail because you have not structured the code correctly.
There are several ways to work with multiple cells/ranges, I will give you one example which is not very efficient, but it will work for your purposes. I will use a For each loop to iterate over every cell in the Range("E10:I610"), and then check those values against 11538, summing the values greater than 11538.
Sub TotalCells()
Dim schedType as String
Dim rng as Range
Dim cl as Range
Dim myTotal as Double
Set rng = Range("E10:I610")
schedType = Range("J5").Value
'## Check what schedType we are working with:
If schedType = "Weekly" Then
For each cl in rng.Cells
If cl.Value > 11538 Then myTotal = myTotal + cl.Value
Next
'## Multiply the sum by 8.4%
myTotal = 0.084 * myTotal
'## Display the result:
MsgBox "The weekly total is: " & myTotal, vbInformation
ElseIf schedType = "Monthly" Then
' You can put another set of code here for Monthly calculation.
End If
## Print the total on the worksheet, cell H6
Range("H6").Value = myTotal
End Sub
As I said, this is not efficient, but it illustrates a good starting point. You could also use formulas like CountIfs or SumIfs or use the worksheet's AutoFilter method and then sum the visible cells, etc.
In the future, it is always best to post all, or as much of your code as possible, including the declaration of variables, etc., so that we don't have to ask questions like "What type of variable is schedType?"
i got some countifv thats counts visible and sumifsv which sum visible cells only ... i want sumif only visible using vba
Default Using SUMIFS on visible lines only
I have a large table, lots of rows and lots of columns that has material (Sand, stone, etc.) batched information. I wanted to be able to allow the user to use filters to select which data they want to view, then be able to review some summary information (totals by hour of the day, totals by material, etc.) on other sheets.
I had it working really well and fast using DSUM and/or SUBTOTAL, but these functions don't exclude non-visible (Filtered) lines in a list.
Using some previous posts I saw on this site, I was able to come up with something that works, but it is extremely slow.
I was hoping that more experienced folks could advise me on something that was faster.
What I need to do is to take a list of records on a sheet that contain up to 30 material columns of information (Target and Actual batch weight amounts.) I need to group each of these lines by material into a relative time bucket (12:00 AM to 12:59 AM, 1:00 AM to 1:59 AM, etc.) The user can of course select with Time Bucket they want to view
I am using the sumifs function with two critieria ( i.e. Time >=12:00 and Time < 1:00 ) to get the hourly buckets.
You can also see from this code that I have to count the number of lines for the data and each criteria value because I could not figure out how to set the range of "B" & "C" without counting. Since I am using a filter, I know that the ranges (from a row perspective) of A,B & C are the same, just the relative columns are different. I tried to use the offset function, (i.e. Range(B) = Range(A).Offset(-1,0).Select or B = A.offset(-1,0).Select but they failed for some reason, and no error messages either. I think I somehow turned the erroring off.
Anyway, long story, really could use some help. Here is the related code:
Function Vis(Rin As Range) As Range
'Returns the subset of Rin that is visible
Dim Cell As Range
'Application.Volatile
Set Vis = Nothing
For Each Cell In Rin
If Not (Cell.EntireRow.Hidden Or Cell.EntireColumn.Hidden) Then
If Vis Is Nothing Then
Set Vis = Cell
Else
Set Vis = Union(Vis, Cell)
End If
End If
Next Cell
End Function
Function SUMIFv(Rin As Range, CriteriaRange1 As Range, CriteriaValue1 As Variant, CriteriaRange2 As Range, CriteriaValue2 As Variant) As Long
'Same as Excel SUMIFS worksheet function, except does not count
'cells that are hidden
Dim A1() As Range
Dim B1() As Range
Dim C1() As Range
Dim Csum As Long
' First count up the number of ranges
Cnt = 0
For Each A In Vis(Rin).Areas
Cnt = Cnt + 1
Next A
ReDim A1(1 To Cnt)
ReDim B1(1 To Cnt)
ReDim C1(1 To Cnt)
CntA = 1
For Each A In Vis(Rin).Areas
Set A1(CntA) = A
CntA = CntA + 1
Next A
CntB = 1
For Each B In Vis(CriteriaRange1).Areas
Set B1(CntB) = B
CntB = CntB + 1
Next B
CntC = 1
For Each C In Vis(CriteriaRange2).Areas
Set C1(CntC) = C
CntC = CntC + 1
Next C
If CntA <> CntB Or CntB <> CntC Then
MsgBox ("Error in Sumifs Function: Counts from Ranges are not the same")
End If
Csum = 0
For Cnt = 1 To CntA - 1
Csum = Csum + WorksheetFunction.SumIfs(A1(Cnt), B1(Cnt), CriteriaValue1, C1(Cnt), CriteriaValue2)
Next
SUMIFv = Csum
End Function
((countifv visible cells only
If you are interested, here is a more general COUNTIF solution, and one that you can also apply to SUM and other functions that operate on ranges of cells.
This COUNTIFv UDF uses the worksheet function COUNTIF to count visible cells only, so the Condition argument works the same as with COUNTIF. So you can use it just as you would COUNTIF:
=COUNTIFv(A1:A100,1)
Note that it uses a helper function (Vis) that returns the disjoint range of visible cells in a given range. This can be used with other worksheet functions to cause them to operate only on the visible cells. For example,
=SUM(Vis(A1:A100))
yields the sum of the visible cells in A1:A100. The reason why this approach of using Vis directly in the argument list does not work with COUNTIF is that COUNTIF will not accept a disjoint range as an input, whereas SUM will.
Here's the UDF code:
Function Vis(Rin As Range) As Range
'Returns the subset of Rin that is visible
Dim Cell As Range
Application.Volatile
Set Vis = Nothing
For Each Cell In Rin
If Not (Cell.EntireRow.Hidden Or Cell.EntireColumn.Hidden) Then
If Vis Is Nothing Then
Set Vis = Cell
Else
Set Vis = Union(Vis, Cell)
End If
End If
Next Cell
End Function
Function COUNTIFv(Rin As Range, Condition As Variant) As Long
'Same as Excel COUNTIF worksheet function, except does not count
'cells that are hidden
Dim A As Range
Dim Csum As Long
Csum = 0
For Each A In Vis(Rin).Areas
Csum = Csum + WorksheetFunction.CountIf(A, Condition)
Next A
COUNTIFv = Csum
End Function ))
this both are countif and sumifs with visible cells but i need sumif not sumifs please edit the 2nd code and compile and post the correct program :)
I don't think this is correct...
......................................using DSUM and/or SUBTOTAL, but
these functions don't exclude non-visible (Filtered) lines in a list.
The behaviour for SUBTOTAL is different for "filtered" and "hidden" rows.
This is from the excel help:
For the function_num constants from 1 to 11, the SUBTOTAL function
includes the values of rows hidden by the Hide Rows command under the
Hide & Unhide submenu of the Format command in the Cells group on the
Home tab in the Excel desktop application. Use these constants when
you want to subtotal hidden and nonhidden numbers in a list. For the
function_Num constants from 101 to 111, the SUBTOTAL function ignores
values of rows hidden by the Hide Rows command. Use these constants
when you want to subtotal only nonhidden numbers in a list.
The SUBTOTAL function ignores any rows that are not included in the
result of a filter, no matter which function_num value you use.