Calculate standard deviation of same text values in same column - vba

I am trying to write a macro in Excel to calculate the standard deviation of same text in column A taking the values from column B and giving the results in column C:
I did it manually by putting the equation=STDEV.S(A2;A3;A4;A16)for "aaa". But I need to do this automatically because I am doing another calculation and procedures which are completing by macros. Here is my code:
Option Explicit
Sub Main()
CollectArray "A", "D"
DoSum "D", "E", "A", "B"
End Sub
' collect array from a specific column and print it to a new one without duplicates
' params:
' fromColumn - this is the column you need to remove duplicates from
' toColumn - this will reprint the array without the duplicates
Sub CollectArray(fromColumn As String, toColumn As String)
ReDim arr(0) As String
Dim i As Long
For i = 1 To Range(fromColumn & Rows.Count).End(xlUp).Row
arr(UBound(arr)) = Range(fromColumn & i)
ReDim Preserve arr(UBound(arr) + 1)
Next i
ReDim Preserve arr(UBound(arr) - 1)
RemoveDuplicate arr
Range(toColumn & "1:" & toColumn & Range(toColumn & Rows.Count).End(xlUp).Row).ClearContents
For i = LBound(arr) To UBound(arr)
Range(toColumn & i + 1) = arr(i)
Next i
End Sub
' sums up values from one column against the other column
' params:
' fromColumn - this is the column with string to match against
' toColumn - this is where the SUM will be printed to
' originalColumn - this is the original column including duplicate
' valueColumn - this is the column with the values to sum
Private Sub DoSum(fromColumn As String, toColumn As String, originalColumn As String, valueColumn As String)
Range(toColumn & "1:" & toColumn & Range(toColumn & Rows.Count).End(xlUp).Row).ClearContents
Dim i As Long
For i = 1 To Range(fromColumn & Rows.Count).End(xlUp).Row
Range(toColumn & i) = WorksheetFunction.SumIf(Range(originalColumn & ":" & originalColumn), Range(fromColumn & i), Range(valueColumn & ":" & valueColumn))
Next i
End Sub
Private Sub RemoveDuplicate(ByRef StringArray() As String)
Dim lowBound$, UpBound&, A&, B&, cur&, tempArray() As String
If (Not StringArray) = True Then Exit Sub
lowBound = LBound(StringArray): UpBound = UBound(StringArray)
ReDim tempArray(lowBound To UpBound)
cur = lowBound: tempArray(cur) = StringArray(lowBound)
For A = lowBound + 1 To UpBound
For B = lowBound To cur
If LenB(tempArray(B)) = LenB(StringArray(A)) Then
If InStrB(1, StringArray(A), tempArray(B), vbBinaryCompare) = 1 Then Exit For
End If
Next B
If B > cur Then cur = B
tempArray(cur) = StringArray(A)
Next A
ReDim Preserve tempArray(lowBound To cur): StringArray = tempArray
End Sub
It would be nice if someone could please give me an idea or solution. The above code is for calculating the summation of same text values. Is there any way to modify my code to calculate the standard deviation?

I went in a different direction and provided a pseudo-STDEV.S.IF to be used much like the COUNTIF or AVERAGEIF function.
Function STDEV_S_IF(rAs As Range, rA As Range, rBs As Range)
Dim a As Long, sFRM As String
sFRM = "STDEV.s("
Set rBs = rBs(1).Resize(rAs.Rows.Count, 1)
For a = 1 To rAs.Rows.Count
If rAs(a).Value2 = rA.Value2 Then
sFRM = sFRM & rBs(a).Value2 & Chr(44)
End If
Next a
sFRM = Left(sFRM, Len(sFRM) - 1) & Chr(41)
STDEV_S_IF = Application.Evaluate(sFRM)
End Function
Syntax: STDEV_S_IF(<criteria range>, <criteria>, <stdev.s values>)
In your sample, the formula in C2 would be,
=STDEV_S_IF(A$2:A$20, A2, B$2:B$20)
Fill down as necessary.
    

Here is a formula and VBA route that gives you the STDEV.S for each set of items.
Picture shows the various ranges and results. My input is the same as yours, but I accidentally sorted it at one point so they don't line up.
Some notes
ARRAY is the actual answer you want. NON-ARRAY showing for later.
I included the PivotTable to test the accuracy of the method.
VBA is the same answer as ARRAY calculated as a UDF which could be used elsewhere in your VBA.
Formula in cell D3 is an array formula entered with CTRL+SHIFT+ENTER. That same formula is in E3 without the array entry. Both have been copied down to the end of the data.
=STDEV.S(IF(B3=$B$3:$B$21,$C$3:$C$21))
Since it seems you need a VBA version of this, you can use the same formula in VBA and just wrap it in Application.Evaluate. This is pretty much how #Jeeped gets an answer, converting the range to values which meet the criteria.
VBA Code uses Evaluate to process a formula string built from the ranges given as input.
Public Function STDEV_S_IF(rng_criteria As Range, rng_criterion As Range, rng_values As Range) As Variant
Dim str_frm As String
'formula to reproduce
'=STDEV.S(IF(B3=$B$3:$B$21,$C$3:$C$21))
str_frm = "STDEV.S(IF(" & _
rng_criterion.Address & "=" & _
rng_criteria.Address & "," & _
rng_values.Address & "))"
'if you have more than one sheet, be sure it evalutes in the right context
'or add the sheet name to the references above
'single sheet works fine with just Application.Evaluate
'STDEV_S_IF = Application.Evaluate(str_frm)
STDEV_S_IF = Sheets("Sheet2").Evaluate(str_frm)
End Function
The formula in F3 is the VBA UDF of the same formula as above, it is entered as a normal formula (although entering as an array does not affect anything) and is copied down to the end.
=STDEV_S_IF($B$3:$B$21,B3,$C$3:$C$21)
It is worth noting that .Evaluate processes this correctly as an array formula. You can compare this against the NON-ARRAY column included in the output. I am not certain how Excel knows to treat it this way. There was previously a fairly extended conversion about how Evaluate process array formulas and determines the output. This is tangentially related to that conversation.
And for completeness, here is the test of the Sub side of things. I am running this code in a module with a sheet other than Sheet2 active. This emphasizes the ability of using Sheets("Sheets2").Evaluate for a multi-sheet workbook since my Range call is technically misqualified. Console output is included.
Sub test()
Debug.Print STDEV_S_IF(Range("B3:B21"), Range("B3"), Range("C3:C21"))
'correctly returns 206.301357242263
End Sub

Related

Cycle through datasets, columns and then rows to add comments based on other cells

I'm trying to make a function to do the following:
Cycle through all my datasets in my sheet
Cycle through each column in my datasets
Look at the title for that column and check if it is in my list.
Find find a few various other columns, but this time using .Find
Now cycle through each row in the column for that specific dataset
Use the column references found in point 4 and the row from point 5 to put the cell's into a variable that will be used on step 7 which is to insert a formatted comment in the originally found column (for that row).
I've tried getting some code working from what I found on a different site but I can't get it working correct, I'm stuck at part 5.
A data example could look like:
My attempted code looks like:
Sub ComTest()
COMLIST = ";Cond;"
Set rng = Range("A1:A" & Cells(Rows.Count, "A").End(xlUp).Row)
For Each a In rng.SpecialCells(xlCellTypeConstants).Areas
With a.CurrentRegion
Set r = .Rows(1)
For j = 1 To r.Columns.Count
TitleCell = r.Cells(j).Address
v = ";" & Range(TitleCell).Value & ";"
'-----------------------------------------------------------------------------------------
If InStr(1, COMLIST, v) Then
On Error Resume Next
xRange = .Offset(1).Resize(.Rows.Count - 1).Columns(j).Address
For i = 1 To UBound(xRange)
v = b.Value
Next i
Condw = r.Columns.Find(Replace(v, ";", "") & " " & "w", lookAt:=xlWhole).Column
Condw = .Cells(r, Condw).Address
' Add more stuff here
End If
'-----------------------------------------------------------------------------------------
Next j
End With
Next a
End Sub
As for part 7, the output would essentially be as follows for "row 1" but this part I should be able to do, it's the looping part that I am struggling with.
This question raises a few points that this answer might resolve for you and others in the future:
I note that not many of your previous questions have accepted answers, and that several of them present answers but you have needed to respond by saying it doesn't suit your needs for a certain reason. It suggests you aren't really providing the right details in your question. I think that's the case here. Perhaps you could outline the outcome you are trying to achieve and, especially for Excel VBA, the precise structure of your spreadsheet data. It's tempting to think in this question that you simply want to know how to take the values of Columns C to F and write them to a comment in Column B for any row that contains data.
Using web code can often take more time to understand and adapt than learning the code syntax from first principles. Your provided code is difficult to follow and some parts seem odd. I wonder, for example, what this snippet is meant to do:
xRange = .Offset(1).Resize(.Rows.Count - 1).Columns(j).Address
For i = 1 To UBound(xRange)
v = b.Value
Next i
Using Option Explicit at the top of your module (which forces you to declare your variables) makes VBA coding and debugging much easier, and code submitted on SO is easier to follow if we can see what data types you meant variables to hold.
If your question is merely "How do I take the values of Columns C to F and write them to the cell in Column B for any row that contains data?", then your code could be as simple as:
Dim condCol As Range
Dim cell As Range
Dim line1 As String
Dim line2 As String
Dim cmt As Comment
'Define the "Cond" column range
'Note: this is an unreliable method but we'll use it here for the sake of brevity
Set condCol = ThisWorkbook.Worksheets("Sheet1").UsedRange.Columns("B")
'Delete any comment boxes
condCol.ClearComments
'Loop through the cells in the column and process the data if it's a number
For Each cell In condCol.Rows
If Not IsEmpty(cell.Value) And IsNumeric(cell.Value) Then
'Acquire the comment data
line1 = "Cond: " & cell.Offset(, 1).Value & "/" & cell.Offset(, 2).Value & _
" (" & Format(cell.Offset(, 3), "0.00%") & ")"
line2 = "Cond pl: $" & cell.Offset(, 4).Value
Set cmt = cell.AddComment(line1 & vbCrLf & line2)
'Format the shape
With cmt.Shape.TextFrame
.Characters(1, 5).Font.Bold = True
.Characters(Len(line1 & vbCrLf), 8).Font.Bold = True
.AutoSize = True
End With
End If
Next
If, on the other hand, your question is that you have unreliable data on your spreadsheet and your only certainty is that the headings exist on any one row, then some form of search routine must be added. In that case your code could look like this:
Dim rng As Range
Dim rowRng As Range
Dim cell As Range
Dim condCol(0 To 4) As Long
Dim line1 As String
Dim line2 As String
Dim allHdgsFound As Boolean
Dim i As Integer
Dim cmt As Comment
Set rng = ThisWorkbook.Worksheets("Sheet1").UsedRange
rng.ClearComments
For Each rowRng In rng.Rows
If Not allHdgsFound Then
'If we haven't found the headings,
'loop through the row cells to try and find them
For Each cell In rowRng.Cells
Select Case cell.Value
Case Is = "Cond": condCol(0) = cell.Column
Case Is = "Cond w": condCol(1) = cell.Column
Case Is = "Cond r": condCol(2) = cell.Column
Case Is = "Cond %": condCol(3) = cell.Column
Case Is = "Cond wpl": condCol(4) = cell.Column
End Select
Next
'Check if we have all the headings
'by verifying the condCol array has no 0s
allHdgsFound = True
For i = 0 To 4
If condCol(i) = 0 Then
allHdgsFound = False
Exit For
End If
Next
Else
If Not IsEmpty(rowRng.Cells(1).Value) Then
'The cell has values so populate the comment strings
line1 = "Cond: " & rowRng.Columns(condCol(1)).Value & "/" & _
rowRng.Columns(condCol(2)).Value & _
" (" & Format(rowRng.Columns(condCol(3)).Value, "0.00%") & ")"
line2 = "Cond pl: $" & rowRng.Columns(condCol(4))
Set cmt = rowRng.Columns(condCol(0)).AddComment(line1 & vbCrLf & line2)
'Format the shape
With cmt.Shape.TextFrame
.Characters(1, 5).Font.Bold = True
.Characters(Len(line1 & vbCrLf), 8).Font.Bold = True
.AutoSize = True
End With
Else
'We've reached a blank cell so re-set the found values
allHdgsFound = False
Erase condCol
End If
End If
Next
Of course your data might be structured in any number of other ways, but we don't know that. My point is that if you can be more specific in your question and provide an outcome you are trying to achieve, you are likely to receive answers that are more useful to you.

Merging a range of excel cells in a column into one single cell seperated by new lines

I need help with excel.
I have a column with hundreds of cells that I need to combine into one cell.
The values in the cells are already centered. Also, some cells have multiple values that are stacked on top of each other using (ALT + ENTER).
I need to choose a range of these cells and combine them and stack them on top of each other into one cell.
If I can also get rid of extra "new lines" between the values as well as repeated values that would be an added bonus.
Here is a picture of what it looks like and what I'm aiming at. I've been trying to learn vbscript and macros, but this is on a bit of a deadline. I appreciate the help.
The following shows you how to combine all numbers from a column into a single cell in VBA Excel, which is what I assume the coding language you are using.
There are two Procedures I use: 1) a columnCombine() Sub and 2) a Custom Split() Function courtesy of Wade Tai of Microsoft
Link to Wade's Article with Split Function: http://msdn.microsoft.com/en-us/library/aa155763%28office.10%29.aspx
columnCombine() Sub:
Sub columnCombine()
'variables needed:
Dim col As Integer
Dim startRow As Integer
Dim endRow As Integer
Dim firstCell As Range
Dim lastCell As Range
Dim i As Integer
Dim s As Variant
Dim destinationCell As Range
Dim strg As Variant
Dim strgTemp() As String
'enter first and last cells of column of interest in the "A1/A2/A3..." format below:'
Set firstCell = Range("A1") 'this can be what you want
Set lastCell = Range("A3") 'this can be what you want
'enter destination cell in same format as above
Set destinationCell = Range("B1") 'this can be what you want
'get column of interest
col = firstCell.Column
'get start row and end row
startRow = firstCell.Row
endRow = lastCell.Row
'set temp string
strg = ""
For i = startRow To endRow
strgTemp = Split(Worksheets("Sheet1").Cells(i, col).Value)
For Each s In strgTemp
If strg = "" Then
strg = s
Else
strg = strg & vbNewLine & s
End If
Next s
Erase strgTemp
Next i
'add column to string
destinationCell.Value = strg
End Sub
Split() Function:
Public Function Split(ByVal InputText As String, _
Optional ByVal Delimiter As String) As Variant
' This function splits the sentence in InputText into
' words and returns a string array of the words. Each
' element of the array contains one word.
' This constant contains punctuation and characters
' that should be filtered from the input string.
Const CHARS = "!?,;:""'()[]{}"
Dim strReplacedText As String
Dim intIndex As Integer
' Replace tab characters with space characters.
strReplacedText = Trim(Replace(InputText, _
vbTab, " "))
' Filter all specified characters from the string.
For intIndex = 1 To Len(CHARS)
strReplacedText = Trim(Replace(strReplacedText, _
Mid(CHARS, intIndex, 1), " "))
Next intIndex
' Loop until all consecutive space characters are
' replaced by a single space character.
Do While InStr(strReplacedText, " ")
strReplacedText = Replace(strReplacedText, _
" ", " ")
Loop
' Split the sentence into an array of words and return
' the array. If a delimiter is specified, use it.
'MsgBox "String:" & strReplacedText
If Len(Delimiter) = 0 Then
Split = VBA.Split(strReplacedText)
Else
Split = VBA.Split(strReplacedText, Delimiter)
End If
End Function
*UPDATE:
If you desire to use this on multiple different columns with the intention of moving everything to one cell, use this code recursively or in some repetitive manner e.g. write a script that uses columnCombine to combine the column sections you are referencing into different cells in one column. Then run the program again (or as many times as you need) so that you get the data into one cell.
If you want to change the order in which you iterate through a column e.g. you want to iterate from A4 to A1 instead of A1 to A4, just change For i = startRow To endRow to For i = endRow To startRow.
Note this will not change the order of organization of data within an individual cell, only a whole column. In other words, {["hello","Hello"],["One"],["Two", "Three"]} would become {["Two","Three"],["One"],["hello","Hello"]}
To change the order within a cell, you would need to either alter the For Each statement in columnCombine() or
manually change the order of strg. Both of which are not to hard to do.
Here is a solution I would do:
Add this in addition to the current variables :
Dim strg2 As Variant
strg2 = ""
Change this code:
For i = startRow To endRow
strgTemp = Split(Worksheets("Sheet1").Cells(i, col).Value)
For Each s In strgTemp
If strg = "" Then
strg = s
Else
strg = strg & vbNewLine & s
End If
Next s
Erase strgTemp
Next i
'add column to string
destinationCell.Value = strg
To:
For i = endRow To startRow
strgTemp = Split(Worksheets("Sheet1").Cells(i, col).Value)
For Each s In strgTemp
If strg = "" Then
strg = s
Else
strg = s & vbNewLine & strg
End If
Next s
If strg2 = "" Then
strg2 = strg
Else
strg2 = strg2 & vbNewLine & strg
End If
strg = ""
Erase strgTemp
Next i
'add column to string
destinationCell.Value = strg2
Remember, this change is specific to iterating through items backward and reordering them backwards. The columnCombine() sub will very depending on how you want the data presented

How can I go through all the formulas and array formulas of a worksheet without repeating each array formula many times?

I would like to write a VBA function, which outputs a list of all the single formulas and array formulas of a worksheet. I want an array formula for a range to be printed for only one time.
If I go through all the UsedRange.Cells as follows, it will print each array formula for many times, because it covers several cells, that is not what I want.
For Each Cell In CurrentSheet.UsedRange.Cells
If Cell.HasArray Then
St = Range(" & Cell.CurrentArray.Address & ").FormulaArray = " _
& Chr(34) & Cell.Formula & Chr(34)
ElseIf Cell.HasFormula Then
St = Range(" & Cell.Address & ").FormulaR1C1 = " _
& Chr(34) & Cell.Formula & Chr(34)
End If
Print #1, St
Next
Does anyone have a good idea to avoid this?
You basically need to keep track of what you've already seen. The easy way to do that is to use the Union and Intersect methods that Excel supplies, along with the CurrentArray property of Range.
I just typed this in, so I'm not claiming that it's exhaustive or bug-free, but it demonstrates the basic idea:
Public Sub debugPrintFormulas()
Dim checked As Range
Dim c As Range
For Each c In Application.ActiveSheet.UsedRange
If Not alreadyChecked_(checked, c) Then
If c.HasArray Then
Debug.Print c.CurrentArray.Address, c.FormulaArray
Set checked = accumCheckedCells_(checked, c.CurrentArray)
ElseIf c.HasFormula Then
Debug.Print c.Address, c.Formula
Set checked = accumCheckedCells_(checked, c)
End If
End If
Next c
End Sub
Private Function alreadyChecked_(checked As Range, toCheck As Range) As Boolean
If checked Is Nothing Then
alreadyChecked_ = False
Else
alreadyChecked_ = Not (Application.Intersect(checked, toCheck) Is Nothing)
End If
End Function
Private Function accumCheckedCells_(checked As Range, toCheck As Range) As Range
If checked Is Nothing Then
Set accumCheckedCells_ = toCheck
Else
Set accumCheckedCells_ = Application.Union(checked, toCheck)
End If
End Function
The following code produces output like:
$B$7 -> =SUM(B3:B6)
$B$10 -> =AVERAGE(B3:B6)
$D$10:$D$13 -> =D5:D8
$F$14:$I$14 -> =TRANSPOSE(D5:D8)
I'm using a collection but it could equally well be a string.
Sub GetFormulas()
Dim ws As Worksheet
Dim coll As New Collection
Dim rngFormulas As Range
Dim rng As Range
Dim iter As Variant
Set ws = ActiveSheet
On Error Resume Next
Set rngFormulas = ws.Range("A1").SpecialCells(xlCellTypeFormulas)
If rngFormulas Is Nothing Then Exit Sub 'no formulas
For Each rng In rngFormulas
If rng.HasArray Then
If rng.CurrentArray.Range("A1").Address = rng.Address Then
coll.Add rng.CurrentArray.Address & " -> " & _
rng.Formula, rng.CurrentArray.Address
End If
Else
coll.Add rng.Address & " -> " & _
rng.Formula, rng.Address
End If
Next rng
For Each iter In coll
Debug.Print iter
'or Print #1, iter
Next iter
On Error GoTo 0 'turn on error handling
End Sub
The main difference is that I am only writing the array formula to the collection if the current cell that is being examined is cell A1 in the CurrentArray; that is, only when it is the first cell of the array's range.
Another difference is that I am only looking at cells that contain formulas using SpecialCells, which will be much more efficient than examining the UsedRange.
The only reliable solution I see for your problem is crosschecking each new formula against the ones already considered to make sure that there is no repetition. Depending upon the amount of information and speed expectations you should rely on different approaches.
If the size is not too important (expected number of records below 1000), you should rely on arrays because is the quickest option and its implementation is quite straightforward. Example:
Dim stored(1000) As String
Dim storedCount As Integer
Sub Inspect()
Open "temp.txt" For Output As 1
For Each Cell In CurrentSheet.UsedRange.Cells
If Cell.HasArray Then
St = Range(" & Cell.CurrentArray.Address & ").FormulaArray = " _
& Chr(34) & Cell.Formula & Chr(34)
ElseIf Cell.HasFormula Then
St = Range(" & Cell.Address & ").FormulaR1C1 = " _
& Chr(34) & Cell.Formula & Chr(34)
End If
If(Not alreadyAccounted(St) And storedCount <= 1000) Then
storedCount = storedCount + 1
stored(storedCount) = St
Print #1, St
End If
Next
Close 1
End Sub
Function alreadyAccounted(curString As String) As Boolean
Dim count As Integer: count = 0
Do While (count < storedCount)
count = count + 1
If (LCase(curString) = LCase(stored(count))) Then
alreadyAccounted = True
Exit Function
End If
Loop
End Function
If the expected number of records is much bigger, I would rely on file storage/checking. Relying on Excel (associating the inspected cells to a new range and looking for matches in it) would be easier but slower (mainly in case of having an important number of cells). Thus, a reliable and quick enough approach (although much slower than the aforementioned array) would be reading the file you are creating (a .txt file, I presume) from alreadyAccounted.

Am I using the isnumeric function correctly?

This program is to convert a column of data from cumulative to non-cumulative. On my sheet I have A1, B1, and C1 with the text Non-Cumulative, Cumulative, and Converted, respectively. I have numbers 1 to 10 beneath A1, then them summed cumulatively beneath B1. C1 is where I want to convert column B back to non-cumulative.
The IsNumeric is used to make the first row of data in C equal to the first row of data in B. It should detect that the title is above the number it is evaluating, thus knowing that no calculations have to be performed. For the rest of them, it'll see that the number above the one it is evaluating is a number, and thus the calculation has to be done.
My problem is that it isn't working. I think the reason is because IsNumeric() keeps coming back as false. Is there a different function I should be using? Do cell references not work in IsNumeric?
Here's the program!
Option Explicit
Dim i As Variant
Sub Conversion()
Sheets("Test Sheet").Select
For i = 1 To 10
If IsNumeric("B" & i) = False Then
Range("C" & i + 1) = Range("B" & i + 1)
Else: Range("C" & i + 1) = Range("B" & i + 1) - Range("B" & i - 1)
End If
Next
End Sub
The way you wrote your code is logical, just a minor syntax changes you need initially. However,
It's also best to check if the range is empty first...
Then check on if the value is numeric.
Better even, if you set the Range into a Range object and use offset
Code:
Option Explicit '-- great that you use explicit declaration :)
Sub Conversion()
Dim i As Integer '-- integer is good enough
Dim rngRange as Range
'-- try not to select anything. And for a cleaner code
Set rngRange = Sheets("Test Sheet").Range("B1")
For i = 1 To 10
If (rangeRange.Offset(i,0).value) <> "" then '-- check for non-empty
If IsNumeric(rangeRange.Offset(i,0).value) = False Then
rangeRange.Offset(i+1,1) = rangeRange.Offset(i+1,0)
Else
rangeRange.Offset(i+1,1) = rangeRange.Offset(i+1,0) - rangeRange.Offset(i-1,0)
End If
End if
Next i '-- loop
End Sub
To make your code more dynamic:
Another suggestion, you may simply Application.WorkSheetFunction.Transpose() the entire B column range that you need to validate into a variant array
Process the array and Transpose back to the Range with column B and C.
By doing so, you may omit setting for loop size manually but setting it using Lower and Upper bound of the array ;)
You need to check if the range of B i is numeric, not the string "B" & i
and rather than selecting the sheet, simply using a parent identifier like:
sheets("sheet1").range("B" & i)
This will help you avoid errors in your code
For i = 1 To 10
If IsNumeric(sheets("test sheet").range("B" & i).value) = False Then
Range("C" & i + 1) = Range("B" & i + 1)
Else: Range("C" & i + 1) = Range("B" & i + 1) - Range("B" & i - 1)
End If
Next

Excel VBA - select, get and set data in Table

I've got a worksheet with a lot of tables in them and I'm just starting to use tables because they seem pretty handy. But I've never manipulated content in an Excel table before. And these tables are basically lists of columns with Firstname and Lastname. Based on the values on these columns, I want to generate a username. But I'm trying to write a generic Sub that takes arguments, such as worksheet and name of the table.
Previously I've done this when the data has not been in a table:
Cells(2, 2).Select
Do
strFirstName = ActiveCell.Value
strLastName = ActiveCell.Offset(0, 2).Value
strFirstName = Left(strFirstName, 1)
strUserName = strFirstName & strLastName
strUserName = LCase(strUserName)
ActiveCell.Offset(0, 5).Value = strUserName
ActiveCell.Offset(1, 0).Select
Loop Until IsEmpty(ActiveCell)
And now I'm trying to do the exact same thing, only with data from a Table. Any ideas? I've added a watch for "ActiveSheet" to see if I can find the tables, and they seem to be in ActiveSheet.ListObjects, but I couldn't see any .Select option there. Perhaps I don't need to select the Table in order to manipulate it's content?
When looping over a range (whether in a table or in a range) it is usually faster to copy the data to a variant array, manipulate that array, and then copy the result back to the sheet.
Sub zz()
Dim oUsers As ListObject
Dim v As Variant
Dim vUserName() As Variant
Dim i As Long
Dim colFirst As Long
Dim colLast As Long
Dim colUser As Long
Set oUsers = ActiveSheet.ListObjects(1)
colFirst = oUsers.ListColumns("FirstName").Index
colLast = oUsers.ListColumns("LastName").Index
colUser = oUsers.ListColumns("UserName").Index
v = oUsers.DataBodyRange
ReDim vUserName(1 To UBound(v, 1), 1 To 1)
For i = 1 To UBound(v, 1)
vUserName(i, 1) = LCase(Left(v(i, colFirst), 1) & v(i, colLast))
Next
oUsers.ListColumns("UserName").DataBodyRange = vUserName
End Sub
If you really want to loop over the range itself:
For i = 1 To oUsers.ListRows.Count
oUsers.ListColumns("UserName").DataBodyRange.Rows(i) = LCase(Left( _
oUsers.ListColumns("FirstName").DataBodyRange.Rows(i), 1) & _
oUsers.ListColumns("LastName").DataBodyRange.Rows(i))
Next
For this situation you could also just use a formula in the UserName column itself, with no vba required
=LOWER(LEFT([#FirstName],1)&[#LastName])
EDIT
Sorry, don't know of a Formula way to remove any of a list of characters from a string. You might have to revert to vba for this. Here's a user defined function to do it. Your formula will become
=DeleteChars([#UserName],{"$","#"})
To Delete the characters replace {"$","#"} with a array list of characters you want to remove (you can make the list as long as you need)
To replace the characters use {"$","#";"X","X"} where the list up to the ; is the old characters, after the ; the new. Just make sure the listsa are the same length.
UDF code:
Function DeleteChars(r1 As Range, ParamArray c() As Variant) As Variant
Dim i As Long
Dim s As String
s = r1
If UBound(c(0), 1) = 1 Then
For i = LBound(c(0), 2) To UBound(c(0), 2)
s = Replace(s, c(0)(1, i), "")
Next
Else
For i = LBound(c(0), 2) To UBound(c(0), 2)
s = Replace(s, c(0)(1, i), c(0)(2, i))
Next
End If
DeleteChars = s
End Function