I want to extract subset of a range from another range by selecting starting columns and length.
For example, how can I extract B1:C1 from another range A1:D1 by indicating starting columns 2 as in the image attached?
Thanks!
In your question, how about the following answer? In this answer, I used a custom function created by Google Apps Script. The sample script is as follows.
Sample script:
Please copy and paste the following script to the script editor of Spreadsheet and save the script.
function someKindOfFunction(range, startColumn, length) {
const sheet = SpreadsheetApp.getActiveSheet();
const orgRange = sheet.getRange(range);
const srcRange = orgRange.offset(0, startColumn - 1, orgRange.getNumRows(), length);
const values = srcRange.getValues();
return values;
}
When you use this script, for your showing sample Spreadsheet, please put a custom function of =SUM(someKindOfFunction("A1:D1",2,2)) to a cell. By this, 5 is returned.
Testing:
When this script is used for your showing sample Spreadsheet, the following result is obtained.
In this case, the range is given as a string like =SUM(someKindOfFunction("A1:D1",2,2)). Please be careful about this.
Note:
At your showing image, you are using =SUM(someKindOfFunction(A1:D1,startingrow: 2, length: 2)).
From this, I guessed someKindOfFunction might be a custom function.
And, from your question, I guessed "startingrow" might be "startingcolumn".
Added:
As another direction, when you use A1:D1 as the range instead of the string value, how about the following sample script? In this sample script, the array is directly used. But, I'm not sure whether this is your expected direction.
function someKindOfFunction(values, startColumn, length) {
return values.map(r =>
r.flatMap((c, j) => {
const start = startColumn - 1;
return j >= start && j < start + length ? c : [];
})
);
}
In this case, when you put a custom function of =SUM(someKindOfFunction(A1:D1,2,2)) to a cell using your sample Spreadsheet, 5 is returned.
References:
Custom Functions in Google Sheets
offset(rowOffset, columnOffset, numRows, numColumns)
This is easily achievable with the OFFSET function:
=sum(offset(Range,,StartCol-1,,Width))
So for your specific example, the expression would be:
=sum(offset(A1:D1,,2-1,,2))
Related
I am sorry if I made the wrong question, I'm getting started with Google Sheets and wanted to figure out how to work on some kind of score calculator based on IF conditionals. Here is the example:
It's going to be used on a Form Responses sheet (I don't want to use the Google Form's Quiz option) where I'll need to answer some Y/N questions, in the "Total" column, there is going to be a final score out of 100 based on the "Yes" or "No" answer.
Let's say that the Row # 2 is a submission from a Google Form, and let's say that every submission has by default a score of 100. What the "No" is doing is deducting 10 points from that initial score of 100.
I started with something like =MINUS("100", IF(A2= "No", 10)) but it only works with one cell.
Thank you so much!
Try this formula in E2:
=MINUS(100, COUNTIF(A2:D2, "No") * 10)
Output:
Update:
If you want to use the column header as reference for deduction points, You can use this Custom Function:
To write a custom function:
Create or open a spreadsheet in Google Sheets.
Select the menu item Tools > Script editor.
Delete any code in the script editor.
For this case, simply copy and paste the code below into your script editor
Click Save save.
Code:
function SCORE(range) {
var map = range.shift();
var data = range;
var result = [];
for(var i = 0; i < data.length; i++){
var tally = 0;
for(var j = 0; j < data[0].length; j++){
if(data[i][j] == "No"){
tally = tally + map[j];
}
}
result.push([100+tally])
}
return result;
}
To call the custom function
Click the cell where you want to use the function.
Type an equals sign (=) followed by the function name and any input value — for example, =SCORE(A1:D4) — and press Enter.
The cell will momentarily display Loading..., then return the result.
Example Usage:
Note: You must always include the column header(deduction points) in your range.
Reference:
COUNTIF
Custom Function
I want a formula to generate random data of birth dates for a specific years (Example: 1995 to 2002) and make it Array like this:
Sheet URL: https://docs.google.com/spreadsheets/d/1XHoxD-hNmpUOMVm_u-cz-4ESrabodsrS0fIfaN-n4js/edit
That might not be the best approach but it will get you closer to what you want:
=DATE(RANDBETWEEN(1995,2002),RANDBETWEEN(1,12),RANDBETWEEN(1,31))
There are two issues with this approach:
you might get a day that does not exist for the particular month. For example, 2/28/2021 exists, but 2/29/2021 does not exist.
I wasn't able to generate an array but only drag down formulas. When I generate an array, the same random numbers are used and as a result the dates are the same.
For the first issue, you can use isdate to check if the random date returned is correct. For example, 2/29/2021 is a wrong date (I hardcopied that date).
but I guess you can filter out the FALSE cases.
I really hope other people can come up with a better approach.
You could try (as I demonstrated in your sheet):
=ARRAY_CONSTRAIN(SORT(SEQUENCE(DATE(1992,12,31)-DATE(1900,1,1),1,DATE(1900,1,1)),RANDARRAY(DATE(1992,12,31)-DATE(1900,1,1)),1),COUNTA(A2:A),1)
SEQUENCE(DATE(1992,12,31)-DATE(1900,1,1),1,DATE(1900,1,1)) - Is used to create an array of valid numeric representations of true dates between 1-1-1900 and 31-12-1992.
SORT(<TheAbove>,RANDARRAY(DATE(1992,12,31)-DATE(1900,1,1)),1) - Is used to sort the array we just created randomly.
ARRAY_CONSTRAIN(<TheAbove>, COUNTA(A2:A),1) - Is used to only return as many random birth-dates we need according to other data.
Note that this is volatile and will recalculate upon sheet-changes and such. Also note that this is just "slicing" a given array and may fall short when you try to use it on a dataset larger than the given array.
As Google Sheets can deal with dates as integers (~ number of days since 1900), choosing a random date between two dates can be a single call to RANDBETWEEN (with the output formatted as Date).
With your initial date written in B1 and your end date in B2, the formula is simply:
=RANDBETWEEN($B$1,$B$2)
You can paste this formula in as many cells as you want, to generate N different random dates.
Of course, as other answers involving random generators in your sheet, the formula will be recomputed at each change. My suggestion to overcome this would simply be to copy/paste the output, using the "Paste special > Values only" option (right click or "Edit" menu).
Script Solution
Just for sake of completeness, here is a solution using a script
Initial Considerations
This cannot function like a in sheet function/formula.
https://developers.google.com/apps-script/guides/sheets/functions
Custom function arguments must be deterministic. That is, built-in spreadsheet functions that return a different result each time they calculate — such as NOW() or RAND() — are not allowed as arguments to a custom function. If a custom function tries to return a value based on one of these volatile built-in functions, it will display Loading... indefinitely.
A custom function cannot affect cells other than those it returns a value to. In other words, a custom function cannot edit arbitrary cells, only the cells it is called from and their adjacent cells. To edit arbitrary cells, use a custom menu to run a function instead.
So a normal script is needed.
The Script
/**
* Sets the values of a range to random dates.
*/
function generateRandomBdays(range, start, end) {
let height = range.getHeight();
let width = range.getWidth();
let output = [];
for (let i = 0; i != height; i++) {
let row = [];
for (let j = 0; j != width; j++) {
row.push(randomBday(start, end));
}
output.push(row)
}
range.setValues(output);
}
/**
* Generates a random date beween start and end
*/
function randomBday(start, end) {
if (start < 2000) start = start - 1900
start = new Date(`${start}`);
if (end < 2000) end = end - 1900
end = new Date(`${end}`);
let bday = new Date(
start.getTime() + (Math.random() * (end.getTime() - start.getTime()))
);
return bday;
}
/**
* Gets active selection and fills with random dates
*/
function main(){
let file = SpreadsheetApp.getActive();
let sheet = file.getActiveSheet()
let range = sheet.getActiveRange();
// ============
generateRandomBdays(range, 1995, 2002); // Change these years to your liking
// ============
}
/**
* Creates menu when sheet is opened.
*/
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Generate Birthdays')
.addItem('Generate!', 'main')
.addToUi();
}
Which works like this:
Installation
You will have to copy it into your script editor and then run one of the functions to authorize the script with the permissions it needs. Then next time you open the sheet you should have the menu available.
Alternatively you can delete the onOpen function and just use it from the script editor.
Within the main function, customize the range of years you need.
References
Apps Script
Overview of Spreadsheet Service in Apps Script
I'm trying to expand the following formula down a column using an array, which I understand doesn't work exactly with countif. I need this as the divisor in the column where I have the numerator expanding successfully down the column. Here is the function that works fine in each row:
=arrayformula(countifs(unique(if('All Data'!$B$2:$B=$A2,'All Data'!$A$2:$A,)),">="&edate(F$2,0),unique(if('All Data'!$B$2:$B=$A2,'All Data'!$A$2:$A,)),"<="&edate(F$2,1)))
I am trying to avoid copying the formulas all over the sheet, as it is rather large and would prefer it to work as an array where it gives me the count for a2 in row 2, a3 in row 3, a4 in row 4, etc, so I tried this formula, which doesn't work:
=arrayformula(countifs(unique(if('All Data'!$B$2:$B=$A2:$A,'All Data'!$A$2:$A,)),">="&edate(F$2,0),unique(if('All Data'!$B$2:$B=$A2:$A,'All Data'!$A$2:$A,)),"<="&edate(F$2,1)))
Is there a workaround I can use to get this expanding for me?
Thank you!
You could accomplish this with an Apps Script custom function. To achieve this, follow these steps:
In your spreadsheet, select Tools > Script editor to open a script bound to your file.
Copy this function in the script editor, and save the project:
function AVERAGE_STUDENTS(classes, monthIndex) {
const sheet = SpreadsheetApp.getActive().getSheetByName("All Data");
const data = sheet.getRange(2, 1, sheet.getLastRow() - 1, sheet.getLastColumn()).getValues();
const monthRows = data.filter(row => row[0].getMonth() + 1 == monthIndex);
return classes.map(classy => {
const classRows = monthRows.filter(row => row[1] == classy[0]);
const totalStudents = classRows.length;
const uniqueDates = new Set(classRows.map(element => JSON.stringify([element[0], element[1]]))).size;
return totalStudents / uniqueDates;
});
}
Now, if you go back to your spreadsheet, you can use this function like any in-built one. You just have to provide the appropriate range for the classes (in this case it would be A3:A18) and the month index (for April, that's 4), as you can see here:
Note:
This function could also be modified in order to just call the function at C1 and populate all months automatically.
Reference:
Custom Functions in Google Sheets
Currently I'm using the following formula to search and count the number of times a given text is used within a given cell:
=COUNTIF(Sheet1!G3:G1151, "COMPLETE")
Any ideas how I can use the same formula against multiple sheets?
Something like the following:
=COUNTIF(Sheet1, Sheet2!G3:G1151, "COMPLETE")
Thanks for your help
In case there are many sheets you want to look for, and to avoid having a to repeat the formula many times for each sheet, you can use a custom function created in Google Apps Script instead. To achieve this, follow these steps:
In your spreadsheet, select Tools > Script editor to open a script bound to your file.
Copy this function in the script editor, and save the project:
function COUNTMANYSHEETS(sheetNames, range, text) {
sheetNames = sheetNames.split(',');
var count = 0;
sheetNames.forEach(function(sheetName) {
var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
var values = sheet.getRange(range).getValues();
values.forEach(function(row) {
row.forEach(function(cell) {
if (cell.indexOf(text) !== -1) count++;
});
});
});
return count;
};
Now, if you go back to your spreadsheet, you can use this function just as you would do with any other function. You just have to provide a string with all the sheet names, separated by a separator specified in the code (in this sample, a comma), another one with the range you want to look for, and the text you want to look for, as you can see here, for example:
=COUNTMANYSHEETS("Sheet1,Sheet2,Sheet3", "G3:G1151", "COMPLETE")
Notes:
It's important that you provide the sheet names separated by the separator specified in sheetNames = sheetNames.split(',');, and nothing else (not empty spaces after the comma, etc.).
It's important that you provide the range in quotes ("G3:G1151"). Otherwise, the function will interpret this as an array of values corresponding to the specified range, and you won't be able to look for the values in other sheets.
In this sample, the code looks for the string COMPLETE, and is case-sensitive. To make it case-insensitive, you could use toUpperCase() or toLowerCase().
If you wanted to look for all sheets in the spreadsheet, you could modify your function so that it only accepts the range and the text as parameters, and get all sheets via SpreadsheetApp.getActive().getSheets();.
Reference:
Custom Functions in Google Sheets
String.prototype.split()
String.prototype.indexOf()
Migrating to new Google spreadsheets. I have a custom formula that combines a few arrays into one array
=TRANSPOSE(SPLIT(ARRAYFORMULA(CONCATENATE('Monthly link'!A10:A&CHAR(13) , 'Monthly link'!R10:R&CHAR(13) , 'Monthly link'!AG10:AG&CHAR(13) , 'Monthly link'!AU10:AU&CHAR(13) )), CHAR(13)))
this formula works perfectly fine in the old Google spreadsheet, but in the new one, it gave me a "Error: Text result of CONCATENATE is longer than the limit of 50000 characters."
Is there a way around this? I've tried the Array_Literal formula but can't seem to get it to work, that seems like the a possible solution. But it seems the function combines arrays across and not down.
I've tried:
=array_literal('Monthly link'!A10:A,'Monthly link'!R10:R,'Monthly link'!AG10:AG,'Monthly link'!AU10:AU)
Don't know if this is working, but might be worth a shot. Someone posted this on Google Docs help forum.
/.../ If you want more than 50,000 characters in a single cell, you can use QUERY's header clause.
Example:
=ArrayFormula(query(row(A1:A70000),,100000))
This creates a cell with 408,893 characters. You can verify by using the LEN function.
Ok I fixed the above line like this, I think it works:
=ArrayFormula(query(A1:A100000,,100000))
This is provided that you have data in column A, from row 1 to row 100000. It will concatenate all of it. I guess max length is 100000 characters?
Looking back to here, you can probably nest concatenate:
=TRANSPOSE(SPLIT(ARRAYFORMULA(CONCAT(CONCATENATE('Monthly link'!A10:A&CHAR(13) ,
'Monthly link'!R10:R&CHAR(13)), CONCATENATE('Monthly link'!AG10:AG&CHAR(13) ,
'Monthly link'!AU10:AU&CHAR(13)) )), CHAR(13)))
I simply added in one more CONCATENATE to combine the strings with only one value, then use CONCAT to combine those.
EDIT
This isn't exactly a fix, but reading google documentation states that you can create an old spreadsheet by going here.
EDIT 2
Try this code:
=TRANSPOSE(SPLIT(CONCAT(ARRAYFORMULA(CONCATENATE('Monthly link'!A10:A&CHAR(13),
'Monthly link'!R10:R&CHAR(13))), ARRAYFORMULA(CONCATENATE(
'Monthly link'!AG10:AG&CHAR(13), 'Monthly link'!AU10:AU&CHAR(13)))), CHAR(13)))
It seems the error is coming from ARRAYFORMULA, has it has a limit of 50000. Not CONCATENATE or CONCAT. So, I use CONCAT to combine two different ARRAYFORMULAs that both house half of the original data. You can continue to divide these until there are even 4 ARRAYFORMULAs that all only have one dataset if need be.
EDIT 3
Currently, I am working on implementing a function in javascript found here.
You can test it currently by Tools->Script editor->Paste, then to run it go Tools->Script Manager->organizeData->Run.
I'll continue to work on it... it is currently not working, but I am close;)
EDIT 4
I finished it! You can see it here. You need to create a new script using the above instructions (Tools->Script editor->Paste), save it, then you can run it from the Script Editor window or from the spreadsheet by doing Tools->Script Manager->organizeData->Run.
What the script does is gets the data from the forms, puts it in the data to be copied, then it has a strange restriction where it requires a letter in the column to be able to copy it, so it adds a letter in so the script will fill the rows with "undefined". From there, all of the rows have "undefined" in them, so data can be copied to all of them.
If you want to know how to implement the script directly into a cell, you can just put:
=organizeData()
it will call the custom function! See here for more details.
My answer is related to another similar Q, marked as duplicete:
Text result of JOIN is longer than the limit of 50000 characters
My solution is to use the formula:
=query(joinSplit(A2:A, ";"), "select Col1, count(Col1) group by Col1", 0)
where joinSplit(A2:A, ";") is a custom formula.
The code to paste into script editor is:
function joinSplit(column, delim)
{
var result = [];
var row = [];
for (var i = 0, l = column.length; i < l; i++)
{
row = column[i].join(delim).split(delim);
row.forEach( function(elt) { result.push([elt]); } );
}
return result;
}
It will return the column of unique items.
If data is:
A;B;C;D
D;D
E;F;A;A
G;A;B;C
The result is column:
A
B
C
D
D
D
E
F
A
A
G
A
B
C
The joinSplit script SAVED MY LIFE.
I tweaked it to trim out whitespace in case your data has a ", " in it.
function joinSplit(column, delim)
{
var result = [];
var row = [];
for (var i = 0, l = column.length; i < l; i++)
{
row = column[i].join(delim).split(delim);
row.forEach( function(elt) { result.push([elt.trim()]); } );
}
return result;
}