Slicer selection based on Range - slicers

I have a Pivot Table showing a list of customers meeting a specific criteria.
Further I have a string of other Pivot Tables connected to a slicer.
I need my slicer to automatically filter for the customer codes appearing on the first pivot table.
I have the below - However I am getting 'Error 457 - The Key is already associated with an element in this collection'
What is going wrong?
Option Explicit
Sub filterSlicers()
Dim i As Long, SI As SlicerItem, SC As SlicerCache, PvT As PivotTable, C As Range, Cell As Range, ws As Worksheet
Dim DictFilter As Scripting.Dictionary
For Each PvT In ThisWorkbook.Sheets("Sheet1").PivotTables
PvT.ManualUpdate = True
Next PvT
Set ws = ThisWorkbook.Sheets("Sheet1")
Set C = ws.Range("A12:A120")
Set DictFilter = New Scripting.Dictionary
For Each Cell In C
DictFilter.Add Cell.Value, 1
Next Cell
Set SC = ThisWorkbook.SlicerCaches("Customer_Code")
SC.ClearAllFilters
For Each SI In SC.VisibleSlicerItems
Set SI = SC.SlicerItems(SI.Name)
If DictFilter.Exists(SI.Name) Then
SI.Selected = True
Else
SI.Selected = False
End If
Next
For Each PvT In ThisWorkbook.Sheets("Sheet1").PivotTables
PvT.ManualUpdate = False
Next PvT
End Sub
Thank you,

Related

User Form multi-selection Listbox arrays or entirerow.copy?

Amateur her so bear with me. I am trying to compile VBA code which runs a userform with list box populated with a 9 column and 100 row table from worksheet1. The user selects the only the items in the list box he needs for a report and they are to copied to worksheet 2.
With help from a 6 year old post on this site, I have managed to do this using an array and a public function to select the chosen rows and then output them to worksheets2. As follows:
Private Sub CommandButton1_Click()
Dim SelectedItems() As Variant
Dim i As Integer
Dim emptyrow As Integer
wsTarget.Activate
Range("A1").Select
SelectedItems = GetSelectedRisks(RiskList)
emptyrow = 15
For i = LBound(SelectedItems) To UBound(SelectedItems)
wsTarget.Cells(emptyrow, 2).Value = SelectedItems(i)
emptyrow = emptyrow + 1
Next
End Sub
Public Function GetSelectedRisks(lBox As MSForms.ListBox) As Variant
Dim tmpArray() As Variant
Dim i As Integer
Dim SelectionCounter As Integer
SelectionCounter = -1
For i = 0 To lBox.ListCount - 1
If lBox.Selected(i) = True Then
SelectionCounter = SelectionCounter + 1
ReDim Preserve tmpArray(SelectionCounter)
tmpArray(SelectionCounter) = lBox.List(i)
End If
Next
However I can only work out how to do this for the 1st column. I just can't work out how to get the other columns into the array and then back out again.
Should I be using an array or am I making this to complicated i.e. should I just be using loops and if selected, entirerow.copy type stuff?

Splitting a dataframe into parts by detection, then writing to multiple csv's?

I have a csv as shown in the image below. The data is a set of separate tables, separated by a blank line, that I require to be in separate csv files.
After importing to R, I'd like to split the data into the various separate tables, and then write these tables to separate csv files. I had the idea of using some kind of string detect, as a 'new' table is signified by the first instance of 'Area' in the first column. Any ideas of how to approach the code for this in R? There are a bunch of tables and doing this manually isn't advisable.
There's a truncation problem too it seems, as the tables will be required to have a differing amounts of columns, however I don't expect that getting rid of NULL or NA data should be too difficult with this.
Thanks for any help.
I don't think R is the right tool for this kind of thing. You should always try to use the right tool based on the task. Since you have Excel installed run this VBA script. That will do what you want.
Sub page_endings()
Dim i As Long 'how many times for pagebreak
Dim searchvalue_for_break_after 'value to do pagebreak
searchvalue_for_break_after = ""
'column A must be filled in with value break after
'example row 6, 12, 18, 24 whatever row you want
'will loop until empty row in column A
For i = 1 To Range("A" & Rows.Count).End(xlUp).Row + 1
If Range("A" & i).Value = searchvalue_for_break_after Then
'will add a pagebreak after the row with value break after
ActiveWindow.SelectedSheets.HPageBreaks.Add before:=Range("A" & i).Offset(1)
End If
Next i
Call Create_Separate_Sheet_For_Each_HPageBreak
End Sub
Sub Create_Separate_Sheet_For_Each_HPageBreak()
Dim HPB As HPageBreak
Dim RW As Long
Dim PageNum As Long
Dim Asheet As Worksheet
Dim Nsheet As Worksheet
Dim Acell As Range
'Sheet with the data, you can also use Sheets("Sheet1")
Set Asheet = ActiveSheet
If Asheet.HPageBreaks.Count = 0 Then
MsgBox "There are no HPageBreaks"
Exit Sub
End If
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
'When the macro is ready we return to this cell on the ActiveSheet
Set Acell = Range("A1")
'Because of this bug we select a cell below your data
'http://support.microsoft.com/default.aspx?scid=kb;en-us;210663
Application.Goto Asheet.Range("A" & Rows.Count), True
RW = 1
PageNum = 1
For Each HPB In Asheet.HPageBreaks
'Add a sheet for the page
With Asheet.Parent
Set Nsheet = Worksheets.Add(after:=.Sheets(.Sheets.Count))
End With
'Give the sheet a name
On Error Resume Next
Nsheet.Name = "Page " & PageNum
If Err.Number > 0 Then
MsgBox "Change the name of : " & Nsheet.Name & " manually"
Err.Clear
End If
On Error GoTo 0
'Copy the cells from the page into the new sheet
With Asheet
.Range(.Cells(RW, "A"), .Cells(HPB.Location.Row - 1, "K")).Copy _
Nsheet.Cells(1)
End With
' If you want to make values of your formulas use this line also
' Nsheet.UsedRange.Value = Nsheet.UsedRange.Value
RW = HPB.Location.Row
PageNum = PageNum + 1
Next HPB
Asheet.DisplayPageBreaks = False
Application.Goto Acell, True
With Application
.ScreenUpdating = True
.EnableEvents = True
End With
Call SaveWorksheetsAsCsv
End Sub
Sub SaveWorksheetsAsCsv()
Dim WS As Excel.Worksheet
Dim SaveToDirectory As String
Dim CurrentWorkbook As String
Dim CurrentFormat As Long
CurrentWorkbook = ThisWorkbook.FullName
CurrentFormat = ThisWorkbook.FileFormat
' Store current details for the workbook
SaveToDirectory = "C:\Users\Excel\Desktop\"
For Each WS In ThisWorkbook.Worksheets
Sheets(WS.Name).Copy
ActiveWorkbook.SaveAs Filename:=SaveToDirectory & ThisWorkbook.Name & "-" & WS.Name & ".csv", FileFormat:=xlCSV
ActiveWorkbook.Close savechanges:=False
ThisWorkbook.Activate
Next
Application.DisplayAlerts = False
ThisWorkbook.SaveAs Filename:=CurrentWorkbook, FileFormat:=CurrentFormat
Application.DisplayAlerts = True
' Temporarily turn alerts off to prevent the user being prompted
' about overwriting the original file.
End Sub
You should take each different table to the uppermost part. All in all, you have 5 tables with different dimensions (Table1: 11x13; Table2: 11x9; Table3: 3x12; Table4: 10x5; Table5: 6x7). Take them side-by-side in the above (A1:M11; N1:V11 etc.). The headings of tables would be in 1st row.
library(readxl)
# Use the path returned from getwd() function that is R's working directory
df <- as.data.frame(read_excel("C://Users//User//Documents//Revolution//Your.xlsx"))
Then, you can handle these 5 tables as:
Table1 <- df[,1:13]
Table2 <- df[,14:22]
Table3 <- df[1:3,23:34]
Table4 <- df[1:10,35:39]
Table5 <- df[1:6,40:46]
By caring dimensions stemmed from different row numbers in the assignmets, you do not face any NA or NULL value in Table1...Table5.

Iterating through a data set and merging specific pairs of rows where data is null in R or excel

I have a data set with several hundred rows. Most rows have complete information, but in some cases two rows share the same key while some attributes are repeated, others are not. Here is an example:
Key Campaign Message Stat1 Stat2 Stat3 Stat4
123 Fun yay 1 2
123 temp yay 3 4
Intended result
123 Fun yay 1 2 3 4
Issues:
Needs to search the entire dataframe of hundreds of records, most of which are not duplicates. Ignore the non-duplicates
Has to specify that when combining rows to accept the Campaign data that is NOT "temp"
All other columns where data matches is ok
Columns where one value is null will result in the non-null value being used in the new record
I am open to solutions in R, SQL or excel (vba)
Appreciate any help!
Turned out to be a bit more involved than I thought, but here it is. I am using a collection to merge duplicate keys. Change IGNORE_TEMP constant to include or exclude temp records.
Sub mergeNonNulls()
' change this constant to ignore or include temp results
Const IGNORE_TEMP As Boolean = True
' temporary store of merged rows
Dim cMerged As New Collection
' data part of the table
Dim data As Range
Set data = ActiveSheet.[a2:g3]
Dim rw As Range ' current row
Dim r As Range ' temporary row
Dim c As Range ' temporary cell
Dim key As String
Dim arr() As Variant
Dim v As Variant
Dim vv As Variant
Dim i As Long
Dim isChanged As Boolean
For Each rw In data.Rows
key = rw.Cells(1) ' the first column is key
If IGNORE_TEMP And rw.Cells(2) = "temp" Then
DoEvents ' pass temp if enabled
Else
If Not contains(cMerged, key) Then
' if this is new key, just add it
arr = rw
cMerged.Add arr, key
Else
' if key exists - extract, merge nulls and replace
arr = cMerged(key)
' iterate through cells in current and stored rows,
' identify blanks and merge data if current is empty
i = 1
isChanged = False
For Each c In rw.Cells
If Len(Trim(arr(1, i))) = 0 And Len(Trim(c)) > 0 Then
arr(1, i) = c
isChanged = True
End If
i = i + 1
Next
' collections in vba are immutable, so if temp row
' was changed, replace it in collection
If isChanged Then
cMerged.Remove key
cMerged.Add arr, key
End If
End If
End If
Next
' output the result
Dim rn As Long: rn = 1 ' output row
Dim numRows As Long
Dim numCols As Long
With ActiveSheet.[a6] ' output start range
For Each v In cMerged
numRows = UBound(v, 1) - LBound(v, 1) + 1
numCols = UBound(v, 2) - LBound(v, 2) + 1
.Cells(rn, 1).Resize(numRows, numCols).Value = v
rn = rn + 1
Next
End With
End Sub
' function that checks if the key exists in a collection
Function contains(col As Collection, key As String) As Boolean
On Error Resume Next
col.Item key
contains = (Err.Number = 0)
On Error GoTo 0
End Function

count the unique values in one column in EXCEL 2010 or R with 1 million rows

After searching the forum, I did not find a good solution for this question. If I missed it, please tell me.
I need to count the unique values in one column in EXCEL 2010.
The worksheet has 1 million rows and 10 columns. All cell values are string or numbers.
I used the solution at Count unique values in a column in Excel
=SUMPRODUCT((A2:A1000000<>"")/COUNTIF(A2:A100000,A2:A1000000&""))
But, it runs so long time that the EXCEL is almost frozen. And, it generates 25 processes in Win 7.
Are there more efficient ways to do it?
Also, in the column, all values have for format of
AX_Y
here, A is a character, X is an integer, Y is an integer from 1 to 10.
For example, A5389579_10
I need to cut off the part after (including) undersocre. for the example,
A5389579
This is what I need to count as unique values in all cells in one column.
For example, A5389579_10
A1543848_6
A5389579_8
Here, the unique value has 2 after removing the part after underscore.
How to do it in EXCEL VBA and R (if no efficient solution for EXCEL)?
If you want to do this by VBA, you can take advantage of the Collection object. Since collections can only contain unique values, trying to add all of your input data to a collection will result in an array of unique values. The code below takes all the variables in a selected range and then outputs an array with distinct values to an other sheet (in this case a sheet named Output).
Sub ReturnDistinct()
Dim Cell As Range
Dim i As Integer
Dim DistCol As New Collection
Dim DistArr()
Dim OutSht As Worksheet
Dim LookupVal As String
Set OutSht = ActiveWorkbook.Sheets("Output") '<~~ Define sheet to putput array
If TypeName(Selection) <> "Range" Then Exit Sub
'Add all distinct values to collection
For Each Cell In Selection
If InStr(Cell.Value, "_") > 0 Then
LookupVal = Mid(Cell.Value, 1, InStr(Cell.Value, "_") - 1)
Else
LookupVal = Cell.Value
End If
On Error Resume Next
DistCol.Add LookupVal, CStr(LookupVal)
On Error GoTo 0
Next Cell
'Write collection to array
ReDim DistArr(1 To DistCol.Count, 1 To 1)
For i = 1 To DistCol.Count Step 1
DistArr(i, 1) = DistCol.Item(i)
Next i
'Outputs distinct values
OutSht.Range("A1:A" & UBound(DistArr)).Value = DistArr
End Sub
Note that since this code writes all the distinct values to a single column in the OutSht-sheet, this will return an error if there are more than 1,048,576 distinct values in your dataset. In that case you would have to split the data to be filled into multiple output columns.
For your specific request to count, use the below in a formula like =COUNTA(GetUniques(LEFT("A1:A100000",FIND("_","A1:A100000")-1)) entered as an array formula with Ctrl+Shift+Enter.
It also accepts multiple ranges / values (e.g. GetUniques("A1:A10","B2:E4"))
Function GetUniques(ParamArray args())
Dim arg, ele, arr, i As Long
Dim c As Collection
Set c = New Collection
For Each arg In args
If TypeOf arg Is Range Then
If arg.Count = 1 Then
arr = array(arg.value)
Else
arr = arg.Value
End If
ElseIf VarType(arg) > vbArray Then
arr = arg
Else
arr = Array(arg)
End If
For Each ele In arr
On Error Resume Next
c.Add ele, VarType(ele) & "|" & CStr(ele)
On Error GoTo 0
Next ele
Next arg
If c.Count > 0 Then
ReDim arr(0 To c.Count - 1)
For i = 0 To UBound(arr)
arr(i) = c(i + 1)
Next i
Set c = Nothing
GetUniques = arr
End If
End Function
edit: added a performance optimisation for ranges (loads them at once into an array - much faster than enumerating through a range)
In R:
# sample data
df <- data.frame(x=1:1000000,
y=sample(1e6:(1e7-1),1e6,replace=T))
df$y <- paste0("A",df$y,"_",sample(1:10,1e6,replace=T))
# this does the work...
length(unique(sub("_[0-9]+","",df$y)))
# [1] 946442
# and it's fast...
system.time(length(unique(sub("_[0-9]+","",df$y))))
# user system elapsed
# 2.01 0.00 2.02
In excel 2010... in the next column add (if original data was in A:A add in B1)
= 1/COUNTIF(A:A,A1) and copy down col B to the bottom of your data. Depending on your PC it may chug away calculating for a long time, but it will work. Then copy col B & paste values over itself.
Then SUM col B

Dynamically count of similar kind of Data

I have one data table in VB page which contain bulk data.In that data table one column named as vType and values in that column is one of Pr defined values such as 'A','B','C','D' etc , which comes from one Datable.
Now I want count of each type at the end.
For ex : CountA = 20,CountB=25 and so on .
Till now I have compared Each value using If condition which is static
For each dr as dataRow in dsType.rows
If dr("vType") = 'A' Then
CountA += 1
ElseIf dr("vType") = 'B' Then
CountB +=1
Next dr
and this If condition will repeat depend upon no of types in that data table (at max 8 fix values) I want to do this in single if condition ( Dynamic if Possible) Can I Count these values and store the same into single varaible? appreciate for you prompt reply.
You can use Linq-To-DataSet and Enumerable.GroupBy + Enumerable.Count on each group:
Dim typeGroups = dsType.AsEnumerable().
GroupBy(Function(row) row.Field(Of String)("vType")).
Select(Function(g) New With{ .Type = g.Key, .Count = g.Count(), .TypeGroup = g })
Note that New With creates an anonymous type in VB.NET with custom properties. So like a class on-the-fly which you can use in the current method.
Now you can enumerate the query with For Each:
For Each typeGroup In typeGroups
Console.WriteLine("Type:{0} Count:{1}", typeGroup.Type, typeGroup.Count)
Next
I cannot use Linq, i need to use simple vb only
Then use a Dictionary:
Dim typeCounts = New Dictionary(Of String, Integer)
For Each row As DataRow In dsType.Rows
Dim type = row.Field(Of String)("vType")
If (typeCounts.ContainsKey(type)) Then
typeCounts(type) += 1
Else
typeCounts.Add(type, 1)
End If
Next
Now you have a dictionary where the key is the type and the value is the count of the rows with this type.
why not getting the pretend result from the db itself?
Like so:
select count(*), vType
from someTable
group by vType
Not so sure about your question .. but this is what I've considered ..
You can make it as Sub ..
Sub AssignIncr(ByVal ds as DataSet,byval sFi as String,byval sCrit as String,ByRef Counter as Integer)
For each dr as dataRow in ds.rows
If dr(sFi) = sCrit Then Counter += 1
Next dr
End Sub
So you may use it by ..
AssignIncr(dsType,"vType","A",CountA)

Resources