NHS Excel website expands

I’ve added a number of excel tutorials to the NHS Excel website and this will become the home for most of my excel related content.

It’s been a busy month at work due to year end and the agreement of balances exercise but its been interesting from an excel point of view. We’ve had to work across a departments and this has thrown up a number of issues, not least of which has been the problem of people using different versions of excel to work on a single workbook. This month I’ve written a tutorial explaining how to force Excel 2007 to work in compatibility mode so that you can create pivot tables that work in earlier versions of excel.

I’ve also written some VBA code to automate log-ins to password protected websites. The idea of this is to use in conjunction with a website query to download data from a central NHS website to a spreadsheet on a regular basis.

Waterfall Charts and the X-Axis

Waterfall charts have become all the rage in the office of late. They are popping up all over. I suppose it is no surprise, it’s springtime and we are well into the planning process and a waterfall chart is the perfect way to demonstrate how a starting position either increases or decreases through a series of changes.

So in our case, when planning for next year’s acute healthcare budget, we may start with the forecast outturn for the current year, throw our hands up in despair and then work through a series of savings initiatives that would hopefully bring us back to a position of affordability.

This can be easily demonstrated by the use of a simple waterfall chart which is a version of a stacked bar chart with some elements formatted so that they are not visible.

Here you can see the driving table and the series clearly visible before formatting.

Life in the waterfall chart world becomes a little more difficult if your movements are going to take you either above or below the x-axis.

You can imagine this happening if instead of starting with forecast outturn we model the affect on the planned deficit/surplas. The chart below shows the starting position before QIPP or savings programs and the incremental affect of appyling each projected saving.

The table required to generate this chart is quite complex but it does at least illustrate the position accurately if the data crosses the x-axis. The simple model shown above would display the data incorrectly.

You can download the spreadsheet I used to draw up these examples which will show all the relevant formulas Waterfall Chart.xlsx but I would also recommend that you take a look at the excellent Peltier website which explains the creation of both simple and complex waterfall charts in more detail and describes the formatting process step by step – I used this site to learn the techniques.

Managing Version Control in Excel using Macros

If you work with spreadsheets that are regularly being updated and amended by multiple users then you will know something about version control. If you then add to this mix, the need to periodically distribute the updated spreadsheet for public viewing you will be more than well aware that Microsoft Excel isn’t really designed to handle version control very well.

I’m currently working with NHS contract proposals for the new financial year. The models are complex and the figures involved are high. We are up to version 20 already and the first cut hasn’t even been published. Between each issued version I know I will be inundated with emails asking what has changed and why.

I’m getting twitchy just thinking about it and I’m already dreaming about version control.

I’ve put together a few macros to enforce some rules for version control and to maintain an audit trail of changes and versions.

Methodology for Controlling Versions in Microsoft Excel
My spreadsheet includes tabs of data related to patient care, but it will work for any model that you wish to maintain a version trail for.

You need a tab for documenting the changes between each version. Mine is called Version Control but then I’m an accountant and don’t really do imagination.
On this sheet are two named ranges, VERSION and FILENAME (in B7 and B9 respectively) and two macro buttons for running the save options.

When the document is saved I want the user to overwrite the master document so that it always shows the latest version and I want them to save the file to a version folder and increment the filename to the next version number. So each save process creates one new file and overwrites another.

To ensure this happens just as I want, I need to prevent users from saving the spreadsheet in their own way. I do this by writing a procedure in the workbook object that will run whenever they attempt to save the document.

1
2
3
4
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Cancel = True
MsgBox "When you are happy with the changes, please go to the VERSION CONTROL tab and freeze the document to save"
End Sub

This directs the user back to the Version Control tab where they can either perform a FREEZE save or a temporary save. The temporary save is for convenience in case large changes are being made, it will save a single version with a temporary filename. The FREEZE save creates a new version and overwrites the master document, it also requires the user to enter some notes relating to the changes made.

Here’s the code for the FREEZE save:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
Sub FreezeWorkbook()
'Working in Excel 97-2010
Dim FileExtStr As String
Dim FileFormatNum As Long
Dim Sourcewb As Workbook
Dim wsVC As Worksheet
Dim Destwb As Workbook
Dim VersionFilePath As String
Dim VersionFileName As String
Dim MasterFilePath As String
Dim MasterFileName As String
Dim VersionDate As String
Dim Version As String
Dim CurrentVersion As String
Dim iLastRowVC As Long 'LAST ROW IN VERSION CONTROL SHEET
Dim Author As String
Dim Changes As String
Dim AmendRef As String

With Application
.ScreenUpdating = False
.EnableEvents = False
End With

'UPDATE THE VERSION CONTROL DETAILS
ActiveSheet.Unprotect
Set wsVC = Worksheets("VERSION CONTROL")
iLastRowVC = wsVC.Cells(Rows.Count, "B").End(xlUp).Row

'Current Version
CurrentVersion = wsVC.Range("a" & iLastRowVC).Value

'Insert version
Version = InputBox("Please enter the next incremental Version Number (Current Version = " & CurrentVersion & ")")
wsVC.Range("a" & iLastRowVC + 1).Value = Version

'Insert version date
VersionDate = Format(Now, "dd-mm-yy hh-mm-ss")
wsVC.Range("B" & iLastRowVC + 1).Value = VersionDate

Author = InputBox("Please enter your name")
wsVC.Range("c" & iLastRowVC + 1) = Author

Changes = InputBox("Please enter a brief description of changes made")
wsVC.Range("d" & iLastRowVC + 1) = Changes

AmendRef = InputBox("Enter the ref to the Amendments Document or N/A if none available")
wsVC.Range("e" & iLastRowVC + 1) = AmendRef

wsVC.Range("F" & iLastRowVC + 1).Value = Range("HLACTIVITY") 'Returns a the total activity value from a named range within the spreadsheet
wsVC.Range("G" & iLastRowVC + 1).Value = Range("HLVALUE") 'Returns a the total financial value from a named range within the spreadsheet

'Save the master workbook
Application.DisplayAlerts = False
Set Sourcewb = ActiveWorkbook

'Force the file extension to remain as Excel 97-2003
FileExtStr = ".xls": FileFormatNum = -4143

'Overwrite the master report or create it if it doesn't yet exist
MasterFilePath = "c:\My Documents\2010-11 A&F Contract Templates\Masters" 'Amend to suit your filing system
MasterFileName = Range("FILENAME") & " Master"

Range("VERSION").FormulaR1C1 = Version 'Overwrite the version number on the version control sheet

With Sourcewb
.SaveAs MasterFilePath & MasterFileName, FileFormat:=FileFormatNum, WriteResPassword:="" 'This ensures that the master is not password protected otherwise I get problems overwriting the file
End With
Application.DisplayAlerts = True

'save the version workbook
Set Sourcewb = ActiveWorkbook
'Force the file extension to remain as Excel 97-2003
FileExtStr = ".xls": FileFormatNum = -4143

'Save the new workbook and close it
VersionFilePath = "c:\My Documents\A2010-11 A&F Contract Templates\Versions\RAP"

VersionFileName = Range("FILENAME") & " " & Version
ActiveSheet.Protect

With Sourcewb
.SaveAs VersionFilePath & VersionFileName & FileExtStr, FileFormat:=FileFormatNum, WriteResPassword:="password" 'this sets password protection in place for the version spreadsheets
End With

MsgBox "New version saved as " & VersionFilePath & VersionFileName & " and Master Updated"

With Application
.ScreenUpdating = True
.EnableEvents = True
End With
End Sub

and the Temporary Save code which saves the file in the versions folder with a “Temp” suffix:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Sub TempSave()
'Working in Excel 97-2010
Dim FileExtStr As String
Dim FileFormatNum As Long
Dim Sourcewb As Workbook
Dim Destwb As Workbook
Dim TempFilePath As String
Dim TempFileName As String
Dim TempVersion As String
With Application
.ScreenUpdating = False
.EnableEvents = False
End With

Set Sourcewb = ActiveWorkbook

'Force the file extension to remain as Excel 97-2003
FileExtStr = ".xls": FileFormatNum = -4143

ActiveSheet.Unprotect
TempFilePath = "c:\My Documents\2010-11 A&F Contract Templates\Versions"
TempVersion = Format(Now, "yy-mm-dd hh-mm-ss")
TempFileName = "Temp " & Range("FILENAME") & " " & TempVersion
Range("VERSION").FormulaR1C1 = TempVersion
ActiveSheet.Protect

With Sourcewb
.SaveAs TempFilePath & TempFileName & FileExtStr, FileFormat:=FileFormatNum
End With

MsgBox "New version saved as " & TempFilePath & TempFileName

With Application
.ScreenUpdating = True
.EnableEvents = True
End With
End Sub

I’ve used the plugin Syntax Higlighter to display the VBA code. It looks great but unfortunately it doesn’t copy and paste well. You can download a working spreadsheet that includes the modules above if you want to copy them – Excel Version Control Example V1 the password to open is NCL.

As usual I have botched this macro together from many hints and tips found elsewhere on the interweb, this page from Ron de Bruin was particularly useful.

I think I need to add some loops into the input box code because users can choose not to enter any comments or an appropriate version number at the moment and I have no means of controlling the errors.

I’ve also had to run this without any password protection on the master file. If I password protect it the macro fails with a write protection error. I’m not sure if I ought to be able to get around this or whether it is not actually possible to save over a write protected file. Dunno.

Excel Pivot Table or CrossTab to Flat List

Excel to CrossTabI often find myself struggling to deal with data that has been rendered almost unusable by the data provider who has converted it to a cross tab format. Usually they think they are helping and have probably started with a nice flat list then spent ages formatting it so the data spreads out across the columns. Sometimes they have achieved this by putting the data into a pivot table but then have pasted the table as values and removed all links to the underlying data. Very helpful.

[GARD align=”center”]

Yesterday I had to deal with a data file that looked a little like this only  it spread out across 455 columns and was frankly useless.

 

I wrote a post a while back that demonstrated how to convert a simple cross tab back to a manageable data list but I wanted to expand this with a slightly more complex example which had more field headings.

So starting with the table above which was probably a pasted copy from a pivot table I applied a little bit of formatting to achieve the following starting table.

What I have done is remove the grouping that the pivot table applies and ensured that I have the relevant  week number, project and department details against each name

I often wish to convert pivot table outputs back into a data file and the way I fill in the blanks caused by grouping data is to prepare a sheet with formulas that copy values from above (or the side as appropriate):

I then copy the pivot table data and then use the paste special command selecting paste values and skip blanks as the options.

This results in the following output, which I then copy and paste over itself as values, to give me the desired structure.

 

Having spent a bit of time on the structure of my starting file I can run the macro that will work through each row and transpose the data from a columnar layout to one based on rows.

The advantage of this format is that I can now create my own pivot tables and cut the data as I see fit to produce multiple summary formats according to my audience.

 

Here’s the macro I use:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
Sub CrossTabToList()

Dim wsCrossTab As Worksheet
Dim wsList As Worksheet
Dim iLastCol As Long
Dim iLastRow As Long
Dim iLastRowList As Long
Dim rngCTab As Range 'Used for range in Sheet1 cross tab sheet
Dim rngList As Range 'Destination range for the list
Dim ROW As Long

Set wsCrossTab = Worksheets("Sheet1") 'AMEND TO SHOW SHEET NUMBER WITH THE CROSS TAB
Set wsList = Worksheets.Add

'Find the last row in Sheet1 with the cross tab
iLastRow = wsCrossTab.Cells(Rows.Count, "A").End(xlUp).ROW

Set the initial value for the row in the destination worksheet
iLastRowList = 2

Find the last column in Sheet1 with the cross tab
iLastCol = wsCrossTab.Range("A2").End(xlToRight).Column

'SET THE HEADING TITLES IN THE LIST SHEET
wsList.Range("A1:F1") = Array("NAME", "PROJECT", "TYPE", "PLAN/ACTUAL", "WEEK", "HOURS")

'Start looping through the cross tab data

For ROW = 3 To iLastRow 'START AT ROW 3 AS THIS IS WHERE DATA BEGINS
Set rngCTab = wsCrossTab.Range("A" & ROW, "C" & ROW)'initial value A3 SETS THE RANGE TO

'INCLUDE ALL STATIC DATA - IN THIS CASE NAME, PROJECT, TYPE
Set rngList = wsList.Range("A" & iLastRowList) 'initial value A3

'Copy individual names in Col A (A3 initially) into as many rows as there are data columns
'in the cross tab (less 3 for Col A-C).
rngCTab.Copy rngList.Resize(iLastCol - 3)

'SELECT THE HEADING ROW WITH FORECAST/ACTUAL
'Move up ROW (INITIALLY 3) rows less TWO and across 3 columns (using offset function). Copy.
rngCTab.Offset(-(ROW - 2), 3).Resize(, iLastCol - 3).Copy

'Paste transpose to columns in the list sheet alongside the static data
rngList.Offset(0, 3).PasteSpecial Transpose:=True

'SELECT THE ROW WITH THE WEEK NUMBERS
'Move up ROW (INITIALLY 3) rows less ONE and across 3 columns (using offset function). Copy.
rngCTab.Offset(-(ROW - 1), 3).Resize(, iLastCol - 3).Copy

'Paste transpose to columns in the list sheet alongside the static data
rngList.Offset(0, 4).PasteSpecial Transpose:=True

'Staying on same row (3 initially) copy the data from the cross tab
rngCTab.Offset(, 3).Resize(, iLastCol - 3).Copy

'Past transpose as column in list sheet
rngList.Offset(0, 5).PasteSpecial Transpose:=True

'Set the new last row in list sheet to be just below the last name copied
iLastRowList = iLastRowList + (iLastCol - 3)

'increment ROW by 1
Next ROW

End Sub

I’m afraid the VBA code isn’t rendering very well – you’ll have to replace all the ” and ‘ marks before the macro editor will recognise the code.
Here is a copy of file – you could copy the macro code from here: Excel to CrossTab.xls

VBA Snippets – Sorting Arrays and Random Selection

The blog post sounds a little Darwinian but I’m afraid it’s just a dull selection of VBA code that enables you to sort one array on the basis of values from another. A double sorting array.

I’ve amended array sorting code from John Walkenbach and Anthony’s VBA Tutorials to create something I can understand.

The DbleBubbleSort routine takes two arrays as arguments and sorts the string array (y) on the basis of the values in a nuber array (x). I’m using John Walkenbachs BubbleSort methodology which is simple but undoubtedly not the most efficient.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Sub DbleBubbleSort(x() As Single, y() As String)

' adapting the BubbleSort method to sort an array on the basis of the values in another array.
' x is an array of numbers (can be random as in the DbleSortTest() routine)
' y is a string array
    Dim First As Integer, Last As Long
    Dim i As Long, j As Long
    Dim xtemp As Single
    Dim ytemp As String
   
    First = LBound(x)
    Last = UBound(x)
    For i = First To Last - 1
        For j = i + 1 To Last
            If x(i) > x(j) Then
                xtemp = x(j)
                ytemp = y(j)
                x(j) = x(i)
                y(j) = y(i)
                x(i) = xtemp
                y(i) = ytemp
            End If
        Next j
    Next i
End Sub

The following routine calls on the DbleBubbleSort sub routine and uses it to provide a random selection of 3 entries from a string array.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Sub DbleSortTest()
'this is an amended version of a function on http://www.anthony-vba.kefra.com/vba/excelvba-simulation.htm
'it randomly selects 3 entries from a text array (y)
'it utilises a random number array and the sorting of one array based on the values in another.

    Dim i As Long
    Dim x(8) As Single, y(8) As String
    Dim str As String

    y(1) = "Anthony"
    y(2) = "Bobby"
    y(3) = "Chris"
    y(4) = "Danny"
    y(5) = "Eton"
    y(6) = "Frank"
    y(7) = "George"
    y(8) = "Harry"
   
  'enters the x array into a worksheet
    For i = 1 To UBound(x)
        x(i) = Rnd
        Cells(i, 2) = x(i)
    Next i
   
  'enters the y array in column 3 of the worksheet
    For i = 1 To UBound(y)
        Cells(i, 3) = y(i)
    Next i
   
    Call DbleBubbleSort(x, y)
   
   'calls the first 3 values of y sorted according to x and enters them in the worksheet
    str = ""
    For i = 1 To 3
        str = str & y(i) & vbCrLf
        Cells(i, 1) = y(i)
    Next i

    MsgBox str
End Sub

VBA Snippets to Sort Arrays in VBA

Here’s another VBA snippet post. I find it handy to have a store of regularly used macros on the blog so I can access them at any time, not that I need them that often of course.

These particular snippets are useful routines for sorting arrays and can be called from other procedures. I’ve taken these particular ones from John Walkenbach’s book: Excel 2007 Power Programming with VBA. I’m working my way through Anthony’s VBA tutorials for excel modelling and array sorting is regular feature.

The BubbleSort code is easy to understand and therefore one of my favourites but the other 3 methods are much quicker for arrays with more than 500 entries.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Sub BubbleSort(list() As String)
' Taken from power programming with VBA
' It's a sorting procedure for 1-dimensional arrays named List
' The procedure takes each array element, if it is greater than the next element, the two elements swap positions.
' The evaluation is repeated for every pair of items (that is n-1 times)
' Change data type to As Integer if your array isn't text

    Dim First As Integer, Last As Long
    Dim i As Long, j As Long
    Dim temp As String

    First = LBound(list)
    Last = UBound(list)
    For i = First To Last - 1
        For j = i + 1 To Last
            If list(i) > list(j) Then
                temp = list(j)
                list(j) = list(i)
                list(i) = temp
            End If
        Next j
    Next i
End Sub

You can test this macro with the following routine which pastes the results into the immediate window.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Sub SortTester()
   
    Dim x(1 To 5) As String
    Dim i As Long
   
    x(1) = "egg"
    x(2) = "apple"
    x(3) = "stuff"
    x(4) = "nonsense"
    x(5) = "1"
   
    Call BubbleSort(x)
   
    For i = 1 To 5
        Debug.Print i, x(i)
    Next i
End Sub

Here are a few more sort options which are a bit more efficient but unfortunately more complex.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Sub WorksheetSort(list)
'   Sorts an array by transferring it to
'   A worksheet and using Excel's sorting command
   
    Dim First As Integer, Last As Long
    Dim i As Long
    Dim FirstCell As Range, LastCell As Range
    Dim CurrCell As Range, FillRange As Range
   
    First = LBound(list, 1)
    Last = UBound(list, 1)
    Set FirstCell = Sheets("Sheet1").Cells(1, 1)
    Set LastCell = Sheets("Sheet1").Cells(Last, 1)
    Set FillRange = Range(FirstCell, LastCell)
    Application.ScreenUpdating = False
   
'Transfer the array to worksheet
    FillRange.Value = list
   
'Sort the worksheet range
    FirstCell.CurrentRegion.Sort Key1:=FirstCell, Order1:=xlAscending, Orientation:=xlTopToBottom
      
'Transfer range back to the array and clear range
    For i = First To Last
        list(i, 0) = FirstCell.Offset(i - 1, 0)
    Next i
    FillRange.Clear
    Application.ScreenUpdating = True
End Sub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
Public Sub Quicksort(list() As Long, ByVal min As Long, ByVal max As Long)
'This technique works only with Integer or Long values.
   
    Dim med_value As Long
    Dim hi As Long
    Dim lo As Long
    Dim i As Long

   ' If min >= max, the list contains 0 or 1 items so it
    ' is sorted.
    If min >= max Then Exit Sub

  ' Pick the dividing value.
   i = Int((max - min + 1) * Rnd + min)
    med_value = list(i)

    ' Swap it to the front.
   list(i) = list(min)

    lo = min
    hi = max
    Do
      ' Look down from hi for a value < med_value.
      Do While list(hi) >= med_value
            hi = hi - 1
            If hi <= lo Then Exit Do
        Loop
        If hi <= lo Then
            list(lo) = med_value
            Exit Do
        End If

       ' Swap the lo and hi values.
       list(lo) = list(hi)
       
       ' Look up from lo for a value >= med_value.
        lo = lo + 1
        Do While list(lo) < med_value
            lo = lo + 1
            If lo >= hi Then Exit Do
        Loop
        If lo >= hi Then
            lo = hi
            list(hi) = med_value
            Exit Do
        End If
       
       ' Swap the lo and hi values.
        list(hi) = list(lo)
    Loop
   
    ' Sort the two sublists.
    Quicksort list(), min, lo - 1
    Quicksort list(), lo + 1, max
End Sub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Sub Countingsort(list)
'This technique works only with Integer or Long values.

    Dim counts()
    Dim i As Long
    Dim j As Long
    Dim next_index As Long
    Dim min, max
    Dim min_value As Variant, max_value As Variant

'   Allocate the counts array. VBA automatically
'   initialises all entries to 0.

    min_value = Minimum(list)
    max_value = Maximum(list)

    min = LBound(list)
    max = UBound(list)
   
    ReDim counts(min_value To max_value)
   
   ' Count the values.
    For i = min To max
        counts(list(i)) = counts(list(i)) + 1
    Next i
' Write the items back into the list array.
    next_index = min
    For i = min_value To max_value
        For j = 1 To counts(i)
            list(next_index) = i
            next_index = next_index + 1
        Next j
    Next i
End Sub
1
2
3
4
5
6
7
8
Function Minimum(list)
    Dim i As Long

    Minimum = list(LBound(list))
    For i = LBound(list) To UBound(list)
        If list(i) < Minimum Then Minimum = list(i)
    Next i
End Function
1
2
3
4
5
6
7
8
Function Maximum(list)
    Dim i As Long

    Maximum = list(LBound(list))
    For i = LBound(list) To UBound(list)
        If list(i) > Maximum Then Maximum = list(i)
    Next i
End Function

VBA Snippets to Delete Rows in Excel

I regularly use simple macros to delete blank rows in my excel spreadsheets and there are loads of examples on internet forums.

Screen shot 2009-09-05 at 15.59.57

I’ve recently had to recreate these delete row macros to install on a new computer and thought it would be useful to keep them filed here on the blog.

This first macro will delete any row in which the cell in the selected column is blank. So in this example, with column B selected, rows 4 & 7 will be deleted.

1
2
3
4
5
6
7
Sub DeleteRowOnBlankCell()
'Deletes entire row within the selected column where cell within selection is blank
Select column then run the macro
On Error Resume Next
Selection.EntireRow.SpecialCells(xlBlanks).EntireRow.Delete
On Error GoTo 0
End Sub

The following macro will only delete rows if the entire row is blank. Just select the area you want to evaluate and run the macro.

1
2
3
4
5
6
7
8
9
10
Sub DeleteBlankRows()

'Deletes the entire row within the selection provided the ENTIRE row is blank.
Dim i As Long
For i = Selection.Rows.Count To 1 Step -1
If WorksheetFunction.CountA(Selection.Rows(i)) = 0 Then
Selection.Rows(i).EntireRow.Delete
End If
Next i
End Sub

To use these macros just copy and paste them into a module within your personal macro workbook (press ALT F11 to open the VBA editor) which will make them available for use whenever excel is open. Sometimes the personal workbook doesn’t show up in the VBA editor and if this is the case I just record a new macro, select the option to save it in the personal workbook, make a few changes to a spreadsheet and stop recording. This will have the effect of creating a new module in  personal.xls, along with a fairly useless macro that can now be deleted.

Conditional Ranking in Excel – RankIF using SumProduct

I was hunting around for a RANKIF function yesterday so I could rank a load of hospitals in terms of their cost per procedure.

RankIF

Unfortunately RANKIF isn’t one of the available functions but I managed to achieve the same effect by utilising the SUMPRODUCT function.

I wrote a SUMPRODUCT tutorial some time ago indicating how the powerful function can act as a multi conditioned SUMIF formula but it seems it can also act as a multi conditioned RANKIF formula as well.

The formula I used to achieve the above ranking was:

=1+SUMPRODUCT(($B$2:$B$12=B2)*($C$2:$C$12>C2))

Copied down the column it will return the number of organisations for a given procedure which have costs higher than the selected row. Adding 1 to the result just ensures that the highest cost organisation starts with a rank of 1 rather than 0.

Excel CrossTab Table to Flat List

I had a query on my sumproduct tutorial that was effectively asking if I could work backwards from an excel crosstab or data table to get back to the simple flat list.

Converting a table like this:

sumproduct

Back to the data sheet like this:

sumproduct

If you didn’t have to do this sort of task very often you could get by with a pivot table solution and a few stages of formatting but it would only work if your data values were numbers that could be summed in the pivot.

If you need to convert Excel data tables (or crosstabs) into flat lists on a regular basis then you will need a macro.

I have prepared a workbook that demonstrates both the pivot table and macro solution and you can download it from here:
ExcelTabletoFlatList.xls

The following macro does the trick and utilises the VBA I introduced in my last post on consolidating worksheets, to determine the last used row and last used column.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
Sub CrossTabToList()

Dim wsCrossTab As Worksheet
Dim wsList As Worksheet
Dim iLastCol As Long
Dim iLastRow As Long
Dim iLastRowList As Long
Dim rngCTab As Range 'Used for range in Sheet1 cross tab sheet
Dim rngList As Range 'Destination range for the list
Dim I As Long

Set wsCrossTab = Worksheets("Sheet1")
Set wsList = Worksheets.Add

'Find the last row in Sheet1 with the cross tab
iLastRow = wsCrossTab.Cells(Rows.Count, "A").End(xlUp).Row

'Set the initial value for the row in the destination worksheet
iLastRowList = 2

'Find the last column in Sheet1 with the cross tab
iLastCol = wsCrossTab.Range("A1").End(xlToRight).Column

'Create a new sheet and set the heading titles
wsList.Range("A1:C1") = Array("NAME", "GRADE", "VALUE")

'Start looping through the cross tab data

For I = 2 To iLastRow

Set rngCTab = wsCrossTab.Range("A" & I) 'initial value A2
Set rngList = wsList.Range("A" & iLastRowList) 'initial value A2

'Copy individual names in Col A (A2 initially) into as many rows as there are data columns
'in the cross tab (less 1 for Col A).
rngCTab.Copy rngList.Resize(iLastCol - 1)

'Move up a I rows less one and across one column (using offset function) to select heading row. Copy.
rngCTab.Offset(-(I - 1), 1).Resize(, iLastCol - 1).Copy

'Paste transpose to columns in the list sheet alongside the names
rngList.Offset(0, 1).PasteSpecial Transpose:=True

'Staying on same row (2 initially) copy the data from the cross tab
rngCTab.Offset(, 1).Resize(, iLastCol - 1).Copy

'Past transpose as column in list sheet
rngList.Offset(0, 2).PasteSpecial Transpose:=True

'Set the new last row in list sheet to be just below the last name copied
iLastRowList = iLastRowList + (iLastCol - 1)

'increment I by 1
Next I

End Sub

Macros to Consolidate Worksheets

I’ve got a spreadsheet with tabs for each individual directorate. Within the tabs are tables with the same layout but different sizes (number of rows).

I want to group together all the used data on each tab and create a single consolidated sheet of continuous data.

Obviously I need a macro to copy and paste my data from each sheet but the tricky bit is selecting the relevant (or used) area in each sheet.

On my search around the interweb I found some really useful snippets of VBA code on the Ozgrid website which enable you to determine the last used row according to a number of different criteria and assign this to a variable such as “iLastRow”.

1
2
'find row before next blank cell in column A
iLastRow = Range("A1").End(xlDown).Row
1
2
'find last used row in column A
iLastRow = Cells(Rows.Count, "a").End(xlUp).Row
1
2
'find last used row on sheet
iLastRow = Cells.Find(what:="*", SearchOrder:=xlByRows, _SearchDirection:=xlPrevious).Row

It’s a simple step then to use this variable to define a range of used cells.

1
Range("A1:P" & iLastRow).Select

I’ve used the above techniques to consolidate my worksheets and here’s a copy of my working code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
Sub ConsolidateSheetsforSubForm()
' ConsolidateSheetsforSubForm Macro
' Copies used subform data in individual sheets and pastes into one sheet as continuous data

Sheets("Ap Table Sub Form").Select

'find last used row column a of Ap Table Sub Form sheet
ilastrowsubf = Cells(Rows.Count, "a").End(xlUp).Row

' Clear the existing data in the subform sheet
'specify location in subform to clear data
Range("a5:k" & ilastrowsubf).ClearContents

' Start selecting the data from Sheet S&M (LY)
Sheets("S&M (LY)").Select

' find last used row in column I
ilastrowsm = Cells(Rows.Count, "i").End(xlUp).Row

' use ilastrowsm variable to define range to copy in S&M sheet
Range("i4:s" & ilastrowsm).Copy

' specify location in subform to paste data
Sheets("Ap Table Sub Form").Select
Range("a5").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Sheets("K&R (BV)").Select

' find last used row in column I of K&R sheet
ilastrowkr = Cells(Rows.Count, "i").End(xlUp).Row

' use ilastrowkr variable to define range to copy in K&R sheet
Range("i4:s" & ilastrowkr).Copy
Sheets("Ap Table Sub Form").Select

' find first blank row in column a of Ap Table Sub Form sheet
ifirstblankrowsubf = (Cells(Rows.Count, "a").End(xlUp).Row) + 1

' specify location in subform to paste data
Range("a" & ifirstblankrowsubf).Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Sheets("OVERHEADS").Select

' find last used row in column I of OVERHEADS sheet
ilastrowover = Cells(Rows.Count, "i").End(xlUp).Row

' use ilastrowover variable to define range to copy in OVERHEADS sheet
Range("i4:s" & ilastrowover).Copy
Sheets("Ap Table Sub Form").Select

' find first blank row in column a of Ap Table Sub Form sheet
ifirstblankrowsubf = (Cells(Rows.Count, "a").End(xlUp).Row) + 1

' specify location in subform to paste data
Range("a" & ifirstblankrowsubf).Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Range("A5").Select
End Sub

You can do similar things in order to find the last column as well:

1
2
3
4
5
6
7
8
9
'find column before next blank cell in row 1
iLastColumn = Range("A1").End(xlToRight).Column

'find last used column in row 1
iLastColumn = Cells(Columns.Count, "a").End(xlToLeft).Column

'find last used column on sheet
iLastColumn = Cells.Find(what:="*", SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious).Column