Sub WtdRtg()
Const MyName As String = "WtdRtg" 'The name of this macro for error messages
' Some constants
Const WtdRtgCol As Long = 2 'The weighted rating column
Const MinRows As Long = 1 'Table must have at least 1 row
Const MinCols As Long = 3 'Table must have at least 3 columns (name, wtdrtg, 1 attribute)
Const HlprHdrName As String = "Headers" 'The label for the Headers row
Const HlprTypName As String = "Types" 'The label for the Types row
Const HlprOrdName As String = "Orders" 'The label for the Orders row
Const HlprWtName As String = "Weights" 'The label for the Weights row
Const ValidTypes As String = "|NUM|" 'List of valid types
Const ValidOrds As String = "|HILO|LOHI|" 'List of valid orders
' Named ranges
Const rnTableName As String = "TableName" 'The name of the table
Const rnHlprHdrs As String = "HelperHeaders" 'The Headers helper row label
Const rnHlprTyps As String = "HelperTypes" 'The Types helper row label
Const rnHlprOrds As String = "HelperOrders" 'The Orders helper row label
Const rnHlprWts As String = "HelperWeights" 'The Weights helper row label
' Worker variables
Dim iCol As Long 'Loop index
Dim iRow As Long 'Loop index
Dim sTmp As String 'Temporary string variable
' Get the name of the table
Dim rnTable As String 'The name of the table
rnTable = Range(rnTableName).Value2 '.Load the table name
' Define a ListObject variable. It will replace ActiveSheet.ListObject in the rest of the code
Dim loTable As ListObject
Set loTable = Range(rnTable).ListObject
' Load the headers into one array and the body into another
Dim arrTableHdrs As Variant 'The table header row
arrTableHdrs = loTable.HeaderRowRange.Value2
Dim arrTableData As Variant 'The table data (body)
arrTableData = loTable.DataBodyRange.Value2
' Do some validity checking
Dim NumRows As Long 'Get the number of rows
NumRows = UBound(arrTableData, 1)
If NumRows < MinRows Then
Call ErrMsg("Table has less than " & MinRows & " rows", MyName)
Exit Sub: End If
Dim NumCols As Long 'Get the number of columns
NumCols = UBound(arrTableData, 2)
If NumCols < MinCols Then
Call ErrMsg("Table has less than " & MinCols & " columns", MyName)
Exit Sub: End If
' Check the helper row labels
sTmp = Range(rnHlprHdrs).Value2 'Check the Header helper label
If sTmp <> HlprHdrName Then
Call ErrMsg("Invalid Header helper row label (" & sTmp & ")" _
& vbCrLf & "Expected (" & HlprHdrName & ")", MyName)
Exit Sub: End If
sTmp = Range(rnHlprTyps).Value2 'Check the Type helper label
If sTmp <> HlprTypName Then
Call ErrMsg("Invalid Type helper row label (" & sTmp & ")" _
& vbCrLf & "Expected (" & HlprTypName & ")", MyName)
Exit Sub: End If
sTmp = Range(rnHlprOrds).Value2 'Check the Order helper label
If sTmp <> HlprOrdName Then
Call ErrMsg("Invalid Order helper row label (" & sTmp & ")" _
& vbCrLf & "Expected (" & HlprOrdName & ")", MyName)
Exit Sub: End If
sTmp = Range(rnHlprWts).Value2 'Check the Weight helper label
If sTmp <> HlprWtName Then
Call ErrMsg("Invalid Weight helper row label (" & sTmp & ")" _
& vbCrLf & "Expected (" & HlprWtName & ")", MyName)
Exit Sub: End If
' Load the helper rows
Dim arrHlprHdrs As Variant 'Load the helper headers
sTmp = Range(rnHlprHdrs).Offset(0, 1).Address & ":" _
& Range(rnHlprHdrs).Offset(0, NumCols - 2).Address
arrHlprHdrs = Range(sTmp).Value2
Dim arrHlprTyps As Variant 'Load the helper types
sTmp = Range(rnHlprTyps).Offset(0, 1).Address & ":" _
& Range(rnHlprTyps).Offset(0, NumCols - 2).Address
arrHlprTyps = Range(sTmp).Value2
Dim arrHlprOrds As Variant 'Load the helper orders
sTmp = Range(rnHlprOrds).Offset(0, 1).Address & ":" _
& Range(rnHlprOrds).Offset(0, NumCols - 2).Address
arrHlprOrds = Range(sTmp).Value2
Dim arrHlprWts As Variant 'Load the helper weights
sTmp = Range(rnHlprWts).Offset(0, 1).Address & ":" _
& Range(rnHlprWts).Offset(0, NumCols - 2).Address
arrHlprWts = Range(sTmp).Value2
' Validity check the individual helper rows
For iCol = 1 To NumCols - 2
' Do the headers match the table headers?
If UCase(arrHlprHdrs(1, iCol)) <> UCase(arrTableHdrs(1, iCol + 2)) Then
Call ErrMsg("Header helper #" & iCol & " (" & arrHlprHdrs(1, iCol) & ")" _
& " <> Table header", MyName)
Exit Sub: End If
' Are the types in the list
If 0 = InStr(1, ValidTypes, (UCase(arrHlprTyps(1, iCol)))) _
Or IsEmpty(arrHlprTyps(1, iCol)) Then
Call ErrMsg("Invalid Type helper #" & iCol & " (" & arrHlprTyps(1, iCol) & ")", MyName)
Exit Sub: End If
' Are the orders in the list
If 0 = InStr(1, ValidOrds, (UCase(arrHlprOrds(1, iCol)))) _
Or IsEmpty(arrHlprOrds(1, iCol)) Then
Call ErrMsg("Invalid Order helper #" & iCol & " (" & arrHlprOrds(1, iCol) & ")", MyName)
Exit Sub: End If
' Are the weights all numbers?
If Not IsNumeric(arrHlprWts(1, iCol)) Or IsEmpty(arrHlprWts(1, iCol)) Then
Call ErrMsg("Weight helper #" & iCol & " (" & arrHlprWts(1, iCol) & ") is not numeric", MyName)
Exit Sub: End If
Next iCol
' Calculate the means and standard deviations for the property columns
Dim Mean As Double 'The mean
Dim StdDev As Double 'The std dev
Dim Z As Double 'Next Z Score
For iRow = 1 To NumRows 'Zero the WtdRtgs
arrTableData(iRow, WtdRtgCol) = 0
Next iRow
' Now, finally, do the work
For iCol = MinCols To NumCols 'Loop through the property columns
With Application.WorksheetFunction
Mean = .Average(.Index(arrTableData, 0, iCol)) 'Calculate the mean
StdDev = .StDev_S(.Index(arrTableData, 0, iCol)) '.and the std ev
End With
For iRow = 1 To NumRows 'Calculate the Z Score for each row in this column
Z = ZScore(arrTableData(iRow, iCol), Mean, StdDev, arrHlprOrds(1, iCol - 2))
arrTableData(iRow, WtdRtgCol) = arrTableData(iRow, WtdRtgCol) _
+ (Z * arrHlprWts(1, iCol - 2)) 'Add the weighted Z Score
Next iRow
Next iCol
' Write out just weighted ratings column (2) of the array
loTable.ListColumns(arrTableHdrs(1, WtdRtgCol)).DataBodyRange.Resize(UBound(arrTableData, 1)) _
= Application.Index(arrTableData, 0, WtdRtgCol)
End Sub