I am using this VBA function, and right now it works fine as the format of my spreadsheet has not changed. But I will be adapting it for another use, and the order is likely going to be changing a bit more often. The table header names that I need for this function will not change their names though. My whole spreadsheet equations already uses structured references, but I am struggling to wrap my head around how exactly I eliminate the remnants of the explicit references to the column position in the VBA code.
The row that states If data(i, 2).Value = expNum Then and
If data(i, 14).Value <> Empty Then
curEnergy = data(i, 14).Value`
Are the ones in question. Column 2 corresponds to a column named "Exp #", and Column 14 corresponds to a column name "G (kcal/mol)" I've attached the full function below, just in case there are critical pieces in other sections that I'm not aware of. I would like to replace those references to 2 and 14 with the structured reference or something robust to withstand reordering of column positions.
Function BoltzmannEnergy(expNum As String) As Double
Application.Volatile
Worksheets("Raw Values").Activate
Dim data As Range, curCell As Range
Dim numRows As Integer, arrayCount As Integer, arraySize As Integer
Dim energies() As Double, curEnergy As Double, minEnergy As Double, RT As Double, BoltzTop As Double, BoltzBtm As Double
Dim expFound As Boolean
Const T As Double = 298
Const R As Double = 0.001985
RT = R * T
BoltzTop = 0
BoltzBtm = 0
expFound = False
arraySize = 5
minEnergy = 0
ReDim energies(0 To arraySize)
Set data = Range("RawValues")
arrayCount = 0
numRows = data.Rows.Count
For i = 1 To numRows
If data(i, 2).Value = expNum Then
If arrayCount = UBound(energies) - 1 Then
ReDim Preserve energies(0 To arrayCount + arraySize + 1)
End If
expFound = True
If data(i, 14).Value <> Empty Then
curEnergy = data(i, 14).Value
If curEnergy <> 0 Then
If curEnergy < minEnergy Then
minEnergy = curEnergy
End If
energies(arrayCount) = curEnergy
arrayCount = arrayCount + 1
End If
End If
ElseIf expFound = True Then
Exit For
End If
Next i
For i = 0 To arrayCount - 1
BoltzTop = BoltzTop + energies(i) * Exp(-(energies(i) - minEnergy) / RT)
BoltzBtm = BoltzBtm + Exp(-(energies(i) - minEnergy) / RT)
Next i
BoltzmannEnergy = BoltzTop / BoltzBtm
End Function
If you know that header names will not change you can replace those number with two variables each referring to a find range.columns.
Something like:
Dim intColumn1, intColumn2 As Integer
Dim rngTemp As Range
'assuming your header row is row 1
Set rngTemp = Worksheets("Raw Values").Rows(1).Find(what:="Header_1_value")
'check if you actually found a cell
If Not rngTemp Is Nothing Then
intColumn1 = rngTemp.Column
End If
'assuming your header row is row 1
Set rngTemp = Worksheets("Raw Values").Rows(1).Find(what:="Header_2_value")
'check if you actually found a cell
If Not rngTemp Is Nothing Then
intColumn2 = rngTemp.Column
End If
Related
I would like to randomize 13 variables which in sum have to be 100%.
For a better understanding I show you what I mean by a snippet. From column F to column R all variables have to understand that in sum (column S) these are not allowed to be over 100.
Each row shall be another scenario, so the rows by itself are independent.
My approach was like the following but unfortunately nothing happens. Anyone an idea? Thanks a lot.
Sub Zufall()
Dim k As Integer
Application.ScreenUpdating = False
Range("F11:R55").ClearContents
DoEvents
Do Until WorksheetFunction.Sum(Range("S11:S55")) = 100
Randomize
For k = Columns(19) To Columns(6) Step -1
Cells(11, k).Formula = Int(Rnd() * 100)
Next k
Range("R55:F11").Value = WorksheetFunction.Sum(Range("S11:S55"))
DoEvents
Loop
Application.ScreenUpdating = True
End Sub
The following code shows how to generate a set of n random single precision numbers between 0 and 1 which sum to 1.
' Returns an array of random numbers (0 <= n < 1) which sum to 1.
' Size is the size of the array to return.
Function GenerateRandomNumbers(ByVal size As Integer) As Variant
ReDim vals(1 To size) As Single
Dim idx As Integer
Dim sum As Single
For idx = 1 To size
vals(idx) = Rnd
sum = sum + vals(idx)
Next idx
For idx = 1 To size
vals(idx) = vals(idx) / sum
Next idx
GenerateRandomNumbers = vals
End Function
You use it like this.
Sub Randomm()
Dim row As Long
Dim col As Long
Dim thirteenRandomNumbersWhichSumToOne As Variant
With Worksheets("Sheet1")
For row = 6 To 50
thirteenRandomNumbersWhichSumToOne = GenerateRandomNumbers(13)
For col = 6 To 18 ' F to R
.Cells(row, col).Value = thirteenRandomNumbersWhichSumToOne(col - 5)
.Cells(row, col).NumberFormat = "0%"
Next col
.Cells(row, 19).FormulaR1C1 = "=SUM(RC6:RC18)"
.Cells(row, 19).NumberFormat = "0%"
Next row
End With
End Sub
This will generate 13 values from A1 through A13:
Sub Randomm()
Dim wf As WorksheetFunction, rng As Range
Set wf = Application.WorksheetFunction
Set rng = Range("A1:A13")
rng.Formula = "=RAND()"
rng.Value = rng.Value
zum = wf.Sum(rng)
For i = 1 To 13
Cells(i, 1).Value = Cells(i, 1).Value / zum
Next i
rng.NumberFormat = "0.00%"
End Sub
this is the current and last code. Missing:
idx values in sum do not limit up to 100% in column S
Changed:
column S was implied by using the easy sum calcluation in Excel
the % wasnt shown right this is why i used it by Format in Excel
Sub Randomm()
Dim wf As WorksheetFunction, rng As Range, zum As Single, i As Integer
Set wf = Application.WorksheetFunction
Set rng = Range("F6:R50")
Dim totalRow As Single
rng.Formula = "=RANDBETWEEN(5,25)/100"
rng.Value = rng.Value
zum = wf.sum(rng)
For i = 6 To 50
Cells(i, 6).Value = Cells(i, 13).Value
Next i
End Sub
Function GenerateRandomNumbers(ByVal size As Integer) As Variant
ReDim vals(1 To size) As Single
Dim idx As Integer
Dim sum As Single
For idx = 1 To size
vals(idx) = Rnd
Next idx
For idx = 1 To size
vals(idx) = vals(idx)
Next idx
GenerateRandomNumbers = vals
End Function
I have an Excel formula that operates on a pre-existing range of data.
The Excel formula is: =STDEV.S(IF(FREQUENCY(range,range),range)) , where "range" is the aforementioned range of data.
My goal is to convert this formula into VBA code.
The following code is my attempt at trying to convert the formula into VBA, as well as my visualization of the process to try and understand why it is not putting out the same result:
Private Sub CommandButton1_Click()
Dim diffArray() As Variant
Dim i As Integer
Dim x As Integer
Dim array1() As Variant, size As Integer, j As Integer
Dim freqArray1() As Variant
Dim freqArray2() As Variant, size2 As Integer, j2 As Integer
'assigns the data values to array1
size = 0
j = 0
ReDim array1(size)
For i = 3 To 15
size = size + 1
ReDim Preserve array1(size)
array1(j) = Cells(i, 2)
j = j + 1
Next i
Cells(20, 2).Value = UBound(array1)
Cells(21, 2).Value = LBound(array1)
If UBound(array1) > 1 Then Cells(19, 2).Value = WorksheetFunction.StDev_S(array1)
'setting freqArray1 to frequency(array1, array1)
freqArray1 = WorksheetFunction.Frequency(array1, array1)
Cells(20, 3).Value = UBound(freqArray1)
Cells(21, 3).Value = LBound(freqArray1)
For i = LBound(freqArray1) To (UBound(freqArray1))
Cells(2 + LBound(freqArray1) + i, 3).Value = freqArray1(i, 1)
Next i
If UBound(freqArray1) > 1 Then Cells(19, 3).Value = WorksheetFunction.StDev_S(freqArray1)
'setting freqArray2 to if(frequency(array1, array1), array1)
size2 = 0
j2 = 0
ReDim freqArray2(size2)
For i = LBound(freqArray1) To (UBound(freqArray1))
If freqArray1(i, 1) Then
size2 = size2 + 1
ReDim Preserve freqArray2(size2)
freqArray2(j2) = freqArray1(i, 1)
j2 = j2 + 1
End If
Next i
Cells(20, 4).Value = UBound(freqArray2)
Cells(21, 4).Value = LBound(freqArray2)
For i = (LBound(freqArray2)) To UBound(freqArray2)
Cells(2 + LBound(freqArray2) + i, 4).Value = freqArray2(i)
Next i
'takes the standard deviation of if(frequency(array1, array1), array1)
If UBound(freqArray2) > 1 Then Cells(19, 4).Value = WorksheetFunction.StDev_S(freqArray2)
End Sub
The data values being operated on are in the orange cells column B(array1).
The array 'frequency(array1, array1)' is in the yellow cells column C.
The array 'if(frequency(array1, array1), array1)' is in the green cells column D.
The goal is for the values in the two blue cells(B18 and D19) to be the same.
I don't understand two things:
Why are the values in the blue cells(B18 and D19) not the same?
Why do the indices of the arrays change?
One starts at '0', the next starts at '1', and the last starts at '-1'?
use a dictionary to create a unique list and use that in the StDev_S
Private Sub CommandButton1_Click()
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
Dim rngArray As Variant
rngArray = ActiveSheet.Range("B3:B15")
Dim i As Long
For i = LBound(rngArray, 1) To UBound(rngArray, 1)
On Error Resume Next
dict.Add rngArray(i, 1), rngArray(i, 1)
On Error Resume Next
Next i
If dict.Count > 0 Then
Dim unqArr As Variant
ReDim unqArr(1 To dict.Count) As Variant
i = 1
Dim key As Variant
For Each key In dict.Keys
unqArr(i) = key
i = i + 1
Next key
ActiveSheet.Cells(19, 4).Value = Application.WorksheetFunction.StDev_S(unqArr)
End If
End Sub
Is there a way (vba code or excel trick) to manipulate a 2 columnar list so that I get a table with all potential combinations depending on a unique identifier in the first column?
E.g. I have one column with Company Names and another with Country Locations. What I need is every set if combinations of the countries per company (see screenshot attached).
This vba module should solve your problem.
Just copy the code to a new module, declare the input and output columns and the number of the first row of your list.
Note that the code will stop once it hits a line where the "Unique Identifier" Cell is empty.
Also, it requires that your list is sorted with respect to your "Unique Identifier".
If a Unique Identifier only appears once, it will still be written into the output list, but only once and with the outColNation2 staying empty in that row. If this is not desired and it should be left out entirely, just delete the commented if-statement.
Example Image of output
Also note, that a unique identifier can repeat at most 100 times. I assume none of them appears that often as that would create a ridiculously long output list.
Option Compare Text
Sub COMBINATIONS()
Dim i As Long, j As Long, k As Long, l As Long, n As Long
Dim arr(100) As String
Dim UI As String
Dim inColUI As Integer, inColNation As Integer
Dim outColUI As Integer, outColNation1 As Integer, outColNation2 As Integer
Dim FirstRowOfData As Integer
Dim YourWS As Worksheet
inColUI = 1 'Column of the "Unique Identifier"
inColNation = 2 'Column of the "Nations" in your example
outColUI = 4
outColNation1 = 5 'output columns
outColNation2 = 6
FirstRowOfData = 2 'First Row of data
Set YourWS = Application.Worksheets("Sheet1") 'Put in your Worksheet Name here.
i = FirstRowOfData
n = FirstRowOfData
With YourWS
Do Until .Cells(i, inColUI) = ""
j = 0
UI = .Cells(i, inColUI)
Do Until .Cells(i - 1, inColUI) <> .Cells(i, inColUI) And j > 0 Or .Cells(i, inColUI) = ""
arr(j + 1) = .Cells(i, inColNation)
i = i + 1
j = j + 1
Loop
If j = 1 Then '<- remove this if-statement and the following marked lines if single appearing UIs should be omitted entirely
.Cells(n, outColUI) = UI '<---
.Cells(n, outColNation1) = arr(1) '<---
n = n + 1 '<---
Else '<---
For k = 1 To j
For l = 1 To j
If arr(k) <> arr(l) Then
.Cells(n, outColUI) = UI
.Cells(n, outColNation1) = arr(k)
.Cells(n, outColNation2) = arr(l)
n = n + 1
End If
Next l
Next k
End If '<---
Loop
End With
End Sub
Edit: cleaned up the code a little bit
Something like the following shows how to iterate through 2 ranges of cells
Dim Rng1 as Range, Rng2 as Range
Dim SrcCell as Range, OthrCell as Range
Dim FullList as string
Rng1 = Range("A1:A12")
Rng2 = Range("B1:B12")
FullList = ""
For Each SrcCell in Rng1
For Each OthrCell in Rng2
FullList = IIF(FullList="","",FullList & vbCrLf) & SrcCell.Value & OthrCell.Value
Next OthrCell
Next srcCell
The FullList string now contains all the combinations but you may require something else. Only intended to give you a start
You need to add code yourself to filter out duplicates
You can do the following (see code below). As another commentee mentioned, when there is only one record of company vs country, it will not show in the output.
The solutions is based on creating a dictionary, each entry is a company and the value is a comma separated string of countries. After the dictionary is created, the dictionary is looped, and a list of countries is then iterated over a nested loop. If the index of the outer loop is the same as the inner index of the loop then the loop is skipped i.e. that would be a Country 1 vs Country 1 combination. Otherwise is added to the output list.
Columns A,B is input and columns D,E,F is output.
Option Explicit
Public Sub sCombine()
Dim r As Range, dest As Range
Dim d As New Dictionary
Dim key As Variant
Dim countries() As String
Dim i As Integer, j As Integer
On Error GoTo error_next
Set r = Sheet1.Range("A1")
Set dest = Sheet1.Range("D:F")
dest.ClearContents
Set dest = Sheet1.Range("D1")
While r.Value <> ""
If d.Exists(r.Value) Then
d(r.Value) = d(r.Value) & "," & r.Offset(0, 1)
Else
d.Add r.Value, r.Offset(0, 1).Value
End If
Set r = r.Offset(1, 0)
Wend
For Each key In d.Keys
countries = Split(d(key), ",")
For i = LBound(countries) To UBound(countries)
For j = LBound(countries) To UBound(countries)
If i <> j Then
dest.Value = key
dest.Offset(0, 1).Value = countries(i)
dest.Offset(0, 2).Value = countries(j)
Set dest = dest.Offset(1, 0)
End If
Next j
Next i
Next key
Exit Sub
error_next:
MsgBox Err.Description
End Sub
The below VBA code sets a range of cells as commentArray, removes any blanks from the array and creates a new, blank free array, called commentResults. I then want to declare the array.
There is a possibility, depending on my source data, that the array could then still be empty so the below doesn't work to declare
thisws.Cells(i, 19).Resize(columnsize:=UBound(commentResults) - LBound(commentResults) + 1).Value = commentResults
So I thought I would add a check (the if statement after the debug.print), that only declared the array if array(0) wasn't empty but I continuously get an error 9 which I can't resolve.
Dim commentArray(4) As Variant
commentArray(0) = Cells(24, 4).Value
commentArray(1) = Cells(25, 3).Value
commentArray(2) = Cells(26, 3).Value
commentArray(3) = Cells(27, 3).Value
'a and b as array loops
Dim a As Long, b As Long
Dim commentResults() As Variant
'loops through the array to remove blanks - rewrites array without blanks into commentArray
For a = LBound(commentArray) To UBound(commentArray)
If commentArray(a) <> vbNullString Then
ReDim Preserve commentResults(b)
commentResults(b) = commentArray(a)
b = b + 1
End If
Next a
Debug.Print b
If IsError(Application.Match("*", (commentResults), 0)) Then
Else
thisws.Cells(i, 19).Resize(columnsize:=UBound(commentResults) - LBound(commentResults) + 1).Value = commentResults
b = 0
End If
Any thoughts on why this might not work?
I have also tried:
If commentResults(0) <> vbNullString Then
thisws.Cells(i, 27).Resize(columnsize:=UBound(commentResults) - LBound(commentResults) + 1).Value = commentResults
End If
Sub CommentArray()
Dim Comments As Range, c As Range
Set Comments = Union(Cells(24, 4), Range(Cells(25, 3), Cells(27, 3)))
Dim commentResults() As Variant
Dim i As Long
i = 0
For Each cell In Comments
If cell.Value <> "" Then
ReDim Preserve commentResults(i)
commentResults(i) = cell.Value
i = i + 1
End If
Next cell
Dim debugStr As String
For i = LBound(commentResults) To UBound(commentResults)
debugStr = debugStr & commentResults(i) & Chr(10)
Next i
MsgBox debugStr
End Sub
I'm pretty new to UDF's and I'm not sure entirely how they function. My function returns correct information so long no new rows are inserted. It's as if headRng gets saved to memory when first used and doesn't get updated even if a new row is inserted. How can I fix this?
Additionally. My function appears to be looping a LOT of times. In my code you'll see a msgbox that appears after 1000 rows. So I know it's looping at least 1000 times. No idea why it's looping though. Forgot I had another workbook open with this same function which was causing the 1000+ loop.
Example of how it might be used: https://i.imgur.com/zRQo0SH.png
Function StraightLineFunc(headRng As Range, dataRng As Range) As Double
Application.Volatile True
Dim arrCntr As Integer
Dim arr() As Variant
Dim rowOffset As Integer
Dim cntr As Integer
Dim stdvTotal As Double
stdvTotal = 0
cntr = 0
arrCntr = 1
For Each cell In headRng
If cell <> "Response" And cell <> "Open-Ended Response" And cell <> "" Then
If cell.Offset(-1, 0) <> "" And cntr > 0 Then
stdvTotal = stdvTotal + StdDev(arr)
End If
If cell.Offset(-1, 0) <> "" Then
cntr = cntr + 1
'new grouping heading
Erase arr
ReDim arr(headRng.Columns.Count)
arrCntr = 1
arr(arrCntr) = cell(dataRng.Row - 1, 1).Value
arrCntr = arrCntr + 1
Else
arr(arrCntr) = cell(dataRng.Row - 1, 1).Value
arrCntr = arrCntr + 1
End If
End If
Next cell
stdvTotal = stdvTotal + StdDev(arr)
StraightLineFunc = stdvTotal
End Function
Function StdDev(arr)
Dim i As Integer
Dim avg As Single, SumSq As Single
Dim k1 As Long, k2 As Long
Dim n As Long
k1 = LBound(arr)
k2 = UBound(arr)
n = 0
avg = Mean(arr)
For i = k1 To k2
If arr(i) = 0 Or arr(i) = "" Then
'do nothing
Else
n = n + 1
SumSq = SumSq + (arr(i) - avg) ^ 2
End If
Next i
StdDev = Sqr(SumSq / (n - 1))
End Function
Function Mean(arr)
Dim Sum As Single
Dim i As Integer
Dim k1 As Long, k2 As Long
Dim n As Long
k1 = LBound(arr)
k2 = UBound(arr)
Sum = 0
n = 0
For i = k1 To k2
If arr(i) = 0 Or arr(i) = "" Then
'do nothing
Else
n = n + 1
Sum = Sum + arr(i)
End If
Next i
Mean = Sum / n
End Function
as about headrng first address remembrance it must be a matter of how you're checking subranges, relying on the presence of certain non blank cells over headrng itself. so that if you insert one or more rows between headrng row and the one above it, it would have a different behavior
as about the looping 1000 times it must be because you must have copied a formula that uses it down to row 1000, so that excel calculates all of them even if you're changing only one row
moreover from your data example I think you should change code as follows
Option Explicit
Function StraightLineFunc1(headRng As Range, dataRng As Range) As Double
Application.Volatile True
Dim arrCntr As Integer
Dim arr() As Variant
Dim rowOffset As Integer
Dim cntr As Integer
Dim stdvTotal As Double
Dim cell As Range
stdvTotal = 0
cntr = 0
arrCntr = 1
For Each cell In headRng
If cell <> "Response" And cell <> "Open-Ended Response" And cell <> "" Then
If cell.Offset(-1, 0) <> "" And cntr > 0 Then
stdvTotal = stdvTotal + WorksheetFunction.StDev(arr)
End If
If cell.Offset(-1, 0) <> "" Then
cntr = cntr + 1
'new grouping heading
Erase arr
arrCntr = 1
ReDim Preserve arr(1 To arrCntr)
arr(arrCntr) = cell(dataRng.Row - 1, 1).Value
Else
arrCntr = arrCntr + 1
ReDim Preserve arr(1 To arrCntr)
arr(arrCntr) = cell(dataRng.Row - 1, 1).Value
End If
End If
Next cell
stdvTotal = stdvTotal + WorksheetFunction.StDev(arr)
StraightLineFunc1 = stdvTotal
End Function
which however could still suffer form the remembrance issue
so I'd also throw in a different "subranges" checking like follows
Function StraightLineFunc2(headRng As Range, dataRng As Range) As Double
'Application.Volatile True
Dim stdvTotal As Double
Dim j1 As Long, j2 As Long
j1 = 1
Do Until InStr("Open-Ended Response", headRng(1, j1)) = 0 And headRng(1, j1) <> ""
j1 = j1 + 1
Loop
Set headRng = headRng.Offset(, j1 - 1).Resize(, headRng.Columns.Count - j1 + 1)
j1 = 1
Do While j1 < headRng.Columns.Count
j2 = j1
Do While headRng(1, j2) <> "Response" And j2 <= headRng.Columns.Count
j2 = j2 + 1
Loop
stdvTotal = stdvTotal + WorksheetFunction.StDev(Range(headRng(1, j1), headRng(1, j2 - 1)).Offset(dataRng.Row - headRng.Row))
j1 = j2 + 1
Loop
StraightLineFunc2 = stdvTotal
End Function