I need to format a large set of data across multiple columns.
I have data in sets of 2 columns that need to be formatted into one set of 2 columns, in addition to headers that need to be in cells to the left of the data.
So I need to covert data that is in two columns to:
header1 header2 data1 data2
So the header needs to be copied and pasted all the way down the columns and the multiple columns and headers need to be appended.
This needs to loop across a whole spreadsheet.
Below is my attempt
colA = 5
colB = 3
colC = 2
rowA = 3
rowB = 3
cellA = "C1"
Worksheets("sheet3").Activate
lastA = Cells(Rows.Count, colB).End(xlUp).Row
For x = rowA To lastA
Worksheets("sheet3").Activate
Data = Cells(x, colA)
Worksheets("sheet3").Activate
Cells(rowB, colB) = Data
rowB = rowB + 1
Next x
Do Until colC = 0
Selection.Cut
Cells(1, colB).Select
ActiveSheet.Paste
Range("D1").Select
Selection.AutoFill Destination:=Cells(3, colC)
colC = colB - 1
Different approach.
Read the source data into an array.
Set up a Class consisting of the data for each resultant row
Iterate through the source array, two columns at a time, to construct each row.
Store the RowData into a collection.
When done, transfer the collection into a "results" array.
Write the results array to a Range (I chose to do do this on a different worksheet). Edit the source and destination ranges as required.
First Insert a Class Module; Rename it RowData, and paste the code below:
Option Explicit
Private pHeaderOdd As String
Private pHeaderEven As String
Private pDataOdd As String
Private pDataEven As String
Public Property Get HeaderOdd() As String
HeaderOdd = pHeaderOdd
End Property
Public Property Let HeaderOdd(Value As String)
pHeaderOdd = Value
End Property
Public Property Get HeaderEven() As String
HeaderEven = pHeaderEven
End Property
Public Property Let HeaderEven(Value As String)
pHeaderEven = Value
End Property
Public Property Get DataOdd() As String
DataOdd = pDataOdd
End Property
Public Property Let DataOdd(Value As String)
pDataOdd = Value
End Property
Public Property Get DataEven() As String
DataEven = pDataEven
End Property
Public Property Let DataEven(Value As String)
pDataEven = Value
End Property
Then, in a regular module, paste the following code:
Sub ReFormat()
Dim V As Variant, vRes() As Variant
Dim cRD As RowData
Dim colRD As Collection
Dim I As Long, J As Long
'Get entire source data into array
'May need a different selection method
' depending on your "real" data arrangement
V = Worksheets("Sheet4").Range("a1").CurrentRegion
'initialize collection
Set colRD = New Collection
'get data in pairs and add to collection
For I = 1 To UBound(V, 2) Step 2 'columns
For J = 2 To UBound(V, 1) 'rows
Set cRD = New RowData
cRD.HeaderOdd = V(1, I)
cRD.HeaderEven = V(1, I + 1)
cRD.DataOdd = V(J, I)
cRD.DataEven = V(J, I + 1)
colRD.Add cRD
Next J
Next I
'Put collection into "results" array for writing to the results range
ReDim vRes(1 To colRD.Count, 1 To 4)
For I = 1 To colRD.Count
vRes(I, 1) = colRD(I).HeaderOdd
vRes(I, 2) = colRD(I).HeaderEven
vRes(I, 3) = colRD(I).DataOdd
vRes(I, 4) = colRD(I).DataEven
Next I
Worksheets("Sheet2").Cells.Clear
Worksheets("Sheet2").Range("A1").Resize(UBound(vRes, 1), UBound(vRes, 2)) = vRes
End Sub
Ensure your worksheet and range references are correct, and run the macro.
Related
I have extract something like this from a database:
[{"identifier":{"strategyType":"element1"},"elnSchedules":[{"paymentDate":["element2","element2"]},{"paymentDate":["element2","element2"]}],"composition":{"components":[{"instrument":{"exerciseType":["element3","element3"]}},{"instrument":{"exerciseType":["element3","element3"]}}]},"links":[]}]
I want to build a vba and create a table in excel that has header: strategyType, paymentDate, exerciseType
and elements: element1, element2, element3 under the corresponding header(while each element can only appear once).
so far I have:
For i = 1 To jsonO.Count
'set headings
If i = 1 Then
j = 1
For Each StrKey In jsonO(i).Keys()
activeWS.Cells(i + offset, j) = StrKey
j = j + 1
Next
End If
j = 1
For Each StrKey In jsonO(i).Keys()
If (StrKey <> "links") Then
activeWS.Cells(i + offset + 1, j) = jsonO(i)(StrKey)
j = j + 1
End If
Next
But this only extracts identifier, eLnschedules, and composition and not able to get into specific element.
Is there any way to do it?
Thanks.
-------------------NEW QUESTION----------------------
I used what's posted on the answer and was trying to build a new function under the function posted in the answer(while both of them are called by the main function):
Public Sub GetEndDate()
Dim activeWS As Worksheet
Set activeWS = ThisWorkbook.Worksheets("Data")
Dim jsonStr As String, Json As Object, headers()
'headers = Array("strategyType", "paymentDate", "exerciseType")
jsonStr = [{"optionFeatures":{"Strike Setting":[{"endDate":["2018-10-16"]}]},"links":[]}] '<== read from cell
Set Json = JsonConverter.ParseJson(jsonStr)(1)
activeWS.Cells(1, 13) = Json("optionFeatures")("Strike Setting")("endDate")
End Sub
However it was not able to read from the string, or do I need to reset the lib again?
Thanks.
Using JSONConverter.bas to parse the JSON string read in from a cell as shown below. This assumes you only want one instance of each value.
Note:
After adding in JSONConverter.bas you need to go VBE > Tools > References > Add a reference to Microsoft Scripting Runtime.
Your JSON structure is as follows:
[] indicates a collection, items accessed by index starting from 1. {} indicates a dictionary with items accessed by key.
I traverse the tree using the appropriate syntax to retrieve the first occurrence of each element.
Option Explicit
Public Sub GetInfoFromSheet()
Dim jsonStr As String, Json As Object, headers()
headers = Array("strategyType", "paymentDate", "exerciseType")
jsonStr = [A1] '<== read from cell
Set Json = JsonConverter.ParseJson(jsonStr)(1)
With ThisWorkbook.Worksheets("Sheet1")
.Cells(1, 1).Resize(1, UBound(headers) + 1) = headers
.Cells(2, 1) = Json("identifier")("strategyType")
.Cells(2, 2) = Json("elnSchedules")(1)("paymentDate")(1)
.Cells(2, 3) = Json("composition")("components")(1)("instrument")("exerciseType")(1)
End With
End Sub
I am trying to calculate the count of Unique values based on a condition.
For example,
For a value in column B, I am trying to count the Unique values in Column C through VBA.
I know how to do it using Excel formula -
=SUMPRODUCT((B2:B12<>"")*(A2:A12=32)/COUNTIF(B2:B12,B2:B12))
that value for 32 is dynamic - Programmatically I am calling them inside my vba code as Name
This is my code :
Application.WorksheetFunction.SumProduct((rng <> "") * (rng2 = Name) / CountIfs(rng, rng))
This is the sample data with the requirement
Alternatively, I Concatenated both the columns for keeping it simple and hoping to identify the Unique values which starts with name* method.
I don't know where I am going wrong. Kindly share your thoughts.
You may try something like this...
Function GetUniqueCount(Rng1 As Range, Lookup As String) As Long
Dim x, dict
Dim i As Long, cnt As Long
Set dict = CreateObject("Scripting.Dictionary")
x = Rng1.Value
For i = 1 To UBound(x, 1)
If x(i, 1) = Lookup Then
dict.Item(x(i, 1) & x(i, 2)) = ""
End If
Next i
GetUniqueCount = dict.Count
End Function
Then you can use it like below...
=GetUniqueCount($A$2:$B$10,C2)
Where A2:B10 is the data range and C2 is the name criteria.
I'd put the values into an array, create a temporary 2nd array and only add values to this array if they are not already present, and then replace the original array. Then it's just a simple matter to sum the unique values:
Sub Unique
dim arr(10) as variant, x as variant
dim arr2() as variant
for x = 1 to 10 ' or whatever
arr(x) = cells(x, 1) ' or whatever
next x
arr2 = UniqueValuesArray(arr)
' now write some code to count the unique values, you get the idea
End Sub
Function UniqueValuesArray(arr As Variant) As Variant()
Dim currentRow, arrpos As Long
Dim uniqueArray() As Variant
Dim x As Long
arrpos = 0
ReDim uniqueArray(arrpos)
For x = 0 To UBound(arr)
If UBound(Filter(uniqueArray, arr(x))) = -1 Then
ReDim Preserve uniqueArray(arrpos)
uniqueArray(arrpos) = arr(x)
arrpos = arrpos + 1
End If
Next x
UniqueValuesArray = uniqueArray
End Function
I have a code that allows me to copy and paste thousands of rows of information based on matching IDs. However the code does not seem to run well in a pivot table. In sheet 4, the IDs are put into a pivot table while in sheet 1 the IDs and the information are not in pivot table (Both IDs in sheet 4 and 1 are in the same column which is column A). However, the IDs appeared more than once in sheet 1. Thus, when i try to run the code, it gave an error that said Cannot enter a null value as an item or field name in pivot table report" on the line 'rngTracker.Value = arrT found below.
Sub Sample()
Dim rngTracker As Range
Dim rngMaster As Range
Dim arrT, arrM
Dim dict As Object, r As Long, tmp
With Workbooks("FAST_Aug2015_Segment_Out_V1.xlsm")
Set rngTracker = .Sheets("Sheet4").Range("A5:D43000")
Set rngMaster = .Sheets("Sheet1").Range("A2:C200000")
End With
'get values in arrays
arrT = rngTracker.Value
arrM = rngMaster.Value
'load the dictionary
Set dict = CreateObject("scripting.dictionary")
For r = 1 To UBound(arrT, 1)
dict(arrT(r, 1)) = r
Next r
'map between the two arrays using the dictionary
For r = 1 To UBound(arrM, 1)
tmp = arrM(r, 1)
If dict.exists(tmp) Then
arrT(dict(tmp), 4) = arrM(r, 3)
End If
Next r
rngTracker.Value = arrT 'Error shown on this line'
End Sub
Above is the code that i have and gave error as mention above. Would appreciate any help. Thank you. :) Below is the image of the pivot table in sheet 4. The column header called "Acc Seg" is not part of the pivot table but it is where the data will be paste from sheet 1 when both IDs in sheet 4 and sheet 1 matched.
Option Explicit
Public Sub Sample()
Const T As Long = 43000
Const M As Long = 200000
Dim arrT1 As Variant, arrM1 As Variant, rngT2 As Range
Dim arrT2 As Variant, arrM2 As Variant, dict As Object, r As Long
With Workbooks("TEST2.xlsm") 'get values in arrays
Set rngT2 = .Sheets("Sheet4").Range("D5:D" & T)
arrM1 = .Sheets("Sheet1").Range("A2:A" & M)
arrM2 = .Sheets("Sheet1").Range("C2:C" & M)
arrT1 = .Sheets("Sheet4").Range("A5:A" & T)
arrT2 = rngT2
End With
Set dict = CreateObject("Scripting.Dictionary")
For r = 1 To UBound(arrT1) 'load the dictionary
dict(arrT1(r, 1)) = r
Next r
For r = 1 To UBound(arrM1, 1) 'map between the arrays using the dictionary
If dict.exists(arrM1(r, 1)) Then arrT2(dict(arrM1(r, 1)), 1) = arrM2(r, 1)
Next r
rngT2 = arrT2
End Sub
I need to create a bunch of dictionaries in VBA; the best solution is to concatenate prefix "dict" with a string variable (enumeration would cause more complexities). Is there a way to convert a string into a VBA statement?
For example, I have a dictionary dictABC created. How to refer to it by using two segment of strings, "dict" and "ABC" concatenated?
(The only way I could think of is to create a "meta-dictionary", with pairs of string "dictABC" and dictionary dictABC.)
If you build an array of dictionary objects and use another single dictionary to act as the index of all of the dictionaries you've created, you should get something akin to what you have described. Consider the following data that has three unique values in Col A.
Col A Col B Col C Col D
Y 196 RNT 4-Jan-2015
Y 127 IYI 12-Feb-2015
X 173 ZVM 24-Jan-2015
Z 124 LRP 16-Jan-2015
Z 176 XTN 27-Jan-2015
Y 137 SUG 30-Jan-2015
X 139 IBG 7-Feb-2015
X 165 DON 11-Feb-2015
Z 153 EUU 16-Feb-2015
After adding Microsoft Scripting Runtime to the VBE's Tools ► References we can run down column A, adding a key entry and index number to the index of dictionaries then redimming the array of dictionaries for room and populating that new dictionary object. If a value in column A already exists, the dNDX is used to figure out which dictionary object in the array should be referenced and adds a new key/item.
Sub mcr_Dict_Over_Dicts()
Dim rw As Long, d As Long, sCOLa As String, ky As Variant
Dim dNDX As New Scripting.Dictionary, dDICTs() As New Scripting.Dictionary
dNDX.CompareMode = TextCompare 'or BinaryCompare
With ActiveSheet
For rw = 2 To .Cells(Rows.Count, 1).End(xlUp).Row
sCOLa = .Cells(rw, 1).Value
If CBool(Len(sCOLa)) Then
If Not dNDX.Exists(sCOLa) Then
'create a new entry in dNDX and a new dictionary in the array
d = dNDX.Count + 1
dNDX.Add Key:=sCOLa, Item:=d
ReDim Preserve dDICTs(1 To d)
dDICTs(d).Add Key:=.Cells(rw, 2).Text, _
Item:=Join(Array(.Cells(rw, 1).Text, .Cells(rw, 2).Text, .Cells(rw, 3).Text, .Cells(rw, 4).Text), ChrW(8203))
Else
'add an entry to an existing dictionary
dDICTs(dNDX.Item(sCOLa)).Add Key:=.Cells(rw, 2).Text, _
Item:=Join(Array(.Cells(rw, 1).Text, .Cells(rw, 2).Text, .Cells(rw, 3).Text, .Cells(rw, 4).Text), ChrW(8203))
End If
End If
Next rw
'return the values to the worksheet reordered in an alternate location
.Cells(1, 6).Resize(1, 4) = .Cells(1, 1).Resize(1, 4).Value
For d = LBound(dDICTs) To UBound(dDICTs)
For Each ky In dDICTs(d)
.Cells(Rows.Count, 6).End(xlUp).Offset(1, 0).Resize(1, 4) _
= Split(dDICTs(d).Item(ky), ChrW(8203))
Next ky
Next d
End With
For d = LBound(dDICTs) To UBound(dDICTs)
dDICTs(d).RemoveAll
Next d
'alternately redim dDICTs
ReDim dDICTs(1)
dNDX.RemoveAll
Set dNDX = Nothing
End Sub
This does demand that there will be some value or combination of values (hash) that can be used as a unique key within each of the dictionaries in the array.
Note that the results in columns G and I are text based. This is a result of them being split out of a concatenated string.
I'm confused why you've thought of and for some reason rejected the simple, obvious solution.
But if you really want to go the code generation route, this is how you would do it.
Function dictAbcd() As Object
Static result As Object
If result Is Nothing Then
Set result = CreateObject("Scripting.Dictionary")
End If
Set dictAbcd = result
End Function
Public Sub Test()
Dim x As Object
Dim dict_index As String
dictAbcd("key") = "value"
dict_index = "Abcd"
Set x = Application.Run("dict" & dict_index)
Debug.Print x("key") ' Prints "value"
End Sub
N.b. doing it this way is completely ridiculous.
You could put the dictionaries into a collection, which allows you to have a string index.
I am having a sheet which contains range of values like "5670&&2","1281&&-3&-5&&7",... etc. in Column A.
Kindly help me to extract the output in VBA in following way:
For E.g 5670&&2 I require A1 cell contains 5670,B1 cell contains &&,C1 cell contains 2.
For E.g 1281&&-3&-5&&7,I would require that A1 cell contains 1281,B1 cell contains &&-,C1 cell contains 3,D1 cell contains &-,E1 cell contains 5,F1 cell contains && and G1 cell contains 7.
Pls help in the same .
Thanks.,
Here i have tried to write code to separate numbers from non-numbers. Numbers and non-numbers are copied to different columns, like Excel Text-To-Columns. Code is a little crazy, if u need i will provide comments. As input the ActiveSheet.UsedRange.Columns(1).Cells is used.
Option Explicit
Sub SeparateNumbers()
Dim targetRange As Range
Dim cellRange As Range
Dim charIndex As Integer
Dim oneChar As String
Dim nextChar As String
Dim start As Integer
Dim copiedCharsCount As Integer
Dim cellValue As String
Dim columnIndex As Integer
Set targetRange = ActiveSheet.UsedRange.Columns(1).Cells
For Each cellRange In targetRange
columnIndex = cellRange.Column
start = 1
copiedCharsCount = 0
cellValue = cellRange.Value
If (VBA.Strings.Len(cellValue) <= 1) Then GoTo nextCell
For charIndex = 2 To Len(cellValue)
oneChar = VBA.Strings.Mid(cellValue, charIndex - 1, 1)
nextChar = VBA.Strings.Mid(cellValue, charIndex, 1)
If VBA.IsNumeric(oneChar) And VBA.IsNumeric(nextChar) Then GoTo nextCharLabel
If Not VBA.IsNumeric(oneChar) And Not VBA.IsNumeric(nextChar) Then GoTo nextCharLabel
cellRange.Offset(0, columnIndex).Value = VBA.Strings.Mid(cellValue, start, charIndex - start)
columnIndex = columnIndex + 1
copiedCharsCount = copiedCharsCount + (charIndex - start)
start = charIndex
nextCharLabel:
If charIndex = Len(cellValue) Then
cellRange.Offset(0, columnIndex).Value = VBA.Strings.Right(cellValue, charIndex - copiedCharsCount)
End If
Next charIndex
nextCell:
Next cellRange
End Sub
Here is one more code. As a side product, function TextSplitToNumbersAndOther can be used independently as a formula to achieve the same effect.
To prevent accidental firing of the macro in a wrong sheet or a wrong column and overwriting neighbouring columns with scrap, named range "Start_point" should be defined by a user. Below this range in the same column, all data will be processed till the first blank row.
Spreadsheet example: http://www.bumpclub.ee/~jyri_r/Excel/Extracting_symbols_into_columns.xls
Option Explicit
Sub ExtractSymbolsIntoColumns()
Dim rng As Range
Dim row_processed As Integer
Dim string_to_split As String
Dim columns_needed As Long
Dim counter As Long
row_processed = 1
counter = 0
Set rng = Range("Start_point")
While rng.Offset(row_processed, 0).Value <> ""
string_to_split = rng.Offset(row_processed, 0).Value
columns_needed = TextSplitToNumbersAndOther(string_to_split)
For counter = 1 To columns_needed
rng.Offset(row_processed, counter).Value = _
TextSplitToNumbersAndOther(string_to_split, counter)
Next
row_processed = row_processed + 1
Wend
End Sub
Function TextSplitToNumbersAndOther(InputText As String, _
Optional SplitPieceNumber As Long) As Variant
Dim piece_from_split(100) As Variant
Dim char_from_input As String
Dim word_count As Long
Dim counter As Long
Dim char_type(100) As Variant
InputText = Trim(InputText)
If Not IsNull(InputText) Then
word_count = 1
piece_from_split(word_count) = ""
For counter = 1 To Len(InputText)
char_from_input = CharFromTextPosition(InputText, counter)
char_type(counter) = CharTypeAsNumber(char_from_input)
If counter = 1 Then
piece_from_split(word_count) = char_from_input
Else
If (char_type(counter - 1) = char_type(counter)) Then
piece_from_split(word_count) = piece_from_split(word_count) & char_from_input
'Merge for the same type
Else
word_count = word_count + 1
piece_from_split(word_count) = char_from_input
End If
End If
Next
End If
If SplitPieceNumber = 0 Then
TextSplitToNumbersAndOther = word_count
Else
If SplitPieceNumber > word_count Then
TextSplitToNumbersAndOther = ""
Else
TextSplitToNumbersAndOther = piece_from_split(SplitPieceNumber)
End If
End If
End Function
Function CharTypeAsNumber(InputChar As String, Optional PositionInString As Long) As Long
If PositionInString = 0 Then PositionInString = 1
If Not IsNull(InputChar) Then
InputChar = Mid(InputChar, PositionInString, 1)
Select Case InputChar
Case 0 To 9
CharTypeAsNumber = 1
Case "a" To "z"
CharTypeAsNumber = 2
Case "A" To "Z"
CharTypeAsNumber = 3
Case Else
CharTypeAsNumber = 4
End Select
Else
CharTypeAsNumber = 0
End If
End Function
Function CharFromTextPosition(InputString As String, TextPosition As Long) As String
CharFromTextPosition = Mid(InputString, TextPosition, 1)
End Function
You can write a UDF (user defined function) to achieve the objective.
Your two example are in an order (ascending) to filter out into adjacent columns in Excel (A, B, C, D...)
So is it correct to assume logically, that you will never have scenarios where you will have to break the string into non-adjacent columns? e.g. 1234 goes to A, && goes to C, 3 goes to D... resulting in A, C, D.
Asumption 2: That your splitted-string is not going to need columns more than Excel can provide.
Steps you may try:
1. Check your string is not empty
2. Split it by the characters other than numerics
3. At the start and end of each non-numeric character you may proceed to the next adjacent column.
search help: Split a string into multiple columns in Excel - VBA