Google Apps Script, Google Sheets, SpreadsheetApp, 2d arrays
In Part 3 of our 2D array data transformation course in Google Apps Script, we worked out how to get the count of each choice of each question item from the survey results in a Google Sheet.
This time we are going to add a final element to our mix. Let’s say we have multiple questions and multiple groups. We want to find out the count for each choice for each question for each group.
Table of Contents
Goal: Count the frequency of each rating choice of each question item for each group.
That really is a mouthful.
We are going to use our same set of survey questions that asks goat companions to rate their goat on 3 question items. We are now using the same 3 sets of questions for a Goat 101 course that has 4 separate sections identified by:
- 101-01
- 101-02
- 101-03
- 101-04
So now we will want to get the count of each choice for each question for each group.
This time around, our survey also requests the user’s group before returning the user’s choice selection for each question. Take a look at the raw data below:
You can grab the Google Sheet data to follow along with your own script below:
We will be using much of our old code in Part 3 to show you how we can recycle and expand on our code for other projects.
We will be using our two example count data displays. Here, you will start to see some of the benefits of the horizontal approach emerge over the vertical approach to displaying the data.
Take a look at the end result of each of the count data displayed by group.
Item question header, subheader choices, choice count with groups by rows
Now you can really see the usefulness of having a horizontal structure when displaying count results by group. Each group result count is stacked under the other with the section identified on the far left column.
This format makes it easy to see at a glance, the different count of each choice for each question and compare them group to group. It wouldn’t take much here to add a sum or average row to the bottom of this data in Google Apps Script.
Choice header, Column A – Question, Block bundled by group
Here we are still using our vertical question approach in our first column, but we are bundling them into blocks by section. This is still quite aesthetically pleasing, but it does make it more difficult to compare results in each group. Particularly when more groups are added.
Recycle and Refit
The good news is that we can build on much of the code we have written in Part 3. For the vertical count display of questions all we really need to add is a group management function and then use our old code to create the count vertically for each group.
For our horizontal count display we will need to do some reshuffling so that we can create all the header details for all the groups before we get the count data for each group. This will be a good opportunity for you to see how we review our code and reapply it for future tasks.
We will also be adding in a sorting function to sort our data by groups. However, apart from that, we won’t have to make too many changes.
You can grab the old code from Part 3 of you can copy the old code below and paste it into your project.
Part 3 files
Code.gs – Old
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
/* * Displaying a count of each question item's choices a survey. * * */ /*##### Display the count of each choice for each item (question) ##### * This is the main run function. */ function runsies(){ //### Add your choices here var QUESTION_RANGE = "B2:D2"; var RESULT_RANGE = "B3:D26"; var CHOICES = ["1.Weak","2.Below Average","3.Average","4.Better Than Average","5.Strong"]; var DISPLAY_LOCATION_START = "G3"; var SOURCE_SHEET_NAME = "MultiQuestion 3"; var DESTINATION_SHEET_NAME = "MultiQuestion 3"; //######################### var ss = SpreadsheetApp.getActiveSpreadsheet(); var sourceSheet = ss.getSheetByName(SOURCE_SHEET_NAME); var resultsVals = sourceSheet.getRange(RESULT_RANGE).getValues(); var questionVals = sourceSheet.getRange(QUESTION_RANGE).getValues(); //Loop through each Item //Count for each choice in each Item var displayResults = singleGroup_MultiQuestionHor_Rating(questionVals,resultsVals,CHOICES); //var displayResults = singleGroup_MultiQuestionVert_Ratin(questionVals,resultsVals,CHOICES); var destinationSheet = ss.getSheetByName(DESTINATION_SHEET_NAME); pasteResults(DISPLAY_LOCATION_START,displayResults,destinationSheet); }; |
Trans.gs – Old
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
//####################### HORIZONTAL SPREAD ############################ //##################################################################### /*##################################################################### * Get count for each choice of each question. * * Creates a vertical choice spread of data from multiple questions. * * @param {array} questions : 2d array of question items. * @param {array} results : 2d array of raw results. * @param {array} choices : array of choice options. * * @returns {array} : 2d array of count data for each item choice. */ /* /* From: [ [Question A, Question B, Question C], [response A, response B, response C], [response A, response B, response C], [response A, response B, response C] ] To: Q = Question Item R = Response Type Rc = Response count [ [Q A, , , ,Q B, , , Q C, , , , ], [R1 A, R2 A, ... , R# A,R1 B, R2 B, ... , R# C,R1 C, R2 C, ... , R# C], [Rc1A, Rc2A, ... , Rc#B,Rc1B, Rc2B, ... , Rc#C,Rc1C, Rc2C, ... , Rc#C] ] */ function singleGroup_MultiQuestionHor_Rating(question,results,choices){ var numOfQuestions = results[0].length; var header = []; var subheader = []; var itemsCount = []; //Paste Items horizontally after each iteration. for(var q = 0; q < numOfQuestions; q++){ //prep header row var emptyArray = newArrayFill(choices.length-1,""); var questionAndSpaces = [question[0][q]].concat(emptyArray); header = header.concat(questionAndSpaces); //prep subheader row subheader = subheader.concat(choices); //prep count row var questionResults =results.map(function(row){ return row[q]; }); var countRes = itemCount(choices,questionResults); itemsCount = itemsCount.concat(countRes); }; return [header, subheader, itemsCount]; }; //####################### VERTICAL SPREAD ############################ //##################################################################### /*##################################################################### * Get count for each choice of each question. * * Creates a vertical choice spread of data from multiple questions. * * @param {array} questions : 2d array of question items. * @param {array} results : 2d array of raw results. * @param {array} choices : array of choice options. * * @returns {array} : 2d array of count data for each item choice. */ /* /* From: [ [Item A , Item B , Item C ], [response A, response B, response C], [response A, response B, response C], [response A, response B, response C] ] To [ [" ", Choice type 1 , Choice type 2 , ... , Choice type # ], [Item A, Response count1, Response count2, ... , Response count#], [Item B, Response count1, Response count2, ... , Response count#], [Item C, Response count1, Response count2, ... , Response count#] ] */ function singleGroup_MultiQuestionVert_Rating(question,results,choices){ var numOfQuestions = results[0].length; var header = [[""].concat(choices)]; //2d array var itemRows = []; for(var q = 0; q < numOfQuestions; q++){ var itemQuestion = [question[0][q]]; //prep count row var questionResults =results.map(function(row){ return row[q]; }); var countRes = itemCount(choices,questionResults); //Add item question to count results itemRows.push(itemQuestion.concat(countRes)); }; return header.concat(itemRows); } //##################################################################### /*##################################################################### * Get count for each choice of each question. * * gets the Item rating choice count for a single question Item. * * @param {array} results : 2d array of raw results. * @param {array} choices : array of choice options. * * @returns {array} : 2d array of count data for each choice in a single question item. */ /* From: [ [response A], [response A], [response A] ] To [ [Response count1, Response count2, ... , Response count#] ] */ function itemCount(choices, results) { var countResults = newArrayFill(choices.length,0); // Loop through list of results for(var res = 0; res < results.length; res++){ // If a response item already exists add 1 to corresponding column. for(var resp = 0; resp < choices.length; resp ++){ var choiceItemNumber = choices[resp][0]; if(results[res][0][0] === choiceItemNumber){ //Add to the count ++countResults[resp]; }; }; }; return(countResults); }; |
TOOLBOX.gs – Old
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
/*##################################################################### * Sort 2d Array by selected column. * * @param {array} results: 2d Array of range data. * @param {string} col: Selected column number to sort by. * * @param {returns} A sorted 2d array of data. */ function sortByColumn(results, col) { results.sort(function(a, b) { if (a[col] === b[col]) { return 0; } else { return (a[col] < b[col]) ? -1 : 1; } }); return results }; /*##################################################################### * Pasting results * * @param {string} StartLoc : The start cell of the location you want to paste to. * @param {array} results : 2d array of question results. * @param {object} sheet : The Sheet item. * */ function pasteResults(StartLoc, results, sheet){ var endLoc = sheet.getRange(StartLoc) .offset(results.length-1,results[0].length-1) .getA1Notation(); var range = sheet.getRange( StartLoc+":"+endLoc); range.setValues(results); }; /*##################################################################### * Creates a new array with selecte fill item. * * @param {number} len : Length of array. * @param {number|string} fillItem : Number or string to fill the array item. * @returns {array} The array the required length with required fill. * */ function newArrayFill(len, fillItem){ var arizzle = []; for(var i = 0; i < len; i++){arizzle[i] = fillItem}; return arizzle; }; |
The Code
Code.gs
Runsies()
The only thing we need to add to our Runsies()
is data related to groups. Before we can continue with running our count we need to sort our data by groups and grab each groups start and end row.
Global Variables
On our Google Sheet, we will need to select the range of all our data. We will need our RESULTS_RANGE
to include the group column this time around. This will be from column B through to column E in our example (Line 3).
We will also need to add a sort column. This will be referenced later when we use a helper function to sort the data set by column (Line 5). The sort column (SORT_COL
) refers to the column of group data once it has been collected in Google Apps Script and NOT the column in Google Sheets.
You will also need to update your source sheet name and destination sheet name (Lines 9 and 10).
The highlighted areas are what we have updated or changed:
1 2 3 4 5 6 7 8 9 10 11 |
//### Add your choices here var QUESTION_RANGE = "C2:E2"; var RESULT_RANGE = "B3:E121"; var CHOICES = ["1.Weak","2.Below Average","3.Average","4.Better Than Average","5.Strong"]; var SORT_COL = 0; //Group column of selected range. var DISPLAY_LOCATION_START = "G3"; var SOURCE_SHEET_NAME = "MultiQMultiGroup 4"; var DESTINATION_SHEET_NAME = "MultiQMultiGroup 4"; //######################### |
Sorting by Groups
Underneath the resultsVals
and questionVals
data we will sort the dataset by group and store it in the results
variable.
var results = sortByColumn(resultsVals, SORT_COL);
The sortByColumn
function is a custom function we will create in the ToolBox.gs file. It essentially sorts a 2d array by a column. It takes the dataset as the first argument and the sort column as the second argument. It returns the same set of data, only sorted by group.
Group row start and end
We want to be able to grab one group’s worth of choice data at a time to get the count data for each item in each question. To do this, we will need to find out the group name, the start row for that group and the end row so that we can isolate that set of data later.
var groupData = getGroupData(results,SORT_COL);
This getGroupData
function takes the sorted results dataset and the sort column as arguments. It returns a 2d array:
1 2 3 4 5 6 |
[ [group 1, start row, end row], [group 2, start row, end row], [group #, start row, end row], [... , ... , ... ] ] |
This function is fairly unique to this project, so I will probably just keep it in the Code.gs file, but it would be just as logical to put it in the ToolBox.gs file.
We will go into details on this one later.
Updating Display Results
In parts 1 to 3 our displayResults
variable called a function that dealt directly with getting the count values for each choice of each question. We still want to do this but we should probably put an intermediary function in to deal with collating all the group’s data. This intermediary function will then call the functions that will create the count values.
1 2 3 |
//Count for each choice in each Item var displayResults = displayItemChoiceCountByGroup_Vert(questionVals,resultsVals,CHOICES, groupData); //var displayResults = displayItemChoiceCountByGroup_Hor(questionVals,resultsVals,CHOICES, groupData); |
Note: The displayItemChoiceCountByGroup_Hor function is currently commented out. Remember you can switch between these two to create your two different types of count data transformations just remember to update the global variables.
Both the functions for the vertical and horizontal display of count data now take an extra argument at the end so that we can apply our groupData
start and end ranges.
Both of these functions are stored in the Trans.gs file.
Here is what runsies()
will look like now:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
/* * Displaying a count of each item in the survey by selection and by group. * * */ /*##### Display the frequency of each choice of multiple question's selections and group ##### * This is the main run function. */ function runsies(){ //### Add your choices here var QUESTION_RANGE = "C2:E2"; var RESULT_RANGE = "B3:E121"; var CHOICES = ["1.Weak","2.Below Average","3.Average","4.Better Than Average","5.Strong"]; var SORT_COL = 0; var DISPLAY_LOCATION_START = "G3"; var SOURCE_SHEET_NAME = "MultiQMultiGroup 4"; var DESTINATION_SHEET_NAME = "MultiQMultiGroup 4"; //######################### var ss = SpreadsheetApp.getActiveSpreadsheet(); var sourceSheet = ss.getSheetByName(SOURCE_SHEET_NAME); var resultsVals = sourceSheet.getRange(RESULT_RANGE).getValues(); var questionVals = sourceSheet.getRange(QUESTION_RANGE).getValues(); //Sort the data by groups. var results = sortByColumn(resultsVals, SORT_COL); //Get list of groups, start row and length var groupData = getGroupData(results,SORT_COL); //Loop through each Item //Count for each choice in each Item var displayResults = displayItemChoiceCountByGroup_Vert(questionVals,resultsVals,CHOICES, groupData); //var displayResults = displayItemChoiceCountByGroup_Hor(questionVals,resultsVals,CHOICES, groupData); var destinationSheet = ss.getSheetByName(DESTINATION_SHEET_NAME); pasteResults(DISPLAY_LOCATION_START,displayResults,destinationSheet); }; |
getGroupData()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/*##################################################################### * Get group data * * Creates a 2d array for data containing each groups name, start col and end col * * @param {array} results : The 2d array of question results by group. * @param {number} col : The column with the group in it. * * @returns {array} 2d array containing question, choices and count for each choice. */ function getGroupData(results, col){ var data = [], dataCount = 0, prev; for(var i = 0; i < results.length; i++){ if(results[i][col] !== prev){ data.push([results[i][col],i]) dataCount++; }else{ data[dataCount-1][2] = i; } prev = results[i][col]; } return data; }; |
The getGroupData()
function has 2 parameters:
- results – the data array that was previously sorted.
- col – the group column of the data. In this case, col zero.
This function is called from the main runsies()
function. The function will return a 2d array containing the group name, start column and end column for each group.
On line 12, we set a few variables. The data
variable will be our array of groups and their start and end points.
We will then assign a helper counter variable that we will call dataCount
and assign it a zero. This counter variable is the place marker for the new data array. Let’s say we have found the start row of one group. The data for that group will be added to the correct location in the array and the dataCount
will increase by one. Now when we find the start of the next group, that data will be placed in the array at the location of the new dataCount
.
We will want to check the previous row value against the current value. If there is a change, then we will know the start and end point of each section. We will use the prev
variable to store this value during each iteration over the rows.
Line 14, commences our loop through each row of our results data.
The first thing we want to ask is: if the results value of the currently iterated row of the group column don’t match the prev
-ious row’s group, then we have found a new group (Line 15). We then store the group name and the row count in our data array. We then add one to our DataCount
and start looking at the next row.
Alternatively, if the previous row’s group name matches the current row’s group name we will update the end row of the previous DataCount
‘s array.
Finally, just before the end of each iteration, we will update the prev
-ious variable with the current group so it can try and match it against the next row’s group once the loop starts again.
Trans.gs
As we mentioned previously, we will add an intermediary function for each style of our count (vertical or horizontal) results to deal with putting together all the group results. This function will also prepare any necessary headers. It will also loop through each section sending the currently iterated section’s data to the count accumulator function. The accumulated count results for that group will then be returned back to the intermediary function to be combined into a single 2d array of count results.
Let’s get started with the vertical display of count data.
displayItemChoiceCountByGroup_Vert( questions, data, choices, groupData)
You can pop the code block out as a separate window and follow along with the description.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
//####################### VERTICAL SPREAD ############################ //##################################################################### /*##################################################################### * Get Item Question Count by Row and by group - Vertical Spread * * Creates a vertical choice spread of data from multiple questions and multiple groups. * * @param {array} questions : 2d array of question items. * @param {array} data : 2d array of question item choice results. * @param {array} choices : array of choice options. * @param {array} groupData : 2d array of each group name, start row and end row. * * @returns {array} : 2d array of count data for each item choice for each group. */ /* From: [ [Group, Item A , Item B , Item C ], [ 1 , response A, response B, response C], [ 2 , response A, response B, response C], [ 3 , response A, response B, response C], [ 1 , response A, response B, response C], [ 2 , response A, response B, response C], [ 3 , response A, response B, response C], ] To [ [Group1, , , , ], [" ", Choice type 1 , Choice type 2 , ... , Choice type # ], [Item A, Response count1, Response count2, ... , Response count#], [Item B, Response count1, Response count2, ... , Response count#], [Item C, Response count1, Response count2, ... , Response count#], [ , , , , ], [Group2, , , , ], [" ", Choice type 1 , Choice type 2 , ... , Choice type # ], [Item A, Response count1, Response count2, ... , Response count#], [Item B, Response count1, Response count2, ... , Response count#], [Item C, Response count1, Response count2, ... , Response count#], ] */ function displayItemChoiceCountByGroup_Vert(questions, data, choices, groupData){ var results = []; //Space data var emptyArray = [newArrayFill(choices.length+1, "")]; //Get array of just the Item (Question) choices var choiceData = data.map(function(row){ row.shift() return row; }); //Loop through each group for(var group = 0; group < groupData.length; group++){ var groupHeader = [[groupData[group][0]].concat(newArrayFill(choices.length, ""))]; //Get the ChoiceData by group. var byGroup = choiceData.slice(groupData[group][1],groupData[group][2]); //Transform the data into the count array var transData = singleGroup_MultiQuestionVert_Rating(questions,byGroup,choices); results = results.concat(groupHeader,transData,emptyArray); }; return results; }; |
Parameters
The displayItemChoiceCountByGroup_Vert()
function is called from the runsies()
function. It takes 4 parameters:
- questions – the list of question items at the top of the survey results.
- data – the choice results data for each question for all groups.
- choices – the choice items. E.g. 1. Weak, 2. Below Average etc.
- groupData – the 2d array containing information on each group’s start and end location in the data array.
Empty Spacer Row
First off, we want to create an array to store all the count data for each group. We’ll creatively call this results
(Line 48).
Next, we want to be able to separate each set of group data by an empty row to make things easier to read. We do this with our trusty newArrayFill()
helper function that we created back in part 2 of this course. Our column length will be the number of choices plus the group column at the start. We will pack this row inside another array (Line 50):
var emptyArray = [newArrayFill(choices.length+1, "")];
Just the choice data
We then want to prepare our data so that we only have our choice data column and not the sections. We remove the sections column using map and shift.
The map method creates a new array by iterating through the old array and modifying it in some way. In our case, we want to remove the zeroeth value of each row and then return the rest of the choice data. We can easily remove the zeroeth item in an array with the shift method.
1 2 3 4 5 |
//Get array of just the Item (Question) choices var choiceData = data.map(function(row){ row.shift() return row; }); |
Loop through the groups
In our next stage, we will loop through all the groups using our groupData
variable. Remember this variable contains a 2d array with a Group name column, group start column and group end column.
Our first task is to create the header. This will contain the group name in the first column and a bunch of empty cells equal to the length of choice items.
1 2 3 4 5 |
var groupHeader = [ [groupData[group][0]].concat( newArrayFill(choices.length, "")) ]; |
Next, we want to just take all the choice data for the current group only. We will add this into a new variable called byGroup
. To get just the current group data we will use the slice method. The slice method makes a copy (Albeit shallow – but that is fine because we are not changing the values) of a selected range of an array.
We will get the group’s start and end rows by referencing the current groupData
array information.
var byGroup = choiceData.slice(groupData[group][1],groupData[group][2]);
Now, we get to run that group data into our old singleGroup_SingleQuestionVert_Rating()
function we built in Part 2. This function takes our list of questions as our first argument, our newly created byGroup
variable of choice results, and our list of choices.
It will return all the count data for each choice for each question and store it in our transData
variable.
var transData = singleGroup_SingleQuestionVert_Rating2(questions,byGroup,choices);
Lastly, we will want to combine our header for each group, the group count data and a spacing row and store it in our results variable.
results = results.concat(groupHeader,transData,emptyArray);
Finally
Finally, we return the accumulated results of all the group back to the runsies()
function.
singleGroup_MultiQuestionVert_Rating()
Guess what? You don’t have to change anything about this function. It will perfectly fine just as is from what you created in Part 3. What a time saver. Ya big legend!
Don’t forget this function also makes use of the itemsCount()
function we created earlier in the course.
Well, our vertical count data display was easy. Let’s take a look at the one.
displayItemChoiceCountByGroup_Hor(questions, data, choices, groupData)
In this intermediary function to display the count results horizontally, we will need to make some changes and take some of the roles that the singleGroup_MultiQuestionHor_Rating function()
was doing back in Part 3. We don’t need a header or subheader before every group, so we will remove it and bring that up into this intermediary function, displayItemChoiceCountByGroup_Hor()
.
Let’s take a look at the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
//####################### HORZONTAL SPREAD ############################ //##################################################################### /*##################################################################### * Get Item Question Count by Row and by group - Horizontal Spread * * Creates a horizontal choice spread of data from multiple questions and multiple groups. * * @param {array} questions : 2d array of question items. * @param {array} data : 2d array of question item choice results. * @param {array} choices : array of choice options. * @param {array} groupData : 2d array of each group name, start row and end row. * * @returns {array} : 2d array of count data for each item choice for each group. */ /* From: [ [Group, Item A , Item B , Item C ], [ 1 , response A, response B, response C], [ 2 , response A, response B, response C], [ 3 , response A, response B, response C], [ 1 , response A, response B, response C], [ 2 , response A, response B, response C], [ 3 , response A, response B, response C], ] To [ [" ",Q A, , , ,Q B, , , Q C, , , , ], [" ",R1 A, R2 A, ... , R# A,R1 B, R2 B, ... , R# C,R1 C, R2 C, ... , R# C], [Group1",Rc1A, Rc2A, ... , Rc#B,Rc1B, Rc2B, ... , Rc#C,Rc1C, Rc2C, ... , Rc#C], [Group2",Rc1A, Rc2A, ... , Rc#B,Rc1B, Rc2B, ... , Rc#C,Rc1C, Rc2C, ... , Rc#C], [Group3",Rc1A, Rc2A, ... , Rc#B,Rc1B, Rc2B, ... , Rc#C,Rc1C, Rc2C, ... , Rc#C], ] */ function displayItemChoiceCountByGroup_Hor(questions, data, choices, groupData){ var numOfQuestions = questions[0].length; var header = [""]; var subheader = [""]; //Start each row with a group. ItemsCount will store all groups count data. var itemsCount = groupData.map(function(group){ return [group[0]]; }); //Get array of just the Item (Question) choices var choiceData = data.map(function(row){ row.shift() return row; }); //Used to create spaces after each question to match len of choices. var emptyArray = newArrayFill(choices.length-1,""); //Paste Items horizontally after each iteration. for(var q = 0; q < numOfQuestions; q++){ //prep header row var questionAndSpaces = [questions[0][q]].concat(emptyArray); header = header.concat(questionAndSpaces); //prep subheader row subheader = subheader.concat(choices); //prep count row var questionResults =data.map(function(row){ return row[q]; }); // Loop through groups getting count data for each item choice. itemsCount = getResponseCountbyItemAndGroup(questionResults,choices,groupData,itemsCount) }; return [header, subheader].concat(itemsCount); }; |
The Parameters
displayItemChoiceCountByGroup_Hor()
has 4 parameters:
- questions: A 2d array of question items.
- data: A 2d array of question item choice results.
- choices: An array of choice options.
- groupData: A 2d array of each group name, start row and end row.
Main Variables
First, we need to know the total number of questions items. This will allow us later to iterate through each set of question choice total counts and stack them horizontally.
var numOfQuestions = questions[0].length;
Next, we will set up our header and footer row arrays. Remember we only need one of these each for the whole documents. We will need a blank space in the first item in the array because that column is dedicated to the section names (Lines 42-43).
Lastly, we will need a variable to store all our count data from all the choices for all the questions for all the groups (There are a lot of for ‘alls’ there). We will store this in our choiceData
variable (Lines 50-54).
1 2 3 4 5 |
//Get array of just the Item (Question) choices var choiceData = data.map(function(row){ row.shift() return row; }); |
We can already populate this 2d array with all our group names for each row. We do this by mapping the groupData
and returning only the zeroeth item in each row – which is the group name.
At this stage, our choiceData
will look like this:
1 2 3 4 5 6 |
choiceData = [ ["101-01", <<ready to add all question choice count data], ["101-02", <<ready to add all question choice count data], ["101-03", <<ready to add all question choice count data], ["101-04", <<ready to add all question choice count data] ]; |
Lets also set up an emptyArray
that will be the length of the choices minus 1. This will be added to the row after each header.
Looping through the questions
To build our count data this time around we will first loop through each question item set and then add it to the right of the previous one (Line 61).
for(var q = 0; q < numOfQuestions; q++)
Prep the header
On each iteration, we need to first set up the header for that question item data set. We will grab the question from the question
parameter. We will then add on the empty array to complete the header using the concat method ( Lines 63 and 64).
1 2 3 |
//prep header row var questionAndSpaces = [questions[0][q]].concat(emptyArray); header = header.concat(questionAndSpaces); |
Prep the subheader
For our subheader, we are simply adding the array of choices for that question item set.
1 2 |
//prep subheader row subheader = subheader.concat(choices); |
Get the current question’s choice data
Next, we will assign the variable questionResults
for all the choice items for the current questions. Remember, the choice items are found in the data map.
1 2 3 4 |
//prep count row var questionResults =data.map(function(row){ return row[q]; }); |
Get the count data
The next stage is to get the count data by group. This seems like a rather self-contained set of things to do so instead of packaging it into the current function, I have put it into a separate one for clarity and readability of the code.
We will call our getResponseCountbyItemAndGroup()
which will transform all our data to a total count of each choice for the current question item by group. We’ll send off 4 arguments with this function:
questionResults
: Our set of choice item data just for that question.choices
: The five choices users can make.groupData
: a 2d array containing the group name, start row and end row for each group.itemsCount
: The main storing variable that will accumulate the count data results by group.
The getResponseCountbyItemAndGroup()
function will return an updated version of the itemsCount
containing a 2d array of the count of all choices for each group for the currently iterated question.
1 |
itemsCount = getResponseCountbyItemAndGroup(questionResults,choices,groupData,itemsCount) |
The final package
Finally, we package all the headers and the count data and return it back to the runsies()
function.
getResponseCountbyItemAndGroup( questionResults,choices,groupData ,itemsCount)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
/*##################################################################### * Get count for each choice of each question of each group. * * Creates a horizontal choice spread of data from multiple questions and multiple groups. * * @param {array} questionResults : 2d array of single question item choice results. * @param {array} choices : array of choice options. * @param {array} groupData : 2d array of each group name, start row and end row. * @param {array} itemsCount : recursive array of the final returned count data as is builds on prev. * * @returns {array} : 2d array of count data for each item choice for each group. */ /* From: [ [response A, response B, response C], [response A, response B, response C], [response A, response B, response C] ] To: Q = Question Item R = Response Type Rc = Response count [ [Group1, Rc1A, Rc2A, ... , Rc#B,Rc1B, Rc2B, ... , Rc#C,Rc1C, Rc2C, ... , Rc#C], [Group2, Rc1A, Rc2A, ... , Rc#B,Rc1B, Rc2B, ... , Rc#C,Rc1C, Rc2C, ... , Rc#C], [Group3, Rc1A, Rc2A, ... , Rc#B,Rc1B, Rc2B, ... , Rc#C,Rc1C, Rc2C, ... , Rc#C], [Group4, Rc1A, Rc2A, ... , Rc#B,Rc1B, Rc2B, ... , Rc#C,Rc1C, Rc2C, ... , Rc#C], ] */ function getResponseCountbyItemAndGroup(questionResults,choices,groupData,itemsCount){ //Loop through each group var results = []; for(var group = 0; group < groupData.length; group++){ //Get the ChoiceData by group. var byGroup = questionResults.slice(groupData[group][1],groupData[group][2]); var transData = itemCount(choices,byGroup); var rowData = [itemsCount[group].concat(transData)]; results = results.concat(rowData); }; return results; }; |
The getResponseCountbyItemAndGroup()
is called from the displayItemChoiceCountByGroup_Hor()
function. Its main aim is to loop through each section and send a single group’s choice count data to the itemCount()
function we created in Part 3 of this course. It then joins all the choice count data for each group back together before returning it back to displayItemChoiceCountByGroup_Hor()
.
The Parameters
getResponseCountbyItemAndGroup()
takes 4 parameters:
questionResults
: choice item results for the current questionchoices
: the 5 choicesgroupData
: a 2d array containing the group name, start row and end row for each group.itemsCount
: The current set of accumulated group data from previous questions.
The group loop
Before we start the group loop we will create a storing array variable for all our results called…erh…results (Line 36).
Our loop will run the length of the groupData
– each row containing a group.
for(var group = 0; group < groupData.length; group++)
Just the group
Next, we will use some of our code you may recognize from our vertical count functions. Grabbing our question results raw data we will use our groupData
start and end values to slice out just the current group (Line 40).
1 |
var byGroup = questionResults.slice(groupData[group][1],groupData[group][2]); |
Send away for counting
We will then send our raw choice item data byGroup
off to be counted with our trusty itemCount()
function we created back in Part 3 of the course. The itemCount()
function takes two arguments:
choices
: the list of choices for the question item.byGroup
: the newly created raw choice data for the current group.
itemCount()
will then return the count for each choice for the current question and group.
1 |
var transData = itemCount(choices,byGroup); |
Putting it all together
Finally, we will create a rowData
variable that will contain the row array. Remember that itemsCount
(Not to be confused with itemCount
) contains all the current count data up to the current iteration of the question item from the parent function displayItemChoiceCountByGroup_Hor()
. For example, if we are on the second time around the loop, itemsCount
will have stored all the count data for the first question of all the groups.
We want to add on the current set of data to each row of our items count we do this here by concatenating the transData
to the current group row of itemsCount
.
var rowData = [itemsCount[group].concat(transData)];
Finally, we append our rowData
to our results and update our results.
results = results.concat(rowData);
Once outside the loop, the results array is sent back to displayItemChoiceCountByGroup_Hor()
.
itemCount()
Hey! You’ve already created this in Part 3. Go ahead and copy and paste it in your Trans.gs file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
/*##################################################################### * Get count for each choice of each question. * * gets the Item rating choice count for a single question Item. * * @param {array} results : 2d array of raw single question results. * @param {array} choices : array of choice options. * * @returns {array} : 2d array of count data for each choice in a single question item. */ /* From: [ [response A], [response A], [response A] ] To [ [Response count1, Response count2, ... , Response count#] ] */ function itemCount(choices, results) { var countResults = newArrayFill(choices.length,0); // Loop through list of results for(var res = 0; res < results.length; res++){ // If a response item already exists add 1 to corresponding column. for(var resp = 0; resp < choices.length; resp ++){ var choiceItemNumber = choices[resp][0]; if(results[res][0][0] === choiceItemNumber){ //Add to the count ++countResults[resp]; }; }; }; return(countResults); }; |
ToolBox.gs
sortByColumn(results, col)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/*##################################################################### * Sort 2d Array by selected column. * * @param {array} results: 2d Array of range data. * @param {string} col: Selected column number to sort by. * * @param {returns} A sorted 2d array of data. */ function sortByColumn(results, col) { results.sort(function(a, b) { if (a[col] === b[col]) { return 0; } else { return (a[col] < b[col]) ? -1 : 1; } }); return results }; |
I know it’s been a long journey, but we are almost there.
If you remember all the way back to the start of this part of the course, one of the first things we needed to do was to sort all the data by section.
Sorting data by a column is a pretty common thing to do with Google Sheets and Google Apps Script. This is why we put this in our ToolBox.gs file. If our project gets more complex we may be able to make use of it again for something else.
Parameters
The sortByColum()
function takes two arguments:
results
: this is the 2d data set that will be sorted.col
: this is the column that will be used to sort the dataset.
Sorting the dataset
We’ll use the Javascript sort method to sort our data. The sort method changes the array it has been assigned to directly. Sort can take an optional function with two parameters that we will make use of in our code.
In the first iteration of this sort function, parameter:
a
: will reference row 1 of the results.b
: will reference row 0 of the results.
When the sort function iterates through items in an array we can check 3 conditions:
a === b
: This is a match. We don’t want anything to change so we should return 0 (Zero).a < b
: a is less than b. It needs to be positioned lower than b so we should return -1.a > b
: a is greater than b. It needs to be positioned higher than b so we should return 1.
Once we have assigned a
0, -1 or 1 to each comparison these values are fed back to the sort method to help put your data in order. There is quite a bit more that goes into the sort
method behind the scenes but this a bigger discussion outside the scope of this course.
The rest
You will still need to keep:
pasteResults()
newArrayFill()
We are still using them. We just don’t need to change them.
Conclusion
The Complete Code
code.gs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
/* * Displaying a count of each item in the survey by selection and by group. * * */ /*##### Display the frequency of each choice of multiple question's selections and group ##### * This is the main run function. */ function runsies(){ //### Add your choices here var QUESTION_RANGE = "C2:E2"; var RESULT_RANGE = "B3:E121"; var CHOICES = ["1.Weak","2.Below Average","3.Average","4.Better Than Average","5.Strong"]; var SORT_COL = 0; //Sort column of selected range. var DISPLAY_LOCATION_START = "G12"; var SOURCE_SHEET_NAME = "MultiQMultiGroup 4"; var DESTINATION_SHEET_NAME = "MultiQMultiGroup 4"; //######################### var ss = SpreadsheetApp.getActiveSpreadsheet(); var sourceSheet = ss.getSheetByName(SOURCE_SHEET_NAME); var resultsVals = sourceSheet.getRange(RESULT_RANGE).getValues(); var questionVals = sourceSheet.getRange(QUESTION_RANGE).getValues(); //Sort the data by groups. var results = sortByColumn(resultsVals, SORT_COL); //Get list of groups, start row and length var groupData = getGroupData(results,SORT_COL); //Loop through each Item //Count for each choice in each Item var displayResults = displayItemChoiceCountByGroup_Vert(questionVals,resultsVals,CHOICES, groupData); //var displayResults = displayItemChoiceCountByGroup_Hor(questionVals,resultsVals,CHOICES, groupData); var destinationSheet = ss.getSheetByName(DESTINATION_SHEET_NAME); pasteResults(DISPLAY_LOCATION_START,displayResults,destinationSheet); }; /*##################################################################### * Get group data * * Creates a 2d array for data containing each groups name, start col and end col * * @param {array} results : The 2d array of question results by group. * @param {number} col : The column with the group in it. * * @returns {array} 2d array containing question, choices and count for each choice. */ function getGroupData(results, col){ var data = [], dataCount = 0, prev; for(var i = 0; i < results.length; i++){ if(results[i][col] !== prev){ data.push([results[i][col],i]) dataCount++; }else{ data[dataCount-1][2] = i; } prev = results[i][col]; } return data; }; |
Trans.gs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
//####################### VERTICAL SPREAD ############################ //##################################################################### /*##################################################################### * Get Item Question Count by Row and by group - Vertical Spread * * Creates a vertical choice spread of data from multiple questions and multiple groups. * * @param {array} questions : 2d array of question items. * @param {array} data : 2d array of question item choice results. * @param {array} choices : array of choice options. * @param {array} groupData : 2d array of each group name, start row and end row. * * @returns {array} : 2d array of count data for each item choice for each group. */ /* From: [ [Group, Item A , Item B , Item C ], [ 1 , response A, response B, response C], [ 2 , response A, response B, response C], [ 3 , response A, response B, response C], [ 1 , response A, response B, response C], [ 2 , response A, response B, response C], [ 3 , response A, response B, response C], ] To [ [Group1, , , , ], [" ", Choice type 1 , Choice type 2 , ... , Choice type # ], [Item A, Response count1, Response count2, ... , Response count#], [Item B, Response count1, Response count2, ... , Response count#], [Item C, Response count1, Response count2, ... , Response count#], [ , , , , ], [Group2, , , , ], [" ", Choice type 1 , Choice type 2 , ... , Choice type # ], [Item A, Response count1, Response count2, ... , Response count#], [Item B, Response count1, Response count2, ... , Response count#], [Item C, Response count1, Response count2, ... , Response count#], ] */ function displayItemChoiceCountByGroup_Vert(questions, data, choices, groupData){ var results = []; //Space data var emptyArray = [newArrayFill(choices.length+1, "")]; //Get array of just the Item (Question) choices var choiceData = data.map(function(row){ row.shift() return row; }); //Loop through each group for(var group = 0; group < groupData.length; group++){ var groupHeader = [[groupData[group][0]].concat(newArrayFill(choices.length, ""))]; //Get the ChoiceData by group. var byGroup = choiceData.slice(groupData[group][1],groupData[group][2]); //Transform the data into the count array var transData = singleGroup_MultiQuestionVert_Rating(questions,byGroup,choices); results = results.concat(groupHeader,transData,emptyArray); }; return results; }; /*##################################################################### * Choice for single group single question count of choices with a scaled rating. * * Creates a 1st header containing the question, a subheader with the response types and then a * row of count data. * * @param {string} question : The question item * @param {array} results : 2d array of question results. * * @returns {array} two row 2d array containing question, choices and count for each choice. */ /* From: [ [Item A , Item B , Item C ], [response A, response B, response C], [response A, response B, response C], [response A, response B, response C] ] To [ [" ", Choice type 1 , Choice type 2 , ... , Choice type # ], [Item A, Response count1, Response count2, ... , Response count#], [Item B, Response count1, Response count2, ... , Response count#], [Item C, Response count1, Response count2, ... , Response count#] ] */ function singleGroup_MultiQuestionVert_Rating(question,results,choices){ var numOfQuestions = results[0].length; var header = [[""].concat(choices)]; //2d array var itemRows = []; for(var q = 0; q < numOfQuestions; q++){ var itemQuestion = [question[0][q]]; //prep count row var questionResults =results.map(function(row){ return row[q]; }); var countRes = itemCount(choices,questionResults); //Add item question to count results itemRows.push(itemQuestion.concat(countRes)); }; return header.concat(itemRows); }; //####################### HORZONTAL SPREAD ############################ //##################################################################### /*##################################################################### * Get Item Question Count by Row and by group - Horizontal Spread * * Creates a horizontal choice spread of data from multiple questions and multiple groups. * * @param {array} questions : 2d array of question items. * @param {array} data : 2d array of question item choice results. * @param {array} choices : array of choice options. * @param {array} groupData : 2d array of each group name, start row and end row. * * @returns {array} : 2d array of count data for each item choice for each group. */ /* From: [ [Group, Item A , Item B , Item C ], [ 1 , response A, response B, response C], [ 2 , response A, response B, response C], [ 3 , response A, response B, response C], [ 1 , response A, response B, response C], [ 2 , response A, response B, response C], [ 3 , response A, response B, response C], ] To [ [" ",Q A, , , ,Q B, , , Q C, , , , ], [" ",R1 A, R2 A, ... , R# A,R1 B, R2 B, ... , R# C,R1 C, R2 C, ... , R# C], [Group1",Rc1A, Rc2A, ... , Rc#B,Rc1B, Rc2B, ... , Rc#C,Rc1C, Rc2C, ... , Rc#C], [Group2",Rc1A, Rc2A, ... , Rc#B,Rc1B, Rc2B, ... , Rc#C,Rc1C, Rc2C, ... , Rc#C], [Group3",Rc1A, Rc2A, ... , Rc#B,Rc1B, Rc2B, ... , Rc#C,Rc1C, Rc2C, ... , Rc#C], ] */ function displayItemChoiceCountByGroup_Hor(questions, data, choices, groupData){ var numOfQuestions = questions[0].length; var header = [""]; var subheader = [""]; //Start each row with a group. ItemsCount will store all groups count data. var itemsCount = groupData.map(function(group){ return [group[0]]; }); //Get array of just the Item (Question) choices var choiceData = data.map(function(row){ row.shift() return row; }); //Used to create spaces after each question to match len of choices. var emptyArray = newArrayFill(choices.length-1,""); //Paste Items horizontally after each iteration. for(var q = 0; q < numOfQuestions; q++){ //prep header row var questionAndSpaces = [questions[0][q]].concat(emptyArray); header = header.concat(questionAndSpaces); //prep subheader row subheader = subheader.concat(choices); //prep count row var questionResults =data.map(function(row){ return row[q]; }); // Loop through groups getting count data for each item choice. itemsCount = getResponseCountbyItemAndGroup(questionResults,choices,groupData,itemsCount) }; return [header, subheader].concat(itemsCount); }; /*##################################################################### * Get count for each choice of each question of each group. * * Creates a horizontal choice spread of data from multiple questions and multiple groups. * * @param {array} questions : 2d array of question items. * @param {array} questionResults : 2d array of single question item choice results. * @param {array} choices : array of choice options. * @param {array} groupData : 2d array of each group name, start row and end row. * @param {array} itemsCount : recursive array of the final returned count data as is builds on prev. * * @returns {array} : 2d array of count data for each item choice for each group. */ /* From: [ [response A, response B, response C], [response A, response B, response C], [response A, response B, response C] ] To: Q = Question Item R = Response Type Rc = Response count [ [Group1, Rc1A, Rc2A, ... , Rc#B,Rc1B, Rc2B, ... , Rc#C,Rc1C, Rc2C, ... , Rc#C], [Group2, Rc1A, Rc2A, ... , Rc#B,Rc1B, Rc2B, ... , Rc#C,Rc1C, Rc2C, ... , Rc#C], [Group3, Rc1A, Rc2A, ... , Rc#B,Rc1B, Rc2B, ... , Rc#C,Rc1C, Rc2C, ... , Rc#C], [Group4, Rc1A, Rc2A, ... , Rc#B,Rc1B, Rc2B, ... , Rc#C,Rc1C, Rc2C, ... , Rc#C], ] */ function getResponseCountbyItemAndGroup(questionResults,choices,groupData,itemsCount){ //Loop through each group var results = []; for(var group = 0; group < groupData.length; group++){ //Get the ChoiceData by group. var byGroup = questionResults.slice(groupData[group][1],groupData[group][2]); var transData = itemCount(choices,byGroup); var rowData = [itemsCount[group].concat(transData)]; results = results.concat(rowData); }; return results; }; //########################## ITEM COUNT ############################### //##################################################################### /*##################################################################### * Get count for each choice of each question. * * gets the Item rating choice count for a single question Item. * * @param {array} results : 2d array of raw single question results. * @param {array} choices : array of choice options. * * @returns {array} : 2d array of count data for each choice in a single question item. */ /* From: [ [response A], [response A], [response A] ] To [ [Response count1, Response count2, ... , Response count#] ] */ function itemCount(choices, results) { var countResults = newArrayFill(choices.length,0); // Loop through list of results for(var res = 0; res < results.length; res++){ // If a response item already exists add 1 to corresponding column. for(var resp = 0; resp < choices.length; resp ++){ var choiceItemNumber = choices[resp][0]; if(results[res][0][0] === choiceItemNumber){ //Add to the count ++countResults[resp]; }; }; }; return(countResults); }; |
ToolBox.gs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
/*##################################################################### * Sort 2d Array by selected column. * * @param {array} results: 2d Array of range data. * @param {string} col: Selected column number to sort by. * * @param {returns} A sorted 2d array of data. */ function sortByColumn(results, col) { results.sort(function(a, b) { if (a[col] === b[col]) { return 0; } else { return (a[col] < b[col]) ? -1 : 1; } }); return results }; /*##################################################################### * Pasting results * * @param {string} StartLoc : The start cell of the location you want to paste to. * @param {array} results : 2d array of question results. * @param {object} sheet : The Sheet item. * */ function pasteResults(StartLoc, results, sheet){ var endLoc = sheet.getRange(StartLoc) .offset(results.length-1,results[0].length-1) .getA1Notation(); var range = sheet.getRange( StartLoc+":"+endLoc); range.setValues(results); }; /*##################################################################### * Creates a new array with selecte fill item. * * @param {number} len : Length of array. * @param {number|string} fillItem : Number or string to fill the array item. * @returns {array} The array the required length with required fill. * */ function newArrayFill(len, fillItem){ var arizzle = []; for(var i = 0; i < len; i++){arizzle[i] = fillItem}; return arizzle; }; |
The Last Bit
Far out! That was a journey and a half wasn’t it?
If I’ve managed to keep you moving through this course all the way up to this point. I am super happy and if you have enjoyed the course and got something out of it, please click the like button and share the course far and wide.
In part 4 we added groups as an extra element to consider while getting the count for each choice for each question. We really started to see the benefit of the horizontal display of our choice count by group and also found some limitations for our vertical display.
When we built-in the groups to the project we added an intermediary function to handle collating all the group data. We discovered that we could still use all of the code from part 3 in our vertical display of count data but we had to make some modifications for our horizontal display. In the Toolbox.gs
, we added a sort helper function and then in the Code.gs
, we added a groupData
helper function to track the dataset by group.
The whole aim of this course was to show you how to transform data from Google Sheets in Google Apps Script and return it back to the Google Sheet. Our secondary aim was to show you how we can recycle functions and old code for new projects and set up our project environment in a somewhat logical manner.
Thank you so much for following along over the 4 parts of the course. It was quite a journey to write, build and prepare and I hope you learnt a lot along the way.
Need help with Google Workspace development?
My team of experts can help you with all of your needs, from custom app development to integrations and security. We have a proven track record of success in helping businesses of all sizes get the most out of Google Workspace.
Schedule a free consultation today to discuss your needs and get started or learn more about our services here.
~Yagi