Excel Formula Optimisation - excel

I am no excel expert and after some research have come up with this formula to look at two sets of the same data from different times. It then displays new entries that are in the latest list of data but not in the old list.
This is my formula:
{=IF(ROWS(L$4:L8)<=(SUMPRODUCT(--ISNA(MATCH($E$1:$E$2500,List1!$E$1:$E$2500,0)))),
INDEX(E$1:E$2500,
SMALL(IF(ISNA(MATCH($E$1:$E$2500&$F$1:$F$2500,List1!$E$1:$E$2500&List1!$F$1:$F$2500,0)),
ROW($F$1:$F$2500)-ROW($F$1)+1),ROWS(L$4:L8))),"")}
Are there any optimisation techniques I could employ to speed up the calculation?
As requested
Some example data(link to a spreadsheet):
https://docs.google.com/file/d/0B186C84TADzrMlpmelJoRHN2TVU/edit?usp=sharing
On this scaled down version its more efficent but on my actual sheet with a lot more data it is slowed.

Well, I was playing around a bit and I think that this works the same, and without the first IF statement:
=IFERROR(INDEX(A$1:A$2500,SMALL(IF(ISNA(MATCH($A$1:$A$2500&$B$1:$B$2500,List1!$A$1:$A$2500&List1!$B$1:$B$2500,0)),ROW($B$1:$B$2500)-ROW($B$1)+1),ROWS(F$2:F2))),"")
That part in your sample data:
ROWS(F$2:F2)<=(SUMPRODUCT(--ISNA(MATCH($A$1:$A$2500,List1!$A$1:$A$2500,0))))
As I understood it, it only sees to it that the row number in which the formula is entered is lower than the number of 'new' items, but it doesn't serve any purpose because when you drag the formula more than required, you still get errors instead of the expected blank. So I thought it could be removed altogether (after trying to substitute it with COUNTA() instead) and use an IFERROR() on the part directly fetching the details.
EDIT: Scratched that out. See barry houdini's comment for the importance of those parts.
Next, you had this:
ROW($B$1:$B$2500)-ROW($B$1)+1
-ROW($B$1)+1 always returns 0, so I didn't find any use to it and removed it altogether.
It's still quite long and takes some time I guess, but I believe it should be faster than previously by a notch :)

A relatively fast solution is to add a multi-cell array formula in a column alongside List 2
{=MATCH($A$1:$A$16,List1!$A$1:$A$11,0)}
and filter the resultant output for #N/A.
(Or see Compare.Lists vs VLOOKUP for my commercial solution)

Array formula is slow. When you have thousands of array formula, it will make the speed very slow. Thus the key will be to avoid any array formula.
The following will be my way to achieve it, using only simple formula. It should be fast enough if you only have 2500 rows.
Column F and H are "Keys", created by concatenating your 2 columns (E and F in your original formula)
Assuming the first line of data is on row 3.
Data:
| A | B | | D | E | F | | H |
| index | final value | | ID | exist in Old? | Key (New) | | Key (Old) |
--------------------------------------------------------------------------------
| 1 | XXX-33 | | 0 | 3 | OOD-06 | | OOC-01 |
| 2 | ZZZ-66 | | 0 | 1 | OOC-01 | | OOC-02 |
| 3 | ZZZ-77 | | 1 | N/A | XXX-33 | | OOD-06 |
| 4 | | | 1 | 4 | OOE-01 | | OOE-01 |
| 5 | | | 1 | 2 | OOC-02 | | OOF-03 |
| 6 | | | 2 | N/A | ZZZ-66 | | |
| 7 | | | 3 | N/A | ZZZ-77 | | |
Column E "exist in Old?": test if the new key (Column F) exists in the old list (Column H)
=MATCH(F3, $H$3:$H$2500, 0)
Column D "ID": to increment by one whenever a new item is found
=IF(ISNA(E3), 1, 0)+IF(ISNUMBER(D2), D2, 0)
the 2nd part of ISNUMBER is just for the first row, where just using D2 can cause an error
Column A "index": just a plain series starting from 1 (until the length of new list Column F)
Column B "final value": to find the new key by matching column A to Column D.
=IF(A3>MAX($D$3:$D$2500), "", INDEX($F$3:$F$2500, MATCH(A3, $D$3:$D$2500, 0))
This column B will be the list you want.
If it is still too slow, there exists some dirty tricks to speed up the calculation, e.g. by utilizing a sorted list with MATCH( , , 1) instead of MATCH( , , 0).

Related

Excel Formula INDEX+AGREGATE+ORDER by another column

I have an excel formula that I need help with, since I'm not that savvy on excel.
The formula is the following:
=IFERROR(INDEX(Liste!$C$2:$N$10000,AGGREGATE(15,6,(ROW(Liste!$A$2:$A$10000)-ROW(Liste!$A$2)+1)/ISNUMBER(SEARCH("BOX"," " & Liste!$B$2:$B$10000 & " ")),ROWS($A$4:$A4)),COLUMN()-1),"")
This formula looks on another sheet (Liste) for lines containing the word BOX on column B and show them on "ROWS($D$4:$D4)" (in this case) starting from Column "C".
I'm not even sure if that formula orders the results on column "B" since they're all the same with the word "BOX".
It works prefectly, the only think I can't achieve (and I've tried diferent combinations) is to change that code to make it show the results in order according to column "C" (not column B).
Sample Data:
Sheet "Liste"
|-----------|----------|-----------|
| B | C | D |
|-----------|----------|-----------|
| NOTBOX | 5 | SAMPLE |
| BOX | 3 | SAMPLE |
| BOX | 1 | SAMPLE |
| BOX | 2 | SAMPLE |
| NOTBOX | 4 | SAMPLE |
|-----------|----------|-----------|
Current Result:
|-----------|----------|
| A | B |
|-----------|----------|
| 3 | SAMPLE |
| 1 | SAMPLE |
| 2 | SAMPLE |
|-----------|----------|
Desired Result:
|-----------|----------|
| A | B |
|-----------|----------|
| 1 | SAMPLE |
| 2 | SAMPLE |
| 3 | SAMPLE |
|-----------|----------|
Can someone give me a hand?
One thing I forgot to state is that I need to drag this formula around (rows and columns) so it's important that I can drag it on a large zone (if lines are added in the future) and that it ignores the errors (blank).
Thank you in advance
Here is an example on how you could do this:
Formula in F2:
=SMALL(IF($A$2:$A$6=$E$2,$B$2:$B$6,""),ROW(A1))
Or in French
=PETITE.VALEUR(SI($A$2:$A$6=$E$2;$B$2:$B$6;"");LIGNE(A1))
Formula in G2:
=INDEX($C$2:$C$6,MATCH(F2,$B$2:$B$6,0))
Or in French
=INDEX($C$2:$C$6;EQUIV(F2;$B$2:$B$6;0))
Note: The first formula is an array formula and need to be confirmed through CtrlShiftEnter.
Drag them both down and include an IFERROR if you drag these formula's down and you don't want to see errors once there are no more hits.

Excel: How to sum a column to date from a specified date where date ranges are involved

I'm sure i have seen this done with a short formula but i am struggling to remember how to do it.
I am trying to find where a date falls in an interval and sum another column, from that point in the interval, to the end of all specified date intervals.
So, in the image below the intervals are in columns D and E and the date to find is in cell F1 i.e. 12/12/2016.
I want to find where 12/12/2016 falls within the ranges and sum column F accordingly i.e. 12/12/2016 - 13/12/2016 (2 days) and then all intervals after will be 2 + 6+2+1+3 = 14. I am returning this result in cell J14.
I have used the idea of histograms to calculate this currently, but the formula is large and unwieldy, and i just know i have seen a similar question, somewhere on SO but can't find it, that does this with SUMPRODUCT and OFFSET only. I guess FREQUENCY could also be used.
So what i have currently is:
=SUMPRODUCT(OFFSET(F6,MATCH(TRUE,OFFSET(E6,0,0,COUNT(E6:E1048576),)>F1,0)-1,0,COUNTA(F6:F1048576),))-(F1-OFFSET(D6,MATCH(TRUE,OFFSET(E6,0,0,COUNT(E6:E1048576),)>F1,0)-1,,,))
Where, if i broke it down into stages, i find which bucket (range) has the target value:
={MATCH(TRUE,OFFSET(E6,0,0,COUNT(E6:E1048576),)>F1,0)}
I calculate the distance into the range with:
=F1-OFFSET(D6,H13-1,,,)
And i sum from this point until the end of the range with:
=SUMPRODUCT(OFFSET(F6,H13-1,0,COUNTA(F6:F1048576),))-I13
So, can anyone help me with a shorter more efficient formula please?
Data:
| Start of measurement | 12/12/2016 | |
|----------------------|------------|----------------|
| | | |
| | | |
| | | |
| From | To | Number of days |
| 13/11/2016 | 17/11/2016 | 5 |
| 10/12/2016 | 13/12/2016 | 4 |
| 03/02/2017 | 08/02/2017 | 6 |
| 06/12/2017 | 07/12/2017 | 2 |
| 09/12/2017 | 09/12/2017 | 1 |
| 12/12/2017 | 14/12/2017 | 3 |
This should work for you:
=SUMIF(E6:E11,">="&F1,F6:F11)-F1+INDEX(D6:D11,MATCH(F1,D6:D11))
How about this?
=SUMIFS(F6:F11,D6:D11,">="&F1)+MINIFS(E6:E11,E6:E11,">="&F1)-F1+1
The SUMIFS() gives the 6+2+1+3 part and the MINIFS() - F1 + 1 gives the 2 part.
Note that the ___IFS() functions are more recent and not available in older versions of Excel.

Spreadsheet Formula to Sum Values Over A if B is not in a List of Values

I have a table that looks the following:
| A | B | C |
| 40 | 1 | 1 |
| 180 | 2 | 2 |
| 34 | 1 |
| 2345 | 3 |
| 23 | 1 |
| 1 | 2 |
| 4354 | 3 |
| 2 | 2 |
| 343 | 4 |
| 2 | 2 |
| 45 | 1 |
| 23 | 1 |
| 4556 | 3 |
I want to get the sum of all fields in A where B is neither 1 nor 2 or any other value from colum C. This column contains the values of B where values from A should not be considered for the sum.
I do not know which values B might contain, those values are random and could grow larger, I just wanted to make the example small. My current solution is
{=SUMIF(B1:B13,C1:C2,A1:A13)}
so i can set the lines that should be excluded from the sum in column C. Unfortunately, the current solution does not solve my problem but something different -- it sums up the corresponding entries by value in C. My preferred solution would look something like
=SUMIF(B1:B13,"<>{1, 2}",A1:A13)
=SUMIF(B1:B13,"<>"&C1:C2,A1:A13)
if that were possible (it isn't). I would like to have:
a field (with a list, for example) or column where i can put in the values of B that I do not want to be part of the sum over A.
a method that works with Open Office as well as Excel. I prefer an OO solution.
You could use an array formula so that you can multiply each value in A with a condition. That condition can be any valid Excel formula, so, for instance, you could use MATCH to test if the B value occurs in C:
=SUM((A1:A13)*ISNA(MATCH(B1:B13,$C:$C,0)))
The ISNA function returns TRUE when the match fails, which in a multiplication is used as a numerical value 1. FALSE will make the product 0.
Make sure to enter this as an array formula with Ctrl+Shift+Enter

Counting the frequency of combinations of numbers (in excel using VBA)

I want excel to count the FREQUENCY that certain number-letter combinations appear down a column in excel (using vba). All my data goes down one column like this:
Column A (only 1,2,3,4,5,s,f appear)
1
2
s
4
3
s
4
2
f
2
s
2
s
I want to count the number of occasions combinations of (1-s, 2-s, 3-s, 4-s, 5-s) occur, strictly when the number occurs first (is in the higher row). I do not want to count occasions when the s comes before the number (e.g. s-2). I know how to count the number of individual letters/numbers using the countIf function.
I might later want to expand my analysis to look at the occasions that three letter-number combinations (e.g. 2-s-3, 2-s-5)
I am very much a VBA noob.
Try inserting a new column to the right of Column A. Use this formula =A1&A2 and fill it down the column. The values will look like this:
+----------+----------+
| Column A | Column B |
+----------+----------+
| 1 | 12 |
| 2 | 2s |
| s | s4 |
| 4 | 43 |
| 3 | 3s |
| s | s4 |
| 4 | 42 |
| 2 | 2f |
| f | f2 |
| 2 | 2s |
| s | s2 |
| 2 | 2s |
| s | s |
+----------+----------+
Now you can count occurences like you were doing before! :D
Of course, you can expand to three character frequency analysis by making the formula =A1&A2&A3.
Seems possible with COUNTIFS, with 1 to 5 inclusive in C1:G1 and in C2:
=COUNTIFS($A1:$A12,C1,$A2:$A13,"s")
copied across to suit.
You can use the VBA equivalent of this formula
=SUMPRODUCT(--(ISNUMBER(A1:A12)),--(A2:A13="s"))
which looks for number, followed by s in the row below (4 for your sample)
code
MsgBox Evaluate("SUMPRODUCT(--(ISNUMBER(A1:A12)),--(A2:A13=""s""))")

return values from multiple matching rows

First off, I'd like to do this without VB if possible, so I don't have to go through the hassle of teaching recipients how to enable macros.
Now, I believe what I'd like to do is simple, but the answer may be complex formula-wise. I'm trying to list out in new columns the values from a specified column in rows which have matching values from two other columns. Sounds tricky I'm sure, but an example should help immensely...
Say I have the following data:
------------------
| sts | pos | bye |
------------------
| 0 | QB | 8 |
| 2 | WR | 3 |
| 2 | QB | 10 |
| 0 | QB | 4 |
| 2 | QB | 7 |
| 0 | WR | 11 |
| 2 | WR | 9 |
| 2 | QB | 5 |
------------------
That's my source. I want to list out the bye value from all rows that have sts = 2, for each respective pos. In other words, from the source data above I'd want to see the following result set:
--------------------------
| pos | byes |
--------------------------
| QB | 10 | 7 | 5 | |
| WR | 3 | 9 | | |
--------------------------
...because those are the bye values in the rows with sts = 2 and pos equal to the corresponding pos in the result table.
Again, I'd like to avoid macros if possible, and just use a formula in the bye cells of the results table.
Hopefully that makes enough sense for you to take a stab at it. Thanks!
FOLLOW-UP:
#Richard-Morgan I attempted to use your formula but can't get it to work. Here is a screenshot of my actual spreadsheet so we can use real cell references:
So sts is B2:B303, pos is D2:D303, and bye is E2:E303. So then I'd like to list out the byes in columns U thru Y. It looks like your answer, if I'm smart enough to implement it, will get me what I need, so any assistance you can provide to get me to the finish line is greatly appreciated!
Something along the lines of the following could be used:
{=INDEX(tbl, SMALL(IF(COUNTIF(G$3, $A$2:$A$9)
*COUNTIF(G$4, $B$2:$B$9), ROW(tbl)-MIN(ROW(tbl))+1), ROW($C1)), COLUMN($C1))}
where the A column is sts, B column is pos, and C column is bye. The variable tbl is the range of data (not the headers). G$3 would be the sts filter and G$4 is the pos filter.
Copy the array formula DOWN to find all the matching byes; #NUM! will appear after finding no more matches. If this bothers your users, you can add an ISERROR or a tricky conditional format that makes the text white on white.
You can then copy over the formula to the next column and enter new filter values.
G H
sts Search 2 2
pos Search QB WR
10 3
7 9
5 #NUM!
#NUM!#NUM!
If your users are comfortable with pivot tables, using them would be much easier, I would think.
EDIT
Making the formula "transpose" is a bit tricky and I am not having any breakthrough on how to fix that. However, if you want to manually edit the column formulas, here is what you want.
(I made the assumption that S is the sts filter. Maybe you're just doing a count there, but I didn't see where you enter the filter for sts. If S isn't the sts filter, update the formulas to point to where sts is 2 or whatever.)
U2:
{=INDEX(tbl, SMALL(IF(COUNTIF($S2, $B$2:$B$303)*COUNTIF($R2, $D$2:$D$303),
ROW(tbl)-MIN(ROW(tbl))+1), ROW($D$1)), COLUMN($D$1))}
V2:
{=INDEX(tbl, SMALL(IF(COUNTIF($S2, $B$2:$B$303)*COUNTIF($R2, $D$2:$D$303),
ROW(tbl)-MIN(ROW(tbl))+1), ROW($D$2)), COLUMN($D$2))}
etc.
This allows the cells to be copied down.
I am sure there is a way to INDIRECT the ROW/COLUMN, but I ran out of time to look at this at the moment.
EDIT 2
If you put a simple number increment somewhere, let's say U1 has 1, V1 has 2, W1 has 3, etc., you could use the following:
{=INDEX(tbl, SMALL(IF(COUNTIF($S2, $B$2:$B$9)*COUNTIF($R2, $D$2:$D$9),
ROW(tbl)-MIN(ROW(tbl))+1), U$1), COLUMN($D$1))}
This will copy down and across.
This sounds like a job for pivot tables and go to special. You need to add an ID # column though. Here is one way you could do it:
Then once the blanks are selected using go to special you just need to delete them and shift cells left.
Good Luck.
OK I figured out a way to get my desired results. It isn't the cleanest or best way, but it achieves my goal of listing the results horizontally, and avoids macros or pivot tables.
I use a hidden worksheet to list out all the pos and sts values, concatenated as a single value. So...
sts | pos | bye
----------------------
2 | QB | 8
2 | RB | 5
2 | QB | 11
0 | WR | 7
. . .
...becomes....
D | E
-----------
5 | 2QB | 8
6 | 2RB | 5
7 | 2QB | 11
8 | 0WR | 7
. . .
Then, I have a "shadow" results area that mimics the results area on my front-page worksheet. It looks like so:
G | H | I | J | K
-----------------------------
5 | QB | | | | |
6 | RB | | | | |
7 | WR | | | | | . . .
In H5:H7, I have the following formula:
=IFERROR((ADDRESS(MATCH("2"&$G5,$D$5:$D$305,0)+4,COLUMN($E5),4)),"")
This returns the first cell reference it finds in the concatenated column that starts with 2 and ends with the value in column G (e.g. the formulas in row 5 are looking for "2QB").
Then, in I5:n7 I have the following modified formula:
=IFERROR(ADDRESS(MATCH("2"&$G5,INDIRECT(ADDRESS(ROW(INDIRECT(H5))+1,4)&":$d$"&MAX(305,ROW(INDIRECT(H5))+1)),0)+ROW(INDIRECT(H5)),COLUMN($E5),4),"")
The reason I modify the subsequent columns is to change the range in which the formula is looking for its value to start at the next row after the previously found value. For example, with the data above, the formula in H5 would look for "2QB" in D5:D*n*, and return the first row it finds and attach it to column E, which would be E5.
The formula in I5 would then look for "2QB" starting in D*6* instead of D5, a row after the row referenced in H5's result.
Hopefully that made sense.
So what I end up with in my hidden worksheet is this:
G | H | I | J | K
-----------------------------
5 | QB | E5 | E7 | | |
6 | RB | E6 | | | |
7 | WR | | | | | . . .
Then, on my front page worksheet, I simply get the values (the bye) referenced by the cells in H5:*n*7 using:
=IFERROR(INDIRECT(lookups!H5),"")
...which gives me my final result:
G | H | I | J | K
-----------------------------
5 | QB | 8 | 11 | | |
6 | RB | 5 | | | |
7 | WR | | | | | . . .
Like I said, it's totally convoluted, but it works, and I can always refine it later if I figure out how. :) Thanks to you who took a swing at this seemingly complex problem for me! I'm sure your answers work beautifully as well.

Resources