Как вы создаете "обратную ось" в 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)
}
}
};
Ответы
Ответ 1
Я написал простую обычную пользовательскую функцию, которая на 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 && 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;
}
Ответ 2
Это в основном манипуляция массивом... ниже - это код, который делает то, что вы хотите, и записывает результат ниже существующих данных.
Конечно, вы можете адаптировать его для записи на новом листе, если хотите.
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);
Ответ 3
Я не думаю, что у вас достаточно ответов на формулы массива, так что вот еще один.
Тестовые данные (лист 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))
(расширение вышеуказанного подхода для поиска строки и столбца в 2d массиве)
Затем объединение этих трех формул в запрос, чтобы отфильтровать любые пустые значения для количества
=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]()
Заметка
Именованные диапазоны Rows и Cols получаются из первого столбца, а строка данных с использованием счетчиков и Tuples является их произведением. Отдельные формулы
=counta(Sheet1!A:A)
=counta(Sheet1!1:1)
а также
=counta(Sheet1!A:A)*counta(Sheet1!1:1)
при необходимости может быть включен в основную формулу с некоторой потерей читабельности.
Для справки, вот "стандартное" решение для разделения/объединения (с ограничением данных в 50 КБ), адаптированное для текущей ситуации:
=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))))),"♫")),"♪"))
Ответ 4
Вот демонстрационный файл, который использует метод с использованием встроенных пользовательских функций и формул массива:
- Создайте новый лист и переименуйте его как "Aux"
- В листе Aux добавьте следующие формулы:
(это предполагает, что исходные данные находятся в листе с именем data)
A1: =COUNTA(data!A:A)
Рассчитать количество строк.
A2: =COUNTA(data!1:1)
Рассчитать количество столбцов.
A3: =CELL("address",data!A1)
Промежуточный шаг.
A4: =LEFT(A3,FIND("!",A3)-1)
Вычисляет имя листа с исходными данными.
- Создать новый лист
- Добавьте следующий новый лист
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)}
возвращает массив с первым столбцом, включая индекс строки, а следующие столбцы являются исходными данными.
Ответ 5
Если ваши данные имеют единственный уникальный столбец, эта таблица может иметь то, что вам нужно.
В листе вашего неволя будет содержаться:
- Ключевой столбец
=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
=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]()
Ответ 7
Манипулирование array.reduce
с использованием array.reduce
и array.splice
- минималистичный подход:
/**
* 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;
}
Использование:
=UNPIVOT(A1:F4,1,{A1,"Month","Sales"})//Outputs 1 static and 2 unpivoted columns from 1 static and 4+ pivoted columns
Ответ 8
Пожалуйста, попробуйте этот код:
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...'