如何在Google表格中创建"反向支点"?

Mar*_*son 14 pivot-table unpivot google-sheets google-apps-script

我试图产生一个"反向枢轴"功能.我已经长时间努力寻找这样的功能,但找不到已经存在的功能.

我有一个摘要表,其中包含最多20列和数百行,但我想将其转换为平面列表,以便我可以导入到数据库(甚至使用平面数据来创建更多数据透视表!)

所以,我有这种格式的数据:

          | Customer 1 | Customer 2 | Customer 3
----------+------------+------------+-----------
Product 1 |          1 |          2 |          3
Product 2 |          4 |          5 |          6
Product 3 |          7 |          8 |          9
Run Code Online (Sandbox Code Playgroud)

并需要将其转换为以下格式:

 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
Run Code Online (Sandbox Code Playgroud)

我已经创建了一个函数,它将读取范围,sheet1并在同一个工作表的底部附加重新格式化的行,但是我试图让它工作,所以我可以使用该函数sheet2来读取整个范围sheet1.

无论我尝试什么,我似乎无法让它工作,并想知道是否有人可以给我任何指针?

这是我到目前为止:

function readRows() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var rows = sheet.getDataRange();
  var numRows = rows.getNumRows();
  var values = rows.getValues();

  heads = values[0]

  for (var i = 1; i <= numRows - 1; i++) {
    for (var j = 1; j <= values[0].length - 1; j++) {
       var row = [values[i][0], values[0][j], values[i][j]];
       sheet.appendRow(row)
    }
  }
};
Run Code Online (Sandbox Code Playgroud)

Vik*_*amp 14

我写了一个简单的通用自定义函数,它是100%可重用的,你可以解开/反转任何大小的表.

在你的情况下你可以像这样使用它: =unpivot(A1:D4,1,1,"customer","sales")

因此,您可以像使用电子表格中的任何内置数组函数一样使用它.

请在此处查看2个示例:https: //docs.google.com/spreadsheets/d/12TBoX2UI_Yu2MA2ZN3p9f-cZsySE4et1slwpgjZbSzw/edit#gid=422214765

以下是来源:

/**
 * 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; i++)
  {
    // skip totally empty or only whitespace containing rows
    if (data[i].join('').replace(/\s+/g,'').length == 0 ) continue;

    // unpivot the row
    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;
}
Run Code Online (Sandbox Code Playgroud)


Ser*_*sas 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);
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

自动将结果写入新工作表中用以下代码替换最后一行代码:

  var ns = SpreadsheetApp.getActive().getSheets().length+1
  SpreadsheetApp.getActiveSpreadsheet().insertSheet('New Sheet'+ns,ns).getRange(1,1,output.length,output[0].length).setValues(output);
Run Code Online (Sandbox Code Playgroud)

  • 三年后,这仍然很好,谢谢! (3认同)

The*_*ter 10

更新:ES6-Array.flatMap在 V8 引擎上使用:

/**
 * Unpivots the given data
 *
 * @return Unpivoted data from array
 * @param {object[][]} arr 2D Input Array
 * @param {object[][]=} headers [optional] Custom headers for output
 * @customfunction
 */
function unpivotFast(arr, headers) {
  const custHeader = arr.shift();
  custHeader.shift();
  const out = arr.flatMap(([prod, ...qty]) =>
    qty.map((num, i) => [custHeader[i], prod, num])
  );
  if (headers) out.unshift(headers[0]);
  return out;
}
Run Code Online (Sandbox Code Playgroud)

用法:

=UNPIVOTFAST(A1:F4,{A1,"Month","Sales"})
Run Code Online (Sandbox Code Playgroud)

使用array.reducearray.splice- 简约方法进行数组操作:

/**
 * Unpivots the given data
 *
 * @deprecated
 * @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;
}
Run Code Online (Sandbox Code Playgroud)

用法:

=UNPIVOT(A1:F4,1,{A1,"Month","Sales"})//Outputs 1 static and 2 unpivoted columns from 1 static and 4+ pivoted columns 
Run Code Online (Sandbox Code Playgroud)


Max*_*rov 8

使用扁平化。它将任何数组转换为单列。

下面是 unpivot 的公式:

=ARRAYFORMULA(SPLIT(FLATTEN(A2:A12&""&B1:F1&""&B2:F12),""))

在此处输入图片说明

FLATTEN创建 1 列Item1Date167455字符串数组,然后我们将其拆分。

复制示例文件进行尝试。

更短:

=index(SPLIT(FLATTEN(A2:A12&""&B1:F1&""&B2:F12),""))

  • 看起来 FLATTEN 函数现在是正式的:)有一个支持页面,它出现在公式完成中:https://support.google.com/docs/answer/10307761 (3认同)
  • 它可以与任何表情符号一起使用吗? (2认同)

Tom*_*rpe 7

我不认为你有足够的数组公式答案,所以这是另一个。

测试数据(表 1)

在此处输入图片说明

客户配方

=ArrayFormula(hlookup(int((row(indirect("1:"&Tuples))-1)/Rows)+2,{COLUMN(Sheet1!$1:$1);Sheet1!$1:$1},2))
Run Code Online (Sandbox Code Playgroud)

(使用一些数学方法使其重复并使用 hlookup 在列标题中找到正确的列)

产品配方

=ArrayFormula(vlookup(mod(row(indirect("1:"&Tuples))-1,Rows)+2,{row(Sheet1!$A:$A),Sheet1!$A:$A},2))
Run Code Online (Sandbox Code Playgroud)

(类似的方法使用 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))
Run Code Online (Sandbox Code Playgroud)

(上述方法的扩展以在二维数组中查找行和列)

然后将这三个公式组合成一个查询,过滤掉数量的任何空白值

=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"))
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

笔记

命名范围 Rows 和 Cols 是使用 counta 从数据的第一列和第一行获得的,元组是它们的乘积。单独的公式

=counta(Sheet1!A:A)

=counta(Sheet1!1:1)
Run Code Online (Sandbox Code Playgroud)

=counta(Sheet1!A:A)*counta(Sheet1!1:1)
Run Code Online (Sandbox Code Playgroud)

如果需要,可以将其包含在主要公式中,但会损失一些可读性。


作为参考,这里是适用于当前情况的“标准”拆分/加入解决方案(具有 50K 数据限制):

=ArrayFormula(split(transpose(split(textjoin("?",true,transpose(if(Sheet1!B2:Z="","",Sheet1!B1:1&"?"&Sheet1!A2:A&"?"&Sheet1!B2:Z))),"?")),"?"))
Run Code Online (Sandbox Code Playgroud)

这也相当慢(处理 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))))),"?")),"?"))
Run Code Online (Sandbox Code Playgroud)