Selecting Nth Row of Filtered Data - excel

I want to tag specified lines of a table for sample review.
Due to the volume of data, running repeated loops over the entire population would result in unacceptably long runtimes (as I have to tag specified sub-populations for QA sampling).
The approach I have taken is to bring in the table, and then filter based on the population I want to sample (for example, filter by location, by product, and by analyst) and then select a percentage of that population for sampling by putting "Sample" into a column.
I have tried several permutations of the code.
The first, where I used the Areas function, threw 1004 errors if there was more than one row.
The second gives strange row selections, including selecting non-hidden rows (and I can't understand why it is picking the rows that it is, as they don't seem to be correctly offset even if it was going by "all rows" not just visible rows).
ActiveSheet.ListObjects("SourceDataTable").Range.AutoFilter Field:=1, Criteria1:="Product1"
sectionCount = ActiveSheet.ListObjects("SourceDataTable").AutoFilter.Range.Columns(1).SpecialCells(xlCellTypeVisible).Cells.Count - 1
If sectionCount = 0 Then sectionSampleSize = 0 Else sectionSampleSize = Int((sectionCount / 10) + 0.5)
MsgBox ("Analyst " & analystLoopCellRef.Value & " ecomm section count is " & sectionCount & " and sample size is " & sectionSampleSize)
Do While sectionSampleSize > 0
sectionLoopRand = Int(sectionCount * Rnd + 1)
MsgBox (sectionLoopRand)
' MsgBox (ActiveSheet.ListObjects("SourceDataTable").DataBodyRange.SpecialCells(xlCellTypeVisible).Areas(1).Columns(40).Rows(sectionLoopRand).Cells(1, 1).Value)
If ActiveSheet.ListObjects("SourceDataTable").DataBodyRange.SpecialCells(xlCellTypeVisible).Columns(40).Rows(sectionLoopRand).Cells(1, 1).Value = "Sample" Then
MsgBox ("Sample overlap")
Else
ActiveSheet.ListObjects("SourceDataTable").DataBodyRange.SpecialCells(xlCellTypeVisible).Columns(40).Rows(sectionLoopRand).Cells(1, 1).Value = "Sample"
' MsgBox ("Sample address is " & ActiveSheet.ListObjects("SourceDataTable").DataBodyRange.SpecialCells(xlCellTypeVisible).Columns(40).Rows(sectionLoopRand).Cells(1, 1).Address)
sectionSampleSize = sectionSampleSize - 1
' MsgBox ("Sample selected")
End If
Loop
Older version
ActiveSheet.ListObjects("SourceDataTable").Range.AutoFilter Field:=1, Criteria1:="Product1"
sectionCount = ActiveSheet.ListObjects("SourceDataTable").AutoFilter.Range.Columns(1).SpecialCells(xlCellTypeVisible).Cells.Count - 1
If sectionCount = 0 Then sectionSampleSize = 0 Else sectionSampleSize = Int((sectionCount / 10) + 0.5)
MsgBox ("Analyst " & analystLoopCellRef.Value & " ecomm section count is " & sectionCount & " and sample size is " & sectionSampleSize)
Do While sectionSampleSize > 0
sectionLoopRand = Int(sectionCount * Rnd + 1)
MsgBox (sectionLoopRand)
' MsgBox (ActiveSheet.ListObjects("SourceDataTable").DataBodyRange.SpecialCells(xlCellTypeVisible).Areas(sectionLoopRand).Columns(40).Cells(1, 1).Value)
If ActiveSheet.ListObjects("SourceDataTable").DataBodyRange.SpecialCells(xlCellTypeVisible).Areas(sectionLoopRand).Columns(40).Cells(1, 1).Value = "Sample" Then
MsgBox ("Sample overlap")
Else
ActiveSheet.ListObjects("SourceDataTable").DataBodyRange.SpecialCells(xlCellTypeVisible).Areas(sectionLoopRand).Columns(40).Cells(1, 1).Value = "Sample"
'' MsgBox ("Sample address is " & ActiveSheet.ListObjects("SourceDataTable").DataBodyRange.SpecialCells(xlCellTypeVisible).Areas(sectionLoopRand).Columns(40).Cells(1, 1).Address)
sectionSampleSize = sectionSampleSize - 1
' MsgBox ("Sample selected")
End If
Loop

Auto filters can create non-contiguous ranges with multiple areas which can be problematic for normal range methods. One way is to loop through the visible cells and build an array of the addresses (or rows). Then by selecting an array element at random you can get the address of a cell in the visible range. For example
Option Explicit
Sub mysample()
Const TABLE_NAME = "SourceDataTable"
Const FILTER_COL = 1
Const TABLE_COL = 40 ' word sample added in table col 40
Const SAMPLE_TERM = "Product1"
Const SAMPLE_RATE = 10 ' 1 in 10 sampled
Const LOOP_MAX = 10000 ' avoid infinite while loop
Dim wb As Workbook, ws As Worksheet
Dim tbl As ListObject, rng As Range, t0 As Single
t0 = Timer
Set wb = ThisWorkbook
Set ws = wb.ActiveSheet
' apply filter and set rng to visible cells in filter col
Set tbl = ws.ListObjects(TABLE_NAME)
tbl.Range.AutoFilter Field:=FILTER_COL, Criteria1:=SAMPLE_TERM
Set rng = tbl.Range.Columns(FILTER_COL).SpecialCells(xlCellTypeVisible)
Debug.Print rng.Address, rng.Cells.Count
' build myrows array of addresses from rng.cells
Dim iCount As Integer, myrows() As String, cell As Range
iCount = -1 ' myrows(0) will be header
ReDim myrows(rng.Cells.Count)
For Each cell In rng.Cells
iCount = iCount + 1
myrows(iCount) = cell.Address
'Debug.Print iCount, cell.Address, cell.Row
Next
' determine sample size
Dim iSampleSize As Integer
If iCount > SAMPLE_RATE / 2 Then
iSampleSize = Round(iCount / SAMPLE_RATE, 0)
End If
'Debug.Print iSampleSize
' select sample
Dim n As Integer, x As Integer, z As Integer
n = 0
Do While n < iSampleSize
' pick one at random
x = 1 + Int(Rnd * iCount) ' avoid header row 0
'Debug.Print n, x
' update table if not previously chosen
If Len(myrows(x)) > 0 Then
ws.Range(myrows(x)).Offset(0, TABLE_COL - FILTER_COL) = "Sample"
myrows(x) = "" ' avoid repeat
n = n + 1
End If
z = z + 1 ' avoid endless loop
If z > LOOP_MAX Then
MsgBox "Max iterations in While Loop exceeded", vbCritical
Exit Sub
End If
Loop
MsgBox iSampleSize & " items selected from " & iCount, vbInformation, "Completed in " & Int(Timer - t0) & " secs"
End Sub

Related

Excel VBA: Data entry based on Listbox values, checks against existing entries

Can't find a way to tell my code that the entry has occurred for a certain user and no additional steps are required when the loop is complete. The issue that I have is that is harder to control or escape my loops so the first loop cannot be ended easily; I have tried giving a value to establish this but it will end it prematurely since there are times when you need to run it, perhaps a different approach can be utilized which I cannot figure out yet.
My current issue is that the code always adds a duplicate of the last user in the user box no matter what and that's because my last if statement checks out ok so it will add it anyway. I want to eliminate that if possible or have another approach.
Here is my code:
'############### START OF CODE SUBMISSION ###############
' define variables
Dim i As Integer, x As Integer, l As Integer, ws As Worksheet, lRow As Long, a As Integer, m As Integer, lCol As Integer, c As Integer, ct As Integer, isDone As Boolean
isDone = False
l = Me.lstDocs.ListCount ' count all entered documents
a = Me.lstTrainee.ListCount ' count all entered names
For x = 0 To l - 1 ' run through documents entered
For m = 0 To a - 1 ' run through names entered
' navigate throgh all entered documents
Set ws = Application.Worksheets(Me.lstDocs.List(x, 0))
' find last row of the selected document
lRow = ws.Cells(Rows.Count, 1).End(xlUp).Row
lCol = ws.Cells(2, Columns.Count).End(xlToLeft).Column
' find matching name, if nothing, add name to list of document matrix
For i = 2 To lRow
If Trim(ws.Cells(i, 1).Value) = Trim(Me.lstTrainee.List(m)) Then ' find the name from existing list on doc matrix
For c = 2 To lCol ' loop through revision until found
' check revision and dates
If Trim(Me.lstDocs.List(x, 1)) = Trim(ws.Cells(2, c).Value) Then ' revision found under the name
' perfect world
If Trim(ws.Cells(i, c).Value) = "" Then ' check that nothing is there under revision column
' MsgBox Me.lstDocs.List(x, 2) debugger to see if it is falling in.
ws.Cells(i, c).Value = Me.lstDocs.List(x, 2) ' add date to cell
With ws
.Hyperlinks.Add Anchor:=ws.Cells(i, c), _
Address:=Me.txtAddress.Value, _
ScreenTip:=Me.txtAddress.Value, _
TextToDisplay:="Open Training File"
End With
ct = ct + 1
If m < a - 1 Then
m = m + 1
' MsgBox "Hit uninteded staetment."
End If
isDone = True
' isDone = True ' commented out LAST ***
Exit For
Else
If MsgBox("Trainee: " & Me.lstTrainee.List(m) & " has been trained on " & ws.Cells(i, c).Value & " on Doc.: " & Me.lstDocs.List(x, 0) & " Rev.: " & Me.lstDocs.List(x, 1) & ". Would you like to replace this?", vbYesNo + vbQuestion) = vbYes Then
' ws.Cells(i, c).Value = Me.lstDocs.List(x, 2) ' replace current value
' ct = ct + 1
' Exit For
Else ' if no is clicked
If i = lRow Then ' last step
' enter the trainig at the end
ws.Cells(lRow + 1, 1).Value = Me.lstTrainee.List(m)
ws.Cells(lRow + 1, c).Value = Me.lstDocs.List(x, 2)
With ws
.Hyperlinks.Add Anchor:=ws.Cells(lRow + 1, c), _
Address:=Me.txtAddress.Value, _
ScreenTip:=Me.txtAddress.Value, _
TextToDisplay:="Open Training File"
End With
ct = ct + 1
' i = lRow
isDone = True
Exit For ' kick out of loop for c
End If
' MsgBox "No Changes were made for: " & Me.lstTrainee.List(m)
End If
End If
' Exit For
End If
' this is where the new can be added as needed
Next c
' Exit For ' kick out of first document revision and continue with next
End If
' If isDone = True Then Exit For
' if this is the last row, most likely the name has not been found. Proceed to add it to the list
If i = lRow And Trim(ws.Cells(lRow, 1).Value) <> Trim(Me.lstTrainee.List(m)) And isDone <> True Then ' always adds at the bottom if no data is captured.
For c = 2 To lCol
If Trim(Me.lstDocs.List(x, 1)) = Trim(ws.Cells(2, c).Value) Then ' revision found under the name
ws.Cells(i + 1, 1).Value = Me.lstTrainee.List(m)
ws.Cells(i + 1, c).Value = Me.lstDocs.List(x, 2)
With ws
.Hyperlinks.Add Anchor:=ws.Cells(i + 1, c), _
Address:=Me.txtAddress.Value, _
ScreenTip:=Me.txtAddress.Value, _
TextToDisplay:="Open File"
End With
ct = ct + 1
End If
Next c
End If
' If isDone = True Then Exit For
Next i
Next m
Next x
MsgBox "A total of " & ct & " records were added.", vbInformation
Call populatorWS
End Sub

Lookup and return another cell value with a gap of cell in between

I am basically stuck in this VBA as I did not know how to lookup 2 cells and return with another cell value. It might be solve with reading project name to lookup first and then reading the week number to match and return the phase in grey area, but to get the 2 lookup together is difficult for me.
This is the first sheet where the input come in as week number and date in each phase
The second sheet will search the project number and week number, return the phase in column J and next.
Use a couple of Dictionary Objects as look-ups to the Project rows and Week columns.
Option Explicit
Sub Macro()
Const SHT_PRJ = "Project"
Const COL_ID_PRJ = "E"
Const COL_PH1 = "F" ' Phase 1
Const ROW_HDR_PRJ = 2 ' header
Const SHT_DEM = "Demand"
Const COL_ID_DEM = "D"
Const ROW_HDR_DEM = 1
Const MAX_PH = 6 ' phases 1 to 6
Dim wb As Workbook
Dim wsIn As Worksheet, wsOut As Worksheet
Dim cell As Range, rng As Range
Dim iRow As Long, iLastRow As Long, iCol() As Integer, iLastCol As Integer
Dim iColWk As Integer
Dim iColor As Variant, sWk As String, iPh As Integer
Set wb = ThisWorkbook
Set wsIn = wb.Sheets(SHT_PRJ)
Dim dict As Object, dictWk As Object, key
Set dict = CreateObject("Scripting.Dictionary")
Set dictWk = CreateObject("Scripting.Dictionary")
' build lookup to row for ID
iLastRow = wsIn.Cells(Rows.Count, COL_ID_PRJ).End(xlUp).Row
For iRow = ROW_HDR_PRJ + 1 To iLastRow
key = Trim(wsIn.Cells(iRow, COL_ID_PRJ))
If dict.exists(key) Then
MsgBox "Duplicate key " & key, vbCritical, "Row " & iRow
Exit Sub
ElseIf Len(key) > 0 Then
dict.Add key, iRow
End If
Next
' build look up to column for week
Set wsOut = wb.Sheets(SHT_DEM)
iLastCol = wsOut.Cells(ROW_HDR_DEM, Columns.Count).End(xlToLeft).Column
For Each cell In wsOut.Cells(ROW_HDR_DEM, 1).Resize(1, iLastCol)
key = Trim(cell.Value)
If dictWk.exists(key) Then
MsgBox "Duplicate week " & key, vbCritical, "Col " & cell.Column
Exit Sub
ElseIf Len(key) > 0 Then
dictWk.Add key, cell.Column
End If
Next
' update demand sheet
ReDim iCol(MAX_PH)
iLastRow = wsOut.Cells(Rows.Count, COL_ID_DEM).End(xlUp).Row
For Each cell In wsOut.Cells(ROW_HDR_DEM + 1, COL_ID_DEM).Resize(iLastRow)
iColor = cell.Interior.ColorIndex
key = Trim(cell.Value)
' each project
If Len(key) > 0 And iColor <> xlColorIndexNone Then '-4142
iRow = dict(key) ' row on project sheet
If iRow < 1 Then
MsgBox "ID " & key & " not found", vbCritical, _
wsOut.Name & " Row " & cell.Row
Exit Sub
Else
' get week numbers for each phase
For iPh = 1 To MAX_PH
sWk = wsIn.Cells(iRow, COL_PH1).Offset(0, 2 * (iPh - 1))
If Len(sWk) > 0 Then
' look up week to column
iCol(iPh) = dictWk(sWk)
If iCol(iPh) < 1 Then
MsgBox "Week " & sWk & " not found", vbCritical, _
wsOut.Name & " Row " & cell.Row
Exit Sub
Else
' update sheet
wsOut.Cells(cell.Row, iCol(iPh)) = "Phase " & iPh
End If
End If
Next
' fill in gaps with previous
For iColWk = iCol(1) To iCol(MAX_PH)
Set rng = wsOut.Cells(cell.Row, iColWk)
If rng.Value = "" Then
rng.Value = rng.Offset(0, -1).Value
End If
Next
End If
End If
Next
MsgBox dict.Count & " projects processed"
End Sub

Fast way to compare two excel files?

I want to compare 2 excels files [Having only 1 sheet in both] having 10-15 columns and rows will be more than 30K. We got one excel macro file which complete the comparison within 5-10Mins. Limitation of this macro is that it can compare only 2-3 columns at a time. So every time we need to run this macro multiple times which is time consuming process. So I created one utility file [.vbs file] which perform this task in one go but it takes around 1-3Hrs.
Is there any other way to perform this comparison in short time in one go?
startTime=Timer()
Set objExcel=Createobject("Excel.application")
objExcel.Visible=True
Set objWorkbook=objExcel.Workbooks.Open("E:\QTP trial version\Data.xls")
'Set deleteAnalysis_CopySheet=objWorkbook.sheets("Analysis_Copy")
'deleteAnalysis_CopySheet.delete
'Set deleteSummarySheet=objWorkbook.sheets("Summary")
'deleteSummarySheet.delete
Set objAnalysis_Copy=objWorkbook.sheets.add
objAnalysis_Copy.name="Analysis_Copy"
Set objSummary=objWorkbook.sheets.add
objSummary.name="Summary"
objSummary.Cells(1,1)="Analysis Row Count"
objSummary.Cells(2,1)="Reporting Row Count"
objSummary.Cells(3,1)="Analysis Column Count"
objSummary.Cells(4,1)="Reporting Column Count"
objSummary.Cells(5,1)="Difference of Row Count"
objSummary.Cells(6,1)="Difference of Column Count"
objSummary.Cells(7,1)="False Count"
' ------------------------1st Check - Verify the position of ''Metrics' in Analysis and Reporting tab. It must be same---------------------
'Get the control of Analysis tab
Set objAnalysis=objExcel.Worksheets.Item("Analysis")
intAnalysisRowCount=objAnalysis.Usedrange.rows.count
objSummary.Cells(1,2)=intAnalysisRowCount
intAnalysisColCount=objAnalysis.Usedrange.Columns.count
objSummary.Cells(3,2)=intAnalysisColCount
'Get Column number of 'Metric' Column from Analysis tab
For intMetricAnalysis=1 to intAnalysisColCount
If(Trim(Lcase(objAnalysis.Cells(1,intMetricAnalysis)))=Trim(Lcase("Metrics"))) Then
Exit for
End If
Next
'Get all Analysis columns in 1 string
strAnalysisColumnOrder=""
For intAnalysisColumnOrder=1 to intAnalysisColCount
strAnalysisColumnOrder=strAnalysisColumnOrder&"*"&objAnalysis.Cells(1,intAnalysisColumnOrder)
If(intAnalysisColumnOrder=1) then
strAnalysisColumnOrder=Replace(strAnalysisColumnOrder,"*","")
End If
Next
Set objReporting=objExcel.Worksheets.Item("Reporting")
intReportingRowCount=objReporting.Usedrange.rows.count
objSummary.Cells(2,2)=intReportingRowCount
intReportingColCount=objReporting.Usedrange.Columns.count
objSummary.Cells(4,2)=intReportingColCount
''Get Column number of 'Metric' Column from Reporting tab
For intMetricReporting=1 to intReportingColCount
If(Trim(Lcase(objReporting.Cells(1,intMetricReporting)))=Trim(Lcase("Metrics"))) Then
Exit for
End If
Next
'Get all Reporting columns in 1 string
strReportingColumnOrder=""
For intReportingColumnOrder=1 to intAnalysisColCount
strReportingColumnOrder=strReportingColumnOrder&"*"&objReporting.Cells(1,intReportingColumnOrder)
If(intReportingColumnOrder=1) then
strReportingColumnOrder=Replace(strReportingColumnOrder,"*","")
End If
Next
''Metric' column number must be same
If(intMetricAnalysis<>intMetricReporting) then
msgbox "Merics column is at "&intMetricAnalysis&" position in 'Analysis' Tab And at "&intMetricReporting&" position in 'Reporting' tab. 'Metrics' column should be at same position in both tab."
strMetricsFlag=False
Else
strMetricsFlag=True
End IF
'-----------2nd Check, Verify count of columns in 'Analysis' And 'Reporting' tab . It Must be same
If intAnalysisColCount<>intReportingColCount Then
msgbox "Column count of 'Reporting' Tab is not same as of 'Analysis tab'."
strAnalysisColCount=False
Else
strAnalysisColCount=True
End If
''---------------3rd Check , Verify Order of columns in 'Analysis' And 'Reporting' tab . It Must be same
If Trim(Lcase(strAnalysisColumnOrder))<>Trim(Lcase(strReportingColumnOrder)) then
msgbox "Column order of 'Reporting' Tab is not same as of 'Analysis tab'. Reporting column order should be "&strAnalysisColumnOrder
strAnalysisColumnOrderFlag=False
Else
strAnalysisColumnOrderFlag=True
End IF
'Creare 'Analysis_Copy' tab and add headers
Set objAnalysisCopy=objExcel.Worksheets.Item("Analysis_Copy")
strFirstCoulmn_AggKeys=""
For intHeaderAggkey=1 to intMetricAnalysis-1
strFirstCoulmn_AggKeys=strFirstCoulmn_AggKeys&"*"&objAnalysis.Cells(1,intHeaderAggkey)
If(intHeaderAggkey=1) then
strFirstCoulmn_AggKeys=Replace(strFirstCoulmn_AggKeys,"*","")
End If
Next
objAnalysisCopy.Cells(1,1)=strFirstCoulmn_AggKeys
strSecondCoulmn_AnalysisMetrics=""
For intHeaderAnalysisMetrics=intMetricAnalysis+1 to intAnalysisColCount
strSecondCoulmn_AnalysisMetrics=strSecondCoulmn_AnalysisMetrics&"*"&objAnalysis.Cells(1,intHeaderAnalysisMetrics)
If(intHeaderAnalysisMetrics=intMetricAnalysis+1 ) then
strSecondCoulmn_AnalysisMetrics=Replace(strSecondCoulmn_AnalysisMetrics,"*","")
End If
Next
objAnalysisCopy.Cells(1,2)="Analysis_"&strSecondCoulmn_AnalysisMetrics
objAnalysisCopy.Cells(1,3)="Reporting_"&strSecondCoulmn_AnalysisMetrics
objAnalysisCopy.Cells(1,4)="Status"
objWorkbook.Save
'$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
If strAnalysisColumnOrderFlag=False OR strMetricsFlag=False OR strAnalysisColCount=False Then
msgbox "So Data Comparision can not be done"
objWorkbook.Save
objWorkbook.Close
objExcel.Quit
Else
intFalseCount=0
For intAnalysisRow=2 to intAnalysisRowCount
' ------ Get the control of ''Analysis' tab and the string of Aggrecate Keys [strAnalysisAggrData] and respective metrics [strAnalysisMetricsData]
Set objAnalysis=objExcel.Worksheets.Item("Analysis")
' Append all data of each row which is before 'Metrics' column
strAnalysisAggrData=""
For intAnalysisColumn=1 to intMetricAnalysis-1
strAnalysisAggrData=strAnalysisAggrData&"*"&objAnalysis.Cells(intAnalysisRow,intAnalysisColumn)
If(intAnalysisColumn=1) then
strAnalysisAggrData=Replace(strAnalysisAggrData,"*","")
End If
Next
' ' Append all data of each row which is after 'Metrics' column
strAnalysisMetricsData=""
For intFromMetric=intMetricAnalysis+1 to intAnalysisColCount
strAnalysisMetricsData=strAnalysisMetricsData&"*"&objAnalysis.Cells(intAnalysisRow,intFromMetric)
If(intFromMetric=intMetricAnalysis+1 ) then
strAnalysisMetricsData=Replace(strAnalysisMetricsData,"*","")
End If
Next
' ------ Get the control of ''Reporting' tab and the string of Aggrecate Keys [strAnalysisAggrData] and respective metrics [strAnalysisMetricsData]
Set objReporting=objExcel.Worksheets.Item("Reporting")
For intReportingRow=1 to intReportingRowCount
' Append all data of each row which is before 'Metrics' column
strReportingAggrData=""
For intBeforeMetricReporting=1 to intMetricReporting-1
strReportingAggrData=strReportingAggrData&"*"&objReporting.Cells(intReportingRow,intBeforeMetricReporting)
If(intBeforeMetricReporting=1) then
strReportingAggrData=Replace(strReportingAggrData,"*","")
End If
Next
' Append all data of each row which is after 'Metrics' column
strReportingMetricsData=""
For intFromReportingMetric=intMetricReporting+1 to intReportingColCount
strReportingMetricsData=strReportingMetricsData&"*"&objReporting.Cells(intReportingRow,intFromReportingMetric)
If(intFromReportingMetric=intMetricReporting+1 ) then
strReportingMetricsData=Replace(strReportingMetricsData,"*","")
End If
Next
'------------------------------------------------------------ Actual Comparision will be from here ------------------------------------------
If Trim(LCase(strAnalysisAggrData))=Trim(LCase(strReportingAggrData)) Then
objAnalysisCopy.Cells(intAnalysisRow,1)=strAnalysisAggrData
objAnalysisCopy.Cells(intAnalysisRow,2)=strAnalysisMetricsData
objAnalysisCopy.Cells(intAnalysisRow,3)=strReportingMetricsData
'Compare Metrics Data
If Trim(LCase(strAnalysisMetricsData))=Trim(LCase(strReportingMetricsData)) Then
objAnalysisCopy.Cells(intAnalysisRow,4)="PASS"
objAnalysisCopy.Cells(intAnalysisRow,4).font.color=vbGreen
Else
objAnalysisCopy.Cells(intAnalysisRow,4)="FAIL"
intFalseCount=intFalseCount+1
objAnalysisCopy.Cells(intAnalysisRow,4).font.color=vbRed
End If
Exit For
End If
Next
Next
objSummary.Cells(5,2)=intAnalysisRowCount-intReportingRowCount
objSummary.Cells(6,2)=intAnalysisColCount-intReportingColCount
objSummary.Cells(7,2)=intFalseCount
objSummary.Cells(7,2).font.color=vbRed
objWorkbook.Save
objWorkbook.Close
objExcel.Quit
EndTime=Timer()
TotalTime=EndTime-startTime
msgbox "Data Comparision is Completed. Comparision time is "&TotalTime&"Secs"
End If
Use a dictionary and you avoid the nested loops and only scan each sheet once. For example as a VBA macro (untested)
Sub compare()
Dim wb As Workbook
Dim ws(2) As Worksheet, wsSum As Worksheet, wsCopy As Worksheet
Dim rowCount(2) As Long, colCount(2) As Integer, colMetric(2) As Integer
Dim colsMetric(2) As String, colsAll(2) As String, colsKeys(2) As String
Dim bMetricsFlag As Boolean, bColCountFlag As Boolean, bColOrderFlag As Boolean
Dim i As Long, ar, msg As String, intFalseCount As Long
Dim t0 as Single
t0 = Timer
Set wb = ThisWorkbook
Set ws(1) = wb.Sheets("Analysis")
Set ws(2) = wb.Sheets("Reporting")
Set wsSum = wb.Sheets("Summary")
wsSum.Cells.Clear
wsSum.Range("A1:A7") = WorksheetFunction.Transpose(Array("Analysis Row Count", _
"Reporting Row Count", "Analysis Column Count", "Reporting Column Count", _
"Difference of Row Count", "Difference of Column Count", "False Count"))
Set wsCopy = wb.Sheets("Analysis_Copy")
wsCopy.Cells.Clear
' get stats for each sheet 1-Analyis 2=Reporting
For i = 1 To 2
ar = Stats(ws(i))
rowCount(i) = ar(0)
colCount(i) = ar(1)
colMetric(i) = ar(2)
colsAll(i) = ar(3)
colsMetric(i) = ar(4)
colsKeys(i) = ar(5)
Next
' summary
With wsSum
.Cells(1, 2) = rowCount(1)
.Cells(2, 2) = rowCount(2)
.Cells(3, 2) = colCount(1)
.Cells(4, 2) = colCount(2)
End With
' check stats
'Metric' column number must be same
If colMetric(1) = 0 Or colMetric(2) = 0 Or colMetric(1) <> colMetric(2) Then
msg = "Metrics columns not the same or missing : " & vbCr & _
"Analysis : " & colMetric(1) & vbCr & _
"Reporting : " & colMetric(2)
MsgBox msg, vbCritical
bMetricsFlag = False
Else
bMetricsFlag = True
End If
' Verify count of columns
If colCount(1) <> colCount(2) Then
msg = "Column counts not the same : " & vbCr & _
"Analysis : " & colCount(1) & vbCr & _
"Reporting : " & colCount(2)
MsgBox msg, vbCritical
bColCountFlag = False
Else
bColCountFlag = True
End If
'Verify Order of columns
If colsAll(1) <> colsAll(2) Then
msg = "Column order not the same : " & vbCr & _
"Analysis : " & colsAll(1) & vbCr & _
"Reporting : " & colsAll(2)
MsgBox msg, vbCritical
bColOrderFlag = False
Else
bColOrderFlag = True
End If
With wsCopy
.Cells(1, 1) = colsKeys(1)
.Cells(1, 2) = "Analysis_" & colsMetric(1)
.Cells(1, 3) = "Reporting_" & colsMetric(2)
.Cells(1, 4) = "Status"
End With
' checks OK ?
If bColOrderFlag And bMetricsFlag And bColCountFlag Then
' ok
Else
MsgBox "So Data Comparision can not be done", vbCritical
Exit Sub
End If
' start comparison
Dim dict As Object, m As Long, c As Long, s As String
Dim sKey As String, sMetric As String
Set dict = CreateObject("Scripting.Dictionary")
' scan Reporting sheet to build dictionary
m = colMetric(2)
For i = 1 To rowCount(2)
'join cols up to and after metric col
sMetric = "": sKey = ""
For c = 1 To colCount(2)
s = Trim(ws(2).Cells(i, c))
If c < m Then
If sMetric <> "" Then sMetric = sMetric & "*"
sMetric = sMetric & s
ElseIf c > m Then
If sKey <> "" Then sKey = sKey & "*"
sKey = sKey & s
End If
Next
dict(sKey) = sMetric
Next
' scan Analysis sheet to compare dictionary
m = colMetric(1)
For i = 2 To rowCount(1)
'join cols up to and after metric col
sMetric = "": sKey = ""
For c = 1 To colCount(1)
s = Trim(ws(1).Cells(i, c))
If c < m Then
If sMetric <> "" Then sMetric = sMetric & "*"
sMetric = sMetric & s
ElseIf c > m Then
If sKey <> "" Then sKey = sKey & "*"
sKey = sKey & s
End If
Next
' result
wsCopy.Cells(i, 1) = sKey
wsCopy.Cells(i, 2) = sMetric
wsCopy.Cells(i, 3) = dict(sKey)
' pass or fail
If sMetric = dict(sKey) Then
wsCopy.Cells(i, 4) = "PASS"
wsCopy.Cells(i, 4).Font.Color = vbGreen
Else
wsCopy.Cells(i, 4) = "FAIL"
wsCopy.Cells(i, 4).Font.Color = vbRed
intFalseCount = intFalseCount + 1
End If
Next
With wsSum
.Cells(5, 2) = rowCount(1) - rowCount(2)
.Cells(6, 2) = colCount(1) - colCount(2)
.Cells(7, 2) = intFalseCount
.Cells(7, 2).Font.Color = vbRed
End With
MsgBox i - 2 & " rows scanned " & vbCrLf & _
intFalseCount & " FAILED", vbInformation, Int(Timer - t0) & "seconds"
End Sub
Function Stats(ws As Worksheet) As Variant
Dim c As Integer, ar(5) As Variant, s As String
ar(0) = ws.UsedRange.Rows.Count
ar(1) = ws.UsedRange.Columns.Count
ar(2) = 0 'metric column
ar(3) = "" ' col aggregated
ar(4) = "" ' cols upto not including metric
ar(5) = "" ' cols after metric
For c = 1 To ar(1)
s = LCase(Trim(ws.Cells(1, c)))
If s = "metric" Then
ar(2) = c
End If
' aggregate headers before/after metric
If ar(2) = 0 Then
If ar(4) <> "" Then ar(4) = ar(4) & "*"
ar(4) = ar(4) & s
ElseIf c > ar(2) Then
If ar(5) <> "" Then ar(5) = ar(5) & "*"
ar(5) = ar(5) & s
End If
' aggregate all
If ar(3) <> "" Then ar(3) = ar(3) & "*"
ar(3) = ar(3) & s
Next
Stats = ar
End Function
Test data generator
Sub testdata()
Dim ws As Worksheet, n, r, c, ar
ar = Array("", "Analysis", "Reporting")
For n = 1 To 2
Set ws = Sheets(ar(n))
For r = 1 To 30000
For c = 1 To 15
ws.Cells(r, c) = Chr(64 + c) & r & "_abcdefghijklmnopqrstuvwxyz_"
Next
Next
ws.Cells(1, 10) = "metric" ' col J
Next
MsgBox "test data created"
End Sub

Export an excel file to txt with same formatting

I have an excel file written in this way:
187712 201 37 0.18
2525 580 149 0.25
136829 137 43 0.31
I need to export this file with same spaces, same formatting in a txt file. How can i do it? I've tryied Save As | Formatted Text (Space Delimited) (*.prn) but not working because i have an issue on the last column. Is there a macro? Thanks.
EDIT: i tryied a macro:
Sub TEST()
Dim c As Range, r As Range
Dim output As String
For Each r In Range("A1:L504").Rows
For Each c In r.Cells
output = output & " " & c.Value
Next c
output = output & vbNewLine
Next r
Open "D:\MyPath\text.txt" For Output As #1
Print #1, output
Close
End Sub
but the result is
187712 201 37 0.18
2525 580 149 0.25
136829 137 43 0.31
These values are only an example because there are about 504 columns!! Anyway the problem is that if in the first column there is a value shorter then the others it lost the formatting like the second row as you can see.
Your posted data shows fixed fields with field-widths of 8,7,7,4 (each field is a combination of characters and trailing blanks). These can be adjusted as necessary in the macro below. Also adjust the folder name to suit your needs:
Sub FixedField()
Dim fld(1 To 4) As Long
Dim V(1 To 4) As String
Dim N As Long, L As Long
Dim K As Long
fld(1) = 8
fld(2) = 7
fld(3) = 7
fld(4) = 4
N = Cells(Rows.Count, "A").End(xlUp).Row
Close #1
Open "c:\TestFolder\test.txt" For Output As #1
For L = 1 To N
outpt = ""
For K = 1 To 4
V(K) = Cells(L, K).Text
While Len(V(K)) <> fld(K)
V(K) = V(K) & " "
Wend
outpt = outpt & V(K)
Next K
MsgBox outpt
Print #1, outpt
Next L
Close #1
End Sub
It is also assumed that the data starts in column A.
I struggled with that also numerous times, the only way I found was with a VBA function I created (the tricky part is determining the "widest" column for plain-text layout). Fair warning: I didn't build a lot "smarts" into this, the output can be a little quirky.
Usage:
Select the cells you want formatted to plain-text, then run the macro (I have the macro assigned to a button, I use it all the time!). If the top row is center-aligned, then let's /assume/ it's a header. And watch for right-aligned columns, and output those right-aligned.
The marco will copy the desired output to the clip-board, then paste the result in Notepad (or similar) to do with as desired.
Example output (I threw in some headers)
CustId Views Selected Cost
187712 201 37 0.18
2525 580 149 0.25
136829 137 43 0.31
The code:
Sub FormatSelectionToPlainText()
' ---------------------------------------------------------------------------
' Author: Jay R. Ohman
' Ohman Automation Corp., http://www.OhmanCorp.com
' ** disclaimer and release: I am NOT an expert **
' ** programmer, use my coding at your own risk! **
' ---------------------------------------------------------------------------
Dim rFound As Range, RngCol1 As Integer, RngRow1 As Integer, ActCol As Integer, ActRow As Integer, x As Integer
Dim MaxCellLen() As Variant, CellAlignRight() As Variant, HdrLen() As Variant, xDbg As Boolean, xVal As Variant
Dim SepSpace As Integer, RetStr As String, RetLen As Integer, MsgStr As String, HasHdr As Boolean
Dim GeneralIsRightAlignedFactor As Single, TotalRows As Integer
Dim oClip As DataObject
xDbg = True ' output stuff to the immediate window?
GeneralIsRightAlignedFactor = 0.75 ' threshhold for deeming a column as right-aligned
Set oClip = New DataObject
MsgStr = "(looking for top row to be center aligned as header)"
If MsgBox("Are the cells to be copied selected?" & vbCrLf & MsgStr, vbYesNo + vbQuestion, "Auto-Fill Time Slots") = vbYes Then
If (Selection Is Nothing) Then
MsgBox "Nothing Selected."
Else
SepSpace = 2 ' number of spaces between columns
RetLen = 0
HasHdr = True
Set rFound = Selection
RngCol1 = rFound.Column
RngRow1 = rFound.Row
Debug.Print Selection.Columns.Count
ReDim Preserve MaxCellLen(Selection.Columns.Count) ' max cell length
ReDim Preserve CellAlignRight(Selection.Columns.Count) ' track the cell alignment
ReDim Preserve HdrLen(Selection.Columns.Count) ' header row max cell length
For ActCol = RngCol1 To RngCol1 + Selection.Columns.Count - 1
x = (ActCol - RngCol1 + 1)
' If xDbg Then Debug.Print Cells(RngRow1, ActCol).HorizontalAlignment
If (Cells(RngRow1, ActCol).HorizontalAlignment <> xlCenter) And (Cells(RngRow1, ActCol).Value <> "") Then HasHdr = False
HdrLen(x) = IIf(HasHdr, Len(Cells(RngRow1, ActCol).Value), 0)
MaxCellLen(x) = 0
CellAlignRight(x) = 0
Next
If xDbg Then Debug.Print "HasHdr: " & HasHdr
TotalRows = (RngRow1 + Selection.Rows.Count) - (RngRow1 + IIf(HasHdr, 1, 0))
For ActCol = RngCol1 To RngCol1 + Selection.Columns.Count - 1 ' go find the longest text in each column
x = (ActCol - RngCol1 + 1)
xVal = IIf(HasHdr, 1, 0)
For ActRow = RngRow1 + xVal To RngRow1 + Selection.Rows.Count - 1
' If xDbg Then Debug.Print Cells(ActRow, ActCol).HorizontalAlignment
xVal = Cells(ActRow, ActCol).Value
If (MaxCellLen(x) < Len(Cells(ActRow, ActCol).Value)) Then MaxCellLen(x) = Len(xVal)
If (Cells(ActRow, ActCol).HorizontalAlignment = xlRight) Or _
((Cells(ActRow, ActCol).HorizontalAlignment = xlGeneral) And (IsDate(xVal) Or IsNumeric(xVal))) Then _
CellAlignRight(x) = CellAlignRight(x) + 1
Next
If xDbg Then Debug.Print "Max Length for Column " & ActCol & ": " & MaxCellLen(x) & _
", CellAlignRight.Count: " & CellAlignRight(x) & "/" & TotalRows
RetLen = RetLen + MaxCellLen(x) + SepSpace
Next
RetLen = RetLen - SepSpace ' subtract that last separator space
If HasHdr Then
For ActCol = RngCol1 To RngCol1 + Selection.Columns.Count - 1
x = (ActCol - RngCol1 + 1)
If (HdrLen(x) > MaxCellLen(x)) Then MaxCellLen(x) = HdrLen(x)
Next
End If
RetStr = "" ' build the output text
For ActRow = RngRow1 To RngRow1 + Selection.Rows.Count - 1
For ActCol = RngCol1 To RngCol1 + Selection.Columns.Count - 1
x = (ActCol - RngCol1 + 1)
MsgStr = Cells(ActRow, ActCol).Value ' re-use string variable
' format for right-aligned
If (CellAlignRight(x) / TotalRows >= GeneralIsRightAlignedFactor) And (Not (HasHdr And (ActRow = 1))) Or (Cells(ActRow, ActCol).HorizontalAlignment = xlRight) Then ' aligned right
RetStr = RetStr & Space(MaxCellLen(x) - Len(MsgStr)) & MsgStr
ElseIf (Cells(ActRow, ActCol).HorizontalAlignment = xlCenter) Then
xVal = Fix((MaxCellLen(x) - Len(MsgStr)) / 2)
RetStr = RetStr & Space(xVal) & MsgStr & Space(MaxCellLen(x) - Len(MsgStr) - xVal)
Else
RetStr = RetStr & MsgStr & Space(MaxCellLen(x) - Len(MsgStr))
End If
If ((ActCol - RngCol1) + 1 < UBound(MaxCellLen)) Then RetStr = RetStr & Space(SepSpace)
Next
RetStr = RetStr & vbCrLf
Next
oClip.SetText RetStr
oClip.PutInClipboard
MsgBox ("The selection has been copied to clipboard." & vbCrLf & "Max line length: " & RetLen)
End If
Else
MsgBox ("Have a nice day. :)")
End If
End Sub

Counting distinct values in excel - frequency function

Counting distinct values in excel - frequency function
yes I have read
Counting distinct values in excel - frequency function
I am try to count a column with different numbers
column contains (search)
1 3 7 9 5 1 3 9 4
result looking for;
C1 C2
1 = 2
2 = 0
3 = 2
4 = 1
etc
You can use COUNTIF to count the number of elements that match a condition.
Suppose you have your numbers in column A, say from A1 to A10:
A1: 1
A2: 3
A3: 7
etc...
Type in somewhere on your sheet, say in column B, the values you are interested in:
B1: 0
B2: 1
etc...
and in C1, type in
=COUNTIF($A$1:$A$10, B1)
This should count the number of values equal to B1 (i.e. 0), in A1:A10.
Enter your numbers in column A and a sequence in column B
A B
1 1
2 1
3 1
4 1
2 1
3 1
4 1
Select both columns and create a pivot table putting col A in rows. Select {COUNT} as function and you are done.
Not exactly what you are asking but i use a macro to generate frequency tables. I like it. Original code was posted by MWE at http://www.vbaexpress.com/kb/getarticle.php?kb_id=406 and i have (hopefully) improved it a bit. Have left in a little bit of redundant code so i get more replies :p
Sub zzzFrequencyDONT_SELECT_WHOLE_COLUMN()
' if user selects massive range - usually whole column - stops them
If Selection.Rows.Count > 60000 Then
MsgBox "Range selected is way too large - over 60,000. You have probably selected an entire column. Select a range of under 60,000 cells and try again"
End If
If Selection.Rows.Count > 60000 Then
Exit Sub
End If
'
' Function computes frequency count of unique values in a selection
'
Dim Count() As Integer
Dim I As Integer, J As Integer
Dim Num As Integer, NumOK As Integer, MaxNumOK As Integer, NumBad As Integer
Dim Row As Integer, Col As Integer, Temp1 As Integer, Temp2 As Integer
Dim strBuffer As String, strBadVals As String
Dim CellVal As Variant
Dim Ans As VbMsgBoxResult
Num = 0
NumBad = 0
NumOK = 0
MaxNumOK = 50
ReDim Count(MaxNumOK, 2)
strBuffer = ""
'
' sequence through each cell in selection
'
For Each Cell In Selection
Num = Num + 1
On Error Resume Next
CellVal = Cell.Value
Select Case Err
Case Is = 0
'
' no error, examine type
'
Select Case LCase(TypeName(CellVal))
Case "integer", "long", "single", "double"
'
' numeric type; if single or double, use
' Fix function to reduce to integer portion
'
If TypeName(CellVal) = "single" Or _
TypeName(CellVal) = "double" Then
CellVal = Fix(CellVal)
End If
'
' check if previously seen
' if so, simply bump counter
' if not, increment NumOK and store value
'
For I = 1 To NumOK
If CellVal = Count(I, 1) Then
Count(I, 2) = Count(I, 2) + 1
GoTo NextCell
End If
Next I
NumOK = NumOK + 1
If NumOK > MaxNumOK Then
MsgBox "capacity of freq count proc exceeded" & vbCrLf & _
"Displaying results so far", vbCritical
GoTo SortCount
End If
Count(NumOK, 1) = CellVal
Count(NumOK, 2) = 1
Case Else
NumBad = NumBad + 1
If Cell.Text <> "" Then
strBadVals = strBadVals & Cell.Text & vbCrLf
Else
strBadVals = strBadVals & "<blank>" & vbCrLf
End If
End Select
Case Is <> 0
NumBad = NumBad + 1
If Cell.Text <> "" Then
strBadVals = strBadVals & Cell.Text & vbCrLf
Else
strBadVals = strBadVals & "<blank>" & vbCrLf
End If
End Select
NextCell:
Next Cell
'
' counting done, sort data
'
SortCount:
For I = 1 To NumOK
For J = I To NumOK
If I <> J Then
If Count(I, 1) > Count(J, 1) Then
Call SwapVals(Count(I, 1), Count(J, 1))
Call SwapVals(Count(I, 2), Count(J, 2))
End If
End If
Next J
Next I
'
' store count data for display
'
Dim percentstore As Single
percentstore = Str(Count(I, 2)) / Str(Num)
For I = 1 To NumOK
strBuffer = strBuffer & Str(Count(I, 1)) & vbTab + Str(Count(I, 2)) & vbTab & FormatPercent(Str(Count(I, 2)) / Str(Num)) & vbCr
Next I
'
' display results
'
MsgBox "CTRL C to copy" & vbCrLf & _
"# cells examined = " & Str(Num) & vbCrLf & _
"# cells w/o acceptable numerical value = " & NumBad & vbCrLf & _
"# unique values found = " & NumOK & vbCrLf & _
"Frequency Count:" & vbCrLf & "value" & vbTab & "frequency" & vbTab & "Percent" & vbCr + strBuffer, vbInformation, "Frequency count - CTRL C to copy"
If NumBad > 0 Then
Ans = MsgBox("display non-numerics encountered?", vbQuestion & vbYesNo)
If Ans = vbYes Then MsgBox "Non Numerics encountered" & vbCrLf & strBadVals
End If
'
' write to worksheet?
'
' Ans = MsgBox("Ok to write out results below selection?" & vbCrLf + _
' "results will be two cols by " & (NumOK + 1) & " rows", vbQuestion + vbYesNo)
' If Ans <> vbYes Then Exit Sub
' Row = Selection.Row + Selection.Rows.Count
' Col = Selection.Column
' Cells(Row, Col) = "Value"
' Cells(Row, Col + 1) = "Count"
' For I = 1 To NumOK
' Cells(Row + I, Col) = Count(I, 1)
' Cells(Row + I, Col + 1) = Count(I, 2)
' Next I
End Sub
Sub SwapVals(X, Y)
'
' Function swaps two values
'
Dim Temp
Temp = X
X = Y
Y = Temp
End Sub

Resources