I am trying to write a function that loops through column values and applies numbers 1,2,3...n in between cells with strings. for example:
data:
hefew
1
3
2
6
bkifew
3
4
2
1
3
I want the function to change the values to:
hefew
1
1
1
1
bkifew
2
2
2
2
2
There could be multiple strings, so the end value could end up being 15 or so.
I have started a basic function but I am not familiar enough with VBA to work the logic. I program in Python and would normally do stuff like this in that language. However, I'm forced to keep this within excel.
current working:
Sub Button2_Click()
Dim rng As Range, cell As Range
cellcount = CountA("A1:A1000")
Set rng = Range("A1:A10")
For Each cell In rng
a = cell.Value
If IsNumeric(a) = True Then
cell.Value = 1
Else
cell.Value = 0
End If
Next cell
End Sub
I don't think this is possible with a for loop. Is there some sort of search and replace function that I could use?
Try this, assuming the numbers are not the result of formulae.
Sub x()
Dim r As Range, n As Long
For Each r In Columns(1).SpecialCells(xlCellTypeConstants, xlNumbers).Areas
n = n + 1
r.Value = n
Next r
End Sub
I created a function instead of sub which is little bit messy but it works. Tested at my pc
Public Function Test(checkrange As Range, checkcell As Range)
Dim cll As Range
Dim arr() As Variant
ReDim Preserve arr(1 To checkrange.Cells.Count)
If IsNumeric(checkcell.Value) = False Then
Test = checkcell.Value
Exit Function
End If
y = 1
For Each cll In checkrange
If IsNumeric(cll.Value) Then
arr(y) = 1
Else
arr(y) = 0
End If
y = y + 1
Next cll
m = 1
For Each cll In checkrange
If cll.Address = checkcell.Address Then
rownumber = m
Exit For
End If
m = m + 1
Next cll
m = 0
For i = LBound(arr) To UBound(arr)
If arr(i) = 0 Then
m = m + 1
End If
If i = rownumber Then Exit For
Next i
Test = m
End Function
Related
I've written the following code to check if values in row A equal to "Forecast" then Range D5:D53 should have its contents cleared.
Row 1 is a vlookup so there's a formula that derives "Actual" or "Forecast"
Dim k As Integer
For k = 1 To 13
If Sheets("2017 Forecast").Cells(k, 1).Value = "Forecast" Then
Sheets("2017 Forecast").Range("D5:D53").Select.ClearContents
End If
Next k
There's no need to use Select before you use ClearContents.
Also, try adding UCase to make sure you don't have any CAPITAL letter in the middle of your text.
Code
Dim k As Integer
With ThisWorkbook.Sheets("2017 Forecast")
For k = 1 To 13
If UCase(.Cells(k, 1).Value2) = "FORECAST" Then
.Range("D5:D53").ClearContents
End If
Next k
End With
Maybe this works for you?
Option explicit
Sub Compare skies()
Dim k As long
Dim ValueRead as variant
With Sheets("2017 Forecast")
For k = 1 To 13
ValueRead = .Cells(k, 1).Value
' Slow, case insensitive string comparison '
If strcomp(ValueRead,"Forecast",vbtextcompare) = 0 Then
.Range("D5:D53").ClearContents ' You want to clear the exact same range 13 times? '
Elseif strcomp(ValueRead,"Actual",vbtextcompare) <> 0 then
Msgbox("VLOOKUP returned a value outside of Actual and Forecast")
End if
Next k
End with
End sub
I do some macro and i upgrade a macro of Diedrich to have a MaxIfs in excel 2010 which work with line an columns i put the code under.
Public Function maxifs(MaxRange As Range, ParamArray Criteria() As Variant) As Variant
Application.Volatile
Dim n As Long
Dim i, j As Long
Dim c As Variant
Dim f As Boolean
Dim w() As Long
Dim k As Long
Dim z As Variant
'Error if less than 1 criteria
On Error GoTo ErrHandler
n = UBound(Criteria)
If n < 1 Then
'too few criteria
GoTo ErrHandler
End If
'Define k
k = 0
'Loop through cells of max range
For i = 1 To MaxRange.Count
For j = 1 To MaxRange.Count
'Start by assuming there is a match
f = True
'Loop through conditions
For c = 0 To n - 1 Step 2
'Does cell in criteria range match condition?
If Criteria(c).Cells(i, j).Value <> Criteria(c + 1) Then
f = False
End If
Next c
'Define z
z = MaxRange
'Were all criteria satisfied?
If f = True Then
k = k + 1
ReDim Preserve w(k)
w(k) = z(i, j)
End If
Next j
Next i
maxifs = Application.Max(w)
Exit Function
ErrHandler:
maxifs = CVErr(xlErrValue)
End Function
So now i will do the minifs and it does not work if all my value are positives.
How can i do?
ps: if you change in this macro max by median it will work too
Thanks for your answers.
It is because you are starting the array w with an empty slot at 0, since the first slot you fill is slot 1.
So w(0) is 0, Which when all the others are positive it is the minimum number.
So change K=-1 instead of K=0 When initially assigning value to k.
I also moved z in front of the loop, there is no reason to keep assigning that array. It only needs to be assigned once.
Also, I changed the ranges a little to only look at the used range, this way you can use full column references.
Also, the loops need to be through the rows and columns not two loops through the whole range as it causes many unnecessary loops.
Public Function minifs(MaxRange As Range, ParamArray Criteria() As Variant) As Variant
Application.Volatile
Dim n As Long
Dim i, j As Long
Dim c As Variant
Dim f As Boolean
Dim w() As Long
Dim k As Long
Dim z As Variant
'Error if less than 1 criteria
On Error GoTo ErrHandler
n = UBound(Criteria)
If n < 1 Then
'too few criteria
GoTo ErrHandler
End If
'Define z
z = Intersect(MaxRange, MaxRange.Parent.UsedRange).Value
'Define k
k = -1
'Loop through cells of max range
For i = 1 To UBound(z, 1)
For j = 1 To UBound(z, 2)
'Start by assuming there is a match
f = True
'Loop through conditions
For c = 0 To n - 1 Step 2
'Does cell in criteria range match condition?
If Intersect(Criteria(c), Criteria(c).Parent.UsedRange).Cells(i, j).Value <> Criteria(c + 1) Then
f = False
End If
Next c
'Were all criteria satisfied?
If f = True Then
k = k + 1
ReDim Preserve w(k)
w(k) = z(i, j)
End If
Next j
Next i
minifs = Application.Min(w)
Exit Function
ErrHandler:
minifs = CVErr(xlErrValue)
End Function
Also a note as this will only do = in the criteria and not any other function like >,<,<>,....
What I am trying to do is develop a model that takes a cell that is greater than 1 then to take the sum of the area to the first row using a cone shape, so for example cell D4, sum the area C3:C5 + B2:B6 + A1:A7.
At the moment I have this but it obviously is not working.
Dim I As Double
Dim J As Double
Dim Size As Integer
Dim x As Integer
Dim y As Integer
Dim z As Integer
'Dim Range As Integer
Dim PV1 As Integer
'MCArray = Worksheets("Data")
I = WorksheetFunction.CountA(Worksheets("Data").Rows(1))
J = WorksheetFunction.CountA(Worksheets("Data").Columns(1))
'Loop to Move down the rows
For x = 1 To J
'Loop to move acoss the columns
For y = 1 To I
'IfElse to determine if cell value is greater or equal to zero
If Cells(J, I).Value >= 0 Then
'Loop to sum the cells above
For z = 1 To J
PV1 = (ActiveCell.Value) + Worksheet.Sum(Range([J - z], [I-z:I+z]))
'IfElse to determine if final sum is greater than zero
If PV1 > 0 Then
Worksheets("MC").Range("B4").Value = PV1
Range([J - z], [I-z:I+z]).Interior.ColourIndex = 1
End If
Next z
End If
Next y
Next x
Here is a function you can use either as a UDF or from another routine. Just pass it the single cell you want to start from (D4 in your example) and this function will calculate the sum of the cone as you described.
Public Function SUMCONE(r As Range) As Double
Application.Volatile
SUMCONE = Application.Sum(r, r(-0, -0).Resize(, 3), r(-1, -1).Resize(, 5), r(-2, -2).Resize(, 7))
End Function
Here is an example of how to use the above function from your VBA routine:
Public Sub Demo()
Dim j&
For j = 5 To 10
If Cells(5, j) > 0 Then
Debug.Print SUMCONE(Cells(5, j))
End If
Next
End Sub
UPDATE
Based on your feedback I have updated the function and the demo routine to form an upward cone summation from the initial cell.
UPDATE #2
The above is for a fixed-size cone, extending upwards, that can be initiated from any cell in the worksheet.
But if you would prefer for the cone to always extend all the way up to row 1 regardless of which cell it originates in, then the following is what you are after:
Public Sub Demo()
Dim i&, j&
For j = 1 To Application.CountA(Worksheets("Data").Rows(1))
For i = 1 To Application.CountA(Worksheets("Data").Columns(1))
If Cells(i, j) > 0 Then
Debug.Print Cells(i, j).Address, SumAndColorCone(Cells(i, j))
End If
Next
Next
End Sub
Public Function SumAndColorCone(r As Range) As Double
Dim i&, k&, c As Range
Set c = r
For i = r.Row - 1 To 1 Step -1
Set c = Union(c, r(-k, -k).Resize(, (k + 1) * 2 + 1))
k = k + 1
Next
c.Interior.Color = vbRed
SumAndColorCone = Application.Sum(c)
End Function
UPDATE #3
As I suspected there was a problem if the cone was initiated too close to the left edge of the worksheet. I've added code to handle that now. Also your method for accessing the large matrix (which I had used in the Demo routine) did not work properly. I fixed that as well:
Public Sub Demo()
Dim i&, j&
For j = 1 To Cells(1, Columns.Count).End(xlToLeft).Column
For i = 1 To Cells(Rows.Count, "A").End(xlUp).Row
If Val(Cells(i, j)) > 0 Then
Debug.Print Cells(i, j).Address, SumAndColorCone(Cells(i, j))
End If
Next
Next
End Sub
Public Function SumAndColorCone(r As Range) As Double
Dim i&, k&, c As Range
Set c = r
For i = r.Row - 1 To 1 Step -1
If r.Column - k < 2 Then Exit For
Set c = Union(c, r(-k, -k).Resize(, (k + 1) * 2 + 1))
k = k + 1
Next
c.Interior.Color = vbRed
SumAndColorCone = Application.Sum(c)
End Function
I have an Excel Worksheet consisting of two columns, one of which is filled with strings and the other is emtpy. I would like to use VBA to assign the value of the cells in the empty column based on the value of the adjacent string in the other column.
I have the following code:
Dim regexAdmin As Object
Set regexAdmin = CreateObject("VBScript.RegExp")
regexAdmin.IgnoreCase = True
regexAdmin.Pattern = "Admin"
Dim i As Integer
For i = 1 To 10 'let's say there is 10 rows
Dim j As Integer
For j = 1 To 2
If regexAdmin.test(Cells(i, j).Value) Then
Cells(i, j + 1).Value = "Exploitation"
End If
Next j
Next i
The problem is that when using this loop for a big amount of data, it takes way too long to work and, most of the time, it simply crashes Excel.
Anyone knows a better way to this?
You have an unnecessary loop, where you test the just completed column (j) too. Dropping that should improve the speed by 10-50%
Dim regexAdmin As Object
Set regexAdmin = CreateObject("VBScript.RegExp")
regexAdmin.IgnoreCase = True
regexAdmin.Pattern = "Admin"
Dim i As Integer
For i = 1 To 10 'let's say there is 10 rows
If regexAdmin.test(Cells(i, 1).Value) Then
Cells(i, 1).offset(0,1).Value = "Exploitation"
End If
Next i
If the regex pattern really is simply "Admin", then you could also just use a worksheet formula for this, instead of writing a macro. The formula, which you'd place next to the text column (assuming your string/num col is A) would be:
=IF(NOT(ISERR(FIND("Admin",A1))),"Exploitation","")
In general, if it can be done with a formula, then you'd be better off doing it so. it's easier to maintain.
Try this:
Public Sub ProcessUsers()
Dim regexAdmin As Object
Set regexAdmin = CreateObject("VBScript.RegExp")
regexAdmin.IgnoreCase = True
regexAdmin.Pattern = "Admin"
Dim r As Range, N As Integer, i As Integer
Set r = Range("A1") '1st row is headers
N = CountRows(r) - 1 'Count data rows
Dim inputs() As Variant, outputs() As Variant
inputs = r.Offset(1, 0).Resize(N, 1) ' Get all rows and 1 columns
ReDim outputs(1 To N, 1 To 1)
For i = 1 To N
If regexAdmin.test(inputs(i, 1)) Then
outputs(i, 1) = "Exploitation"
End If
Next i
'Output values
r.Offset(1, 1).Resize(N, 1).Value = outputs
End Sub
Public Function CountRows(ByRef r As Range) As Long
If IsEmpty(r) Then
CountRows = 0
ElseIf IsEmpty(r.Offset(1, 0)) Then
CountRows = 1
Else
CountRows = r.Worksheet.Range(r, r.End(xlDown)).Rows.Count
End If
End Function
I am able to search a text in column A of my spreadsheet by using this
With WB.Sheets("MySheet")
Set FindRow = .Range("A:A").Find(What:="ProjTemp1", LookIn:=xlValues)
End With
After which I can get the row number by doing FindRow.Row
How do I then get back the row number where Column A == "ProjTemp1" && Column B == "ProjTemp2" && Column C == "ProjTemp3"
Try to use Autofilter:
Dim rng As Range
'disable autofilter in case it's already enabled'
WB.Sheets("MySheet").AutoFilterMode = False
With WB.Sheets("MySheet").Range("A1:C1")
'set autofilter'
.AutoFilter Field:=1, Criteria1:="=ProjTemp1"
.AutoFilter Field:=2, Criteria1:="=ProjTemp2"
.AutoFilter Field:=3, Criteria1:="=ProjTemp3"
End With
With WB.Sheets("MySheet")
On Error Resume Next
Set rng = .Range("A2:A" & .Rows.Count).Rows.SpecialCells(xlCellTypeVisible)
On Error GoTo 0
End With
If Not rng Is Nothing Then
MsgBox rng.Row ' returns first visible row number
End If
WB.Sheets("MySheet").AutoFilterMode = False 'disable autofilter'
An alternative suggestion is to just loop through the table and use nested if-statements like this:
Sub ReturnRowNumber()
Dim i As Long, GetRow As Long
For i = 2 To Sheets("MySheet").Cells(Rows.Count, 1).End(xlUp).Row
'Criteria search
If Sheets("MySheet").Cells(i, 1).Value = "ProjTemp1" Then
If Sheets("MySheet").Cells(i, 2).Value = "ProjTemp2" Then
If Sheets("MySheet").Cells(i, 3).Value = "ProjTemp3" Then
'Returns row
GetRow = i
End If
End If
End If
Next i
End Sub
Just posted similar reply at MSDN and wanted to share here if anyone is still using VBA. The function for multiple match that works pretty fast.
It might help a lot if you are interested in effective code since using Application.Match() is much much faster that Find() or INDEX() method or simple looping.
The syntax is the same as COUNTIFS() but it returns the match index instead of counting.
Public Function MultiMatch(ParamArray X0() As Variant) As Variant
MultiMatch = CVErr(xlErrNA)
If UBound(X0) = -1 Then Exit Function
On Error GoTo ErrorHandler
Set Xws = X0(1).Parent
X_rFrow = X0(1)(1, 1).Row
X_rLrow = X_rFrow + X0(1).Rows.Count - 1
jLAST = UBound(X0)
l = X_rFrow
j = 0
Do While IsError(MultiMatch) And j + 1 <= jLAST And Not IsError(X1)
jCOL = X0(j + 1).Column
Set TRNG = Xws.Range(Xws.Cells(l, jCOL), Xws.Cells(X_rLrow, jCOL))
X1 = Application.Match(X0(j), TRNG, 0)
If Not IsError(X1) Then
l = TRNG(X1).Row
If X1 = 1 Then
If j + 1 = jLAST Then
MultiMatch = l - X_rFrow + 1
Else
j = j + 2
End If
Else
j = 0
End If
End If
Loop
Exit Function
ErrorHandler:
MultiMatch = CVErr(xlErrName)
End Function
This can work in such a way that X amount of values to search are Y columns to search for X values in a row, having 0 as a result of nothing and Row>= 1 the row that has the X amount of values per column in the same row.
Public Function find(sheetName As String, initCol As Integer, initRow As Integer, ParamArray values()) As Variant
Dim i As Long, GetRow As Long
On Error GoTo nextRow
For i = initRow To Sheets(sheetName).cells(Rows.Count, 1).End(xlUp).row
For ii = 0 To UBound(values)
If Sheets(sheetName).cells(i, initCol + ii).Value2 = values(ii) Then
GetRow = ii
If ii = UBound(values) Then
find = i
Exit Function
End If
GoTo nextCol
End If
If ii = 0 Then GoTo nextRow
nextCol:
Next ii
nextRow:
Next i
endFind:
find = GetRow
End Function
Use :
vRow = find("sheet", 1, 1, "test", "test1","test2")
"sheet" = sheetName, 1 = Col index start, 1 = row number start, ["test","test1","test2"] is ParamArray
"find" Function will search "test" in colunm A, "test1" in B &
"test2" in C and it will return the row number that has these values
followed in the same row