I have the following equation:
=EXP(FORECAST(LN($A4), LN(OFFSET(INDIRECT($B$1 & "!B8"), MATCH($A4, INDIRECT($B$1 & "!A8:A308"), 1) - 1, COLUMN() - COLUMN($B4), 2)), LN(OFFSET(INDIRECT($B$1 & "!a8"), MATCH($A4, INDIRECT($B$1 & "!A8:A308"), 1) - 1, 0, 2))))
The calculation is performing linear interpolation in log-log space. In the evaluation of this portion:
INDIRECT($B$1 & "!A8:A308"), 1) - 1, COLUMN() - COLUMN($B4), 2)
The column difference (COLUMN() - COLUMN($B4)) results in an array (e.g., {0}). This causes a value error for the MATCH() function. If I run the INDIRECT(...) call above, then the column difference doesn't result in an array. My current solution is wrap the column difference with LARGE(..., 1), which effectively flattens the array.
The question is why does the column difference result in array, and is there a better way to deal with this?
Found it! Microsoft Office Reference says this about the COLUMN function: "If the reference argument is omitted ... the COLUMN function returns the column numbers of reference as a horizontal array."
That's the explanation. The solution, then, is to use COLUMN(A1) instead of COLUMN().
Related
I am comparing two corresponding columns of data. In the first row, it passes because variable A and variable B are in column 2. It does not have to contain the variable starting with C, but it can only be any of the letters contained in cell 1:1. For row three, it does not pass because variable starting with A is not an option within cell 3:1. What conditional formatting would I do for a large data set in Excel 2013. That version of excel is all I have available.
If you had excel 365, it would be much easier. Using 2013, you either have to use FILTERXML to isolate the variables, or hard-code for a certain number of variables. Hard-coding gets long very fast.
I can't check in excel 2013, but it would probably look something like this:
=SUM(--ISERROR(
VLOOKUP(
FILTERXML("<x><y>"&SUBSTITUTE(B1,CHAR(10),"</y><y>")&"</y></x>","//y"),
FILTERXML("<x><y>"&SUBSTITUTE(A1,CHAR(10),"</y><y>")&"</y></x>","//y"),
1,FALSE)
))>0
Where the first FILTERXML creates an array of variables from cell B1, the second from cell A1. VLOOKUP finds matches for items in column B to items in column A, and SUM(--ISERROR(... counts how many non-matches are found.
For excel 365 it could look like this:
=LET(
Txt_1, $A1,
Txt_2, $B1,
Delim, CHAR(10),
SeqChar1, SEQUENCE(LEN(Txt_1)),
CharArr1, MID(Txt_1, SeqChar1, 1),
CharVarStartArr1, FILTER(SeqChar1, IF((CharArr1 = Delim) + (SeqChar1 = 1), TRUE, FALSE)),
CharVarLenArr1, FILTER(SeqChar1, IF((CharArr1 = Delim) + (SeqChar1 = LEN(Txt_1)), TRUE, FALSE)) - CharVarStartArr1 + 1,
VarArr1, SUBSTITUTE(MID(Txt_1, CharVarStartArr1, CharVarLenArr1), Delim, ""),
SeqChar2, SEQUENCE(LEN(Txt_2)),
CharArr2, MID(Txt_2, SeqChar2, 1),
CharVarStartArr2, FILTER(SeqChar2, IF((CharArr2 = Delim) + (SeqChar2 = 1), TRUE, FALSE)),
CharVarLenArr2, FILTER(SeqChar2, IF((CharArr2 = Delim) + (SeqChar2 = LEN(Txt_2)), TRUE, FALSE)) - CharVarStartArr2 + 1,
VarArr2, SUBSTITUTE(MID(Txt_2, CharVarStartArr2, CharVarLenArr2), Delim, ""),
OR(IF(ISERROR(XLOOKUP(VarArr2, VarArr1, VarArr1, NA())), TRUE, FALSE))
)
SeqChar1 creates an array of index locations for each string character using SEQUENCE.
CharArr1 creates an array of (single) characters. I'm using MID with array SeqChar1 to output the results as an array.
CharVarStartArr1 is an array with the starting point of each variable. This is done by getting rid of every position that doesn't correspond to a delim character. I also included the first character or we would be missing the first variable.
CharVarLenArr2 finds the approximate string length by subtracting the starting index from the index of the next variable. I included the index of the last string character to get the length for the last variable.
VarArr1 is an array containing each variable. Every variable but the first one also contains the delim character, so I remove them with SUBSTITUTE.
I need to perform a quite a lot of lookups with wildcards on the worksheet using a macro (mainly lookup for value & returning the value from another column though with proper adjustment it can be also just looking for a value with wildcard, and some lookups only as checks if the value exists in the dataset). My data can't be sorted and all the lookups are within a loop A or loops within loop A; wildcards are included mostly for condition "string begins with...". I often have to find a value in one row and find corresponding value in row N rows below or above.
I have a working code, but I wonder if it can be done faster. #response to comment about posting it on Code Review (sorry, I cannot comment yet :)) - preparation the whole code to posting would take a bit too much time for me, confidentiality etc, so I prefer to treat it as a general question to be worked on this example.
Example data (I can add more columns, if I need any helper column):
Example Data picture at Imgur
Assume 100 000 rows (max xPagesCount = 1000, typically around 400; all values for certain xPage is in one block). Due to a lot of possible rows with additional data I can't simply find one value and add numbers to the found row to find the other values by their position.
Example lookups to perform while looping through consecutive xPages (so, for each given xPage):
value in row just below row with "RESTRICTIONS:" text
find name (which is always given with height (column C) = 35)
find RSW number (which can be in several rows depending on page content, but always below name)
find all rows starting with the same four digits as RSW, in two formats: DDDD.LLL.DD and DDDD.DDDDD.DD (L letter, D digit) (I use internal loop here)
check if there is a text "MASTER" (or "MASTER " etc.)
find all values between values "DOCUMENTS:" and "OPTIONS:", which quantity can be from 1 to 50 (I use internal loop here)
I was wondering, what is the fastest way to do such lookups?
What I tried:
using a dictionary on all dataset (keys in column A or C with, values
col.D) but as dictionary can't work on wildcards, I had to add ifs
for not finding a key to perform additional Application.Match
lookup... and then realized it mostly worked on these Match lookups
and not sure I even need a dictionary. I also have duplicate values
within a page and dictionary was getting only first value, regardless
their position (for example, several attachments could have value 1).
The main use remained dict.exists("MASTER") but when I removed
dictionary and changed it to IsError(Application.Match(...)) the code
worked slightly faster.
Application.Match in whole range, typical example: Application.Match(xPage & "4???.*", sh.Range("A1:A" & LastRow), 0)
in few places I use If xValue Like "????.???.??" Then construction
I have dictionary lookups with ifs redirecting to Application.Match:
xValue = dict(xPage & "ATH.416")
If dict(xPage & "ATH.416") = "" Then xValue = Application.Match("ATH.*", Sheets(1).Range("D:D"), 0)
What I consider, but not sure it's worth the effort:
altering the code that at the beginning of the iteration I find the first and the last row for xPage, and then each later check is performed in this range
xStartPage = sh.Range("D" & Application.Match(xPage, sh.Range("A1:A" & LastRow), 0))
'or, I guess better:
xStartPage = xEndPage + 1
If xPage = xPagesCount Then
xEndPage = LastRow
Else
xEndPage = sh.Range("D" & Application.Match(xPage + 1, sh.Range("A1:A" & LastRow), 0) - 1)
End If
xValue = sh.Range("D" & Application.Match("4???.*", sh.Range("D" & xStartPage & ":D" & xEndPage), 0)).Value
I am using excel's aggregate (small) function to find the smallest value for each name that appears in a column. The issue is that the formula below simply returns 0s everywhere there is a value in B.
The formula I am using is
=IF($B2<>"", AGGREGATE(15,7, ($B:$B)*($A2=$A:$A)*($B2<>""), 1), "")
where B contains the data I want the smallest value from and A contains identifying strings.
I appreciate any help you can lend!
You want to divide by the criteria:
=IF($B2<>"", AGGREGATE(15,7, ($B:$B)/(($A2=$A:$A)*($B:$B<>"")), 1), "")
Whenever ($A2=$A:$A) or ($B2<>"") is FALSE it will return 0 and anything multiplied by 0 is 0 and as such the smallest value is 0.
By dividing by the criteria we throw an #DIV/0 error which the 7 in the AGGREGATE's second criterion forces the formula to ignore and as such we only get the smallest of what returns TRUE or 1 in both Boolean. 1*1=1.
But one more thing. AGGREGATE is an array type formula so limiting the to only the data will speed it up.
=IF($B2<>"", AGGREGATE(15,7, ($B$1:INDEX($B:$B,MATCH("zzz",$A:$A)))/(($A2=$A$1:INDEX($A:$A,MATCH("zzz",$A:$A)))*($B$1:INDEX($B:$B,MATCH("zzz",$A:$A))<>"")), 1), "")
As per your comment:
=IF($B2 = AGGREGATE(15,7, ($B:$B)/(($A2=$A:$A)*($B:$B<>"")), 1),AGGREGATE(15,7, ($B:$B)/(($A2=$A:$A)*($B:$B<>"")), 1), "")
I am looking to make a list of heading type 1, sub-heading type 2 and sub-sub-heading type 3, and each subsequent instance of a heading increments in excel. e.g.
Outcome 1
Output 1.1
Activity 1.1.1
Activity 1.1.2
Output 1.2
Activity 1.2.1
Activity 1.2.2
Activity 1.2.3
Outcome 2
Output 2.1
Activity 2.1.1
etc
Here is my formula - getting to be a complicated nested IF statement:
IF([#Column1]="","",
IF([#Column1]="Outcome", "Outcome " & COUNTIF(tbOOA[[#Headers],[Column1]]:[#Column1], [#Column1]),
IF([#Column1]="Output","Output "& COUNTIF(tbOOA[[#Headers],[Column1]]:[#Column1],"Outcome") ***&"."&*** COUNTIF(tbOOA[[#Headers],[Column1]]:[#Column1],[#Column1]),
"Activity " & "serious help")))
In Column 1, choose from a list of 'Outcome', 'Output', or 'Activity'.
In column 2, calculate the appropriate number, e.g. Output 1.2
If the row is empty, then nothing. - Fine
If it is "Outcome", count from the header until current row for the number of instances of "Outcome". - Fine
Else if it is "Output", count the number of "Outcome"s there are. - Fine
This is where it falls apart. Trying to calculate the number after the "." (bold and italic)
I need to count the # of instances of "Output", but then this has to reset to 1 each time there is a new 'Outcome'.
The logic I'm trying to follow is:
(# of "Outputs" from the table header until the current row) minus
(# of "Outputs" from the table header until the last instance of "Outcome")
I've tried several attempts at calculating row number, but everything has been problematic.
The logic is the same for activities, though will complicate the formula even more and I haven't bothered to start on that until I can get level 2 sorted.
Does anyone know of a similar problem/solution?
If you are open to using hidden helper columns, the formulas become much more manageable. Use Column A to hold your "Outcome", "Output", and "Activity" data.
Then use column B to deal with Outcome numbers, column C to deal with Output numbers, and column D to deal with Activity numbers. Merge the final results together in Column E.
In B1, C1, and D1, manually write in the first values (1, 0, and 0).
Then, fill down starting from B2 with the following:
=IF(A2="Outcome",B1+1,B1)
This works by incrementing only if you have found your next Outcome.
Fill down from C2 with the following:
=IF(A2="Outcome",0,IF(A2="Output",C1+1,C1))
This works by incrementing only if you have found your next Output. It resets to 0 if you have a new Outcome.
Then fill down from D2 with
=IF(OR(A3="Outcome",A3="Output"),0,IF(A3="Activity",D2+1,D2))
It's very similar to the prior formula, but resets on an Outcome or an Output.
Finally, in D4, merge it all together with
=B1&IF(C1>0,"."&C1&IF(D1>0,"."&D1,""),"")
& is a string concatenate operation. By checking if the inner values are 0, we only concatenate . and the next number if the next number is non-zero.
I had a similar problem, where I wanted to create the multi-level heading numbers based on the indentation of list of texts. So the numbers must be generated automatically with a user-defined formula (UDF) like below:
For this to work, you must type ="1" in cell A2. The same formula in A3 (below) must be copied down to A4:A14.
=NextLevelNum(A2;IndentLevel(B3))
Function IndentLevel I took from https://professor-excel.com/how-to-return-the-indentation-of-a-cell-in-excel/
Function NextLevelNum I did myself. All code below.
Option Explicit
Public Function IndentLevel(Ref As Range) As Long
Application.Volatile
IndentLevel = Ref.IndentLevel
End Function
Public Function NextLevelNum(prevNumRef As Range, level As Integer) As String
Dim prevNum As String
Dim nums() As String
Dim prevLevel As Integer
prevNum = prevNumRef.Value
nums = Split(prevNum, ".")
prevLevel = UBound(nums) + 1
' Ensure 1 <= level <= prevLevel +1
level = WorksheetFunction.Max(level, 1)
level = WorksheetFunction.Min(level, prevLevel + 1)
ReDim Preserve nums(0 To level - 1)
If level = prevLevel + 1 Then
nums(level - 1) = "1"
Else
nums(level - 1) = CStr(CInt(nums(level - 1)) + 1)
End If
NextLevelNum = Join(nums, ".")
End Function
The reason I'm doing this is so I don't have to use matrix multiplication and can use sumproduct instead. I can do it with matrix multiplication, but I'd rather not have the overhead of explaining what that is and how to do it to my colleagues. I need to apply a scalar to two arrays that I'm applying a sumproduct against. The problem is that since the scalar is length 1, it breaks the sumproduct formula.
Here is what the formula looks like:
=sumproduct(1/sumif(conditionals), array 2, array 3)
What I would like to do is scale the first array, which is my scalar, based on the size of the other 2 arrays. I have tried this, which I figured wouldn't work, but it should get the idea of what I'm trying to accomplish across:
=sumproduct(rept(1/sumif(conditionals),count(array 2)), array 2, array 3)
The problem here is that the rept function returns a string, which I'm unable to use round() or any other formulas I can think of to make into an 1xN array of that scalar. I should note I'm not trying to cast the variable necessarily, but if there is a way to do that it would likely solve the problem.
Any ideas?
Have you tried?
=sumproduct((1/sumif(conditionals))*'array 2'*'array 3')
In addition, wouldn't that be mathematically the same as
=(1/sumif(conditionals))*sumproduct('array 2','array 3')
The following formula should solve your problem:
=SUMPRODUCT(
ROW(INDIRECT("1:" & COUNT(array 2))) ^ 0 / SUMIF(conditionals),
array 2,
array 3
)
This works as follows:
First obtain the number of elements of array 2:
COUNT(array 2)
Then convert it into a 'virtual' range reference (only rows):
INDIRECT("1:" & COUNT(array 2))
Then determine the row numbers from this virtual range reference, the result of which is {1,2,...,COUNT}:
ROW(INDIRECT("1:" & COUNT(array 2)))
Finally, convert the row number by raising to the power 0:
ROW(INDIRECT("1:" & COUNT(array 2))) ^ 0
This gives you the desired array {1,1,...,1} with COUNT elements, which can be used in the SUMPRODUCT() function.