Lookup most used text in range based on criteria - vba

I have the following CSE formula to return the most used text in a range,excluding empty cells.
=INDEX(A4:D4,MODE(IF(A4:D4<>"",MATCH(A4:D4,A4:D4,0))))
My problem is that the formula returns #NA when there is only one value in the range. How can I adjust the formula to return that value?

If only concerned with a single cell being present causing problems and wanting to retrieve use this CSE:
=IF(COUNTIF(A4:D4,"*"), INDEX(A4:D4,MATCH(FALSE,ISBLANK(A4:D4),0)),INDEX(A4:D4,MODE(IF(A4:D4<>"",MATCH(A4:D4,A4:D4,0)))))
Otherise, with all distinct values being present or no mode in general,
You can count the distinct values and use that tested against the number of columns. If equal there is no mode and so use If statement to default into handling the True.
=IF(SUMPRODUCT(1/COUNTIF(A4:D4,A4:D4))=COLUMNS(A4:D4),"Do Something",INDEX(A4:D4,MODE(IF(A4:D4<>"",MATCH(A4:D4,A4:D4,0)))))
Again, a CSE so enter with Ctrl + Shift + Enter.
This bit of above formula counts the unique values:
SUMPRODUCT(1/COUNTIF(A4:D4,A4:D4))

Related

Extracting "hidden" data from expanding/collapsing pivot table - Excel

I'm not sure if this is possible but as you can see I have a pivot table with multiple dependent and expandable fields. I am trying to concatenate the data from columns A:D into one cell which works fine in row 2 but doesn't work with blank parent cells, as you can see in column F.
Any ideas for how to achieve this?
Pivot table
This answer assumes that you don't want to just Repeat All Item Labels in the PivotTable from the "Report Layout" drop-down on the Pivt Table Tools "Design" tab.
A formula to get the first non-blank value on or above the same row as the current cell from Column B can be constructed with a combination of AGGREGATE, SUMPRODUCT and OFFSET, like so:
=OFFSET($B2,SUMPRODUCT(AGGREGATE(14,6,ROW($B$1:$B$100)*--(ROW($B$1:$B$100)<=ROW())*--(LEN($B$1:$B$100)>0),1))-ROW(),0)
How does it work?
Starting with the outermost part, OFFSET($B2, VALUE, 0) - this will start in cell B2, then look up or down by VALUE rows to get the value.
Next we need to know how many rows we will need to look up-or-down. Now, if we can work out the bottom-most row with data, we can subtract the current ROW() from that, giving us OFFSET($B2, NON_BLANK-ROW(),0)
So, to finish up we need to work out which rows are not blank, AND which rows are on-or-above our current row, then take the largest of those. This is going to take an ArrayFormula, but we can use SUMPRODUCT to make that calculate properly. To find the largest number we could use MAX or LARGE - but we get less errors if we pick AGGREGATE(14,6,..,1). (The 14 means "we want the kth largest number", the 6 means "ignore error values", and the 1 is k - so "we want the largest number, ignoring errors")
But, what list of numbers are we going to look at, I don't hear you ask. Well, we want the ROW for output from our range (I'm using $B$1:$B$100, because using the whole column B would take far to long to calculate repeatedly), a comparison against the current ROW(), and check that the LENgth is > 0. Those last two are comparisons, so let's write them out first:
ROW($B$1:$B100)<=ROW()
and
LEN($B$1:$B$100)>0
We want to use -- to convert TRUE and FALSE to 1 and 0 - this means that any "bad" values become 0, and any "good" values are larger than 0:
ROW($B$1:$B$100)*--(ROW($B$1:$B$100)<=ROW())*--(LEN($B$1:$B$100)>0)
This gives us the Row number when the Row is on-or-before the current row AND Column B is not blank - if either of those are False, then we get 0 instead. Stick that in the AGGREGATE to find the largest number:
AGGREGATE(14, 6, ROW($B$1:$B$100)*--(ROW($B$1:$B$100)<=ROW())*--(LEN($B$1:$B$100)>0), 1)
Then put it in a SUMPRODUCT to force Excel to treat it as an ArrayFormula, and that's your NON_BLANK. This then gives you that first formula right at the top of the post

How to pull out corresponding column and row figures from a two dimensional data table?

peeps.
I currently have a two dimensional data table, which measures the sensitivity of two inputs, with regards to profit.
The whole data table, is (D183:AI234). I take the max profit one can gain from taking a max() value, and I was wondering what formula could I create, so I can get the corresponding values in the data table (two of them) which create the profit judged by max() from the whole data table.
Things I've tried: hlookup, vlookup to get both the inputs from row and column, but get N/A from both.
For example, to get the input from the row, based on look_up value, I used this formula: =HLOOKUP(E237,D183:AI234,1,0)
Kind Regards
Data Table:
Here's a hacky way to do it using array functions. I assume there is something cleaner.
Edit I misread the original question.
I am assuming that the data itself is in the range D183:AI234--the labels for the y-category are in C183:C234 and the labels for the x-category are in D182:AI182.
To find the row of the maximum value: MAX((D183:AI234 = MaxVal)*ROW(D183:D234))
With the row number there are a variety of options for actually accessing the value in your y-labels:
You can OFFSET from the upper left of the table (assumed to be
C182). OFFSET(C182, MAX((D183:AI234 = MaxVal)*ROW(D183:D234)) - ROW(C182), 0).
You can access the labeled cell using INDIRECT and ADDRESS, the row number, and a column identifier for the labels. INDIRECT(ADDRESS(MAX((D183:AI234 = MaxVal)*ROW(D183:D234)), COLUMN(C182)))
You can determine the relative position among the labels and use INDEX and the row number to retrieve the value. INDEX(C183:C234,MAX((D183:AI234 = MaxVal)*ROW(D183:D234)), COLUMN(C182)) - ROW(C182))
Note that these are all array functions and must be entered with CTRL + SHIFT + ENTER.
I'd prefer the INDEX approach as it is non-volatile (both OFFSET and INDIRECT are volatile functions and will recalculate every time a change is made to the sheet/excel recalculates) and I generally consider it to be better practice.
To get the x-value you identify the column of the maximum with MAX((D183:AI234 = MaxVal)*COLUMN(D183:AI183)) and adapt whichever of the three methods you choose.
Original answer to find the address of the max (but not the associated category values) below:
To find the row of the maximum entry you want to multiply a boolean array where your values match the maximum, so, that is (D183:AI234 = MaxVal) by the row numbers, so ROW(D183:D234). The result of that multiplication is a vector of (0,0,..,Row of Maximum Val,...), so you take the MAX of that to find the row number.
The same is true for the column, but you would use COLUMN(D183:AI183). Then you can get a cell address using the ADDRESS function.
Putting it all together...
=ADDRESS(MAX((D183:AI234 = MaxVal)*ROW(D183:D234)),MAX((D183:AI234 = MaxVal)*COLUMN(D183:AI183)))
This must be entered as an array function (CTRL + SHIFT + ENTER)

VBA excel Copy Paste

Hi, I am totally new to Excel VBA. Firstly, I want to copy the data when the condition is met(copy data with reference to 144)
Secondly, compare the cells, if it is IT Operations(Table1) to IT Operations(Table2) then copy the price(money) to column F. If the variable is no there then leave blank.
This can be done with formulas. Here is one way of thinking about filling column F, with the prices for the matching items in column E, by matching the number given in the last row in E (144 Total); which i shall assume is E10 in this case.
Total formula in F1 which you then drag down is:
=IFERROR(IFERROR(VLOOKUP(E1,INDIRECT(CELL("address",OFFSET($H$1,MATCH(1*LEFT($E$10,FIND(" ",TRIM($E$10),1)-1),$G:$G,0)-1,,1,1))&":"&CELL("address",OFFSET($I$1,MATCH($E$10,$G:$G,0)-1,,1,1))),2,FALSE),VLOOKUP(E1,G:I,3,FALSE)),"")
In steps:
Extract the number of interest e.g. 144, and get rid of any trailing/leading whitespace using:
LEFT($E$10,FIND(" ",TRIM($E$10),1)-1)
Find which row this value is in as this will be the first row of the lookup range for this number. *1 converts text to a number.
MATCH(1*LEFT($E$10,FIND(" ",TRIM($E$10),1)-1),$G:$G,0)
This gives row 9.
We can use something simpler to find the last row of the range, which holds 144 Total
MATCH($E$10,$G:$G,0)
This gives row 15. So we know the data lies between rows 9 and 15 for 144.
We can turn this into a range to use in a VLOOKUP with INDIRECT and OFFSET.
=CELL("address",OFFSET($G$1,MATCH(1*LEFT($E$10,FIND(" ",TRIM($E$10),1)-1),$G:$G,0)-1,,1,1))&":"&CELL("address",OFFSET($H$1,MATCH($E$10,$G:$G,0)-1,,1,1))
This gives us $G$9:$H$15. Note adjustments of -1, to put OFFSET back in the right row, and that the OFFSET start cells are in different columns to provide the columns required for the VLOOKUP.
So we can now lookup column E values e.g. Enhancement, in our newly defined range which is accessed via INDIRECT:
=VLOOKUP(E1,INDIRECT(CELL("address",OFFSET($H$1,MATCH(1*LEFT($E$10,FIND(" ",TRIM($E$10),1)-1),$G:$G,0)-1,,1,1))&":"&CELL("address",OFFSET($I$1,MATCH($E$10,$G:$G,0)-1,,1,1))),2,FALSE)
This is saying VLOOKUP(E1,$G$9:$H$15,2,FALSE) i.e. get the price column from the range for the item specified in E1.
If this is not found i.e. returns #N/A, we can use this to first check if this is because of the merged cell that holds the 144 Total; where the value is actually in column G not H, and use an IFERROR to say, if not found in $G$9:$H$15 then try for a match using columns G:I and return column 3.
Which with pseudo formula, using priorLookup as placeholder, for the formula described in the steps above, looks like:
IFERROR(priorLookup, VLOOKUP(E1,G:I,3,FALSE))
If this still returns #N/A, we know the value is not present and we should return "". This we can handle this with another IFERROR:
IFERROR(IFERROR(priorLookup, VLOOKUP(E1,G:I,3,FALSE)),"")
So giving us the entire formula stated at the start.
Here it is used in the sheet:

Count of cells in a certain column the does not contain a specified value

I can't find anywhere a function that would allow me to count every cell in a column except a certain value given.
For example there is a column where the values are (-), 1, 2 and 3 , I want to count all the cells that are not "(-)".
So there should be a function like
=countif(A1:A100, Not "(-)")
Does anyone how to do that?
This can be done using <> for not equal, like so:
=COUNTIF(A1:A100, "<>-")
Replace the - with what you want to not count, and change A1:A100 to the range of data you would like the formula to consider.

Dynamic reference in excel formula

I have the following array formula which works for what I want to do but I'm trying to change the formula when a user selects a value.
=INDEX($A$2:$B$70,SMALL(IF($A$2:$B$70=$A$121,ROW($A$2:$B$70)),ROW(1:1))-1,1)
It's used for a monthly report and the user will choose from a drop down the day of the month, e.g 1,2,3 - 31.
So if the user selects 1 from the drop down menu I want the formula to use the above formula.
If they select 2 for example I want the formula to move over a column so it would change to
=INDEX($A$2:$C$70,SMALL(IF($A$2:$C$70=$A$121,ROW($A$2:$C$70)),ROW(1:1))-1,1)
and so on moving over a column at a time.
It this possible at all or can it even be done without VBA?
I have an example of what I want done on the following link
https://docs.google.com/spreadsheets/d/1MDOzoQxYLgW-UOyljZsMwSu8zyAB7O2k1V-bTNP5_F0/edit?usp=sharing
All the data is on the first tab called staff. Each employee has a row and the duty assigned under the corresponding day column.
On the Roster tab it summarises each day. So what I am trying to get to happen is when you choose the day of the month (or preferably the actual date) the sheet changes to reflect the data.
At the moment the code I have working does for just Day 1 because the column references are coded into the formula. I was hoping to somehow choose 6 for example from the drop down and then the formula will map chosen day to the corresponding range in the raw data and update the formula and change the formula from Staff!$A$2:$B$68 to Staff!$A$2:$G$68.
If the formula finds no more entries if shows #NUM! but I intended to use the function ISERROR() to replace #NUM! with "".
This is what I'm trying to achieve it if makes sense?
There are a few issues here/ You are returning the value from column A so the first range can be $A$2:$A$70 and that means you don't need the 1 to specify the column_num. The IF statement was covering A2:C70 when you really only want either B2:70 or C2:C70 depending on the 1 or 2.
Assuming that A122 has either a 1 or 2 in it then,
=INDEX($A$2:$A$70, SMALL(IF(INDEX($B$2:$C$70, 0, $A$122) = $A$121, ROW($1:$69)), ROW(1:1)))
Standard non-array alternative,
=INDEX($A$2:$A$70, SMALL(INDEX(ROW($1:$69)+(INDEX($B$2:$C$70, 0, $A$122) <> $A$121)*1E+99,, ), ROW(1:1)))