Passing a Four-Dimensional Array to a Chart SeriesCollection using VBA - arrays

I want to know how to pass a single dimension of a four-dimensional array to an XY Scatter Chart using VBA.
[I know that I could do it by laboriously typing out separate arrays for everything, but I was hoping to make my code more compact. The compactness is particularly useful for populating the array, which I have not included below as I have that done already. I also know that if I were using 2D arrays, then I could use Application.Index, but my understanding is that that will not work for a three dimensional array or four dimensional array - if I'm wrong about that, then please let me know. I have looked at numerous queries on several websites, and I can only find answers about 2D arrays using Application.Index - if there is a page I've missed, then please let me know.]
I have an array variable that I have populated:
Dim arrData() As Single
arrData(1 To 2, 1 To 4, 1 To 3, 1 To 56)
arrData contains Single-type data for variables 1 to 56, within a 2x4x3 layout, so 1,344 data points.
I have a chart:
Dim bIOpt As Byte
Dim bPOpt As Byte
For bIOpt = 1 To 4
For bPOpt = 1 To 4
Worksheets("Data").Activate
Range(Cells(1, 1), Cells(2, 2)).Select
Charts.Add
With ActiveChart
.Type = xlXYScatter
.ChartTitle.Delete
With .SeriesCollection(1)
.XValues = ?
.Values = ?
End With
.SeriesCollection.NewSeries
With .SeriesCollection(2)
.XValues = ?
.Values = ?
End With
.SeriesCollection.NewSeries
With .SeriesCollection(3)
.XValues = ?
.Values = ?
End With
End With
Next bPOpt
Next bIOpt
I want each SeriesCollection to contain 56 points from arrData: the XValues will come from the bPOpt version of arrData(2, bPOpt, n, ); the (Y) Values will come from the bIOpt version of arrData(1, bIOpt, n, ).
I do not know how to write the ".XValues = ?" and ".Values = ?" lines so as to achieve my aim.
Thank you for your help.
Chris

You could use whatever looping algorithm you want to generate 1-D arrays for XValues and YValues, then pass these arrays to the chart series:
Dim XVals(1 to 56) As Double, YVals(1 to 56) As Double
' loop to populate XVals, YVals from arrData
' blah blah
' end of loop
With .SeriesCollection(1)
.Values = YVals
.XValues = XVals
End With
Note that there's no advantage in VBA to using Singles in place of Doubles, or Ints in place of Longs.
Note also that Excel's charts are much more reliable when working with data from worksheet cells than with arrays. If you experience problems, you might first dump XVals and YVals into a couple columns, then use this data range as the chart series data.

Related

Finding the average of each column in a multi array in excel vba

I have an array that is
myarray (1 to 37, 1 to 44) as variant
I'm trying to speed up my spreadsheet so rather than using big tables with lots of formulas in I'm turning to arrays, calculating them in memory and then writing the array to the sheet like such.
'Write array to worksheet.
Sheet1.Range("A1").Resize(UBound(myarray, 1), UBound(myarray, 2)).Value = myarray
This is working well except I'm not sure how to write code to perform certain calculations on specific rows or columns of the array. So if I wanted to know the average of all the elements in row 2 for example, how would that be written?
Many thanks in advance.
P.S. I should say I know I can cycle through and perform the average like so but I am more talking about using the various worksheet functions on specific rows or columns, this is what I don't understand how to write.
Thanks but I've sussed it now. I place all the elements of that particular row or column into a new array then simply use the worksheet function command. So in this example I am using functions on each column of the myarray. I'll post this in case it helps anyone else with worksheet functions in arrays.
For i = 1 To 44
Erase Calc1Array()
ReDim Calc1Array(1 To 37)
For j = 1 To 37
Calc1Array(j) = MyArray(j, i)
Next j
Answer1 = Application.WorksheetFunction.Max(Calc1Array)
Answer2 = Application.WorksheetFunction.Average(Calc1Array)
Answer3 = Application.WorksheetFunction.StDev(Calc1Array)
Next i

Filling a 3D array with 2D arrays in VBA

I have a set of data where each item has a 2D array of information corresponding to it. I'd like to create a 3D array where the first dimension is the item name and the second and third dimensions are taken up by the info corresponding to the item.
I can read the data for each item into a 2D array, but I can't figure out how to get the 2D array read into the 3D array.
I know the sizes of all the dimensions so I can create an array of fixed size before I begin the reading and writing process.
I'd like to do this by looping only through the names of the items and not looping through every cell of every 2D array.
It is easy to get the 2D arrays read in to an ArrayList but I want to be able to name the items and be able to read these back in to excel and it seems difficult to do with an ArrayList.
The question is: how do I read a 2D selection from excel into a 3D fixed sized array in VBA?
Here is an example of each approach: array of arrays or Dictionary of arrays. The Dictionary approach is considerably easier than the array of arrays if what you want is keyed lookup of values. There might be merits to the array of arrays in other cases.
This is dummy code with no real purpose but to show a couple things: grabbing a single value and an array of values. I am building a 2D array of values by grabbing some Range data which is easy and 2D. From there I build up the arrays of these values and then put them into the relevant data structure. Then I poke at the data structure to get some values out of it.
Array of Arrays approach is shown first (and outputs on the left of the picture).
Sub ArraysOfArrays()
Dim arrA() As Variant
Dim arrB() As Variant
'wire up a 2-D array
arrA = Range("B2:D4").Value
arrB = Range("F2:H4").Value
Dim arrCombo() As Variant
ReDim arrCombo(2, 1) As Variant
'name and give data
arrCombo(0, 0) = "arrA"
arrCombo(1, 0) = arrA
'add more elements
ReDim Preserve arrCombo(2, 2)
arrCombo(0, 1) = "arrB"
arrCombo(1, 1) = arrB
'output a single result
'cell(2,2) of arrA
Range("B6") = arrCombo(1, 0)(2, 2)
Dim str_search As String
str_search = "arrB"
'iterate through and output arrB to cells
Dim i As Integer
For i = LBound(arrCombo, 1) To UBound(arrCombo, 1)
If arrCombo(0, i) = str_search Then
Range("B8").Resize(3, 3).Value = arrCombo(1, i)
End If
Next i
End Sub
Couple key points here:
You can only expand the array using ReDim. ReDim is very particular that you only change the last dimension of the array when used with Preserve. Since I need one of them to track the number of entries, I do that in the second index which is... unnatural. If you know the size in advance, this painful step is skipped.
My final array is a 2xN array where the 2 contains a name and a YxZ array of data.
In order to find a given array in the mix, you have to iterate through them all.
Dictionary of Arrays is far less code and more elegant. Be sure to add the reference Tools->References in the VBA editor to Microsoft Scripting Runtime.
Sub DictionaryOfArrays()
Dim dict As New Scripting.Dictionary
'wire up a 2-D array
arrA = Range("B2:D4").Value
arrB = Range("F2:H4").Value
dict.Add "arrA", arrA
dict.Add "arrB", arrB
'get a single value
Range("F6") = dict("arrB")(2, 2)
'get a array of values
Range("F8").Resize(3, 3) = dict("arrA")
End Sub
Picture of input data and results
Data to copy if you want it (paste in B1)
a b
1 2 3 10 11 12
4 5 6 13 14 15
7 8 9 16 17 18

How to reference an array and write to another array with more than one column per iteration

I am curious if I can copy multiple columns to a new array from an existing array in one iteration of a loop. Suppose we have the following general example:
Array1 contains 10,000 elements in column1, 10,000 elements in column2, and 10,000 elements in column 3, etc.
Let's say that I want a new array generated off that information, only I want only columns 1 and 2 populated. Can I do this by looping only once with a correctly dimensioned target array? For instance:
'Assume TargetArray has already been ReDimmed to the size of Array1 in the code prior
For i=0 to UBound(Array1)
TargetArray(x,1)= Array1(x,1)
TargetArray(x,2)=Array1(x,2)
Next
So can this be done in one step, or do I have to make a loop for each dimension I want to add to the array. Is there any speed savings by doing two operations per loop as stated above (assuming it works).
Thanks for all of your help!
Have you tried just using Range objects? I just made 100 values in columns A and B, and copy them to F and G. Or are you trying to plug values from the first three columns into an equation to give you values for the new two columns?
Sub CopyRange()
Dim Array1 As Range
Dim Array2 As Range
Set Array1 = Range("A1:B100")
Set Array2 = Range("F1:G100")
Array2.Value = Array1.Value
End Sub
Your example should work as what RubberDuck commented.
It is similar in below example which works at my end.
I can't fit it to comments so I have no choice to post it as answer.
Dim TargetArray ' declared as Variant type, not array of variants
ReDim TargetArray(0 To Ubound(Array1, 0), 0 To 1) ' for 2 columns
For i = 0 To Ubound(Array1, 1)
TargetArray(i, 0) = Array1(i, 0)
TargetArray(i, 1) = Array1(i, 1)
Next
Is this close to what you have? If so, then that should work.

ReDim existing array with a second dimension?

I declared an array in my VBA function that has a dynamic size. As I cannot ReDim the first dimension of a two- or more-dimensional array, can I add a second dimension to a set array?
This is how I dynamically set the size of my array.
Dim nameArray() As String
Dim arrayCount As Long
For i = 1 To 100
ReDim Preserve nameArray(1 to arrayCount)
nameArray(arrayCount) = "Hello World"
arrayCount = arrayCount + 1
Next i
Now I would like to add a second dimension.
ReDim Preserve nameArray(1 To arrayCount, 1 To 5)
doesn't work.
Is there a workaround?
There isn't any built-in way to do this. Just create a new two-dimensional array and transfer the contents of your existing one-dimensional array into the first row of that new array.
This is what this function does:
Function AddDimension(arr() As String, newLBound As Long, NewUBound As Long) As String()
Dim i As Long
Dim arrOut() As String
ReDim arrOut(LBound(arr) To UBound(arr), newLBound To NewUBound)
For i = LBound(arr) To UBound(arr)
arrOut(i, newLBound) = arr(i)
Next i
AddDimension = arrOut
End Function
Example usage:
nameArray = AddDimension(nameArray, 1, 5)
There is one (also works to delete a dimension), but you will have to think in terms of worksheet dimensions...
use transposition
While I highly prefer the previous 'by hand' method from Jean-François Corbett's and I don't like to rely on Excel build-in function (especially this one!), I would just like to clarify another way for future readers coming here:
adding a dimension to a 1d line vector (a row) means transposing it in Excel
Here, nameArray(1 to arrayCount) is a row (index is a column number) and because of it, if you add a dimension it will become a column since 2d arrays are indexed as (row,column). So, you can just do this:
nameArray = Application.Worksheetfunction.Transpose(nameArray) 'transforms the array to nameArray(1 To arrayCount, 1 To 1), so then:
redim preserve nameArray(1 To arrayCount, 1 To 5)
without any other manipulation.
BUT beware of the very confusing Excel's Transpose function (at least it is the case for me!): the advantage here is that it automatically adds a dimension and redimensions the array for you.
It works as expected only because you are using a 'based 1' index '1d array'.
IF this is not the case, all indices will be shifted by 1, (that's how Transpose is build in to be coherent with cells and ranges). That is: if you start with
nameArray(0 to arrayCount)
you will end up with
nameArray(1 to arrayCount + 1, 1 to 5)
with precautions
While I am at it, it may be off-topic and their are many topics about it, but their are other traps in this Transpose function one should never be enough warned about (not to mention it can consume more time and resources than Jean-François Corbett's solution) :
• if you are dealing with '1d column' in an excel 2d array (a column), that is an array:
nameArray(1 to arrayCount, 1 to 1) [*]
and if you make the transpose of it, the column dimension will be "skipped" and the result will be:
nameArray(1 to arrayCount)
It makes sense since you will end up with an Excel row (so why bother with an extra dim?). But I have to say this is not the intuitive behaviour I would expect, which should be more something like nameArray(1 to 1, 1 to arrayCount).
Note that, a contrario, it can be used to delete a dimension and redimension the array automatically from 2d to 1d: redim preserve the last dimension to 1 to 1 and then transpose! This is the resulting array just above.
• But finally all is not lost: suppose you transpose this array of 1 line:
nameArray(0 to 0, 0 to arrayCount)
you correctly get an array of 1 column:
nameArray(1 to arrayCount + 1, 1 to 1)
(well, almost) - and look back at ref [*] now...
Thus, if you are to use this build-in function and if, moreover you need more dimensions or worst, need to compose transpositions, it can become a bit tricky...
For all this or if you simply need to know correct indexing and number of dimensions of your arrays (not only number of columns), I would suggest a very useful function from users John Coleman and Vegard, the post just below.
While all this should appear logical and trivial for people used to work with Excel sheets, it's very not the case when you are more used to matrix manipulations and I think suggesting the use of this Transpose function should come with some precisions.

Vb.Net Sort 3x One Dimensional Arrays

I have 3 one dimensional arrays.
Each contains information that corresponds to the other 2 arrays.
e.g Array 1 contains a customer first name
Array 2 contains a customer last name
Array 3 contains the customer phone number.
This is not my actual example but is easiest to explain.
How do I sort all three arrays so that they are sorted in order by say customer last name.
If Mr Smith is sorted and has moved from position 10 to position 5 in the lastname array, I would expect his phone number and first name to also be in position 5 in the respective arrays.
I am dealing with arrays with 10,000's of items so I would like to avoid looping (my current method) as this is incredibly slow.
Hoping to use the array.sort methods.
Can someone help me?
Ok - So I have tried to use a new data Type but am still at a loss how I can instantly filter using this. Below is my sample code which has a couple of issues. If someone can resolve - it would love to learn how you did it.
The purpose of the code is to return an array containing grouped issues.
For simplicity I have assumed in the example that each constant found is an issue.
If an issue is found, combine it with other issues found on that same worksheet.
e.g The number 2 is found in both cells A1 and A2 on sheet 1. The array should return A1:A2.
If the issues are found in A1 on sheet 1 and A2 in sheet 2, two seperate array entries would be returned.
Test File and Code Here
Public Type Issues
ws_Sheet As Integer
rng_Range As String
s_Formula As String
s_Combined As String
d_ItemCount As Double
End Type
Sub IssuesFound()
Dim MyIssues() As Issues
Dim i_SheetCount As Integer
Dim s_Formula As String
Dim rng_Range As Range
Dim d_IssueCounter As Double
Dim s_SearchFor As String
Dim a_TempArray() As Issues
Dim d_InsertCounter As Double
d_IssueCounter = -1
' Loop All Sheets Using A Counter Rather Than For Each
For i_SheetCount = 1 To ActiveWorkbook.Sheets.Count
' Loop all Constants On Worksheet
For Each rng_Range In Sheets(i_SheetCount).Cells.SpecialCells(xlCellTypeConstants, 23)
If d_IssueCounter = -1 Then
' First Time and Issue Is Found, Start Recording In An Array
d_IssueCounter = d_IssueCounter + 1
ReDim MyIssues(0)
MyIssues(0).ws_Sheet = i_SheetCount
MyIssues(0).rng_Range = rng_Range.AddressLocal
MyIssues(0).s_Formula = rng_Range.Value
MyIssues(0).s_Combined = i_SheetCount & "#" & rng_Range.Value
MyIssues(0).d_ItemCount = 0
Else
' Going To Look For Issues Found On The Same Sheet with The Same Constant Value
s_SearchFor = i_SheetCount & "#" & rng_Range.Value
' HELP HERE: Need To Ideally Return Whether The Above Search Term Exists In The Array
' Without looping, and I want to return the position in the array if the item is found
a_TempArray = MyIssues 'Filter(MyIssues.s_Combined, s_SearchFor, True, vbTextCompare)
If IsVarArrayEmpty(a_TempArray) = True Then
' New Issue Found - Increase Counter By + 1
d_IssueCounter = d_IssueCounter + 1
' Increase The Array By 1
ReDim Preserve MyIssues(d_IssueCounter)
' Record The Information About The Constant Found. Sheet Number, Constant, Range, and also a combined string for searching and the array position
MyIssues(0).ws_Sheet = i_SheetCount
MyIssues(0).rng_Range = rng_Range.AddressLocal
MyIssues(0).s_Formula = rng_Range.Value
MyIssues(0).s_Combined = i_SheetCount & "#" & rng_Range.Value
MyIssues(0).d_ItemCount = 0
Else
' Get The Array Position Where Other Issues With The Same Worksheet and Constant are Stored
d_InsertCounter = a_TempArray.d_ItemCount
' Add The New Found Constant To The Range Already Containing The Same Constants on This Worksheet
MyIssues(d_InsertCounter).rng_Range = Union(rng_Range, Range(MyIssues(d_InsertCounter).rng_Range)).AddressLocal
End If
End If
Next
Next
End Sub
Function IsVarArrayEmpty(ByRef anArray As Issues)
Dim i As Integer
On Error Resume Next
i = UBound(anArray, 1)
If Err.Number = 0 Then
IsVarArrayEmpty = False
Else
IsVarArrayEmpty = True
End If
End Function
Sample Test File and Code Here
As suggested, you should not be using concurrent arrays at all. You should be defining a type with three properties and then creating a single array or collection of that type.
To answer your question though, there is no way to sort three arrays in concert but there is a way to sort two. What that means is that you can create a copy of the array that you want to use as keys and then use the copy to sort one of the other arrays and the original to sort the other. Check out the documentation for the Array.Copy overload that takes two arrays as arguments.
That said, copying the array and then sorting twice is a big overhead so you may not gain much, if anything, from this method. Better to just do it the right way in the first place, i.e. use a single array of a complex type rather than concurrent arrays of simple types. It's not 1960 any more, after all.

Resources