Related
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)
I have a table consisting of 3 different categories : MainLand, Island, City ; each one has 2 different possible values where one is assigned to them depending on 'weight'. If 'Weight' <= 2kg then our output, 'Cost', is eg. 1.2 (for Mainland or 1.3 for island etc). If 'weight' >2kg , we need to round it to the nearest integer , then calculate 0.3(for mainland or 0.9 for island etc) times the extra integers above 2kg. The cost then will be the original value for the first 2kg and then add up the decimal of each area times the extra integers . I tried creating my own function to select 3 cells and do the calculation since it seemed too complex for linear functions given from excell.
But I always get an error...
Note all variable values are from user-selected cells and the Location value simply checks which text there is inside the cell to assign the proper numbers for calculation.
Thats what I have so far... Any tips?
Function TotalCost(ByVal tmx As Integer, ByVal weight As Double, _
ByVal Location As Text)
Dim b As Integer
Dim c As Integer
Dim d As Integer
d = 0
c = 0
f = 0
If Location Like "Mainland" And weight <= 2 Then
TotalCost = 1.2
ElseIf Location Like "Mainland" And weight > 2 Then
weight = Round(weight, 0)
c = weight - 2
Do While c > 0
c = c - 1
d = d + 1
Loop
TotalCost = tmx * ((d * 9.55) + 1.2)
ElseIf Location Like "City" And weight <= 2 Then
TotalCost = 1.1
ElseIf Location Like "City" And weight > 2 Then
weight = Round(weight, 0)
c = weight - 2
Do While c > 0
c = c - 1
d = d + 1
Loop
TotalCost= tmx * ((d * 0.55) + 1.1)
ElseIf Location Like "Island" And weight <= 2 Then
TotalCost = 1.3
ElseIf Location Like "Island" And weight > 2 Then
weight = Round(weight, 0)
c = weight - 2
Do While c > 0
c = c - 1
d = d + 1
Loop
TotalCost= tmx * ((d * 0.7) + 1.3)
End If
End Function
Something more like this might be better:
Function TotalCost(ByVal tmx As Integer, ByVal weight As Double, _
ByVal Location As String)
Dim base As Double, mult As Double, tot As Double, wtExtra As Double
Select Case Location
Case "Mainland"
base = 1.2
mult = 9.55
Case "City"
base = 1.1
mult = 0.55
Case "Island"
base = 1.3
mult = 0.7
Case Else
TotalCost = "?Location?"
Exit Function
End Select
If weight > 2 Then
'round up to nearest kg and subtract 2
wtExtra = Application.Ceiling(weight, 1) - 2
TotalCost = tmx * ((wtExtra * mult) + base)
Else
TotalCost = base
End If
End Function
You can do your calculation with a relatively simple formula but it needs a little preparation, some of which is the same you will need for the UDF as well. Here is the formula. I'll explain it in detail below.
=VALUE(INDEX(Rates,1,MATCH($F$2,$B$1:$D$1))+(INDEX(Rates,2,MATCH($F$2,$B$1:$D$1))*(-INT($G$2/-2)-1)))*INT(MAX($H$2,1))
First, the setup.
I placed the table you published in your question into a range A1:D3.
I created a named range for the numbers, B1:D3, and called it Rates
Then I created a Validation drop-down in F2 which refers to the range B1:D1 ("City", "Mainland" "Islands")
I marked cell G2 for entering the Weight
And H2 for entering the quantity of parcels.
Next the preparations.
[G6] =MATCH(F2,B1:D1) returns 1, 2 or 3 depending upon what is selected in F2
[G7] =(-INT(G2/-2)-1) returns the number of surcharges for extra weight, like 0 for anything up to 2(kg), 1 for 2.1 to 4.0, 2 for 2.1 to 6.0 etc.
[G5] =INT(MAX(H2,1))
All of these formulas must be tested individually by checking their results while F2:H2 are being changed. (I did that.) That leads to the basic formula.
=INDEX(Rates,1,G6)+(INDEX(Rates,2,G6)*G7)
and, because it's a string,
=VALUE(INDEX(Rates,1,G6)+(INDEX(Rates,2,G6)*G7))
and, to multiply with the number of parcels,
=VALUE(INDEX(Rates,1,G6)+(INDEX(Rates,2,G6)*G7))*G5
Now all that remains to be done is to replace G5, G6 and G7 in the above with the formulas in G5, G6 and G7. Then add $ signs to enable copying of the formula and you get the result first above offered.
I need to find how many minutes exist between two string.
h1 = TimeValue("06:00:00")
h2 = TimeValue("22:00:00")
res = DateDiff("n", h1, h2)
However, res = 17/08/1902 whereas the expected result is 960.
Sub calcul(hours As Variant, Optional n As Integer = 0)
i = 3
Do While (Cells(i, 0) <> "")
Dim res As Date
Dim h2 As Date
Dim h1 As Date
Dim h As Integer
If (n = 0) Then
h = 0
Else
h = Cells(i, 7).Value - 1
End If
h1 = TimeValue(hours(h)("h1"))
h2 = TimeValue(hours(h)("h2"))
res = DateDiff("n", h1, h2)
...
The problem here is how you you've defined res.
Dates and time values are numbers. Even if you see it as 30/09/2019 or 12:00:00, actually, for Excel, both cases are numbers.
First date Excel can recognize properly is 01/01/1900 which integer numeric value is 1. Number 2 would be 02/01/1900 and so on. Actually, today is 43738.
For times is the same, but the decimal parts are the hours, minutes and second. 0,5 means 12:00:00. So, actually, 43738,5 means 30/09/2019 12:00:00.
Anyways, in your case, you are obtaining time difference between 2 times in minutes. The result is 960, but you are asigning this value to a date type, so 960 is getting converted to 17/08/1902.
Dim h1 As Date
Dim h2 As Date
Dim res As Single
h1 = TimeValue("06:00:00")
h2 = TimeValue("22:00:00")
res = DateDiff("n", h1, h2)
Debug.Print res
The code above will return 960 properly. Adapt it to your needs.
UPDATE: Because DateDiff returns a Long, defining res as Single is not worth it at all. I did it because working with times, in many cases, needs decimals, but if you are using just DateDiff, then you can perfectly do res as Long or res as Integer.
Note the difference between DateDiff and a normal substraction with a simple code:
Dim time1 As Date
Dim time2 As Date
Dim res1 As Integer
Dim res2 As Single 'or double if you wish
time1 = "06:00:00"
time2 = "06:30:30"
'time difference between these 2 values are 30 minutes and 30 seconds (30,5 minutes in decimal)
res1 = DateDiff("n", time1, time2)
res2 = (time2 - time1) * 1440 '1440 is the number of minutes in a whole day
Debug.Print "With DateDiff:" & res1, "Normal: " & res2
The output of this code is:
With DateDiff:30 Normal: 30,5
Using DateDiff sometimes is not worth it. Depending on how accurate you need the result, DateDiff may compensate or not. I would suggest you to avoid it if you can (this is jut my opinion)
Hope this helps
UPDATE 2: About the code above, yes, a solution would be using DateDiff("s", time1, time2) / 60 to get the seconds transformed into minutes, but this value, because of decimals, should be assigned to a data type that allows it.
Using Excel for Mac. I want the formula to round decimal numbers to fractions. The smallest increment I would want is 1/16 (which I can easily do) but I want automatically to reduce sixteenths to 1/8, 1/4, and 1/2 when appropriate (not rounding here).
Only rounding to the nearest sixteenth. (I have all the fractions rounding to the nearest 1/16)
But THEN reduce the fractions, eg:
14/16 - this should become 7/8
12/16 - this should become 3/4
8/16 - this should become 1/2
So in the end, I will have a large Excel sheet of fractions, some as small as 1/16, and some larger.
How would I do this?
I gather you do have some functionality for formatting fractions. Excel 2007 for example has:
I was contemplating 'doing the math' to achieve numbers (decimals) in multiples of 0.0625 and then applying formatting just for appearances.
This will work for values between 0 and 1 . Select the cells and run this small macro:
Sub FixFormat()
Dim r As Range, v As Variant, s As String
Dim numerator As Long
Selection.NumberFormat = "General"
For Each r In Selection
v = r.Value
If v >= 0 And v <= 1 Then
r.NumberFormat = "??/16"
numerator = CLng(Split(r.Text, "/")(0))
Select Case numerator
Case 0, 1, 3, 5, 7, 9, 11, 13, 15
Case 2, 6, 10, 14
r.NumberFormat = "# ?/8"
Case 4, 12
r.NumberFormat = "?/4"
Case 8
r.NumberFormat = "?/2"
End Select
End If
Next r
End Sub
The macro will detect the value and apply the proper formatting.
Rounding to 1/16ths
ROUND(YourNumber*16,0)/16
Then set the cell to two digit fractions. They will automatically reduce to 1/2, 1/4, 1/8, 1/16. Rounding drops off the "extra" portion. This could be used with round up and round down as well.
I have location points collected from a garmin device stored in an excel sheet in Degree Minutes format ---
W00208.172,N1046.977
How can I convert it to either Decimal Degrees or Degrees Minutes seconds Format ?
So I was looking for a lazy answer and wasn't happy so gonna put here what I made in the end:
Wanting to convert between these two formats:
33°59'05.5"S 22°39'42.7"E and -33.98485,22.66186
The left being "Degrees Minutes Seconds" and the right being "Decimal"
Of course I have to make some assumptions so they are that it assumes your are following the above formats exactly.
Ok so first is from Degree to Decimal (the source being in cell B2):
=ROUNDDOWN(if(mid(B2,find(" ",B2)-1,1)="S","-","")&mid(B2,1,find("°",B2)-1)+mid(B2,find("°",B2)+1,find("'",B2)-find("°",B2)-1)/60+mid(B2,find("'",B2)+1,find("""",B2)-find("'",B2)-1)/60/60,5)&","&rounddown(if(right(B2,1)="W","-","")&mid(right(B2,find(" ",B2)-1),1,find("°",right(B2,find(" ",B2)-1))-1)+mid(right(B2,find(" ",B2)-1),find("°",right(B2,find(" ",B2)-1))+1,find("'",right(B2,find(" ",B2)-1))-find("°",right(B2,find(" ",B2)-1))-1)/60+mid(right(B2,find(" ",B2)-1),find("'",right(B2,find(" ",B2)-1))+1,find("""",right(B2,find(" ",B2)-1))-find("'",right(B2,find(" ",B2)-1))-1)/60/60,5)
And then from Decimal to Degree (the source being in cell C2):
=abs(ROUNDDOWN(left(C2,find(",",C2)-1))) & "°" & ROUNDDOWN((abs(left(C2,find(",",C2)-1))-abs(ROUNDDOWN(left(C2,find(",",C2)-1))))*60) & "'" & round(((abs(left(C2,find(",",C2)-1))-abs(ROUNDDOWN(left(C2,find(",",C2)-1))))*60- rounddown((abs(left(C2,find(",",C2)-1))-abs(ROUNDDOWN(left(C2,find(",",C2)-1))))*60))*60,1) & """"&if(value(left(C2,find(",",C2)-1))<0,"S","N")& " " & abs(ROUNDDOWN(RIGHT(C2,len(C2)-find(",",C2)))) & "°" & rounddown((abs(RIGHT(C2,len(C2)-find(",",C2)))-abs(ROUNDDOWN(RIGHT(C2,len(C2)-find(",",C2)))))*60) & "'" & round(((abs(RIGHT(C2,len(C2)-find(",",C2)))-abs(ROUNDDOWN(RIGHT(C2,len(C2)-find(",",C2)))))*60- rounddown((abs(RIGHT(C2,len(C2)-find(",",C2)))-abs(ROUNDDOWN(RIGHT(C2,len(C2)-find(",",C2)))))*60))*60,1) & """"&if(value(right(C2,len(C2)-find(",",C2)))<0,"W","E")
If only you could have variables in formulae... most of that is duplicate formulae with some simple enough rules...Anyway, hope it helps someone.
The range of longitude (E or W) is -180 to 180, so 3 digits
The range of latitude (N or S) is -90 to 90, so 2 digits
Take "W00208.172", take the first 3 digits and store it to degress:
deg = 002
Then take the rest as decimal minutes:
min = 08.172
Now convert to decimal degrees (DEG):
decDegrees = deg + min / 60.0
Same for latitude: except that degrees are now only 2 digits
N1046.977: Take 10 degrees, and 46.977 minutes.
For both think if you want to keep the "W" symbol:
Often it is better to mutiply with -1 in case of of "W" or "S", do this as last step in your conversion!
Negative decimal degrees means W (for longitude) or S (for latitude).
For your provides coordinates that means:
lat: North 10046.977 = 10 + 46.977 / 60 = 10.78295;
lon: (2 + 8.172 / 60) * -1 = -2.1362;
so that location should be in Burkina Faso, 15m near a road (= plausible) 5,52 km south east east of Dununuai
This formula works on Google Sheets for converting from the format 018°40.1333 to 18.66888833 and -34°01.0597 to -34.01766167
=IF(MID(B2,1,1) = "-", (INT(MID(B2, 1, SEARCH("°", B2) - 1)) - MID(B2, SEARCH("°", B2) + 1, len(B2))/60), (INT(MID(B2, 1, SEARCH("°", B2) - 1)) + MID(B2, SEARCH("°", B2) + 1, len(B2))/60))