In cell A1 I have an interval value which is an integer which can either be 1, 5 or 15 which means 1 minute, 5 minutes or 15 minutes
In range "B1:B12", I have the sample data (it will be more)
12:03
12:03
12:06
12:06
12:09
12:11
12:14
12:15
12:15
12:16
12:31
12:32
Now in column C, I need to extract time slots based on number is available in cell A1.
If cell A1 has 15 then the column C would contain below
12:15
12:30
because first 15th minute in column starts at 12:15 then increment it by 15 mins until last available data which falls in that 15 min range.
If cell A1 has 5 then the column C would contain below
12:05
12:10
12:15
12:30
because first 5th minute starts at 12:05 then increment it by 5 mins until last available data which falls in that 15 min range.
and if cell A1 has 1 then extract everything except duplicates.
I hope I have explained the scenario properly. I am not able to think of a logic to do this in excel vba and need help with how to start so that I can try to apply the logic and comeup with a code.
Thank you
EDIT
I am adding a pic of the desired result.
if A1 contains 1 then copy all timestamps in column C without the duplicates
if A1 contains 5 then show only 5 times of 5 min interval
if A1 contains 15 then show only 3 times of 15 min interval
Using VBA (1) returning an array
You will need to ctrl-shif-enter
Option Explicit
Function get_unique_time_interval(ByVal interval_size As Double, ByVal input_data As Range) As Variant
interval_size = interval_size / 60# / 24#
Dim d
Set d = CreateObject("Scripting.Dictionary")
Dim r As Range
For Each r In input_data
Dim t As Double
t = Int(r.Value / interval_size) * interval_size
If Not d.exists(t) Then d.Add t, t
Next r
get_unique_time_interval = Application.WorksheetFunction.Transpose(d.keys)
End Function
(2) hard-coded outputting to C1
option explicit
Sub get_unique_time_interval_sub()
Dim interval_size As Double: interval_size = ActiveSheet.Range("a1").Value
Dim input_data As Range: Set input_data = Range(ThisWorkbook.Sheets(1).Range("b1"), ThisWorkbook.Sheets(1).Range("b1").End(xlDown))
interval_size = interval_size / 60# / 24#
Dim d
Set d = CreateObject("Scripting.Dictionary")
Dim r As Range
For Each r In input_data
Dim t As Double
t = Int(r.Value / interval_size) * interval_size
If Not d.exists(t) Then d.Add t, t
Next r
Dim v As Variant
Dim i As Long
i = 0
If Not IsEmpty(Range("c1").Value) Then Range(Range("c1"), Range("c1").End(xlDown)).ClearContents
For Each v In d.keys
ThisWorkbook.Sheets(1).Range("c1").Offset(i, 0).Value = v
i = i + 1
Next v
End Sub
this works as a single cell formula in C1:
=UNIQUE(INT(B1:B12/(A1/60/24))*(A1/60/24))
breaking it down:
A1/60/24
is the time interval you want (A1 in minutes so divide by 60 mins per hour and 24 hours per day to get to Excel time interval units). Then:
INT(B1:B12/(A1/60/24))
rounds you down to the start of the time period. Then:
*(A1/60/24)
converts you back up to the time of the interval in which the input time falls. Then:
UNIQUE(
pulls out the unique values.
The helpful thing here is that by including B1:B12 as a range it makes everything (helpfully) spill
If you accept a formula based answer then This should work for you:
A1: some inteval value.
B1:
=0.5+A1/(24*60)
Where 0.5 mean 12:00 in Excel.
In B2:
=B1+$A$1/(24*60)
And drag this formula down to where you need it.
Exampls:
Related
I have a start time, duration and a data value in Columns A, B and C respectively.
How can I capture the data that falls during the start time and end time and insert the sum of this data in a 30-minute cycle (e.g. 09:00, 09:30, 10:00, 10:30 etc) in the "Output" column?
For example, if Data_A had a start time was at 09:15 and end time at 10:15, its value would be returned at 09:00, 09:30 and 10:00.
If more than 1 data value was received within the same 30-minute cycle, the values would be summed.
For example, Data_A has a value of 0.1 and Data_B has a value of 0.2.
Data_B has a start time at 09:50 and end time at 10:10. The sum values at 09:00, 09:30 and 10:00 would be 0.1, 0.3 and 0.3 respectively.
If no data was received for any 30-minute cycle, it simply returns a zero.
The following SUMIFS function can be used to sum the values at start time but I couldn't modify it to take into account the duration and end time.
=SUMIFS($C$2:$C$10,$A$2:$A$10,">="&G2,$A$2:$A$10,"<"&G2+TIME(0,29,59))
The dataset I have is over a year's worth, I am open to solutions using cell equations or VBA.
Link to dropbox file
I would create a VBA function SumBetweenDateTimeLoop with inputs the cell of the starting datetime, the range of starting time, range of ending time, range of values to add and minutes of the loop (in this case fixed to 30).
Public Function SumBetweenDateTimeLoop(this_date As Range, dt_start As Range, dt_end As Range, values As Range, min_loop As Integer)
Dim i As Integer
Dim v As Double
Dim d1 As Date, d2 As Date
Dim v1 As Date, v2 As Date
If dt_start.Count <> dt_end.Count Or values.Count <> dt_end.Count Or dt_start.Count <> values.Count Then
Call MsgBox("Length of ranges have to be the same.")
Exit Function
End If
v = 0
For i = 1 To dt_start.Count
d1 = CDate(dt_start(i))
d2 = CDate(dt_end(i))
v1 = CDate(this_date)
v2 = DateAdd("n", min_loop, v1)
If Not ((d1 < v1 And d2 < v1) Or (d1 > v2 And d2 > v2)) Then
v = v + CDbl(values(i))
End If
Next i
SumBetweenDateTimeLoop = v
End Function
The result is reported in the red column (there are some differences from your green column but it should be correct if I understood correctly your problem).
In order to call the function (after have copied it into a module in VBA), type in the desired Excel cell the following request.
=SumBetweenDateTimeLoop(G2;A$2:A$10;E$2:E$10;C$2:C$10;30)
Code explanation
The idea is to define extremes of the cycle, so define v1 as the datetime in column G and v2 = v1+min_loop.
Then make a loop over the cells of the ranges (Excel columns A, E and C), define the extremes of datetime (d1 start datetime in column A and d2 ending datetime in column E) and check whether or not both d1 and d2 are lower than v1 or greather than v2.
If this condition is not satisfied, it means that at least a part of the time is inside the desired cycle and then add the relative value to the incremental parameter v, which at the end will be the return of the function.
It's not that difficult: the solution is 48 :-)
By this I mean that, in Excel, and entire day is one unit (e.g. 01/01/2022 + 1 = 02/01/2022).
As a result, one hour equals 1/24th and half an hour (30 minutes) corresponds with 1/48th of a day.
So, you want to round down to 1/48, which you can do using this simple formula:
=FLOOR(B2*48,1)/48
Hereby some examples:
I have a date column and two other columns. I want to make a dynamic array function that shows the months and the sumproduct of the other two columns for each month and also filter by the year in the cell D2.
My current formula is this =CHOOSE({1,2}, SORT(UNIQUE(TEXT(B5:B14,"MMMM"))), SUMPRODUCT(C5:C14,D5:D14,--(YEAR(B5:B14)=D2)))
It filter by year but I dont have any idea of how to sumproduct by the month in the dynamic array.
I know that it will easy add another column and multiply the two columns but this is for a real task with a file of more than 200K rows, so add another row is not an option.
(click on Image of Worksheet configuration to see the example of the dynamic arrray)
Image of Worksheet configuration
Sample Data Table:
Date
Value1
Value2
2021-01-15
19
23
2021-01-15
12
15
2021-01-30
16
22
2021-02-15
13
11
2021-02-28
29
12
2021-02-28
15
14
2021-03-03
14
15
2021-03-03
25
27
2021-03-03
16
21
2021-03-03
29
27
2020-01-15
10
23
2020-01-20
18
30
2020-01-30
27
11
2020-02-15
22
18
2020-02-28
23
11
2020-02-28
22
18
2020-03-03
29
17
2020-03-03
20
17
2020-03-03
13
13
2020-03-03
19
22
=SUMPRODUCT(
FILTER($C$5:$C$24,
TEXT($B$5:$B$24,"mmmmyyyy") = $G5&$D$2),
FILTER($D$5:$D$24,
TEXT($B$5:$B$24,"mmmmyyyy") = $G5&$D$2))
This uses the FILTER function to produce 2 arrays of data, where the Month and Year match the values specified (one for each column of data). It then SUMPRODUCTs those arrays together.
Unfortunately, I was not quite able to make it work with just 1 FILTER, but someone else might be able to figure out if that is possible.
(N.B. Because of the use of TEXT to convert the date to a string, this might have issues if Excel is not set to the same language that you wrote the month in; for example, if Excel is set to English, then it will not recognise the Spanish months shown in your example.)
You will need a helper column:
Then we can use SUMIFS:
=LET(
dt, B5:B24,
ttl, E5:E24,
tgt, D2,
mnt, SORT(UNIQUE(FILTER(MONTH(dt),YEAR(dt)=tgt))),
omnt, UNIQUE(TEXT(SORT(FILTER(dt,YEAR(dt)=tgt)),"MMMM")),
smttl, SUMIFS(ttl,dt,">="&DATE(tgt,mnt,1),dt,"<"&DATE(tgt,mnt+1,1)),
CHOOSE({1,2},omnt,smttl))
To do it without a helper column AND make it dynamic spillable we can use VBA and create a UDF that will do that:
Function SUMPRODUCTARRAY(dtrng As Range, rng1 As Range, rng2 As Range, yr As Long, mnth As Variant) As Variant()
Dim tempArr() As Variant
ReDim tempArr(LBound(mnth, 1) To UBound(mnth, 1), 1 To 1)
Dim dtrngA() As Variant
dtrngA = dtrng.Value
Dim rng1A() As Variant
rng1A = rng1.Value
Dim rng2A() As Variant
rng2A = rng2.Value
Dim i As Long
For i = LBound(mnth, 1) To UBound(mnth, 1)
Dim j As Long
For j = LBound(dtrngA, 1) To UBound(dtrngA, 1)
If month(dtrngA(j, 1)) = mnth(i, 1) And Year(dtrngA(j, 1)) = yr Then
tempArr(i, 1) = tempArr(i, 1) + (rng1A(j, 1) * rng2A(j, 1))
End If
Next j
Next i
SUMPRODUCTARRAY = tempArr
End Function
Then the formula is:
=LET(
dt, B5:B24,
fst, C5:C24,
scd, D5:D24,
tgt, D2,
mnt, SORT(UNIQUE(FILTER(MONTH(dt),YEAR(dt)=tgt))),
omnt, UNIQUE(TEXT(SORT(FILTER(dt,YEAR(dt)=tgt)),"MMMM")),
smttl, SUMPRODUCTARRAY(dt,fst,scd,tgt,mnt),
CHOOSE({1,2},omnt,smttl))
I have data like this in Excel:
Person1 A A B A C 3
Person2 0
Person3 A B C D E F 6
Person4 A A A 1
I am trying to find a formula that replicates the number in the last cell of each row, the number of unique elements associated with that person, excluding blanks. So for example Person1 has 3 since there is A, B, and C even though there are three A's. The number of columns is fixed / the same for everyone. The values A, B, C, etc, are strings (as opposed to numerics).
Can this be done using a formula?
This should work for you:
=SUMPRODUCT((B1:G1<>"")/COUNTIF(B1:G1,B1:G1&""))
This is what I found:
=SUMPRODUCT(1/COUNTIF(B1:F1,B1:F1&""))
It works quite interestingly:
It sums the results of the countifs, and it divides each one by 1.Thus, if you have the above example, it returns 4, because it sums:
1/1 + 1/3 + 1/3 + 1/1 + 1/3 + 1/3
An UDF to use in sheet
Public Function GetUniqueCount(ByVal rng As Range) As Long
Dim dict As Object, currCell As Range
Set dict = CreateObject("Scripting.Dictionary")
For Each currCell In rng
If Not IsEmpty(currCell) Then dict(currCell.Value) = 1
Next currCell
GetUniqueCount = dict.Count
End Function
In sheet use:
I am trying to calculate XIRR for data that I have in the below format
PurchaseDate Script No.ofunits PurchNAVrate NetAmount ForXIRR TotalReturn
17/11/2014 A 2241 33 75000 -75000 96000
8/1/2015 B 53 649 35000 -35000 43000
14/1/2015 B 75 658 50000 -50000 61500
14/10/2014 C 2319 32 75000 -75000 108000
8/1/2015 D 318 109 35000 -35000 40000
14/1/2015 D 450 110 50000 -50000 57000
8/6/2015 D 175 114 20000 -20000 22000
I tried lots of different permutations and combinations but not able to calculate XIRR for each Fund. Is there a way I can do it perhaps using offset or search.
This is a dynamic list which keeps getting changed based on new funds that are bought or sold
Values for Fund A should be around 14%
Values for Fund B should be around 13%
Values for Fund C should be around 21%
Values for Fund D should be around 8%
Update
Given that solution mentioned by #xor-lx was working very nicely in ms excel but not in google sheets, I have now modified the structure of my data based on #kyle advice and now I can calculate XIRR easily for each fund as a whole. Refer below
PurchaseDate Today Script No.ofunits PurchNAVrate NetAmount ForXIRR TotalReturn XIRR
17/11/14 31/08/2016 A 2241 33 75000 -75000 96000 "=XIRR(G2:H2,A2:B2)"
Total for above fund "=XIRR(G2:H3,A2:B3)"
8/1/2015 31/08/2016 B 53 649 35000 -35000 43000 "=XIRR(G4:H4,A4:B4)"
14/1/2015 31/08/2016 B 75 658 50000 -50000 61500 "=XIRR(G5:H5,A5:B5)"
Total for above fund "=XIRR(G4:H6,A4:B6)"
14 Oct 14 31/08/2016 C 2319 32 75000 -75000 108000 "=XIRR(G7:H7,A7:B7)"
Total for above fund "=XIRR(G7:H8,A7:B8)"
8 Jan 15 31/08/2016 D 318 109 35000 -35000 40000 "=XIRR(G9:H9,A9:B9)"
14 Jan 15 31/08/2016 D 450 110 50000 -50000 57000 "=XIRR(G10:H10,A10:B10)"
8/6/2015 31/08/2016 D 175 114 20000 -20000 22000 "=XIRR(G11:H11,A11:B11)"
Total for above fund "=XIRR(G9:H12,A9:B12)"
Assuming your table is in A1:G8 (with headers in row 1), and that your Fund of choice, e.g. "B", is in J2, array formula**:
=XIRR(INDEX(F:G,N(IF(1,MODE.MULT(IF(B$2:B$8=J2,{1,1}*ROW(B$2:B$8))))),N(IF(1,{1,2}))),CHOOSE({1,2},INDEX(A:A,N(IF(1,MODE.MULT(IF(B$2:B$8=J2,{1,1}*ROW(B$2:B$8)))))),TODAY()))
Copy down to give similar results for Funds in J3, J4, etc.
I tend to prefer this to set-ups involving OFFSET; not only is it briefer (and therefore more efficient), but also, more importantly, non-volatile.
See here for more if you're interested:
https://excelxor.com/2016/02/16/criteria-with-statistical-functions-growth-linest-logest-trend/
Regards
**Array formulas are not entered in the same way as 'standard' formulas. Instead of pressing just ENTER, you first hold down CTRL and SHIFT, and only then press ENTER. If you've done it correctly, you'll notice Excel puts curly brackets {} around the formula (though do not attempt to manually insert these yourself).
XIRR requires two arrays of arguments -- values and dates (with an optional third "guess" argument). To try to stitch together those arrays from non-contiguous ranges of purchase dates, total returns, purchase amounts, and a number of "today's dates" would be quite a challenge for an Excel formula. Hence I provide a User Defined Function written in VBA.
The arguments for the function are the fund or script (sFund) and your data table range laid out as you have in your example above.
As suggested in one of your comments, I used TODAY as the date corresponding to the Total Return column. Easily changed if that is not the case.
Also, note that I did not use the "forXIRR" column, rather I just used the negative of the NetAmount column. If you delete the forXIRR column, you will need to change the reference to .TotalReturn
I created a custom Class in order to make things a bit more understandable.
After you insert the Class Module, you must rename it cFUND
Class Module
Option Explicit
'Rename cFUND
Private pFund As String
Private pPurchDt As Date
Private pPurchDts As Collection
Private pPurchAmt As Double
Private pPurchAmts As Collection
Private pTotalReturn As Double
Private pTotalReturns As Collection
Public Property Get Fund() As String
Fund = pFund
End Property
Public Property Let Fund(Value As String)
pFund = Value
End Property
Public Property Get PurchDt() As Date
PurchDt = pPurchDt
End Property
Public Property Let PurchDt(Value As Date)
pPurchDt = Value
End Property
Public Function ADDPurchDt(Value As Date)
pPurchDts.Add Value
End Function
Public Property Get PurchDts() As Collection
Set PurchDts = pPurchDts
End Property
Public Property Get PurchAmt() As Double
PurchAmt = pPurchAmt
End Property
Public Property Let PurchAmt(Value As Double)
pPurchAmt = Value
End Property
Public Function ADDPurchAmt(Value As Double)
pPurchAmts.Add Value
End Function
Public Property Get PurchAmts() As Collection
Set PurchAmts = pPurchAmts
End Property
Public Property Get TotalReturn() As Double
TotalReturn = pTotalReturn
End Property
Public Property Let TotalReturn(Value As Double)
pTotalReturn = Value
End Property
Public Function ADDTotalReturn(Value As Double)
pTotalReturns.Add Value
End Function
Public Property Get TotalReturns() As Collection
Set TotalReturns = pTotalReturns
End Property
Public Function CalcXIRR()
Dim v, d, w
Dim I As Long
With Me
ReDim d(.PurchDts.Count * 2 - 1)
ReDim v(UBound(d))
I = 0
For Each w In .PurchAmts
v(I) = w
I = I + 1
Next w
For Each w In .TotalReturns
v(I) = w
I = I + 1
Next w
I = 0
For Each w In .PurchDts
d(I) = w
I = I + 1
Next w
Do Until I > UBound(d)
d(I) = Date
I = I + 1
Loop
End With
CalcXIRR = WorksheetFunction.XIRR(v, d)
End Function
Private Sub Class_Initialize()
Set pPurchDts = New Collection
Set pPurchAmts = New Collection
Set pTotalReturns = New Collection
End Sub
Regular Module
Option Explicit
Function fundXIRR(sFund As String, rTable As Range) As Double
Dim vSrc As Variant
Dim cF As cFUND
Dim I As Long
vSrc = rTable
Set cF = New cFUND
For I = 2 To UBound(vSrc, 1)
If vSrc(I, 2) = sFund Then
With cF
.PurchDt = vSrc(I, 1)
.ADDPurchDt .PurchDt
.Fund = vSrc(I, 2)
.PurchAmt = -vSrc(I, 5)
.ADDPurchAmt .PurchAmt
.TotalReturn = vSrc(I, 7)
.ADDTotalReturn .TotalReturn
End With
End If
Next I
fundXIRR = cF.CalcXIRR
End Function
Given your data above, XIRR for B would be calculated as:
XIRR({-35000;-50000;43000;61500},{"1/8/2015";"1/14/2015";"8/29/2016";"8/29/2016"}
Here are the results for your data above:
I will assume based on your table that the date is column A, the fund (or "Script") is column B and the "ForXIRR" is column F. The below will do it for you:
{=XIRR(IF(B:B="A",F:F,0),IF(B:B="A",A:A,0))}
Make sure you enter this with Shift + Ctrl + Enter since it is an array formula.
Note: The above will calculate for "A", but substitute that part of the formula for any other fund, or use a cell reference to calculate for others.
EDIT:
I realized the above will only work for the first letter in the sequence, please use below, and again, adjust the "B" to whatever you need. This will work for any of the letters present. Also, adjust all the ranges to match your needs, I only did rows 1:10 for my test purposes. The below adjusts the start of the range so it is always the first value for whichever fund is input (ie "A", "B", "C") to allow this to calculate. Without this change, the above will always start with a zero unless it is the first in the list ("A" in the OPs example), and XIRR only calculates with a negative number as the first value.
=XIRR(IF(OFFSET($B$1:$B$10,MATCH("B",$B$1:$B$10,0)-1,0,ROWS($B$1:$B$10)-MATCH("B",$B$1:$B$10,0)+1,1)="B",OFFSET($F$1:$F$10,MATCH("B",$B$1:$B$10,0)-1,0,ROWS($B$1:$B$10)-MATCH("B",$B$1:$B$10,0)+1,1),0),IF(OFFSET($B$1:$B$10,MATCH("B",B1:B10,0)-1,0,ROWS($B$1:$B$10)-MATCH("B",$B$1:$B$10,0)+1,1)="B",OFFSET($A$1:$A$10,MATCH("B",$B$1:$B$10,0)-1,0,ROWS($B$1:$B$10)-MATCH("B",$B$1:$B$10,0)+1,1),0))
Consider a spreadsheet which performs some computations based on a fixed value (in the rxample below, D3) and is iterative. E.g.
D3: 4
B3: 12
B4: 58 (=B3*$D$3+10)
B5: 242 (=B4*$D$3+10)
B6: 978 (=B5*$D$3+10)
Total = 1290 (=sum(B2:B5))
Now, suppose I wanted to try out different values of D3 (let's call this P) and store the different totals I get, i.e.
P Total
4 1290
5 2252
6 3618
7 5460
How would I do this with Excel? A macro? Please note that the above example is a simplified version of the real thing. It should be clear that I need to compute B3-B6 so I can compute the sum.
Update:
Each computation requires several columns. E.g. we would use values on B3,B4, .. and C3,C4, ... .
A macro can do this. If B7 contains the sum formula, try this
Sub RunSimulation()
Dim p as long
for p = 4 to 7
Range("D3")=p
Debug.Print Range("B7")
Range("L" & (p-1)) = Range("B7").Value
next
End Sub
EDIT: added a line for storing the results, as requested.
If you don't want to enter the sum formula in your sheet, you can calculate the total in VBA either:
Dim total as Long
Dim row as long
total = 0
for row = 2 to 5
total = total + Range("B" & row)
Next
Debug.Print total
(Use Double instead of Long for total if you are dealing with floating point numbers.)
Usually it is done in following manner:
A B C D
1 x tmp1 tmp2 Total
2 3 $A2+10 $B2*10+10 $C2*$B2 The formulae are just for example.
3 $A2+1 $A3+10 $B3*10+10 $C3*$B3
4 $A3+1 $A4+10 $B4*10+10 $C4*$B4
Excel has capabilities to automatically increment indices in formulae.