我正在尝试制作一个"反向枢轴"功能。我已经很长时间地搜索了这样一个函数,但是找不到那个已经存在的函数。
我有一个摘要表,其中包含最多20列和数百行,但我想将其转换为平面列表,以便我可以导入到数据库(甚至使用平面数据来创建更多的数据透视表!)
所以,我有这种格式的数据:
| Customer 1 | Customer 2 | Customer 3
----------+------------+------------+-----------
Product 1 | 1 | 2 | 3
Product 2 | 4 | 5 | 6
Product 3 | 7 | 8 | 9
需要将其转换为以下格式:
Customer | Product | Qty
-----------+-----------+----
Customer 1 | Product 1 | 1
Customer 1 | Product 2 | 4
Customer 1 | Product 3 | 7
Customer 2 | Product 1 | 2
Customer 2 | Product 2 | 5
Customer 2 | Product 3 | 8
Customer 3 | Product 1 | 3
Customer 3 | Product 2 | 6
Customer 3 | Product 3 | 9
我创建了一个函数,它将读取sheet1
的范围,并在同一个工作表的底部附加重新格式化的行,但是我试图让它工作,所以我可以在{ {1}}将从sheet2
读取整个范围。
无论我尝试什么,我似乎无法让它发挥作用,并且想知道是否有人可以给我任何指示?
这是我到目前为止所做的:
sheet1
答案 0 :(得分:12)
我写了一个简单的常规自定义函数,它是100%可重复使用的,您可以对任意大小的表进行忽略/反转。
在您的情况下,您可以像这样使用它:=unpivot(A1:D4,1,1,"customer","sales")
因此,您可以像使用电子表格中的任何内置数组函数一样使用它。
以下是来源:
/**
* Unpivot a pivot table of any size.
*
* @param {A1:D30} data The pivot table.
* @param {1} fixColumns Number of columns, after which pivoted values begin. Default 1.
* @param {1} fixRows Number of rows (1 or 2), after which pivoted values begin. Default 1.
* @param {"city"} titlePivot The title of horizontal pivot values. Default "column".
* @param {"distance"[,...]} titleValue The title of pivot table values. Default "value".
* @return The unpivoted table
* @customfunction
*/
function unpivot(data,fixColumns,fixRows,titlePivot,titleValue) {
var fixColumns = fixColumns || 1; // how many columns are fixed
var fixRows = fixRows || 1; // how many rows are fixed
var titlePivot = titlePivot || 'column';
var titleValue = titleValue || 'value';
var ret=[],i,j,row,uniqueCols=1;
// we handle only 2 dimension arrays
if (!Array.isArray(data) || data.length < fixRows || !Array.isArray(data[0]) || data[0].length < fixColumns)
throw new Error('no data');
// we handle max 2 fixed rows
if (fixRows > 2)
throw new Error('max 2 fixed rows are allowed');
// fill empty cells in the first row with value set last in previous columns (for 2 fixed rows)
var tmp = '';
for (j=0;j<data[0].length;j++)
if (data[0][j] != '')
tmp = data[0][j];
else
data[0][j] = tmp;
// for 2 fixed rows calculate unique column number
if (fixRows == 2)
{
uniqueCols = 0;
tmp = {};
for (j=fixColumns;j<data[1].length;j++)
if (typeof tmp[ data[1][j] ] == 'undefined')
{
tmp[ data[1][j] ] = 1;
uniqueCols++;
}
}
// return first row: fix column titles + pivoted values column title + values column title(s)
row = [];
for (j=0;j<fixColumns;j++) row.push(fixRows == 2 ? data[0][j]||data[1][j] : data[0][j]); // for 2 fixed rows we try to find the title in row 1 and row 2
for (j=3;j<arguments.length;j++) row.push(arguments[j]);
ret.push(row);
// processing rows (skipping the fixed columns, then dedicating a new row for each pivoted value)
for (i=fixRows;i<data.length && data[i].length > 0 && data[i][0];i++)
{
row = [];
for (j=0;j<fixColumns && j<data[i].length;j++)
row.push(data[i][j]);
for (j=fixColumns;j<data[i].length;j+=uniqueCols)
ret.push(
row.concat([data[0][j]]) // the first row title value
.concat(data[i].slice(j,j+uniqueCols)) // pivoted values
);
}
return ret;
}
答案 1 :(得分:10)
这基本上是数组操作......下面是一个执行你想要的代码并将结果写回现有数据下面。
如果您愿意,您当然可以根据自己的情况进行调整。
function transformData(){
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();//read whole sheet
var output = [];
var headers = data.shift();// get headers
var empty = headers.shift();//remove empty cell on the left
var products = [];
for(var d in data){
var p = data[d].shift();//get product names in first column of each row
products.push(p);//store
}
Logger.log('headers = '+headers);
Logger.log('products = '+products);
Logger.log('data only ='+data);
for(var h in headers){
for(var p in products){ // iterate with 2 loops (headers and products)
var row = [];
row.push(headers[h]);
row.push(products[p]);
row.push(data[p][h])
output.push(row);//collect data in separate rows in output array
}
}
Logger.log('output array = '+output);
sheet.getRange(sheet.getLastRow()+1,1,output.length,output[0].length).setValues(output);
}
自动将结果写入新工作表中,用以下代码替换最后一行代码:
var ns = SpreadsheetApp.getActive().getSheets().length+1
SpreadsheetApp.getActiveSpreadsheet().insertSheet('New Sheet'+ns,ns).getRange(1,1,output.length,output[0].length).setValues(output);
答案 2 :(得分:3)
我认为您没有足够的数组公式答案,所以这是另一个答案。
测试数据(第1页)
客户公式
=ArrayFormula(hlookup(int((row(indirect("1:"&Tuples))-1)/Rows)+2,{COLUMN(Sheet1!$1:$1);Sheet1!$1:$1},2))
(使用一些数学运算使其重复,并使用hlookup在列标题中查找正确的列)
产品公式
=ArrayFormula(vlookup(mod(row(indirect("1:"&Tuples))-1,Rows)+2,{row(Sheet1!$A:$A),Sheet1!$A:$A},2))
(使用mod和vlookup在行标题中查找正确行的类似方法)
数量公式
=ArrayFormula(vlookup(mod(row(indirect("1:"&Tuples))-1,Rows)+2,{row(Sheet1!$A:$A),Sheet1!$A:$Z},int((row(indirect("1:"&Tuples))-1)/Rows)+3))
(上述方法的扩展,可以找到二维数组中的行和列)
然后将这三个公式组合成一个查询,以过滤出数量的空白值
=ArrayFormula(query(
{hlookup(int((row(indirect("1:"&Tuples))-1)/Rows)+2, {COLUMN(Sheet1!$1:$1);Sheet1!$1:$1},2),
vlookup(mod(row(indirect("1:"&Tuples))-1,Rows)+2,{row(Sheet1!$A:$A),Sheet1!$A:$A},2),
vlookup(mod(row(indirect("1:"&Tuples))-1,Rows)+2,{row(Sheet1!$A:$A),Sheet1!$A:$Z},int((row(indirect("1:"&Tuples))-1)/Rows)+3)},
"select * where Col3 is not null"))
注意
使用counta从数据的第一列和行获得命名范围Rows和Cols,而Tuples是其乘积。单独的公式
=counta(Sheet1!A:A)
=counta(Sheet1!1:1)
和
=counta(Sheet1!A:A)*counta(Sheet1!1:1)
如果需要,可以将包含在主公式中。
作为参考,以下是适合当前情况的“标准”拆分/合并解决方案(数据上限为50K):
=ArrayFormula(split(transpose(split(textjoin("♫",true,transpose(if(Sheet1!B2:Z="","",Sheet1!B1:1&"♪"&Sheet1!A2:A&"♪"&Sheet1!B2:Z))),"♫")),"♪"))
这也相当慢(处理2401个数组元素)。如果将计算限制为数据的实际维度,则对于小型数据集而言,速度会更快:
=ArrayFormula(split(transpose(split(textjoin("♫",true,transpose(if(Sheet1!B2:index(Sheet1!B2:Z,counta(Sheet1!A:A),counta(Sheet1!1:1))="","",Sheet1!B1:index(Sheet1!B1:1,counta(Sheet1!1:1))&"♪"&Sheet1!A2:index(Sheet1!A2:A,counta(Sheet1!A:A))&"♪"&Sheet1!B2:index(Sheet1!B2:Z,counta(Sheet1!A:A),counta(Sheet1!1:1))))),"♫")),"♪"))
答案 3 :(得分:2)
输入表
此功能将处理许多客户和许多产品,并将多个客户/产品条目的数量相加并将其汇总到一个简单的表中。
代码:
function rPVT() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Sheet1');
var osh=ss.getSheetByName('Sheet2');
osh.clearContents();
var vA=sh.getDataRange().getValues();
var itoh={};
var pObj={};
vA[0].forEach(function(h,i){if(h){itoh[i]=h;}});
for(var i=1;i<vA.length;i++) {
for(var j=1;j<vA[i].length;j++) {
if(!pObj.hasOwnProperty(itoh[j])){pObj[itoh[j]]={};}
if(!pObj[itoh[j]].hasOwnProperty(vA[i][0])){pObj[itoh[j]][vA[i][0]]=vA[i][j];}else{pObj[itoh[j]][vA[i][0]]+=(vA[i][j]);}
}
}
var oA=[['Customer','Product','Quantity']];
Object.keys(pObj).forEach(function(ik){Object.keys(pObj[ik]).forEach(function(jk){oA.push([ik,jk,pObj[ik][jk]]);});});
osh.getRange(1,1,oA.length,oA[0].length).setValues(oA);
}
输出表:
以下函数读取Sheet2,它是上述函数的输出,并将其返回为原始格式。
function PVT() {
var ss=SpreadsheetApp.getActive();
var sh2=ss.getSheetByName('Sheet2');
var sh3=ss.getSheetByName('Sheet3');
sh3.clearContents();
var vA=sh2.getRange(2,1,sh2.getLastRow()-1,sh2.getLastColumn()).getValues();
pObj={};
vA.forEach(function(r,i){if(!pObj.hasOwnProperty(r[1])){pObj[r[1]]={};}if(!pObj[r[1]].hasOwnProperty(r[0])){pObj[r[1]][r[0]]=r[2];}else{pObj[r[1]][r[0]]+=r[2];}});
var oA=[];
var ikeys=Object.keys(pObj);
var jkeys=Object.keys(pObj[ikeys[0]]);
var hkeys=jkeys.slice();
hkeys.unshift('');
oA.push(hkeys);
ikeys.forEach(function(ik,i){var row=[];row.push(ik);jkeys.forEach(function(jk,j){row.push(pObj[ik][jk]);});oA.push(row);});
sh3.getRange(1,1,oA.length,oA[0].length).setValues(oA);
}
答案 4 :(得分:1)
Here is a demo file使用内置自定义函数和数组公式的方法:
=COUNTA(data!A:A)
计算行数
A2:=COUNTA(data!1:1)
计算列数
A3:=CELL("address",data!A1)
中级步骤
A4:=LEFT(A3,FIND("!",A3)-1)
使用源数据计算工作表的名称。 =ArrayFormula( VLOOKUP( MOD(ROW(INDIRECT("A1:A"&Aux!A1*Aux!A2))-1,Aux!A1)+1+1, {(ROW(INDIRECT("A1:A"&Aux!A1+1))),INDIRECT(Aux!A4&"!R1C1:R"&Aux!A1+1&"C"&Aux!A2+1,false)}, 2 ) )
B1:列标题
B2:
=ArrayFormula( VLOOKUP( SIGN(ROW(INDIRECT("A1:A"&Aux!A1*Aux!A2))), {(ROW(INDIRECT("A1:A"&Aux!A1+1))),INDIRECT(Aux!A4&"!R1C1:R"&Aux!A1+1&"C"&Aux!A2+1,false)}, MOD(ROW(INDIRECT("A1:A"&Aux!A1*Aux!A2))-1,Aux!A2)+1+2 ) )
C1:值
C2:
=ArrayFormula( VLOOKUP( MOD(ROW(INDIRECT("A1:A"&Aux!A1*Aux!A2))-1,Aux!A1)+1+1, {(ROW(INDIRECT("A1:A"&Aux!A1+1))),INDIRECT(Aux!A4&"!R1C1:R"&Aux!A1+1&"C"&Aux!A2+1,false)}, MOD(ROW(INDIRECT("A1:A"&Aux!A1*Aux!A2))-1,Aux!A2)+1+2 ) )
主要构造的描述
ROW(INDIRECT("A1:A"&Aux!A1*Aux!A2)
返回一个连续数字数组,其高度与所需的最终结果相同。 {(ROW(INDIRECT("A1:A"&Aux!A1+1))),INDIRECT(Aux!A4&"!R1C1:R"&Aux!A1+1&"C"&Aux!A2+1,false)}
返回一个数组,第一列包含行索引,下一列是源数据。答案 5 :(得分:1)
如果您的数据只有一个唯一键列,那么this spreadsheet就可以满足您的需求。
您的unpivot表格将包含:
=OFFSET(data!$A$1,INT((ROW()-2)/5)+1,0)
=OFFSET(data!$A$1,0,IF(MOD(ROW()-1,5)=0,5,MOD(ROW()-1,5)))
=INDEX(data!$A$1:$F$100,MATCH(A2,data!$A$1:$A$100,FALSE),MATCH(B2,data!$A$1:$F$1,FALSE))
其中5
是要取消忽略的列数。
我没有制作电子表格。我在相同的搜索中碰巧遇到了这个问题。
答案 6 :(得分:1)
=ARRAYFORMULA({"Customer", "Product", "Qty";
QUERY(TRIM(SPLIT(TRANSPOSE(SPLIT(TRANSPOSE(QUERY(TRANSPOSE(QUERY(TRANSPOSE(
IF(B2:Z<>"", B1:1&"♠"&A2:A&"♠"&B2:Z&"♦", )), , 999^99)), , 999^99)), "♦")), "♠")),
"where Col1<>'' order by Col1")})
答案 7 :(得分:1)
使用array.reduce
和array.splice
进行数组操作-简约方法:
Export-*
用法:
/**
* Unpivots the given data
*
* @return Unpivoted data from array
* @param {A1:F4} arr 2D Input Array
* @param {3} numCol Number of static columns on the left
* @param {A1:C1} headers [optional] Custom headers for output
* @customfunction
*/
function unpivot(arr, numCol, headers) {
var out = arr.reduce(function(acc, row) {
var left = row.splice(0, numCol); //static columns on left
row.forEach(function(col, i) {
acc.push(left.concat([acc[0][i + numCol], col])); //concat left and unpivoted right and push as new array to accumulator
});
return acc;
}, arr.splice(0, 1));//headers in arr as initial value
headers ? out.splice(0, 1, headers[0]) : null; //use custom headers, if present.
return out;
}
答案 8 :(得分:1)
这里是另一种选择:
=arrayformula
(
{ "PRODUCT","CUSTOMER","QTY";
split
( transpose ( split
( textjoin("✫" ,false,filter(Sheet2!A2:A,Sheet2!A2:A<>"") & "✤" &
filter(Sheet2!B1:1,Sheet2!B1:1<>""))
,"✫",true,false)),"✤",true,false
),
transpose ( split ( textjoin ( "✤", false, transpose ( filter
(
indirect( "Sheet2!B2:" & MID(address(1,COUNTA( Sheet2!B1:1)+1), 2,
FIND("$",address(1,COUNTA( Sheet2!B1:1)+1),2)-2)
)
, Sheet2!A2:A<>""
))),"✤",true,false)
)
}
)
说明:
1. "PRODUCT","CUSTOMER","QTY"
-- Use for giving title
2. split
( transpose ( split
( textjoin("✫" ,false,filter(Sheet2!A2:A,Sheet2!A2:A<>"") & "✤" &
filter(Sheet2!B1:1,Sheet2!B1:1<>""))
,"✫",true,false)),"✤",true,false
)
-- Use for distributing Row1 and ColumnA, to be Product and Customer Columns
3. transpose ( split ( textjoin ( "✤", false, transpose ( filter
(
indirect( "Sheet2!B2:" & MID(address(1,COUNTA( Sheet2!B1:1)+1), 2,
FIND("$",address(1,COUNTA( Sheet2!B1:1)+1),2)-2)
)
, Sheet2!A2:A<>""
))),"✤",true,false)
)
--use to distributed data qty to Qty Column
Sheet2 Pict:
结果表Pict:
答案 9 :(得分:1)
有一个未记录的函数FLATTEN。它将任何数组转换为单列。
以下是不可透视的公式:
=ARRAYFORMULA(SPLIT(FLATTEN(A2:A12&"//"&B1:F1&"//"&B2:F12),"//"))
FLATTEN
创建一个由Item1//Date1//67455
个字符串组成的1列数组,然后将其拆分。