I am processing an unformatted CSV, New-AmbSYS-to-2018-Jan.csv, from NHS England Ambulance Quality Indicators, by reading its contents into an array, and carrying out a number of steps that lead to a final output array format.
The file
There are an initial 79 columns in the file and, depending on the month, a variable number of rows. The total number of rows will never be enormous.
Header row
First 5 columns:
| Year | Month | Region | Org Code | Org Name |
|------|-------|--------|----------|----------|
Rest of columns - A further 74 columns labeled A0
to A73
. These are codes for indicators.
I am not going to show you all of those columns for obvious reasons. The dataset is publicly available and I have included the direct download link and the website link.
Process:
Reduce number of columns and re-order
Function
GetData
grabs all the data from the sheet (it will always be the first sheet), ignoring the header row (optional argument), and loads into an arraydataArray
.Function
ReduceColumnsArray
takesdataArray
, grabs the columns of interest and orders them, as per the specification in arraycolumnsToKeepArray
. I tried to apply what I learnt from the answers to my prior question on CR.
The new smaller array, also called dataArray
, is now composed of 21 columns.
The column ordering is as follows (1 sample row included. Header added to aid with later column mapping description):
| Year | Month | Region | Org Code | Org Name | A8 | A10 | A11 | A12 | A24 | A30 | A33 | A36 | A25 | A31 | A34 | A37 | A26 | A32 | A35 | A38 |
|------|-------|--------|----------|------------------------------------|------|-------|-------|------|---------|----------|----------|----------|-----|------|------|------|-----|------|------|------|
| 2018 | 1 | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | 8469 | 55555 | 21759 | 2743 | 3639835 | 68077967 | 80385509 | 11249217 | 430 | 1225 | 3694 | 4101 | 705 | 2528 | 8738 | 8344 |
Unpivot columns in array and populate output array
Output format
The output array, finalArray
, has the following output format; with 4 output rows for each single input row (in dataArray
):
| Date | Year | Month | STP | Region | Org Code | Org Name | Category | Count of Incidents | Total (hours) | Mean (hh:mm:ss) | 90th centile (hh:mm:ss) |
|------|------|-------|-----|--------|----------|----------|----------|--------------------|---------------|-----------------|-------------------------|
| | | | | | | | | A8 | A24 | A25 | A26 |
| | | | | | | | | A10 | A30 | A31 | A32 |
| | | | | | | | | A11 | A33 | A34 | A35 |
| | | | | | | | | A12 | A36 | A37 | A38 |
Observations
There are additional columns in the output,
Date
(1),STP
(4) andCategory
(8).From column 6 of input,
dataArray
, onwards, every 4 columns unpivot into 4 rows in one column such that, for the remaining 16 columns (6:21), there are 4 columns with 4 rows (for every one input row) generated.E.g. If you look at
A8
,A10
,A11
andA12
, they "unpivot", I use that term loosely, into theCount of Incidents
column. It will be the actual corresponding values i.e.8469 | 55555 | 21759 | 2743
for the sample row, but I have used the headers for illustrative purposes.
Arranging the data
Function
GetFinalArray
takesdataArray
as an argument and performs the assignment ofdataArray
values tofinalArray
.Dimensioning
finalArray
is defined by the fact UBound(finalArray,2) is known,12
; and that the UBound(finalArray,1) will be, due to the unpivot, 4 * the number of rows read in i.e. 4 * UBound(dataArray,1).
The additional output columns
Column 1 (Date): Function
GetDate
is used to populate this.Column 4 (STP): This uses a placeholder,
vbNullString
, at present.Column 8 (Category): This is populated by the
GetCategory
function. This is based on the input rows being ordered such that rows in always run in sequence 1,2,3,4, which map to "Cat1: Life Threatening" , "Cat2: Emergency", "Cat3: Urgent" , "Cat4: Less Urgent". Themod
function is applied to the current row to determine this i.e. if current row in arraymod
4, 4 for the unpivot, is0
thenCat4
, if 1 thenCat1
etc.
Input to output mapping
The individual row/column mapping from dataArray
to finalArray
i.e. input to output, using a single row example is:
| | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|---|---|-----|-----|---|-----|-----|-----|---|-----|------|------|------|
| 1 | | 1,1 | 1,2 | | 1,3 | 1,4 | 1,5 | | 1,6 | 1,10 | 1,14 | 1,18 |
| 2 | | 1,1 | 1,2 | | 1,3 | 1,4 | 1,5 | | 1,7 | 1,11 | 1,15 | 1,19 |
| 3 | | 1,1 | 1,2 | | 1,3 | 1,4 | 1,5 | | 1,8 | 1,12 | 1,16 | 1,20 |
| 4 | | 1,1 | 1,2 | | 1,3 | 1,4 | 1,5 | | 1,9 | 1,13 | 1,17 | 1,21 |
Where, as shown, in matching picture below, the red are the finalArray
(row,column) positions and the inner black, "coordinates", are the corresponding values to assign from dataArray
.
To get the "coordinates" I use the fact that the row part of the coordinate increments 1 for every 4 rows of the outer array.
So row 5 would be:
| 5 | | 2,1 | 2,2 | | 2,3 | 2,4 | 2,5 | | 2,6 | 2,10 | 2,14 | 2,18 |
Pattern to get output column for current row of finalArr
is implemented by function GetFinalColumn
, and has the following logic:
| | finalColumn |
|-------------------------|----|----|----|----|
|MOD of outer row number | 9 | 10 | 11 | 12 | <input value
|-------------------------|----|----|----|----|
| 1 | 6 | 10 | 14 | 18 | >output
| 2 | 7 | 11 | 15 | 19 | >output
| 3 | 8 | 12 | 16 | 20 | >output
| 0 | 9 | 13 | 17 | 21 | >output
So, if I am currently assigning to the outer array (1,9) then 1 mod 4 is 1; and the intersection above, of 1 and 9, is 6. So I use (1,6) as my coordinates.
Module MainSub
Option Explicit
Public Sub Main()
Dim wb As Workbook
Dim ws As Worksheet
Set wb = ThisWorkbook
Set ws = wb.Sheets(1)
Dim dataArray()
dataArray = ReduceColumnsArray(GetData(ws, 1))
Dim finalArray As Variant
finalArray = GetFinalArray(dataArray)
Dim destinationRange As Range
Set destinationRange = wb.Worksheets("Output").Range("A2")
destinationRange.Resize(UBound(finalArray, 1), UBound(finalArray, 2)) = finalArray
End Sub
Module FunctionsModule
Option Explicit
Option Base 1
Public Function GetFinalArray(ByVal dataArray As Variant) As Variant 'to convert to function
Dim i As Long
Dim j As Long
Dim finalArray()
Dim totalOutputRows As Long
Dim numberRowsInDataSet As Long
numberRowsInDataSet = UBound(dataArray, 1)
totalOutputRows = 4 * numberRowsInDataSet 'scale to account for columns becoming rows
ReDim finalArray(1 To totalOutputRows, 1 To 12)
Dim outputCol As Long
outputCol = 0
Dim sourceRow As Long
For i = LBound(finalArray, 1) To UBound(finalArray, 1)
If i Mod 4 = 1 Then sourceRow = sourceRow + 1
finalArray(i, 1) = GetDate(dataArray(sourceRow, 1), dataArray(sourceRow, 2)) 'this needs formatting to generate output 'yyyy-mm-dd' ;adding -01 as dd
finalArray(i, 2) = dataArray(sourceRow, 1)
finalArray(i, 3) = dataArray(sourceRow, 2)
finalArray(i, 4) = vbNullString 'STP - TODO Add function call in to return this value
finalArray(i, 5) = dataArray(sourceRow, 3)
finalArray(i, 6) = dataArray(sourceRow, 4)
finalArray(i, 7) = dataArray(sourceRow, 5)
finalArray(i, 8) = GetCategory(i) 'Category
Dim n As Long
For n = 9 To 12
finalArray(i, n) = dataArray(sourceRow, GetFinalColumn(n, i)) 'finalColumn
Next n
Next i
GetFinalArray = finalArray
End Function
Public Function ReduceColumnsArray(ByVal dataArray As Variant) As Variant
'From the initial 79 columns keep only 21 columns as specified in columnsToKeepArray
Const ColsToKeep As Long = 21
Dim columnsToKeepArray()
columnsToKeepArray = Array(1, 2, 3, 4, 5, 14, 16, 17, 18, 19, 25, 28, 31, 20, 26, 29, 32, 21, 27, 30, 33)
Dim i As Long
Dim j As Long
Dim tempArr()
ReDim tempArr(1 To UBound(dataArray, 1), 1 To ColsToKeep)
For i = LBound(dataArray, 1) To UBound(dataArray, 1)
For j = LBound(columnsToKeepArray) To UBound(columnsToKeepArray)
tempArr(i, j) = dataArray(i, columnsToKeepArray(j)) 'map the required columns to the output array
Next j
Next i
ReduceColumnsArray = tempArr
End Function
Public Function GetData(ByVal ws As Worksheet, Optional ByVal offsetRows As Long = 0) As Variant
Dim dataArray()
Dim startRange As Range
Set startRange = ws.Range("A1").CurrentRegion
With startRange
dataArray = .Offset(offsetRows, 0).Resize(.Rows.Count - offsetRows, .Columns.Count).Value2
End With
GetData = dataArray
End Function
Public Function GetDate(ByVal yearValue As String, ByVal monthValue As String) As Date '? or string
GetDate = CDate(Format$(yearValue & "-" & monthValue & "-" & "01", "yyyy-mm-dd"))
End Function
Public Function GetCategory(ByVal i As Long) As String
Select Case i Mod 4 'I know number will not exceed range for mod
Case 1
GetCategory = "Cat1: Life Threatening"
Case 2
GetCategory = "Cat2: Emergency"
Case 3
GetCategory = "Cat3: Urgent"
Case 0
GetCategory = "Cat4: Less Urgent"
Case Else
GetCategory = "Unknown"
End Select
End Function
Public Function GetFinalColumn(ByVal finalColumn As Long, ByVal i As Long) As Long
Dim testVal As Long
Dim n As Long
testVal = i Mod 4 'i will always be a positive integer. It will not exceed the max for mod.
Select Case testVal
Case 0
n = 3
Case 1
n = 0
Case 2
n = 1
Case 3
n = 2
End Select
Select Case finalColumn
Case 9
GetFinalColumn = 6 + n
Case 10
GetFinalColumn = 10 + n
Case 11
GetFinalColumn = 14 + n
Case 12
GetFinalColumn = 18 + n
End Select
End Function
Test dataset:
This represents a sample of rows from dataArray
as produced in the sub main
by:
dataArray = ReduceColumnsArray(GetData(ws, 1))
Dataset -
| Year | Month | Region | Org Code | Org Name | A8 | A10 | A11 | A12 | A24 | A30 | A33 | A36 | A25 | A31 | A34 | A37 | A26 | A32 | A35 | A38 |
|------|-------|--------|----------|------------------------------------|------|-------|-------|------|---------|----------|----------|----------|-----|------|------|------|-----|------|-------|-------|
| 2017 | 8 | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| 2017 | 9 | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| 2017 | 10 | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| 2017 | 11 | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | 7658 | 52321 | 22062 | 2586 | 3248966 | 57927986 | 75274310 | 11186014 | 424 | 1107 | 3412 | 4326 | 688 | 2192 | 8100 | 8934 |
| 2017 | 12 | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | 8551 | 56491 | 21108 | 2748 | 3800787 | 81948038 | 94600478 | 13564482 | 444 | 1451 | 4482 | 4936 | 724 | 3071 | 10736 | 10309 |
| 2018 | 1 | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | 8469 | 55555 | 21759 | 2743 | 3639835 | 68077967 | 80385509 | 11249217 | 430 | 1225 | 3694 | 4101 | 705 | 2528 | 8738 | 8344 |
Output from test dataset
| 01/08/2017 | 2017 | 8 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat1: Life Threatening | . | . | . | . |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/08/2017 | 2017 | 8 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat2: Emergency | . | . | . | . |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/08/2017 | 2017 | 8 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat3: Urgent | . | . | . | . |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/08/2017 | 2017 | 8 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat4: Less Urgent | . | . | . | . |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/09/2017 | 2017 | 9 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat1: Life Threatening | . | . | . | . |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/09/2017 | 2017 | 9 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat2: Emergency | . | . | . | . |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/09/2017 | 2017 | 9 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat3: Urgent | . | . | . | . |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/09/2017 | 2017 | 9 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat4: Less Urgent | . | . | . | . |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/10/2017 | 2017 | 10 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat1: Life Threatening | . | . | . | . |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/10/2017 | 2017 | 10 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat2: Emergency | . | . | . | . |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/10/2017 | 2017 | 10 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat3: Urgent | . | . | . | . |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/10/2017 | 2017 | 10 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat4: Less Urgent | . | . | . | . |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/11/2017 | 2017 | 11 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat1: Life Threatening | 7658 | 3248966 | 424 | 688 |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/11/2017 | 2017 | 11 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat2: Emergency | 52321 | 57927986 | 1107 | 2192 |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/11/2017 | 2017 | 11 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat3: Urgent | 22062 | 75274310 | 3412 | 8100 |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/11/2017 | 2017 | 11 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat4: Less Urgent | 2586 | 11186014 | 4326 | 8934 |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/12/2017 | 2017 | 12 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat1: Life Threatening | 8551 | 3800787 | 444 | 724 |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/12/2017 | 2017 | 12 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat2: Emergency | 56491 | 81948038 | 1451 | 3071 |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/12/2017 | 2017 | 12 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat3: Urgent | 21108 | 94600478 | 4482 | 10736 |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/12/2017 | 2017 | 12 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat4: Less Urgent | 2748 | 13564482 | 4936 | 10309 |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/01/2018 | 2018 | 1 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat1: Life Threatening | 8469 | 3639835 | 430 | 705 |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/01/2018 | 2018 | 1 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat2: Emergency | 55555 | 68077967 | 1225 | 2528 |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/01/2018 | 2018 | 1 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat3: Urgent | 21759 | 80385509 | 3694 | 8738 |
|------------|------|----|---|-----|-----|------------------------------------|------------------------|-------|----------|------|-------|
| 01/01/2018 | 2018 | 1 | | Y56 | RRU | LONDON AMBULANCE SERVICE NHS TRUST | Cat4: Less Urgent | 2743 | 11249217 | 4101 | 8344 |
Select Case
inGetFinalColumn
is not needed. Usen = (i - 1) Mod 4
instead. \$\endgroup\$