I'm creating a report with multiple columns. What I need is that the columns that show only whole numbers, no decimals, should be rounded to the whole number (so that it not only shows a rounded number, it actually equals a round number). The columns that show the numbers with two numbers after the decimal should not be rounded.
What I can do is:
If c.NumberFormat = "#,##0_);(#,##0)" Then
c.Formula = "=round(" & Right(strFormula, Len(strFormula) - 1) & ",0)"
End If
However, I have the entire report in an array, and I would like to just paste the whole array into the sheet rather than pasting one cell at a time. I would also rather not process and round each cell based on the cell formatting, rather I would like to copy the formatting of the range where the report will go into an array, and work from the array. I believe this will cut a few seconds off the process.
Is there a way to copy the formatting of a range into an array?
Is there a way to copy the formatting of a range into an array?
Focusing on the question as posed, yes, that can be done. Unfortunately, it can't done in a one-liner, in the way that a range's values can be assigned to an array with myArray = Range("A2:F25"), for example. (See, also, brettdj's comment.) Instead, you'd need something like:
Dim rng as Range
Dim formatArray() As String
Dim i as Long, j as Long
Set rng = Range(A2:F20) 'or whatever the range is
Redim formatArray(1 to rng.Rows.Count, 1 to rng.Columns.Count)
For i = 1 to rng.Rows.Count
For j = 1 to rng.Columns.Count
formatArray(i, j) = rng.Cells(i, j).NumberFormat
Next
Next
...
A couple of observations, though:
You actually only need to know the formatting for a single row of the range since, presumably, the number formatting in a column will not change mid-column.
That would simplify the code to:
...
Redim formatArray(1 to rng.Columns.Count)
For i = 1 to rng.Columns.Count
formatArray(i) = rng.Cells(1, i).NumberFormat
Next
...
assuming, for sake of example, that row 1 of the range has the necessary number formats.
I am curious why you would want to modify a formula on the worksheet so that it will round, since you could presumably do the calculation in your code, with rounding, and then write the resulting value back to the sheet for your report.
If you need to apply number formats to the values you are writing to the worksheet (not just modify formulas so that they will produce whole numbers), that can be done to whole columns within the range at once, i.e.,
...
For i = 1 to rng.Columns.Count
rng.Columns(i).NumberFormat = formatArray(i)
Next
...
If you need to convert the results of a cell's formula to a rounded value, you can do that with something like
rng.Cells(2, 5).Value = WorksheetFunction.Round(rng.Cells(2, 5).Value, 0)
to give an example for a single cell. This assumes, of course, that the data feeding into the formula are already in the sheet and that the formula has been recalculated.
Related
I regularly work with data spanning multiple columns and need a convenient way to highlight multiple rows that contain the same value in a specific column, but I need to alternate between highlighted and non-highlighted.
For example, I'll have several rows with data in Column A like:
700105862
700105862
700105862
700103235
700103235
700108783
700108783
700108783
And what I'd want to do is highlight the first three rows (700105862), then not highlight 700103235, then again, highlight 700108783.
I was wondering if there was a conditional formatting formula that'd make this possible.
Any help would be greatly appreciated!
Thank you,
if your numbers are divided into chunks of always different numbers repetitions, then you could use this VBA code:
Sub main()
Dim item As Variant
Dim startRow As Long
Dim okHighlight As Boolean
With Range("A1", Cells(Rows.count, 1).End(xlUp))
For Each item In GetUniqueValues(.Cells).Items
If okHighlight Then .Range(.Cells(startRow, 1), .Cells(item, 1)).Interior.ColorIndex = 48
startRow = item + 1
okHighlight = Not okHighlight
Next
End With
End Sub
Function GetUniqueValues(rng As Range) As Dictionary
Dim cell As Range
Dim dict As Dictionary
Set dict = New Dictionary
With dict
For Each cell In rng
.item(cell.Value) = cell.row - rng.Rows(1).row + 1
Next
End With
Set GetUniqueValues = dict
End Function
a Conditional formatting approach is possible on with a helper column
assuming:
your data are in column A and begin from row 2
column B is free
then:
write the following formula in helper column B cells:
=IF(A2<>A1,B1+1,0)
apply conditional formatting to column A with the following formula:
=INT(B2/2)=B2/2
and choosing the format you like to highlight cells
Sure, if you know what ranges you want to highlight you'd simply set the conditional formatting to be between x and y values. Comment on this question with what you dont get and I'll amend the answer accordingly.
I apologize if there is already the same question asked elsewhere with an answer however I have been unable to find it so here I go.
I will also mention that I am a VBA beginner, mostly playing around with codes obtained from other people to get what I want.
I currently have data in Columns A-D, with the information in column C being the important column. Everything else should be ignored.
I have a line of text in cell C1 of sheet1. It is 25 characters long and resembles the following:
4760-000004598700000000000
I have over ~970,000 rows of data and need to pull out the information found within each of these cells into two different cells in another sheet.
I cannot simply use a formula due to the number of records (excel crashes when I try).
If using the mid function for C1, I would enter something like (C1,2,3) and (C1,5,11). (except it would be for each cell in column C)
The leading zeroes between the + or - and the beginning of the first non-zero value are of no consequence but I can fix that part on my own if need be.
Ideally the information would be pulled into an existing sheet that I have prepared, in the A and B columns. (IE:sheet2)
For example, using the text provided above, the sheet would look like:
A|B
760|-0000045987 or -45987
I have looked into array, split and mid codes but I had troubles adapting them to my situation with my limited knowledge of VBA. I am sure there is a way to do this and I would appreciate any help to come up with a solution.
Thank you in advance for your help and please let me know if you need any additional information.
It sounds like what you're after could be achieved by the Text to Columns tool. I'm not sure whether you're trying to include this as a step in an existing macro, or if this is all you want the macro to do, so I'll give you both answers.
If you're just looking to split the text at a specified point, you can use the Text to Columns tool. Highlight the cells you want to modify, then go to the Data tab and select "Text to Columns" from the "Data Tools" group.
In the Text to Columns wizard, select the "Fixed Width" radio button and click Next. On step 2, click in the data preview to add breaks where you want the data to be split - so, in the example you gave above, click between "760" and "-". Click Next again.
On step 3, you can choose the format of each column that will result from the operation. This is useful with the leading zeroes you mentioned - you can set each column to "Text". When you're ready, click Finish, and the data will be split.
You can do the same thing with VBA using a fairly simple bit of code, which can be standalone or integrated into a larger macro.
Sub RunTextToColumns()
Dim rngAll As Range
Set rngAll = Range("A1", "A970000")
rngAll.TextToColumns _
DataType:=xlFixedWidth, _
FieldInfo:=Array(Array(0, 2), Array(3, 2))
With Sheets("Sheet4").Range("A1", "A970000")
.Value = Range("A1", "A970000").Value
.Offset(0, 1).Value = Range("B1", "B970000").Value
End With
End Sub
This takes around a second to run, including the split and copying the data. Of course, the hard-coded references to ranges and worksheets are bad practice, and should be replaced with either variables or constants, but I left it this way for the sake of clarity.
How about this:
Sub GetNumbers()
Dim Cel As Range, Rng As Range, sCode As String
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Set Rng = Sheets("Sheet1").Range("C1:C" & Sheets("Sheet1").Range("C1048576").End(xlUp).Row)
For Each Cel In Rng
Sheets("Sheet2").Cells(Cel.Row, 1).Value = Mid(Cel.Value, 2, 3)
sCode = Mid(Cel.Value, 5, 11)
'Internale loop to get rid of the Zeros, reducing one-by-one
Do Until Mid(sCode, 2, 1) <> "0" And Mid(sCode, 2, 1) <> 0
sCode = Left(sCode, 1) & Right(sCode, Len(sCode) - 2)
Loop
Sheets("Sheet2").Cells(Cel.Row, 2).Value = sCode
Next
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End Sub
I think there's an array formula thing that would do this, but I prefer the brute force approach. There are two ways to fill in the fields, with a procedure or with a function. I've done both, to illustrate them for you. As well, I've purposely used a number of ways of referencing the cells and of separating the text, to illustrate the various ways of achieving your goal.
Sub SetFields()
Dim rowcounter As Long, lastrow As Long
lastrow = ActiveSheet.Cells(Rows.Count, 3).End(xlUp).Row 'get the last row in column "C"
For rowcounter = 1 To lastrow 'for each row in the range of values
'put the left part in column "D"
ActiveSheet.Range("D" & rowcounter) = FieldSplitter(ActiveSheet.Cells(rowcounter, 3).Text, True)
'and the right part in the column two over from colum "C"
ActiveSheet.Cells(rowcounter, 3).Offset(0, 2) = FieldSplitter(ActiveSheet.Cells(rowcounter, 3).Text, False)
Next rowcounter
End Sub
Function FieldSplitter(FieldText As String, boolLeft As Boolean) As String
If boolLeft Then
FieldSplitter = Mid(FieldText, 2, 3) 'one way of getting text from a string
Else
FieldSplitter = Left(Right(FieldText, 16), 5) ' another way
End If
'Another useful function is Split, as in myString = Split (fieldtext, "-")(0) This would return "4760"
End Function
To preface this, I have very little experience in Excel VBA, but have used some VBA in Access.
I have a file which may contain multiple values in a single cell that need to be extracted out onto individual rows, and then have the data in multiple columns from the source row re-associated with the extracted values.
The multiple values in the single cell that need to be extracted are always in a uniform format. The cell may contain any number of sets of (), but the value I need to extract is always between the 2nd : and the closing ). This is the 'Identifier'.
For example:
(00050008009:STC:363711188)(00040022506:NYC:652263975)
Would need to extract these values onto individual rows:
363711188
652263975
All remaining values from the Source Row the value was extracted from then need to be re-associated with the value.
For example, my file may look like this:
Original File Format
I then need the file to appear as follows, on a new tab:
New File Format
I believe that a module making use of a loop, or multiple loops, is likely what is needed, but I have no idea of how to go about doing this in Excel. I'm open to all solutions. Any help is greatly appreciated! Thank you!
Without writing it for you, here are some pointers to get you started.
You'll need to loop through each cell in the column that contains the information you're looking for. For this, look into Worksheet.Range.
As you go through each cell, you'll need to examine the data that is actually entered into that cell. Using the Worksheet.Range.Value you can extract the contents of the cell.
Use excels string functions to parse the cell value into the values your looking for. Ex: InStr, InStrRev, etc... See this link for your options and usage for each function.
Finally you'll need to insert a row for each value that you find. Lookup Worksheet.Rows.Insert.
This should be the basic framework for what you need to do.
you may want to start with this code:
Option Explicit
Sub main()
Dim myArr As Variant
Dim cell As Range
Dim iRow As Long, nArr As Long
With Worksheets("batches").Range("A1").CurrentRegion '<--| change "batches" with your actual sheet name
For iRow = .Rows.Count To 2 Step -1 '<--|loop through data rows backwards, not to process rows multiple times
Set cell = .Cells(iRow, 3) '<--| 3rd column of current row is being processed
cell = Mid(cell, 2, Len(cell) - 2) '<--|get the cell value between first and last bracket
myArr = Split(cell, ")(") '<--|parse the resulting string with ")(" as delimiter and obtain and array
nArr = UBound(myArr) '<--| calculate the array size
If nArr > 0 Then '<--| if more than one element in array...
With .Rows(iRow) '<--|... then refer to entire current row
.Offset(1).Resize(nArr).Insert '<--| ...insert n-1 rows...
.Resize(nArr + 1).Value = .Value '<--|...duplicate current row into newly inserted ones
End With
cell.Resize(nArr + 1).Value = Application.Transpose(myArr) '<--|fill 3rd column of current and newly inserted rows with array elements
End If
Next iRow
For iRow = 2 To .Rows.Count '<--|loop through data rows
With .Cells(iRow, 3) '<--| 3rd column of current is being processed
.Value = Right(.Value, Len(.Value) - InStrRev(.Value, ":")) '<--| "finish" it
End With
Next iRow
End With
End Sub
As per your example, it assumes that your data start from cell "A1" and there's no blank row or column between it and the bottom-right cell of your data
Let me preface this question by saying I am not super technical so much of my verbiage may seem obscure..
On sheet1 I have three seperate horizontal ranges of cells (3 seperate series of steps):
A1:D1
A2:C2
A3:E3
On sheet two, I'd like to link to create live links to these ranges, such that if I change information on sheet1, it will be automatically reflected in sheet2.
The catch is, that on sheet2, I want the ranges to be listed after one another in one row, to create one long series of steps.
Range1-->Range2-->Range3 (all on one row)
How do I ensure that if I add an additional step to, say, the first range on sheet1, that on sheet 2, the new cell will be added and the following cells will all be pushed over to the right by one cell?
To accommodate ranges that might grow, start from the first cell and then find the last occupied cell with End(xlToRight). Once you've found all the range extents, you can combine them with an array UDF:
Function ConcatRanges(ParamArray ranges()) As Variant()
Application.Volatile
Dim ret() As Variant
ReDim ret(1 To 1, 1 To (Application.Caller.Columns.Count))
Dim RetIdx&, i&, cell As Range
RetIdx = 1
For i = 0 To UBound(ranges)
For Each cell in Application.Range(ranges(i), ranges(i).End(xlToRight))
ret(1, RetIdx) = cell.Value
RetIdx = RetIdx + 1
Next
Next
For RetIdx = RetIdx To UBound(ret, 2)
ret(1, RetIdx) = vbNullString
Next
ConcatRanges = ret
End Function
For your example, you'd call it like this:
=ConcatRanges(Sheet1!A1, Sheet1!A2, Sheet1!A3)
I have a loop wherein I take the mean of several columns of numbers with the same number of rows each.
The point of the loop is to capture these means in a new vector.
So for each loop I need to indicate "all rows". In matlab this would be easy, just use ":" But I can't figure out what the analogy is in VB. Please help! Thanks.
(Please advise me as to what I put in the code below where I have ALLROWS).
My attempt so far:
For i = 1 To CA
mrCA11(i) = Application.WorksheetFunction.Average(revCA11(**ALLROWS**,i))
Next i
In matlab this would be:
For i = 1:CA
mrCA11(i) = mean(revCA11(:,i));
Next i
EDIT: I've also tried this trick to no avail:
For j = 1 To CA
For i = 1 To s11
temp11(i) = revCA11(i, j)
Next i
mrCA11(j) = Application.WorksheetFunction.Average(temp11)
Next j
I get the error message: "Unable to get the Average property of the Worksheet Function class"
As everybody (Tim and shahkalpesh at least) pointed out, we need to understand what is revCall or more specifically, we need to understand how you want to give them ALL ROWS in argument.
Finding the last row (or column or cell)
A common Excel issue is to find the last used row / column / cell.
This will give you the end of your vector.
Excel give you several methods to deal with this:
xlTypeLastCell
Last cell used in the entire sheet (regardless if it's used in column A or not)
lastRow = ActiveSheet.Cells.SpecialCells(xlCellTypeLastCell).Row
End(xlUp)
Last cell used (including blanks in-between) in Column A is as simple as this:
lastRow = Range("A" & Rows.Count).End(xlUp).Row
End(xlToLeft)
Last cell used (including blanks in-between) in Row 1 is as simple as this:
lastRow = ActiveSheet.Cells(1, Columns.Count).End(xlToLeft).Row
UsedRange
Last cell used in the WorkSheet (according to Excel interpretation):
Set rangeLastCell = ActiveSheet.UsedRange
Using an array as argument
The methods above told you how to find the last row (if this is what you need). You can then easily create your vector and use it in your procedure revCA11.
You can either give an array as argument as Tim pointed out in his answer with this kind of statement:
myArray = ActiveSheet.Range("A1", Cells(lastRow, lastColumn).Value
Or you can use the integer (or long) to build your vector inside your procedure as simple as declaring a range:
Range("A1:A" & lastRow)
You might clarify exactly how revCA11 is declared/created, but maybe something along these lines might work for you:
Sub Tester()
Dim arr, x
arr = ActiveSheet.Range("A1:D5").Value '2-D array
'average each column
Debug.Print "Columns:"
For x = 1 To UBound(arr, 2)
Debug.Print x, Application.Average(Application.Index(arr, 0, x))
Next x
'average each row
Debug.Print "Rows:"
For x = 1 To UBound(arr, 1)
Debug.Print x, Application.Average(Application.Index(arr, x, 0))
Next x
End Sub