Skip to content

Unfolding Column Groups - BareFrame.unfoldColumns

BareFrame.unfoldColumns expands repeated column groups into separate rows. Use it when a sheet stores multiple items per row as side-by-side column groups, and you need each item as its own row.

JavaScript
const unfolded = df.unfoldColumns({
  fixedHeaders: ["timestamp", "email"],
  groups: [
    ["person1_name", "person1_age"],
    ["person2_name", "person2_age"],
  ],
  groupHeaders: ["name", "age"],
});

Without gaslamp

Google Sheets has no built-in operation to unfold column groups into rows. You must write a nested loop manually, keeping track of which columns belong to each group:

JavaScript
const values = sheet.getDataRange().getValues();
const headers = values[0];
// headers: ["timestamp", "email", "person1_name", "person1_age", "person2_name", "person2_age"]

const rows = values.slice(1);
const result = [];

for (const row of rows) {
  // Group 1: person1_name(2), person1_age(3)
  result.push([row[0], row[1], row[2], row[3]]);
  // Group 2: person2_name(4), person2_age(5)
  result.push([row[0], row[1], row[4], row[5]]);
}

// result:
// ["2024-03-25", "a@example.com", "Alice", 30]
// ["2024-03-25", "a@example.com", "Bob",   25]

If the sheet layout changes (columns added, reordered, or groups extended), every index must be updated manually.


With gaslamp

unfoldColumns expresses the same transformation with column names and group declarations. The sheet layout can change freely as long as column names stay the same.

JavaScript
const df = gaslamp.BareFrame.fromSheet(sheet);

const unfolded = df.unfoldColumns({
  fixedHeaders: ["timestamp", "email"],
  groups: [
    ["person1_name", "person1_age"],
    ["person2_name", "person2_age"],
  ],
  groupHeaders: ["name", "age"],
});

console.log(unfolded.display());
// | timestamp  | email         | name  | age |
// | ---------- | ------------- | ----- | --- |
// | 2024-03-25 | a@example.com | Alice | 30  |
// | 2024-03-25 | a@example.com | Bob   | 25  |

Options

Option Type Description
fixedHeaders string[] Columns that are copied unchanged to every output row
groups string[][] Column groups to expand — one group becomes one output row
groupHeaders string[] New column names for the expanded values
strict boolean When true, throws on missing columns or size mismatches. Default: false

groups and groupHeaders must have the same length. Each inner array in groups must also have the same length as groupHeaders.


Strict mode

By default, missing columns in a group are filled with null. Pass strict: true to throw an error instead:

JavaScript
const unfolded = df.unfoldColumns({
  fixedHeaders: ["timestamp"],
  groups: [["missing_col"]],
  groupHeaders: ["name"],
  strict: true,
});
// Error: BareFrame.unfoldColumns: column "missing_col" in groups[0] does not exist

Practical Workflow

A survey response sheet stores answers from up to 3 respondents per row. Unfold each response into its own row, then write to a normalized output sheet.

JavaScript
function workflow_unfoldSurvey() {
  const book = SpreadsheetApp.getActiveSpreadsheet();
  const inputSheet  = book.getSheetByName("Survey");
  const outputSheet = book.getSheetByName("Responses")
    ?? book.insertSheet("Responses");

  const df = gaslamp.BareFrame.fromSheet(inputSheet);

  const responses = df.unfoldColumns({
    fixedHeaders: ["submitted_at", "team"],
    groups: [
      ["resp1_name", "resp1_score"],
      ["resp2_name", "resp2_score"],
      ["resp3_name", "resp3_score"],
    ],
    groupHeaders: ["name", "score"],
  });

  responses.toSheet(outputSheet);
  console.log(`Unfolded ${df.length} rows → ${responses.length} rows.`);
}

Next Steps