I am working with lots of laboratory test results that are presented within one column in excel however the results come in different units from different labs so I need to convert some of the data so that all of the lab data is in the same units.
I have created a macro that looks up the lab data that I want to change and will convert the data however some of the results are presented as <0.07 etc. however the macro will not recognise and convert anything with < at the start of the number.
Can anyone suggest a way that I can amend the following macro to also convert lab results that contain < at the start?
Sub CONVERT_UNITS()
'
' CONVERT_UNITS Macro
'
'
ActiveCell.FormulaR1C1 = _
"=IF(RC12=""ug/kg"",RC11/1000,IF(RC12=""mg/l"",RC11*1000,RC11))"
Range("M2").Select
Selection.AutoFill Destination:=Range("M2:M" & Range("L" & Rows.Count).End(xlUp).Row)
Range(Range("M2"), Range("M2").End(xlDown)).Select
End Sub
For reference my lab data is contained in column K with the units presented in column L. I want the converted lab results to be put in column M (see example data below). I am only converting results that are in ug/kg (divide result by 1000) and mg/l (multiply result by 1000), all the other results will remain as they are.
I would create a User Defined Function for that:
Public Function convertResults(ByVal result As Variant, ByVal unit As String) As Variant
Dim hasSmaller As Boolean
If Left(result,1) = "<" Then
hasSmaller = True
result = Replace(result, "<", "")
End If
If unit = "ug/kg" Then result = result / 1000
If unit = "mg/l" Then result = result * 1000
If hasSmaller Then result = "<" & result
convertResults = result
End Function
... and then (for example in cell M2) just write =convertResults(K2, L2).
How can I get my new cells ref or Variables containing the row & Column number recognised as an actual eg. R38C8 reference for use by the consolidate function?
My two pivot tables rows and columns constantly change, I've got the new row and column number range - ready for a R1C1 ref but can't get the consolidate to work using a variable or cell reference in the formula - I'm new at this, just done a 3week crash course... Please help, it's driving me crazy!...
Last part of my code, trying a cells ref (consolidate doesn't like "& arS" or RarS:
RS = 32 + RowCountS
CS = ColCountS
RI = 63 + RowCountI
CI = ColCountI
arS = Cells(RS, CS) ' Stock table, last cell in the range
arI = Cells(RI, CI) 'Invoiced Table last cell in the range
' Creates a new table by consolidating the negative invoiced amounts with the stock movements totals
'
Sheets("Calculated Stock").Range("A4").Select
Selection.Consolidate Sources:=Array( _
"'Calculated Stock'!R32C1:R" & arS"" _
, _
"'Calculated Stock'!R63C1:R" & arI"" _
), Function:=xlSum, TopRow:=True, LeftColumn:=True, CreateLinks:=False
Range("A4").Select
This line
arS = Cells(RS, CS)
sets the variant arS to whatever value or text is held in the cell. I think that you think it holds a pointer to the cell - but that would require the line
Set arS = cells(rs,cs)
You would have eliminated this error if you had Option Explicit set and had declared arS as a range with
Dim arS as Range
With the code you have you would use
"'Calculated Stock'!R32C1:R" & RS & "C" & CS
With arS set as a pointer you would use
"'Calculated Stock'!R32C1:" & arS.address(true,true,xlR1C1)
So I am creating a function to replace some manual index/match formulas. Note that this function works, but my problem is with speed. So I have a PivotTable with 6 columns and approx. 200.000 rows. I want this to find the value (and I don't use the pivotfunctions, meaning that this is just a table in pivot format) I found that this runs faster than having it in a regular data table. Both would be imported from a SQL table.
A single piece of this formula runs instantly, but the performance slows down when I have a few hundreds in the same sheet.
So any ideas on how to speed this up?
Function getnum2(ByVal Comp As String, Period As String, Measure As String, Optional BU As String, _
Optional Country As String, Optional Table As String, Optional TableSheet As String) As Double
Dim pTable As PivotTable, wTableSheet As Worksheet
If BU = "" Then
BU = "Group"
End If
If Country = "" Then
Country = "Total"
End If
If TableSheet = "" Then
Set wTableSheet = Worksheets("Data")
Else
Set wTableSheet = Worksheets(TableSheet)
End If
If Table = "" Then
Set pTable = wTableSheet.PivotTables("PivotTable1")
Else
Set pTable = wTableSheet.PivotTables(Table)
End If
'Find match
If Intersect(pTable.PivotFields("Bank").PivotItems(Comp).DataRange.EntireRow, _
pTable.PivotFields("Date").PivotItems(Period).DataRange.EntireRow, _
pTable.PivotFields("Business Unit").PivotItems(BU).DataRange.EntireRow, _
pTable.PivotFields("Country").PivotItems(Country).DataRange.EntireRow, _
pTable.PivotFields("Name").PivotItems(Measure).DataRange) Is Nothing Then
getnum2 = "No match"
ElseIf Intersect(pTable.PivotFields("Bank").PivotItems(Comp).DataRange.EntireRow, _
pTable.PivotFields("Date").PivotItems(Period).DataRange.EntireRow, _
pTable.PivotFields("Business Unit").PivotItems(BU).DataRange.EntireRow, _
pTable.PivotFields("Country").PivotItems(Country).DataRange.EntireRow, _
pTable.PivotFields("Name").PivotItems(Measure).DataRange).Count > 1 Then
getnum2 = "More than 1 match"
Else
getnum2 = Intersect(pTable.PivotFields("Bank").PivotItems(Comp).DataRange.EntireRow, _
pTable.PivotFields("Date").PivotItems(Period).DataRange.EntireRow, _
pTable.PivotFields("Business Unit").PivotItems(BU).DataRange.EntireRow, _
pTable.PivotFields("Country").PivotItems(Country).DataRange.EntireRow, _
pTable.PivotFields("Name").PivotItems(Measure).DataRange)
End If
End Function
Rather than calling the function three times, you could use a variable:
Function getnum2(ByVal Comp As String, Period As String, Measure As String, Optional BU As String, _
Optional Country As String, Optional Table As String, Optional TableSheet As String) As Double
Dim pTable As PivotTable, wTableSheet As Worksheet
Dim rgResult as Range
If BU = "" Then
BU = "Group"
End If
If Country = "" Then
Country = "Total"
End If
If TableSheet = "" Then
Set wTableSheet = Worksheets("Data")
Else
Set wTableSheet = Worksheets(TableSheet)
End If
If Table = "" Then
Set pTable = wTableSheet.PivotTables("PivotTable1")
Else
Set pTable = wTableSheet.PivotTables(Table)
End If
'Find match
Set rgResult = Intersect(pTable.PivotFields("Bank").PivotItems(Comp).DataRange.EntireRow, _
pTable.PivotFields("Date").PivotItems(Period).DataRange.EntireRow, _
pTable.PivotFields("Business Unit").PivotItems(BU).DataRange.EntireRow, _
pTable.PivotFields("Country").PivotItems(Country).DataRange.EntireRow, _
pTable.PivotFields("Name").PivotItems(Measure).DataRange)
if rgResult Is Nothing Then
getnum2 = "No match"
ElseIf rgResult.Count > 1 Then
getnum2 = "More than 1 match"
Else
getnum2 = rgResult.Value
End If
End Function
One very simple way to achieve this is by using two PivotTables.
In PivotTable 1, put all fields but the numeric one you want to
return in the ROWS area, and put the field that you want to return in
the VALUES area with aggregation set to COUNT.
In PivotTable 2, put all fields but the numeric one you want to
return in the ROWS area, and put the field that you want to return in
the VALUES area with aggregation set to SUM or MIN or MAX (It doesn't
matter which).
Then you can use a paramatized GETPIVOTDATA function to check PivotTable 1 to see if the thing you're looking up is unique (i.e. COUNT = 1) and if so, then look up the SUM/MIN/MAX of that item in PivotTable2. Given the item is unique, then the SUM/MIN/MAX is only operating on one number, and so does nothing to it.
Here's how that looks, using simplified data:
I've added conditional formatting to the two Pivots to highlight multiple occurances where we want to return the text 'Multiple Items', and as you can see, the formula that is populating column 6 of the Lookup table is only returning unique items as per your requirements.
Here's the formula, using Table notation as my Lookup range has been turned into an Excel Table:
=IF(GETPIVOTDATA("6",$A$3,"1",[#1],"2",[#2],"3",[#3],"4",[#4],"5",[#5])=1,GETPIVOTDATA("6",$H$3,"1",[#1],"2",[#2],"3",[#3],"4",[#4],"5",[#5]),"Multiple Items")
If I randomise the input cells in the Lookup table, you can see what happens when some items aren't in the PivotTable:
This approach works because the field you want to return is a numeric one, meaning you can add it to the VALUES pane of the Pivot. But you could still use this to return strings, by adding a unique ID to the source data, such as the row number, and putting that in the VALUES field, then retrieving it with the double GETPIVOTDATA lookup and using it to retrieve the associated string in the source data.
Another approach is to simply concatenate your columns into a primary key using a suitable delimiter such as the pipe character | and then use that as your lookup key. If you did a binary search on sorted data, this would be lightning fast. (I discuss this at http://dailydoseofexcel.com/archives/2015/04/23/how-much-faster-is-the-double-vlookup-trick/ ). The down side is that you wouldn't be warned in the event that there were multiple items. But it would be possible to do a second lookup using the match position returned by the first, to see if you get another result, and if so then return "Multiple Items". This would be super-fast.
Here's the fastest way to do this: using Binary Match on a sorted lookup table.
On the left I have 5 columns x 1048575 rows of random numbers between 1 and 10. These have been concatenated in column G to make a non-unique key, and then sorted ascending on that key.
(Because the concatenated key is text, it gets sorted alphabetical from left to right, which is why 1|1|1|1|10 appears between 1|1|1|1|1 and 1|1|1|1|2)
I gave the data in Column G the named range of Concat to simplify the formula. My lookup formula in J2 returns the row number of the lookup item if and only if that item is unique to the dataset. The formula is:
=IF(OR( AND(INDEX(Concat,MATCH(I2,Concat,1))=I2, MATCH(I2,Concat,1)=1), AND(INDEX(Concat,MATCH(I2,Concat,1))=I2,INDEX(Concat,MATCH(I2,Concat,1)-1)<>I2)),MATCH(I2,Concat,1),NA())
This executes in 0.01 milliseconds for one instance, for a lookup table of 1048576 rows. My double GETPIVOTDATA approach above took 6 milliseconds. So there you have it: a complex formula that gives a 600 times efficiency boost.
I can explain the formula later in need, but note that some of the complexity is due to the edge case where you may have a unique item appearing in row 1. If I leave out that edge case, then the formula is as follows:
=IF( AND(INDEX(Concat,MATCH(I3,Concat,1))=I3,INDEX(Concat,MATCH(I3,Concat,1)-1)<>I3),MATCH(I3,Concat,1),NA())
I'm trying to make a table that has subtotals every few columns. My vba code brings and sorts data from another sheet into sections and now i'm trying to write the code to have the subtotal formulas put in. Here is what i have:
Sub Macro21()
Dim FI(1 To 3) As Variant
FI(1) = "Fixed Income"
FI(2) = 10
FI(3) = 21
Sheets("Sheet1").Cells(FI(2), 3).FormulaR1C1 = "=SUBTOTAL(9,R[1]C:R[FI(3)-FI(2)]C)"
End Sub
FI(2) and FI(3) are the beginning and ending rows for this section. I use them in other parts of the macro and they are updated as new items are put under a category.
When I run this it give me an error. Any ideas?
I think you need to build the formula as a string, not make it refer your Variant array. How about:
Sheets("Sheet1").Cells(FI(2), 3).FormulaR1C1 = _
"=SUBTOTAL(9,R[1]C:R[" _
& CStr(FI(3)-FI(2)) _
& "]C)"
This assuming the resulting string is what you'd like to calculate...
I'm getting a type mismatch error from the following code:
blattZFq3.Cells(month, siaw) = Application.WorksheetFunction.CountIfs(Worksheets(i).Range("AF10:AF290"), month, Year(Worksheets(i).Range("AE10:AE290")), minYear)
I'm guessing it's a problem with the second criteria, more specifically the Year function as criteria for a range since the code worked fine in a previous version with just the first criteria and using countif.
minYear is declared as Variant and has been assigned the value of 2012 by a previous function.
Basically I want the cell in the range blattZFq3 to contain the number of times a number matching month occurs in a column, but only if the year of a date in the same row but different column matches minYear.
Does anybody have any suggestions?
Thanks in advance....
You can't do this function to the array: Year(Worksheets(i).Range("AE10:AE290")) as it's expecting a range for the second area to check.
Also, I would avoid using the word Month as a variable name, as it's also the name of a function.
You will have to write the function with 3 criteria to get around the restriction, or write a formula into the target area.
Function with 3 criteria:
blattZFq3.Cells(MyMonth, siaw) = _
WorksheetFunction.CountIfs(Worksheets(i).Range("AF10:AF290"), MyMonth, _
Worksheets(i).Range("AE10:AE290"), ">=" & DateSerial(minYear, 1, 1), _
Worksheets(i).Range("AE10:AE290"), "<=" & DateSerial(minYear, 12, 31))
As a formula into the cell:
blattZFq3.Cells(MyMonth, siaw).Formula = _
"=SUMPRODUCT(--(SheetName!AF10:AF290=" & MyMonth & ")," & _
"--(YEAR(SheetName!AE10:AE290)=" & minYear & "))"
Work with date sometimes is tricky. Are you using english version?
You can try to write the same formula code in Excel and test it before you put it VBA. You can also try something like:
blattZFq3.Cells(month, siaw) = "=CONTIFS(.....)"