Am I using the isnumeric function correctly? - vba

This program is to convert a column of data from cumulative to non-cumulative. On my sheet I have A1, B1, and C1 with the text Non-Cumulative, Cumulative, and Converted, respectively. I have numbers 1 to 10 beneath A1, then them summed cumulatively beneath B1. C1 is where I want to convert column B back to non-cumulative.
The IsNumeric is used to make the first row of data in C equal to the first row of data in B. It should detect that the title is above the number it is evaluating, thus knowing that no calculations have to be performed. For the rest of them, it'll see that the number above the one it is evaluating is a number, and thus the calculation has to be done.
My problem is that it isn't working. I think the reason is because IsNumeric() keeps coming back as false. Is there a different function I should be using? Do cell references not work in IsNumeric?
Here's the program!
Option Explicit
Dim i As Variant
Sub Conversion()
Sheets("Test Sheet").Select
For i = 1 To 10
If IsNumeric("B" & i) = False Then
Range("C" & i + 1) = Range("B" & i + 1)
Else: Range("C" & i + 1) = Range("B" & i + 1) - Range("B" & i - 1)
End If
Next
End Sub

The way you wrote your code is logical, just a minor syntax changes you need initially. However,
It's also best to check if the range is empty first...
Then check on if the value is numeric.
Better even, if you set the Range into a Range object and use offset
Code:
Option Explicit '-- great that you use explicit declaration :)
Sub Conversion()
Dim i As Integer '-- integer is good enough
Dim rngRange as Range
'-- try not to select anything. And for a cleaner code
Set rngRange = Sheets("Test Sheet").Range("B1")
For i = 1 To 10
If (rangeRange.Offset(i,0).value) <> "" then '-- check for non-empty
If IsNumeric(rangeRange.Offset(i,0).value) = False Then
rangeRange.Offset(i+1,1) = rangeRange.Offset(i+1,0)
Else
rangeRange.Offset(i+1,1) = rangeRange.Offset(i+1,0) - rangeRange.Offset(i-1,0)
End If
End if
Next i '-- loop
End Sub
To make your code more dynamic:
Another suggestion, you may simply Application.WorkSheetFunction.Transpose() the entire B column range that you need to validate into a variant array
Process the array and Transpose back to the Range with column B and C.
By doing so, you may omit setting for loop size manually but setting it using Lower and Upper bound of the array ;)

You need to check if the range of B i is numeric, not the string "B" & i
and rather than selecting the sheet, simply using a parent identifier like:
sheets("sheet1").range("B" & i)
This will help you avoid errors in your code
For i = 1 To 10
If IsNumeric(sheets("test sheet").range("B" & i).value) = False Then
Range("C" & i + 1) = Range("B" & i + 1)
Else: Range("C" & i + 1) = Range("B" & i + 1) - Range("B" & i - 1)
End If
Next

Related

VBA Excel Format Range when value is found

I'm trying to implement a macro that looks for the words "TRUE" and "FALSE" in a huge array of data - and then, when found, changes the color of the cells above it.
Specifically, I would like it to color not the TRUE/FALSE-cell, but the 30 cells directly above it. This is where things get tricky... I hope someone can help.
I've tried adapting the below code, but mostly I'm adding it as inspiration at this point.
Sub ChangeColor()
lRow = Range("C" & Rows.Count).End(xlUp).Row
Set MR = Range("C2:C" & lRow)
For Each cell In MR
Select Case cell.Value
Case "Yes"
cell_colour = 4
Case "y"
cell_colour = 4
Case Else
cell_colour = 3
End Select
cell.Interior.ColorIndex = cell_colour
Next
End Sub
Using a datafield array
Looping through a range is always time consuming; this should speed it up.
Caveat: Formatting single cells can maximize file size, so at least I reformat the whole column C to xlColorIndexNone.
Option Explicit
Public Sub Mark30CellsAbove()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("MySheet")
Dim v As Variant
Dim i As Long, j As Long, n As Long, m As Long, r As Long
Dim Rng As Range
Dim t As Double
' stop watch
t = Timer
' get last row in column C
n = ws.Range("C" & ws.Rows.Count).End(xlUp).Row
' get values to one based 2dim array
v = ws.Range("C1:C" & n).Value
' clear existing colors over the WHOLE column to minimize file size
ws.Range("C:C").Interior.ColorIndex = xlColorIndexNone
' loop through C2:Cn and mark 30 rows before found condition
For i = 2 To n
' check condition, find string "true" or "false"
If InStr(".true.false.", "." & LCase(v(i, 1)) & ".") > 0 Then
' set range block - fixed rows count 30 above found cell
If i < 32 Then ' only in case of less than 30 rows
Set rng = ws.Range("C2:C" & (i - 1))
Else
Set rng = ws.Range("C" & (i - 30) & ":C" & (i - 1))
End If
rng.Interior.ColorIndex = 4
End If
Next i
MsgBox "Time needed: " & Format(Timer - t, "0.00") & " seconds."
End Sub
Of course you could also loop within If - EndIf, just to see this slower method:
If InStr(".true.false.", "." & LCase(v(i, 1)) & ".") > 0 Then
' Always avoid to loop through a range
' For j = i - 1 To i - 30 Step -1
' If j < 2 Then Exit For ' optional escape if one line under title row
' ws.Cells(j, 3).Interior.ColorIndex = 4
' Next
End If
The code that I posted should only highlight cells in column B whose value is different from the corresponding cell in column A. I tested it and it worked OK.
If you want to try conditional formatting:
Select column B, or the part of column B that you want to colour conditionally.
In the following, I will assume that B1 is the active cell within the selection.
On the Home tab of the ribbon, click Conditional Formatting > New Rule...
Select "Use a formula to determine which cells to format".
Enter the formula =B1<>A1
If the active cell is not in row 1, adjust the formula accordingly. For example, if the active cell within the selection is B3, use =B3<>A3
Click Format...
Activate the Fill tab.
Select the desired fill colour.
Click OK until all dialogs have closed.
Change some values in column A and/or B to see the result.
Refer - https://social.technet.microsoft.com/Forums/ie/en-US/2fffa4d8-bbba-473b-9346-5fce8f0728a8/using-vba-to-change-a-cell-colour-based-on-the-information-in-a-different-cell-excel-2010?forum=excel
First you need to check whether the row of the cell is higher than 30 and then it you can offset to change the color:
Thus instead of this line: cell.Interior.ColorIndex = cell_colour
write this:
If cell.Row > 30 Then cell.Offset(-30, 0).Interior.ColorIndex = cell_colour
This may be done without VBA. You should set up two conditional formatting with formulas. First:
=COUNTIF(OFFSET(INDIRECT(ADDRESS(ROW(), COLUMN())),1,0,29,1), "TRUE")>0
and the same for false. To highlight the cell you just need to use Highlight Cell Rules (the top option for CF).
I would do this with conditional formatting
Mark all your data and press "Conditional Formatting". Enter 2 rules with Use a formula...
First rule is for TRUE. Assuming that you start with Col A:
=COUNTIF(A2:A31;TRUE)
The second rule is similar, just exchange TRUE by FALSE. Below the formula, press the "Format" button to set the color.
Explanation:
I reverted the logic: Instead of searching for TRUE/FALSE and then format the cells above, I look for every cell if it has at least one entry TRUE resp. FALSE in the next 30 cells. However, I have to admit I don't know how fast Excel can handle such a large amount of data.

Cycle through datasets, columns and then rows to add comments based on other cells

I'm trying to make a function to do the following:
Cycle through all my datasets in my sheet
Cycle through each column in my datasets
Look at the title for that column and check if it is in my list.
Find find a few various other columns, but this time using .Find
Now cycle through each row in the column for that specific dataset
Use the column references found in point 4 and the row from point 5 to put the cell's into a variable that will be used on step 7 which is to insert a formatted comment in the originally found column (for that row).
I've tried getting some code working from what I found on a different site but I can't get it working correct, I'm stuck at part 5.
A data example could look like:
My attempted code looks like:
Sub ComTest()
COMLIST = ";Cond;"
Set rng = Range("A1:A" & Cells(Rows.Count, "A").End(xlUp).Row)
For Each a In rng.SpecialCells(xlCellTypeConstants).Areas
With a.CurrentRegion
Set r = .Rows(1)
For j = 1 To r.Columns.Count
TitleCell = r.Cells(j).Address
v = ";" & Range(TitleCell).Value & ";"
'-----------------------------------------------------------------------------------------
If InStr(1, COMLIST, v) Then
On Error Resume Next
xRange = .Offset(1).Resize(.Rows.Count - 1).Columns(j).Address
For i = 1 To UBound(xRange)
v = b.Value
Next i
Condw = r.Columns.Find(Replace(v, ";", "") & " " & "w", lookAt:=xlWhole).Column
Condw = .Cells(r, Condw).Address
' Add more stuff here
End If
'-----------------------------------------------------------------------------------------
Next j
End With
Next a
End Sub
As for part 7, the output would essentially be as follows for "row 1" but this part I should be able to do, it's the looping part that I am struggling with.
This question raises a few points that this answer might resolve for you and others in the future:
I note that not many of your previous questions have accepted answers, and that several of them present answers but you have needed to respond by saying it doesn't suit your needs for a certain reason. It suggests you aren't really providing the right details in your question. I think that's the case here. Perhaps you could outline the outcome you are trying to achieve and, especially for Excel VBA, the precise structure of your spreadsheet data. It's tempting to think in this question that you simply want to know how to take the values of Columns C to F and write them to a comment in Column B for any row that contains data.
Using web code can often take more time to understand and adapt than learning the code syntax from first principles. Your provided code is difficult to follow and some parts seem odd. I wonder, for example, what this snippet is meant to do:
xRange = .Offset(1).Resize(.Rows.Count - 1).Columns(j).Address
For i = 1 To UBound(xRange)
v = b.Value
Next i
Using Option Explicit at the top of your module (which forces you to declare your variables) makes VBA coding and debugging much easier, and code submitted on SO is easier to follow if we can see what data types you meant variables to hold.
If your question is merely "How do I take the values of Columns C to F and write them to the cell in Column B for any row that contains data?", then your code could be as simple as:
Dim condCol As Range
Dim cell As Range
Dim line1 As String
Dim line2 As String
Dim cmt As Comment
'Define the "Cond" column range
'Note: this is an unreliable method but we'll use it here for the sake of brevity
Set condCol = ThisWorkbook.Worksheets("Sheet1").UsedRange.Columns("B")
'Delete any comment boxes
condCol.ClearComments
'Loop through the cells in the column and process the data if it's a number
For Each cell In condCol.Rows
If Not IsEmpty(cell.Value) And IsNumeric(cell.Value) Then
'Acquire the comment data
line1 = "Cond: " & cell.Offset(, 1).Value & "/" & cell.Offset(, 2).Value & _
" (" & Format(cell.Offset(, 3), "0.00%") & ")"
line2 = "Cond pl: $" & cell.Offset(, 4).Value
Set cmt = cell.AddComment(line1 & vbCrLf & line2)
'Format the shape
With cmt.Shape.TextFrame
.Characters(1, 5).Font.Bold = True
.Characters(Len(line1 & vbCrLf), 8).Font.Bold = True
.AutoSize = True
End With
End If
Next
If, on the other hand, your question is that you have unreliable data on your spreadsheet and your only certainty is that the headings exist on any one row, then some form of search routine must be added. In that case your code could look like this:
Dim rng As Range
Dim rowRng As Range
Dim cell As Range
Dim condCol(0 To 4) As Long
Dim line1 As String
Dim line2 As String
Dim allHdgsFound As Boolean
Dim i As Integer
Dim cmt As Comment
Set rng = ThisWorkbook.Worksheets("Sheet1").UsedRange
rng.ClearComments
For Each rowRng In rng.Rows
If Not allHdgsFound Then
'If we haven't found the headings,
'loop through the row cells to try and find them
For Each cell In rowRng.Cells
Select Case cell.Value
Case Is = "Cond": condCol(0) = cell.Column
Case Is = "Cond w": condCol(1) = cell.Column
Case Is = "Cond r": condCol(2) = cell.Column
Case Is = "Cond %": condCol(3) = cell.Column
Case Is = "Cond wpl": condCol(4) = cell.Column
End Select
Next
'Check if we have all the headings
'by verifying the condCol array has no 0s
allHdgsFound = True
For i = 0 To 4
If condCol(i) = 0 Then
allHdgsFound = False
Exit For
End If
Next
Else
If Not IsEmpty(rowRng.Cells(1).Value) Then
'The cell has values so populate the comment strings
line1 = "Cond: " & rowRng.Columns(condCol(1)).Value & "/" & _
rowRng.Columns(condCol(2)).Value & _
" (" & Format(rowRng.Columns(condCol(3)).Value, "0.00%") & ")"
line2 = "Cond pl: $" & rowRng.Columns(condCol(4))
Set cmt = rowRng.Columns(condCol(0)).AddComment(line1 & vbCrLf & line2)
'Format the shape
With cmt.Shape.TextFrame
.Characters(1, 5).Font.Bold = True
.Characters(Len(line1 & vbCrLf), 8).Font.Bold = True
.AutoSize = True
End With
Else
'We've reached a blank cell so re-set the found values
allHdgsFound = False
Erase condCol
End If
End If
Next
Of course your data might be structured in any number of other ways, but we don't know that. My point is that if you can be more specific in your question and provide an outcome you are trying to achieve, you are likely to receive answers that are more useful to you.

VBA find cell of closest value

I have an excel file that looks like:
12123 51212
12123.5 45832
12124 37656
12124.5 32987
12125 42445
and so on, where column A is always 0.5 increasing and column B has a certain output.
Now I have a specific value in cell E2, say 12124,23 and I want a VBA code to return, in this case, that the best matching value is in cell A3, because I need this cell location in further code, I don't need the corresponding value in column B. I don't know how to start, however. The file can be up to 30000 rows big.
I'd only like to know first which method to use, then I will try to write the code myself of course :)
JV
You don't have to use VBA for your problem, Excel will do it perfectly fine!
Try this
=vlookup(E2;A:A;2;true)
and for what you are trying to do, you HAVE TO sort your A column in an ascending fashion, or else you will get an error!
And if you do need that in VBA,
a simple for+if structure with a test like this
Function pr24(ByVal Value_To_Match As Double) As Range
For i = 2 To ActiveSheet.Cells(Rows.Count, 1).End(xlUp).Row
If Cells(i, 1) > Value_To_Match Then
If Abs(Cells(i - 1, 1) - Value_To_Match) >= Abs(Cells(i, 1) - Value_To_Match) Then
pr24 = Range(Cells(i, 1))
Else
pr24 = Range(Cells(i - 1, 1))
End If
Exit For
Else
End If
Next i
End Function
or you can use the worksheet function Vlookup
Application.WorksheetFunction.VLOOKUP()
You could use VLOOKUP function for this:-
Application.WorksheetFunction.VLOOKUP(lookup_value, table_array, column_index, range_lookup)
Set your values as below:-
lookup_value = 12124.23
table_array = would be the range Ax:Bx containing your values
column_index = 2 (the second column of table_array)
range_lookup = true
Setting range_lookup to true means that if the vlookup doesn't find the exact value it will return the closest match.
Note this will only work if the values in column A are sorted in ascending order.
Hope this helps.
You need to sort your data in column A first (smallest to largest), and then you can use a simple lookup formula:
=LOOKUP(E2,A:A)
If you don't want to sort the data, then you can use a VBA loop like so - however this is very inefficient - you should always use worksheet formulas where you can:
Sub SO()
Dim resultCell As Excel.Range
Dim checkCell As Double
Dim bestDiff As Double
checkCell = Range("E2").Value
bestDiff = checkCell
For i = 1 To Range("A" & Rows.count).End(xlUp).Row
If Range("A" & i).Value <= checkCell Then
If (checkCell - Range("A" & i).Value) < bestDiff Then
bestDiff = checkCell - Range("A" & i)
Set resultCell = Range("A" & i)
End If
End If
Next i
MsgBox "Best match is in " & resultCell.Address
Set resultCell = Nothing
End Sub
You dont'need VBA, a call co VLOOKUP Excel function will do the trick; remember to set the last parameter to true, to find a non exact match with the searched value
It should be like something similar to:
= VLOOKUP(E2, A:B, 2, true)

excel VBA : how to skip blank cells between 2 cells that contain values?

I am working out a button that can auto sum value at column C that column A = column B
like the picture :
PIC:
I can only copy the value in column C (that the word in column A = column B) to column E so far.
the code
Private Sub CommandButton2_Click()
Dim i As Integer, q As Integer
q = 2
For i = 3 To 100
Range("E" & q).Value = Range("b" & 3).Value
If Range("B" & i).Value = "A-RDL1" And Range("c" & i).Value = "OPEN" Then
Range("E" & i).Value = Range("d" & i).Value
End If
Next i
End Sub
the question 1) is how can I skip the blanks E9 to E17, so the numbers can be continuous? (AFTER CLICK THE BOTTON)
question 2) is it possible to auto sum the Numbers in column E instead of show each?
Thanks a lot and sorry for my poor English...
1) Yes, you can skip those, just carry out a check in the cell value and compare to empty string: Range("").Value2 = "". I personally prefer to do it like this though, to avoid false positives: Len(Trim(Range("").Value2)) = 0.
2) Yes, you can do that. just declare an Integer variable or two and use that to carry out a running count of your values.

VBA countif statement only returns 0

I'm working on a macro that is supposed to count the number of times the term "GM" appears in a column. I decided to use a countif statement, as I have before and it worked well. However, for some reason when I run my code it outputs 0 every time, which definitely is not correct. I've run this same code with other columns and strings and it has worked fine, but for some reason if I search this certain column for the term "GM" it fails. The only thing I can think of is maybe countif only works if the string you're searching for is the only string in a cell, because in all cases where this is true the code works fine. In this particular case the string I'm looking for is not the only string in the cell and the code is failing. I've tried to find more info on whether or not this is true but I can't find anything online. Here's the code if anyone would like to take a look:
Function OemRequest() As Long
Sheets("CS-CRM Raw Data").Select
Sheets("CS-CRM Raw Data").Unprotect
Dim oem As Long
Dim LastRow As Long
Dim LastColumn As Long
'Determines size of table in document
LastRow = Range("A" & Rows.Count).End(xlUp).row
LastColumn = Cells(1, Columns.Count).End(xlToLeft).Column
oem = Application.WorksheetFunction.CountIf(Range(2 & "2:" & 2 & LastRow), "gm")
OemRequest = oem
End Function
You are correct that the COUNTIF as written will only match cells where the whole content is "gm". The criteria in the COUNTIF function will also accept wildcards, so to match on cells that contain "gm" do:
.CountIf(Range(2 & "2:" & 2 & LastRow), "*gm*")
Update
As you noted there is also an issue with your Range call. As it is, the expression inside the parens will evaluate to "22:2<LastRow>" (where <LastRow> is the value of the LastRow variable).
The 2's in there should be a variable containing the column name you're interested in. Something like:
Dim col as String
col = "B"
... Range(col & "2:" & col & LastRow) ...
This will evaluate to "B2:B<LastRow>", which is what you want.
Another possibility:
oem = WorksheetFunction.CountIf(Columns(LastColumn).Cells(2).Resize(rowsize:=LastRow - 1), "gm")
This will count cells containing "gm" (use wilcards if needed) in the LAST column of the table, except the one in the first row. (It assumes the table upper left corner is in cell "A1")
Of course you can create a variable if you would like to count any other column:
Dim lngCol as Long
lngCol = ...
oem = WorksheetFunction.CountIf(Columns(lngCol).Cells(2).Resize(rowsize:=LastRow - 1), "gm")
I think in this way
Sub Main()
Application.ScreenUpdating = 0
Dim Count As Double
Range("C1").Activate 'Firs row in the column
Do While ActiveCell.Value <> ""
If InStr(ActiveCell.Value, "MyText") Then
Count = Count + 1
End If
ActiveCell.Offset(1, 0).Activate
Loop
Application.ScreenUpdating = 1
End Sub
This will work, only if the data cell is not empty, if there is an empty space in middle of the worksheet, do this:
Sub Main()
Application.ScreenUpdating = 0
Dim Count As Double
Range("C1").Activate
Do While ActiveCell.Row <> Rows.Count ' This wil evaluate all the rows in the 'C' Column
If InStr(ActiveCell.Value, "MyText") Then
Count = Count + 1
End If
ActiveCell.Offset(1, 0).Activate
Loop
Application.ScreenUpdating = 1
End Sub
Hope it's work for you.