VBA to transpose data based on empty lines - excel

I have an EXTREMELY large data set in excel with varying data sets (some have 12 lines and some with 18, etc) that are currently in rows that needs to be transposed to columns. All the groupings are separated by a empty/blank line.
I started the VBA to transpose this it but dont know how to include/look at the blank line and loop it to the end of each sheet. Any ideas/suggestions?
Range("F1:F12").Select
Selection.Copy
Sheets("Sheet4").Select
Range("A1").Select
Selection.PasteSpecial Paste:=xlPasteAll, Operation:=xlNone, SkipBlanks:= _
False, Transpose:=True
Sheets("Sheet3").Select
Range("F14:F27").Select
Application.CutCopyMode = False
Selection.Copy
Sheets("Sheet4").Select
Range("A2").Select
Selection.PasteSpecial Paste:=xlPasteAll, Operation:=xlNone, SkipBlanks:= _
False, Transpose:=True
Range("G14").Select

Avoid using Select statements at all costs and when possible, use the Array data structure to process data. Processing data in Arrays is much faster than reading/writing from the worksheet. The Procedure below should do what you want. Note that although it's not ideal to use ReDim Preserve in a loop, however, I have used it for row counts of over 100,000 with no issue. Point being, 13,000 rows should be no problem.
Sub Transpose()
Dim Data_Array
Dim OutPut_Array()
Dim LR As Long, Counter As Long, LR2 As Long
Dim i As Long
Application.ScreenUpdating = False
'Find the last row of your data in Sheet3 Column A
'I added 1 so that the conditional statement below
'doesn't exclude the last row of data
With Sheets("Sheet3")
LR = .Cells(Rows.Count, "A").End(xlUp).Row + 1
Data_Array = .Range("A1:A" & LR).Value2
End With
'See explanation in the edit section below
On Error Resume Next
For i = LBound(Data_Array, 1) To UBound(Data_Array, 1)
'if the cell is not blank then increase the counter by one
'and for each non blank cell in the Data_Array,
'add it to the OutPut_Array
'If its not blank then output the prepopulated OutPut_Array to Sheet4 and
'set the counter back to zero
If Trim(Data_Array(i, 1)) <> vbNullString Then
Counter = Counter + 1
ReDim Preserve OutPut_Array(1 To 1, 1 To Counter)
OutPut_Array(1, Counter) = Data_Array(i, 1)
Else
With Sheets("Sheet4")
LR2 = .Cells(Rows.Count, "A").End(xlUp).Row
.Range("A" & LR2 + 1).Resize(1, Counter).Value2 = OutPut_Array
End With
Counter = 0
End If
Next i
End Sub
Test Data:
Result:
This could also be done with a nested dictionary however in this case it would need to be assisted by array to create a one to many relationship using conditional statements, and then transposing the dictionary, but I am still trying to perfect that method so I went with the above, lol. Hope this is helpful.
Edit: Added On Error Resume Next as per OP's request for the procedure to work even if there is more than one blank between the rows of data. In this case On Error Resume Next avoids the Run-time error '1004' Application-defined or Object Defined Error associated with the Range.Resize property. The error is thrown when the if statement is looking at occurences of a blank cells greater than 1. In the else portion of the statement, the counter variable would be equal to 0, thus causing the second dimension of the range to be 0 and throwing the error. If the cells in column A are truly blank as the OP suggests, then this is a valid method to trap the error. Also added the Trim() function to handle blank cells that may have spaces.

Try adapting this.
Sub x()
Dim r As Range
application.screenupdating=false
For Each r In Sheet1.Columns(1).SpecialCells(xlCellTypeConstants).Areas
r.Copy
Sheet2.Range("A" & Rows.Count).End(xlUp)(2).PasteSpecial Transpose:=True
'Sheet2.Cells(1, Columns.Count).End(xlToLeft).Offset(, 1).PasteSpecial Transpose:=True
Next r
application.screenupdating=true
End Sub

Related

Changing cell range to cover all data in column

I created a macro in my excel sheet
The aim of the macro is to copy the cells in one column, one by one (L1,L2...), into a specific cell (A1). then after the calculations are done, copy the value from another cell E2, to the column next to L, meaning to M1, M2...
i couldn't know how to loop these steps to all the cells in the column.
Sub Checking_Frequences()
'
' Checking_Frequences Macro
'
'
Range("L1").Select
Selection.Copy
Range("A1").Select
ActiveSheet.Paste
Range("E2").Select
Application.CutCopyMode = False
Selection.Copy
Range("M1").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Range("L2").Select
Application.CutCopyMode = False
Selection.Copy
Range("A1").Select
ActiveSheet.Paste
Range("E2").Select
Application.CutCopyMode = False
Selection.Copy
Range("M2").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
End Sub
is there a way to add a delay, so that excel finish calculations before copying the result from E2?
any advice?
regards
Your workbook can do with a little organisational upgrading. I may not have done it the way you best like but I think the code below will let you take a big step forward. Install it in a standard code module and run only the procedure WriteArrays. Take time to understand how it works first.
Sub WriteArrays()
' 043
' number of results wanted from each Base
Const Iterations As Integer = 5 ' adjust to suit
Const TgtTab As String = "Sheet3" ' Output tab (change to suit)
Const TgtRow As Long = 2 ' modify to suit
Const TgtClm As Long = 4 ' first output column (modify to suit)
Dim Src As Variant ' array of source Base numbers
Dim R As Long ' SrcRng row counter
Dim WsTgt As Worksheet ' Target worksheet (for output)
Dim Arr As Variant ' value to write to sheet
Dim Operand As Double ' calculated by a formula
Dim i As Long ' loop counter
Operand = 2 ^ (1 / 12) ' = 1.0594630943593 (adjust to suit)
With Worksheets("Frequencies")
' set the range L1:L(last used row) - modify to suit
' read all values into an array
Src = .Range(.Cells(1, "L"), .Cells(.Rows.Count, "L").End(xlUp)).Value
End With
Set WsTgt = Worksheets(TgtTab)
For R = LBound(Src) To UBound(Src)
Arr = BaseArray(Src(R, 1), Operand, Iterations)
With WsTgt.Cells(TgtRow, TgtClm - 1 + R).Resize(UBound(Arr))
.Value = Application.Transpose(Arr)
.NumberFormat = "0.00"
End With
' If R = 5 Then Exit For
Next R
End Sub
Private Function BaseArray(ByVal Base As Double, _
ByVal Operand As Double, _
ByVal Iterations As Integer) As Variant
' 043
Dim Fun As Variant ' function return value
Dim i As Integer
ReDim Fun(1 To Iterations)
For i = LBound(Fun) To UBound(Fun)
Fun(i) = Base
Base = Round(Base * Operand, 2)
Next i
BaseArray = Fun
End Function
There are 4 constants at the top of the code which you will have to set. The last 3 deal with the output. You asked for output in column M on the same sheet. But this code will add 235 columns. So I thought it better to start a new sheet. You can easily run the code multiple times with different parameters and output the results on different sheets. But they must exist before the code is run.
Const Iterations specifies how many rows there will be in each column. You seem to want 50. I tested with only 5. Modify this constant to suit your needs.
A little further down there is the Operand which is the formula taken from your cell C1. It can be changed.
Of course, the tab Frequencies must exist and it must have numbers in column L. You can start from row 2 instead of 1. But if you want to limit the output you may like to avail yourself of the method I used, here: If R = 5 Then Exit For (at the end of the Next ../.. For loop). It just curtails the loop after 5 numbers from the list, if you enable the line by removing the leading apostrophe.
I wish you the best of luck with your venture :-)

VBA Left Function?

I'm relatively new to VBA and have some code I wrote that seems like it should be straightforward but is not behaving as expected. I am trying to separate my primary WorkSheet (GAWi) into three other worksheets (LWi, WMi, & OTi) based on the first letter in column H. Basically if the first letter is "L" I want that row to be copied and pasted onto sheet LWi and then deleted from the original sheet. Then if it is W it goes onto WMi and if it is A it goes onto OTi. It is functioning properly for the first two If statements (placing items that begin with L & W onto the correct sheets), but for the last one items that begin with P and 0 are also being placed onto sheet OTi. I'm at a complete loss, it seems pretty easy and I can't figure out where I went wrong. Any advice would be much appreciated, also I'm sure this code is pretty unelegant by most standards so any tips on how to shorten it would also be welcomed-I've just started getting into VBA in the last couple weeks. Thank so much!
Sheets("GAWi").Select
Columns("H:H").Select
Dim lwr As Range
Set lwr = ActiveSheet.UsedRange
For i = lwr.Cells.Count To 1 Step -1
If Left(lwr.Item(i).Value, 1) = "L" Then
lwr.Item(i).EntireRow.copy
Sheets("LWi").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
ActiveCell.Offset(1, 0).Select
Sheets("GAWi").Select
lwr.Item(i).EntireRow.Delete
End If
If Left(lwr.Item(i).Value, 1) = "W" Then
lwr.Item(i).EntireRow.copy
Sheets("WMi").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
ActiveCell.Offset(1, 0).Select
Sheets("GAWi").Select
lwr.Item(i).EntireRow.Delete
End If
If Left(lwr.Item(i).Value, 1) = "A" Then
lwr.Item(i).EntireRow.copy
Sheets("OTi").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
ActiveCell.Offset(1, 0).Select
Sheets("GAWi").Select
lwr.Item(i).EntireRow.Delete
End If Next i
there's a main flaw in your logic: the use of UsedRange
despite being it a 2D range, its Item() property would act as if it were a 1D array with one row listed after another
so that were "A1:H10" (eight columns) the address of UsedRange, UsedRange.Item(1) would point to "A1", UsedRange.Item(8) would point to "H1" and UsedRange.Item(9) would point to "A2" …
so you have to loop through the cells of column H only
Then there's a coding flaw, which is the use of all those Select/Selection: get in the habit of always use explicit range reference qualified up to their parent worksheet and workbook
. This can be reached, for instance, with the use of With... End With construct
here's a possible code (explanations in comments):
Option Explicit
Sub TransferRows()
Dim i As Long
With Sheets("GAWi") ' reference "source" sheet
For i = .Cells(.Rows.Count, "H").End(xlUp).Row To 1 Step -1 ' loop backwards from referenced sheet column H last not empty cell row index to 1
Select Case UCase(.Cells(i, "H").Value) ' check for referenced sheet column H current row content
Case "L"
TransferRow Intersect(.UsedRange, .Rows(i)), Sheets("LWi") ' pass referenced sheet current row "used" range and "LWi" destination sheet to the helper sub
Case "W"
TransferRow Intersect(.UsedRange, .Rows(i)), Sheets("WMi") ' pass referenced sheet current row "used" range and "WMi" destination sheet to the helper sub
Case "A"
TransferRow Intersect(.UsedRange, .Rows(i)), Sheets("OTi") ' pass referenced sheet current row "used" range and "OTi" destination sheet to the helper sub
End Select
Next i
End With
End Sub
Sub TransferRow(sourceRng As Range, destSht As Worksheet)
With destSht
.Cells(.Rows.Count, 1).End(xlUp).Offset(1).Resize(, sourceRng.Columns.Count).Value = sourceRng.Value
End With
sourceRng.Delete xlUp
End Sub
As you see, other than the amendements due to the preface explanations I put in there:
the use of Select Case syntax instead of If Then End If
which I think is much clearer and would also correct a minor logic flaw of your orginal code: once a check is positive there's no need to run other ones (this you could have obtained by means of If - Then - ElseIf - Endif construct)
the use of a "helper" sub to demand the repetitive code to
which gives you much more control over your code and helps its maintenance
the use of Cells(Rows.Count, colIndex).End(xlUp) pattern
which is the most frequently used one to get the reference to the last not empty cell in some colIndex (be it a number or a letter) column
Thanks to HTH's great response I was able to clean up my code a bit and think I got it figured out. I opted to stick with the If Then Else If format since I am not too familiar with using Case yet. Here's the first section of it, I just repeated the copy, paste, delete row for each starting letter.
Set rng = Range("GAWi!H:H")
For k = rng.Cells.Count To 1 Step -1
If Left(rng.Item(k).Value, 1) = "W" Then
With rng.Item(k)
.EntireRow.copy
Sheets("WMi").Activate
ActiveCell.Offset(1, 0).PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
.EntireRow.Delete
End With
ElseIf Left(rng.Item(k).Value, 1) = "L" Then....
This is running well for my purposes but if anyone has more suggestions they are much appreciated.

Setting a dynamic paste range within a nested loop

I have code that creates a bunch of new sheets, names them, and then loops through them searching a dataset for the name of the sheet and transposing data rows with a value matching the name of the sheet.
I've gotten it to work transposing each row to the next column to the right, but for printing purposes, I'd like it to move to the bottom of the last pasted cell, skip a row (or better yet, insert a page break), and then paste the next one.
Something about the way I've tried to tell it to count the rows, move down, and then start again, isn't working. It appears to be pasting multiple times over previously pasted data.
I've tried several different ways of counting the rows and adding a row, or inserting a page break, but I can't get it working. I thought maybe I needed to move the rowcount function out of the IF statement, but that didn't work either.
Sub Franchise_Data4()
'searches Raw Data sheet for the Franchise ID associated with each sheet name; then transposes each relevant row onto the associated sheet'
Dim Scol As Range, Cell As Object, rawdata As Worksheet, ws As Worksheet, lc As Long, rowcountA As Integer, startR As Integer, labels As Range
Set rawdata = ThisWorkbook.Worksheets("Raw Data")
Set Scol = rawdata.Range("$C$2:$C$2000") 'Franchise ID column on Raw Data sheet'
Set labels = ThisWorkbook.Worksheets("Raw Data").Range("A1:AZ1")
For Each ws In ThisWorkbook.Worksheets
If ws.Name <> "Raw Data" And ws.Name <> "Pivot" Then
With ws 'cycles through all of the sheets with Franchise ID's as the name
startR = 0
For Each Cell In Scol 'should scan the C column on the Raw Data sheet'
If IsEmpty(Cell) Then Exit For
If Cell.Value = ws.Name Then 'checks for cells that contain the same Franchise ID as the current sheet in the cycle'
Cell.EntireRow.Copy
ws.Cells(startR + 1, 2).PasteSpecial Paste:=xlPasteValuesAndNumberFormats, Operation:= _
xlNone, SkipBlanks:=False, Transpose:=True
labels.Copy
ws.Cells(startR + 1, 1).PasteSpecial Paste:=xlPasteValuesAndNumberFormats, Operation:= _
xlNone, SkipBlanks:=False, Transpose:=True
End If
rowcountA = Cells(Cells.Rows.Count, "A").End(xlUp).Row
startR = rowcountA + 1
Next
End With
End If
Next ws
Application.CutCopyMode = False
End Sub
It appears to paste the first data set correctly, then move down 1 row (instead of the rowcount+1) and paste again. Then I guess it either stops, or it continues pasting the rest in the same spot.
You need to fully qualify the Worksheet that the Cells are on.
rowcountA = Cells(Cells.Rows.Count, "A").End(xlUp).Row
There's an implicit ActiveSheet here, not a reference to ws as you would want. You already have a With ws...End With so change this line to:
rowcountA = .Cells(.Rows.Count, "A").End(xlUp).Row
Note that there are other instances where you are "repeating" the ws instead of fully taking advantage of the With ws...End With.

Code to copy and paste one range to another, multiple times in VBA excel

I have written the following code to copy and paste range w21:W1759 into range AD21:
Sub CommandButton1_Click()
Dim i As Integer, j As Integer
For j = 1 To Range("d7")
Range("d8") = j
'Calculate
Range("w21:W1759").Select
Selection.Copy
Range("AD21").Select
Selection.PasteSpecial Paste:=xlValues, Operation:=xlNone, SkipBlanks:= _
False, Transpose:=False
Next j
End Sub
My data in range w21:W1759 is set to change (due to random sampling) on every click and I want the new data in this range to be copied and pasted to range "ae" (the adjacent column). Then on the next click to "af" and so on and so on. What code do I need to add to the above to achieve this?
Thanks very much for the help
This will depend somewhat on what is to the right of column AC. If column Ad is the first empty column then it is easy to copy to. Subsequent copying operations can use the same next-empty-column method to fill columns AE, AF, etc.
Sub CommandButton1_Click()
Dim i As Long, j As Long
With Worksheets("Sheet1")
For j = 1 To .Range("d7")
.Range("d8") = j
.Calculate
With .Range("w21:w1759")
.Parent.Cells(21, Columns.Count).End(xlToLeft).Offset(0, 1).Resize(.Rows.Count, .Columns.Count) = .Value
End With
Next j
End With
End Sub
I've altered your Copy, PasteSpecial Values method to be a direct value transfer. This is faster and does not involve the clipboard.

Find last non-empty cell of a row, then copy till that cell and paste

VBA code which finds the last non empty cell of Row 5, copies the data till that cell of Row 5 and pastes that data as values after transposing. I tried to record a macro by copying the data but it didn't work.
I'm assuming you're talking about excel vba. The below code copies from a range in sheet1 column A, down to the last data in the row ( you can change this to be only to row 5 if you have data after 5 that you don't want to copy). Then it transposes it on to sheet2.
update to only go to 5
Sub someMacro()
Dim answerRange As Range
Dim checkBlankRange As Range
Dim lastRowInRange As Long
Dim lastcolumn As Long
Set checkBlankRange = Worksheets("Sheet1").Range("A1:A5") 'changed to 1 to 5
lastRowInRange = 5 ' default to 5
lastcolumn = Sheets("Sheet1").Cells.Find(What:="*", SearchDirection:=xlPrevious, SearchOrder:=xlByColumns).Column
'or you can use line below for last column
'lastcolumn = Sheets("Sheet1").Cells(1, Sheets("Sheet1").Columns.Count).End(xlToLeft).Column
For Each cell In checkBlankRange
If cell.Value = "" Then 'first empty cell
lastRowInRange = cell.row 'get the row number of the empty cell
Exit For
End If
Next cell
Set answerRange = Sheets("Sheet1").Range(Sheets("Sheet1").Cells(1, 1), Sheets("Sheet1").Cells(lastRowInRange, lastcolumn))
answerRange.Copy
ActiveWorkbook.Sheets("Sheet2").Range("A1").PasteSpecial Paste:=xlPasteAll, Operation:=xlNone, SkipBlanks:=False, Transpose:=True
End Sub
if the data is always going to be in those colums you wont need alot of the code and you can just use what you had in your recorded macro - cleaned up to remove selects
Sub simpleVersion()
Sheets("Sheet1").Range("A1:F5").Copy
Sheets("Sheet2").Range("A1").PasteSpecial Paste:=xlPasteAll, Operation:=xlNone, SkipBlanks:=False, Transpose:=True
End Sub

Resources