0

I have a need to run successive passes of built in excel functions on a single matrix of input.

The problem is, the input [range] is what I assume, a pointer constant.

So sure, I can do a WorkSheetFunction calculations on the [range] input and place the output into a variant.

But, I do have a need to run more passes on the variant data. I have a more advanced calculation that is going to run 4 transforms on data that use standard excel functions like average, and median.

Here's my code

Public Function RankECDF(ByRef r_values As Range, Optional ByVal zeroFlag As Boolean = 0) As Variant()

Dim i As Integer, j As Integer, N As Integer, M As Integer

Dim total As Integer

Dim y() As Variant

N = r_values.Rows.Count
M = r_values.Columns.Count

y = r_values.Value    'copy values from sheet into an array

Dim V() As Variant
Dim AltV As Variant

Dim OutV As Variant
Dim OutAltV As Variant

'quite possible to makes the Variant larger to hold the "other arrays"

ReDim V(1 To N, 1 To M)
ReDim AltV(1 To N, 1 To M)

ReDim OutV(1 To N, 1 To M)
ReDim OutAltV(1 To N, 1 To M)

'first pass just checks for zero's.  Could speed this process up by implementing the zeroFlag check to skip the double loop

total = WorksheetFunction.Sum(r_values)
For R = 1 To N
    For C = 1 To M
        If y(R, C) = "" Then
            V(R, C) = ""
            AltV(R, C) = 0
        Else
            'would error if cell was ""
            'V(R, C) = WorksheetFunction.Average(WorksheetFunction.Rank(y(R, C), r_values, 1), WorksheetFunction.CountIf(r_values, "<=" & y(R, C))) / WorksheetFunction.Count(r_values)
            V(R, C) = y(R, C)
            AltV(R, C) = y(R, C)
        End If
    Next C
Next R

'second loop does rankecdf conversions
For RA = 1 To N
    For CA = 1 To M
       'OutV(RA, CA) = 1
       'OutV(RA, CA) = WorksheetFunction.Rank(V(RA, CA), V, 1)

       'OutAltV(RA, CA) = 2
       'OutAltV(RA, CA) = WorksheetFunction.Average(WorksheetFunction.Rank(y(RA, CA), r_values, 1), WorksheetFunction.CountIf(r_values, "<=" & y(RA, CA))) / WorksheetFunction.Count(r_values)
    Next CA
Next RA

If (zeroFlag) Then
    RankECDF = AltV
    'RankECDF = OutAltV(1 to N, 1 to M)
Else
    RankECDF = V
    'RankECDF = OutV(N, M)
End If

End Function

The problem can be identified right around here:

OutV(RA, CA) = WorksheetFunction.Rank(V(RA, CA), V, 1)
Prisoner
  • 1,839
  • 2
  • 22
  • 38
thistleknot
  • 1,098
  • 16
  • 38
  • One idea I had was to create a Dim of Range type and see if I can copy VALUES in. However, I got stuck when trying to set the Range to the same size as the original without just being a pointer copy. Another idea I had was if I could run similar functions on variants using an built in object function, but I didn't see any functions when I typed "name of variant".Sum() for example. – thistleknot Jul 22 '14 at 00:56

2 Answers2

1
WorksheetFunction.Rank(y(R, C), r_values, 1)

You cannot put an Array on arg1. Just do:

i = y(R, C)

Then:

WorksheetFunction.Rank(i, r_values, 1)

It worked fine for me

RamenChef
  • 5,557
  • 11
  • 31
  • 43
Jack
  • 11
  • 1
0

Updated from comments as I see the answer I initially posited misread the problem:

As a general rule, arrays and performing calculations purely in memory are faster than you might think. For one example I used to use the Application.Match function to find the index position of a value in an array, rather than simple brute force iteration. Turns out that iteration was a far faster (up to 10x faster!!!) method. Check out Tim's answer to my question about Matching values in a string array.

I suspect it is the same with rank/sorting. Worksheet functions are expensive. For/Next is not, relatively speaking.

As for the specific needs to rank from an array, there are examples of custom functions which rank and sort arrays, collections, dictionaries, etc. I ultimately end up using a bunch of Chip Pearson's Array helper functions, he has a number of them; which do really cool sh!t like reversing an array, sorting array, determining whether an array is allocated (I use this one a lot) or empty, or all numeric, etc. There are about 30 of them.

here is the code to sort an array.

Note: I did not post his code because there is a lot of it. WHile it appears daunting, because it is a lot of code to re-invent the wheel, it does work and saves a lot of trouble and is very useful. I don't even use these in Excel, since I do most of my dev in PowerPoint now -- I think all of these modules ported over with zero or almost zero debugging on my end. They're really buttoned up quite nicely.

Getting the rank

Once the array is "sorted" then determining the rank of any value within it is trivial and only requires some tweaking since you may want to handle ties appropriately. One common way of dealing with ties is to "skip" the next value, so if there is a two-way tie for 2nd place, the rank would go {1, 2, 2, 4, 5, 6, etc.}

Function GetRank(arr As Variant, val As Variant)
'Assumes arr is already sorted ascending and is a one-dimensional array
Dim rank As Long, i As Long
Dim dictRank As Object
Set dictRank = CreateObject("Scripting.Dictionary")
rank = 0
For i = LBound(arr) To UBound(arr)
    rank = rank + 1
    If dictRank.Exists(arr(i)) Then
        'Do nothing, handles ties

    Else
        'store the Key as your value, and the Value as the rank position:
        dictRank(arr(i)) = rank
    End If
    If arr(i) = val Then Exit For
Next

GetRank = rank
End Function
Community
  • 1
  • 1
David Zemens
  • 53,033
  • 11
  • 81
  • 130
  • I don't want the rank of v(ra, ca), r_values, 1), but rather the .rank(v(ra, ca), v, 1) – thistleknot Jul 22 '14 at 01:57
  • ahhh crap. I misread your original code, I thought that `v = r_values.Value`... let me re-think this then. – David Zemens Jul 22 '14 at 02:15
  • this guy is having a similar issue: Paul_Hossler here http://www.vbaexpress.com/forum/archive/index.php/t-22867.html idea was "so I guess your original idea about running the array into a range on a worksheet is the only want to use CountIf" – thistleknot Jul 22 '14 at 02:19
  • What dimensions are the range? Are they single row/column or is it an array? Also, I'm looking at your code and `V` and `AltV` both look to be essentially the same as `y`, which **is** `r_values.Value`. Can you explain why you have to use `v` instead of `r_values` since they appear to hold the same data? – David Zemens Jul 22 '14 at 02:20
  • I think if I copy the range in by value vs byref then I can do multiple runs on the same range? The difference is I replace "" with 0's for one variant vs keeping the "", which has an affect on followup calculations. Another idea I had was to make a new Range object based on the RankECDF I return at the end. – thistleknot Jul 22 '14 at 02:23
  • Are you only trying to do a `Rank` calculation once (it's difficult to tell, you have several lines commented out which appear to invoke the `Rank` function)? If only once, then one idea might be to modify the range, then use the `AutoFilter.Sort` method to get the sorted/ranked data. Then you can turn off the autofilter.sort . – David Zemens Jul 22 '14 at 02:36
  • I think we're both overthinking this :) I made an update above which hopefully is helpful for you. – David Zemens Jul 22 '14 at 03:12
  • thank you, I found the issue is with the way I wanted to work with ranges... I was hoping to avoid having to recode excel functions...http://stackoverflow.com/questions/3235107/how-to-convert-a-variant-array-to-a-range – thistleknot Jul 22 '14 at 04:21
  • Yes, that's one of the links I suggested. Once you sort the array it should be pretty easy to derive the rank. – David Zemens Jul 22 '14 at 05:47
  • but I want to export to a worksheet so I can use excel's built in functions! – thistleknot Jul 22 '14 at 11:28
  • I don't understand your last comment; you can still "export" (by which I think you mean to put the array data back to a worksheet) to a worksheet and use built-in functions. Absolutely nothing is preventing you from doing that. What is the problem? – David Zemens Jul 22 '14 at 12:24
  • This question is closed and no longer relevant to what it is I am wanting to do. the question now is, can I export a user selected 2d range that is read into a variant then exported to a temporary worksheet that is reread as a new range for me to work with in vba. – thistleknot Jul 22 '14 at 15:22
  • 'by which I think you mean to put the array data back to a worksheet) to a worksheet and use built-in functions. Absolutely nothing is preventing you from doing that. What is the problem? ' The problem is I'm not done with the data. I want to run successive passes of excel functions, aka loops. I want to do like 3 or 4 loops on a dataset. I'm transforming one dataset, then running a new calculation on it, before I'm ready to export a final range to my worksheet.The only way to do excel functions on any of those loops is if the data I want to run the calculations on is inside a range/worksheet – thistleknot Jul 22 '14 at 15:23
  • So then create a new worksheet, put the data there, do the worksheet function, and then transfer the range value back to the array in memory... I think it's going to be noticeably slower this way, but if that's not a concern for you, that would be the answer. – David Zemens Jul 22 '14 at 15:37