How to hide/unhide "na" columns using VBA - excel

I have a row consisting of 31 columns (E27:AI27). All the cells within this range get their input (a number from 1-31) from other cells outside of this range, Let's say from a couple of drop down lists. Depending on how I twerk these drop-down lists the sequence of the numbers in that specific range (E27:AI27) can look like the following alternatives:
Alt. 01: 1 2 3 4 5................................29 30 31 (31 cells total)
Alt. 02: 2 3 4 5 6...........................29 30 31 na (31 cells total)
Alt. 03: 3 4 5 6 7...........................30 31 na na (31 cells total)
Alt. 04: 4 5 6 7 8......................30 31 na na na (31 cells total)
etc etc etc.
Alt. 30: 30 31 na......................na na na na na (31 cells in total)
Alt. 31: 31 na na..................... na na na na na (31 cells in total)
Now what I want to do is to automatically, temporarily, hide those columns that contain string "na" within them. When I then re-twerk my drop down lists again, i want to be able to unhide those columns that change back from "na" to a number. For example, i want to be able to shift between alternative 1 and alternative 2 (see above), depeding on how i twerk my drop down lists. Shifting between these two alternatives would mean that AI27 would go from showing, to hiding, to showing, to hiding and so on (while E27AH27 would all be showing since they would all have numbers inside then, 1-30.
Last but not least, there are two drop down lists that control the values of the cells E27:AI27. Drop down list called month (C18) and drop down list called day (D18). The former (C18) sets the days for a month. If C18=February then there will be 28 days and the last two columns (AH:AI) would be "na". In addition to that the latter drop down list (D18) sets the start day, namly the first number (in cell E27) in the sequence. If D18=21, then the alternative for february month would be:
21 22 23 24 25 26 27 28 na na na na na na na na na na na na.... (containing 23 na)
Can anyone please help me set up VBA code for this?

Try this one :
Private Sub Worksheet_Change(ByVal Target As Range)
'change the "E23" with the cell with your dropdown
If Not Intersect(Target, Range("E23")) Is Nothing Then
Call UnhideHideColumns
End If
End Sub
Sub UnhideHideColumns()
Dim bytColumnCheck As Byte
Dim blnNeedToUnhide As Boolean
Dim intFirstNAColumn As Integer
'check all columns and find out if they are already hidden
For bytColumnCheck = 5 To 35
'if any of the columns is hidden, unhide all the columns to the right and exit loop
If Cells(1, bytColumnCheck).EntireColumn.Hidden = True Then
Range(Cells(1, bytColumnCheck), Cells(1, 35)).EntireColumn.Hidden = False
Exit For
End If
Next bytColumnCheck
'find first occurence of "na" in values, if any exists
If Not Cells.Find("na", LookIn:=xlValues, after:=Range("E27")) Is Nothing Then
intFirstNAColumn = Cells.Find("na", LookIn:=xlValues).Column
'now hide the columns to the right of the first "NA", including
Range(Cells(1, intFirstNAColumn), Cells(1, 35)).EntireColumn.Hidden = True
End If
End Sub
Note that the Call UnhideHideColumns will be called every time the cell with the dropdown changes, even let's say from Set1 to Set1, or from 5 to 5. So it can trigger the macro unnecessary often.

This worked for me:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim i As Integer
For i = 9 To 40: Cells(28, i).EntireColumn.Hidden = Cells(28, i) = "na": Next
End Sub

Related

Insert range of values into a multidimensional array

I am trying to create a multidimensional array where the first column contains identifiers and the adjacent columns contains data relevant to that identifier. So for instance I would like to create an array with the following structure:
Banana 10 20 30 40
Coconut 5 10 2 4
Apple 3 4 5 6
The construction of the array begins with the definition of the relevant identifiers. So for instance in the above that would be Banana, Coconut and Apple. The data I use to construct the array would have a layout as in the below:
Banana 10 20 30 40
Parrot 5 3 1 4
Apple 3 4 5 6
Car 10 20 30 40
Donkey 4 12 3 0
Coconut 5 10 2 4
As such, I start out by defining the Banana, Coconut and Apple identifiers and then want to automatically populate my array based on a loop through of identifier name in the data (I have defined this as "INPUT"). However, I am unsure of how to correctly insert the adjacent data in my array every time there is a match of identifiers. I would much appreciate if someone can explain how I can do this based on the code below.
identifierArray = Array("Banana", "Coconut", "Apple")
NumElements = UBound(identifierArray) - LBound(identifierArray) + 1
For Each Element In identifierArray
ReDim Preserve arr(0 To NumElements, x)
arr(i, 0) = identifierArray(i)
i = i + 1
Next Element
For Each cell In ws.Range("INPUT")
For Each Element In identifierArray
If cell.Value = Element Then
[Need help here]
End If
Next Element
Next cell
I don't need help with creating VLOOKUP or INDEX/MATCH solutions as that is not relevant to the above.
You can fill an array from a range on your sheet like this:
Option Explicit
Sub Test()
Dim arr
With ThisWorkbook.Sheets("Data")
arr = .Range("A1:E6")
End With
End Sub
So a range like this:
Turns into a array like this:
So you don't need to loop at all, which means faster execution and cheaper to code.

Excel - Writing a Complex Formula

I have the following numbers in the range AI3:AJ41:
34 3
26 3
25 3
24 2
24 2
24 2
24 2
24 2
24 2
24 2
24 2
24 2
22 2
22 2
22 2
22 2
21 2
21 2
21 2
21 2
19 2
19 2
19 2
19 2
19 2
19 2
19 2
19 2
17 2
17 2
17 2
15 2
15 2
15 2
15 2
12 1
12 1
12 1
12 1
The AI column contains values while the AJ column contains the following formula in cell AJ3:
=ROUNDUP(AI3/12,0)
which spans down to AI41.
In cell AJ2 I have the following sum formula
=SUM(AJ3:AJ41)
which results in 77.
I want to write a formula into cell AI2, to get the same result as I now have in AJ2 without using the (helper) column AJ i.e. only using the values of the AI column.
Maybe it can be a VB macro to do is, i don't know...
I've provided a link to my Sample File for easy copy/pasting.
Thank you for your time & help.
Updated November 27
I'm still at an airport, and decided to do this problem a stab without VBA/UDF.
An Array function can get you what you want. Type this into your worksheet with CTL SHIFT ENTER for a non-vba solution:
=SUM(ROUNDUP(COUNTIF(A1:AH1,">="&ROW(INDIRECT("A1:A"&MAX(A1:AH1),TRUE)))/12,0))
You can download XLSM file here.
For anyone who ever wishes they could perform VBA "loops" in a regular Excel formula, this is a pretty good example of how to approach as this essentially "loops" through the range with a higher integer each time by using the Row and Indirect functions. Chip Pearson's sight is brilliant for stuff like this.
UPDATED Nov 26
I am sitting bored in an airport, so I will give this another go... I think this custom function will get the OP what they want. Put this in anywhere and you'll get 77. =UMutCustom2(A1:AH1)
The custom function code needed is here:
Function UMutCustom2(rng As Range) As Double
Dim r As Long
For r = 1 To Application.WorksheetFunction.Max(rng)
UMutCustom2 = Application.WorksheetFunction.RoundUp(Application.WorksheetFunction.CountIf(rng, ">=" & r) / 12, 0) + UMutCustom2
Next r
End Function
original Answer
I included both of these examples in a file here.
Probably easiest to use an array formula. In cell Ai1 put this formula: =SUM(ROUNDUP(AI3:AI999/12,0))
However, after typing the formula YOU MUST HIT CTR SHIFT ENTER!
This will create curly brackets around the formula so when you view the formula it should appear: {=SUM(ROUNDUP(AI3:AI999/12,0))} and does sum to 77 on my version of file.
(good news is that in 2019 Excel's new query engine will not require the CTL SHIFT!)
Alternatively, if you want to make a custom function using vba, you could use this custom function, that would not require ctr shift enter... here's VBA code to make that:
Function UMuTCustomFunc(rng As Range) As Double
Dim ws As Worksheet, rCell As Range
Set ws = Sheets(rng.Parent.Name)
For Each rCell In Intersect(ws.UsedRange, rng).Cells
UMuTCustomFunc = Application.WorksheetFunction.RoundUp(rCell.Value / 12, 0) + UMuTCustomFunc
Next rCell
End Function
Sumproduct
If you don't want to use an array formula (too lazy to press CTRL SHIFT ENTER) you can use:
=SUMPRODUCT(ROUNDUP(AI$3:AI$41/12,0))
and press ENTER. The result is 77.
Funny
I didn't even know there was a ROUNDUP function so I used this at first:
=SUMPRODUCT(IF(MOD(AI3:AI41,12)=0,INT(AI3:AI41/12),INT(AI3:AI41/12)+1))
but it doesn't work without entering it as an array formula. This should serve as a reminder that even SUMPRODUCT won't always work as a 'non-array' formula.
Addition
If you want to apply the same principle to the range A1:AH1, use this formula:
=SUMPRODUCT(ROUNDUP($A1:$AH1/12,0))
The result is 87 though.

to get last row of each day from excel sheet

i need to find the last record of each day in excel if there are multiple entries
*******intention is to get EOD balance ROW OF EACH DATE********
like i have data in excel something like this
date CR_DR amount EOD balance
----------------------
7/9/2017 19:09 CR 10 10
7/10/2017 18:37 CR 25 35
7/10/2017 21:06 DR 10 25
7/11/2017 19:21 CR 15 40
7/15/2017 14:17 DR 20 20
7/17/2017 17:12 CR 100 120
7/18/2017 7:44 DR 30 90
7/18/2017 14:08 DR 50 40
7/18/2017 16:52 CR 120 160
for which i need to get data like (get the last row of each day)
7/9/2017 19:09 CR 10 10
7/10/2017 21:06 DR 10 25
7/11/2017 19:21 CR 15 40
7/15/2017 14:17 DR 20 20
7/17/2017 17:12 CR 100 120
7/18/2017 16:52 CR 120 160
Solution 1
Enter the following formula in Cell F2
=IFERROR(MAX(IF(INT($A$2:$A$10)=INT(INDEX($A$2:$A$10, MATCH(0, FREQUENCY(IF(EXACT(INT($A$2:$A$10), TRANSPOSE(INT($F$1:F1))), MATCH(ROW($A$2:$A$10), ROW($A$2:$A$10)), ""), MATCH(ROW($A$2:$A$10), ROW($A$2:$A$10))), 0))),$A$2:$A$10,0)),"")
This is an array formula so commit it by pressing Ctrl+Shift+Enter. Drag/Copy down as required.
Then in Cell G2 enter
=VLOOKUP($F2,$A$2:$D$10,COLUMN(C1)-COLUMN($A$1),FALSE)
Drag this formula across (to right) till Cell I2 and down as required.
See image for reference.
Solution 2
Instead of using an ugly looking long formula, here we'll use a helper column.
In Cell F2 enter the following formula
=MAX(IF(INT($A$2:$A$10)=INT(A2),$A$2:$A$10,0))
This is an array formula so commit it by pressing Ctrl+Shift+Enter. Drag/Copy down as required.
Then in Cell G2 enter
=IFERROR(INDEX($F$2:$F$10,MATCH(0,INDEX(COUNTIF($G$1:G1,$F$2:$F$10),0,0),0)),"")
Drag/Copy down as required.
Finally, in Cell H2 enter
=VLOOKUP($G2,$A$2:$D$10,COLUMN(C1)-COLUMN($A$1),FALSE)
Drag this formula across (to right) till Cell J2 and down as required.
See image for reference.
EDIT : As per comment.
Instead of
=VLOOKUP($G2,$A$2:$D$10,COLUMN(C1)-COLUMN($A$1),FALSE)
use below formula in Cell H2
=INDEX(B$2:B$10,MAX(IF($A$2:$A$10=$G2,ROW($A$2:$A$10)-ROW(INDEX($A$2:$A$10,1,1))+1)))
This is an array formula so commit it by pressing Ctrl+Shift+Enter. Drag this formula across (to right) till Cell J2 and down as required. Drag/Copy down as required.
Screen-Shot :
Ok, this is crude and I'm sure there are "proper" ways to do this, but it works for what you want to do. I used columns A thru D for the data. In column F I used the formula =Left(A3,5) and copied it down. You will have to modify according to your situation. Hope this helps or gets you headed in right direction. Good Luck
Sub FindLast()
'FIND LAST LISTED DATE AND COPY
Dim myCount, myCount2, myRange, myRow, x, y, r
r = 3
myCount = Sheets("Sheet1").UsedRange.Rows.Count - 2
MsgBox "Rows used are " & myCount
For x = 3 To myCount Step 1
y = Range("F" & x).Value
MsgBox "Value of x is " & x
MsgBox "Value of y is " & y
myCount2 = WorksheetFunction.CountIf(Range("F3:F" & myCount + 2), y)
MsgBox "myCount2 value is " & myCount2
Max_date = Application.WorksheetFunction.Max(Range("A" & x & ":" & "A" & x + myCount2 - 1))
myRow = Range("A" & x + myCount2 - 1).Row
MsgBox "myRow number is: " & myRow
MsgBox CDate(Max_date)
Sheets("Sheet1").Range("I" & r).Value = Max_date
Sheets("Sheet1").Range("J" & r).Value = Range("B" & myRow).Value
Sheets("Sheet1").Range("K" & r).Value = Range("C" & myRow).Value
Sheets("Sheet1").Range("L" & r).Value = Range("D" & myRow).Value
r = r + 1
If myCount2 <> 1 Then
x = x + (myCount2 - 1)
Else
x = x
End If
Next x
End Sub
You will notice there are a lot of message boxes. I used these to test the parameters and make sure they were coming out like I wanted. When you are comfortable with how it's working, just comment them out.
Also, the output is set to I3 thru L8 for the example you posted. Please make sure to edit these if your real sheet has any data in these areas. My suggestion is test first to get familiar with what's happening. I did't put any "failsafes" or "exits" in, as I ran out of time. There are plenty of examples on this site to help with that.
Regards
It requires entering the dates in column A (I didn't feel motivated enough to figure out how to pull the unique ones out), but if you are feeling spunky, you could use some array formulas. :-)
Column A Column B C D E
7/9/2017 7/9/2017 19:09 CR 10 10
7/10/2017 7/10/2017 21:06 DR 10 25
7/11/2017 7/11/2017 19:21 CR 15 40
7/15/2017 7/15/2017 14:17 DR 20 20
7/17/2017 7/17/2017 17:12 CR 100 120
7/18/2017 7/18/2017 16:52 CR 120 160
Enter dates in column A. Then in column B enter:
=IFERROR(INDEX(Date,SMALL(IF(ROUNDDOWN(Date,0)=$A1,ROW(Date)-1),COUNTIFS(Date,">="&$A1,Date,"<"&$A1+1))),"")
closing out of the cell with CTRL-SHIFT-ENTER so that the formula in the formula bar looks like
{=IFERROR(INDEX(Date,SMALL(IF(ROUNDDOWN(Date,0)=$A1,ROW(Date)-1),COUNTIFS(Date,">="&$A1,Date,"<"&$A1+1))),"")}
Note: it does not work to enter the curly braces yourself. ;-) You must close out of the cell with CTRL-SHIFT-ENTER!
The Date in the formula is a named range - I don't like typing $A$2:$A$10 all the time. If you don't want to do named ranges, replace Date with $A$2:$A$10 or your applicable range.
Column C:
=IFERROR(INDEX(CR_DR,SMALL(IF(ROUNDDOWN(Date,0)=$A1,ROW(Date)-1),COUNTIFS(Date,">="&$A1,Date,"<"&$A1+1))),"")
Same thing - close out of cell with CTRL-SHIFT-ENTER. Named range again for CR_DR, same rules as for Date above. Copy/paste the for the other columns, changing the range that INDEX is searching as appropriate for the column you want.
Explanation: ROUNDDOWN removes the time from the date in your source data so that it can match it to the date in column A. If the date matches, its row (-1 because the row number of A2 is 2, but it is the first item in $A$2:$a$10) is put into an array for the SMALL function. This chooses the nth smallest item from the array, defined by the COUNTIFS function, which counts how many dates fall between the date in column A and the day after the date in column A (thus actually giving you the largest item). INDEX then uses the range and looks up the row number that SMALL delivered to it. If it errors out, the cell is blank.
If you don't want to hand-enter specific dates, auto-fill for all dates and it will simply be blank on dates with nothing:
7/9/2017 7/9/2017 19:09 CR 10 10
7/10/2017 7/10/2017 21:06 DR 10 25
7/11/2017 7/11/2017 19:21 CR 15 40
7/12/2017
7/13/2017
7/14/2017
7/15/2017 7/15/2017 14:17 DR 20 20
7/16/2017
7/17/2017 7/17/2017 17:12 CR 100 120
7/18/2017 7/18/2017 16:52 CR 120 160

How to Loop through a column in VBA with Muiltiple columns?

I am trying to highlight expiry dates in Excel VBA. I need Column B to Highlight dates that will expire within the next month and Column c to highlight dates that will expire within 4 - 6 months. At present the code works if I delete columns and run the code on one column at a time e.g.
Name Green Will Expire within 1 Month
Name 1 01/01/2013
Name 2 17/07/2013
Name 3 03/04/2013
Name 4 24/03/2013
Name 5 16/07/2013
Name 6 26/01/2013
Name 7 28/06/2013
Name 8 01/07/2013
Name 9 09/01/2013
Name 10 31/07/2013
Name (Column A), Green Will Expire within 1 Month (Column B).
If I run the code with these two columns only (Having deleted Column C) the code works fine. If I include my third column, Orange Expires in 4 - 6 Months (Column C):
Name Green Will Expire within 1 Month Orange Expires in 4 - 6 Months
Name 1 01/01/2013 01/01/2013
Name 2 17/07/2013 01/12/2013
Name 3 03/04/2013 03/04/2013
Name 4 24/03/2013 20/11/2013
Name 5 16/07/2013 16/07/2013
Name 6 26/01/2013 26/01/2013
Name 7 28/06/2013 28/06/2013
Name 8 01/07/2013 01/07/2013
Name 9 09/01/2013 09/01/2013
Name 10 31/07/2013 31/07/2013
Only the second for loop works. I need both of these for loops to run without having to delete any columns.
VBA Code:
Private Sub CommandButton1_Click()
Dim Cell As Range
For Each Cell In Range("B2:B1000").Cells
If Cell.Value <= Date + 30 And Not (Cell.Value < Date) And IsEmpty(Cell.Offset(0, 1).Value) Then
Cell.Interior.ColorIndex = 35
End If
Next Cell
For Each Cell In Range("C2:C1000").Cells
If Cell.Value <= Date + 180 And Not (Cell.Value <= Date + 120) And IsEmpty(Cell.Offset(0, 1).Value) Then
Cell.Interior.ColorIndex = 45
End If
Next Cell
End Sub
Any Help is greatly appreciated.
Thank You
The part of your conditional statement will always evaluate to False if Column C is not empty:
And IsEmpty(Cell.Offset(0, 1).Value)
On a related note, the same fragment is in the second loop, so you may want to remove it from there, too. But I am not sure if that part would be required logic. In any case, the same holds: If column D is not empty, this will always evaluate to false, so the body of the If...Then will be omitted.

Deleting a column and shifting left in Excel if a value in a cell on that column is below a threshold using macros?

I have a data set for an unrelated classification problem I am going true, and I want to cut down on the number of attribute totals. I've put my data set into a CSV format which excel handles quite nicely, but I need to make some changes - and I've never done any VBA so the syntax seems extremely foreign to me.
Basically, I have 7071 columns (attributes) and 70 rows (instances). Cells below each of the columns (say A75 for column A) contain a certain statistic method for attribute evaluation. What I want to do is check against the value in this cell ("A75" in this example), and if it is below a certain threshold, delete the entire column as well as shift everything to the left. So if I had something like :
o A B C D ... XYZ
1 2 5 4 9 ... 2
2 3 9 0 1 ... 3
3 1 1 5 6 ... 0
...
75 0.23 0.5 0.6 0.44 ... 0.8
And my calculations determined that the minimum threshold is 0.3, then the macro would get rid of column A and shift the others to the left:
o A B C ... XYZ
1 5 4 9 ... 2
2 9 0 1 ... 3
3 1 5 6 ... 0
...
75 0.5 0.6 0.44 ... 0.8
Any help at all would be greatly appreciated.
This should do it for you. If you deleting rows or columns begin deleting from the end and move to the start (ie right to left or bottom to top).
Option Explicit
Sub RemoveColumns()
Dim vEvalRange As Variant, ii As Integer
'NOTE: This assumes the usedrange starts at cell A1!
'Screen updating when deleting cells slows things down
Application.ScreenUpdating = False
'Set your evaluation row to an array. It's faster to sort through variables than sheet ranges
vEvalRange = ActiveSheet.Range(ActiveSheet.Cells(ActiveSheet.UsedRange.Rows.Count, 1), ActiveSheet.Cells(ActiveSheet.UsedRange.Rows.Count, ActiveSheet.UsedRange.Columns.Count)).Value
'count from right to left otherwise your columns will shift and mess up your array
For ii = UBound(vEvalRange, 2) To LBound(vEvalRange, 2) Step -1
If vEvalRange(1, ii) < 0.3 Then
ActiveSheet.Columns(ii).Delete
End If
Next ii
Application.ScreenUpdating = True
End Sub

Resources