Searching column for consecutive values and returning three different values to other cells - vba

I have a series of columns. One contains a time stamp for a data point, the next is the data point, the one following that is a conversion from decimal to binary for that data point, and the remaining columns are that binary string split into each bit. Each column has a title: "No Engine Speed", "Engine Derate", and so on.
Here's what I'd like to do, but don't have the skill with VBA/Excel to do. I'm trying to take all that information and put it into something that's more friendly to a reader.
So, for example this:
would get me this:
The description comes from the column titles, and the time range start/end would come from the first column. The error codes I'll get with an Excel IF function or some kind of VLOOKUP function based on the contents of the description column. What I need is a VBA code or set of Excel functions that will populate those description and time stamp columns for me based on the contents of those bit columns.
How I envisioning it working is as follows: each bit column is searched through, if a 1 is found and the four cells more more below it are also found to be 1s the date stamp of the first 1 and final 1 populate the Start/End times in the more readable report I'm creating. In addition to this, the column header is copied to the description field.
The reason I want to only get the time stamps if there are five or more 1s consecutively is that I want some time to pass before the state is considered to be an 'event'. A second condition I'd like to meet would be that the time stamps are reasonably close together (say, within 2 minutes of each other). This is why even though there is a '1' event in the picture I linked for "16-May-15 21:52:47" I excluded it from the second image I linked.
The numbers next to the time stamps (and the timestamps themselves) will change depending on when the user opens the workbook. Those columns are the result of a query to a database and change based on what the previous shift's start and end time were. As a side note, when copying the time stamps I know that you need to paste the cell value and not just a regular paste, otherwise you wind up copying a query array to the database. I don't know if that has any bearing on a coding solution but I thought it was worth mentioning.
I considered trying to use some type of VLOOKUP function, but what I found doesn't quite do what I want because it doesn't check if there's five events or more in succession. Any guidance or direction you all can provide would be greatly appreciated. I feel like I have an idea of how to do this but I'm new to VBA and my Excel abilities are not up to the task just yet, and my Googling isn't turning up what I need.
I hope I was clear in my explanation of what I'm trying to do, feel free to ask questions if I wasn't.
Thanks,
Dan
EDIT 1:
As Grade 'Eh' Bacon suggested, I asked an additional question related to this one that resulted in a solution better suited to my needs. It can be found here.

Since you don't seem to have a problem using helper columns, this can be done in a fairly straightforward way, given that your data is already sorted by date.
RAW DATA TAB
Add a new column (we'll call it column X) which checks to see if your cell is the first cell which starts a string of 5 date stamps, all of which being, as you say, 2 minutes apart [starting in X2, ending at an assumed X100 assuming the datestamp is column A, and the reference code in decimal is column B]:
=COUNTIFS(A2:A$100,">=" & A2 - TIMEVALUE("00:02:00"),B2:B$100,B2)
This counts how many cells below the current cell are no more than 2 minutes later, including itself, and also have the same code in column B. We will use this to check whether that cell starts a new string of 5 + identical, near-in-time, codes.
In Column Y, starting at Y2, put:
=IF(AND(OR(B2<>B1,A2 - TIMEVALUE("00:02:00")>A1),X2 >= 5), TRUE)
This will first check if either (1) the code in the current cell doesn't match the code in the previous cell; OR (2) the time of the current row is at least 2 minutes later than the last line (either way, this is a new cycle). Then we check for the AND condition of whether the current row in column X shows a match of at least 5 cells below with the same code in the same time cycle. If TRUE, then it will return TRUE. Otherwise, it will return FALSE.
Then the code in column Z returns the number of the nth "hit code" we're on for that row. ie: whether this is the 1st, 2nd, nth time that a string of 5 codes has been hit [starting in Z2; hardcode Z1 as "0", or do some other special case so the first one won't add the title of the cell above, resulting in a #VALUE! error]:
=IF(Y2,Z1+1,Z1)
This will turn Z into an ascending list of positions, repeating values whenever a NEW code has failed to be created. Now we need to grab the description of the code that this row represents.
Assume you have an ordered list of all your codes, where column 1 would be equivilent to "1000000...", column 2 would be equivilent to "01000000.." etc. Name that single-row column (or, single column row) as a Range, which I will call Code_Index.
In column AA, starting at AA2, put the following:
=INDEX(Code_Index,SEARCH("1",C2))
This checks at which character "1" appears in column C at that row, and that becomes the position we want to pull the description from (which we have placed in the named range Code_Index).
Finally, we need to add a row which checks to see when a specific block of 5+ codes ends. Say, AB:AB (I forgot about this initially, hence its a little out of order initially). In AB2 and copied down, you will check to see whether there are at least 5 rows in a row of the same thing, within the 2 minute block, and also whether the next row is the same thing, within a new 2 minute block.
=IF(AND(COUNTIFS(B$1:B2,B2,A$1:A2,">"A2 - TIMEVALUE("00:02:00")>=5,OR(B3<>B2, A3 + TIMEVALUE("00:02:00")>= A2)),MAX(Z$1:Z1),"")
RESULTS PAGE
Now assume that's all on Sheet1, and you want your 'clean' results on Sheet2.
In sheet 2, have column A be an index, which simply starts at 1 on A2 & adds 1 each row afterwards. Column B will be another 'helper' column, column C will pull the description, column D will pull the start time, and column E will pull the end time.
In column B, put the following formula, which will check which 'new' index we're on (from column Z on the last tab). Starting at B2,
=MATCH(A2,Sheet1!Z:Z,0)
This will find the first row from the raw data tab which matches the current index number on A1. Then simply use this in an index formula in each of the next 3 columns, to pull the description, start time, and end time.
In C2 (pulling the description from AA on the last tab):
=INDEX(Sheet1!AA:AA,B2)
In D2 (pulling the start time from A on the last tab)
=INDEX(Sheet1!A:A,B2)
In E2 (pulling the end time from A on the last tab, *based on the row number from the column AB index)
=INDEX(Sheet1!A:A,MATCH(A2,Sheet1!AB:AB,0))
Let me know if I've misconstrued how you want your "2 minute time blocks" set up; do some rigorous testing to make sure it acts the way you expect.

Related

Complex IF. formula in Excel

I checked various posts on IF formulas but I cannot find a way to receive the correct result in my report. I manage deliveries and I would like to calculate the delay days basing on the data from delivery report. The trick is that the delay will depend on the status of delivery, as in each case I have to consider a different date and column in Excel. These are the data:
Status of delivery:
Confirmed
Unloaded
Unloading
Not confirmed
Started
In route
Pick-up pending
Prepared
This delivery status is updated in C column in my Raw Data report. For each, I will have to calculate the delay in a different way therefore I figured that IF formula could be of use.
Below you can see the columns that contain the relevant dates for the calculations:
Status of delivery and reference date:
Confirmed - D
Unloaded - D
Unloading - D
Not confirmed - S
Started - D
In route - S
Pick-up pending - E
Prepared - S
I made this formula as below, sadly, only the first record is calculated correctly, the rest of the delays is "null".
=IF(C2="Confirmed";(TODAY()-D2);IF(C2="Unloaded";(TODAY()-D2);IF(C2="Unloading";(TODAY()-D2);IF(C2="Not confirmed";(TODAY()-S2);IF(C2="Started";(TODAY()-D2);IF(C2="In route";(TODAY()-S2);IF(C2="Pick-up pending";(TODAY()-E2);IF(C2="Prepared";(TODAY()-S2);"null"))))))))
Do you happen to have any idea where am I making the error which I don´t see? I will be grateful for any help. If it´s also relevant, I am using Excel 2016.
Breaking it down so the long line becomes readable.
=IF(C2="Confirmed";
(TODAY()-D2);
IF(C2="Unloaded";
(TODAY()-D2);
IF(C2="Unloading";
(TODAY()-D2);
IF(C2="Not confirmed";
(TODAY()-S2);
IF(C2="Started";
(TODAY()-D2);
IF(C2="In route";
(TODAY()-S2);
IF(C2="Pick-up pending";
(TODAY()-E2);
IF(C2="Prepared";
(TODAY()-S2);
"null"
)
)
)
)
)
)
)
)
At first glance, what I'm looking at is basically a vertical lookup schedule here.
So, I created these two colums. One is the text status we're looking for.
The other is the calculated date.
Single date VLOOKUp
Assuming D2, S2 and E2 are fixed fields I made the formula =TODAY() - $D$2
Then it's a simple matter of doing the VLOOKUP in the correct field.
Because we're working with a date type field we need to convert the number to text to get a meaning full date. (I used JJ in the screenshot for years because I have a dutch locale)
Then we also need to handle when VLOOKUP can't find anything, for that we use IFERROR.
=IFERROR(TEXT(VLOOKUP(F6;$A$2:$B$9;2);"MM-DD-YY");"NULL")
And now you have an easy to expand lookup table you can put anywhere, hide on a worker tab, etc.. where you can calculate your values, where you can add and remove values.
Many rows, many dates
But, say you have many rows with different statusses and dates, and you wish to know the number of days it'll take. Then this vertical lookup doesn't look so useful because it can only be used for one row.
We can still leverage VLOOKUP to make our life a bit easier
Assuming there are dates in colums D, E and S
=IFERROR(DATEDIF(TODAY();INDIRECT(ADDRESS(ROW();VLOOKUP(F2;$A$2:$B$9;2;FALSE)));"d");"NULL")
We use VLOOKUP to see which column we need to look in. We use a number here for column, not a letter.
We then use ADDRESS to get an excel address reference for the current ROW(), and the column we found via vlookup
We funnel that through INDIRECT so we can get the value from that targeted cell.
Then we get a DATEDIFference in days from offset today.
We wrap it all in an IFERROR to keep things clean.
You could use an INDEX($D2:$S2;0;VLOOKUP($F2;$A$2:$B$9;2;FALSE)) to get the same effect, as pointed out by Dirk Reichel but you have to mindful then that the index used is from the start of the matrix range. So here the matrix starts on row D. So D2 is index 1 instead of 4 it is with my original method, so you'd need to adjust the lookup table accordingly.

VBA: Copy one value, paste it down the row for fixed number of times and then repeat

I am working on cleaning weather data available online on Canadian Government website. The problem sounds simple but unfortunately I couldn't get what I want via VBA.
WHY DO I HAVE A PROBLEM?
To clean data, I want to see if there is relationship between weather and moods.
The data is divided in sections (i.e one section by station for one year). Each section of the data has daily weather information (365 rows, 366 for leap year) in tabular form. The station name is nowhere mentioned in that data. It is only mentioned in one cell at top of each section. So I want to copy the station name from the header section and paste it down the row for all the days.
For-example:
If there are 8000 stations in canada that are monitoring weather data daily, then there will be 8000 sections of tables, each section will have daily weather data.
Here's my query in steps:
1) Copy one value of cell(cell B1) and paste it down the row 4694 times range(AC27:AC4720). 4694 covers 10 years +section headers, empty spaces.
2) Move down one extra row (so leave AC4721 blank)
3) Start the process again.
The function has repeat the process till the end of the file.
Formula in simple language:
continuing after blank space....
the cell is at AC4722 now, relative to this cell the formula has to copy the value of B4696, repeat step (1) and step (2) of the query.
In this way the next will AC9417, relative to this cell the formula has to copy the value of B9391, repeat step (1) and step (2) of the query.
The difference between relation positions (AC27,B1), (AC4722,B4696), (AC9417,B9391) is always 26
Please help.
To input the value all you need to do is:
Range("AC27:AC4720") = Range("B1")
No need to iterate over each row.
The hardest part is working out the range that you need to copy it to. Which can be done many ways depending on how your file looks.

Divide time column in time spans based on several columns

So far I have managed fine using Excel's formulas, so I might not need VBA to solve this problem.
I want to create time spans in column V based on the times in column O. The time spans are: 00-05, 05-07, 07-09, 09-15, 15-18 and 18-00. Thus far I have been using the formula (example for row 25):
=IF([#Old]="","",IF([#Old]*24<5,"00-05",IF([#Old]*24<7,"05-07",IF([#Old]*24<9,"07-09",IF([#Old]*24<15,"09-15",IF([#Old]*24<18,"15-18","18-00"))))))
But I want the time spans to be conditional on column M as well, so that fx the time spans for rows 41 to 42 should still be 05-07, because they are in the batch that started in 05-07 (row 31). I have uploaded what the result should look like in the picture below. I also have a column that counts the start and end of the batch (from 1 and upwards). This might help to solve the issue, but I'm not sure how to do the expand the conditional if-statement.
Try this (this works from cell F2, since other rows are being checked - M1 and R1 in this case, which have no simple reference in a teble-ish way):
=IF([#batch]<>M1,IF([#Old]="","",IF([#Old]*24<5,"00-05",IF([#Old]*24<7,"05-07",IF([#Old]*24<9,"07-09",IF([#Old]*24<15,"09-15",IF([#Old]*24<18,"15-18","18-00")))))),V1)
Basically I added an extra if to check if it is a new batch.
IF([#batch]<>M1,(do the long calculation), (use the same value as above))

Copy matching cells based on one column

Please see here for snippet from my spreadsheet, what I am trying to do is fairly simple, however I am unable to find a way to do this after searching through online forums extensively.
Column A contains my order numbers and column B the line items that correspond to each order number.
Column D contains the delivery date as it appears on my printed order sheet, you will see this only pulls through for the first line item on each order - the raw data displays this way and so there is way to change the raw data
Column E simply extrapolates just the date rather than the format Delivery Date: dd/mm/yyyy.
What I would like then, is for column E to have the delivery date copied down to all corresponding cells for each order number - so as per the attached sheet, 30 Jul 2015 would appear for all line items that correspond to order no #1192.
I feel v look up etc will only work to manipulate data once I have these dates copied down. I have tried index match but it doesn't seem to do what I want it to do.
Is there a way to copy down the dates for all line items relative to their order number? I understand that it will probably require copying full lines down column D first and keeping the formula in column E to extrapolate just the date.
Any help is much appreciated
You don't need a macro for this. There are many ways to go about this, I'll show you two, you can figure out the one you like from there.
Select coloumn E, go to Home, Editing, Find & Select, Go To Special. Hit Formulas (if the values are not formulas, go for Constants), and check only Errors. Now type =E2 (or whatever is above your active cell), and hit Ctrl+Enter. It is a wise idea to copy-paste values the whole coloumn E after this.
Another way would be entering this formula in coloumn F (cell F2, then pull it down):
=IFERROR(E2;F1)
Or you could combine this with your original formula, or use a macro to insert the formula in the empty/#error cells etc...
Assuming you are using =RIGHT(D2,LEN(D2)-FIND(":",D2)-1) in E2, then you are well on the way to a solution.
You also mentioned INDEX/MATCH which, if used in column F will pull the Delivery Date in Column E for each Order No:
=INDEX($E$2:$E$31,MATCH(A2,$A$2:$A$31,0))
This finds the position of the first match for your Order No and returns the Delivery Date from column E.

VBA msgbox duplicate value base on 2 columns

I'm trying to get a msgbox when a value is duplicate base on 2 columns. The first column Value can be repeated but the second column will determine if it's a duplicate or not.
i.e.
Column B = Code,
Column L = Month
The user can enter the Code several times, but if he enters it on the same month I want the msgbox pop up
Is your intention to warn\inform the user? If so, I would do this without a macro. I would use conditional formatting to make the cell change color whenever the duplicate information is entered.
Create a column on your worksheet with a formula that concatenates the information in column B&L the formula would be =B1&L1 (copy this formula down the table). You can hide the column so nobody sees it. For this example, let's say you used column "M".
Select the entire Code or Month column (or both) and click the CONDITIONAL FORMATTING button on the Home tab, choose NEW RULE, USE FORMULA TO DETERMINE WHICH CELLS TO FORMAT, then enter the following formula: =COUNTIF($M$4:$M$1000,M1)>1 (note I am assuming your range of data is less than 1000 records, otherwise increase that number). Set the format to something like a red fill and instantly duplicates will be flagged. The user will also be able to quickly locate the record where this combination was already entered as that will turn red too.
If you really do want a macro to do this, you could simply write a loop to compares the active cell value of B(activerow) & L(activerow) to each previous B#&L# combination. If a match is found, use the intersect method to pop-up a the message. Here is really a good article about the intersect method: http://www.ozgrid.com/VBA/vba-intersect.htm.