Quickly filling up excel cells from a Array - vba

I generate arrays from a external data tool and now want to fill the cells up using the data in the arrays. I have written the following code.
With ThisWorkbook.Worksheets("Data")
Lastrow = .Cells(.Rows.count, 2).End(xlUp).Row
For i = LBound(Price) To UBound(Price)
.Cells((Lastrow + 1), 1) = Price(i)
.Cells((Lastrow + 1), 2) = Quantity(i)
Lastrow = Lastrow + 1
Next i
End With
All the arrays are of same length and I have around 25 odd arrays to work with. The code works fine but the problem I am facing is of speed. It takes me around 5-6 hours to fill the sheet once with around 3000 as the length of array. Please suggest your best way. Thank you.

Here is an example of how to populate to a range from an array without looping:
Sub PopulateFromArray()
Dim MyArr As Variant
MyArr = Array("Hello", "World", "This is some", "Text")
Range("A1").Resize(UBound(MyArr) + 1, 1).Formula = Application.Transpose(MyArr)
End Sub
We are using resize to resize the range to populate using the upper boundary of the array. We add one to it because it is option base 0. We transpose the array because by the nature of an array the data goes across and we need it to go down. If we wanted to span columns instead of rows we would need to double transpose it like this:
Application.Transpose(Application.Transpose(MyArr))

With ThisWorkbook.Worksheets("Data")
NextRow = .Cells(.Rows.count, 2).End(xlUp).Row + 1
num = UBound(Price) - LBound(Price)
.Range(.Cells(NextRow, 1), .Cells(NextRow + num, 1)) = Application.Transpose(Price)
.Range(.Cells(NextRow, 2), .Cells(NextRow + num, 2)) = Application.Transpose(Quantity)
End With

you can dump an array to a worksheet range very simply like this:
range("A1:B5").value = myArray
you can populate an array conversly:
dim myArray as variant
myArray = range("A1:B5").value
I use this method very frequently, I hardly ever work with data on a worksheet, I prefer to take it into an array first then work with the array.

You have number of arrays (25) with different data (e.g. Price, Quantity, SomeOtherArray) as per your question. As per my comment above.
Option Explicit
Public Sub GetData()
Dim ws As Worksheet
Dim LastRow As Long
Dim arrPrice As Variant
Dim arrQty As Variant
Set ws = Sheets(3)
'-- starts at zero index
arrPrice = Array(50, 60, 70, 75)
arrQty = Array(250, 100, 50, 200)
'-- change columns as per your needs
LastRow = ws.Range("B" & ws.Rows.Count).End(xlUp).Row
'-- UBound + 1 is because the array starts at zero index above
ws.Range("B1").Offset(LastRow).Resize(UBound(arrPrice)+1).Value = Application.Transpose(arrPrice)
ws.Range("B1").Offset(LastRow, 1).Resize(UBound(arrQty)+1).Value = Application.Transpose(arrQty)
End Sub

To fill a range of N rows by M columns, put the data into a 2-dimensional array of the same size, then assign that array to the Value property of the range.
ReDim varValues(1 To lngRowCount, 1 To lngColCount)
Or
ReDim varValues(0 To lngRowCount - 1, 0 To lngColCount - 1)
I presume you can handle populating the array. Then:
Set rngTheRange = 'I presume you can handle this, too
rngTheRange.Value = varValues
Here is an example that uses this technique to fill the current selection with values 0 through N - 1, where N is the number of cells in the selection:
Option Explicit
Public Sub X()
Dim rngCurrent As Range
Dim lngRows As Long
Dim lngCols As Long
Dim lngR As Long
Dim lngC As Long
Dim varValues() As Variant
Set rngCurrent = Selection
lngRows = rngCurrent.Rows.Count
lngCols = rngCurrent.Columns.Count
ReDim varValues(0 To lngRows - 1, 0 To lngCols - 1) As Variant
For lngR = 0 To lngRows - 1
For lngC = 0 To lngCols - 1
varValues(lngR, lngC) = lngR * lngCols + lngC
Next
Next
rngCurrent.Value = varValues
End Sub

Related

VBA: Set range to columns in different worksheets

I am trying to run the LinEst function through VBA. The problem that I am having is that my X-variables are in the same column but on different worksheets.
My question: Is it possible to combine these columns from the different sheets to one range?
Below is my attempt to code but it gets stuck on the Union part. I provided my sample as well.
Thank you in advance!
Sub FM()
Dim sResult As Worksheet
Set sResult = Sheets("Result")
Dim sY As Worksheet
Set sY = Sheets("Y")
Dim sX1 As Worksheet
Set sX1 = Sheets("X1")
Dim sX2 As Worksheet
Set sX2 = Sheets("X2")
Dim sX3 As Worksheet
Set sX3 = Sheets("X3")
Dim sX4 As Worksheet
Set sX4 = Sheets("X4")
Dim x() As Variant
ReDim x(1 To 4)
x(1) = sX1.Columns("A")
x(2) = sX2.Columns("A")
x(3) = sX3.Columns("A")
x(4) = sX4.Columns("A")
Dim rY As Range
Set rY = sY.Columns("A")
sResult.Range("B2").Value = Application.WorksheetFunction.LinEst(rY, x, True, True)(1, 4)
End Sub
Sample
In your update, x is an Array of Range objects but it needs to be an array of values from each respective range. That is almost certainly the mismatch error.
Resolving that, you'll need to fix your ranges, too, because it seems unlikely that you're using 4 million rows of data (Excel 2007+ has 1048576 rows per worksheet). We can use a method from this answer to help obtain the last row with data from a given column range.
This should get your x values and put them in an array known_x and the known_y array also, which you can use in your LineEst function.
Dim known_x() 'Will contain all of your x values
Dim known_y()
Dim i As Long
Dim rng As Variant
Dim val As Variant
Dim ws As Variant
Dim obs As Long
Dim SHEET_NAMES As String 'Comma-separated list of worksheets
SHEET_NAMES = "X1,X2,X3,X4"
'## Gets the size of the array needed to contain all of the values
For Each ws In Worksheets(Split(SHEET_NAMES, ","))
With ws
obs = obs + GetLastRow(.Columns(1))
End With
Next
ReDim known_x(1 To obs)
'## Dump the values in to the array
i = 1
For Each ws In Worksheets(Split(SHEET_NAMES, ","))
With ws
Set rng = .Range("A1:A" & GetLastRow(.Columns(1)))
For Each val In rng.Value
known_x(i) = val
i = i + 1
Next
End With
Next
'## Dump your y in to an array
With Worksheets("Sheet2")
Set rng = .Range("A1:A" & GetLastRow(.Columns(1)))
known_y = Application.Transpose(rng.Value))
End With
NOTE: If you are in fact using 4 million+ observations, then I think your known_y's parameter may be wrong, because that should be the same size as known_x's in the LinEst function, and you will need to add logic to ensure the arrays are the same size, etc.
NOTE: I've no idea what you're doing with (1, 4) at the end of your LinEst function call.
I don't want to put a useless answer, but if you play a bit with it, you will find something useful. And it produces some result in B2:
Option Explicit
Sub FM()
Dim sResult As Worksheet
Set sResult = Sheets(1)
Dim sY As Worksheet
Set sY = Sheets(2)
Dim sX1 As Worksheet
Set sX1 = Sheets(3)
Dim sX2 As Worksheet
Set sX2 = Sheets(4)
Dim sX3 As Worksheet
Set sX3 = Sheets(6)
Dim sX4 As Worksheet
Set sX4 = Sheets(5)
Dim x() As Variant
ReDim x(1 To 4)
x(1) = sX1.Cells(1, 1).Value
x(2) = sX1.Cells(2, 1).Value
x(3) = sX1.Cells(3, 1).Value
x(4) = sX1.Cells(4, 1).Value
Dim rY As Range
Set rY = sY.Range(sY.Cells(1, 1), sY.Cells(5, 1))
sResult.Range("B2").Value = Application.WorksheetFunction.LinEst(rY(1, 1), x(1), True, True)
End Sub
The problem is mainly in the way you refer to Arrays and Ranges. Check all of them again and you can make a workable solution.

How to read a dynamic range?

I am trying to create a macro that reads data and does econometrics on the data. At this point I am trying to implement a latent variable MLE estimation.
The data can be of any length, depending on the user input. Suppose there is data in column O and column P. Ex-ante I have no idea how many rows of data exist.
I would like to first read how many data there are and then upload the data into my array variable before I can do any econometrics/statistics on it.
In this problem, the user has 25 data points for each variable. Some other user may enter different data with different number of data points.
In the code below, I am trying to read the variable "D" into an array. I first count the number of non-empty cells and then create an array of that size and try to read the value of the cells into the array. But I am getting a "type mismatch" error.
I've tried both "Variant" and "Array" types. Variant seems to be working but Array is not.
Sub SampleStats()
Dim Rng As String
Dim Var1(1 To 100) As Double
Dim Var2() As Double
Dim Var3 As Variant
Dim NumElements2 As Integer
Dim length2 As Integer
NumElements2 = WorksheetFunction.Count(Range("P:P"))
length2 = NumElements2+1
MsgBox NumElements2
ReDim Var2(1 To NumElements2)
Rng = "P2:P" & length2
MsgBox Rng
Var3 = Range(Rng).Value
MsgBox Var3(1,1)
Var2 = Range(Rng).Value
MsgBox Var2(1,1)
End Sub
My questions are:
Whats the best way to read data when you don't know how long the columns go?
What the best way to store data (Variant or Array or something else) when the final objective is doing some statistics?
First you get the Range with the column of data you want to pass into the array. Second you use the Application.Transpose function on the data and assign it to a Variant to create a 1-dimensional array from the Range.Value property.
If you just assign the range's Value directly to the Variant you will get a 2-dimensional array of N rows x 1 column. Sample code:
Option Explicit
Sub GetRangeToArray()
Dim ws As Worksheet
Dim rngData As Range
Dim varData As Variant
Dim lngCounter As Long
' get worksheet reference
Set ws = ThisWorkbook.Worksheets("Sheet1")
' get the column to analyse - example here is A2:A last row
' so using 1 in column reference to Cells collection
Set rngData = ws.Cells(2, 1).Resize(ws.Cells(ws.Rows.Count, 1).End(xlUp))
' convert range from 2d to 1d array
varData = Application.Transpose(rngData.Value)
' test array
For lngCounter = LBound(varData) To UBound(varData)
Debug.Print varData(lngCounter)
Next lngCounter
End Sub
sub createarraywithoutblanks()
creatary ary, Sheets("Table_Types"), "A":
alternative ary:
BuildArrayWithoutBlanks ary
end sub
Sub creatary(ary As Variant, sh As Worksheet, ltr As String)
Dim x, y, rng As range
ReDim ary(0)
Set rng = sh.range(ltr & "2:" & ltr & sh.range("A10000").End(xlUp).Row).SpecialCells(xlCellTypeVisible)
x = 0
For Each y In rng
ary(x) = y
x = x + 1
ReDim Preserve ary(x)
Next y
End Sub
Function Letter(oSheet As Worksheet, name As String, Optional num As Integer)
If num = 0 Then num = 1
Letter = Application.Match(name, oSheet.Rows(num), 0)
Letter = Split(Cells(, Letter).Address, "$")(1)
End Function
Sub alternative(ary As Variant)
Dim Array_2()
Dim Array_toRemove()
Dim dic As New Scripting.Dictionary
Dim arrItem, x As Long
For Each arrItem In ary
If Not dic.Exists(arrItem) Then
dic.Add arrItem, arrItem
Else
ReDim Preserve Array_toRemove(x)
Array_toRemove(x) = dic.Item(arrItem)
x = x + 1
End If
Next
'For Each arrItem In Array_toRemove
' dic.Remove (arrItem)
'Next arrItem
ary = dic.Keys
End Sub
Sub BuildArrayWithoutBlanks(ary As Variant)
Dim AryFromRange() As Variant, AryNoBlanks() As Variant
Dim Counter As Long, NoBlankSize As Long
'set references and initialize up-front
ReDim AryNoBlanks(0 To 0)
NoBlankSize = 0
'load the range into array
AryFromRange = ary
'loop through the array from the range, adding
'to the no-blank array as we go
For Counter = LBound(AryFromRange) To UBound(AryFromRange)
If AryFromRange(Counter) <> 0 Then
NoBlankSize = NoBlankSize + 1
AryNoBlanks(UBound(AryNoBlanks)) = AryFromRange(Counter)
ReDim Preserve AryNoBlanks(0 To UBound(AryNoBlanks) + 1)
End If
Next Counter
'remove that pesky empty array field at the end
If UBound(AryNoBlanks) > 0 Then
ReDim Preserve AryNoBlanks(0 To UBound(AryNoBlanks) - 1)
End If
'debug for reference
ary = AryNoBlanks
End Sub

Is it more optimal to process Range values in for loop or with array

I want to know what is better to populate the cells in a Range:
Checking how many rows and columns I want to populate and use a For..Next loop to go through them accessing the cells (see my code below), or
Do the same but with a Variant array loaded with the cell data, or
Some other method?
Here is my code:
' Count number of rows and columns we have
rowCounter = CountRowsFunction
colCounter = CountColsFunction
' Do operations needed
For i = 2 To rowCounter
originalSheet.Cells(i, colCounter + 1).Value = Round(DateDiff("n", originalSheet.Cells(i, ColumnsIndex(2)).Value, originalSheet.Cells(i, ColumnsIndex(3)).Value) / 60, 2)
Next i
In this case, I'm accessing the cells directly. But, I believe this may cause unnecessary calls to the spreadhseet. Thanks
The usage of an array to set multiple cell values is vastly superior. In the example below the range A1:MZ390 (152100 cells) are set with two methods:
Set all cells to 1; iterate cells and set each cell value to 2
Set all cells to 1; assign range to array; iterate array and set all array values to 2; assign back to range
Method 2 takes less than a second and method 1 takes > 4 seconds on my PC.
In the example it iterates the array, but you can use less code lines and just do varData = 2 - but it is unlikely that people want to set a bunch of cell values to a constant.
Option Explicit
Sub Test()
Dim dt1 As Date, dt2 As Date
Dim lngX As Long, lngY As Long
Dim varData As Variant
Dim ws As Worksheet
Dim rng As Range
'set ws
Set ws = ThisWorkbook.Worksheets("Sheet1")
'for loop method - without screen updating
Application.ScreenUpdating = False
ws.Cells.Delete
Set rng = ws.Range("A1:MZ390")
dt1 = Now
rng.Value = 1
For lngY = 1 To rng.Rows.Count
For lngX = 1 To rng.Columns.Count
rng.Cells(lngY, lngX).Value = 2
Next lngX
Next lngY
dt2 = Now
Application.ScreenUpdating = True
Debug.Print "For loop (without screen updating) took: " & Format(dt2 - dt1, "s") & " seconds"
'array method
ws.Cells.Delete
Set rng = ws.Range("A1:MZ390")
dt1 = Now
rng.Value = 1
varData = rng.Value
For lngX = 1 To UBound(varData, 1)
For lngY = 1 To UBound(varData, 2)
varData(lngX, lngY) = 2
Next lngY
Next lngX
rng.Value = varData
dt2 = Now
Debug.Print "Array method took: " & Format(dt2 - dt1, "s") & " seconds"
End Sub
Arrays are more efficient I believe, I use them when using a lot of data.
You'd use something like this, add a break point at the start of the loop, and use the locals window to view the array a.
Dim a() As Variant
a = Range("a1:a10").Value
For i = 1 To UBound(a)
Next i

Define array to obtain data from range as "double" var type

I'm fairly new to VBA, so please bear with me.
I want to tell VBA to get an array from a range of cells. The user will paste a column of data into cell C2 so cells below C2 will be populated. The number of cells populated is up to the user.
I am also going to need each of the elements in the array to be taken as doubles as I'm going to make operations with them.
Therefore if the list is
1.2222
2.4444
3.5555
Then I need the array to preserve the decimal points.
How do I do this?
This is what I've got this fur, with no luck:
Set ThisWS = Excel.ActiveWorkbook.Worksheets("Hoja1")
Dim InputValues() As Double 'Define Array
Dim LRow As Long 'Define length of array
With Sheets("Hoja1")
LRow = .Range("C" & .Rows.count).End(xlUp).Row
End With
InputValues = ThisWS.Range("C2:C" & LRow).Value 'Error 13: data type doesn't match
End Sub
Thanks!
Excel.ActiveWorkbook. isn't needed in Excel, it is implied. I didn't need to type cast the cell value CDbl(.Cells(x, "C")).
Sub Example()
Dim InputValues() As Double
Dim lastRow As Long, x As Long
With Worksheets("Hoja1")
lastRow = .Range("C" & .Rows.Count).End(xlUp).Row
ReDim InputValues(lastRow - 2)
For x = 2 To .Range("C" & .Rows.Count).End(xlUp).Row
InputValues(x - 2) = CDbl(.Cells(x, "C"))
Next
End With
End Sub
This example is more efficient but won't make a noticeable difference unless you are working with a very large amount of data.
Sub Example2()
Dim InputValues() As Double, vInputValues As Variant
Dim x As Long
With Worksheets("Hoja1")
vInputValues = .Range("C2", .Range("C" & .Rows.Count).End(xlUp)).Value2
ReDim InputValues(UBound(vInputValues) - 1)
For x = 1 To UBound(vInputValues)
InputValues(x - 1) = CDbl(vInputValues(x, 1))
Next
End With
End Sub
Set ThisWS = Excel.ActiveWorkbook.Worksheets("Hoja1")
Dim CurRow As Long
Dim LRow As Long 'Define length of array
LRow = ThisWS.Range("C" & Rows.count).End(xlUp).Row
Dim InputValues(1 to LRow - 1) As Double 'Define Array
For CurRow = 2 to LRow
InputValues(CurRow - 1) = ThisWS.Range("C" & CurRow).Value
Next CurRow
End Sub
you can simply go like follows
Option Explicit
Sub main()
Dim InputValues As Variant 'Define Array
With Excel.ActiveWorkbook.Worksheets("Hoja1") ' refer to wanted worksheet
InputValues = .Range("C2", .Cells(.Rows.Count, 3).End(xlUp)).value 'fill array with values in column "C" cells from row 2 down to last non emtpy one
End With
End Sub
should you ever need to handle array values as of Double type, then you can use CDbl() function
In VBA you can assign .Value and .Value2 arrays only to a Variant
As a side note if the range is formated as table, you can just do something like
Dim InputValues() ' As Variant by default
InputValues = [transpose(Hoja1!Table1[Column3])] ' Variant(1 to number of rows in Table1)

Put range into an array for more efficient loops

I have a bunch of loops in a function and they loop over an array that I defined as a dynamic range. Below is some pseudocode of what I want to achieve
Dim myArray As Range
myArray(x, j) 'So the array loops through each column and then each row in that column
For i = 1 to lastRow
If myArray(x, j).Value = myArray(x, i).Value Then
'Do something
I have a bunch of these loops and it's super slow with datasets of 100+ rows. Basically everywhere where I have myArray defined as a Range, I want to change it to a Variant so I can a) loop over the array and b) use the array to check if values are the same instead of checking the range against a range, which is probably the root cause of the performance issues when there are 200 rows * 500 columns
Edit
How can I convert a dynamically defined range into an array?
Do I need to do something like this?
lastRow = UBound(myArray, 1)
lastColumn = UBound(myArray, 2)
And then
If myArray(x, j) = myArray(x, i) Then
'Do something
To load a range into an array:
Dim RngArr() as Variant
RngArr = WorkSheets("Sheet1").Range("A1:D4").Value
This will create and array that is 4 x 4.
To make the range dynamic
Dim RngArr() as Variant
Dim lastrow as Long
Dim lastColumn as Long
lastrow = 10
lastColumn = 10
With WorkSheets("Sheet1")
RngArr = .Range(.Cells(1,1),.Cells(lastrow,lastColumn)).Value
End With
When loading the array this way the lower bound of both dimensions is 1 and not 0 as it would be otherwise.
To iterate through the array:
Dim i as long, j as long
For i = lbound(RngArr, 1) to Ubound(RngArr, 1)
For j = lbound(RngArr, 2) to Ubound(RngArr, 2)
'Do something with RngArr(i,j)
Next j
Next i
The second criteria of the lbound and ubound is the dimension.