“Aggregate Data Series” to average instead of sum in Google Apps Script? - google-apps-script

Nearly duplicate question
I am currently trying to create a couple charts through Google App Script. I would like to have one of the series in one of the charts to be aggregated to an average, but I cannot figure out how. The default is sum and I have yet to find a way to change it. I have been trying everything I can find to solve this issue. It does not seem to be documented at all in the chart options, and I have found minimal documentation elsewhere.
What I've tried:
Setting it for each series
Reverse engineering the code myself
.setOption('applyAggregateData',0) (from link above)
.setOption("series", {"0":{"applyAggregateData":"average"}}) (combination of answers I found)
.setOption('aggregateFunction',0) (combination of answers I found)
None of this has worked for me, and the best I'm able to get is aggregation by sum. I am not sure what I am missing here, as it seems to have worked for others in the past.
Chart code:
var chart2 = exportSheet.newChart()
.setChartType(Charts.ChartType.COLUMN)
.setPosition(2, 2, 0, 0)
.addRange(searchSheet.getRange('B11:B'))
.addRange(searchSheet.getRange('G11:G'))
.setOption("series", {"0":{"aggregateFunction":"average"}}) //Does not work
.setOption("title", 'Avg. Ton Per Mile')
.setOption('hAxis.title', 'Dates')
.setOption('vAxis.title', 'Salt Usage (T)')
.build();

I also can't seem to make it work but I have thought of a workaround for this. See if it works for you. This can be used until you figure out how to aggregate by average.
Code:
function myFunction() {
// Used only 1 sheet for testing purposes
var exportSheet = SpreadsheetApp.getActiveSheet();
// Set a query formula somewhere that takes the average of your data
// Our data should now be in Y11:Z that contains the average
exportSheet.getRange('Y11')
.setFormula('=query(query({B11:B,G11:G},"select Col1, avg(Col2) where Col1 is not null group by Col1"), "offset 1", 0)');
var chart2 = exportSheet.newChart()
.setChartType(Charts.ChartType.COLUMN)
.setPosition(2, 8, 0, 0)
// now get the output of the query
.addRange(exportSheet.getRange('Y11:Y'))
.addRange(exportSheet.getRange('Z11:Z'))
.setOption("title", 'Avg. Ton Per Mile')
.setOption('hAxis.title', 'Dates')
.setOption('vAxis.title', 'Salt Usage (T)')
.build();
exportSheet.insertChart(chart2);
}
We pre-process the data via query's average and then pass the range of the output to the chart instead.
Sample Data:
Output:
Hidden query output:
Note:
Choose a range that can be hidden from plain sight for your query formula if you don't want it to be seen. (Furthermore, you can also actually hide the column to where it will be placed)
Editing B11:B and G11:G ranges will still update the chart with aggregation.
Appending to the range where query is set (in this sample, Y11:Y and Z11:Z) will not update the average, but just append to the range normally without aggregation.

Related

Find cell address of each matching value in range - Google Apps Script

I have a range that assigns shifts to a set of employees, in which the row labels are dates (ie, the Y axis is a chronological set of dates), and the column headers are locations (Building1, Building2, etc). Each row, then, contains employees assigned to each location for that day. Or, alternatively, each column will contain a chrono list of who will be assigned to the location specified in that column's header.
I am attempting to match a name, say "John Doe" for each instance he appears throughout the range, and return a 2 column list of dates and locations for which he is assigned. John Doe will be listed many times over the dates in question and various locations (in multiple columns).
I've reached the limit of my expertise both with AppsScript and Filter functions and greatly appreciate any help. I believe a loop is necessary, but perhaps there is a better way. For what its worth, my goal is to take this list and put every assignment on the user's calendar (I've solved for this already). TIA everyone!
Sample input and output situation
From your provided Spreadsheet, I believe your goal is as follows.
You want to achieve the following situation using Google Apps Script.
In this case, how about the following sample script?
Sample script:
Please copy and paste the following script to the script editor of Spreadsheet and save the script. When you use this script, please put a custom function of =SAMPLE(Data!A3:F20,"John Doe") to a cell. By this, the result values are returned.
const SAMPLE = ([h, ...v], searchName) =>
[["Data", "Location"], ...v.flatMap(([hh, ...vv]) => {
const i = vv.indexOf(searchName);
return i != -1 ? [[hh, h[i + 1]]] : [];
})];
If you don't want to include the header row, you can also use the following script.
const SAMPLE = ([h, ...v], searchName) =>
v.flatMap(([hh, ...vv]) => {
const i = vv.indexOf(searchName);
return i != -1 ? [[hh, h[i + 1]]] : [];
});
Testing:
When this sample script is used for your sample input values, the following situation is obtained.
In the case of "John Doe", from your expected output, "Building4" of "8/8/2022" is not included as shown in the red background cell. But, I'm worried that you might have miscopied. So, I proposed the above sample script. If you want to except for the value of the specific date, please tell me. This can be also achieved.
Reference:
Custom Functions in Google Sheets
The result that you are looking for could be achieved by using Google Sheets built-in functions in a formula:
=ARRAYFORMULA(QUERY(SPLIT(FLATTEN(Data!A4:A20&"💣"&Data!B3:F3&"💣"&Data!B4:F20),"💣"),"SELECT Col1,Col2 WHERE Col3 = 'John Doe'")
Briefly, the above formula uses FLATTEN and Google Sheets array handling feature to "unpivot" your double entry table, then uses QUERY to filter and limit the data to be returned.
Related
How do you create a "reverse pivot" in Google Sheets?

Sorting Columns Based on Values from different sheet

I've created a table with Names, Type of Sale, Location and Amount. The names is taken from a different sheet with formula ={"";Unique(rawData!A:A)} and for other columns like location Amount and type of sales, I've used Vlookup and Sumifs function based on the requirement.
This is working fine, but, when I try to sort with the googlescript with the code
function autoSort() {
const ss = SpreadsheetApp.getActiveSpreadsheet()
const ws = ss.getSheetByName("Summary")
const range = ws.getRange("A2:D1000")
range.sort({column:4, ascending: false})
}
the Amount column is getting sorted but again it is changing back in a blink.
Is this due to the formula ={"";Unique(rawData!A:A)} or I'm doing something else wrong here.. Kindly help
What's going wrong?
When you are "manually" sorting your data with the script, your ={"";Unique(rawData!A:A)} formula does not change. All you are doing is rearranging the other vlookup and sumif physical formulas around the sheet. Since the names never change, neither are your resulting values.
This is a very basic and rudimentary explanation. I'm sure there is a much better explanation for what is happening, but my basic understanding is just that you can't sort formulas because ultimately nothing about the order actually changes
A solution?
Do do everything in one formula in order to group everything together.
I have come up with one to do this.
=ifna(
sort(
{
Unique(rawData!A2:A),
arrayformula(VLOOKUP(Unique(rawData!A2:A), rawData!A2:D10, 2, 0)),
arrayformula(VLOOKUP(Unique(rawData!A2:A), rawData!A2:D10, 3, 0)),
arrayformula(VLOOKUP(Unique(rawData!A2:A), rawData!A2:D10, 4, 0))
},
4, 0
)
)
This pulls all the data at once (including the names) and sorts it as a whole. The VLOOKUP references the unique array, instead of an actual cell reference. This allows the values to be sorted, without rearranging the physical formulas. Below is a link to the spreadsheet I created to test this. This may need to be changed and edited to your liking (especially given that I didn't incorporate any SUMIF formulas), but it does what you appear to be asking for.
https://docs.google.com/spreadsheets/d/1Yj0IhZsbbXvhHQfkhn3P8Ac-A8nq5D3fiOZAsVYiPVs/edit?usp=sharing
This formula creates a blank first row. This can be fixed by filtering out blank values in the unique formula, but I left it as it was right now to avoid confusion. If you would like help incorporating that, just let me know and I can edit the formula.

Google App Script, .getValue returns ERROR

I have quite a big problem. If I read columns cells with for loop and range(r,c.).getValue() (same for reading with range(r,c,m,1).getValues() ) the values are not read. I got #ERROR!. I am reading cell values that are some as reference from other sheet and other with some if, round and plus/minus calculations.
As you maybe see on the figure I have a fixed test value over some columns (number 2, 3, 4, 5) and these are always read OK.
In the source code I added 3 lines that read fixed entered values (2,3,4,5).
Any suggestion why as I have several scripts with reading cell values but nothing similar happend till now.
Thank you!
Regards,
If my understanding if correct, you would like to obtain values from cells that have dynamic values (calculated or referenced).
It might fail if you use range.getValue() since there are no actual value in the cell. You can try to use range.getDisplayValues() instead to get the displayed value. This is documented here.
If you are doing this, your code will be like this :
for(var ii=0; ii < iDNRows; ii++)
{
i0mik_od_zacetka = sIzracuni.getRange(ii+StartRowData, iStartColumnData).getDisplayValue();
.
.
.
}
Or you can do .getDisplayValues() :
values = sIzracuni.getRange(startrow,startcol,rownum,colnum).getDisplayValues();
Do comment if I have misunderstood your question.

Filter date range on a chart in Google Apps Script

I have a spreadsheet with monthly data going back nearly a decade and am creating a dashboard for this and other sheets like it.
I am attempting to filter the data table for a chart showing the last 12 months of data. I've attempted to use a number range filter on the dates using getTime() or valueOf(), but both refuse to to build a filter when I set a minimum value using date.valueOf() (I assume because of size). I've also tried the solution in the link below, using viewWindow, but that failed.
Date Range Google Chart Tools
I know this can be done with the visualization APIs, but unfortunately I'm stuck with Google Apps Script for this. I'm trying to use one data table for everything, but if this is impossible I can create a separate one with just 12 rows. I was really hoping to let users load custom ranges of time, but there's no ChartRangeFilter in GAS.
As you've found, the NumberRangeFilter doesn't work on dates, and Apps Script currently doesn't support any other range filters. One workaround I've employed is to convert your dates to numbers, and use a NumberRangeFilter to filter them. For example, "October 19, 2012" could be converted to "20121019".
In order to avoid weird jumps in the graph due to the whole between 20121031 and 20121101, you need to set the filter on the numerical date but use a DataViewDefinition to only show the actual date in the graph.
Here's a rough piece of sample code. Column 0 is the actual date, 1 is the numerical date, and 2 & 3 are values I want to plot.
var dateFilter = Charts.newNumberRangeFilter()
.setFilterColumnIndex(1)
.build();
var view = Charts.newDataViewDefinition()
.setColumns([0, 2, 3]);
var areaChart = Charts.newAreaChart()
.setDataViewDefinition(view)
.setStacked()
.setDimensions(800, 400)
.build();

Selecting the last value of a column

I have a spreadsheet with some values in column G. Some cells are empty in between, and I need to get the last value from that column into another cell.
Something like:
=LAST(G2:G9999)
except that LAST isn't a function.
Similar answer to caligari's answer, but we can tidy it up by just specifying the full column range:
=INDEX(G2:G, COUNT(G2:G))
So this solution takes a string as its parameter. It finds how many rows are in the sheet. It gets all the values in the column specified. It loops through the values from the end to the beginning until it finds a value that is not an empty string. Finally it retunrs the value.
Script:
function lastValue(column) {
var lastRow = SpreadsheetApp.getActiveSheet().getMaxRows();
var values = SpreadsheetApp.getActiveSheet().getRange(column + "1:" + column + lastRow).getValues();
for (; values[lastRow - 1] == "" && lastRow > 0; lastRow--) {}
return values[lastRow - 1];
}
Usage:
=lastValue("G")
EDIT:
In response to the comment asking for the function to update automatically:
The best way I could find is to use this with the code above:
function onEdit(event) {
SpreadsheetApp.getActiveSheet().getRange("A1").setValue(lastValue("G"));
}
It would no longer be required to use the function in a cell like the Usage section states. Instead you are hard coding the cell you would like to update and the column you would like to track. It is possible that there is a more eloquent way to implement this (hopefully one that is not hard coded), but this is the best I could find for now.
Note that if you use the function in cell like stated earlier, it will update upon reload. Maybe there is a way to hook into onEdit() and force in cell functions to update. I just can't find it in the documentation.
Actually I found a simpler solution here:
http://www.google.com/support/forum/p/Google+Docs/thread?tid=20f1741a2e663bca&hl=en
It looks like this:
=FILTER( A10:A100 , ROW(A10:A100) =MAX( FILTER( ArrayFormula(ROW(A10:A100)) , NOT(ISBLANK(A10:A100)))))
LAST() function is not implemented at the moment in order to select the last cell within a range. However, following your example:
=LAST(G2:G9999)
we are able to obtain last cell using the couple of functions INDEX() and COUNT() in this way:
=INDEX(G2:G; COUNT(G2:G))
There is a live example at the spreedsheet where I have found (and solved) the same problem (sheet Orzamentos, cell I5). Note that it works perfectly even refering to other sheets within the document.
Summary:
=INDEX( FILTER( G2:G , NOT(ISBLANK(G2:G))) , COUNTA(G2:G) )
Details:
I've looked through and tried several answers, and here's what I've found:
The simplest solution (see Dohmoose' answer) works if there are no blanks:
=INDEX(G2:G; COUNT(G2:G))
If you have blanks, it fails.
You can handle one blank by just changing from COUNT to COUNTA (See user3280071's answer):
=INDEX(G2:G; COUNTA(G2:G))
However, this will fail for some combinations of blanks. (1 blank 1 blank 1 fails for me.)
The following code works (See Nader's answer and jason's comment):
=INDEX( FILTER( G2:G , NOT(ISBLANK(G2:G))) , ROWS( FILTER( G2:G , NOT(ISBLANK(G2:G)) ) ) )
but it requires thinking about whether you want to use COLUMNS or ROWS for a given range.
However, if COLUMNS is replaced with COUNT I seem to get a reliable, blank-proof implementation of LAST:
=INDEX( FILTER( G2:G , NOT(ISBLANK(G2:G))) , COUNT( FILTER( G2:G , NOT(ISBLANK(G2:G)) ) ) )
And since COUNTA has the filter built in, we can simplify further using
=INDEX( FILTER( G2:G , NOT(ISBLANK(G2:G))) , COUNTA(G2:G) )
This is somewhat simple, and correct. And you don't have to worry about whether to count rows or columns. And unlike script solutions, it automatically updates with changes to the spreadsheet.
And if you want to get the last value in a row, just change the data range:
=INDEX( FILTER( A2:2 , NOT(ISBLANK(A2:2))) , COUNTA(A2:2) )
In order to return the last value from a column of text values you need to use COUNTA, so you would need this formula:
=INDEX(G2:G; COUNTA(G2:G))
try this:
=INDIRECT("B"&arrayformula(max((B3:B<>"")*row(B3:B))))
Suppose the column in which you are looking for the last value is B.
And yes, it works with blanks.
This one works for me:
=INDEX(I:I;MAX((I:I<>"")*(ROW(I:I))))
It looks like Google Apps Script now supports ranges as function parameters. This solution accepts a range:
// Returns row number with the last non-blank value in a column, or the first row
// number if all are blank.
// Example: =rowWithLastValue(a2:a, 2)
// Arguments
// range: Spreadsheet range.
// firstRow: Row number of first row. It would be nice to pull this out of
// the range parameter, but the information is not available.
function rowWithLastValue(range, firstRow) {
// range is passed as an array of values from the indicated spreadsheet cells.
for (var i = range.length - 1; i >= 0; -- i) {
if (range[i] != "") return i + firstRow;
}
return firstRow;
}
Also see discussion in Google Apps Script help forum: How do I force formulas to recalculate?
I looked at the previous answers and they seem like they're working too hard. Maybe scripting support has simply improved. I think the function is expressed like this:
function lastValue(myRange) {
lastRow = myRange.length;
for (; myRange[lastRow - 1] == "" && lastRow > 0; lastRow--)
{ /*nothing to do*/ }
return myRange[lastRow - 1];
}
In my spreadsheet I then use:
= lastValue(E17:E999)
In the function, I get an array of values with one per referenced cell and this just iterates from the end of the array backwards until it finds a non-empty value or runs out of elements. Sheet references should be interpreted before the data is passed to the function. Not fancy enough to handle multi-dimensions, either. The question did ask for the last cell in a single column, so it seems to fit. It will probably die on if you run out of data, too.
Your mileage may vary, but this works for me.
function lastRow(column){
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var lastRow = sheet.getLastRow();
var lastRowRange=sheet.getRange(column+startRow);
return lastRowRange.getValue();
}
no hard coding.
In a column with blanks, you can get the last value with
=+sort(G:G,row(G:G)*(G:G<>""),)
This gets the last value and handles empty values:
=INDEX( FILTER( H:H ; NOT(ISBLANK(H:H))) ; ROWS( FILTER( H:H ; NOT(ISBLANK(H:H)) ) ) )
The answer
$ =INDEX(G2:G; COUNT(G2:G))
doesn't work correctly in LibreOffice. However, with a small change, it works perfectly.
$ =INDEX(G2:G100000; COUNT(G2:G100000))
It always works only if the true range is smaller than (G2:G10000)
Is it acceptable to answer the original question with a strictly off topic answer:)
You can write a formula in the spreadsheet to do this. Ugly perhaps? but effective in the normal operating of a spreadsheet.
=indirect("R"&ArrayFormula(max((G:G<>"")*row(G:G)))&"C"&7)
(G:G<>"") gives an array of true false values representing non-empty/empty cells
(G:G<>"")*row(G:G) gives an array of row numbers with zeros where cell is empty
max((G:G<>"")*row(G:G)) is the last non-empty cell in G
This is offered as a thought for a range of questions in the script area that could be delivered reliably with array formulas which have the advantage of often working in similar fashion in excel and openoffice.
function getDashboardSheet(spreadsheet) {
var sheetName = 'Name';
return spreadsheet.getSheetByName(sheetName);
}
var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
var dashboardSheet = getDashboardSheet(spreadsheet);
Logger.log('see:'+dashboardSheet.getLastRow());
I was playing with the code given by #tinfini, and thought people might benefit from what I think is a slightly more elegant solution (note I don't think scripts worked quite the same way when he created the original answer)...
//Note that this function assumes a single column of values, it will
//not function properly if given a multi-dimensional array (if the
//cells that are captured are not in a single row).
function LastInRange(values)
{
for (index = values.length - 1; values[index] == "" && index > 0; index--) {}
return String(values[index]);
}
In usage it would look like this:
=LastInRange(D2:D)
Regarding #Jon_Schneider's comment, if the column has blank cells just use COUNTA()
=INDEX(G2:G; COUNT**A**(G2:G))
I found another way may be it will help you
=INDEX( SORT( A5:D ; 1 ; FALSE) ; 1 ) -will return last row
More info from anab here:
https://groups.google.com/forum/?fromgroups=#!topic/How-to-Documents/if0_fGVINmI
Found a slight variation that worked to eliminate blanks from the bottom of the table.
=index(G2:G,COUNTIF(G2:G,"<>"))
I'm surprised no one had ever given this answer before. But this should be the shortest and it even works in excel :
=ARRAYFORMULA(LOOKUP(2,1/(G2:G<>""),G2:G))
G2:G<>"" creates a array of 1/true(1) and 1/false(0). Since LOOKUP does a top down approach to find 2 and Since it'll never find 2,it comes up to the last non blank row and gives the position of that.
The other way to do this, as others might've mentioned, is:
=INDEX(G2:G,MAX((ISBLANK(G2:G)-1)*-ROW(G2:G))-1)
Finding the MAXimum ROW of the non blank row and feeding it to INDEX
In a zero blank interruption array, Using INDIRECT RC notation with COUNTBLANK is another option. If V4:V6 is occupied with entries, then,
V18:
=INDIRECT("R[-"&COUNTBLANK(V4:V17)+1&"]C",0)
will give the position of V6.
to get the last value from a column you can also use MAX function with IF function
=ARRAYFORMULA(INDIRECT("G"&MAX(IF(G:G<>"", ROW(G:G), )), 4)))
I have gone through way too many of these implementations of last-row for a specific column. Many solutions work but are slow for large or multiple datasets. One of my use cases requires me to check the last row in specific columns across multiple spreadsheets. What I have found is that taking the whole column as a range and then iterating through it is too slow, and adding a few of these together makes the script sluggish.
My "hack" has been this formula:
=ROW(index(sheet!A2:A,max(row(sheet!A2:A)*(sheet!A2:A<>""))))-1
Example: Add this to Cell A1, to find the last row in column A. Can be added anywhere, just make sure to manage the "-1" at the end depending on which row the formula is placed. You can also place this is another col, rather than the one you're trying to count, and you don't need to manage the -1. You could also count FROM a starting Row, like "C16:C" - will count values C16 onwards
This formula is reliably giving me the last row, including blanks in the middle of the dataset
To use this value in my GS code, I am simply reading the cell value from A1. I understand that Google is clear that spreadsheet functions like read/write are heavy (time-consuming), but this is much faster than column count last-row methods in my experience (for large datasets)
To make this efficient, I am getting the last row in a col once, then saving it as a global variable and incrementing in my code to track which rows I should be updating. Reading the cell every-time your loop needs to make an update will be too inefficient. Read once, iterate the value, and the A1 cell formula (above) is "storing" the updated value for the next time your function runs
Please let me know if this was helpful to you! If I encounter any issues I will comment on this answer.
=QUERY({G2:G9999,ARRAYFORMULA(ROW(G2:G9999))},"Select Col1 where Col1 is not null Order By Col2 desc limit 1",0)
In the query, Col1 refers to column G, and Col2 refers to a virtual column, populated with the row numbers returned by ARRAYFORMULA(ROW(G2:G9999)).
I haven't evaluated the other answers, so I can't say if this is the best way, but it worked for me.
Bonus: to return the first non-empty cell:
QUERY({G2:G9999},"Select Col1 where Col1 is not null limit 1",0)
Refs: QUERY, ARRAYFORMULA, ROW.