Home > Blockchain >  Converting weekly data in a table to monthly data using VBA
Converting weekly data in a table to monthly data using VBA

Time:10-06

I have a table of hours against weeks (start of the week is a Sunday). The weekly data goes up to 12-16 months dependent on user input. I want to create a VBA macro which will iterate through this table of weekly hours data and convert the columns into monthly data.

Example: All October 2021 related columns will collapse into 1 column called Oct-21. This will also combine the hours. 2nd row in the image below would equal 4 3 4 0= therefore value would be 11 in the new combined column's 2nd row.

My current thinking was calculating the Sundays between the start date and the last date which is below:

Dim d As Date, format As String, w As Long, FirstSunday As String
format = format(lastMonth, "Medium Date")
d = DateSerial(Year(format), Month(format), 1)
w = Weekday(d, vbSunday)
FirstSunday = d   IIf(w <> 1, 8 - w, 0)

Any ideas on how to do this?

Weekly Data image

This is the desired output for the new monthly table

CodePudding user response:

Not sure how you want to group the weeks into months as some months will have 5 weeks. This code inserts a column when the month changes and then fills it with a sum formula for the relevant week columns. It assumes the dates are on row 1 , the task numbers in column 1 and the first week is in column 2.

Option Explicit

Sub ByMonth()

    Dim wb As Workbook, ws As Worksheet
    Dim LastCol As Long, LastRow As Long, c As Long, n As Long
    Dim dt As Date

    Set wb = ThisWorkbook
    Set ws = wb.Sheets("Sheet1")
    
    LastCol = ws.Cells(1, Columns.Count).End(xlToLeft).Column
    LastRow = ws.Cells(Rows.Count, 1).End(xlUp).Row

    ' scan cols from right to left insert new columns
    Application.ScreenUpdating = False
    For c = LastCol   1 To 3 Step -1
        ' add columns on month change
        If Month(ws.Cells(1, c)) <> Month(ws.Cells(1, c - 1)) Then
             ws.Columns(c).Insert
             With ws.Columns(c)
                .HorizontalAlignment = xlCenter
                '.Interior.Color = RGB(255, 255, 200)
                .Font.Bold = True
                .Cells(1).NumberFormat = "@"
             End With
        End If
    Next

    ' scan left to right filling new cols with sum() formula
    ' hide weekly columns
    LastCol = ws.Cells(1, Columns.Count).End(xlToLeft).Column
    n = 0
    For c = 2 To LastCol   1
       If ws.Cells(1, c) = "" Then
          dt = ws.Cells(1, c - 1)
          ws.Cells(1, c) = MonthName(Month(dt), True) & "  " & Year(dt)
          ws.Cells(2, c).Resize(LastRow - 1).FormulaR1C1 = "=SUM(RC[-" & n & "]:RC[-1])"
          n = 0
       Else
          ws.Columns(c).EntireColumn.Hidden = True
          n = n   1
       End If
    Next

    ' copy visible month columns to sheet2
    ws.Cells.SpecialCells(xlCellTypeVisible).Copy
    With wb.Sheets("Sheet2")
        .Activate
        .Range("A1").PasteSpecial xlPasteValues
        .Range("A1").Select
    End With
      
    ' end
    ws.Columns.Hidden = False
    Application.ScreenUpdating = True
    Application.CutCopyMode = False
    MsgBox "Done"

End Sub

CodePudding user response:

Please, try the next code. It assumes that in column A:A, starting from the 6th row, there are (not sorted) tasks. If they are sorted, the code will run without problem, too. It uses arrays and a dictionary and mostly working in memory, should be very fast for big ranges:

Sub SumWeeksMonths()
  Dim sh As Worksheet, sh1 As Worksheet, lastR As Long, arrWk, arrMonths, arrTasks
  Dim i As Long, k As Long, j As Long, El, arr, arrFin, dict As New Scripting.Dictionary
  
  Set sh = ActiveSheet 'use there the sheet to be processed
  Set sh1 = sh.Next    'use here the sheet where the processed result to be returned
  lastR = sh.Range("A" & sh.rows.count).End(xlUp).row 'last row (in column A:A)
  arrWk = sh.Range(sh.Range("B5"), sh.cells(5, sh.Columns.count).End(xlToLeft)).Value 'place the Week headers in a 2D array
  ReDim arrMonths(UBound(arrWk, 2) - 1)'redim the 1D array to keep the unique munths, at a maximum size
  For i = 1 To UBound(arrWk, 2) - 1    'create the array of (only) months:
        If month(DateValue(arrWk(1, i))) <> month(DateValue(arrWk(1, i   1))) Then
            k = k   1: arrMonths(k) = Format(DateValue(arrWk(1, i   1)), "mmm-yyyy")
        Else
            arrMonths(k) = Format(DateValue(arrWk(1, i)), "mmm-yyyy")
        End If
  Next i
  ReDim Preserve arrMonths(k) 'preserve only the existing Date elements
  For Each El In sh.Range("A4:A" & lastR).Value
     dict(El) = 1 'extract the unique tasks (only to count them for ReDim the necessary array)
 Next El
  'place all the range to be processed in an array (for faster iteration):
  arr = sh.Range("A5", sh.cells(lastR, sh.cells(5, sh.Columns.count).End(xlToLeft).Column)).Value
  ReDim arrFin(1 To UBound(dict.Keys)   1, 1 To UBound(arrMonths)   2) 'reDim the final array to keep processed data
  ReDim arrTasks(UBound(arrMonths))  'redim the array to temporarily keep the array of each task summ
  dict.RemoveAll: k = 0  'clear the dictionary and reitinialize the K variable
  
  For i = 2 To UBound(arr)              'iterate between the main array elements:
     If Not dict.Exists(arr(i, 1)) Then 'if the Task key does not exist:
        For Each El In arrMonths        'iterate between each month in arrMonths:
            For j = 2 To UBound(arr, 2) 'iterate between all arr columns for the i row:
                If month(DateValue(arr(1, j))) = month(El) Then 'if column months is a specific arrMonths column:
                    arrTasks(k) = arrTasks(k)   arr(i, j)               'sumarize everything in the arrTask each element
                End If
         Next j
         k = k   1                      'increment k, for the next month
       Next El
       dict.Add arr(i, 1), arrTasks     'create the dictionary key with the tasks array as item
       ReDim arrTasks(UBound(arrMonths)): k = 0 'reinitialize arrTasks and k variable
    Else                                        'if dictionary (task) key exists:
        For Each El In arrMonths
            For j = 2 To UBound(arr, 2)
                If month(DateValue(arr(1, j))) = month(El) Then
                    arrTasks(k) = dict(arr(i, 1))(k)   arr(i, j) 'add the sum to the allready existing elements
                End If
         Next j
         k = k   1
       Next El
       dict(arr(i, 1)) = arrTasks                     'make the item the updaded array
       ReDim arrTasks(UBound(arrMonths)): k = 0       'reinitialize arrTasks and k variable
    End If
  Next i

  'place the processed values in final array (arrFin):
  For i = 0 To UBound(arrMonths) 'firstly the headers:
        arrFin(1, i   2) = arrMonths(i)
  Next i
  'Extract the tasks value for each month and place in the final array appropriate columns:
  For i = 0 To dict.count - 1           'iterate between the dictionary elements:
      arrFin(i   2, 1) = dict.Keys(i)   'place the task in the array first column, starting from the second row
     For j = 0 To UBound(dict.items(i)) 'iterate between the dictionary item array elements
        arrFin(i   2, j   2) = dict.items(i)(j) 'place the appropriate array elements in the final array (arrFin)
     Next j
  Next i
  'drop the final array at once and make some formatting:
  With sh1.Range("A1").Resize(UBound(arrFin), UBound(arrFin, 2))
    .Value = arrFin
    With .rows(1)
        .Font.Bold = True
        .Interior.ColorIndex = 20
        .BorderAround 1
    End With
    .EntireColumn.AutoFit
    .BorderAround 1
 End With
 sh1.Activate 'to see the processing result...
 MsgBox "Ready..."
End Sub

Please, test it and send some feedback.

  • Related