【问题标题】:How to "flatten" or "collapse" a 2D Excel table into 1D?如何将 2D Excel 表格“展平”或“折叠”成 1D?
【发布时间】:2010-10-15 19:21:45
【问题描述】:

我在 Excel 中有一个包含国家和年份的二维表。例如。

        1961        1962        1963        1964
USA      a           x            g           y
France   u           e            h           a
Germany  o           x            n           p

我想“展平”它,这样我在第一列中有 Country,在第二列中有 Year,然后在第三列中有 value。例如。

Country      Year       Value
USA          1961       a
USA          1962       x
USA          1963       g
USA          1964       y
France       1961       u
              ...

我在这里展示的示例只是一个 3x4 矩阵,但我拥有的真实数据集要大得多(大约 50x40 左右)。

有什么建议我可以如何使用 Excel 做到这一点?

【问题讨论】:

    标签: vba excel


    【解决方案1】:

    您可以使用 excel 数据透视表功能来反转数据透视表(本质上就是您在这里所拥有的):

    这里有很好的说明:

    http://spreadsheetpage.com/index.php/tip/creating_a_database_table_from_a_summary_table/

    如果您不想手动按照说明操作,则链接到以下 VBA 代码(将其放入模块中):

    Sub ReversePivotTable()
    '   Before running this, make sure you have a summary table with column headers.
    '   The output table will have three columns.
        Dim SummaryTable As Range, OutputRange As Range
        Dim OutRow As Long
        Dim r As Long, c As Long
    
        On Error Resume Next
        Set SummaryTable = ActiveCell.CurrentRegion
        If SummaryTable.Count = 1 Or SummaryTable.Rows.Count < 3 Then
            MsgBox "Select a cell within the summary table.", vbCritical
            Exit Sub
        End If
        SummaryTable.Select
        Set OutputRange = Application.InputBox(prompt:="Select a cell for the 3-column output", Type:=8)
    '   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)
                OutputRange.Cells(OutRow, 3).NumberFormat = SummaryTable.Cells(r, c).NumberFormat
                OutRow = OutRow + 1
            Next c
        Next r
    End Sub
    

    -亚当

    【讨论】:

    • 很棒的资源,这正是我想要的。谢谢亚当!
    • 这对我有用。这使用起来非常简单。使用此代码的人不需要通过此答案中提供的链接。只需按原样复制代码并运行宏。就是这样!
    【解决方案2】:

    @Adam Davis 的回答是完美的,但如果您像我一样对 Excel VBA 一无所知,以下是我为使代码在 Excel 2007 中工作所做的工作:

    1. 使用需要展平为表格的矩阵打开工作簿并导航到该工作表
    2. 按 Alt-F11 打开 VBA 代码编辑器。
    3. 在左侧窗格的“项目”框中,您将看到一个树结构,表示 Excel 对象和任何已存在的代码(称为模块)。右键单击框内任意位置,选择“插入->模块”创建一个空白模块文件。
    4. 从上面复制 @Adman Davis 的代码并将其粘贴到打开的空白页中并保存。
    5. 关闭 VBA 编辑器窗口并返回到电子表格。
    6. 单击矩阵中的任何单元格以指示您将使用的矩阵。
    7. 现在您需要运行宏。此选项的位置将根据您的 Excel 版本而有所不同。当我使用 2007 时,我可以告诉您,它会将其宏保留在“视图”功能区中,作为最右侧的控件。点击它,您会看到一个宏的清单,只需双击一个名为“ReversePivotTable”的宏即可运行它。
    8. 然后它将显示一个弹出窗口,要求您告诉它在哪里创建展平表。只需将其指向电子表格中的任何空白区域,然后点击“确定”

    你已经完成了!第一列是行,第二列是列,第三列是数据。

    【讨论】:

      【解决方案3】:

      在 Excel 2013 中需要执行以下步骤:

      • 选择数据并转换为表格(插入 -> 表格
      • 为表调用查询编辑器(Power Query -> 从表
      • 选择包含年份的列
      • 在上下文菜单中选择“Unpivot Columns”-命令。

      Support Office: Unpivot columns (Power Query)

      在 Excel 2016 中,Power Query 被称为 Get & Transform,它位于 Data 选项卡中。

      【讨论】:

      • 使用新方法使旧问题保持最新的奖励积分。特别提及嵌套在您提供的 support.office.com 链接中的 Troubleshooting Power Query 链接可能是一个想法。我记得当分析工具包不是默认安装时,任何提及EOMONTH function 之类的功能都必须附有如何访问它的说明。
      • 在 Excel 2016 中,Power Query 被称为 Get & Transform,它位于 Data 选项卡中。否则,此解决方案将完美运行。
      • 这是迄今为止最简单的解决方案。适用于任意数量的列,不需要使用宏。
      【解决方案4】:

      扁平化数据矩阵(又名表格)可以通过一个数组公式¹和两个标准公式来完成。

            

      数组公式¹和G3:I3中的两个标准公式是,

      =IFERROR(INDEX(A$2:A$4, MATCH(0, IF(COUNTIF(G$2:G2, A$2:A$4&"")<COUNT($1:$1), 0, 1), 0)), "")
      =IF(LEN(G3), INDEX($B$1:INDEX($1:$1, MATCH(1E+99,$1:$1 )), , COUNTIF(G$3:G3, G3)), "")
      =INDEX(A:J,MATCH(G3,A:A,0),MATCH(H3,$1:$1,0))
      

      根据需要填写。

      虽然数组公式会因其循环计算而对性能产生负面影响,但您所描述的 40 行 × 50 列的工作环境不应过度影响性能并导致计算滞后。


      ¹ 数组公式需要使用 Ctrl+Shift+Enter↵ 完成。一旦正确输入第一个单元格,它们就可以像任何其他公式一样被填充或向下或向右复制。尝试减少对更接近代表实际数据范围的范围的全列引用。数组公式以对数方式消耗计算周期,因此最好将引用范围缩小到最小值。更多信息请参见Guidelines and examples of array formulas

      【讨论】:

        【解决方案5】:

        对于想要使用数据透视表执行此操作并遵循以下指南的任何人: http://spreadsheetpage.com/index.php/tip/creating_a_database_table_from_a_summary_table/

        如果您想在 Excel 2007 或 2010 中执行此操作,则首先需要启用数据透视表向导。

        要找到您需要通过主 Excel 窗口图标转到“Excel 选项”的选项,并查看在“自定义”部分中选择的选项,然后从“选择命令”中选择“不在功能区中的命令” :" 下拉菜单和“数据透视表和数据透视图向导”需要添加到右侧.. 见下图。

        完成后,Excel 窗口顶部的快捷栏菜单中应该会出现一个可透视的小向导图标,然后您可以按照上面链接中所示的相同过程进行操作。

        【讨论】:

          【解决方案6】:

          我开发了另一个宏,因为我需要经常刷新输出表(输入表被其他人填充)并且我想在我的输出表中包含更多信息(更多复制的列和一些公式)

          Sub TableConvert()
          
          Dim tbl As ListObject 
          Dim t
          Rows As Long
          Dim tCols As Long
          Dim userCalculateSetting As XlCalculation
          Dim wrksht_in As Worksheet
          Dim wrksht_out As Worksheet
          
          '##block calculate and screen refresh
          Application.ScreenUpdating = False
          userCalculateSetting = Application.Calculation
          Application.Calculation = xlCalculationManual
          
          '## get the input and output worksheet
          Set wrksht_in = ActiveWorkbook.Worksheets("ressource_entry")'## input
          Set wrksht_out = ActiveWorkbook.Worksheets("data")'## output.
          
          
          '## get the table object from the worksheet
          Set tbl = wrksht_in.ListObjects("Table14")  '## input
          Set tb2 = wrksht_out.ListObjects("Table2") '## output.
          
          '## delete output table data
          If Not tb2.DataBodyRange Is Nothing Then
              tb2.DataBodyRange.Delete
          End If
          
          '## count the row and col of input table
          
          With tbl.DataBodyRange
               tRows = .Rows.Count
               tCols = .Columns.Count
          End With
          
          '## check every case of the input table (only the data part)
          For j = 2 To tRows '## parse all row from row 2 (header are not checked)
              For i = 5 To tCols '## parse all column from col 5 (first col will be copied in each record)
                  If IsEmpty(tbl.Range.Cells(j, i).Value) = False Then
                      '## if there is time enetered create a new row in table2 by using the first colmn of the selected cell row and cell header plus some formula
                      Set oNewRow = tb2.ListRows.Add(AlwaysInsert:=True)
                      oNewRow.Range.Cells(1, 1).Value = tbl.Range.Cells(j, 1).Value
                      oNewRow.Range.Cells(1, 2).Value = tbl.Range.Cells(j, 2).Value
                      oNewRow.Range.Cells(1, 3).Value = tbl.Range.Cells(j, 3).Value
                      oNewRow.Range.Cells(1, 4).Value = tbl.Range.Cells(1, i).Value
                      oNewRow.Range.Cells(1, 5).Value = tbl.Range.Cells(j, i).Value
                      oNewRow.Range.Cells(1, 6).Formula = "=WEEKNUM([@Date])"
                      oNewRow.Range.Cells(1, 7).Formula = "=YEAR([@Date])"
                      oNewRow.Range.Cells(1, 8).Formula = "=MONTH([@Date])"
                  End If
             Next i
          Next j
          ThisWorkbook.RefreshAll
          
          '##unblock calculate and screen refresh
          Application.ScreenUpdating = True 
          Application.Calculate
          Application.Calculation = userCalculateSetting
          
          End Sub
          

          【讨论】:

            【解决方案7】:

            在某些情况下(例如由于安全原因无法嵌入宏等),VBA 解决方案可能不可接受。对于这些情况,以及其他一般情况,我更喜欢使用公式而不是宏。

            我试图在下面描述我的解决方案。

            • 输入数据如问题所示 (B2:F5)
            • column_header (C2:F2)
            • row_header (B3:B5)
            • data_matrix (C3:F5)
            • no_of_data_rows (I2) = COUNTA(row_header) + COUNTBLANK(row_header)
            • no_of_data_columns (I3) = COUNTA(column_header) + COUNTBLANK(column_header)
            • no_output_rows (I4) = no_of_data_rows*no_of_data_columns
            • 种子区为K2:M2,为空白但被引用,因此不被删除
            • K3(拖过说 K100,参见 cmets 描述)= ROW()-ROW($K$2)
            • L3(拖过 L100,参见 cmets 描述)= IF(K3,IF(COUNTIF($L$2:L2,L2)
            • M3(拖过 M100,见 cmets 说明)= IF(K3,IF(M2
            • N3(拖过说 N100,参见 cmets 描述)= INDEX(row_header,L3)
            • O3(拖动 O100,参见 cmets 说明)= INDEX(column_header,M3)
            • P3(拖动 P100,参见 cmets 说明)= INDEX(data_matrix,L3,M3)
            • K3 中的注释:可选:检查是否预期没有。已实现输出行数。不需要,如果一个人只准备此表,则限制为否。输出行数。
            • L3 中的注释:目标:每个 RowIndex (1 .. no_of_data_rows) 必须重复 no_of_data_columns 次。这将为 row_header 值提供索引查找。在此示例中,每个 RowIndex (1 .. 3) 必须重复 4 次。 算法:检查 RowIndex 已经发生了多少次。如果它小于 no_of_data_columns 次,则继续使用该 RowIndex,否则递增 RowIndex。 可选:检查是否预期否。的输出行数已达到。
            • M3 中的注释:目标:每个 ColumnIndex (1 .. no_of_data_columns) 必须在一个循环中重复。这将为 column_header 值提供索引查找。在此示例中,每个 ColumnIndex (1 .. 4) 必须在一个循环中重复。 算法:如果 ColumnIndex 超过 no_of_data_columns,则从 1 重新开始循环,否则增加 ColumnIndex。 可选:检查是否预期否。的输出行数已达到。
            • R4 中的注释:可选:使用列 K 进行错误处理,如 L 列和 M 列所示。检查是否查找值 IsBlank 以避免由于空白而导致输出中出现错误的“0”在 data_matrix 中输入。

            【讨论】:

            • 请不要在帖子中包含您的电子邮件地址或其他联系信息。我已经为你删除了。
            【解决方案8】:

            更新了 ReversePivotTable 函数,因此我可以指定标题列和行数

            Sub ReversePivotTable()
            '   Before running this, make sure you have a summary table with column headers.
            '   The output table will have three columns.
                Dim SummaryTable As Range, OutputRange As Range
                Dim OutRow As Long
                Dim r As Long, c As Long
            
                Dim lngHeaderColumns As Long, lngHeaderRows As Long, lngHeaderLoop As Long
            
                On Error Resume Next
                Set SummaryTable = ActiveCell.CurrentRegion
                If SummaryTable.Count = 1 Or SummaryTable.Rows.Count < 3 Then
                    MsgBox "Select a cell within the summary table.", vbCritical
                    Exit Sub
                End If
                SummaryTable.Select
            
                Set OutputRange = Application.InputBox(prompt:="Select a cell for the 3-column output", Type:=8)
                lngHeaderColumns = Application.InputBox(prompt:="Header Columns")
                lngHeaderRows = Application.InputBox(prompt:="Header Rows")
            '   Convert the range
                OutRow = 2
                Application.ScreenUpdating = False
                'OutputRange.Range("A1:D3") = Array("Column1", "Column2", "Column3", "Column4")
                For r = lngHeaderRows + 1 To SummaryTable.Rows.Count
                    For c = lngHeaderColumns + 1 To SummaryTable.Columns.Count
                        ' loop through all header columns and add to output
                        For lngHeaderLoop = 1 To lngHeaderColumns
                            OutputRange.Cells(OutRow, lngHeaderLoop) = SummaryTable.Cells(r, lngHeaderLoop)
                        Next lngHeaderLoop
                        ' loop through all header rows and add to output
                        For lngHeaderLoop = 1 To lngHeaderRows
                            OutputRange.Cells(OutRow, lngHeaderColumns + lngHeaderLoop) = SummaryTable.Cells(lngHeaderLoop, c)
                        Next lngHeaderLoop
            
                        OutputRange.Cells(OutRow, lngHeaderColumns + lngHeaderRows + 1) = SummaryTable.Cells(r, c)
                        OutputRange.Cells(OutRow, lngHeaderColumns + lngHeaderRows + 1).NumberFormat = SummaryTable.Cells(r, c).NumberFormat
                        OutRow = OutRow + 1
                    Next c
                Next r
            End Sub
            

            【讨论】:

              【解决方案9】:

              声称具有某种普遍性的代码 这本书应该有两张纸: 酸 = 源数据 Dest =“扩展”表将放在这里

                  Option Explicit
                  Private ws_Sour As Worksheet, ws_Dest As Worksheet
                  Private arr_2d_Sour() As Variant, arr_2d_Dest() As Variant
                  ' https://stackoverflow.com/questions/52594461/find-next-available-value-in-excel-cell-based-on-criteria
                  Public Sub PullOut(Optional ByVal msg As Variant)
                      ws_Dest_Acr _
                              arr_2d_ws( _
                              arr_2d_Dest_Fill( _
                              arr_2d_Sour_Load( _
                              arr_2d_Dest_Create( _
                              CountA_rng( _
                              rng_2d_For_CountA( _
                              Init))))))
                  End Sub
              
                  Private Function ws_Dest_Acr(Optional ByVal msg As Variant) As Variant
                      ws_Dest.Activate
                  End Function
              
                  Public Function arr_2d_ws(Optional ByVal msg As Variant) As Variant
                      If IsArray(arr_2d_Dest) Then _
                         ws_Dest.Cells(1, 1).Resize(UBound(arr_2d_Dest), UBound(arr_2d_Dest, 2)) = arr_2d_Dest
                  End Function
              
                  Private Function arr_2d_Dest_Fill(Optional ByVal msg As Variant) As Variant
                      Dim y_Sour As Long, y_Dest As Long, x As Long
                      y_Dest = 1
                      For y_Sour = LBound(arr_2d_Sour) To UBound(arr_2d_Sour)
                          ' without the first column
                          For x = LBound(arr_2d_Sour, 2) + 1 To UBound(arr_2d_Sour, 2)
                              If arr_2d_Sour(y_Sour, x) <> Empty Then
                                  arr_2d_Dest(y_Dest, 1) = arr_2d_Sour(y_Sour, 1)    'iD
                                  arr_2d_Dest(y_Dest, 2) = arr_2d_Sour(y_Sour, x)    'DTLx
                                  y_Dest = y_Dest + 1
                              End If
                          Next
                      Next
                  End Function
              
                  Private Function arr_2d_Sour_Load(Optional ByVal msg As Variant) As Variant
                      arr_2d_Sour = ReDuce_rng(ws_Sour.UsedRange, 1, 0).Offset(1, 0).Value
                  End Function
              
                  Private Function arr_2d_Dest_Create(ByVal iRows As Long)
                      Dim arr_2d() As Variant
                      ReDim arr_2d(1 To iRows, 1 To 2)
                      arr_2d_Dest = arr_2d
                      arr_2d_Dest_Create = arr_2d
                  End Function
              
                  Public Function CountA_rng(ByVal rng As Range) As Double
                      CountA_rng = Application.WorksheetFunction.CountA(rng)
                  End Function
              
                  Private Function rng_2d_For_CountA(Optional ByVal msg As Variant) As Range
                      ' without the first line and without the left column
                      Set rng_2d_For_CountA = _
                      ReDuce_rng(ws_Sour.UsedRange, 1, 1).Offset(1, 1)
                  End Function
              
                  Public Function ReDuce_rng(rng As Range, ByVal iRow As Long, ByVal iCol As Long) _
                         As Range
                      With rng
                          Set ReDuce_rng = .Resize(.Rows.Count - iRow, .Columns.Count - iCol)
                      End With
                  End Function
              
                  Private Function Init()
                      With ThisWorkbook
                          Set ws_Sour = .Worksheets("Sour")
                          Set ws_Dest = .Worksheets("Dest")
                      End With
                  End Function
              
              'https://youtu.be/oTp4aSWPKO0
              

              【讨论】:

                猜你喜欢
                • 2013-04-19
                • 2020-01-20
                • 1970-01-01
                • 1970-01-01
                • 2018-01-28
                • 2018-12-27
                • 2015-05-28
                • 2011-09-01
                • 2019-03-12
                相关资源
                最近更新 更多