Counting Unique Values for Multiple Columns in Excel - arrays

I've attempted using Pivot tables and SUMPRODUCT & COUNTIF formulas after looking through possible solutions but haven't found anything positive yet. Below is the input data:
Level 1 Level 2 Level 3 Level 4 Level 5
Tom Liz
Tom Liz Mel
Tom Liz Dan
Tom Liz Dan Ian
Tom Liz Dan Ken
Tom Tim
Tom Tim Fab
Tom Tim Fab Ken
Tom Tim Fab Ken Jan
Eve
Expected output data is below. The intent is to not have to feed in a pre-loaded list of names. The expectation is that the program could determine the counts based on the input data alone:
Counts
-------
Tom: 9
Eve: 1
Liz: 5
Tim: 4
Mel: 1
Dan: 3
Fab: 3
Ian: 1
Ken: 3
Jan: 1
Any help towards this is appreciated....thanks!
UPDATE: A preloaded list with the list of Names CAN be used to generate the counts. The above description was updated accordingly.

First enter the following UDF in a standard module:
Public Function ListUniques(rng As Range) As Variant
Dim r As Range, ary(1 To 9999, 1 To 1) As Variant
Dim i As Long, C As Collection
Set C = New Collection
On Error Resume Next
For Each r In rng
v = r.Value
If v <> "" Then
C.Add v, CStr(v)
End If
Next r
On Error GoTo 0
For i = 1 To 9999
If i > C.Count Then
ary(i, 1) = ""
Else
ary(i, 1) = C.Item(i)
End If
Next i
ListUniques = ary
End Function
Then hi-light a section of a column, say G1 thru G50 and enter the Array Formula:
=listuniques(A2:E11)
Array formulas must be entered with Ctrl + Shift + Enter rather than just the Enter key.
If done correctly you should see something like:
Finally in H1 enter:
=COUNTIF($A$2:$E$11,G1)
and copy down
NOTE
User Defined Functions (UDFs) are very easy to install and use:
ALT-F11 brings up the VBE window
ALT-I
ALT-M opens a fresh module
paste the stuff in and close the VBE window
If you save the workbook, the UDF will be saved with it.
If you are using a version of Excel later then 2003, you must save
the file as .xlsm rather than .xlsx
To remove the UDF:
bring up the VBE window as above
clear the code out
close the VBE window
To use the UDF from Excel:
=myfunction(A1)
To learn more about macros in general, see:
http://www.mvps.org/dmcritchie/excel/getstarted.htm
and
http://msdn.microsoft.com/en-us/library/ee814735(v=office.14).aspx
and for specifics on UDFs, see:
http://www.cpearson.com/excel/WritingFunctionsInVBA.aspx
Macros must be enabled for this to work!

Related

Extract data from sheet range based on condition (like DataFrame.loc) using VBA

The problem for some of you Python coders out there is doing DataFrame.loc[DataFrame['Name']=='John'][-1].
This is what I have in the spreadsheet, let's say from cell range (A1:E3).
Name
Bio
Date
John
Loves travelling to the mountains
11/20
Joe
plays computer
11/20
John
goes to the sea a lot
01/22
Jenny
dances salsa
02/22
On a separate sheet, I have 2 buttons and 2 cells
Add information to database (built in VBA)
Extract latest information to database
First cell allows me to enter the name such as 'John'
Second cell that allows me to enter the Bio (or equivalently extract the bio to)
How could I, if I type John and click the button to extract the bio, get the latest Bio (the one dated 01/22) for John into another cell?
If you got an older version of Excel you may do this with SUMPRODUCT:
=SUMPRODUCT(MAX(--(A2:A5=G5)*C2:C5))
In Excel 365, you can use function MAXIFS:
MAXIFS
function
Hi I figured out a solution for anybody else looking for something similar and have closed this query.
if we set up a data array that can be used to store the table in the sheet range. Then we can loop through the data as such:
Dim rowCount
Dim dataArray As Variant
Dim bio
For i = 1 To rowCount
If dataArray(i, 1) = Name Then
bio = dataArray(i, 2)
End If
Next
Sheets("Main").Range("A5") = bio
where rowCount should be the number of rows in the table, this can be specified inside a cell on the sheet, and Name be whatever name it is such as 'John'

Update cell value if new value is availiable

So I am brainstorming a few different ways to accomplish this task... but none of the ways I am thinking are very clean. I am looking for a clean way to accomplish this.
I have 2 workbooks (workbook A, workbook B).
Workbook A looks like this:
A B C D E
Tom Bob Sam Ted Meg
1 4 9 3 2
The A,B,C... are the columns (not actually on the sheet) and the 1,4,9,3,2 is data on the last row (could be row 10 or row 1000, etc...)
Workbook B looks like this:
A B
Sam 5
Meg 1
I want to update workbook A with any values on workbook B. So... in this example... Sam and Meg has a new value... So I want to update workbook A to look like this:
A B C D E
Tom Bob Sam Ted Meg
1 4 5 3 1
I feel like the simplest way to do this may be to make something like a dictionary or something like that but I have never used a dictionary and don't know if some other method would be easier / simpler.
So if you want to get data from one book to another then vlookup is the simplest solution
https://photos.app.goo.gl/CRjxhJHVKm6xKJ8x7
I nested it within an if statement that checks to see if there is an error, if there is an error then it just grabs the value from the line above. I.e. if the Name doesn't exist on your second sheet, then there is an error and it will just grab from the line above.
=IF(ISERROR(VLOOKUP(A1,[Book2]Sheet1!$A$2:$B$5,2,FALSE)),A11,VLOOKUP(A1,[Book2]Sheet1!$A$2:$B$5,2,FALSE))
On the face value, this does what you are asking but the way you phrased the question implies that there is some other functionality and some other things you need to do that this solution will possibly get in the way of. That's why I was trying to clarify what you really want to achieve.
Anyway, hopefully its a step in the right direction... Goodluck!
Here is my take on it. No dictionary, however going through memory in an array:
Sample data:
Sheet1:
Sheet2:
Sample code:
Sub Test()
Dim lr As Long, x As Long, col As Long
Dim arr As Variant
'Step 1: Get the values to update
With Sheet1
lr = .Cells(.Rows.Count, 1).End(xlUp).Row
arr = .Range("A1:B" & lr)
End With
'Step 2: Go through our values to update
With Sheet2
For x = LBound(arr) To UBound(arr)
col = .Rows(1).Find(arr(x, 1), Lookat:=xlWhole).Column
.Cells(.Cells(.Rows.Count, col).End(xlUp).Row + 1, col) = arr(x, 2)
Next x
End With
End Sub
Result:
Remove the +1 in case you want to rewrite the last entry in the found column. I just assumed you wanted to paste the value underneath. Also, I refered to sheets, you can simply refer to the other open workbook.
Firstly, a little more background on my problem. I am opening text files, extracting data, re-arranging the data on an excel sheet (wkbTemp), and then copying the re-arranged data to "wkbCompiledDataTable."
Here is how i solved my problem.
wkbTemp.Sheets(1).Activate
LRow = Cells(Rows.Count, 9).End(xlUp).Offset(0, 0).row
wkbTemp.Sheets(1).Range("J1:N" & LRow).Copy
wkbCompiledDataTable(1).Activate
LRow = Cells(Rows.Count, 1).End(xlUp).Offset(0, 0).row
wkbCompiledDataTable(1).Sheets(1).Range("D" & LRow).PasteSpecial _
Paste:=xlPasteValues, _
SkipBlanks:=True, _
Transpose:=True
As I said earlier, the actual application is much more complicated. This routine is actually in a loop. So for example I actually have "wkbCompiledDataTable(i)" instead of "wkbCompiledDataTable(1)." But for me this was the simplest solution. wkbTemp.Sheets(1) Column "J" has data. Column "K" is blank. Column "L", Column "M", Column "N" has data (These 3 columns are the columns I was concerned with data changing).

How to automatically transfer specific fields in another excel sheet?

I have an excel sheet containing some data, i.e. main sheet. How can I automatically transfer the fields that contains the same value for a specific column, i.e. here A, in the main sheet to another sheet.
Please take a look at this example:
this the main sheet:
A B C D
1 Smith 100 Mar 1
2 Bob 95 Apr 5
3 John 34 Aug 4
4 Bob 88 mar 7
5 John 7 Apr 4
Let's say column A is the reference column for sorting. We want all fields containing the same value in A to move to a separated sheet. The same for other duplicate values of A is also expected.
The result should be :
Sheet 1:
A B C D
1 Bob 95 Apr 5
2 Bob 88 mar 7
Sheet 2:
A B C D
1 John 34 Aug 4
2 John 7 Apr 4
Sheet 3:
A B C D
1 Smith 100 Mar 1
If possible I want this method to be up-to-date, as if a row is removed in the main sheet, the change applies in the corresponding sheet.
You can either:
1) duplicate the data in the sheets with sheet2(cell) = sheet1(cell) references for each sheet. Then having a filter on for each new sheet for column as wanted value.
2) write a VB script, possible triggered by the worksheets(main data) onChange function monitoring that data range, and copying the rowns to new WSs.
3) make a new pivot table for each "additional" sheet where you can configure the data you want to show
implementation kinda depends on if you want to keep the data "up to date" like if there is a row deletion in main data should it be removed from other sheets etc. but simple one time copy would be code like(not actual syntax but just pseudocode):
sub Worksheet_Change(range)
if range in wnated range
copy entire row from sheet (data) to sheet X (sheet X will be chosen depending on value of column A)

Creating a database in excel

I'm working with a database in excel. I will try to make it as simple as possible.
For example,
I have a vlookup range/array of fruits, and who likes each fruit.
Fruit - Person
1. Apple – DeShoun
2. Apple – John
3. Apple – Scott
4. Pear – Scott
5. Strawberries – John… ect
In my database I have a list of fruit and the vendor that sells it
Fruit - Vendor
1. Apple – Sprouts
2. Apple – Walmart
3. Apple – Trader Joe’s
4. Strawberries – Abel Farms
5. Banana – Sprouts
6. Pear – Sprouts…. ect
I need to be able to find the fruit “apple” within my database and create new rows of information within the database so that it looks like the following.
Fruit - Vendor - Person
1. Apple – Sprouts - DeShoun
2. Apple – Walmart - DeShoun
3. Apple – Trader Joe’s - DeShoun
4. Apple – Sprouts - John
5. Apple – Walmart – John
6. Apple – Trader Joe’s - John
7. Apple – Sprouts - Scott
8. Apple – Walmart - Scott
9. Apple – Trader Joe’s – Scott
10. Strawberries – Abel Farms - John
11. Banana – Sprouts - #N/A
12. Pear – Sprouts - Scott
Since I will be working on a minimum of 1000+ rows, I need to know if there’s there a process to expedite this in any way.
Does anyone have any suggestions or links/articles that can point me in the right direction?
Feel free to comment or ask any questions that could help lead to a good answer.
Thanks
Let's say your Fruit-Person table is Table 2, Fruit-Vendor is table 3. Fruit is the common field across tables here. You will need to build a Table 1 with unique values from Fruit column. (There are many ways of building a table with unique values, if you aren't aware, they should be available online)
I am listing the process for Excel-2013, there is a chance it might be slightly different in the older versions.
Step 0:
You have 3 tables as per earlier description.
Step 1:
Convert all of them to Tables one by one.
Alt+N>>T, or, select A1:A5 >> Insert >> Table. Tick Choose My Table has Headers.
Repeat this process for all 3 tables. They should look like this:
Step 2:
Create Pivot Table on Multiple Ranges
A) Create Pivot table on Table 1 (Insert>>PivotTable). Tick check "Add this data to Data Model". IMP
B) Under Pivot Table fields, ALL; you should see all 3 tables
Step3:
Create Relationships
In the Analyze tab, click Relationships. A box which says Manage relationships should open up. The idea is to build relationships.
A) Try building a relationship between Table 1 and Table 2.
New >> choose following options:
Table: Table 2
Column (Foreign): Fruit
Related Table: Table 1
Related Column (Primary): Fruit
B) Let's try building it now between Table 1 & 3
New >> choose following options:
Table: Table 3
Column (Foreign): Fruit
Related Table: Table 1
Related Column (Primary): Fruit
It should look like this:
Step 4:
Forming the Pivot
A) Get Fruit from Table 1, Person from Table 2, Vendor from Table 3 (in that order) as row labels
B) Now, Table2/Fruit and Table3/Fruit need to go as Value Labels.
The table so formed is your almost final table. The rows you want will be the ones which have a 1 in column D and E both. You can get those rows off by filtering/pasting as values.
(As a process, pasting images isn't the popular method it seems, but I couldn't have explained it better visually without them)
I'm pretty new to VBA, but had a bit of a fiddle around and this seems to work sort of as you describe (as a potential example...). Have put each table of data on a separate worksheet.
Sub FruityPerson_Matching()
Dim strFruit As String, strPerson As String, strVendor As String 'to hold text.
Dim myWB As Workbook, myWS_P As Worksheet, myWS_V As Worksheet, myWS_C As Worksheet
Dim LastRow As Integer, n As Integer, iNewRow As Integer
Dim rFruit As Range, checkCell As Range
Set myWB = Application.ActiveWorkbook
Set myWS_P = myWB.Worksheets("Person")
Set myWS_V = myWB.Worksheets("Vendor")
Set myWS_C = myWB.Worksheets("Combined")
LastRow = myWS_P.Cells(myWS_P.Rows.Count, "A").End(xlUp).Row
First looping through list of people, finds the first instance of their fruit within the vendors list:
For n = 2 To LastRow
strFruit = Cells(n, 1).Value
strPerson = Cells(n, 2).Value
Set rFruit = myWS_V.Range("A:B").Find(What:=strFruit, LookIn:=xlValues, _
LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If Not rFruit Is Nothing Then
Set checkCell = rFruit 'For checking when findnext gets back to original cell.
strVendor = myWS_V.Cells(rFruit.Row, 2).Value
Add this to a new row (so that sure it is blank) in the final combined data sheet:
iNewRow = myWS_C.Range("A" & myWS_C.Rows.Count).End(xlUp).Offset(1).Row
myWS_C.Range("A" & iNewRow).Value = strFruit
myWS_C.Range("B" & iNewRow).Value = strVendor
myWS_C.Range("C" & iNewRow).Value = strPerson
Since potential multiple vendors per fruit, now looping through them for same person:
Do
Set rFruit = myWS_V.Range("A:B").FindNext(After:=rFruit)
If Not rFruit Is Nothing Then
If rFruit.Address = checkCell.Address Then Exit Do
'Shows: are back at start.
strVendor = myWS_V.Cells(rFruit.Row, 2).Value
iNewRow = myWS_C.Range("A" & myWS_C.Rows.Count).End(xlUp).Offset(1).Row
myWS_C.Range("A" & iNewRow).Value = strFruit
myWS_C.Range("B" & iNewRow).Value = strVendor
myWS_C.Range("C" & iNewRow).Value = strPerson
Else
Exit Do
End If
Loop
Else
'What do if strFruit not found...?
Exit Sub
End If
Next
End Sub
Finally moving on to next person in loop etc until reaching the last row of data.
Something like what you had in mind?
It can be difficult to get your head round at first, but I would recommend looking into the INDEX MATCH functions. Used together they can do exactly what vlookup does, but with a little understanding they are far more flexible and may well be better suited to your needs :)
http://fiveminutelessons.com/learn-microsoft-excel/how-use-index-match-instead-vlookup
Might be helpful, or google to find a tutorial that suits you
Specifically for your problem, the hardest part will be matching every vendor, person to each fruit... VBA might be necessary

Aggregating grouped data with repeating data

I am trying to figure out how to aggregate the number of hours a person worked that is repeated by each fringe code that they have.
The data looks like this
Company Code Employee Code Employee Name Hours Fringe Code Fringe Amount
030 12345 Joe Blow 8 VAC 10.00
030 12345 Joe Blow 8 DUE 5.00
030 12345 Joe Blow 8 INTDUE 2.00
030 54698 Alan Low 8 VAC 10.00
030 54698 Alan Low 8 DUE 5.00
030 54698 Alan Low 8 INTDUE 2.00
Now the total number of hours should by 16. In the report I have grouped the following fields Company Code, Employee Code, Employee Name, Hours so that they only appear once (not sure how to show that).
I tried to do the following
Sum(FIRST(Fields!Hours.Value, "Employee_Name") , "Company_Code")
but I get an error saying that the value is using a First aggregate in an outer aggregate and that they cannot be specified as nested aggregates.
Any ideas?
I am not sure what you want so I guess a possible result scenario.
I 've reproduced the providing input table.
Using this tablix data settings:
Add this code to the report. This function will flag those rows that must be summed.
Dim groups As System.Collections.Hashtable
Function MyFunc(ByVal group As Object) As integer
dim flag as integer
If (groups Is Nothing) Then
groups = New System.Collections.Hashtable
End If
If (Not groups.Contains(group)) Then
flag = 1
groups.Add(group, nothing)
else
flag = 0
End If
MyFunc = flag
End Function
And this expression in the tablix textbox you want to put the sum.
=sum(val(iif(Code.MyFunc(Fields!Employee_Code.Value) = 1, Fields!Hours.Value, 0)))
You'll get this preview
Let me know if this was helpful.
Well I had issues with some of the above suggestions but I figured out how to solve the problem.
I used the following equation:
=SUM(MAX(Fields!Regular_Hours.Value, "Employee_Code","Company_Code")
Thus I used the MAX function to get only one occurrence per Employee Code and then did a sum of that one occurrence over the Company Code grouping.
Thanks for your guys help!

Resources