VBA iteration not returning value - excel

I am writing a very simple sub process to assign a letter grade to numeric grade values. I have a loop and I am trying to set the cell value to the output of my function. This seems like a very simple task but the first two iterations of my loops are not assigning any values. My Loop only goes through 4 rows.
Function get_letter(grade As Double)
Select Case grade
Case 0 To 59: letter = "F"
Case 60 To 69: letter = "D"
Case 70 To 79: letter = "C"
Case 80 To 89: letter = "B"
Case 90 To 100: letter = "A"
End Select
get_letter = letter
End Function
Sub assign_letter_grade()
Dim x As Integer
Dim grade As Range
Dim letter As Range
num_rows = Range("A2", Range("A2").End(xlDown)).Rows.Count
Set grade = Range("J2")
Set letter = Range("K2")
For x = 1 To num_rows
letter.Value = get_letter(grade.Value)
Set grade = grade.Offset(1, 0)
Set letter = letter.Offset(1, 0)
Next
End Sub
Why don't this set the values of my first two rows in the loop?

Try this:
Function get_letter(grade As Double) As String
if grade < 60 Then
get_letter = "F"
Elseif grade < 70 Then
get_letter = "D"
Elseif grade < 80 Then
get_letter = "C"
Elseif grade < 90 Then
get_letter = "B"
Else
get_letter = "A"
End If
End Function

The problem comes from the scores that is NOT included in the range. For VBA a score of 79.25 does not falls in either Case C or B. You could try to see if below would fix the problem:
Function get_letter(grade As Double)
Select Case grade
Case 0 To 59.99: letter = "F"
Case 60 To 69.99: letter = "D"
Case 70 To 79.99: letter = "C"
Case 80 To 89.99: letter = "B"
Case 90 To 100.99: letter = "A" ' assuming student can get a score over 100
End Select
get_letter = letter
End Function

Or using INDEX/MATCH
Function get_letter(grade As Double) As String
get_letter = Evaluate("INDEX({""F"",""D"",""C"",""B"",""A""},MATCH(" & grade & ",{0,60,70,80,90,100}))")
End Function
sample
Sub b()
Debug.Print get_letter(59.99)
Debug.Print get_letter(60)
End Sub

Because you start at row 2:
Set grade = Range("J2")
Set letter = Range("K2")
and then immediately offset by 1 row in your loop:
Set grade = grade.Offset(1, 0)
Set letter = letter.Offset(1, 0)
therefore missing rows 1 to 2 out and starting at Row 3. Use the x variable in your loop to correct the problem:
For x = 1 To num_rows
letter.Value = get_letter(grade.Value)
Set grade = Range("J" & x)
Set letter = Range("K" & x)
Next

Related

Excel - VBA code works only for a few tries

I created this code in VBA so that every time I delete a number or the cell is empty(D7:O36), this code will run automatically(on selection change).
The code runs fine if a certain small amount of cells(~100) gets empty at once, then the cells will get filled with a "-".
The problem is that after doing it all at once more that around 100 times(each cell), Excel will stop working with error of run time error 1004. I've read about the error but it doesn't look like it applies here, at least not to the naked eye.
I don't know if the problem is how I implemented it or that i'm doing something too heavy for excel to handle.
UPDATE:
Thanks to - Tim Williams - comment bellow, the issue was not only fixed(for some reason it worked) but the code got super small and simple, AND it runs faster AND each time the "-" is added, Excel doesn't pull you to the active cell(you can activate other cell meanwhile the code is running)
Comment:
Maybe simpler: Dim c As Range: For Each c In Me.Range("D7:O36").Cells: If Len(c.Value)=0 Then c.Formula = "=""-""": Next c –
Tim Williams
Here is the updated code:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim c As Range
For Each c In Me.Range("D7:O36").Cells
If c.Value = "" Then
c.Formula = "-":
End If
Next c
End Sub
Original code:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim num As Integer
Dim letter As String
Dim count As Integer
Dim cellvalue As String
Dim cellnum As String
letter = "D"
num = 7
count = 0
For i = 0 To 432
cellnum = letter & num
cellvalue = Range(cellnum).Value
If cellvalue = "" Then
Range(cellnum).Select
ActiveCell.FormulaR1C1 = "-"
End If
If num = 36 Then
If count = 0 Then
letter = "E"
ElseIf count = 1 Then
letter = "F"
ElseIf count = 2 Then
letter = "G"
ElseIf count = 3 Then
letter = "H"
ElseIf count = 4 Then
letter = "I"
ElseIf count = 5 Then
letter = "J"
ElseIf count = 6 Then
letter = "K"
ElseIf count = 7 Then
letter = "L"
ElseIf count = 8 Then
letter = "M"
ElseIf count = 9 Then
letter = "N"
ElseIf count = 10 Then
letter = "O"
End If
num = 6
count = count + 1
End If
num = num + 1
Next i
End Sub

Replace number with corresponding letter

I'm trying to format some number data. For each cell within the range I need to replace the first number only with the corresponding letter. 1 = A, 2 = B etc. and then delete the 2nd and 3rd numbers.
So for example:
11111 --> A11
12345 --> A45
23456 --> B56
56789 --> E89
Is there a simple way to do that with formatting? I only need to go up to E.
Here's a little VBA code to accomplish what you need:
s = "56789"
s = Chr(Asc(Mid(s, 1, 1)) + 16) & Mid(s, 4)
My suggestion would be
Option Explicit
Function conA_E(inp As String) As String
Dim res As String
Dim ch As String
On Error GoTo EH
ch = Left(inp, 1)
If ch <= 6 And ch >= 1 Then
res = Chr(Asc(Mid(inp, 1, 1)) + 16) & Mid(inp, 4)
Else
'res = ch & Mid(inp, 4) ' In Case 2nd and 3rd digit should always be deleted
res = inp ' No change if first digit is bigger than 5
End If
conA_E = res
Exit Function
EH:
conA_E = inp
End Function
Sub TestIt()
Dim inp As String
inp = "1214222"
Debug.Print conA_E(inp)
End Sub

Using "if then" statements inside a "for" loop in Microsoft VBA

I am trying to pull numerical grade values and convert them into a string which will represent the grade, A >90, B >80, and so on. I planned to use a for loop to gather the values from the spreadsheet and then if then statements to assign the letter designation. Here is the code I have so far.
For i = 1 To 11
' Pull Grade numerical value
Grade = Cells(2 + i, 16).Value
' Assign Numerical Value a script Grade
If Grade > 60 Then
Letter = "D"
If Grade > 70 Then
Letter = "C"
If Grade > 80 Then
Letter = "B"
If Grade > 90 Then
Letter = "A"
Else
Letter = "F"
Exit
' Print the letter grade
Cells(2 + i, 17).Text = Letter
Next i
I keep getting errors either pertaining to "Exit" or to "Next i". I have tried using "End" statements as well but that didn't fix the problems either.
Or just use a formula in column Q:
=IF(P:P>=90,"A",IF(P:P>=80,"B",IF(P:P>=70,"C",IF(P:P>=60,"D","F"))))
so it updates automatically and you don't need to use VBA.
Alternatively you can add a sheet named GradeList with the following data
and use
=INDEX(GradeList!B:B,MATCH(P:P,GradeList!A:A,1))
as formula. This way you can easily edit the numbers/grades later, and it will pick the correct grades automatically:
I would prefer Select Case in this situation. And start the Loop from 3 so you don't need to add 2:
For i = 3 To 11
' Pull Grade numerical value
Grade = Cells(i, 16).Value
' Assign Numerical Value a script Grade
Select Case Grade
Case Is >= 90: letter = "A"
Case Is >= 80: letter = "B"
Case Is >= 70: letter = "C"
Case Is >= 60: letter = "D"
Case Else: letter = "F"
End Select
' Print the letter grade
Cells(i, 17).Value = letter
Next i
Great answers from everyone, but surprised not to see a Select...Case mentioned here. This is a perfect example, perhaps even textbook.
Select...Case evaluates a variable according to a number of criteria and then does whatever is written inside the case statement.
So your code would look like this:
For i = 3 To 13
' Pull Grade numerical value
Grade = Worksheets("YourSheetNameHere").Cells(i, 16).Value 'Change that sheet name
'do our evaluation based on "grade"
Select Case Grade
Case >= 90
Letter = "A"
Case >= 80
Letter = "B"
Case >= 70
Letter = "C"
Case >= 60
Letter = "D"
Case Else
Letter = "F"
End Select
Next i
You have a bunch of problems here.
First exit needs to be end if
Second You need elseif for multiple conditions in the same if statement.
Lastly you need to reorder your ifs. If you have a grade of 90 it would return a "D" as that is the first true statement it encounters.
You might as well just loop from 3 - 13 instead of adding 2 each time aswell. And make sure you use explicit references, it will eventually bite you.
I missed one thing, Make all your comparisons >= Folks won't be happy if their 90 is a B.
For i = 3 To 13
' Pull Grade numerical value
Grade = Worksheets("YourSheetNameHere").Cells(i, 16).Value 'Change that sheet name
' Assign Numerical Value a script Grade
If Grade >= 90 Then
Letter = "A"
elseIf Grade >= 80 Then
Letter = "B"
elseIf Grade >= 70 Then
Letter = "C"
elseIf Grade >= 60 Then
Letter = "D"
Else
Letter = "F"
End if
' Print the letter grade
Worksheets("YourSheetNameHere").Cells(i, 17).value = Letter 'Change that sheet name
Next i
Alternatively, use INDEX and MATCH as Application.Function:
Sub Test()
Dim Grade As Long: Grade = 55
Dim arr1 As Variant: arr1 = Array("A", "B", "C", "D", "F")
Dim arr2 As Variant: arr2 = Array(100, 89, 79, 69, 59)
With Application
Debug.Print .Index(arr1, .Match(Grade, arr2, -1))
End With
End Sub
Or drop the INDEX function, and call the array directly:
With Application
Debug.Print arr1(.Match(Grade, arr2, -1) - 1)
End With
Obviously implement this in your loop/function and write the returned value to your cells. Change Grade variable to see the different values this would return =)
I changed the format of the code to match Jclasley and it works fine, here is my final code:
For i = 1 To 11
' Pull Grade numerical value
Grade = Cells(2 + i, 16).Value
' Assign Numerical Value a script Grade
Select Case Grade
Case Is >= 90
Letter = "A"
Case Is >= 80
Letter = "B"
Case Is >= 70
Letter = "C"
Case Is >= 60
Letter = "D"
Case Else
Letter = "F"
End Select
Cells(2 + i, 17).Value = Letter
Next i
End Sub

How to define variable in range

I just started VBA coding and I am struck here:
For one cell this program works:
Dim score As Integer, result As String
score = Range("A1").Value
If score >= 60 Then
result = "pass"
Else
result = "fail"
End If
Range("B1").Value = result
And how about a column of cells? Can loop works for this?
My code using loop - But How to define variable in range?
Dim score As Integer, result As String, I As Integer
score = Range("AI").Value
For I = 1 To 6
If score >= 60 Then
result = "pass"
Else
result = "fail"
End If
Range("BI").Value = result
Next I
Thanks in advance!
Almost, you just need to use string concatenation (&)
Dim score As Integer, result As String, I As Integer
'score = Range("AI").Value
For I = 1 To 6
score = Range("A" & I).Value '// Needs to be inside the loop to update.
If score >= 60 Then
result = "pass"
Else
result = "fail"
End If
Range("B" & I).Value = result
Next I
This can also be written as:
For i = 1 To 6
Range("B" & i).Value = IIf(Range("A" & i).Value >= 60, "pass", "fail")
Next
you can also go with a "formula" approach:
Range("B1:B6").FormulaR1C1 = "=If(RC1 >= 60, ""pass"", ""fail"")"
thus maintaining that check active for any possible subsequent change in columns A cells values
or, should you want to have "static" values only:
With Range("B1:B100")
.FormulaR1C1 = "=If(RC1 >= 60, ""pass"", ""fail"")"
.Value = .Value
End With

UDF: Handling ranges and variable numbers of arguments

I'm trying to write a UDF (user-defined function) to create an average for non-numeric data (I'm converting it into numeric form then back again at the end). I can get the UDF to work if I list individual cells; I get a #VALUE! error if I try to refer to a range of cells. There may be a mix of both ranges and individual cells to process.
Any ideas?
The code so far is below.
Function avlvl(ParamArray av() As Variant)
Dim a As Integer
'creates an average ks3 level from data in format "5a"
a = 0
n = 0
total = 0
Do While a < UBound(av()) + 1
'ignore blank or zero cells
If av(a) = 0 Or av(a) = "" Then
a = a + 1
Else
'convert data into numeric value - split into level and sub level
level = Val(Left(av(a), 1))
sl = Right(av(a), 1)
If sl = "c" Then
sublevel = 0
ElseIf sl = "C" Then
sublevel = 0
ElseIf sl = "b" Or sl = "B" Then
sublevel = 1 / 3
ElseIf sl = "a" Or sl = "A" Then
sublevel = 2 / 3
Else
sublevel = 0
End If
'score is numeric value of the data
score = level + sublevel
'total is teh toatl of the cells so far
total = total + score
a = a + 1
n = n + 1
End If
Loop
ave = total / n
'reconvert into format level and sublevel (a,b,c)
averagelevel = Application.WorksheetFunction.RoundDown(ave, 0)
asl = ave - averagelevel
If asl < 0.17 Then
averagesublevel = "c"
ElseIf asl < 0.5 Then
averagesublevel = "b"
ElseIf asl < 0.84 Then
averagesublevel = "a"
ElseIf asl < 1 Then
averagelevel = averagelevel + 1
averagesublevel = "c"
Else
averagesublevel = "c"
End If
avlvl = averagelevel & averagesublevel
End Function
What's going on is that the range is coming in as a single object of type Range, and your code is trying to treat is as though it is coming in as an array.
The best approach would be to create a new array within the body of the function, and then assign the elements in the range to the new array. You need to test for the type of the elements of the ParamArray. If an element is type String, then put it directly in the new array; if an element is type Range, loop through it, assigning its cell values to the new array.
Then you would do your processing on the new array.
The following code provides the machinery to pass in ranges as well as individual cells or values. I've not included your code but have indicated where it would go.
Function avlvl(ParamArray av() As Variant) As Variant
Dim a As Integer
Dim i As Long
Dim avArr()
Dim element As Variant
a = 0
i = 0
Do While a < UBound(av) + 1
If TypeName(av(a)) = "String" Then
avArr(i) = av(a)
i = i + 1
ElseIf TypeName(av(a)) = "Range" Then
For Each element In av(a)
ReDim Preserve avArr(0 To i)
avArr(i) = element
i = i + 1
Next
Else
avlvl = CVErr(xlErrValue)
Exit Function
End If
a = a + 1
Loop
i = 0
Do While i < UBound(avArr) + 1
'...
'now process the elements of avArr()
'...
i = i + 1
Loop
End Function
If you have a disjoint range of cells and you want to pass them to a UDF, one approach is to create a Defined Name and pass it to the UDF as a single argument.

Resources