I am trying to return the values from a user defined function, but all that is returned are 0s. I feel like the values I'm assigning to the variables wk1 and wk2 aren't being used in the function.
The goal of the subroutine is to calculate the weekly returns of stocks, given the prices provided in Worksheet "Prices".
I'm not very savvy with VBA so any help is appreciated
Thanks for the help!
Public Sub wklyrtn()
Dim wk1, wk2 As Long
Dim row As Long, column As Long
Dim matrix1(2 To 261, 2 To 11) As Integer
Sheets("Prices").Select
Selection.Activate
For row = 2 To 261
For column = 2 To 11
wk2 = Cells(row, column).Value
wk1 = Cells(row + 1, column).Value
matrix1(row, column) = Rtrn(wk1, wk2)
Next column
Next row
Sheets("Returns").Select
Selection.Activate
For row = 2 To 261
For column = 2 To 11
Cells(row, column) = matrix1(row, column)
Next column
Next row
End Sub
Public Function Rtrn(wk1, wk2)
Dim delt As Long
Application.Volatile True
delt = wk2 - wk1
Rtrn = delt / wk1
End Function
Try this. Not sure what you are trying to do with the Matrix. But this will give you the values you need. You need to refer to an object (your worksheets) instead of using select (always avoid that in general and try to refer to an object instead by using set.
Option Explicit
Public Sub wklyrtn()
Dim wk1 As Long, wk2 As Long
Dim row As Long, column As Long
Dim matrix1(2 To 261, 2 To 11) As variant
Dim wks As Worksheet, wks2 As Worksheet
Set wks = ThisWorkbook.Sheets("Prices")
With wks
For row = 2 To 261
For column = 2 To 11
wk2 = wks.Cells(row, column).Value
wk1 = wks.Cells(row + 1, column).Value
matrix1(row, column) = Rtrn(wk1, wk2)
Next column
Next row
End With
Set wks2 = ThisWorkbook.Sheets("Returns")
With wks2
For row = 2 To 261
For column = 2 To 11
wks2.Cells(row, column) = matrix1(row, column)
Next column
Next row
End With
End Sub
Public Sub Get_Price_Index_Var()
Dim lRow As Long, bCol As Byte 'Avoid naming varaibles same as VBA objects, properties, etc
Dim vResults(2 To 261, 2 To 11) As Variant 'Using variant datatype gives flexibility to hold the result of the operations perform
Dim aResults As Variant 'Used get resulting array to be enter in Returns Worksheet
Dim vVal1 As Variant, vVal2 As Variant
With ThisWorkbook.Sheets("Prices")
For lRow = 2 To 261
For bCol = 2 To 11
vVal1 = .Cells(lRow, bCol).Value
vVal2 = .Cells(1 + lRow, bCol).Value
vResults(lRow, bCol) = fReturns(vVal1, vVal2)
Next: Next: End With
aResults = WorksheetFunction.Index(vResults, 0, 0)
With ThisWorkbook.Sheets("Returns")
.Cells(2, 2).Resize(UBound(aResults), UBound(aResults, 2)).Value = aResults
End With
End Sub
Public Function fReturns(vVal1 As Variant, vVal2 As Variant) As Variant
fReturns = (vVal2 - vVal1) / vVal1
End Function
other than working with Variant variables (as you've already been told), you can take advantage of using arrays to both speed up macro execution and shorten down your code:
Option Explicit
Public Sub wklyrtn()
Const ROWMIN As Long = 2
Const ROWMAX As Long = 261
Const COLMIN As Long = 2
Const COLMAX As Long = 11
Dim row As Long, column As Long
Dim data As Variant, matrix1 As Variant
With Sheets("Prices")
data = .Range(.Cells(ROWMIN, COLMIN), .Cells(ROWMAX + 1, COLMAX)).Value '<--| read all needed values into 'data' array (it needs one row more at the bottom)
End With
ReDim matrix1(1 To ROWMAX - ROWMIN + 1, 1 To COLMAX - COLMIN + 1) As Double '<--| size returned valuse array accordingly to chosen rows and column indexes ranges
For row = 1 To ROWMAX - ROWMIN + 1
For column = 1 To COLMAX - COLMIN + 1
matrix1(row, column) = Rtrn(data(row + 1, column), data(row, column)) '<-- store returned values into 'matrix1' array
Next column
Next row
Sheets("Returns").Cells(2, 2).Resize(ROWMAX - ROWMIN + 1, COLMAX - COLMIN + 1 + 1).Value = matrix1 '<--| write returned values from 'matrix1' array into cells
End Sub
Public Function Rtrn(wk1, wk2)
Dim delt As Long
Application.Volatile True
delt = wk2 - wk1
Rtrn = delt / wk1
End Function
Related
I am working with a schedule, that I have imported and formatted into my workbook.
I am wanting this to populate Phase in the upper listbox and then when a phase is selected the sub-task associated with those phases are displayed in the bottom listbox.
I want to use an array but I seem to be having problems when the columns are not next to each other or there are "gaps" with the blank cells.
My first attempt using assigning the Array to the currentregion worked but brought all columns and fields in. Listbox1 should contain (ID, PHASE NAME, DURATION, START DATE, FINISH DATE) List box 2 should when a Phase is selected contain the subtasks if any from the column to the right, listed before the next next Phase name. (ID, SUB-TASK NAME, DURATION, START DATE, FINISH DATE)
(See picture)
I have code but its more me trouble-shooting than an actual semi working script.
Dim shT As Worksheet
Dim schnumrng1 As Range
Dim schnumrng2 As Range
Dim schnumrng3 As Range
Dim schnumrng4 As Range
Dim schnumrng5 As Range
Dim schpersonrng As Range
Dim schphaserng As Range
Dim schlistrng As Range
Dim maxschnum
Dim schstatus
Dim schperson
Dim schlistnum
Dim Ar() As String
Dim i As Long
Dim j As Long
Dim rng As Range
Dim cl As Range
Dim lc
'allowevents = True
''Set Screen parameters
'Application.ScreenUpdating = False
'Application.EnableEvents = False
'
Worksheets("Schedule").Visible = True
ThisWorkbook.Worksheets("Schedule").Activate
'
Set shT = Worksheets("Schedule")
maxschnum = shT.Cells(shT.Rows.Count, "A").End(xlUp).Row
Set schnumrng = Range("B5", "B" & maxschnum)
'Set Ranges for the list box
Set schnumrng1 = Range("A5", "A" & maxschnum)
Set schnumrng2 = Range("B5", "B" & maxschnum)
Set schnumrng3 = Range("D5", "D" & maxschnum)
Set schnumrng4 = Range("E5", "E" & maxschnum)
Set schnumrng5 = Range("F5", "F" & maxschnum)
'This is static and not moving to the next line in my for statement / switched to named ranges and errors
Set rng = schnumrng1, schnumrng2, schnumrng3, schnumrng4, schnumrng5
'Set rng = Range("A5,B5,D5,E5,F5")
i = 1
j = 1
For Each lc In schnumrng
If lc <> vbNullString Then
For Each cl In rng
ReDim Preserve Ar(1, 1 To i)
Ar(j, i) = cl.Value
i = i + 1
Next cl
Else
End If
j = j + 1
Next lc
With ScheduleForm.SchMainTasklt
.ColumnCount = i - 1
.ColumnWidths = "50;150;50;50;50"
.List = Ar
End With
My problem then is two fold, trying to use the dynamic ranges or another tool Index? collection? to populate the 1st list box. 2. How to deal with blanks and noncontiguous columns when data is not separated for organization purposes.
I don't know if I figured out your intentions well.
First, only the data in column b, not empty cells, is extracted from listbox1.
Second, when listbox1 is selected, data related to listbox2 is collected through the selected listbox value.
Module Code
Place this code in the module. This is because global variables must be used.
Public vDB As Variant
Public Dic As Object 'Dictionary
Sub test()
Dim shT As Worksheet
Dim maxschnum As Long
Dim Ar() As String
Dim i As Long
Dim j As Long
Dim vC() As Variant
Dim cnt As Integer, n As Integer
Dim c As Integer
Dim s As String, s2 As String
Worksheets("Schedule").Visible = True
ThisWorkbook.Worksheets("Schedule").Activate
'
Set Dic = CreateObject("Scripting.Dictionary") 'New Scripting.Dictionary
Set shT = Worksheets("Schedule")
maxschnum = shT.Cells(shT.Rows.Count, "A").End(xlUp).Row
With shT
vDB = .Range("a5", .Range("f" & maxschnum))
End With
'vC is data colum A,B,D,E,F
vC = Array(1, 2, 4, 5, 6)
s2 = vDB(2, 2)
For i = 2 To UBound(vDB, 1)
s = vDB(i, 2) 'column B
If s = "" Then
n = n + 1
Else
If Dic.Exists(s) Then
Else
If i > 2 Then
Dic(s2) = Dic(s2) & "," & n
End If
Dic.Add s, i
s2 = s
cnt = cnt + 1
ReDim Preserve Ar(1 To 5, 1 To cnt)
For c = 0 To UBound(vC)
Ar(c + 1, cnt) = vDB(i, vC(c))
Next c
End If
n = 0
End If
Next i
Dic(s2) = Dic(s2) & "," & n
' Records information about the data in a dictionary.
' Dic is "phase neme" is Key, Item is "2,4"
' example for KICkOFF
' dic key is "KICKOFF", Item is "5,4"
' 5 is KICOFF's row number in array vDB
' 4 is the number of blank cells related to kickoff.
With ScheduleForm.SchMainTasklt
.ColumnCount = 5
.ColumnWidths = "50;150;50;60;60"
.BoundColumn = 2
'.List = Ar
.Column = Ar 'In the state that the array has been converted to row column, you can use listbox.column.
End With
End Sub
Form Code
Private Sub UserForm_Initialize()
Call test
End Sub
Private Sub SchMainTasklt_Click()
Dim s As String, sItem As String
Dim arr As Variant, vC As Variant
Dim vR() As Variant
Dim st As Long, ed As Long
Dim iLast As Long, iFirst As Long
Dim i As Long, n As Integer
Dim j As Integer
vC = Array(1, 3, 4, 5, 6) 'data colums A,C,D,E,F
s = SchMainTasklt.Value
'MsgBox s
sItem = Dic(s)
arr = Split(sItem, ",")
st = Val(arr(0))
ed = Val(arr(1))
iFirst = st + 1
iLast = st + ed
If ed = 0 Then
MsgBox "no data!!"
Exit Sub
End If
For i = iFirst To iLast
n = n + 1
ReDim Preserve vR(1 To 5, 1 To n)
For j = 0 To UBound(vC)
vR(j + 1, n) = vDB(i, vC(j))
Next j
Next i
With ListBox2
.ColumnCount = 5
.ColumnWidths = "50;150;50;60;60"
.BoundColumn = 2
.Column = vR
End With
End Sub
Result Image
When you click the "KICKOFF" , Show kickoff related data in listbox2.
I have a form where users enter the name of a project and the type of transaction.
I have written a macro that returns a selection of data from a table based on the name of the project the user entered, and it works perfectly.
Now I need to add in a function that reverses the order of that same list if the user enters a specific transaction type, it reverses the order of the same list of data.
For example, if type A returns:
Bob
Jerry
Andrew
Jeff
Then type B would reverse that order and return:
Jeff
Andrew
Jerry
Bob
The VBA I wrote for the first portion, to return the list based on project name is:
Sub finddata()
Dim projectName As String
Dim transactionType As String
Dim finalRow As Integer
Dim i As Integer
Sheets("Template_Test").Range("G10:I38").ClearContents
projectName = Sheets("Template_Test").Range("E10").Value
finalRow = Sheets("Project_Structure").Range("A20000").End(xlUp).Row
transactionType = Sheets("Template_Test").Range("E14").Value
For i = 2 To finalRow
Sheets("Project_Structure").Activate
If Cells(i, 1) = projectName Then
Sheets("Project_Structure").Range(Cells(i, 2), Cells(i, 4)).Copy
Sheets("Template_Test").Activate
Sheets("Template_Test").Range("G100").End(xlUp).Offset(1, 0).PasteSpecial xlPasteFormulasAndNumberFormats
End If
Next i
Sheets("Template_Test").Range("E10").Select
End Sub
I can get the selection to reverse order using the built in vba function strReverse and a specific range, but my data is not a consistent length of cells - sometimes it's 6 names and sometimes it's 15 - and I can't figure out how to get it to adjust the length it needs to reverse without including blank cells underneath the range.
Here is a method using the .Reverse method of ArrayList object
Option Explicit
Public Sub ReverseAList()
Dim ws As Worksheet, arr(), i As Long, aList As Object, lastRow As Long
Set ws = ThisWorkbook.Worksheets("Sheet1")
Set aList = CreateObject("System.Collections.ArrayList")
With ws
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row 'whichever column is required to determine last row. Assumes there are headers in row1
If lastRow = 2 Then arr(0) = .Range("A2").Value
arr = .Range("A2:A" & lastRow).Value
For i = LBound(arr, 1) To UBound(arr, 1)
aList.Add arr(i, 1)
Next i
aList.Reverse
For i = 0 To aList.Count - 1
arr(i + 1, 1) = aList(i)
Next
.Cells(2, 2).Resize(aList.Count, 1) = arr
End With
End Sub
Data and output
Same thing re-writing a sub by Ryan Wells as a function:
Public Sub ReverseAList2()
Dim ws As Worksheet, arr(), i As Long, aList As Object, lastRow As Long
Set ws = ThisWorkbook.Worksheets("Sheet1")
With ws
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row 'whichever column is required to determine last row. Assumes there are headers in row1
If lastRow = 2 Then arr(0) = .Range("A2").Value
arr = .Range("A2:A" & lastRow).Value
.Cells(2, 2).Resize(UBound(arr), 1) = ReverseArray(arr)
End With
End Sub
Public Function ReverseArray(vArray As Variant) As Variant
Dim vTemp As Variant, i As Long, iUpper As Long, iMidPt As Long
iUpper = UBound(vArray, 1)
iMidPt = (UBound(vArray, 1) - LBound(vArray, 1)) \ 2 + LBound(vArray)
For i = LBound(vArray) To iMidPt
vTemp = vArray(iUpper, 1)
vArray(iUpper, 1) = vArray(i, 1)
vArray(i, 1) = vTemp
iUpper = iUpper - 1
Next i
ReverseArray = vArray
End Function
I am working on a list and doing all the calculations on VBA however when i want to write my list to the predefined range i get nothing. The following is a an example of the code i'm using. I am not posting the actual code because it's long however this example has the same problem.
Option Explicit
Sub readArray()
Dim CoGrade() As Variant
Dim LastRow As Integer
Dim NPSeQuedan() As Variant
Dim SeQuedanRng As Range
'erases information from arrays if there was any
Erase CoGrade
Erase NPSeQuedan
'-------------------------------------------------------------------------
'find the last row on the data i want to read
LastRow = Range("b10000").End(xlUp).Row
'the relevant data starts on row 34
ArrayRows = LastRow - 34 + 1
'redifines the variables with the total numbers of stocks in the portfolio
ReDim CoGrade(ArrayRows, 1)
ReDim NPSeQuedan(ArrayRows, 1)
'reads each relevant number into its proper variable
CoGrade = Range(Cells(34, 2), Cells(LastRow, 2))
'' test
Set SeQuedanRng = Range(Cells(34, 13), Cells(34 + ArrayRows - 1,
13))
For a = 1 To ArrayRows
NPSeQuedan(a, 1) = CoGrade(a, 1)
Next
SeQuedanRng.Value = NPSeQuedan
'''
end sub
Here is another solution (though #SJR 's idea of using 1-dimensional arrays is good). I added various points about your original code in the comments to the code:
Sub readArray()
Dim CoGrade As Variant 'Don't bother with ()
Dim LastRow As Long 'Integer risks overflow
Dim A As Long, ArrayRows As Long 'you use these -- so declare it
Dim NPSeQuedan As Variant 'etc.
Dim SeQuedanRng As Range
'erases information from arrays if there was any
'Erase CoGrade -- VBA is garbage collected and these have just been declared, so 100% pointless
'Erase NPSeQuedan
'-------------------------------------------------------------------------
'find the last row on the data i want to read
LastRow = Cells(Rows.Count, "B").End(xlUp).Row 'why hard-wire in 10000?
'the relevant data starts on row 34
ArrayRows = LastRow - 34 + 1
'redifines the variables with the total numbers of stocks in the portfolio
'ReDim CoGrade(ArrayRows, 1) -- pointless
ReDim NPSeQuedan(1 To ArrayRows, 1 To 1) 'this is important for what you are doing
'reads each relevant number into its proper variable
CoGrade = Range(Cells(34, 2), Cells(LastRow, 2)).Value
'' test
Set SeQuedanRng = Range(Cells(34, 13), Cells(34 + ArrayRows - 1, 13))
For A = 1 To ArrayRows
NPSeQuedan(A, 1) = CoGrade(A, 1)
Next
SeQuedanRng.Value = NPSeQuedan 'works now!
End Sub
You can do it like this, which incorporates several of the comments made by John Coleman.
Sub readArray()
Dim CoGrade As Variant
Dim LastRow As Long, ArrayRows as Long, a as Long
Dim NPSeQuedan() As Variant
Dim SeQuedanRng As Range
'find the last row on the data i want to read
LastRow = Range("b10000").End(xlUp).Row
'the relevant data starts on row 34
ArrayRows = LastRow - 34 + 1
'redifines the variables with the total numbers of stocks in the portfolio
ReDim NPSeQuedan(1 To ArrayRows)
'reads each relevant number into its proper variable
CoGrade = Range(Cells(34, 2), Cells(LastRow, 2))
Set SeQuedanRng = Range(Cells(34, 13), Cells(34 + ArrayRows - 1, 13))
For a = 1 To ArrayRows
NPSeQuedan(a) = CoGrade(a, 1)
Next
SeQuedanRng.Value = Application.Transpose(NPSeQuedan)
End Sub
I don't know why this function doesn't loop through the worksheets , what am I missing ?
I've gone through Almost every resource I can find both on stack overflow and Google but could not find an answer that I could implement.
I've tried looping through worksheet numbers however that didn't work so I am now attempting to loop through worksheet names. This also does not work.
I'm pretty sure it's a small error but I could not find the cause after days of searching.
Sub CreateUniquesList()
Dim WS_Count As Integer 'number of WorkSheets
Dim Sheet As Integer 'WorkSheet number
Dim Uniques() As String 'Array of all unique references
Dim UniquesLength As Integer
Dim size As Integer 'number of items to add to Uniques
Dim Row As Integer 'row number
Dim Column As Variant 'column number
Dim Columns As Variant
Dim blanks
Dim LastRow As Integer
Dim i As Integer
Dim wks As Variant, wksNames() As String
WS_Count = ActiveWorkbook.Worksheets.Count
ReDim wksNames(WS_Count - 1)
i = 0
For Each wks In Worksheets
wksNames(i) = wks.Name
i = i + 1
Next
Columns = Array(3, 4, 8, 11, 12, 17, 18)
ReDim Uniques(0)
Uniques(0) = "remove this item"
WS_Count = ActiveWorkbook.Worksheets.Count
' For Sheet = 1 To WS_Count
For Each wks In wksNames
For Each Column In Columns
' LastRow = ActiveWorkbook.Worksheets(Sheet).Cells(Rows.Count, Column).End(xlUp).Row
' size = WorksheetFunction.CountA(Worksheets(Sheet).Columns(Column)) - 1
LastRow = ActiveWorkbook.Worksheets(wks).Cells(Rows.Count, Column).End(xlUp).Row
size = WorksheetFunction.CountA(Worksheets(wks).Columns(Column)) - 1
UniquesLength = UBound(Uniques) - LBound(Uniques) + 1
ReDim Preserve Uniques(UniquesLength + size - 1)
blanks = 0
i = 1
For Row = LastRow To 2 Step -1
If Cells(Row, Column).Value <> "" Then
Uniques(UniquesLength + i - 1 - blanks) = Cells(Row, Column).Value
Else
blanks = blanks + 1
End If
i = i + 1
Next Row
Next Column
Next wks
' Next Sheet
'remove first unique element
For i = 1 To UBound(Uniques)
Uniques(i - 1) = Uniques(i)
Next i
ReDim Preserve Uniques(UBound(Uniques) - 1)
End Sub
I took a look at the code and have rewritten a fair portion of it as I don't think a lot of it was necessary (probably leftover from your attempts to make things work). Try this, and if you don't understand any of it, post a comment and I'll explain further.
Sub CreateUniquesList()
Dim Uniques() As String 'Array of all unique references
Dim Row As Integer 'row number
Dim Column As Variant 'column number
Dim Columns As Variant
Dim LastRow As Integer
Dim wks As Worksheet
Columns = Array(3, 4, 8, 11, 12, 17, 18)
ReDim Uniques(0)
For Each wks In ThisWorkbook.Worksheets
For Each Column In Columns
LastRow = wks.Cells(wks.Rows.Count, Column).End(xlUp).Row
For Row = LastRow To 2 Step -1
If wks.Cells(Row, Column).Value <> "" Then
Uniques(UBound(Uniques)) = wks.Cells(Row, Column).Value ' set the last element of the array to the value
ReDim Preserve Uniques(UBound(Uniques)+1) ' increment the size of the array
End If
Next Row
Next Column
Next wks
' lose the last element of the array as it's one larger than it needs to be
ReDim Preserve Uniques(UBound(Uniques) - 1)
End Sub
Try this
WS_Count = ActiveWorkbook.Worksheets.Count
' For Sheet = 1 To WS_Count
For Each wks In Worksheets
For Each Column In Columns
'LastRow = ActiveWorkbook.Worksheets(Sheet).Cells(Rows.Count,column).End(xlUp).Row
'size = WorksheetFunction.CountA(Worksheets(Sheet).Columns(Column)) - 1
LastRow = ActiveWorkbook.Worksheets(wks.Name).Cells(Rows.Count,Column).End(xlUp).Row
size = WorksheetFunction.CountA(Worksheets(wks.Name).Columns(Column)) - 1
UniquesLength = UBound(Uniques) - LBound(Uniques) + 1
ReDim Preserve Uniques(UniquesLength + size - 1)
blanks = 0
i = 1
For Row = LastRow To 2 Step -1
If Cells(Row, Column).Value <> "" Then
Uniques(UniquesLength + i - 1 - blanks) = Cells(Row, Column).Value
Else
blanks = blanks + 1
End If
i = i + 1
Next Row
Next Column
Next wks
I am assigning numbers their order in which they appear in the list and i do that using countif function in excel something like this,
=COUNTIF(A$2:A2,A2)
Number Count
10 1
10 2
10 3
11 1
11 2
11 3
12 1
I wish to achieve the same using VBA. However, here are the specifics.
I want to take a variable and compute the countif function and then loop them through.
Once the variable has all numbers(array) I want to paste them in a location.
Assuming column A is sorted as per your list above you could use the following.
Dim arr(100,1) as double '100 = arbitrary number for this example
dim n as double
n=1
arr(roW,0) = Cell(roW + 2, 1).value
arr(roW,1) = n
For roW = 1 to 100
IF Cell(roW + 2, 1).value = Cell(roW + 1, 1).value Then
n = Cell(roW + 2, 1).value
Else
n=1
End if
arr(roW,0) = Cell(roW + 2, 1).value
arr(roW,1) = n
Next
Range("C2:D102")=arr
And another option,
Sub GetUniqueAndCountif()
Dim cUnique As Collection
Dim Rng As Range
Dim Cell As Range, nW As Range
Dim sh As Worksheet
Dim vNum As Variant
Set sh = ThisWorkbook.Sheets("Sheet1")
Set Rng = sh.Range("A2", sh.Range("A2").End(xlDown))
Set cUnique = New Collection
On Error Resume Next
For Each Cell In Rng.Cells
cUnique.Add Cell.Value, CStr(Cell.Value)
Next Cell
On Error GoTo 0
For Each vNum In cUnique
Set nW = Cells(Rows.Count, "C").End(xlUp).Offset(1, 0)
nW = vNum
nW.Offset(, 1) = WorksheetFunction.CountIf(Rng, nW)
Next vNum
End Sub
The following code evaluates the results as a single array formula and assigns this to a varaiable v. You can adapt references and add variable declarations as needed.
Sub CountifArray()
v = Evaluate(Replace("INDEX(COUNTIF(OFFSET(y,,,ROW(y)-MIN(ROW(y))+1),y),)", "y", "A2:A8"))
Range("B2:B8") = v
End Sub
This is my suggestion.
Sub Counts()
Dim ws As Worksheet
Set ws = ThisWorkbook.ActiveSheet
Dim lngLastRow As Long
lngLastRow = ws.UsedRange.Rows.Count
Dim Arr() As Variant
'Taking values in column A into an array
Arr = ws.Range("A2:A" & lngLastRow).Value
Dim Arr2() As Variant
'another Array for Countif results
ReDim Arr2(lngLastRow - 2, 0)
Dim count As Long
Dim i As Long, j As Long 'counters
'counting
For i = LBound(Arr) To UBound(Arr)
count = 0
For j = LBound(Arr) To i
If Arr(j, 1) = Arr(i, 1) Then count = count + 1
Next
'filling the array with results
Arr2(i - 1, 0) = count
Next
'sending results back to the worksheet
ws.Range("B2:B" & lngLastRow).Value = Arr2
Set ws = Nothing
End Sub