取消透视 Excel 矩阵/数据透视表?
Posted
技术标签:
【中文标题】取消透视 Excel 矩阵/数据透视表?【英文标题】:Unpivot an Excel matrix/pivot-table? 【发布时间】:2015-08-20 10:01:09 【问题描述】:有没有一种快速“取消透视” Excel 矩阵/数据透视表(在 Excel 或其他地方)的方法,无需编写宏或其他代码? 同样,我可以自己编写代码(C# 或 VBA 或其他)。 我想知道是否有可能无需代码快速完成?
例如我需要转换这个权限矩阵(以 Excel 表格/矩阵形式给出)
到这个半规范化的表中(这样我就可以将它插入到 SQL 数据库中):
例如在 SQL 中我可以这样做:
CREATE TABLE dbo.T_DocumentMatrix
(
[Function] [varchar](255) NULL,
[GROUP-Admin] [varchar](255) NULL,
[GROUP-SuperUser] [varchar](255) NULL,
[GROUP-Manager] [varchar](255) NULL,
[GROUP-OLAP] [varchar](255) NULL,
[GROUP-1] [varchar](255) NULL,
[GROUP-2] [varchar](255) NULL,
[GROUP-3] [varchar](255) NULL,
[GROUP-4] [varchar](255) NULL,
[GROUP-5] [varchar](255) NULL,
[GROUP-6] [varchar](255) NULL,
[GROUP-7] [varchar](255) NULL,
[GROUP-8] [varchar](255) NULL,
[Externals] [varchar](255) NULL
);
从excel中复制粘贴数据,然后
SELECT *
FROM
(
SELECT
[Function]
,[GROUP-Admin]
,[GROUP-SuperUser]
,[GROUP-Manager]
,[GROUP-OLAP]
,[GROUP-1]
,[GROUP-2]
,[GROUP-3]
,[GROUP-4]
,[GROUP-5]
,[GROUP-6]
,[GROUP-7]
,[GROUP-8]
,[Externals]
FROM T_DocumentMatrix
) AS p
UNPIVOT
(
Rights FOR GroupName IN
(
[GROUP-Admin]
,[GROUP-SuperUser]
,[GROUP-Manager]
,[GROUP-OLAP]
,[GROUP-1]
,[GROUP-2]
,[GROUP-3]
,[GROUP-4]
,[GROUP-5]
,[GROUP-6]
,[GROUP-7]
,[GROUP-8]
,[Externals]
)
) AS unpvt
;
但是,这需要我为组中的每个更改更改表创建脚本和 unpivot-script...
【问题讨论】:
可以很快完成:superuser.com/a/78464(但您可能需要先将*
替换为1
)。
@user3964075:实际上你不需要替换 *,它可以作为字符串正常工作。之前找到了那个帖子,但对我没有用,因为我无法启动枢轴向导。在 youtube 上找到了答案。
【参考方案1】:
哦,好吧,这有点复杂。 问题之一是,向导调用快捷方式在非英文版的 excel 中不起作用(该死,在家里我会有英文版,但在这里工作......)
这是一个很好的视频: https://www.youtube.com/watch?v=pUXJLzqlEPk
但是可以删除 youtube 视频,所以要让它成为一个可靠的答案:
首先,您需要转到“选项”,然后添加菜单带项“数据透视表和数据透视图向导”。
创建多个合并数据透视表
并使用自定义变体
并选择范围,然后在新工作表中
然后删除行和列字段
双击数字(图中为 54)
Excel 会为您提供中途标准化数据。
【讨论】:
【参考方案2】:虽然这是一个非常古老的问题,并且 Stefan 在过去找到了一个开明的答案,但它可能值得重新审视。我自己也遇到了对这种无代码、动态 Unpivot 方法的需求,谷歌搜索将我带到了这里。是的,Power Query 完成了这项工作,但这并不是完全无代码的,因为在 Power BI 中运行了一个脚本化的后台解决方案,它需要用户刷新数据(因此,它在工作簿中不是自动的)并且它不会在 Excel for Mac (tmk) 上运行。
以下是基于动态数组并使用 LET 函数的方法,因此需要 Excel 2016 或 Microsoft 365。
假设 Stefan 的数据位于单元格 A1 到 N8 中。在 Power Query 中,我们会说 Stefan 想要“Unpivot B1:N8 By A1:A8”。
注意:以下方法也将接受 By 的多个列,例如您可能需要“Unpivot D1:N8 By A1:C8”。
=LET( unPivMatrix, B1:N8,
byMatrix, A1:A8,
upC, COLUMNS( unPivMatrix ),
byC, COLUMNS( byMatrix ),
dmxR, MIN( ROWS( unPivMatrix ), ROWS( byMatrix ) ) - 1,
upCells, dmxR * upC,
upSeq, SEQUENCE( upCells,, 0 ),
upHdr, INDEX( INDEX( unPivMatrix, 1, ), 1, SEQUENCE( upC ) ),
upBody, INDEX( unPivMatrix, SEQUENCE( dmxR ) + 1, SEQUENCE( 1, upC ) ),
byBody, INDEX( byMatrix, SEQUENCE( dmxR ) + 1, SEQUENCE( 1, byC ) ),
attr, INDEX( upHdr, MOD( upSeq, upC ) + 1 ),
mux, INDEX( upBody, upSeq/upC + 1, MOD( upSeq, upC ) + 1 ),
demux, IFERROR( INDEX(
IFERROR( INDEX( byBody,
IFERROR( INT( SEQUENCE( upCells, byC,0 )/byC/upC ) + 1, MOD( upSeq, upC ) + 1 ),
SEQUENCE( 1, byC + 1 ) ),
attr ),
upSeq + 1, SEQUENCE( 1, byC + 2 ) ),
mux ),
demux
)
这是如何工作的 - 读取输入
输入是您想要取消透视的范围,我称之为 unPivMatrix B1:N8(可以是您需要的任何维度)以及您想要通过我调用的方式取消透视它们的列按矩阵 A1:A8。
作为一项规则,byMatrix 的行需要与 unPivMatrix 的行相同,因此您必须从 A1 而不是 A2 开始。我决定采取 这个约定是因为它们可能是 A1 中的标题并且带有一些 小型模组,这个公式可以产生完全相同的输出 Power Query,但这不是 Stefan 要求的。
公式首先计算每个矩阵中的列数:upC 反透视列,byC 列。从这些它计算 dmxR(多路分解行):通过取 unPivMatrix 和 byMatrix 的行中的最小值并减去 1,因为 unPivMatrix 有一个标题,将在 unpivot 中传递多少行值。如果输入具有不同的行数(根据定义这是一个错误),则采用 MIN 是一个错误预防步骤。
这些值用于创建稍后将用于形成输出的整形变量。 upCells 是未透视的值的数量,用于生成名为 upSeq 的索引模式,该模式从 0 开始计算将在 INDEX 内部使用的值的数量稍后发挥作用。我们从 0 开始,因为 upSeq 将被调制以形成输入和输出的正确索引。
现在我们将分解矩阵的各个部分,以便对它们进行多路复用。零件如下所示:
有一个 unpivot 标头 (upHdr),其中包含值数据的标识符(在 Stefan 的情况下,GROUP-Admin、GROUP-SuperUser 等)。这些将被多路复用到一个列中,该列稍后将放置在每个未透视的值旁边。 upHdr 是通过将整个 unPivMatrix 放入 INDEX 函数并读取第 1 行和所有列来创建的。我将该 INDEX 函数嵌套到另一个 INDEX 中,该 INDEX 使用大小为 upC 的垂直 SEQUENCE 将水平数组重塑为垂直数组。
upBody 包含我们想要通过多路复用来取消透视的值。它是通过将 upMatrix 放入一个 INDEX 并通过将输出 SEQUENCE( dmxR ) + 1 § 对每个要读取的列 SEQUENCE( upC ) 的行数对其进行整形来创建的。 byBody 包含将针对 upBody 中的每个值进行多路复用的数据。它的创建方式与 upBody 相同。
§ - 加 1 跳过标题行
这是如何工作的 - 塑造和编写输出
输出的形状如下:
现在,我们通过将 upHdr 放入 INDEX 并应用基于 upSeq 的调制序列(每 upC 次重复,例如 1;2;3;4;5;6;7;8;1;2;...
),将 upHdr 复用到 attr 或属性(使用 Power Query 术语) . 注意:这就是 upSeq 中从 0 开始的地方。 attr 的输出看起来像(在 Stefan 的情况下)GROUP-Admin; GROUP-SuperUser, etc.
。
mux 是将针对每个属性和 byBody 行多路复用的值(使用 Power Query 术语)。它是通过将 valBody 放入 INDEX 中,然后将其重塑为由行创建的多路复用模式
upSeq/upC + 1
产生一行1,1,1,1,1,1,1,1,2,2,...
和列
MOD( upSeq, upC )
+ 1 产生一列1;2;3;4;5;6;7;8;1;...
。
mux 的输出将是 unPivMatrix 的内容。在 Stefan 的情况下,这会有点特别,因为他使用 * 和空白作为数据。此公式会将空白转换为 0。因此,如果这是一个问题,您可以将 mux 包装到 IF( ISBLANK( mux ), "", mux )
中,但我没有添加它,因为我想要一个通用的 unpivot,因为我确信 Stefan 早就继续前进了。
这是如何工作的 - 将部分解复用到输出中
现在简单的部分已经完成,是时候进行困难的部分了 - 将所有这些整合到一个动态数组中。将多个数组放在一起需要一个技巧,并且这个技巧必须应用两次,因为如您所见,我们将三个表放在一起。诀窍就像有一个 APPEND 函数,如:
APPEND( APPEND( table1, table2 ), table3 )
.
要合并两个数组,请将第一个数组放入 INDEX 中,然后引用数组外部的单元格以强制 #REF!错误。例如,如果我有一个 3 x 2 的字母 A 到 F 数组,并且我引用单元格 3、3,它将引发引用错误。
现在,您可以通过将 INDEX 包装在 IFERROR 中,将错误替换为要附加的表来利用这些错误。有点像:
IFERROR( INDEX( table1,
SEQUENCE( table1.rows ),
SEQUENCE( 1, table1.columns + table2.columns ) ),
table2 )
从这个意义上说,上面的公式等价于 APPEND(table1, table2),其中 APPEND 是我们想要的两个表的逐行追加。 (注意:切换序列模式,您可以按列进行追加。)
因此,希望这个解释能够清楚地说明在一个名为 demux 的变量的最后阶段发生了什么,该变量提供了结果。我为结果命名,然后引用它,以便您可以轻松地探索、修改或优化公式。所以,demux 真的很像:
APPEND( byBody, APPEND( attr, mux ) )
我不会深入探讨最后阶段的工作原理,因为这已经是一个很长的答案,但简短的总结是,此附加使用由 upCells、upC 和 byC 创建的维度来形成输出。
我已经对此进行了测试,但我没有对其进行性能优化或使其达到#SwissEngineering 标准。
【讨论】:
答案是完美的,如果我能让它工作,它可以解决一个大问题。但无法在 Excel 2016 中使用。Excel 无法识别 LET 函数。有什么线索吗? 嘿@varun - 是的,事实上,在我发布这个之后,我发现 LET 和 SEQUENCE 只在 Excel 365 中 - 甚至不是 2019 年!两者都需要完成。虽然有使用 INDEX & ROW 的 SEQUENCE 的解决方法,但 LET 问题仍然存在。 LET 可以通过将公式折叠成不可读的混乱来消除,但真正的问题将是计算时间。 LET 允许您的公式“干燥”。此处发布的 PowerQuery 方法[***.com/questions/20541905/… by Telwyn 可以为您工作。 是否可以应用它来保留未透视数据中的多个列?例如,假设原始列实际上是二维的,月份跨越 4 列,每列在 4*12=48 列上重复。我需要一个包含 4 个值列的非数据透视表,或者一个包含 2 个属性列的非数据透视表 @Scott - 所以,要理解,你的意思是 upHdr 会是多列,逐行重复吗?例如这些列是每个月的压力、湿度、温度、二氧化碳。即 Jan Pressure、Jan Hum、... Feb Pressure、Feb Hum... 行可能代表城市、州。然后输出将有 4 个值 (P,h,T,C) 用于上面图像 2 中的 values 并且 attr 将具有月份。如果是这样,它就像一个 python-pandas 融化,是的,它可以在动态数组公式中解决。 @Scott - 它让我想起了这个 OP:***.com/questions/68109101/… 但列标题中会有多行,对吧?【参考方案3】:我正在使用这个 VBA 代码
Sub Unpivot()
'
Dim Rowlabel As Range
Dim Columnlabel As Range
Dim Pap As Range
Dim Tabl As Range
Dim i As Integer
Dim j As Integer
Dim a As Integer
Dim b As Integer
Dim Data As Range
Dim k As Integer
Dim Label As Range
Dim pvtCache As PivotCache
Dim pvt As PivotTable
Dim SrcData As String
'
ActiveSheet.Copy Before:=Worksheets(1)
Set Tabl = Selection
For Each Pap In Tabl
If Pap.MergeCells Then
With Pap.MergeArea
.UnMerge
.Value = Pap.Value
End With
End If
Next
i = Application.InputBox("Number of row contain label:", "Excel", i, Type:=2)
j = Application.InputBox("Number of column contain label:", "Excel", j, Type:=2)
On Error Resume Next
Sheets("Unpivot_Table").Delete
Sheets.Add.Name = "Unpivot_Table"
Set Pap = Range("Unpivot_Table!B2")
b = Tabl.Rows.Count
a = Tabl.Columns.Count
Set Data = Range(Tabl.Cells(i + 1, j + 1), Tabl.Cells(b, a))
Set Columnlabel = Range(Tabl.Cells(i + 1, 1), Tabl.Cells(b, j))
Set Rowlabel = Range(Tabl.Cells(1, j + 1), Tabl.Cells(i, a))
Pap.Select
For Each Column In Data.Columns
Column.Copy
Selection.PasteSpecial Paste:=xlPasteValues
Columnlabel.Copy
Selection.Offset(0, 1).PasteSpecial Paste:=xlPasteValues
Column.Copy
Selection.Offset(b - i, -1).Select
Next Column
Pap.Offset(0, j + 1).Select
For Each Column In Rowlabel.Columns
Column.Copy
Range(Selection, Selection.Offset(b - i - 1, 0)).PasteSpecial Paste:=xlPasteValues, Transpose:=True
Selection.End(xlDown).Offset(1, 0).Select
Next Column
Set Label = Range(Pap.Offset(-1, 0), Pap.Offset(0, i + j + 1))
For k = 1 To i + j + 1
Label.Cells(1, k).Value = Application.InputBox(Label.Cells(2, k).Value & " is belong to Fieldname", "Hoang", k, Type:=2)
Next
Range(Pap.End(xlUp), Pap.End(xlDown).End(xlToRight)).Select
SrcData = ActiveSheet.Name & "!" & Selection.Address
On Error Resume Next
Sheets("Pivot").Delete
Sheets.Add.Name = "Pivot"
Set pvtCache = ActiveWorkbook.PivotCaches.Create( _
SourceType:=xlDatabase, _
SourceData:=SrcData)
Set pvt = pvtCache.CreatePivotTable( _
TableDestination:="Pivot!" & Sheets("Pivot").Range("A3").Address(ReferenceStyle:=xlR1C1), _
TableName:="PivotTable1")
End Sub
【讨论】:
【参考方案4】:我相信您可以使用如下的模运算。使用 cols 和 rows 图例将您的数据放入此 UDF 的参数中。
Function MyUnpivot(matice As Range) As Variant
Dim I As Integer
Dim J As Integer
Dim radka As Integer
Dim sloupec As Integer
I = matice.Rows.Count - 1
J = matice.Columns.Count - 1
Dim returnVal()
ReDim Preserve returnVal(1 To I * J, 1 To 3)
For x = 1 To I * J
radka = ((x - 1) Mod I) + 2
sloupec = WorksheetFunction.Floor_Math((x - 1 / 2) / I) + 2
returnVal(x, 1) = matice.Cells(1, sloupec)
returnVal(x, 2) = matice.Cells(radka, 1)
returnVal(x, 3) = matice.Cells(radka, sloupec)
Next
MyUnpivot = returnVal
End Function
【讨论】:
【参考方案5】:Power Query 还有另一种方式:
选择您的单元格 菜单Data
> From a table or a range
在 Power Query 编辑器中,选择所有列保存第一个,然后 Transform
> Unpivot
表未透视。转到Home
> Close and load
您的未透视表在这里。如果您的原始表格已更新,请右键单击它并选择 Refresh
【讨论】:
以上是关于取消透视 Excel 矩阵/数据透视表?的主要内容,如果未能解决你的问题,请参考以下文章