2015-09-16 79 views
-2

我曾经有一个非常简单的矩阵。这个矩阵是这样的:在矩阵中插入行

year week amount  
2002 1  687 
until 
2013 52  8546 

然而,一些星期没有被记录。因此,下面的宏是为我写的。这个宏插入一个新行,在第一列中命名为正确的一年,在第二列中命名为正确的一周,在第三列中命名为零。

Sub CreateUnrecordedWeeks() 

' Defining used objects 
Dim FY As Integer, LY As Integer 
Dim I As Integer, ii As Integer 
Dim ObjDic1 As Object 
Set ObjDic1 = CreateObject("Scripting.Dictionary") 
Dim ObjDic2 As Object 
Set ObjDic2 = CreateObject("Scripting.Dictionary") 
Dim WkRg As Range 
Dim F As Range 
Set WkRg = Cells(1, 1).CurrentRegion 


With ObjDic1 
    For Each F In WkRg.Columns(1).Cells 
     .Item(F.Value & "/" & F.Offset(0, 1).Value) = F.Offset(0, 2).Value 
    Next F 

'  AAA = .keys: BBB = .items 

     FY = Evaluate("MIN((A:A))") 
     LY = Evaluate("MAX(A:A)") 

    For I = FY To LY 
     For ii = 1 To 52 
      If (.exists((I & "/" & ii))) Then 
       ObjDic2.Item(I & "/" & ii) = Array(I, ii, .Item(I & "/" & ii)) 
      Else 
       ObjDic2.Item(I & "/" & ii) = Array(I, ii, "0") 
      End If 
     Next ii 
    Next I 
End With 

With ObjDic2 
    Cells(1, 1).Resize(.Count, 3) = Application.Transpose(Application.Transpose(.items)) 
End With 


End Sub 

然而,我的矩阵改为:

Year Week 10230001 10230003 etc etc 
2002 1  564  56 
until 
2013 52  85   5868 

所以我的问题是:我应该如何改变聘用大矩阵宏观(174个colums至少)

正如我使用旧的宏,我看到宏实际上不插入一行,但将它下面的单元格复制下来。结果,附加的列不会移动,所以错误的时间变量连接到它。所以我需要向下复制整个矩阵或插入一行。但是如何?

+0

你对“第一周”的定义是什么?从1月1日开始的七天?从一年的第一个星期一开始的一周?包含今年第一个星期四的一周?其中第三个是ISO定义,但前两个也很受欢迎。该定义确定哪些年份有第53周,因此在考虑添加缺失的周数之前必须知道。 –

+0

@TonyDallimore,谢谢你的指挥。事实上,由于焦点确实在时间上,应该知道应该在哪里增加第53周。但是,我使用的数据集具有很强的季节性。在原始数据集中没有第53周。为了避免复杂的统计数据,并且看到第53周没有必要,我决定看到它不像一个日期那么多,将它添加到52 –

回答

0

Part 1对不起,解答共分两部分。我无法使用Stack Overflow的新系统发布图像,而我使用的文本表却导致此答案超过了30,000个字符限制。我已经报告了这个错误,Stack Overflow的技术人员正在调查。

我并没有试图调试现有的代码,因为:

  • 它包含了一些简单的错误,一个非常初级程序员不会做出提示很差质量的代码。调试质量差的代码可能非常难以实现,因为您不确定作者的意图。
  • 这个问题并不能证明使用一本字典更不用说两个字典。
  • 它没有做任何接近你想要的东西,所以重写是必要的。

我创建了一些数据来代表你的:

| A | B | C | D | E | F | G | 
1 |Year |Week |Amounts|--> |  |  |  | 
2 | 2002|  2|  11|  21|  31|  41|  51| 
3 | 2002|  15|  24|  34|  44|  54|  64| 
4 | 2002|  17|  |  36|  46|  56|  66| 
5 | 2002|  18|  27|  37|  47|  57|  | 
6 | 2002|  43|  27|  37|  47|  57|  67| 
7 | 2002|  44|  28|  38|  48|  58|  68| 
8 | 2003|  21|  32|  42|  52|  |  | 
9 | 2003|  23|  34|  44|  54|  64|  74| 
10| 2003|  24|  10|  20|  30|  40|  50| 
11| 2003|  44|  30|  40|  50|  60|  70| 
12| 2003|  45|  31|  41|  |  |  | 
13| 2003|  46|  32|  42|  52|  62|  72| 
14| 2003|  52|  13|  23|  33|  43|  53| 
15| 2003|  53|  14|  24|  34|  44|  54| 
16| 2005|  1|  17|  27|  37|  47|  | 
17| 2005|  29|  20|  30|  40|  50|  60| 
18| 2005|  53|  19|  29|  39|  49|  59| 
19| 2006|  1|  20|  30|  40|  50|  60| 
20| 2006|  2|  11|  21|  31|  41|  51| 
21| 2007|  53|  0|  0|  0|  0|  0| 

注意要点:

  • 我省略了大部分的行,所以你可以看到我的所有数据。下面的宏插入所有缺失的行
  • 你说你希望宏最少处理174列。我添加了一些额外的列来演示宏可以处理多于3列。我没有对所有使用的列进行测试,但它应该能够处理任何数量的列,直到您正在使用的版本的Excel最大值。
  • 某些行具有空尾列。我不知道您的数据是否有空列结尾,但这表明宏不受空列影响。新行的大小适合最长的现有行。
  • 如果第53周有现有行,宏将接受它们,但它仅创建第1至52周的行。
  • 2002或2003年没有第1周。我怀疑第1周缺少任何一个数据,但如果是的话,宏将插入它。
  • 没有2004年。这个宏在这个缺失的一年增加了第1至52周。
  • 该表应该表明我有2002年至2006年的不完整数据。目前我没有2007年的数据,所以我在第53周添加了一个空行。该宏将插入第1至52周。如果我有不需要第53周,我可以在第1周做出行,宏将在第2周到第52周添加行。目前,您有2002年到2013年的数据。如果您想开始收集2014年的数据,请添加一个排2014年,宏将增加其余。
0

宏观数据后的第2部分

启动已运行。

| A | B | C | D | E | F | G | H | I | 
1 |Year |Week |Amounts|--> |  |  |  |  |  | 
2 | 2002|  1|  0|  0|  0|  0|  0|  |  | 
3 | 2002|  2|  10|  20|  30|  40|  |  |  | 
4 | 2002|  3|  11|  21|  31|  41|  51|  |  | 
5 | 2002|  4|  0|  0|  0|  0|  0|  |  | 
6 | 2002|  5|  0|  0|  0|  0|  0|  |  | 
7 | 2002|  6|  0|  0|  0|  0|  0|  |  | 
8 | 2002|  7|  0|  0|  0|  0|  0|  |  | 
9 | 2002|  8|  0|  0|  0|  0|  0|  |  | 
10 | 2002|  9|  0|  0|  0|  0|  0|  |  | 
11 | 2002|  10|  0|  0|  0|  0|  0|  |  | 
12 | 2002|  11|  0|  0|  0|  0|  0|  |  | 
13 | 2002|  12|  0|  0|  0|  0|  0|  |  | 
14 | 2002|  13|  0|  0|  0|  0|  0|  |  | 
15 | 2002|  14|  0|  0|  0|  0|  0|  |  | 
16 | 2002|  15|  24|  34|  44|  54|  64|  |  | 
17 | 2002|  16|  0|  0|  0|  0|  0|  |  | 
18 | 2002|  17|  |  36|  46|  56|  66|  |  | 
19 | 2002|  18|  27|  37|  47|  57|  |  |  | 
20 | 2002|  19|  0|  0|  0|  0|  0|  |  | 
21 | 2002|  20|  0|  0|  0|  0|  0|  |  | 
22 | 2002|  21|  0|  0|  0|  0|  0|  |  | 
23 | 2002|  22|  0|  0|  0|  0|  0|  |  | 
24 | 2002|  23|  0|  0|  0|  0|  0|  |  | 
25 | 2002|  24|  0|  0|  0|  0|  0|  |  | 
26 | 2002|  25|  0|  0|  0|  0|  0|  |  | 
27 | 2002|  26|  0|  0|  0|  0|  0|  |  | 
28 | 2002|  27|  0|  0|  0|  0|  0|  |  | 
29 | 2002|  28|  0|  0|  0|  0|  0|  |  | 
30 | 2002|  29|  0|  0|  0|  0|  0|  |  | 

尝试宏并研究我的代码。我已经解释了宏的每个部分的目标,但是我没有解释大部分的VBA语句,因为一旦知道它们存在就很容易查询语句。例如,尝试搜索“Excel VBA Option Explicit”。回过头来问一些问题,但你可以为自己制定的方案越多,开发速度就会越快。

Option Explicit 
    ' Constants allow you to use names instead of literals that might change over 
    ' time. You only have one header row and perhaps this will not change but 
    ' it is better to avoid making such assumptions. If you ever do add a second 
    ' header row, one change here will fix the macro. 
    Const RowDataFirst As Long = 2 

    ' Columns can be letters or numbers with "A"=1, "B"=2, "C"=3 and so on 
    Const ColYear As Long = 1 
    Const ColWeek As Long = 2 
    Const ColDataFirst As Long = 3 

    ' Change to your name for the worksheet containing the matrix 
    Const WshtName As String = "Data" 
Sub CreateUnrecordedWeeks() 

    Dim ColCrnt As Long 
    Dim ColLast As Long 
    Dim RowCrnt As Long 
    Dim RowLast As Long 
    Dim RowValues() As Variant 
    Dim WeekCrnt As Long 
    Dim YearCrnt As Long 

    ' This stops the screen being repainted everytime a row is inserted 
    Application.ScreenUpdating = False 

    ' "Cells(1, 1).CurrentRegion" requires/assumes that the user has started the 
    ' macro with the correct worksheet active. This may be very likely in this 
    ' case but it is is a bad habit to make this assumption so best not to start. 
    ' Use a With statement to specify the worksheet unless there is an 
    ' operational reason why using the worksheet selected by the user is 
    ' appropriate. 
    With Worksheets(WshtName) 

    ' Excel VBA often provides several methods of achiving the same objective. 
    ' There are several methods of finding the last row and or column none of 
    ' which gives what the naive programmer might expect in every situation. 
    ' Separate Finds for the last row and last column containing any value is 
    ' the most reliable and I believe appropriate for your situation. In 
    ' particular it allows some existing rows to have missing trailing values 
    ' without this causing problrms for the macro. 
    RowLast = .Cells.Find("*", .Range("A1"), xlFormulas, , xlByRows, xlPrevious).Row 
    ColLast = .Cells.Find("*", .Range("A1"), xlFormulas, , xlByColumns, xlPrevious).Column 

    ' "Debug.Print" outputs values to the Immediate Window (at the bottom of the screen). 
    ' I use Debug.Print to check values are as I expect before moving on to the next 
    ' section of the macro. 
    'Debug.Print "RowLast " & RowLast 
    'Debug.Print "ColLast " & ColLast 

    ' Validate existing rows have valid years and weeks in ascending order 
    '===================================================================== 
    ' To add missing rows, the macro requires that, in every case, Row(N+1) is 
    ' for a later year or week than Row(N). Validating that this requirement is 
    ' met before starting the update avoiding creating a half updated matrix. 

    ' For rows RowDataFirst to RowLast: 
    ' 1) column A must hold a value in the range 2000 to 2099 
    ' 2) column B must hold a value in the range 1 to 53 
    ' 3) Cells(Row+1,"A") must be equal to or greater than Cells(Row,"A") 
    ' 4) If Cells(Row+1,"A") equals Cells(Row,"A") then Cells(Row+1,"B") 
    '  must be greater than Cells(Row,"B") 

    ' Check the first data row here. Within loop check the second of each 
    ' pair. This means each row is only checked once 
    If Not HasRowValidYearWeek(RowDataFirst) Then 
     ' User has already been told of problem 
     ' "Debug.Assert False" stops execution. I place it at the top of every path 
     ' through my code. Once it has been reached, I comment it out. Any that remain 
     ' when I have finished testing imply my testing has been inadequate. 
     'Debug.Assert False 
     Exit Sub 
    End If 

    ' Check each data row (except the first) against the previous row 
    For RowCrnt = RowDataFirst + 1 To RowLast 

     If Not HasRowValidYearWeek(RowCrnt) Then 
     ' User has already been told of problem 
     'Debug.Assert False 
     Exit Sub 
     End If 

     If .Cells(RowCrnt, ColYear) = .Cells(RowCrnt - 1, ColYear) Then 
     If .Cells(RowCrnt, ColWeek) > .Cells(RowCrnt - 1, ColWeek) Then 
      ' Same year, increased week so current row belongs after previous row 
      'Debug.Assert False 
     ElseIf .Cells(RowCrnt, ColWeek) = .Cells(RowCrnt - 1, ColWeek) Then 
      'Debug.Assert False 
      Call MsgBox("Row " & RowCrnt & " has the same year" & _ 
         " and week as the previous row.", vbOKOnly) 
      Exit Sub 
     Else 
      'Debug.Assert False 
      Call MsgBox("Row " & RowCrnt & _ 
         " belongs before the previous row.", vbOKOnly) 
      Exit Sub 
     End If 
     ElseIf .Cells(RowCrnt, ColYear) > .Cells(RowCrnt - 1, ColYear) Then 
     ' Increased year so current row belongs after previous row 
     'Debug.Assert False 
     Else 
     'Debug.Assert False 
     Call MsgBox("Row " & RowCrnt & _ 
        " belongs before the previous row.", vbOKOnly) 

     End If 
    Next 

    'Debug.Print "Data OK" 

    ' Generate a row of zeros for any row to be inserted. This row is the length 
    ' of the longest existing row. 
    ReDim RowValues(1 To 1, 1 To ColLast) 
    ' VBA allows a range to loaded to an array or an array to be loaded to a 
    ' range with: 
    ' 1) VariantArray = Range.Value 
    ' 2) Range.Value = VariantArray 
    ' With format 1, the interpreted ReDims VariantArray to match the range 
    ' size. With format 2, The range and array sizes should match. I leave you 
    ' to experiment to discover what happens if the the sizes do not match. 
    ' VariantArray is a two dimensional array. The first dimension is for rows 
    ' and the second for columns. The is the opposite of the normal convention 
    ' but means the access matches Cells(Row, Column) 
    ' RowValues(1, ColYear) and RowValues(1, ColWeek) will be overwritten when 
    ' a row is inserted. 
    For ColCrnt = 1 To ColLast 
     RowValues(1, ColCrnt) = 0 
    Next 

    RowCrnt = 2 

    ' The first row must be for week 1 of a year 
    YearCrnt = .Cells(RowCrnt, ColYear).Value 
    WeekCrnt = 0 

    ' This is the main loop. It cannot be a For-Loop because rows will be 
    ' inserted and the end value for a For-Loop can be changed within the loop. 
    ' Each repeat of this loop does one of the following: 
    ' 1) Determines that the next required row is already present and 
    '  advances to the next row 
    ' 2) Determines a mid-year is missing and inserts it. The previous 
    '  current row remains the current row 
    ' 3) Determines the current year is complete and prepares for the next 
    ' 4) Determines a trailing week for a year is missing and adds it. The 
    '  year previous current row remains the current row 
    Do While RowCrnt <= RowLast 
     If YearCrnt = .Cells(RowCrnt, ColYear).Value Then 
     ' Have another row for the same year 
     'Debug.Assert False 
     WeekCrnt = WeekCrnt + 1 
     If WeekCrnt = .Cells(RowCrnt, ColWeek).Value Then 
      ' The next row is already present 
      'Debug.Assert False 
      RowCrnt = RowCrnt + 1 ' Advance to next row 
      ' No more processing for this loop 
     Else 
      ' The next row is not present 
      'Debug.Assert False 
      .Rows(RowCrnt).Insert ' Insert row above RowCrnt 
      RowLast = RowLast + 1 
      RowValues(1, ColYear) = YearCrnt 
      RowValues(1, ColWeek) = WeekCrnt 
      .Range(.Cells(RowCrnt, 1), .Cells(RowCrnt, ColLast)).Value = RowValues 
      RowCrnt = RowCrnt + 1 ' Advance to previous current row 
      ' No more processing for this loop 
     End If 
     Else 
     ' Next row is for a different year 
     'Debug.Assert False 
     If WeekCrnt = 52 Or WeekCrnt = 53 Then 
      ' YearCrnt is finished 
      'Debug.Assert False 
      YearCrnt = YearCrnt + 1 
      WeekCrnt = 0 
      ' No more processing for this loop 
     Else 
      ' A trailing week is missing. Add it. 
      'Debug.Assert False 
      WeekCrnt = WeekCrnt + 1 
      .Rows(RowCrnt).Insert ' Insert row above RowCrnt 
      RowLast = RowLast + 1 
      RowValues(1, ColYear) = YearCrnt 
      RowValues(1, ColWeek) = WeekCrnt 
      .Range(.Cells(RowCrnt, 1), .Cells(RowCrnt, ColLast)).Value = RowValues 
      RowCrnt = RowCrnt + 1 ' Advance to previous current row 
      ' No more processing for this loop 
     End If 
     End If 

    Loop 

    End With 

    Application.ScreenUpdating = True 

End Sub 
Function ColNumToCode(ByVal ColNum As Long) As String 

    Dim ColCode As String 
    Dim PartNum As Long 

    ' Last updated 3 Feb 12. Adapted to handle three character codes. 
    If ColNum = 0 Then 
    ColNumToCode = "0" 
    Else 
    ColCode = "" 
    Do While ColNum > 0 
     PartNum = (ColNum - 1) Mod 26 
     ColCode = Chr(65 + PartNum) & ColCode 
     ColNum = (ColNum - PartNum - 1) \ 26 
    Loop 
    End If 

    ColNumToCode = ColCode 

End Function 
Function HasRowValidYearWeek(ByVal RowCrnt As Long) As Boolean 

    ' Return True if column ColYear of RowCrnt is in the range 2000-2099 and 
    ' column ColWeek of RowCrnt is in the range 1-53 

    HasRowValidYearWeek = True 

    With Worksheets(WshtName) 

     If IsNumeric(.Cells(RowCrnt, ColYear).Value) Then 
     If .Cells(RowCrnt, ColYear).Value >= 2000 And _ 
      .Cells(RowCrnt, ColYear).Value <= 2099 Then 
      'Debug.Assert False 
      ' Column A of first data row has good value 
     Else 
      'Debug.Assert False 
      Call MsgBox("Cell " & ColNumToCode(ColYear) & RowCrnt & _ 
         " is not in the range 2000-2099.", vbOKOnly) 
      HasRowValidYearWeek = False 
     End If 
     Else 
     'Debug.Assert False 
     Call MsgBox("Cell " & ColNumToCode(ColYear) & RowCrnt & _ 
        " is not numeric.", vbOKOnly) 
      HasRowValidYearWeek = False 
     End If 

     If IsNumeric(.Cells(RowCrnt, ColWeek).Value) Then 
     If .Cells(RowCrnt, ColWeek).Value >= 1 And _ 
      .Cells(RowCrnt, ColWeek).Value <= 53 Then 
      'Debug.Assert False 
      ' Column A of first data row has good value 
     Else 
      'Debug.Assert False 
      Call MsgBox("Cell " & ColNumToCode(ColWeek) & RowCrnt & _ 
         " is not in the range 1-53.", vbOKOnly) 
      HasRowValidYearWeek = False 
     End If 
     Else 
     'Debug.Assert False 
     Call MsgBox("Cell " & ColNumToCode(ColWeek) & RowCrnt & _ 
        " is not numeric.", vbOKOnly) 
     HasRowValidYearWeek = False 
     End If 

    End With 

End Function