VBA fill cells by random numbers depending on other cells - vba

I am working with Excel 2013 and I need to fill the range of cells by random numbers. To be exact, Range(B1:B4) fill by numbers in order to value of cells in previous column, I mean in Range(A1:A4). I really have no idea how to fill that using VBA if there is that condition, otherwise it's simple.
Here is a scetch of cells
# | A | B |
----------------------
1 | Yes | 1 |
----------------------
2 | No | 2 |
----------------------
3 | Maybe | 3 |
----------------------
4 | No | 2 |
----------------------

If all you need is random numbers, you don't need VBA. Just set your cell formula equal to:
"=RANDBETWEEN(1,3)"
However, your random numbers will change every time your worksheet is calculated. To avoid this, you can define the following sub and associate it, for example, with an action button:
Sub makeRand()
Dim targetRange As Range
Dim xlCell As Range
Dim upperBound As Integer
Dim lowerBound As Integer
Set targetRange = Range("B1:B4")
upperBound = 3
lowerBound = 1
Randomize
For Each xlCell In targetRange
xlCell.Value = Int((upperBound - lowerBound + 1) * Rnd + lowerBound)
Next xlCell
End Sub

If all you need to do is set this with vba, this should give you values 1, 2, or 3:
Range("B1:B4").Formula = "=RANDBETWEEN(1,3)"
If you only need an Excel formula, you can always just paste =RANDBETWEEN(1,3) into the formula bar.
If you're trying to define column B values based on column A values, just use:
Range("B1:B4").Formula = "=IF(A1 = ""Yes"", 1, IF(""No"", 2, If(""Maybe"", 3, ""ERROR"")))"
If neither of those are what you want, you're going to have to clarify better.

Related

VBA Average Function Error - 1004

I am getting an error 'Unable to get average property of Worksheetfunction class' when attempting to calculate the average of 2 ranges.
My table can be found below.
When I calculate the average of the % column, I have no problems. It provides me with the correct output, however, I have problems when I try and calculate the average for the $ column.
Col | % | $
1 | 2.33% | $2.33
2 | 3.64% | $3.64
3 | 10.83% | $10.83
4 | 6.07% | $6.07
5 | - | -
6 | 12.99% | $12.99
7 | 18.99% | $18.99
Dim myRange As Range
Dim myAverage As Variant
'The user selects the range
Set myRange = Application.InputBox( _
prompt:="Please select the range", Title:="My Range", Type:=8)
'This splits the range into two areas because the user typically does not select the row with the "-" in it.
'myRange would typically look something like (B1:B4,B6:B7) OR (C1:C4,C6:C7)
Area1 = myRange.Areas(1)
Area2 = myRange.Areas(2)
myAverage = Application.WorksheetFunction.Average(Area1, Area2)
The error I receive is 'Unable to get average property of Worksheetfunction class' and it happens with the myAverage calculation.
Any ideas as to why it calculates the % column without problem, but doesn't calculate the $ column?
Thanks in advance!
Declare as the proper type (Range object):
Dim Area1 As Range
Dim Area2 As Range
Use the Set keyword to assign to Object variables.
Set Area1 = myRange.Areas(1)
Set Area2 = myRange.Areas(2)
Then, you should be able to use the Application.Average or Application.WorksheetFunction.Average to get the mean.
myAverage Application.Average(Area1, Area2)
Otherwise, you're passing a variant array to the function, which is not supported, hence it raises the error. The Average function requires either a contiguous range of multiple cells, or itemized list of individual cells or values. You can't pass it multiple ranges of multiple cells each.
Or, omit the Area1/Area2 steps entirely and do simply:
myAverage = Application.Average(myRange.Areas(1), myRange.Areas(2))

Is there a coalesce-like function in Excel?

I need to fill a cell with the first non-empty entry in a set of columns (from left to right) in the same row - similar to coalesce() in SQL.
In the following example sheet
---------------------------------------
| | A | B | C | D |
---------------------------------------
| 1 | | x | y | z |
---------------------------------------
| 2 | | | y | |
---------------------------------------
| 3 | | | | z |
---------------------------------------
I want to put a cell function in each cell of row A such that I will get:
---------------------------------------
| | A | B | C | D |
---------------------------------------
| 1 | x | x | y | z |
---------------------------------------
| 2 | y | | y | |
---------------------------------------
| 3 | z | | | z |
---------------------------------------
I know I could do this with a cascade of IF functions, but in my real sheet, I have 30 columns to select from, so I would be happy if there were a simpler way.
=INDEX(B2:D2,MATCH(FALSE,ISBLANK(B2:D2),FALSE))
This is an Array Formula. After entering the formula, press CTRL + Shift + Enter to have Excel evaluate it as an Array Formula. This returns the first nonblank value of the given range of cells. For your example, the formula is entered in the column with the header "a"
A B C D
1 x x y z
2 y y
3 z z
I used:
=IF(ISBLANK(A1),B1,A1)
This tests the if the first field you want to use is blank then use the other. You can use a "nested if" when you have multiple fields.
Or if you want to compare individual cells, you can create a Coalesce function in VBA:
Public Function Coalesce(ParamArray Fields() As Variant) As Variant
Dim v As Variant
For Each v In Fields
If "" & v <> "" Then
Coalesce = v
Exit Function
End If
Next
Coalesce = ""
End Function
And then call it in Excel. In your example the formula in A1 would be:
=Coalesce(B1, C1, D1)
Taking the VBA approach a step further, I've re-written it to allow a combination of both (or either) individual cells and cell ranges:
Public Function Coalesce(ParamArray Cells() As Variant) As Variant
Dim Cell As Variant
Dim SubCell As Variant
For Each Cell In Cells
If VarType(Cell) > vbArray Then
For Each SubCell In Cell
If VarType(SubCell) <> vbEmpty Then
Coalesce = SubCell
Exit Function
End If
Next
Else
If VarType(Cell) <> vbEmpty Then
Coalesce = Cell
Exit Function
End If
End If
Next
Coalesce = ""
End Function
So now in Excel you could use any of the following formulas in A1:
=Coalesce(B1, C1, D1)
=Coalesce(B1, C1:D1)
=Coalesce(B1:C1, D1)
=Coalesce(B1:D1)
If you know there will not be any overlap across columns, or want the overlap, then this is a pretty fast way to solve for a coalesce. The below formula does not apply to your values and columns, but rather to my mock-up so you will need to adjust to make it relevant.
=LEFT(TRIM(CONCATENATE(Q38,R38,S38,T38,U38,V38,W38,X38,Y38)),1)
With the updated IFS function in excel you don't need to nest. You can try something like
Create a blank cell to the right
Then enter.
=IFS(ISBLANK(A1),B1,ISBLANK(A1),C1,ISBLANK(A1),D1)
Highlight column and paste as needed.
If you only want to coalesce to 0, which is a very common use case, you can simply use the SUM() function around a single value. As a convenience that one treats all blanks as zero, and it's extra convenient since it's so short.
Not a generic solution like the other answers, but a helpful shortcut in many cases where that's exactly what you want.
Inside the array enter the variables that are not allowed.
Function Coalesce(ParamArray Fields() As Variant) As Variant
Dim v As Variant
For Each v In Fields
If IsError(Application.Match(v, Array("", " ", 0), False)) Then
Coalesce = v
Exit Function
End If
Next
Coalesce = ""
End Function
Depending on how many cells you want to check, you can chain together multiple ISBLANK checks.
For instance, when checking columns A, B, then C:
=IF(ISBLANK(A1),IF(ISBLANK(B1),C1,B1),A1)
For columns A, B, C, and D:
=IF(ISBLANK(A1),IF(ISBLANK(B1),IF(ISBLANK(C1),D1,C1),B1),A1)
... and so on.
Late to the party and leveraging #AndyMC's answer you can use the following to evaluate vlookups, index(match()) etc. to coalesce your formula statements.
Public Function Coalesce(ParamArray Fields() As Variant) As Variant
Dim v As Variant
For Each v In Fields
If Not IsError(v) Then
Coalesce = v
Exit Function
End If
Next
Coalesce = CVErr(xlErrNA)
End Function
and use it in your Worksheet as followed: =Coalesce(INDEX(SHEET1!$A:$AF,MATCH(Main!$Q36,SHEET1!$I:$I,0),MATCH(Main!D$34,SHEET1!$1:$1,0)),INDEX(SHEET2!A:CR,MATCH(Main!$Q36,SHEET2!$M:$M,0),MATCH("SOMETHING",SHEET2!$1:$1,0)))
For the first statement that does not return #N/A it will return the actual matched value.

Write a formula into a cell depending on another cell value

I was hoping to write a Macro that does a very repetitive task for me but entering VBA is harder than expected. I will learn how to program macros for excel when I have some time because it seem extremely useful, but I can't spend 5 to 12 hours this week.
Maybe someone here can help!
I have a few excel files that follow this pattern:
Column C - Column D
--------------------
text | (empty)
number | (empty)
number | (empty)
text | (empty)
number | (empty)
text | (empty)
text | (empty)
number | (empty)
text | (empty)
number | (empty)
Where text and number alternate randomly for a few thousand cells. I need column D to hold, when column C is a number, the difference with previous number, otherwise it must stay blank:
Column C - Column D
--------------------
text | (empty)
3 | (empty)
14 | (=C3-C2) : 11
text | (empty)
16 | (=C5-C3) : 2
text | (empty)
text | (empty)
21 | (=C8-C5) : 5
22 | (=C9-C8) : 1
So the algorithm is:
var previousNumberCell
var i = 1
for all (selected) cells/rows
if (Row(i).column(C) holds number) {
Row(i).column(D).value = "=C"+i+"-"C"+previousNumberCell
previousNumberCell = i;
}
i++
End
I don't care if for the first or last cell it doesn't work.
Thank you so much for the help, or if you can point me to where I can find the answer to this.
EDIT: this is a simplified version of the problem, there are 2 things I don't know how do well with excel macros: select a cell, and tell if cell is a number... for the record, number cells have been converted from text to number format.
Give this a shot:
Sub MyMacro()
Dim rng as Range
Dim cl as Range
Dim lastNum as Range
Set rng = Range(Selection.Address) 'Make sure that your active SELECTION is correct before running the macro'
If Not rng.Columns.Count = 1 Then
MsgBox "Please select only 1 column of data at a time.",vbCritical
Exit SUb
Else:
For each cl in rng
If IsNumeric(cl.Value) Then
If lastNum Is Nothing Then
cl.Offset(0,1).Formula = "=" & cl.Address
Else:
cl.Offset(0,1).Formula = "=" & cl.Address & "-" & lastNum.Address
End If
set lastNum = cl
End If
Next
End If
End Sub
Do you require VBA?
Insert a new Column before column C
Column C with your values becomes column D
You might need columnheaders..
In cell C2 put: =IF(E2=0;0;SUM(E$2:$E2)) this identifies rows with number
In cell E2 put: =IF(ISNUMBER(D2);1;0) this sets order for each row with a number to use next in vlookup
in cell F2 put: =IF(ISNUMBER(D2);ABS(D2-VLOOKUP(MAX($C$1:C1);$C$1:D1;2;0));"")
Autofill columns C, E and F.
In column F you get your results, except first, which is "#VALUE"
Hi you can do this with an if formula and a named formula . if (isnumber ,named formula,0)
named formula (=lookup formula)

Creating separate line entries for multi-value fields

I have a table with a list of items. Essentially it's an export from an issue tracking tool. One of the columns of that table contains comma-separated values. I am looking for a way to create separate entries for the individual values of multi-value entries.
Example: (this is a simplified example, the real case contains around a dozen columns)
Source data:
ID | Title | Areas Affected |
1 | Issue title A | Area X, Area Y |
2 | Issue title B | Area Y, Area Z |
3 | Issue title C | Area X, Area Z |
What I am trying to get to:
ID | Title | Areas Affected |
1 | Issue title A | Area X |
1 | Issue title A | Area Y |
2 | Issue title B | Area Y |
2 | Issue title B | Area Z |
3 | Issue title C | Area X |
3 | Issue title C | Area Z |
It is OK that there are now duplicate entries for IDs and Titles?
Is there a formula, macro, or VBA script to achieve this?
You need to split rows on that column using the comma as a separator. In VBA you have the Split() function that you can use to return an array. For the first element just put it back in the cell where the list was. For the others, insert a new line for each element in the array (meaning you can have n elements in that comma-separated list), copy the entire row on that new line and put the i-th value in there.
After some reading/going through sample code, here's the answer if anyone needs. This is the actual working code, which doesn't fit 1:1 the examples I posted in the question.
Sub DataLobs()
Application.ScreenUpdating = False 'Nice to have to increase the script speed.
Dim wsSrc As Worksheet
Dim wsDst As Worksheet
Dim curRowSrc As Integer
Dim curRowDst As Integer
Dim ttlRows As Integer
Dim splitLob() As String
' Setting initial values to start rows in source and destination
' tables, as well as the total number of rows
curRowSrc = 5
curRowDst = 5
ttlRows = 10000
Set wsSrc = Worksheets("Source") 'whatever you worksheet is
Set wsDst = Worksheets("Destination") 'or whatever your worksheet is called
wsDst.Range("A5:F" & ttlRows).Clear
' Goes through column D in the source table
' and copies rows where the D cell is not blank
' into the destination table
For curRowSrc = 5 To ttlRows
If wsSrc.Range("D" & curRowSrc).Value <> "" Then ' There are some blank cells in the source table, so we are eliminating them.
' Split the cell value against the comma
splitLob = Split(wsSrc.Range("D" & curRowSrc).Value, ", ") 'THIS IS WHERE #AlexandreP.Levasseur's HINT COMES INTO PLAY!
For i = LBound(splitLob) To UBound(splitLob)
wsDst.Range("A" & curRowDst).Value = splitLob(i)
wsDst.Range("B" & curRowDst).Value = wsSrc.Range("A" & curRowSrc)
wsDst.Range("C" & curRowDst).Value = wsSrc.Range("C" & curRowSrc)
wsDst.Range("D" & curRowDst).Value = wsSrc.Range("AC" & curRowSrc)
wsDst.Range("E" & curRowDst).Value = wsSrc.Range("AE" & curRowSrc)
wsDst.Range("F" & curRowDst).Value = wsSrc.Range("AD" & curRowSrc)
curRowDst = curRowDst + 1
Next
End If
Next curRowSrc
End Sub

Autofill from a named range to a unnamed destination in Excel using VBA

I am trying to autofill from a named range to its right neighbour cells. The original range contains combined cells and is 4x4 cells in size. My problem is that the Autofill-method needs a Destination:=Range("...") and does not take something like Destination:=xlSameAreaToTheRight.
The solution would be if I could do something like
Range("origin").AutoFill Destination:=( Range("origin").Area + Rows(4) ), _
Type:=xlFillDefault
So how can I find the rows and cols of a named range in the format A1:D4 and add like 4 cols to it?
Thnks for your support!
I am not sure what you want to get as result, but here are the answers to your particular questions:
You can get the Range as String in the format $A$1:$D$4 using .Address, and to get the amount ofthe columns just add .Columns.Count to the named range. (Using offset is should be no problem to add the amount of the columns to the active cell within your range.)
Dim TestRange As String
Dim AmountColumns As Integer
TestRange = ActiveWorkbook.Sheets(1).Range("Test").Address
AmountColumns = ActiveWorkbook.Sheets(1).Range("Test").Columns.Count
Thanks to stema's answer I found a (quite simple) solution:
To add rows or cols to a named range and use it as AutoFill destination one can simply do
Range("origin").AutoFill Destination:=( Range("origin").Range("A1:H2") ), _
Type:=xlFillDefault
what would autofill four columns and two rows to our "origin":
| 1 | 2 |
| 1,1 | 1,2 | 2,1 | 2,2 |
This leads to:
| 1 | 2 | 3 | 4 |
| 1,1 | 1,2 | 2,1 | 2,2 | 2,7 | 3,12 | 3,54 | 3,96 |
Here's one way to use #stema's suggestion.
Sub namedfill()
Dim locOrig As String
Dim firstCell As String
Dim lastCell As String
'Pluck address of top-left and bottom-right cells from .Address string
locOrig = Range("Origin").Address
firstCell = Left(locOrig, 4)
lastCell = Right(locOrig, 4)
Range("Origin").AutoFill Destination:=Range(firstCell, Range(lastCell).Offset(0, 4)), Type:=xlFillDefault
End Sub