web-dev-qa-db-ja.com

Googleスプレッドシートで「リバースピボット」をどのように作成しますか?

「リバースピボット」機能を作成しようとしています。私はそのような機能を長く懸命に探してきましたが、すでにそこにあるものを見つけることができません。

最大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から範囲を読み取り、同じシートの下部に再フォーマットされた行を追加する関数を作成しましたが、範囲全体を読み取る関数を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)
    }
  }
};
21
Marc Henson

単純な一般的なカスタム関数を作成しました。これは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;
}
22
Viktor Tabori

それは基本的に配列操作です...以下はあなたが望むことをし、既存のデータの下に結果を書き戻すコードです。

もちろん、必要に応じて、新しいシートに書き込むように調整することもできます。

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);
}

enter image description here

結果を新しいシートに自動的に書き込むには、コードの最後の行を次のように置き換えます。

  var ns = SpreadsheetApp.getActive().getSheets().length+1
  SpreadsheetApp.getActiveSpreadsheet().insertSheet('New Sheet'+ns,ns).getRange(1,1,output.length,output[0].length).setValues(output);
10
Serge insas

配列数式の答えが十分だとは思わなかったので、別の答えを示します。

テストデータ(シート1)

enter image description here

顧客の公式

=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))

(2次元配列で行と列の両方を見つけるための上記のアプローチの拡張)

次に、これら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"))

enter image description here

名前付き範囲の行と列は、countaを使用してデータの最初の列と行から取得され、タプルはそれらの積です。個別の式

=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))))),"♫")),"♪"))
5
Tom Sharpe

更新:V8エンジンで ES6-Array.flatMap を使用:

/**
 * 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;
}

使用法:

=UNPIVOTFAST(A1:F4,{A1,"Month","Sales"})

array.reduce および array.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;
}

使用法:

=UNPIVOT(A1:F4,1,{A1,"Month","Sales"})//Outputs 1 static and 2 unpivoted columns from 1 static and 4+ pivoted columns 
3
TheMaster

ここにデモファイルがあります 組み込みのカスタム関数と配列数式を使用するメソッドを使用します:

  1. 新しいシートを作成し、名前を「Aux」に変更します
  2. Auxシートに次の式を追加します:(これは、ソースデータがdataという名前のシートにあることを前提としています)
    A1:=COUNTA(data!A:A)行数を計算します。
    A2:=COUNTA(data!1:1)列の数を計算します。
    A3:=CELL("address",data!A1)中間ステップ。
    A4:=LEFT(A3,FIND("!",A3)-1)ソースデータを含むシートの名前を計算します。
  3. 新しいシートを作成する
  4. 次の新しいシートを追加します
    A1:行ヘッダー
    A2:
 = 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)}は、行インデックスを含む最初の列を持つ配列を返し、次の列はソースデータです。
2
Rubén

入力シート

enter image description here

この関数は、多くの顧客と多くの製品を処理し、複数の顧客/製品エントリの数量を合計して、1つの単純なテーブルに要約します。

コード:

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);
}

出力シート:

enter image description here

次の関数は、上記の関数の出力である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);
}
2
Cooper

ここに別の選択肢があります:

=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:

enter image description here

結果シートの写真:

enter image description here

1
user11982798

データに単一の一意のキー列がある場合、 このスプレッドシート に必要なものがある可能性があります。

ピボット解除シートには次のものが含まれます。

  • キー列=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は、ピボットを解除する列の数です。


スプレッドシートは作成しませんでした。私はこの質問に私を導いた同じ検索でそれを偶然見つけました。

=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")})

0

1
player0

このコードを試してください:

https://github.com/Max-Makhrov/GoogleSheets/blob/master/UnpivotTable.js

それはあなたがピボットしているものにより多くの力を与えます

入力サンプル:

  input:

    * file            =     file object, 
                            default: ActiveSpreadsheet
    * strHead1        =     "'Wide_Table_Sample2'!A2:B2";
    * strHead2Start   =     "'Wide_Table_Sample2'!C1:C2";
    * strLabels       =     'PlanFact,Metric,Volume'; 
                            default: 'Label1,Label2,Label3...'
1
Max Makhrov