Rank in Vba with criteria

kelly mort

Well-known Member
Joined
Apr 10, 2017
Messages
2,169
Office Version
  1. 2016
Platform
  1. Windows
I have this table layout

Section Score Rank
A 34 2
B 45 1
C 23 1
A 46 1




Now I am looking for a vba code to rank and show the out as I have under the rank column based on the section. I can do this with a formula but I need the vba code for it to speed up things small I think.


Thanks in advance
Kelly
 

Excel Facts

Save Often
If you start asking yourself if now is a good time to save your Excel workbook, the answer is Yes
The following macro assumes that...

1) the active sheet contains the data to be ranked,

2) the data starts at cell A1,

3) Column A contains the section,

4) Column B contains the score, and

5) the rank is to be placed in Column C.

Code:
Option Explicit

Sub RankScoresBySection()


    Dim dicSection As Object
    Dim vItem As Variant
    Dim vSection As Variant
    Dim rScore As Range
    Dim rCell As Range
    Dim LastRow As Long
    Dim i As Long
    
    Application.ScreenUpdating = False
    
    With ActiveSheet
        If .FilterMode Then .ShowAllData
    End With
    
    LastRow = Cells(Rows.Count, "A").End(xlUp).Row
    If LastRow > 1 Then
        'Data exists
    Else
        MsgBox "No data exists!", vbExclamation
        Exit Sub
    End If
    
    Set dicSection = CreateObject("Scripting.Dictionary")
    dicSection.CompareMode = 1 'vbTextCompare
    
    vSection = Range("A1:A" & LastRow).Value

    For i = LBound(vSection) + 1 To UBound(vSection)
        If Not dicSection.Exists(vSection(i, 1)) Then
            dicSection(vSection(i, 1)) = ""
        End If
    Next i
    
    For Each vItem In dicSection.keys()
        With ActiveSheet.UsedRange
            .AutoFilter field:=1, Criteria1:=vItem
            Set rScore = .Offset(1, 1).Resize(.Rows.Count - 1, 1).SpecialCells(xlCellTypeVisible)
            For Each rCell In rScore
                Cells(rCell.Row, "C").Value = WorksheetFunction.Rank(rCell.Value, rScore)
            Next rCell
            .AutoFilter
        End With
    Next vItem
    
    Application.ScreenUpdating = True
    
    Set dicSection = Nothing
    Set rScore = Nothing
    Set rCell = Nothing
    
End Sub

Hope this helps!
 
Last edited:
Upvote 0
Great! !!!

It is more than "helps"

Thanks a lot.


I have some questions:

1. Which line is pointing to column B?
2. Is there a way to attach "st" , "nd" etc to the ranks from this code?
 
Upvote 0
1. Which line is pointing to column B?

The following line defines the range for Column B and assigns it to rScore...

Code:
[COLOR=#574123]Set rScore = .Offset(1, 1).Resize(.Rows.Count - 1, 1).SpecialCells(xlCellTypeVisible)[/COLOR]

2. Is there a way to attach "st" , "nd" etc to the ranks from this code?

I've amended the macro accordingly. Note that I've added the function GetOrdinalSuffixForRank to retrieve the appropriate suffix for the rank. Also, I've included code to handle any potential errors.

Code:
Option Explicit

Sub RankScoresBySection()


    Dim dicSection As Object
    Dim vItem As Variant
    Dim wsData As Worksheet
    Dim vSection As Variant
    Dim rScore As Range
    Dim rCell As Range
    Dim Score As Variant
    Dim Rnk As Double
    Dim LastRow As Long
    Dim i As Long
    
    Application.ScreenUpdating = False
    
    Set wsData = ActiveSheet
    
    With wsData
        If .FilterMode Then .ShowAllData
        LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
    End With
    
    If LastRow > 1 Then
        'Data exists
    Else
        MsgBox "No data exists!", vbExclamation
        Exit Sub
    End If
    
    On Error GoTo ErrHandler
    
    Set dicSection = CreateObject("Scripting.Dictionary")
    dicSection.CompareMode = 1 'vbTextCompare
    
    vSection = wsData.Range("A1:A" & LastRow).Value
    
    For i = LBound(vSection) + 1 To UBound(vSection)
        If Not dicSection.Exists(vSection(i, 1)) Then
            dicSection(vSection(i, 1)) = ""
        End If
    Next i
    
    For Each vItem In dicSection.keys()
        With wsData.UsedRange
            .AutoFilter field:=1, Criteria1:=vItem
            Set rScore = .Offset(1, 1).Resize(.Rows.Count - 1, 1).SpecialCells(xlCellTypeVisible)
            For Each rCell In rScore
                Score = rCell.Value
                If Application.IsNumber(Score) Then
                    Rnk = WorksheetFunction.Rank(CDbl(Score), rScore)
                    rCell.Offset(, 1).Value = Rnk & GetOrdinalSuffixForRank(Rnk)
                End If
            Next rCell
            .AutoFilter
        End With
    Next vItem
    
ErrHandler:
    If Err <> 0 Then
        wsData.AutoFilterMode = False
        MsgBox "Error " & Err.Number & ":" & vbCrLf & vbCrLf & Err.Description, vbCritical, "Error"
    End If
    
    Application.ScreenUpdating = True
    
    Set dicSection = Nothing
    Set rScore = Nothing
    Set rCell = Nothing
    
End Sub


Function GetOrdinalSuffixForRank(Rnk As Double) As String

    Dim sSuffix As String
    
    If Rnk Mod 100 >= 11 And Rnk Mod 100 <= 20 Then
        sSuffix = "th"
    Else
        Select Case (Rnk Mod 10)
            Case 1
                sSuffix = "st"
            Case 2
                sSuffix = "nd"
            Case 3
                sSuffix = "rd"
            Case Else
                sSuffix = "th"
        End Select
        
    End If
    
    GetOrdinalSuffixForRank = sSuffix
    
End Function
 
Upvote 0
I really love this script! !!!


In the process of adjusting to suit what I am doing, I ran into a problem:

Say I have like

Section Score 1 Score 2 Score 3

As my column headers, can I rank them separately without having to duplicate this script and call them separately?

It just got tougher for me. I tried to use some for loops but having some weird outputs.
 
Upvote 0
I'm glad to hear that you like the code. That's great. I'm assuming that you want the results as follows...

Code:
[COLOR=#574123]Section | Score1 | Score2 | Score3 | Rank1 | Rank2 | Rank3[/COLOR]

If so, try...

Code:
    For Each vItem In dicSection.keys()
        With wsData.UsedRange
            .AutoFilter field:=1, Criteria1:=vItem
            Set rScore = .Offset(1, 1).Resize(.Rows.Count - 1, 1).SpecialCells(xlCellTypeVisible)
            For i = 0 To 2
                For Each rCell In rScore.Offset(, i)
                    Score = rCell.Value
                    If Application.IsNumber(Score) Then
                        Rnk = WorksheetFunction.Rank(CDbl(Score), rScore.Offset(, i))
                        rCell.Offset(, 3).Value = Rnk & GetOrdinalSuffixForRank(Rnk)
                    End If
                Next rCell
            Next i
            .AutoFilter
        End With
    Next vItem

Hope this helps!
 
Upvote 0
Mission successful! !!

I love to learn new things each moment. That's a cool loop you have there. Faster than I thought.

Regards
Kelly
 
Upvote 0
H
I'm glad to hear that you like the code. That's great. I'm assuming that you want the results as follows...

Code:
[COLOR=#574123]Section | Score1 | Score2 | Score3 | Rank1 | Rank2 | Rank3[/COLOR]

If so, try...

Code:
    For Each vItem In dicSection.keys()
        With wsData.UsedRange
            .AutoFilter field:=1, Criteria1:=vItem
            Set rScore = .Offset(1, 1).Resize(.Rows.Count - 1, 1).SpecialCells(xlCellTypeVisible)
            For i = 0 To 2
                For Each rCell In rScore.Offset(, i)
                    Score = rCell.Value
                    If Application.IsNumber(Score) Then
                        Rnk = WorksheetFunction.Rank(CDbl(Score), rScore.Offset(, i))
                        rCell.Offset(, 3).Value = Rnk & GetOrdinalSuffixForRank(Rnk)
                    End If
                Next rCell
            Next i
            .AutoFilter
        End With
    Next vItem

Hope this helps!

Hi,
I just got into yet a new trouble:

How do I prevent the code from ranking numbers less than 1?

Thanks
 
Upvote 0
Try...

Code:
    For Each vItem In dicSection.keys()        With wsData.UsedRange
            .AutoFilter field:=1, Criteria1:=vItem
            For i = 0 To 2
                .AutoFilter field:=i + 2, Criteria1:=">0"
                Set rScore = .Offset(1, i + 1).Resize(.Rows.Count - 1, 1).SpecialCells(xlCellTypeVisible)
                For Each rCell In rScore
                    Score = rCell.Value
                    If Application.IsNumber(Score) Then
                        Rnk = WorksheetFunction.Rank(CDbl(Score), rScore)
                        rCell.Offset(, 3).Value = Rnk & GetOrdinalSuffixForRank(Rnk)
                    End If
                Next rCell
                .AutoFilter field:=i + 2
            Next i
            .AutoFilter
        End With
    Next vItem

Hope this helps!
 
Upvote 0

Forum statistics

Threads
1,224,823
Messages
6,181,181
Members
453,022
Latest member
Mohamed Magdi Tawfiq Emam

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top