I am trying to calculate the percentage of hours people are participating and available. People are not always chosen to participate, but should not be penalized if they are available.
Column B is the participation percentage.
Row 2 is the master row with the total number of hours that were available, with their respective dates above in Row 1.
When a person is available but not selected, a lowercase X is placed in their row and on the date. Ideally, I would like "x" to take on the value of the master row cell of the same column, but retain the appearance of an x, so the range can easily be calculated.
The only way I can see doing this right now is making a separate column (hidden) that would contain an If/then? like: if I$7="x", then(sum(L7+I2) and I don't even know how I would do that, much less across multiple cells.
I thought this would be a cheesy workaround except sometimes the hours aren't always 3, sometimes they're 2.
=SUM(F7/(F2-(3*(COUNTIFS(F7:7,"x")))))
Rows 3 through 7 in the picture, need 100% in Column B.
Participation spreadsheet
You can use this user defined function. Copy this code into the Script Editor and save it. Then enter =total(row($F2)) in F2 and copy it down to all rows. It sends the row number to the function:
function total(ln) {
var ss=SpreadsheetApp.getActiveSpreadsheet()
var s=ss.getActiveSheet()
var lc=s.getLastColumn()//get the last column with data
var rng=s.getRange(2, 7, 1, lc-6).getValues()// get row 2 values
var rng1=s.getRange(ln, 7, 1, lc-6).getValues()// get values of current row
var count=0 //set count to 0
for(var i=0;i<rng[0].length;i++){
if(rng1[0][i]=="x"){// if value is x
var cnst=rng[0][i]//get row 2 value
count=count+cnst}//add to count
else{count=count+rng1[0][i]}}//if not x, added cell value to count
return count // return the total
}
The function will expand as new columns are added.
Related
What I want is for a single column (C-L respectively) to count exactly how many cells in their respective row match the same year (from P onward) as the labeled column. So L5(red) will count how many cells from P5-Z5 have "2022" and K8(pink) will count how many cells from P8-Z8 have "2021" in them.
I currently have the number manually entered into each cell, but would like to automate it so it will count the years on its own. Every string of formulas I have tried all come up as error. It was easier to get a second page to count the amount of cells that have a specific word. But now I can't get a single page to count how many cells contain the specific year date in it on its own page.
Here are various logic formulas I've tried. Each of them just comes up as error.
=COUNTIFS(!P5:cc5,(2022))
=COUNTIFS(!p5:cc5,"&2022")
=COUNTIFS(!p5:cc5,\<="2022")
=COUNTIFS(!p5:cc5,"\<=01/01/2022")
=COUNTIFS(!p5:cc5,"\>=01/01/2022",!p5:cc5,"\<01/01/2023")
=COUNTIF(!P5:cc,YEAR(2022))
=COUNTIFS(!P5:cc,"\<="&DATE(2022))
=(COUNTIF(!p5:cc,"\>="01/01/2022)-COUNTIF(!$p$5:$cc,"\>="01/01/2022))
This one is the formula I have for reading the second page to count how many times the specific name shows up. O5 is the cell with the name in it. So I was basing my year counting off this and trying to google my way through it.
=(COUNTIF('Queue List'!$B$3:$D$400,O5))
Sheet layout
As far as I understand you have 10 columns (C-L) that will have to find how many dates in P:CC are in year 2013,2014,2015... right? You can do it like with this formula in C5:
=COUNTIF(ARRAYFORMULA(YEAR($P5:$CC5)),2010+COLUMN())
You're "scanning" the year of the whole row with arrayformula, and then seeing if it matches 2022. In this case I changed 2022 with 2012+COLUMN() so you can drag it or copy and paste to the whole column and the number of column added to 2012 will "calculate" the corresponding year of each column
Another option is to create a whole array with one single formula in C5:
=MAKEARRAY(ROWS(P5:P),10,LAMBDA(r,c,IF(COUNTA(INDEX(P5:CC,r))=0,"",COUNTIF(ARRAYFORMULA(YEAR(INDEX(P5:CC,r))),2012+c))))
Obviously you can adapt it to a specific range. Right now it creates a "rectangle" of 10 columns wide (C-L) and to the bottom of the page (counting the rows between P5 to P (the end of the sheet). "r" and "c" are the number of the row and the column of each cell being calculated (C5 is Row 1, Column 1. D7 is Row 2, Column 3, etc). With INDEX you can select the row to count from the whole range (using that "r" that equals the row), and with c I use the same logic that with the other formula in order to add to 2012+1 in C, 2012+2 in D, 2012+3 in E, etc.
And COUNTA checks first if there is any value in that row, if it doesn't it leaves that row empty (so you won't have a bunch of unnecesary "0"
I need to enumerate rows in a google sheet to use as simple unique ids. When I add a new row, I want it to get assigned the next number that hasn't been used. The problem is that I need to be able to delete a row, and not have any of the ids change. So if I had rows 1 through 5 enumerated, and I deleted row 3, I would expect to have this:
DATA ID
A 1
B 2
D 4
E 5
I can easily make a function that enumerates numbers for each row, but I don't know how to make that number immutable once created the first time. Also, if I were to delete the last row (ID 5 above) and then add another row, I don't know how to ensure that the new row's id would be 6 instead of a new 5. Thank you.
In my case all I want is to be sure to have unique numbers that won't repeat even if rows or data get added or subtracted. So I just used this formula. In my case I only had a few hundred rows, but you could increase the "1000" to whatever power of ten is going to be bigger than your total number of rows:
=int((now() - DATE(2021, 7, 19)) * 86400) * 1000 + row()
As #MattKing pointed out, this will recalculate if you do something like insert a column before this function, or even just reopen the spreadsheet. So you can tweak the cell to be self-referential. It's not really recursive, but you do have to turn on "Iterative Calculation" for this to work. Go to:
File -> Speadsheet Settings -> Calculation -> set "Iterative Calculation" to "On". Then to be safe, you can set the "Max number of iterations" to 1 since this isn't actually recursive. Then use this cell formula but change both occurrences of the cell referenced in the VALUE() function (AL11 in the example below) to whatever cell this is in:
=IF(VALUE(AL11) > 0, AL11, INT((NOW() - DATE(2021, 7, 19)) * 86400) * 1000 + ROW())
My intention is to convert a single line of data into rows consist of a specific number of columns in Google Sheets.
For example, starting with the raw data:
A
B
C
D
E
F
1
id1
attr1-1
attr2-1
id2
attr2-1
attr2-2
And the expected result is:
(by dividing columns by three)
A
B
C
1
id1
attr1-1
attr1-2
2
id2
attr2-1
attr2-2
I already know that it's possible a bit manually, like:
=ARRAYFORMULA({A1:C1;D1:F1})
But I have to start over with it every time the target range is moved OR the subset size needs to be changed (in the case above it was three)!
So I guess there will be a much more graceful way (i.e. formula does not require manual update) to do the same thing and suspect ARRAYFORMULA() is the key.
Any help will be appreciated!
I added a new sheet ("Erik Help") where I reduced your manually entered parameters from two to one (leaving only # of columns to be entered in A2).
The formula that reshapes the grid:
=ArrayFormula(IFERROR(VLOOKUP(SEQUENCE(ROUNDUP(COUNTA(7:7)/A2),A2),{SEQUENCE(COUNTA(7:7),1),FLATTEN(FILTER(7:7,7:7<>""))},2,FALSE)))
SEQUENCE is used to shape the grid according to whatever is entered in A2. Rows would be the count of items in Row 7 divided by the number in A2 (rounded to the nearest whole number); and the columns would just be whatever number is entered in A2.
Example: If there are 11 items in Row 7 and you want 4 columns, ROUNDUP(11/4)=3 rows to the SEQUENCE and your requested 4 columns.
Then, each of those numbers in the grid is VLOOKUP'ed in a virtual array consisting of a vertical SEQUENCE of ordered numbers matching the number of data pieces in Row 7 (in Column 1) and a FLATTENed (vertical) version of the Row-7 data pieces themselves (in Column 2). Matches are filled into the original SEQUENCE grid, while non-matches are left blank by IFERROR
Though it's a bit messy, managed to get it done thanks to SEQUENCE() function anyway.
It constructs a grid by accepting number of rows/columns input, and that was exactly I was looking for.
For reference set up a sheet with the sample data here:
https://docs.google.com/spreadsheets/d/1p972tYlsPvC6nM39qLNjYRZZWGZYsUnGaA7kXyfJ8F4/edit#gid=0
Use a custom formula
Although you already solved this. If you are doing this kind of thing a lot, it could be beneficial to look into Apps Script and custom formulas.
In this case you could use something like:
function transposeSingleRow(range, size) {
// initialize new range
let newRange = []
// initialize counter to keep track
let count = 0;
// start while loop to go through row (range[0])
while (count < range[0].length){
// add a slice of the original range to the new range
newRange.push(
range[0].slice(count, count + size)
);
// increment counter
count += size;
}
return newRange;
}
Which works like this:
The nice thing about the formula here is that you select the range, and then you put in a number to represent its throw, or how many elements make up a complete row. So if instead of 3 attributes you had 4, instead of calling:
=transposeSingleRow(A7:L7, 3)
you could do:
=transposeSingleRow(A7:L7, 4)
Additionally, if you want this conversion to be permanent and not dependent on formula recalculation. Making it in run fully in Apps Script without using formulas would be neccesary.
Reference
Apps Script
Custom Functions
I am working on adding the time I spend on my habits using google sheets. If you look at this example sheet, I am keeping my individual habits in columns 3-8 (see the offsets on the first row).
To add the food related habits times (columns 5 and 6), I can use the range in offset function (see formulae in D17 below "Food").
The question is: how do I add the numbers for exercise and sleep (column offsets 4, 7, and 8)? The number of columns here could be 2, 3, or more! And they might not be consecutive.
Thanks for any pointers.
To sum entries of the rows whose columns are in the given array, I would use
=SUMPRODUCT(COUNTIF({5,8,9},COLUMN(D3:J3))*(D3:J3))
This is the formula for E18 in your spreasheet.
Since the columns might not be consecutive and there can be a variable number of them, I think it is appropriate to use an Apps Script custom function, and use the spread syntax to account for the variable number of columns.
Just open the script bound to your file, copy this function and save the project:
function HABIT_TOTALS(...habitIndexes) {
const sheet = SpreadsheetApp.getActiveSheet();
const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
let output = [];
for (let dayIndex = 0; dayIndex < 7; dayIndex++) {
let dayValue = 0;
habitIndexes.forEach(habitIndex => {
const columnIndex = headers.indexOf(habitIndex) + 1;
const dailyHabitValue = sheet.getRange(3, columnIndex).getValue();
const dayHabitValue = sheet.getRange(4 + dayIndex, columnIndex).getValue();
dayValue = Number(dayValue) + Number(dailyHabitValue) + Number(dayHabitValue);
});
output.push([dayValue]);
}
return output;
}
Notes:
This function can be used as any in-built formula from Sheets (e.g. =HABIT_TOTALS(4,7,8)).
This function gets, as arguments, the indexes of the habits to retrieve (in this case 4, 7, 8), to be found on the first row in the sheet.
It loops through all days of the week (dayIndex), returning the total amount for each day. Because of this, there's no need to drag the formula down.
For each day, it finds the column index based on the habit index provided as an argument, and adds the values for Daily and for the current day to the total value for the day.
After retrieving the total amount for the day, this value is pushed to output, the value returned by this function.
This function could be used for the Food habits, just changing the arguments: =HABIT_TOTALS(5,6), or for any other combination.
Reference:
Custom Functions in Google Sheets
Spread syntax (...)
For the calculation concerning food you can try in cell D18
=sum(filter(filter($D$3:$I$11, regexmatch($C$3:$C$11, "Daily|"&text($C18, "ddd"))), regexmatch($D$1:$I$1&"", "5|6")))
and fill down.
The numbers at the end refer to the colum numbers you have in row 1. So in E18 (Sleep and excercise) you would have
=sum(filter(filter($D$3:$I$11, regexmatch($C$3:$C$11, "Daily|"&text($C18, "ddd"))), regexmatch($D$1:$I$1&"", "4|7|8")))
Of course, it is also possible to write the last part in a cell and then refer to that cell. That would mean you can enter in E18
=sum(filter(filter($D$3:$I$11, regexmatch($C$3:$C$11, "Daily|"&text($C18, "ddd"))), regexmatch($D$1:$I$1&"", D$17)))
and fill down AND to the right.
See if that helps?
I am trying to generate a random Cell from specific range:
I need to each cell Row to generate a random selection from a a specific column (range)
below is a picture of my set up and my failed attempts:
You can do this like this:
Add a random number next to your data set using =RAND(). I've used column B, but you can put it wherever you like.
Add this formula to cells C2 to H2
=INDEX($A$2:$A$21,RANK.EQ(INDEX($B$2:$B$21,COLUMN()-2),$B$2:$B$21))
How it works:
RAND() returns a random number in the range [0..1) This is used as a random sort order for your data
Breaking down the formula:
COLUMN()-2 returns a sequential number 1..6 for columns C to H
INDEX($B$2:$B$21, ... ) returns the 1st to 6th number from the random number list
RANK.EQ( ... ,$B$2:$B$21) returns the position of the random number in the sorted random number list, 1..20.
=INDEX($A$2:$A$21, ... ) returns an item from your data set, based on the random rank from above.
Note: This will return a new randon sample each time Excel recalculates.
The only way to make a random selection that does not repeat is to make an array of integers, than randomize it, and than take out one by one.
For example you start with:
1 2 3 4 5 6 7
you randomize it (swap elements randomly)
2 4 5 1 7 3 6
than you take elements out one by one. (keep an index of how much elements you used in some cell)
Ok here is one trick, add another column with random numbers, than select both random column and range column and click the sort button (picture is from libreoffice but there is a similar button in excel) This will randomize your range column. Than you simply assign values to "w-1" : "w-6" like this =B2, =B3, =B4, =B5, =B6, =B7
I tried this and it works like you wanted, the only problem is it will shuffle values in your range column.