-
-
Save jimpea/4bf717e62d4ac432b23134c59965a8aa to your computer and use it in GitHub Desktop.
| /* | |
| Append two ranges horizontally. | |
| Inputs: | |
| - range1: the first range | |
| - range2: the second range | |
| - default: the value entered into missing rows. | |
| Return: The merged ranges, with empty rows filled with the default value. Missing | |
| value within either of the two ranges filled with zeros (0). The number of rows | |
| equals that of the range with the greatest number of rows and the number of columns | |
| is the sum of the columns in each input range. | |
| Example: Merge the ranges E1:F3 and H1:I2 with cells in the missing row marked as "na". | |
| =APPENDRANGEH(E1:F3,H1:I2,"na") | |
| */ | |
| APPENDRANGEH = LAMBDA(range1, range2, default, | |
| LET( | |
| rows1, ROWS(range1), | |
| rows2, ROWS(range2), | |
| cols1, COLUMNS(range1), | |
| cols2, COLUMNS(range2), | |
| rowindex, SEQUENCE(MAX(rows1, rows2)), | |
| colindex, SEQUENCE(1, cols1 + cols2), | |
| result, IF( | |
| colindex <= cols1, | |
| INDEX(range1, rowindex, colindex), | |
| INDEX(range2, rowindex, colindex - cols1) | |
| ), | |
| IFERROR(result, default) | |
| ) | |
| ); | |
| /* | |
| Append two ranges vertically. | |
| Inputs: | |
| - range1: the first range | |
| - range2: the second range | |
| - default: the value entered into missing rows. | |
| Return: The merged ranges, with empty columns filled with the default value. Missing | |
| values within either of the two ranges filled with zeros (0). The number of columns | |
| equals that of the range with the greatest number of columnss and the number of rows | |
| is the sum of the rows in each input range. | |
| Example: Merge the ranges A1:C2 and A4:B5 with cells in the missing column in the second | |
| range marked as "missing". | |
| =APPENDRANGEV(A1:C2,A4:B5,"missing") | |
| range(A1:C2) | |
| 1 2 3 | |
| 4 5 6 | |
| range(A4:B5) | |
| 10 | |
| 30 40 | |
| gives the following output: | |
| 1 2 3 | |
| 4 5 6 | |
| 10 0 missing | |
| 30 40 missing | |
| Note the second range has one fewer columns than the first, these positions are marked by | |
| 'missing' in the output'. The second range also only has one value in the first row. This | |
| is replaced by a zero(0) in the output. | |
| */ | |
| APPENDRANGEV = LAMBDA(range1, range2, default, | |
| LET( | |
| rows1, ROWS(range1), | |
| rows2, ROWS(range2), | |
| cols1, COLUMNS(range1), | |
| cols2, COLUMNS(range2), | |
| rowindex, SEQUENCE(rows1 + rows2), | |
| colindex, SEQUENCE(1, MAX(cols1, cols2)), | |
| result, IF( | |
| rowindex <= rows1, | |
| INDEX(range1, rowindex, colindex), | |
| INDEX(range2, rowindex - rows1, colindex) | |
| ), | |
| IFERROR(result, default) | |
| ) | |
| ); | |
| /* | |
| Convert an m x n range to an m*n x 1 range | |
| Example: flatten the range in A1# | |
| =FLATTEN(A1#) | |
| inputs: | |
| - array: the cell range to flatten | |
| Return:: | |
| - A list of the cells in the array. Empty cells in the range | |
| include as zero (0) in the list. | |
| */ | |
| FLATTEN = LAMBDA(array, | |
| LET( | |
| rows_, ROWS(array), | |
| cols_, COLUMNS(array), | |
| seq_, SEQUENCE(rows_ * cols_, 1, 0, 1), | |
| row_, INT(seq_ / cols_), | |
| col_, MOD(seq_, cols_), | |
| INDEX(array, row_ + 1, col_ + 1) | |
| ) | |
| ); | |
| /* Example least squares fit to a first order polynomial. | |
| Inputs: Note the input_ and response_ arrays correspond to the 'standard curve' | |
| used to build the linear model. Both ranges must be equal length. | |
| - input_: the input ('x values') | |
| - response_: the response to the input ('y values') | |
| - test_: m x 1 array of test inputs | |
| Return: | |
| - The response of the linear model to the test_ inputs. | |
| */ | |
| LINFIT = LAMBDA(input_, response_, test_, | |
| iferror( | |
| LET( | |
| total_rows_, COUNT(test_), | |
| ones_, SEQUENCE(total_rows_, 1, 1, 0), | |
| coeffs_, LINEST(response_, input_), | |
| INDEX(coeffs_, 1, 2) * ones_ + INDEX(coeffs_, 1, 1) * test_ | |
| ), | |
| "error: inputs and response must be same size") | |
| ); | |
| /* | |
| implementation of Simpson's rule to calculate definite integral | |
| \int_a^b f(x) dx \approx \frac{b - a}{6}\left[f(a) + 4f(\frac{a + b}{2}) + f(b)\right] | |
| Inputs: | |
| - lower: lower limit | |
| - upper: upper limit | |
| - dx: interval | |
| - f: function that maps the array to output | |
| Example: Calculate the area under the curve (x+1)/(x+2), defined by the lambda function, | |
| from the lower, upper and dx in cells O6, O7 and O8. | |
| =SIMPSON(O6,O7,O8,LAMBDA(n, (n + 1)/ (n + 2))) | |
| */ | |
| SIMPSON = LAMBDA(lower,upper,dx,f, | |
| LET( | |
| seq_,SEQUENCE(1+(upper-lower)/dx,1,lower,dx), | |
| REDUCE( | |
| 0, | |
| seq_, | |
| LAMBDA(acc,b, | |
| acc + | |
| ((dx) / 6) * | |
| (f(b - dx) + 4 * f((2 * b - dx) / 2) + f(b)) | |
| ) | |
| ))); | |
| /* | |
| Remove blanks cells from a single row or column. | |
| Input: | |
| array_list: a list either m x 1 or 1 x n. | |
| Return: | |
| list with blanks removed | |
| */ | |
| NOBLANK = LAMBDA(array_list, | |
| FILTER(array_list, | |
| array_list <> "", | |
| "" | |
| ) | |
| ); | |
| /* | |
| Example factorial implementation using recursion | |
| Inputs: | |
| - n: the integer argument | |
| Return: | |
| - The factorial of the input n | |
| Of course, Excel already has a FACT function, which works with arrays | |
| as the input. This version only works with a single value: | |
| Example, get the factorial of the integer in cell A2: | |
| =jp.MYFACT(A2) | |
| To apply this function to an array in cell A2#, wrap it into a MAP | |
| function: | |
| =LAMBDA(array, MAP(array, LAMBDA(n, jp.MYFACT(n))))(A2#) | |
| */ | |
| MYFACT = LAMBDA(n, IF(n < 2, 1, n * MYFACT(n - 1))); | |
| /* Example fibonacci implementation using recursion | |
| F_0 = 0, F_1 = 1, F_n = F_{n-1} + F_{n-2} | |
| Inputs: | |
| - n1_: The first initial term F_0 | |
| - n2_: The second initial term F_1 | |
| - c_: the number of times to count up the sequence. | |
| Example: to find the third Fibonacci number starting from 0, 1: | |
| = jp.MYFIB(0,1,n) | |
| As with the MYFACT function, wrap this in a MAP function to work with an array | |
| for instance for an array in cells A4:A11: | |
| =LAMBDA(array, MAP(array, LAMBDA(n, jp.MYFIB(0,1,n))))(A4:A11) | |
| */ | |
| MYFIB = LAMBDA(n1_, n2_, c_, | |
| IF(c_ < 1, n1_ + n2_, MYFIB(n2_, n1_ + n2_, c_ - 1)) | |
| ); | |
| /* | |
| Implementation of Markov chain | |
| Inputs: | |
| m: Left Stochastic matrix that describes the transitions of a Markov Chain. | |
| v: initial vector in chain | |
| n: length of chain | |
| Return: | |
| The vector in the state reached after n interations | |
| Example: | |
| With a 3 by 3 matrix defined in $B$2:$D$4 and a 3 by 1 vector in $B$7:$B$9, | |
| Using 3 iterations, the following returns a 3 by 1 vector: | |
| =MARKOV($B$2:$D$4, $B$7:$B$9, 3) | |
| */ | |
| MARKOV = Lambda(m, v, n, | |
| if(n=0, | |
| v, | |
| Markov(m, mmult(m, v), n-1))); | |
| // Return a version 4 UUID (aka GUID) | |
| // example from <https://stackoverflow.com/questions/7031347> | |
| // The first character of the third group is always 4 to | |
| // signify a V4 UUID per RFC 4122. The first character of the | |
| // fourth group is always between 8 and b, also per RFC 4122. | |
| UUID = LAMBDA( | |
| LOWER( | |
| CONCATENATE( | |
| DEC2HEX(RANDBETWEEN(0,4294967295),8), | |
| "-", | |
| DEC2HEX(RANDBETWEEN(0,65535),4), | |
| "-", | |
| DEC2HEX(RANDBETWEEN(16384,20479),4), | |
| "-", | |
| DEC2HEX(RANDBETWEEN(32768,49151),4), | |
| "-", | |
| DEC2HEX(RANDBETWEEN(0,65535),4), | |
| DEC2HEX(RANDBETWEEN(0,4294967295),8) | |
| ) | |
| ) | |
| ); | |
| /* | |
| Fold up a list to an array -- for instance a list of 12 values | |
| can fold into a 3 by 4, 4 by 3, 2 by 6, 6 by 2, 1 by 12 or 12 by 1 array. | |
| Inputs: | |
| data - a list of values. This must contain rows * cols elements. | |
| rows - number of rows in the output array | |
| cols - number of cols in the output array | |
| Return: | |
| an array rows x cols | |
| Raises: | |
| #VALUE! error on worksheet if data does not contain rows * cols | |
| elements. | |
| */ | |
| fold = lambda(data, rows, cols, | |
| let( | |
| lvalues_, sequence(rows, cols, 0, 1), | |
| larray_, sequence(rows*cols, 1, 0, 1), | |
| xlookup(lvalues_, larray_, data) | |
| )); | |
| /* | |
| The cumulative values of an nx1 array | |
| inputs: | |
| array_, an n x 1 array | |
| return: An nx1 array of cumulative values derived from the input | |
| Example: calculate the cumulative values of the array in cells | |
| A1:A5: | |
| =CUMULATOR(A1:A5) | |
| */ | |
| CUMULATOR = LAMBDA(array_, | |
| LET( | |
| seq_,SEQUENCE(COUNT(array_),1,1,1), | |
| MAP( | |
| seq_, | |
| LAMBDA(n,SUM(INDEX(array_,SEQUENCE(n,1,1,1)))) | |
| ) | |
| ) | |
| ); | |
| // mark data with user-defined upper and lower limits | |
| // | |
| // inputs: | |
| // data: numerical array | |
| // lower: the lower limit | |
| // upper: the upper limit | |
| // return: | |
| // array of shape(data), elements set to -1, 0 or 1 according to | |
| // less than lower limit, between limits or more that upper limit | |
| MARKLIMITS = LAMBDA(data,lower, upper, | |
| LET( | |
| mark, 0, | |
| map(data, lambda(x, if(x > upper, 1, if(x < lower, -1, 0)))) | |
| ) | |
| ); | |
| // Mark data with upper and lower limits | |
| // Assume: | |
| // - the data is normally distributed | |
| // - the data is numeric | |
| // - the data is in a contiguous range (array) | |
| // - the data is normally distributed. | |
| // If not normally distributed, set your own limits | |
| // and use the MARKLIMITS formula instead | |
| // | |
| // Depends on the MARKLIMITS formula | |
| // | |
| // Inputs: | |
| // data: numerical array | |
| // alpha: Defines the uppar and lower limit | |
| // Return: | |
| // array of same dimension as input data marked | |
| // 1: over upper limit, 0: between limits, -1: below lower limit | |
| // | |
| // Example: | |
| // `=mark.limits.2T(B18#, D16)` | |
| // data in cell `B18#`, the alpha value in cell `D16` | |
| // | |
| // for instance, | |
| // set alpha = 0.1 to give the 10% - 90% interval | |
| // set alpha = 0.05 to give the 5% - 95% interval | |
| // set alpha = 0.025 to give the 2.5% - 97.5% interval | |
| MARKLIMITS2T = LAMBDA(data,alpha, | |
| LET( | |
| mean, average(data), | |
| zl, norm.inv(alpha, 0, 1), | |
| zu, norm.inv(1 - alpha, 0, 1), | |
| lower_limit, mean + zl * STDEV.S(data), | |
| upper_limit, mean + zu * STDEV.S(data), | |
| MARKLIMITS(data, lower_limit, upper_limit) | |
| //CHOOSE({1,2, 3, 4}, zl, zu, lower_limit, upper_limit) | |
| ) | |
| ); | |
| //Count the number of repeated values in a list. | |
| //Assumes the list is vertical. | |
| // | |
| // inputs: | |
| // range: a | |
| // output: | |
| // A. n x 2 sorted list of the unique values in the input list and the counts. | |
| // | |
| // Example: | |
| // =count_uniques(tbl_names[initials]) | |
| // | |
| UNIQUE_COUNTS = LAMBDA(range, | |
| LET( | |
| uniques, SORT(UNIQUE(range)), | |
| counts, MAP(uniques, LAMBDA(s, SUM(IF(s = range, 1, 0)))), | |
| SORTBY(CHOOSE({1, 2}, uniques, counts), counts, -1) | |
| ) | |
| ); | |
| // Filter a list by their multiplicity, filtered by a | |
| // limiting value | |
| // | |
| // assumes: the input list is vertical n x 1 | |
| // | |
| // inputs: | |
| // range: the input array (n x 1) | |
| // cutoff: The lower limit cutoff | |
| // | |
| // returns: | |
| // n X 2 array from the range giving the multiplicity, ordered | |
| // according to multiplicity. | |
| // | |
| // example: | |
| // List the repleated elements in a list of names, restricting | |
| // to multiplicity above 2: | |
| // `=jp.FILTER_UNIQUE_COUNTS(tbl_names[fname], 2)` | |
| FILTER_UNIQUE_COUNTS = LAMBDA(range, cutoff, | |
| LET( | |
| input_array, UNIQUE_COUNTS(range), | |
| FILTER(input_array, CHOOSECOLS(input_array,2) > cutoff))); | |
| // Output data to given number of significant figures | |
| // | |
| // inputs: | |
| // data: An excel range containing numerical data | |
| // sig_figs: number of sugnificant figures | |
| // return: the data limited to the given number of significant figures | |
| // | |
| // Example: | |
| // Output the data in range 'data_' to the number of significant figures | |
| // given in 'sig_figs' | |
| SIG_FIGS = LAMBDA(data, sig_figs, | |
| ROUND(data, sig_figs - INT(LOG10(ABS(data))) | |
| ) | |
| ); | |
| newton_raphson = LAMBDA(guess, afun, delta, | |
| /* Newton Raphson method for estimating roots. **Note** Does | |
| not work for complex roots. | |
| args: | |
| guess: user guess for the root | |
| afun: a LAMBDA expression that takes one argument | |
| delta: serves both as the calculation cut-off limlit and | |
| the delta interval to use in calculating the function | |
| derivative | |
| return: | |
| The best guess for the root. | |
| Example: | |
| This expression returns a root for the expression passed as the LAMBDA | |
| expression (-3.23606798) | |
| =newton_raphson(-5, LAMBDA(x, x^2 + 2*x -4), 0.000011) | |
| For the other root (1.23067978): | |
| =newton_raphson(2, LAMBDA(x, x^2 + 2*x -4), 0.000011) | |
| */ | |
| LET( | |
| f1_, afun(guess), | |
| f2_, (afun(guess + delta) - f1_)/delta, | |
| new_guess_, guess - f1_/f2_, | |
| IF(ABS(f1_) < delta, new_guess_, newton_raphson(new_guess_, afun, delta)) | |
| ) | |
| ); | |
| /* | |
| Normalise a matrix | |
| Input: | |
| - M: range of numerical data | |
| Return: | |
| - The data in M normalised to mean:0 and stdev: 1 | |
| */ | |
| mnorm = LAMBDA(M, | |
| LET( | |
| means, BYCOL(M, LAMBDA(col, AVERAGE(col))), | |
| stdevs, BYCOL(M, LAMBDA(col, STDEV.S(col))), | |
| (M - means) / stdevs | |
| ) | |
| ); | |
| /* | |
| Euclidian distance | |
| Input: | |
| - P: a data point from a row of numeric data | |
| Return: | |
| - The Euclidiean distance between the point and the origin | |
| */ | |
| EuclidianD = LAMBDA(P, SQRT(SUM(P ^ 2))); | |
| /* | |
| Unpivot an Excel table | |
| Users may need to collect data directly into an excel range using the intersection | |
| between column and rows to capture information. For instance a microtitre plate | |
| has rows labelled A..H and columns labelled 1..12 with data collected on the (row, column) | |
| pairs. | |
| Inputs: | |
| input: An excel range including the column titles and rows. | |
| Return: The pivoted record putting each (row, column, data) record | |
| On eache line. | |
| Example: | |
| `=unpivot(A3:H6)` | |
| Here, the column headers are in cells A4:A6 and the row headers in cells A4:C4. | |
| Note that the upperleft corner cell is blank. The data is in cells B4:H6. This | |
| gives 21 cells of data -- one for each row, column pair. The output includes a | |
| a header row ["row", "col", "data"]. The user can now select the formula cell | |
| and insert a pivot table | |
| */ | |
| unpivot = LAMBDA(input, | |
| let( | |
| data, take(input, -(ROWS(input)-1), -(COLUMNS(input) - 1)), | |
| data_column_count, COLUMNS(data), | |
| data_row_count, ROWS(data), | |
| row_sequence, SEQUENCE(data_row_count, 1, 1, 1), | |
| col_sequence, SEQUENCE(data_column_count, 1, 1, 1), | |
| row_names, INDEX(input, row_sequence + 1, 1), | |
| col_names, INDEX(input, 1, col_sequence + 1), | |
| data_count, data_column_count * data_row_count, | |
| data_sequence, SEQUENCE(data_count, 1, 0, 1), | |
| stacked, hstack( | |
| index(row_names, int(data_sequence/data_column_count) + 1), | |
| index(col_names, MOD(data_sequence, data_column_count) + 1), | |
| tocol(data) | |
| ), | |
| headers, {"row","col","data"}, | |
| vstack(headers, stacked) | |
| ) | |
| ); | |
| /* find common elements in two ranges | |
| Args: | |
| range_a: the first range to test | |
| range_b: the second range to test | |
| Return: | |
| List of common elements | |
| */ | |
| common=LAMBDA(range_a, range_b, | |
| LET( | |
| message, "not_found", | |
| list_a, trim(TOCOL(range_a)), | |
| list_b, trim(TOCOL(range_b)), | |
| mapped, MAP(list_a, LAMBDA(test, FILTER(list_b, list_b = test, message))), | |
| unique(FILTER(mapped, mapped <> message)) | |
| ) | |
| ); | |
| // Interpolate for y between two points (ax, ay), (bx, by) at an intermediate | |
| // value x | |
| // Assumes: | |
| // bx != ax | |
| // by != ay | |
| // bx - ax != 0 | |
| interpy = lambda(ax,ay, bx, by, x, | |
| let( | |
| da, bx - ax, | |
| db, by - ay, | |
| slope, db/da, | |
| ay + (x - ax)*slope | |
| ) | |
| ); | |
| // Interpolate for x between two points (ax, ay), (bx, by) at an intermediate | |
| // value y | |
| // Assumes: same as for interpy | |
| interpx = lambda(ax, ay, bx, by, y, | |
| let( | |
| da, bx - ax, | |
| db, by - ay, | |
| slope, db/da, | |
| ax + (y - ay)/slope | |
| ) | |
| ); |
for more precise, the function SIMPSON must be
SIMPSON = LAMBDA(lower,upper,dx,f,
LET(
seq_,SEQUENCE(0+(upper-lower)/dx,1,lower+dx,dx),
REDUCE(
0,
seq_,
LAMBDA(acc,b,
acc +
((dx) / 6) *
(f(b - dx) + 4 * f((2 * b - dx) / 2) + f(b))
)
)));
Thanks a lot for sharing.
For the newton_raphson(), it might be useful to enclose the test in an ABS function to ensure we are close to zero and not only inferior to delta:
Instead of
IF(f1_ < delta ...
read
IF(ABS(f1_ < delta)...
Best Regards
Thanks @Excali78 for the feedback, I have updated the gist.
Hi jimpea,
You might have a look to Ismail HOSEN video and link to his lambda functions in the comments section, REPEATEACHROWNTIMES() and VSTACKNTIMES(): https://www.youtube.com/watch?v=oP82s9roDuQ
I join you 2 LAMBDAs that might interest your readers, you migth adapt / translate the parameters (now in French) and add them to your repository.
ROUNDTOSUM(myTable, [targetTotal], [RoundingStep]) : rounds a table to a target total with a certain rounding step. It rounds with the greatest remainder method, and in cas of ex-aequos, it prioritises small absolute values (greatest relative errors) :
=LAMBDA(maTable,[totalCible],[pasArrondi],
LET( pas,IF(ISOMITTED(pasArrondi),1,pasArrondi),
somTable,SUM(maTable),
totCib,MROUND(IF(ISOMITTED(totalCible), somTable,totalCible),pas),
nbElements,ROWS(maTable)COLUMNS(maTable),
homothetie,maTable(totCib/somTable),
arrondiInf,pas*INT(homothetie/pas),
nbPasADistribuer, ROUND((totCib-SUM(arrondiInf))/pas,0),
restesAvecTableEtIndex,HSTACK(TOCOL(homothetie-arrondiInf),TOCOL(maTable),SEQUENCE(nbElements)),
restesTries,SORTBY(restesAvecTableEtIndex,INDEX(restesAvecTableEtIndex,0,1),-1,INDEX(restesAvecTableEtIndex,0,2),1),
restesTriesAvecPas,HSTACK(restesTries,IF(SEQUENCE(nbElements)<=nbPasADistribuer,1,0)),
indicesTriesAvecPas,SORTBY(restesTriesAvecPas,INDEX(restesTriesAvecPas,0,3)),
restesAvecTableEtIndex
))
The 2nd formula creates a rounded table so as to match the rows and columns subtotals of a contingency table (expected values):
PRORATA.RC(rowOfColumnsTotals, columnOfRowsTotals, nDigits)
=LAMBDA(ligneTotauxColonnes,colonneTotauxLignes,nDigits,
LET(total,SUM(ligneTotauxColonnes),
nCol,COLUMNS(ligneTotauxColonnes),
IF(total<>SUM(colonneTotauxLignes),"sous-totaux LC incompatibles entre eux !",
LET(matrice,ligneTotauxColonnes*colonneTotauxLignes/total,
MMULT(ROUND(MMULT(matrice,MINTEG(nCol)),nDigits),MINVERSE(MINTEG(nCol)))))))
Helper function:
MINTEG(n) : returns a matrix of dimension n for computing the integration/cumulative sum of a matrix of n columns. Basically, it returns a triangular matrix with 1's and 0's.
=LAMBDA(n,LET(dim,n,--(SEQUENCE(,dim,1)>=SEQUENCE(dim,,1))))
Hope this helps,
Thierry
Really interesting work! Thanks for sharing.