In This Chapter
Excel’s pivot table feature is, arguably, the most innovative and powerful feature in Excel. Pivot tables first appeared in Excel 5, and the feature has been improved in every subsequent version. This chapter is not an introduction to pivot tables. We assume that you’re familiar with this feature and its terminology and that you know how to create and modify pivot tables manually.
As you probably know, creating a pivot table from a database or list enables you to summarize data in ways that otherwise would not be possible — and is amazingly fast and requires no formulas. You also can write VBA code to generate and modify pivot tables.
This section gets the ball rolling with a simple example of using VBA to create a pivot table.
Figure 8.1 shows a simple worksheet range that contains four fields: SalesRep, Region, Month, and Sales. Each record describes the sales for a particular sales representative in a particular month.
Figure 8.2 shows a pivot table created from the data, along with the PivotTable Fields task pane. This pivot table summarizes the sales performance by sales representative and month. This pivot table is set up with the following fields:
If you were to record a macro in Excel 2016 while building the pivot table in Figure 8.2, the macro recorder would generate the following code:
Sub CreatePivotTable() Sheets.Add ActiveWorkbook.PivotCaches.Create _ (SourceType:=xlDatabase, _ SourceData:="Sheet1!R1C1:R13C4", _ Version:=6).CreatePivotTable _ TableDestination:="Sheet2!R3C1", _ TableName:="PivotTable1", _ DefaultVersion:=6 Sheets("Sheet2").Select Cells(3, 1).Select With ActiveSheet.PivotTables("PivotTable1").PivotFields("Region") .Orientation = xlPageField .Position = 1 End With With ActiveSheet.PivotTables("PivotTable1").PivotFields("SalesRep") .Orientation = xlRowField .Position = 1 End With With ActiveSheet.PivotTables("PivotTable1").PivotFields("Month") .Orientation = xlColumnField .Position = 1 End With ActiveSheet.PivotTables("PivotTable1").AddDataField _ ActiveSheet.PivotTables("PivotTable1").PivotFields("Sales"), _ "Sum of Sales", xlSum End Sub
If you execute this macro, it will almost certainly end with an error. Examine the code, and you’ll see that the macro recorder hard-coded the worksheet name (Sheet2) for the pivot table. If that sheet already exists (or if the new sheet that’s added has a different name), the macro ends with an error. But a more serious problem is that the macro recorder also hard-coded the pivot table name. The new pivot table’s name won’t be PivotTable1 if the workbook has other pivot tables.
But even though the recorded macro doesn’t work, it’s not completely useless. The code provides lots of insight for writing code to generate pivot tables.
VBA code that works with pivot tables can be confusing. To make any sense of the recorded macro, you need to know about a few relevant objects, all of which are explained in the Help system.
As with most recorded macros, the preceding example isn’t as efficient as it could be. And, as noted, it’s likely to generate an error. You can simplify the code to make it more understandable and also to prevent the error. The hand-crafted code that follows generates the same pivot table as the procedure previously listed:
Sub CreatePivotTable() Dim PTCache As PivotCache Dim PT As PivotTable ' Create the cache Set PTCache = ActiveWorkbook.PivotCaches.Create( _ SourceType:=xlDatabase, _ SourceData:=Range("A1").CurrentRegion) ' Add a new sheet for the pivot table Worksheets.Add ' Create the pivot table Set PT = ActiveSheet.PivotTables.Add( _ PivotCache:=PTCache, _ TableDestination:=Range("A3")) ' Specify the fields With PT .PivotFields("Region").Orientation = xlPageField .PivotFields("Month").Orientation = xlColumnField .PivotFields("SalesRep").Orientation = xlRowField .PivotFields("Sales").Orientation = xlDataField 'no field captions .DisplayFieldCaptions = False End With End Sub
The CreatePivotTable procedure is simplified (and might be easier to understand) because it declares two object variables: PTCache and PT. A new PivotCache object is created by using the Create method. A worksheet is added, and it becomes the active sheet (the destination for the pivot table). Then a new PivotTable object is created by using the Add method of the PivotTables collection. The last section of the code adds the four fields to the pivot table and specifies their location within it by assigning a value to the Orientation property.
The original macro hard-coded both the data range used to create the PivotCache object ('Sheet1!R1C1:R13C4') and the pivot table location (Sheet2). In the CreatePivotTable procedure, the pivot table is based on the current region surrounding cell A1. This ensures that the macro will continue to work properly if more data is added.
Adding the worksheet before the pivot table is created eliminates the need to hard-code the sheet reference. Yet another difference is that the hand-written macro doesn’t specify a pivot table name. Because the PT object variable is created, your code doesn’t ever have to refer to the pivot table by name.
As always, the best way to master this topic is to record your actions in a macro to find out its relevant objects, methods, and properties. Then study the Help topics to understand how everything fits together. In almost every case, you’ll need to modify the recorded macros. Or, after you understand how to work with pivot tables, you can write code from scratch and avoid the macro recorder.
In this section, we present VBA code to create a relatively complex pivot table.
Figure 8.3 shows part of a large worksheet table. This table has 15,840 rows and consists of hierarchical budget data for a corporation. The corporation has 5 divisions, and each division contains 11 departments. Each department has 4 budget categories, and each budget category contains several budget items. Budgeted and actual amounts are included for each of the 12 months. The goal is to summarize this information with a pivot table.
Figure 8.4 shows a pivot table created from the data. Note that the pivot table contains a calculated field named Variance. This field is the difference between the Budget amount and the Actual amount.
Here’s the VBA code that created the pivot table:
Sub CreatePivotTable() Dim PTcache As PivotCache Dim PT As PivotTable Application.ScreenUpdating = False ' Delete PivotSheet if it exists On Error Resume Next Application.DisplayAlerts = False Sheets("PivotSheet").Delete On Error GoTo 0 ' Create a Pivot Cache Set PTcache = ActiveWorkbook.PivotCaches.Create( _ SourceType:=xlDatabase, _ SourceData:=Range("A1").CurrentRegion.Address) ' Add new worksheet Worksheets.Add ActiveSheet.Name ="PivotSheet" ActiveWindow.DisplayGridlines = False ' Create the Pivot Table from the Cache Set PT = ActiveSheet.PivotTables.Add( _ PivotCache:=PTcache, _ TableDestination:=Range("A1"), _ TableName:="BudgetPivot") With PT ' Add fields .PivotFields("Category").Orientation = xlPageField .PivotFields("Division").Orientation = xlPageField .PivotFields("Department").Orientation = xlRowField .PivotFields("Month").Orientation = xlColumnField .PivotFields("Budget").Orientation = xlDataField .PivotFields("Actual").Orientation = xlDataField .DataPivotField.Orientation = xlRowField ' Add a calculated field to compute variance .CalculatedFields.Add"Variance","=Budget-Actual" .PivotFields("Variance").Orientation = xlDataField ' Specify a number format .DataBodyRange.NumberFormat ="0,000" ' Apply a style .TableStyle2 ="PivotStyleMedium2" ' Hide Field Headers .DisplayFieldCaptions = False ' Change the captions .PivotFields("Sum of Budget").Caption =" Budget" .PivotFields("Sum of Actual").Caption =" Actual" .PivotFields("Sum of Variance").Caption =" Variance" End With End Sub
The CreatePivotTable procedure starts by deleting the PivotSheet worksheet if it already exists. It then creates a PivotCache object, inserts a new worksheet named PivotSheet, and creates the pivot table from the PivotCache. The code then adds the following fields to the pivot table:
Note that the Orientation property of the DataPivotField is set to xlRowField in the following statement:
.DataPivotField.Orientation = xlRowField
This statement determines the overall orientation of the pivot table, and it represents the Sum Values field in the Pivot Table Fields task pane (see Figure 8.5). Try moving that field to the Columns section to see how it affects the pivot table layout.
Next, the procedure uses the Add method of the CalculatedFields collection to create the calculated field Variance, which subtracts the Actual amount from the Budget amount. This calculated field is assigned as a data field.
Finally, the code makes a few cosmetic adjustments:
The final example creates a series of pivot tables that summarize data collected in a customer survey. That survey data consists of 150 rows. Each row contains the respondent’s sex plus a numerical rating using a 1–5 scale for each of the 14 survey items.
Figure 8.6 shows a few of the 28 pivot tables produced by the macro. Each survey item is summarized in 2 pivot tables (one showing percentages, and one showing the actual frequencies).
The VBA code that created the pivot tables follows:
Sub MakePivotTables() ' This procedure creates 28 pivot tables Dim PTCache As PivotCache Dim PT As PivotTable Dim SummarySheet As Worksheet Dim ItemName As String Dim Row As Long, Col As Long, i As Long Application.ScreenUpdating = False ' Delete Summary sheet if it exists On Error Resume Next Application.DisplayAlerts = False Sheets("Summary").Delete On Error GoTo 0 ' Add Summary sheet Set SummarySheet = Worksheets.Add ActiveSheet.Name ="Summary" ' Create Pivot Cache Set PTCache = ActiveWorkbook.PivotCaches.Create( _ SourceType:=xlDatabase, _ SourceData:=Sheets("SurveyData").Range("A1"). _ CurrentRegion) Row = 1 For i = 1 To 14 For Col = 1 To 6 Step 5 '2 columns ItemName = Sheets("SurveyData").Cells(1, i + 2) With Cells(Row, Col) .Value = ItemName .Font.Size = 16 End With ' Create pivot table Set PT = ActiveSheet.PivotTables.Add( _ PivotCache:=PTCache, _ TableDestination:=SummarySheet.Cells(Row + 1, Col)) ' Add the fields If Col = 1 Then 'Frequency tables With PT.PivotFields(ItemName) .Orientation = xlDataField .Name ="Frequency" .Function = xlCount End With Else ' Percent tables With PT.PivotFields(ItemName) .Orientation = xlDataField .Name ="Percent" .Function = xlCount .Calculation = xlPercentOfColumn .NumberFormat ="0.0%" End With End If PT.PivotFields(ItemName).Orientation = xlRowField PT.PivotFields("Sex").Orientation = xlColumnField PT.TableStyle2 ="PivotStyleMedium2" PT.DisplayFieldCaptions = False If Col = 6 Then ' add data bars to the last column PT.ColumnGrand = False PT.DataBodyRange.Columns(3).FormatConditions. _ AddDatabar With pt.DataBodyRange.Columns(3).FormatConditions(1) .BarFillType = xlDataBarFillSolid .MinPoint.Modify newtype:=xlConditionValueNumber, newvalue:=0 .MaxPoint.Modify newtype:=xlConditionValueNumber, newvalue:=1 End With End If Next Col Row = Row + 10 Next i ' Replace numbers with descriptive text With Range("A:A,F:F") .Replace"1","Strongly Disagree" .Replace"2","Disagree" .Replace"3","Undecided" .Replace"4","Agree" .Replace"5","Strongly Agree" End With End Sub
Note that all these pivot tables were created from a single PivotCache object.
The pivot tables are created in a nested loop. The Col loop counter goes from 1 to 6 by using the Step parameter. The instructions vary a bit for the second column of pivot tables. Specifically, the pivot tables in the second column do the following:
The Row variable keeps track of the starting row of each pivot table. The final step is to replace the numeric categories in columns A and F with text. For example, 1 is replaced with Strongly Agree.
A pivot table is a summary of data in a table. But what if you have a summary table, and you’d like to create a normalized table from the summary? Figure 8.7 shows an example. Range B2:F14 contains a summary table — similar to a very simple pivot table. Columns I:K contain a 48-row table created from the summary table. In the table, each row contains one data point, and the first two columns describe that data point. In other words, the transformed data is normalized. (See the sidebar, “Data appropriate for a pivot table,” earlier in this chapter.)
Excel doesn’t provide a way to transform a summary table into a normalized table, so it’s a good job for a VBA macro. For example, the UserForm, shown in Figure 8.8, gets the input and output ranges and also has an option to convert the output range to a table.
When the user clicks the OK button in the UserForm, VBA code validates the ranges and then calls the ReversePivot procedure with this statement:
Call ReversePivot(SummaryTable, OutputRange, cbCreateTable)
It passes three arguments:
This procedure will work for any size summary table. The number of data rows in the output table will be equal to (r-1) * (c-1), where r and c represent the number of rows and columns in SummaryTable.
The code for the ReversePivot procedure follows:
Sub ReversePivot(SummaryTable As Range, _ OutputRange As Range, CreateTable As Boolean) Dim r As Long, c As Long Dim OutRow As Long, OutCol As Long ' Convert the range OutRow = 2 Application.ScreenUpdating = False OutputRange.Range("A1:C3") = Array("Column1","Column2","Column3") For r = 2 To SummaryTable.Rows.Count For c = 2 To SummaryTable.Columns.Count OutputRange.Cells(OutRow, 1) = SummaryTable.Cells(r, 1) OutputRange.Cells(OutRow, 2) = SummaryTable.Cells(1, c) OutputRange.Cells(OutRow, 3) = SummaryTable.Cells(r, c) OutRow = OutRow + 1 Next c Next r ' Make it a table? If CreateTable Then _ ActiveSheet.ListObjects.Add xlSrcRange, _ OutputRange.CurrentRegion, , xlYes End Sub
The procedure is fairly simple. The code loops through the rows and columns in the input range and then writes the data to the output range. The output range will always have three columns. The OutRow variable keeps track of the current row in the output range. Finally, if the user checked the check box, the output range is converted to a table by using the Add method of the ListObjects collection.
3.144.254.245