VBA alternative to Application.Quotient that includes decimals - vba

I am trying to use Application.AverageIfs and I need to divide the answer by 3. I tried this way:
Range("C1:C676") = (Application.IfError(Application.AverageIfs(Sheets(modelName).Range("R:R"....
Sheets(modelName).Range("U:U"), "OGV"), "0") / 3)
and also without the brackets round the first application and the final 3 but this gives a type mismatch error.
Nesting it within Application.Quotient works but it only gives the integer part of the answer and I need the decimal as well. Is there a decimal-friendly alternative? I would prefer to continue to use the application syntax rather than putting range().formula = "=averageifs( if its possible.
Edit: after J.Fox's suggestion I have broken the formula parts into variables. The problem seems to be the criZFrom and criZTo variables which use a range in a separate sheet as criteria. The formula works fine if I replace these variables with "1" and "2" respectively. The code is now:
Set rng = Sheets(wsName).Range("C1:C676")
Set avgCol = Sheets(modelName).Range("M:M")
Set colZFrom = Sheets(modelName).Range("G:G")
Set criZFrom = Sheets(wsName).Range("A1:A676")
Set colZTo = Sheets(modelName).Range("H:H")
Set criZTo = Sheets(wsName).Range("B1:B676")
Set colTime = Sheets(modelName).Range("V:V")
Set colVType = Sheets(modelName).Range("U:U")
criVType = "OGV"
criAM = "AM"
Range("A1:A676").Formula = "=roundup(row()/26,0)"
Range("B1:B676").Formula = "=if(mod(row(),26)=0,26,mod(row(),26))"
rng = Application.AverageIfs(avgCol, colZFrom, criZFrom, colZTo, criZTo, colTime, criAM, colVType, criVType) / 3
Here is some sample data:
from sheets(modelName), this has the data that I am trying to average and most of the criteria ranges:
From sheets(wsName), this has the criteria for the problem variables and is where I want the result to appear (in column C):

Looks like you're missing a closing parenthesis after "OGV") to close out the AverageIfs function, i.e.:
Range("C1:C676") = Application.IfError(Application.AverageIfs(Sheets(modelName).Range("R:R", Sheets(modelName).Range("U:U"), "OGV")), 0) / 3
Also, not sure if .... was just for on here or in your code, but you'd want to use _ instead, as in:
Range("C1:C676") = _
Application.IfError(Application.AverageIfs(Sheets(modelName).Range("R:R", _
Sheets(modelName).Range("U:U"), "OGV")), 0) / 3
Edit: If you're still getting an error, I suggest breaking up your formula into component parts and assigning each part to a variable so you can troubleshoot exactly where the issue is, like so:
Sub test()
Dim rng As Range, col1 As Range, col2 As Range, str As String, modelName As String
modelName = "Sheet1"
Set rng = Range("C1:C676")
Set col1 = Sheets(modelName).Columns(18)
Set col2 = Sheets(modelName).Columns(21)
str = "OGV"
rng = Application.IfError(Application.AverageIfs(col1, col2, str), 0) / 3
End Sub
Can we see some sample data? It might be an issue of what order the arguments are being passed for the AverageIfs function.
Edit 2: I think I might see what the problem is. You're using the AverageIfs function with the intention of validating each line separately based on the specific criteria for each line by using a range for Arg3 and Arg5 instead of single values, which AverageIfs doesn't like. Criteria for Ifs functions will always need to be a single value instead of a range of values. Instead, I think you would need to iterate each line separately using a loop, like this:
Set avgCol = Sheets(modelName).Range("M:M")
Set colZFrom = Sheets(modelName).Range("G:G")
Set colZTo = Sheets(modelName).Range("H:H")
Set colTime = Sheets(modelName).Range("V:V")
Set colVType = Sheets(modelName).Range("U:U")
criVType = "OGV"
criAM = "AM"
Range("A1:A676").Formula = "=roundup(row()/26,0)"
Range("B1:B676").Formula = "=if(mod(row(),26)=0,26,mod(row(),26))"
Dim x as Long
Dim t as Variant
For x = 1 To 676
Set criZFrom = Sheets(wsName).Range("A" & x)
Set criZTo = Sheets(wsName).Range("B" & x)
Set Rng = Sheets(wsName).Range("C" & x)
t = Application.WorksheetFunction.AverageIfs(avgCol, colZFrom, criZFrom.Value, colZTo, criZTo.Value, colTime, criAM, colVType, criVType)
t = CDbl(t / 3)
Rng.Value = t
Next x

Related

LibreOffice Calc: Can I get the cell address from VLOOKUP?

I'm using VLOOKUP, in Calc, like this:
VLOOKUP(B11,G2:J7,4,0)
Normally when any of us uses this, we want to get the value in the cell this function finds. In this case, rather than the value, I'd like to get a string with the cell address in it instead or the row and column of that cell. For instance, if I have a double precision floating point value of 30.14 in cell J5 and that's the answer, rather than having it return 30.14, I want it to return something like "J5" or 9,4 or some other way for me to read the result in a macro.
I've tried using =ADDRESS() and =CELL("address", ) but I'm getting errors (=CELL() gives me '#REF!').
EDIT: I'm using this routine as a wrapper around VLOOKUP with a table of floating point numbers (which is why it returns a DOUBLE instead of getting the cell value as a STRING or something else). All I have to do is pass it the column I want to get the data from:
Function getLookup(valColumn as Integer) as Double
oDoc = ThisComponent
oSheet = oDoc.Sheets (workSheet)
rangeInfo = lookupTopLeft + ":" + lookupBottomRight
cellRange = oSheet.getCellRangeByName(rangeInfo)
oCell = oSheet.GetCellByPosition(dataCellColumn, dataCellRow)
searchValue = oCell.getString()
Mode = 0
svc = createUnoService( "com.sun.star.sheet.FunctionAccess" )
args = Array(searchValue, cellRange, valColumn, Mode)
getLookup = svc.callFunction("VLOOKUP", args)
End Function
Note I'm using some local variables in this. They're private, for the module only, so I don't have to change cell references in multiple places while I'm working on designing my spreadsheet. "lookupTopLeft" and "lookupBottomRight" are "G2" and "J7", the top left and bottom right cells for the data I'm working with. "dataCellColumn", and "dataCellRow" are the column and row coordinates for the source for the key I'm using in VLOOKUP.
(#JohnSUN, I think this may be modified from an answer you provided somewhere.)
I'd like to be able to do a similar wrapper routine that would return the column and row of a cell instead of the value in the cell.
One of many possible options:
Option Explicit
Const lookupTopLeft = "G2"
Const lookupBottomRight = "J7"
Const dataCellColumn = 1
Const dataCellRow = 10
Const workSheet = 0
Function getCellByLookup(valColumn As Integer) As Variant
Dim oSheet As Variant, cellRange As Variant, oCell As Variant
Dim oColumnToSearch As Variant
Dim oSearchDescriptor As Variant
Dim searchValue As String
Dim nRow As Long
oSheet = ThisComponent.getSheets().getByIndex(workSheet)
cellRange = oSheet.getCellRangeByName(lookupTopLeft + ":" + lookupBottomRight)
searchValue = oSheet.GetCellByPosition(dataCellColumn, dataCellRow).getString()
Rem If we are looking not for a value, but for a cell,
Rem then using VLOOKUP is unnecessary, a simple Find is enough
oColumnToSearch = cellRange.getCellRangeByPosition(0, 0, 0, _
cellRange.getRows().getCount()-1) ' Resize full range to one first column
Rem Set search params
oSearchDescriptor = oColumnToSearch.createSearchDescriptor()
oSearchDescriptor.setSearchString(searchValue)
oSearchDescriptor.SearchType = 1 ' Search in Values!
Rem Try to find searchValue in oColumnToSearch
oCell = oColumnToSearch.findFirst(oSearchDescriptor)
If Not IsNull(oCell) Then ' Only if the value was found
nRow = oCell.getRangeAddress().StartRow
Rem Offset oCell to valColumn
oCell = cellRange.getColumns().getByIndex(valColumn-1).GetCellByPosition(0,nRow)
getCellByLookup = Replace(oCell.AbsoluteName, "$", "")
Else ' If the value from B11 is not found - warn about it
getCellByLookup = "Not found"
EndIf
End Function

VBA Concatenate Index/Match to Return Multiple Values

The code is a user-defined function that can be used in the worksheet. The function takes three arguments:
=MatchConcat(X1,X2,X3)
X1 = The value you want to match
X2 = The column of values that you want to match against, and
X3 = The column of values that you want to return if there is a match.
It returns a comma-delimited string of the matched values.
Now, my concern is.... I was actually looking for an alternative and much better (performs faster) VBA function same as below. Currently this VBA function below is very helpful with my large set of data. Unfortunately, using this function to a thousands of rows or even hundreds take so long to load. It took 5 to 10 minutes (for a less than 1000 rows). I have already turned off the automatic formula calculation but still having the same issue.
Function MatchConcat(LookupValue, LookupRange As Range, ValueRange As Range)
Dim lookArr()
Dim valArr()
Dim i As Long
lookArr = LookupRange
valArr = ValueRange
For i = 1 To UBound(lookArr)
If Len(lookArr(i, 1)) <> 0 Then
If lookArr(i, 1) = LookupValue Then
MatchConcat = MatchConcat & ", " & valArr(i, 1)
End If
End If
Next
MatchConcat = Mid(MatchConcat, 3, Len(MatchConcat) - 1)
End Function

Unable to get gamma_inv property of the worksheetfunction class error

My code so far, first part, where I produce random numbers to the specific range:
Dim i As Long
Randomize
For i = 1 To 20000
Range("A" & i) = Rnd()
Next i
Range("A1") = ("Rand")
ActiveCell.Range("A1:A20000").Select
Sheets("Sheet1").Columns("A").Copy
Sheets("Sheet1").Columns("B").PasteSpecial xlPasteValues
Sheets("Sheet1").Columns("A").Delete
Range("A1") = ("Rand")
MsgBox ("Nagenerovaných 20 000 hodnôt")
In the second part I am trying to get Gamma.Inv:
Dim alfa As Integer
Dim beta As Integer
Dim a As Long
Range("I2").Value = InputBox("zadaj parameter alfa")
Range("J2").Value = InputBox("zadaj parameter beta")
Range("B2").Select
Range("I2").Value = alfa
Range("J2").Value = beta
For a = 1 To 20000
Range("B" & a) = WorksheetFunction.Gamma_Inv(Rnd(), alfa, beta)
Next a
The first part of the code works fine, but it takes a while to make these random numbers. Is there a more efficient way to do this?
The second part does not work. What I am trying to do is using random number I have already generated instead of Rnd() in gamma function. I Have tried the second part of the code with the rand(), because I wanted to know, if its gonna work.
PS: it's a school project and each part of the code is started by pressing a click button, in case you are wondering why I have separated it into 2 parts.
For the first part you can do this:
Dim rand_rng As Range
Set rand_rng = Range("A1:A20000")
rand_rng = "=Rand()"
rand_rng = rand_rng.Value2
Range("A1") = ("Rand")
MsgBox ("Nagenerovaných 20 000 hodnôt")
Second part has to be like this:
Dim g_range As Range
Set g_range = Range("B1:B20000")
Range("I2").Value = InputBox("zadaj parameter alfa")
Range("J2").Value = InputBox("zadaj parameter beta")
g_range.formula = "=Gamma.Inv(A1, $I$2, $J$2)"
N.B. parameter that is getting defined needs to be on the left side (i.e. alpha = Range("I2").Value)
Might be a bit faster to set them all at once:
[I2] = InputBox("zadaj parameter alfa")
[J2] = InputBox("zadaj parameter beta")
[A1:B20000] = [{"=RAND()", "=GAMMA.INV(A1, I$2, J$2)"}]

Writing a loop in Excel for Visual Basic

How do i write the following code as a loop. I want to copy values from a table in sheet 4 in a row from range (b:17:L17"). is there a more efficient way to do it with loops ?
ActiveSheet.Range("B17").Value = Sheets(4).Range("G8")
ActiveSheet.Range("C17").Value = Sheets(4).Range("G9")
ActiveSheet.Range("D17").Value = Sheets(4).Range("G10")
ActiveSheet.Range("E17").Value = Sheets(4).Range("G11")
ActiveSheet.Range("F17").Value = Sheets(4).Range("G12")
ActiveSheet.Range("G17").Value = Sheets(4).Range("G13")
ActiveSheet.Range("H17").Value = Sheets(4).Range("G14")
ActiveSheet.Range("I17").Value = Sheets(4).Range("G15")
ActiveSheet.Range("J17").Value = Sheets(4).Range("G16")
ActiveSheet.Range("K17").Value = Sheets(4).Range("G17")
ActiveSheet.Range("L17").Value = Sheets(4).Range("G18")
Yes, there is:
ActiveSheet.Range("B17:L17").Value = Application.Transpose(Sheets(4).Range("G8:G18").Value)
You can, using something like this (VB.Net, but may copy easily to VBA):
Dim cell as Integer, c as Integer
cell = 8
For c = 66 To 76
ActiveSheet.Range(Chr(c) & "17").Value = Sheets(4).Range("G" & cell)
cell = cell + 1
Next
The Chr() function gets the character associated with the character code (66-76), and then this value is concatenated with the string "17" to form a complete cell name ("B17", "C17", ...)
I am also incrementing the cell number for G at the same time.
Use this if you really want to use a loop - but there could be better ways, like the answer given by #A.S.H
Solution explanation:
Establish your rules! What is changing in the range for active sheet? The column is going to grow as the for/to cycle does! So, we should sum that to it. What is the another thing that is going to increment? The Range in the other side of the '=' so, by setting an algorithm, we may say that the row is const in the Activesheet range and the column is the on variable on the other side.
Solution:
Sub Test()
Const TotalInteractions As Long = 11
Dim CounterInteractions As Long
For CounterInteractions = 1 To TotalInteractions
'where 1 is column A so when it starts the cycle would be B,C and so on
'where 7 is the row to start so when it begins it would became 8,9 and so on for column G
ActiveSheet.Cells(17, 1 + CounterInteractions).Value = Sheets(4).Cells(7 + CounterInteractions, 7)
Next CounterInteractions
End Sub
This is probably your most efficient solution in a with statement:
Sub LoopExample()
Sheets("Sheet4").Range("G8:G18").Copy
Sheets("Sheet2").Range("B17").PasteSpecial xlPasteValues, Transpose:=True
End Sub

Range object returns empty values

I have a set range to a variable in this fashion:
Dim srcRng As Range
Set srcRng = Range(hrwb.Worksheets(1).Range(yomColAddress)(1).Address, _
Cells(hrwb.Worksheets(1).Range(yomColAddress).row + 200, rightMostCol)(1).Address)
for some weird reason when I call
srcRng(1) 'actually instead of 1 is i that runs 1 to srcRng.Count
it doesn't return the upper leftmost cell value. Any ideas why?
(for those who are not familiar with this technique: http://www.cpearson.com/excel/cells.htm)
Informations:
at execution time the variables yomColAddress=$AL$9 and righMostCol=40
hrwb.Worksheets(1).Range(yomColAddress)(1) works as expected.
With MsgBox srcRng(1).Address & " value:" & srcRng(1).Value I get "$AL$9 value:"
The value of AL9 is the text "yom"
The actual code is:
Dim srcRng As Range
Set srcRng = Range(hrwb.Worksheets(1).Range(yomColAddress)(1).Address, Cells(hrwb.Worksheets(1).Range(yomColAddress).row + 200, rightMostCol)(1).Address)
Dim i As Integer
i = 1
While (weekDayCol = 0 And i <= srcRng.count)
If loneHebDayLetter("à", "ä", srcRng(i)) Then'loneHebDayLetter checks some conditions on a cell
weekDayCol = srcRng(i).Column
End If
i = i + 1
Wend
I think I get what goes wrong here:
The code itself is working well but not on the good data (This is a supposition but I just did some tests with a custom workbook)
Short version
Just add srcRng.Select after Set srcRng (no real interest but to understand what it does) and I think you will get what happens if my supposition is correct.
Longer version
When you do Set srcRng = ... it does create the correct Range but it is not linked to any sheet actually ... It just means remember a Range which goes from cell X to cell Y.
The point is: The sheet (let's say "sheet2") where your code is executed isn't the same as the one where the datas are (say "sheet1") so srcRng(1) is understood as Sheets("sheet2").srcRng(1) instead of Sheets("sheet1").srcRng(1) (<- that's what you want)
Even if not elegant, this should work:
Dim srcRng As Range
With hrwb.Worksheets(1)
Set srcRng = Range(.Range(yomColAddress)(1).Address, Cells(.Range(yomColAddress).row + 200, rightMostCol)(1).Address)
Dim i As Integer
i = 1
While (weekDayCol = 0 And i <= srcRng.count)
If loneHebDayLetter("à", "ä", .Range(srcRng.Address)(i).Value) Then 'I assume it take the value not the cell: if it take the cell you may get an error!
weekDayCol = srcRng(i).Column
End If
i = i + 1
Wend
End With
What is important is the use of .Range(srcRng.Address)(i).Value to access the value in the right worksheet! (That's why this trick is not needed here: srcRng(i).Column because colum numbers do not change from one sheet to an other)
(NOTE: I used with to optimize/clarify the code)
If something isn't clear tell me