using "Large if" in vba - excel

I am working on a spreadsheet in which I have data of quarter-wise sales by co. and sector. My data is arranged in below mentioned format:
Row 1 has date
Data starts from row 2
Column B: Company Name
Column C: Sector Name of the co. for ex. energy, materials, technology etc
Column D: Sales figure of co.
Column E, F, G: Price, shares, volume of co.
Column H: Blank
From next column onwards I have same data fields for next quarter
Column I: Company Name
Column J: Sector Name of the co. for ex. energy, materials, technology etc
Column K: Sales figure of co
So on and so forth
Now, in another worksheet I need to get name and sales figures for top 3/5/10/15 etc (top n co.s depending on user input) within each sector. For ex. sales figures and co. name of top 3 companies in Energy sector.
I have been trying to write a vba code for this but I am struggling. I have mentioned below the code I was trying but its not flexible at all since in my code I have given reference of column C and D which would actually change after each quarter
Sub try()
Dim r As Long
n = Range("topcos").Value + 5
For r = 5 To n
p = 1
Worksheets("Top co. share with co name").Activate
Cells(r, 16).Select
Selection.FormulaArray = "=LARGE(IF('Co Wise'!$C$3:$C$600=P$3,'Co Wise'!$D$3:$D$600,""""),p)"
p = p + 1
Next r
End Sub

It seems that you merely forgot that p is a variable and should be treated as such in the formula:
Selection.FormulaArray = "=LARGE(IF('Co Wise'!$C$3:$C$600=P$3,'Co Wise'!$D$3:$D$600,"""")," & p & ")"

Alternatively:
Sub SO()
Sheets("Top co. share with co name").Range("P5:P" & [topcos] + 5).FormulaArray = _
"=LARGE(IF('Co Wise'!$C$3:$C$600=P$3,'Co Wise'!$D$3:$D$600,""""),ROW()-4)"
End Sub
Would do the same thing without the need for loops and variables.

Related

Find value in column where running total is equal to a certain percentage

In Excel I have a list of values (in random order), where I want to figure out which values that comprise 75% of the total value; i.e. if adding the largest values together, which ones should I include in order to get to 75% of the total (largest to smallest). I would like to find the "cut-off value", i.e. the smallest number to include in the group of values (that combined sum up to 75%). However I want to do this without first sorting my data.
Consider below example, here we can see that the cutoff is at "Company 6", which corresponds to a "cut-off value" of 750.
The data I have is not sorted, hence I just want to figure out what the "cut-off value" should be, because then I know that if the amount in the row is above that number, it is part of group of values that constitute 75% of the total.
The answer can be either Excel or VBA; but I want to avoid having to sort my table first, and I want to avoid having a calculation in each row (so ideally a single formula that can calculate it).
Row number
Amount
Percentage
Running Total
Company 1
1,000
12.9%
12.9%
Company 2
950
12.3%
25.2%
Company 3
900
11.6%
36.8%
Company 4
850
11.0%
47.7%
Company 5
800
10.3%
58.1%
Company 6
750
9.7%
67.7%
Company 7
700
9.0%
76.8%
Company 8
650
8.4%
85.2%
Company 9
600
7.7%
92.9%
Company 10
550
7.1%
100.0%
Total
7,750
75% of total
5,813
EDIT:
My initial thought was to use percentile/quartile function, however that is not giving me the expected results.
I have been trying to use a combination of percentrank, sort, sum and aggregate - but cannot figure out how to combine them, to get the result I need.
In the example I want to include Companies 1 through 6, as that summarize to 5250, hence the smallest number to include is 750. If I add Company 7 I get above the 5813 (which is where 75% is).
VBA bubble sort - no changes to sheet.
Option Explicit
Sub calc75()
Const PCENT = 0.75
Dim rng, ar, ix, x As Long, z As Long, cutoff As Double
Dim n As Long, i As Long, a As Long, b As Long
Dim t As Double, msg As String, prev As Long, bFlag As Boolean
' company and amount
Set rng = Sheet1.Range("A2:B11")
ar = rng.Value2
n = UBound(ar)
' calc cutoff
ReDim ix(1 To n)
For i = 1 To n
ix(i) = i
cutoff = cutoff + ar(i, 2) * PCENT
Next
' bubble sort
For a = 1 To n - 1
For b = a + 1 To n
' compare col B
If ar(ix(b), 2) > ar(ix(a), 2) Then
z = ix(a)
ix(a) = ix(b)
ix(b) = z
End If
Next
Next
' result
x = 1
For i = 1 To n
t = t + ar(ix(i), 2)
If t > cutoff And Not bFlag Then
msg = msg & vbLf & String(30, "-")
bFlag = True
If i > 1 Then x = i - 1
End If
msg = msg & vbLf & i & ") " & ar(ix(i), 1) _
& Format(ar(ix(i), 2), " 0") _
& Format(t, " 0")
Next
MsgBox msg, vbInformation, ar(x, 1) & " Cutoff=" & cutoff
End Sub
So, set this up simply as I suggested.
You can add or change the constraints as you wish to get the results you need - I chose Binary to start but you could limit to integer and to 1, 2 or 3 for example.
I included the roundup() I used as well as the sumproduct.
I used Binary as that gives a clear indication of the ones chosen, integer values will also do the same of course.
Smallest Value of a Running Total...
=LET(Data,B2:B11,Ratio,0.75,
Sorted,SORT(Data,,-1),MaxSum,SUM(Sorted)*Ratio,
Scanned,SCAN(0,Sorted,LAMBDA(a,b,IF((a+b)<=MaxSum,a+b,0))),
srIndex,XMATCH(0,Scanned)-1,
Result,INDEX(Sorted,srIndex),Result)
G2: =SORT(B2:B11,,-1)
H2: =SUM(B2:B11)*0.75
I2: =SCAN(0,G2#,LAMBDA(a,b,IF((a+b)<$H$2,a+b,0)))
J2: =XMATCH(0,I2#)
K2: =INDEX(G2#,XMATCH(0,I2#)-1)
The issue that presents itself is that there could be duplicates in the Amount column when it wouldn't be possible to determine which of them is the correct result.
If the company names are unique, an accurate way would be to return the company name.
=LET(rData,A2:A11,lData,B2:B11,Ratio,0.75,
Data,HSTACK(rData,lData),Sorted,SORT(Data,2,-1),
lSorted,TAKE(Sorted,,-1),MaxSum,SUM(lSorted)*Ratio,
Scanned,SCAN(0,lSorted,LAMBDA(a,b,IF((a+b)<=MaxSum,a+b,0))),
rSorted,TAKE(Sorted,,1),rIndex,XMATCH(0,Scanned)-1,
Result,INDEX(rSorted,rIndex),Result)
Note that you can define a name, e.g. GetCutOffCompany with the following part of the LAMBDA version of the formula:
=LAMBDA(rData,lData,Ratio,LET(
Data,HSTACK(rData,lData),Sorted,SORT(Data,2,-1),
lSorted,TAKE(Sorted,,-1),MaxSum,SUM(lSorted)*Ratio,
Scanned,SCAN(0,lSorted,LAMBDA(a,b,IF((a+b)<=MaxSum,a+b,0))),
rSorted,TAKE(Sorted,,1),rIndex,XMATCH(0,Scanned)-1,
Result,INDEX(rSorted,rIndex),Result))
Then you can use the name like any other Excel function anywhere in the workbook e.g.:
=GetCutOffCompany(A2:A11,B2:B11,0.75)

Excel - Entries in List of values be based on some other column value

We have a scenario in excel (2010) where the list of values present in a dropdown change dynamically based on some column of that row. For eg. Consider the "Supervisor" dropdown in sheet1 below:
Emp Grade Supervisor
A 14
B 12
C 13
D 12
E 12
F 13
G 14
Now let's say there is a dropdown for the supervisor. For every employee, the supervisor can be a person of his grade or higher grades only. So, for eg. For grade 13 employee, can have a supervisor with grade 13 or grade 14 only, not grade 12.
How can I write a custom condition like this inside the list of values? I have tried with things like named range, offset etc. but none allows specifying custom conditions. Any help?
I found the following document to be helpful in creating dependent Data Validation dropdowns: DV0064 - Dependent Lists Clear Cells, which can be downloaded here (for free):
http://www.contextures.com/excelfiles.html#DataVal
You can tailor the example to your needs.
=OFFSET('validation pivot'!$A$1,0,1,COUNTIFS('validation pivot'!$A:$A,">="&B2),1)
The supervisor needs to be at least his pay grade (>=B2). In order to have it work you need to have the pivot inserted in validation pivot A1. How to create the pivot (hasty notes):
add grade and emp 'emp as subset
tabular view 'to have separate columns
repeat labels ' to be able to count them
remove autosums(both within and total) 'to not deal with evading it
hide column labels and filters 'same
descending order(grade) 'to get a simple match method
data: store none 'to refresh the descending order every time
See uploaded sample file.
This code (column A = EMP, B = Grade, C = Supervisor)
Sub test()
Dim actualgrade As Integer
Dim lastRowA As Integer
Dim numbers As String
lastRowA = Sheets("sheet1").Cells(Sheets("sheet1").Rows.Count, "A").End(xlUp).Row
For i = 2 To lastRowA '1 = headers
actualgrade = Cells(i, 2)
For j = 2 To lastRowA
If Cells(j, 2) >= actualgrade Then
numbers = numbers & " " & Cells(j, 1).Value
End If
Next j
Cells(i, 3).Value = numbers
numbers = ""
Next i
End Sub
Makes this result:
Emp Grade Supr
A 14 A G
B 12 A B C D R F G
C 13 A C F G
D 12 A B C D R F G
R 12 A B C D R F G
F 13 A C F G
G 14 A G
Feel free to change it like you need it

Pulling data out of an worksheet by country

I have a huge amount of people in my excel sheet and I want to split them by country with excel coding, here is an example of my data:
Country | Name
UK | Tom
Austria | Bobsky
UK | Ralf
Germany | Badolf
Germany | Schwartz
UK | Andy
So would it be possible to just separate the people who are in the UK into s different part of my spreadsheet?
I have already tried
INDEX(B1:B6, MATCH("UK", A1:A6,0)) - this returns a repeated row if the match function returns no result
I have also tried many things with
if(VLOOKUP(etc etc) = "UK".....
and I have found this doesn't work either. I thought this would be something excel could do simply without having to filter + copy & paste or use VBA but this is not easy.
This is doable for a couple thousand rows of data. If your 'huge amount of people' is much more than that, an advanced filter or pivot table is a more viable solution.
      
With UK in D3 use the following in E3.
=IFERROR(INDEX($B$2:$B$9999, SMALL(INDEX(ROW($1:$9995)+($A$2:$A$9996<>D3)*1E+99, , ), ROW(1:1))), "")
Fill down as necessary.
Without wasting time to draft a complex Excel formula:
Option 1
Use a PivotTable where you use the Country column as a filter
Option 2
Use a Microsoft Query Data->From Other Sources->Microsoft Query on each worksheet like this:
SELECT * FROM [Sheet1$] WHERE Country = 'UK' ORDER BY NAME
Sub processData()
'I have workout for only UK
'this macro copies records of UK people from Sheet1 to Sheet2
lastrows = Worksheets("Sheet1").Cells(Rows.count, 1).End(xlUp).Row
count = 1
For x = 2 To lastrows
If (Worksheets("Sheet1").Cells(x, 1) = "UK") Then
Worksheets("Sheet2").Cells(count, 1) = Worksheets("Sheet1").Cells(x, 1)
Worksheets("Sheet2").Cells(count, 2) = Worksheets("Sheet1").Cells(x, 2)
count = count + 1
End If
Next x
MsgBox "Task finished"
End Sub

excel: looking up values using INDEX

i am using the following formula to index my data on sheet 2 and find the matching values which are similar or exact to that which is typed into my cell K22 on sheet 1.
I have used the below code and combined it together using & statements to give me multiple results from seperate columns.
=IF(ISERROR(INDEX(Sheet2!$A$1:$D$10004,SMALL(IF(LEFT(Sheet2!$A$1:$D$10004,5)=LEFT(Sheet1!$K$22,5),ROW($A$1:$D$10004)),ROW(1:1)),1)),"",INDEX(Sheet2!$A$1:$D$10004,SMALL(IF(LEFT(Sheet2!$A$1:$D$10004,5)=LEFT(Sheet1!$K$22,5),ROW($A$1:$D$10004)),ROW(1:1)),1))
so my finished combined code looks like this:
=`IF(ISERROR(INDEX(Sheet2!$A$1:$D$10004,SMALL(IF(LEFT(Sheet2!$A$1:$D$10004,5)=LEFT(Sheet1!$K$22,5),ROW($A$1:$D$10004)),ROW(1:1)),1)),"",INDEX(Sheet2!$A$1:$D$10004,SMALL(IF(LEFT(Sheet2!$A$1:$D$10004,5)=LEFT(Sheet1!$K$22,5),ROW($A$1:$D$10004)),ROW(1:1)),1)) & " - " & IF(ISERROR(INDEX(Sheet2!$A$1:$D$10004,SMALL(IF(RIGHT(Sheet2!$A$1:$D$10004,5)=RIGHT(Sheet1!$K$22,5),ROW($A$1:$D$10004)),ROW(1:1)),3)),"",INDEX(Sheet2!$A$1:$D$10004,SMALL(IF(RIGHT(Sheet2!$A$1:$D$10004,5)=RIGHT(Sheet1!$K$22,5),ROW($A$1:$D$10004)),ROW(1:1)),3)) & " - " & IF(ISERROR(INDEX(Sheet2!$A$1:$D$10004,SMALL(IF(LEFT(Sheet2!$A$1:$D$10004,5)=LEFT(Sheet1!$K$22,5),ROW($A$1:$D$10004)),ROW(1:1)),4)),"",INDEX(Sheet2!$A$1:$D$10004,SMALL(IF(LEFT(Sheet2!$A$1:$D$10004,5)=LEFT(Sheet1!$K$22,5),ROW($A$1:$D$10004)),ROW(1:1)),4))
so when i perform a search this displays the results like so
Supplier x - Manchester - V0001
i am using ROW() function because i have many of these formulas in seperate rows on my sheet to return multiple search results.
i am also using LEFT() function as a way of telling it to find similar results rather than exact, because someone may type 'Hotel' instead of 'Hotels' etc.
my data on sheet 2 looks like this:
Name Description Location Number
Supplier X Catering Manchester v0001
Supplier Y Hotels London v0002
Supplier Z Catering London v0003
so if i do a search for catering in my cell K22 on sheet 1 i would get a result like this:
Supplier X - Manchester - V0001
Supplier Z - London - V0003
But now if i want to just search for catering suppliers in London, i get the same search results again:
Supplier X - London - V0001
Supplier Z - London - V0003
however it changes the location of supplier x from manchester to london, even though in my column data on sheet 2, supplier x location is manchester?
Can someone please show me why this is happening as it should only be returning the suppliers which have a matching location of London next to them.
Thanks in advance,

Auto calculate average over varying number values row by row

I have an Excel file with several columns in it and many rows. One column, say A has ID numbers. Another column, say G has prices. Column A has repeating ID numbers, however not all numbers repeat the same amount of times. Sometimes just once, other times 2, 3 or several times. Each column G for that row has a unique price.
Basically, I need to average those prices for a given ID in column A. If each ID was repeated the same number of times, this would be quite simple, but because they are not I have to manually do my average calculation for each grouping. Since my spreadsheet has many many rows, this is taking forever.
Here is an example (column H is the average that I am currently calculating manually):
A ... G H
1 1234 3.00 3.50
2 1234 4.00
3 3456 2.25 3.98
4 3456 4.54
5 3456 5.15
11 8890 0.70 0.95
13 8890 1.20
...
So in the above example, the average price for ID# 1234 would be 3.50. Likewise, the average price for ID# 3456 would be 3.98 and for #8890 would be 0.95.
NOTICE how rows are missing between row 5 and 11, and row 12 is missing too? That is because they are filtered out for some other reason. I need to exclude those hidden rows from my calculations and only calculate the average for the rows visible.
Im trying to write a VBA script that will automatically calculate this, then print that average value for each ID in column H.
Here is some code I have considered:
Sub calcAvg()
Dim rng As Range
Set rng = Range("sheet1!A1:A200003")
For Each Val In rng
Count = 0
V = Val.Value '''V is set equal to the value within the range
If Val.Value = V Then
Sum = Sum + G.Value
V = rng.Offset(1, 0) '''go to next row
Count = Count + 1
Else
'''V = Val.Value '''set value in this cell equal to the value in the next cell down.
avg = Sum / Count
H = avg '''Column G gets the avg value.
End If
Next Val
End Sub
I know there are some problems with the above code. Im not too familiar with VBA. Also this would print the avg on the same line everytime. Im not sure how to iterate the entire row.
This seems overly complicated. Its a simple problem in theory, but the missing rows and differing number of ID# repetitions makes it more complex.
If this can be done in an Excel function, that would be even better.
Any thoughts or suggestions would be greatly appreciated. thanks.
If you can add another row to the top of your data (put column Headers in it) its quite simple with a formula.
Formula for C2 is
=IF(A2<>A1,AVERAGEIFS(B:B,A:A,A2),"")
copy this down for all data rows.
This applies for Excel 2007 or later. If using Excel 2003 or earlier, use AVERAGEIF instead, adjusting ranges accordingly
If you can't add a header row, change the first formula (cell C1) to
=AVERAGEIFS(B:B,A:A,A1)
In my way ..
Sub calcAvg()
Dim x, y, i, y2, t, Count, Mount As Integer
Dim Seek0 As String
x = 1 '--> means Col A
y = 1 '--> means start - Row 1
y2 = 7 '--> means end - Row 19
For i = y To y2
If i = y Then
Seek0 = Cells(i, x)
t = i
Count = Cells(i, x + 6)
Mount = 1
Else
If Cells(i, x) <> Seek0 Then
Cells(t, x + 7) = Count / Mount
Count = Cells(i, x + 6)
Mount = 1
t = i
Seek0 = Cells(i, x)
Else
Count = Count + Cells(i, x + 6)
Mount = Mount + 1
End If
End If
Next
End Sub
Hope this helps ..

Resources