hope all is well.
I am slightly stuck on a VBA function called randbetween in Excel.
Nature of the problem is that I need to create random numbers based on a bunch of other numbers, about 50,000 other numbers in total.
The random numbers I generate must be between 1 and X. X being the other numbers in column D1:D50,000.
As an example: if cell D1 contains the number 5, then I need to create a random number between 1 and 5 in Cell A1. then move on to D2,D3,D4.....etc and create random numbers for each one accordingly, A2,A3,A4...etc.
I tried to use the following but unfortunately the offset part doesn't work. I want to dynamically work through each cell.
the code is as follows:
r = WorksheetFunction.RandBetween(1, Offset(A1, n, 9))
'where n = 2
Most grateful for any help,
Your use of OFFSET is the wrong syntax. You would need somthing like
Range("A1").Offset(RowOffset, ColumnOffset)
But there is a much better approach to achieve your stated goal. Use Range.FormulaR1C1
Sub Demo()
Dim rng As Range
' Define range
Set rng = [A1:A50000]
' Put formulas into the range
rng.FormulaR1C1 = "=RANDBETWEEN(1,RC4)"
'optional, replace formulas with values
rng.Value = rng.Value
End Sub
Related
I am relatively new to VBA and I am working on the following exercise from WiseOwl in order to improve my coding skills. The goal of the task is to populate random numbers within cells B3:G3 randomly, where no random number can be the same. In trying to accomplish that, I have done the following macro
Sub LuckyDip()
Dim i As Variant
Dim Num As Integer
For Each i In Worksheets(1).Range("B3:G3")
Num = WorksheetFunction.RandBetween(1, 59)
Cells(3, i).Offset(0, 1).Value = Num
Next i
End Sub
The problem with the code is the following is that instead of populating random numbers within each cell consecutively within the range of B3:G3, it instead populates the random numbers by the random number generated within those cells in the range. For example, if in the cell B3, the random number is 30, then the program will populate a random number in row 3 and cell 30, rather than actually replacing the random number 30 which was originally in cell B3. The problem I know lies in my For Each loop, however, I am not sure how to change it accordingly where the random number does not refer to a specific cell at all, and rather just acts as a value that I can replace. Thanks for any assistance in advance.
You should learn right off the bat that reading/writing cells, one-by-one is a terrible idea. It's really, really slow. And while it won't be noticeable in the tiny range in this question, it becomes painful with larger ranges. So just get into the habit of reading and writing ARRAYS to the worksheet instead of individual values.
For example:
Sub LuckyDip()
Dim j&, v
With Worksheets(1).Range("B3:G3")
v = .Value2
With WorksheetFunction
For j = 1 To UBound(v, 2)
v(1, j) = .RandBetween(1, 59)
Next
End With
.Value2 = v '<--v is an array of values, written to the sheet in one go.
End With
End Sub
With
For Each i In Worksheets(1).Range("B3:G3")
i is a range object and not a number and as such:
Cells(3, i)
will refer to the value in i to be the column number.
You want a standard for loop:
For i = 2 to 7
Where 2 is the column number of B and 7 the column number of G
Sub LuckyDip()
Dim i As Long
Dim Num As Long
For i = 2 To 7
Num = WorksheetFunction.RandBetween(1, 59)
ActiveSheet.Cells(3, i).Offset(0, 1).Value = Num
Next i
End Sub
One note: As you can see, I added ActiveSheet one should get in the habit of always denoting the parent sheet to all range/cell objects. Ideally using the codename.
I have four columns
I want to find all combinations of numbers from each column that add up to an exact value. Is there a VBA script that can help me achieve this goal?
I am looking for an output like:
Let's say that I want the values to add up to 2721, then the code should return the combination as (1,3,6,7) i.e. the corresponding row of each column and so on until it finds all such combinations. If it is not able to find the exact sum then find the nearest sum to that value.
It can be easily done in VBA using ReSize method, Sum function, Loops and conditional statements. Below is an exampe with sample data as:
Step 1: Write a VBA Macro similar to this.
Option Explicit
Function Check_Combination_Sum(ByVal lngSum As Long) As String
Dim rng As Range
Dim rngSource As Range
Set rngSource = Range("A2:A16")
For Each rng In rngSource.Cells
If WorksheetFunction.Sum(rng.Resize(1, 4)) = lngSum Then
rng.Resize(1, 4).Interior.Color = RGB(200, 255, 220)
Check_Combination_Sum = rng.Row & "," & Check_Combination_Sum
End If
Next
If Len(Check_Combination_Sum) > 1 Then
Check_Combination_Sum = Left(Check_Combination_Sum, Len(Check_Combination_Sum) - 1)
End If
End Function
Sub Check_Data()
MsgBox Check_Combination_Sum(197)
End Sub
Step 2: The code above will highlight the rows containing the data with total sum as given and also pop the rows number. Modify the code to suit your requirements.
I am trying to average non-contiguous cells as shown.
I am taking the average of columns A and C for each row. I am trying to do the same but with a named range (including columns A and C), because my actual data have thousands of columns and it will be hell to write the formula let alone for the users to understand what is being averaged.
Obviously, I don't understand how indexing a named range works. I expected that index(RangeAC,2) would give me the second row of values in RangeAC. Instead, I get the second row in column A. Trying index(RangeAC,2,2) results in an error.
Is it possible to get this average with a named range or do I need a different approach?
I don't know if I'm missing something, but isnt this as simple as using the Excel intersect operator?:
=AVERAGE(RangeAC 8:8)
Put in the first row of the named Range data(which seems to be 8:8 in your case), and copy down...
Isnt that the same as the suggested VBA UDF from MrExcel forums?
Option 1:
Lets say the name of your range is my_data like this one:
This is the formula to use:
Public Function calculate_avg(rng As Range) As Double
calculate_avg = WorksheetFunction.Average(Range(rng.Cells(1, 1).Address, Cells(rng.Rows.Count + rng.Cells(1, 1).Row - 1, rng.Columns.Count + rng.Cells(1, 1).Column - 1).Address))
End Function
Option 2:
Your named range is the following:
You want the average of the 2. and the 3. column. (C&D).
This is how you get it:
Option Explicit
Public Function calculate_avg(rng As Range, Optional l_starting_col As Long = 1, Optional l_end_col As Long = 1) As Double
Dim my_start As Range
Dim my_end As Range
Set my_start = Cells(rng.Cells(1, 1).Row, l_starting_col + rng.Cells(1, 1).Column - 1)
Set my_end = Cells(rng.Cells(rng.Rows.Count, l_end_col).Row, rng.Columns.Count - rng.Cells(1, l_end_col).Column + l_end_col)
'Debug.Print my_start.Address
'Debug.Print my_end.Address
calculate_avg = WorksheetFunction.Average(Range(my_start, my_end))
End Function
You pass as arguments the starting and the end column. Thus something like this:
?calculate_avg([my_test_big],2,3) in the immediate window returns 72,5. The same can be used as an Excel formula. Good luck! :)+
Option 3
Public Function calculate_avg_row(rng As Range, Optional l_row As Long = 1) As Double
Dim my_start As Range
Dim my_end As Range
Set my_start = Cells(rng.Cells(l_row, 1).Row, rng.Cells(l_row, 1).Column)
Set my_end = rng.Cells(l_row, rng.Columns.Count)
Debug.Print my_start.Address
Debug.Print my_end.Address
calculate_avg_row = WorksheetFunction.Average(Range(my_start, my_end))
End Function
This one works like this:
calculate_avg_row([test_rng],5)
And gives the average of the 5th row of the named range, including all columns of the named range.
Could you not attach a name to a formula as well? If so,go to the "Formula" tab , "Define Name"and type in the "Refers to" box =Average(A1,C1)). In the name box, you could name it "Average" or whatever you choose to call it.The references would continue to be non-contiguous if you dragged to the right or down the sheet. I am not sure if that is exactly what you're seeking.
I appreciate everyone's help. This problem has taken me considerably longer than I was willing to spend on it. Non-contiguous ranges are a nightmare in Excel.
Eric at Mr Excel proposed the most elegant working solution - just one line of VBA.
The third parameter of the Index function Reference form can be used to specify the area number:
= AVERAGE( INDEX(RangeAC, ROW()), INDEX(RangeAC, ROW(), , 2) )
or if RangeAC does not start at row 1, something like:
= AVERAGE( INDEX(RangeAC, ROW()-ROW(RangeAC)+1), INDEX(RangeAC, ROW()-ROW(RangeAC)+1, , 2) )
I would like to do a vertical lookup for a list of lookup values and then have multiple values returned into columns for each lookup value. I actually managed to do this after a long Google search, this is the code:
=INDEX(Data!$H$3:$H$70000, SMALL(IF($B3=Data!$J$3:$J$70000, ROW(Data!$J$3:$J$70000)-MIN(ROW(Data!$J$3:$J$70000))+1, ""), COLUMN(A$2)))
Now, my problem is, as you can see in the formula, my lookup range contains 70,000 rows, which means a lot of return values. But most of these return values are double. This means I have to drag above formula over many columns until all lookup values (roughly 200) return #NUM!.
Is there any possible way, I guess VBA is necessary, to return the values after duplicates have been removed? I'm new at VBA and I am not sure how to go about this. Also it takes forever to calculate having so many cells.
[Edited]
You can do what you want with a revised formula, not sure how efficient it will be with 70,000 rows, though.
Use this formula for the first match
=IFERROR(INDEX(Data!$H3:$H70000,MATCH($B3,Data!$J3:$J70000,0)),"")
Now assuming that formula in in F5 use this formula in G5 confirmed with CTRL+SHIFT+ENTER and copied across
=IFERROR(INDEX(Data!$H3:$H70000,MATCH(1,($B3=Data!$J3:$J70000)*ISNA(MATCH(Data!$H3:$H70000,$F5:F5,0)),0)),"")
changed the bolded part depending on location of formula 1
This will give you a list without repeats.....and when you run out of values you get blanks rather than an error
Not sure if you're still after a VBA answer but this should do the job - takes about 25 seconds to run on my machine - it could probably be accelerated by the guys on this forum:
Sub ReturnValues()
Dim rnSearch As Range, rnLookup As Range, rnTemp As Range Dim varArray
As Variant Dim lnIndex As Long Dim strTemp As String
Set rnSearch = Sheet1.Range("A1:A200") 'Set this to your 200 row value range
Set rnLookup = Sheet2.Range("A1:B70000") 'Set this to your lookup range (assume 2
columns)
varArray = rnLookup
For Each rnTemp In rnSearch
For lnIndex = LBound(varArray, 1) To UBound(varArray, 1)
strTemp = rnTemp.Value
If varArray(lnIndex, 1) = strTemp Then
If WorksheetFunction.CountIf(rnTemp.EntireRow, varArray(lnIndex, 2)) = 0 Then 'Check if value exists already
Sheet1.Cells(rnTemp.Row, rnTemp.EntireRow.Columns.Count).End(xlToLeft).Offset(0, 1).Value =
varArray(lnIndex, 2)
End If
End If
Next Next
End Sub
Let's say I have a range called rng1
Set rng1 = Worksheets("Sheet1").Range("A1","A5")
Is there a quick and easy way to perform a mathematical function, lets say divide all those cell values by 2, for all the cells in rng1?
Any help is appreciated!
It's very easy, but the final code will depend on where you want to store the new values. For example, if you want to store the values divided by 2 in the next column:
Sub test()
Dim cell As Range
For Each cell In Range("A1:A5")
cell.Offset(, 1).Value = cell.Value / 2
Next
End Sub
Mind you there are more efficient ways to do this than using offset if your range is large, but for a smaller range, this is totally acceptable and fast.
If you want to overwrite the values, you can simply use cell.Value in place of cell.Offset(,1).Value
Another Way
Sub Main()
[B1:B5] = [INDEX((A1:A5/2),)]
End Sub
How it works is well explained here.