Group two non-adjacent columns into 2d array for Excel VBA Script - arrays

I think this question might be related to Ms Excel -> 2 columns into a 2 dimensional array but I can't quite make the connection.
I have a VBA script for filling missing missing data. I select two adjacent columns, and it finds any gaps in the second column and linearly interpolates based on (possibly irregular) spacing in the first column. For instance, I could use it on this data:
1 7
2 14
3 21
5 35
5.1
6 42
7
8
9 45
to get this output
1 7
2 14
3 21
5 35
5.1 35.7 <---1/10th the way between 35&42
6 42
7 43 <-- 1/3 the way between 42 & 45
8 44 <-- 2/3 the way between 42 & 45
9 45
This is very useful for me.
My trouble is that it only works on contiguous columns. I would like to be able to select two columns that are not adjacent to each other and have it work the same way. My code starts out like this:
Dim addr As String
addr = Selection.Address
Dim nR As Long
Dim nC As Long
'Reads Selected Cells' Row and Column Information
nR = Range(addr).Rows.Count
nC = Range(addr).Columns.Count
When I run this with contiguous columns selected, addr shows up in the Locals window with a value like "$A$2:$B$8" and nC = 2
When I run this with non-contiguous columns selected, addr shows up in the Locals window with a value like "$A$2:$A$8,$C$2:$C$8" and nC = 1.
Later on in the script, I collect the values in each column into an array. Here's how I deal with the second column, for example:
'Creates a Column 2 (col1) array, determines cells needed to interpolate for, changes font to bold and red, and reads its values
Dim col2() As Double
ReDim col2(0 To nR + 1)
i = 1
Do Until i > nR
If IsEmpty(Selection(i, 2)) Or Selection(i, 2) = 0 Or Selection(i, 2) = -901 Then
Selection(i, 2).Font.Bold = True
Selection(i, 2).Font.Color = RGB(255, 69, 0)
col2(i) = 9999999
Else
col2(i) = Selection(i, 2)
End If
i = i + 1
Loop
This is also busted, because even if my selection is "$A$2:$A$8,$C$2:$C$8" VBA will treat Selection(1,2) as a reference to $B$2, not the desired $C$2.
Anyone have a suggestion for how I can get VBA to treat non-contiguous selection the way it treats contiguous?

You're dealing with "disjoint ranges." Use the Areas collection, e.g., as described here. The first column should be in Selection.Areas(1) and the second column should be in Selection.Areas(2).

Related

Extracting data with Vlookup and checking values with Arrayformula

I need to extract data from one tab (extracted data) to another tab and validate the data in the following way:
if 0% assign 3
if from 0 till -10% assign 2
if from -10% and more assign 1
if from 0% till 10% assign 4
if from 10% and more assign 5
here is the link to the file https://docs.google.com/spreadsheets/d/1f8SFi2hNP6Anav7G7BYWyK-fasPk1pT1A2HFJblT-FI/edit?usp=sharing
I suggest you use two vlookups.
If you have a tab called 'Ranges' with the following two columns:
Percentage Result
-1000% 1
-10% 2
0% 3
10% 4
11% 5
Then the formula in cell B1 on the 'calculations' tab would be something like:
=arrayformula({"Con Potential";iferror(vlookup(vlookup(A2:A,'Extracted data'!A:D,4,0),Ranges!A:B,2,1),)})
Delete all data below cell B1 for the arrayformula to work correctly.
The second vlookup references col D on the 'Extracted data' tab because that is the percentage I think you are comparing? If not, alter 4 in the vlookup to another column.
If it helps, please see:
https://stackoverflow.com/help/someone-answers
NB: In place of Ranges!A:B you could use a fixed array:
=arrayformula({"Con Potential";iferror(vlookup(vlookup(A2:A,'Extracted data'!A:D,4,0),{-10,1;-0.1,2;0,3;0.1,4;0.11,5},2,1),)})
If you want to temporarily see the fixed array in case you want to edit any values, place this in a cell somewhere out of the way:
={-10,1;-0.1,2;0,3;0.1,4;0.11,5}
, is used to bump to a new column, ; is used as a return.
Relevance
Looking at 'Relevance' lookup from 'Position Delta' and this table in your sheet:
Since a 'position delta' value of 10 cannot both have a relevance of 5 and 4, I've made the assumption that 10 gets 5. If that is incorrect, then I'll adjust the boundaries.
Add this to cell C1 on the 'calculations' tab (clearing all cells below):
=arrayformula({"Relevance";iferror(vlookup(vlookup(calculations!A2:A,'Extracted data'!A:D,3,0),{0,5;11,4;21,3;31,2;41,1;51,0},2,1),)})
The fixed array {0,5;11,4;21,3;31,2;41,1;51,0} has these values:
0 5
11 4
21 3
31 2
41 1
51 0
If you need to change the boundaries so 10 is a 4, not 5, then change the vlookup to use this fixed range {0,5;10,4;20,3;30,2;40,1;50,0}:
0 5
10 4
20 3
30 2
40 1
50 0
vlookup is incremental and anything up to 11 will get 5, then 11 to 20 will get 4, 21 to 30 will get 3 and so on.
,1) in the vlookup at the far right gets the nearest value match until 'position delta' has reached the next boundary.

Looping through column in a multidimensional array

I have a multidimensional array with a layout as set out below:
Banana 10 20 30 40
Coconut 5 10 2 4
Apple 3 4 5 6
I want to loop through a specific column range in a worksheet to check if the values are either 'Banana', 'Coconut' or 'Apple'. When the cell value equate to a value in the first column of my array, I want to then output the array values next to that specific identifier. So for instance I want the output to be as below:
Shark
Banana 10 20 30 40
Pear
Apple 3 4 5 6
I understand that I need to loop through each cell in my range and then evaluate if the cell is equal to the values in the first column of the array. However, I am not sure how to do this. Typically I just use the setup below but I would like to understand how I can create a better solution in this case where I only want to loop through the first column in the array.
For Each cell In ws.Range("OUTPUT")
For y = LBound(arr, 2) To UBound(arr, 2)
If cell.Value = y Then
For m = 1 To x
ws.Cells(cell.Row, n + 1) = arr(n, m)
Next m
n = n + 1
End If
Next y
Next cell

What to use as logical function other than If in excel

I have below set of data.
LLimit ULimit Col C
1 3 a
3 5 b
5 11 c
11 15 d
15 17 e
17 20 f
in col D if i enter 3.5 i need result in col E as "b" (corrosponding value to lowerlimit and upper limit). I have used If or statement, However is there a way i can do this using Index, Match or array. I tried and it works absolutely fine with the limit numbers (like 5, 11, 17, 3 etc) but not working with between numbers like 14 (between 11 and 14).
Below is what i used
{=INDEX(F5:F10,MATCH(1,(((D5:D10)>=H4)*((E5:E10)>=H4))*1,0))}
Was trying to attach workbook, but don't know how to do it.
Try this
Assuming D2=3.5 then
In E2
=INDEX(C2:C7,SUMPRODUCT((A2:A7<=D2)*(B2:B7>=D2)*(ROW(C2:C7)-ROW(C2)+1)))
Edit:
To exclude the lower boundary, try this
=INDEX(C2:C7,SUMPRODUCT((A2:A7<D2)*(B2:B7>=D2)*(ROW(C2:C7)-ROW(C2)+1)))
Use Vlookup as follow:
=VLOOKUP(D2,$A$2:$C$7,3)
$A$2:$C$7 is your initial table LLimit ULimit Col C
Vlookup will look in the first column for the value <= D2 and return the corresponding value in column 3
Update
For non sorted column A your Formula needs a modification:
={INDEX($C$2:$C$7,MATCH(1,($A$2:$A$7<=D2)*($B$2:$B$7>=D2),0),1)}
You wrote the same condition >= for both limit
{=INDEX($F$5:$F$10,MATCH(1,((($D$5:$D$10)<=H4)*(($E$5:$E$10)>=H4))*1,0))}

Merge multiple arrays of unique occurrences

I want to merge multiple arrays of unique occurrences to a single array. To get the arrays in the first place I use this code, where image series is a slice from a tiff image imported using imread:
a = unique(img_series);
occu = [a,histc(img_series(:),a)];
I do that multiple times, because the tiff image I'm using has multiple hundred images stacked, which my RAM will not support to import at once. So each 'occu' looks something like this (first number is the unique value, second number is the number of occurrences):
occu1 occu2 .....
0 1 1 2
12 1 10 1
14 1 12 1
15 1 14 2
.. .. .. .. .....
Now I want to merge them all together, or better merge them in each iteration, when I'm reading another stacked image.
The merged results should be a 2D matrix similar to the one above. The number of occurrences of the same values should be added to one another, as this is the whole point of counting them. So the result of the above example should be this:
occu_total
0 1
1 2
10 1
12 2
14 3
15 1
.. ..
I found the join command, but that one does not seem to work here. I guess I could do it the long way of searching the matching number and add the occurrences together and so on, but there must be a quicker way of doing it.
A = [0 1;12 1; 14 1;15 1];B = [1 2;10 1;12 1;14 2];
tmp = [A;B]; %// merge arrays into a single one
tmp(:,1) = tmp(:,1)+1;%// remove zero occurrences by adding 1 to everything
C = accumarray(tmp(:,1),tmp(:,2)); %// add occurrences all up
D = [1:numel(C)].'; %// create numbered array
E = [D C];
E((C==0),:)=[]; %// get output
E(:,1) = E(:,1)-1;%// subtract the 1 again
E =
0 1
1 2
10 1
12 2
14 3
15 1
Job for accumarray. This takes the first argument as your dictionary key, and adds the values of the each key together. The addition and subtraction of 1 is done because 0 cannot be an index in MATLAB. To circumvent this (assuming you have no negative numbers), you can simply add 1 and remove that afterwards, shifting all your indices to positive integers. If you hit negative numbers, subtract tmp(:,1) = min(tmp(:,1)+1 and add E(:,1) = min(tmp(:,1)-1

Working with arrays in VBA memory and avoiding loops using vectorization

I am versed in MATLAB but find myself working in VBA these days as MATLAB is less accessible to me and I struggle with trying to do stuff in VBA (like vectorization) that I could easily handle in MATLAB.
Lets say I have a data table in excel of the following form:
record startDate endDate count
1 100 103 10
2 98 102 5
3 101 104 4
I would like to do all my processing in memory (avoiding loops) and then output results file that looks like this:
1 2 3 Sum
98 0 5 0 5
99 0 5 0 5
100 10 5 0 15
101 10 5 4 19
102 10 5 4 19
103 10 0 4 14
104 0 0 4 4
Basically, I start with earliest date and loop through the latest date and then check to see if each date is included in the date window for each record and if it is I apply the record count to that day and then sum them up.
I created the included output using a simple worksheet function, but I would like to be able to replicate the process in VBA specifically avoiding looping at least reducing to 1 loop instead of embedded loops.
If I were in MATLAB I would find the logical array that meets a condition, for example:
numDays = 7;
numRecords = 3;
startDate = [100; 98; 101];
endDate = [103; 102; 104];
dateVector = [98; 99; 100; 101; 102; 103; 104];
count = [10; 5; 4];
dateLogic = logical(numDays,numRecords);
for d = 1:numDays
dateLogic(d,:) = dateVector(d) >= startDate(:,1) & dateVector(d) <= endDate(:,1)
end
countMatrix = dateLogix * count';
Sum = sum(countMatrix,2);
This would give me a logical matrix of zeros and ones that I can cross multiply with count vector to get my counts and ultimately my Sum vector. I believe I could even use a bsxfun to remove the loop on days.
Please excuse any potential syntax errors as I do not have access to MATLAB right now.
Anyway, how can I do something similar in VBA. Is there an equivalent colon notation to reference the entire range of columns or rows in an array. I will be applying to large data set so efficiency is of the essence. The more I can do in memory before pasting the better.
Thanks in advance.
Here's one possibility, try with sampe data in A1:A4 of a new workbook.
Sub NewTable()
Set Table = Sheet1.[a2:d4]
With Application
Record = .Transpose(.Index(Table, , 1))
FirstDate = .Transpose(.Index(Table, , 2))
LastDate = .Transpose(.Index(Table, , 3))
Count = .Transpose(.Index(Table, , 4))
Dates = .Evaluate("row(" & .Min(FirstDate) & ":" & .Max(LastDate) & ")")
Values = .PV(, Count, .PV(, .GeStep(Dates, FirstDate), .GeStep(LastDate, Dates)))
Sum = .MMult(Values, .Power(.Transpose(Record), 0))
End With
Sheet1.[F1].Offset(, 1).Resize(, UBound(Values, 2)) = Record
Sheet1.[F2].Resize(UBound(Dates)) = Dates
Sheet1.[G2].Resize(UBound(Values), UBound(Values, 2)) = Values
Sheet1.[G2].Offset(, UBound(Values, 2)).Resize(UBound(Dates)) = Sum
End Sub

Resources