Two New Functions; Let and Lambda

Two new Excel functions have recently attracted a lot of attention; these are:

  • LET (now available to Office 365 subscribers and
  • LAMBDA (currently available to Office Insiders program only)

Microsoft documentation can be found at: Announcing LET

Have you ever had to repeat the same expression multiple times within a formula, created a mega formula or wished that you had a way to reuse portions of your formula for easier consumption? With the addition of the LET function, now you can!

Introducing LET

LET allows you to associate a calculation or value in your formula with a name. It’s names except on a formula level.

LAMBDA: The ultimate Excel worksheet function

In December 2020, we announced LAMBDA, which allows users to define new functions written in Excel’s own formula language, directly addressing our second challenge. These newly defined functions can call other LAMBDA-defined functions, to arbitrary depth, even recursively. With LAMBDA, Excel has become Turing-complete. You can now, in principle, write any computation in the Excel formula language. LAMBDA is available to members of the Insiders: Beta program. The initial release has some implementation restrictions that we expect to lift in the future. We discussed LAMBDA and some of our research on spreadsheets in a sponsored video presented at POPL 2021.

Further documentation and lengthy user discussion on the LAMBDA function

Examples of the LET function from ablebits.com:

Using LET function in Excel with formula examples

Discussion of the advantahes and limitations of the LAMBDA function:

What Makes Excel’s Lambda Functions so Awesome (and what doesn’t)?

I will follow up in later posts with my own comments, including comparison with my VBA Eval function, and linking to similar, and better functionality with Python and pyxll.

Posted in Excel, UDFs | Tagged , , , , | 3 Comments

Python callable arguments from Excel

Many Scipy functions have “callable” arguments, i.e. functions that can be called, with the returned data used as input by the calling function. Types of callable arguments include:

  • Functions in the active Python module
  • Functions in any active loaded library
  • Lambda functions

When the callable argument is passed from Excel, using pyxll, the function name will be passed as a text string, which must be converted to a function object in the Python code. Different procedures are required for the three types of function listed above:

  • Functions in the active module can be called with the “globals” method (The globals () method returns the dictionary of the current global symbol table. )
  • Functions in active loaded libraries can be called with the “getattr” method (The getattr() method returns the value of the attribute of an object.)
  • Strings in lambda format can be converted to lambda functions with the eval() function

Examples of each of these methods are included in the code below:

import scipy as sp
import scipy.stats as stats
import numpy  as np

mods = {'np': np, 'stats': stats, 'sp': sp}

def GetCallable(func, mod = None):
    # Convert string to callable
    # Remove spaces and = from start of string
    func = func.replace('=', '')
    func = func.strip()
    
    # If string starts with 'lambda' convert ^ to **, then convert string to lambda function
    if func[0:6] == 'lambda':
        func = func.replace('^', '**')
        func = eval(func)
    # Else if module is not specified, convert string to function from globals
    elif mod is None:
        func = globals()[func]
    # or if mod is specified, convert mod string to module, then func string to function from mod 
    else:
        mod = mods[mod]
        func = getattr(mod, func)
    return func

The output from this function is a function object that can be passed to any function requiring callable arguments:

@xl_func
@xl_arg('x', 'numpy_array', ndim = 2)
@xl_arg('y', 'numpy_array', ndim = 2)
@xl_arg('rank', 'numpy_array<var>', ndim = 2)
@xl_arg('weigher', 'str')
@xl_arg('mod', 'str')
@xl_arg('additive', 'bool')
def py_weightedtau(x, y, rank = None, weigher = None, mod  = None, additive = None):
    """
Compute a weighted version of Kendall's :math:`\tau`.
   ...
Function argument descriptions
    ...
    """
    if weigher is not None: weigher = GetCallable(weigher, mod)
    
    kwargs = {'rank': rank, 'weigher': weigher, 'additive': additive}
    kwargs = {k:v for k,v in kwargs.items() if v is not None}

    return stats.weightedtau(x, y, **kwargs)[0]

Posted in Excel, Link to Python, NumPy and SciPy, PyXLL, UDFs | Tagged , , , , , , , | 1 Comment

Python optional arguments from Excel – Part 2

The previous post provided a method for using pyxll to pass optional arguments from Excel to Python whilst preserving the default values of any called Python function for arguments that were omitted in the Excel function.

One condition where this does not work is for Python functions where “None” is a valid argument, but it is not the default. For instance, many Scipy functions working with multi-dimension array input have an “axis” argument that allows “None” as input, but the default is 0. In this case the default value for the Excel function should be set to 0, and the data type set to variant. Before calling the Python function two additional steps are then required:

  • If the argument is a number, convert it from “float” to “int”.
  • If the argument is the string “None” convert it to the Python None object.

Typical code is shown below:

@xl_func
@xl_arg('a', 'numpy_array', ndim = 2)
@xl_arg('axis', 'var')
@xl_arg('dtype', 'str')
@xl_return('numpy_array')
def py_hmean(a, axis = 0, dtype = None):
    ...
    if axis != 'None': axis = int(axis)
    kwargs = {'axis': axis, 'dtype': dtype}
    kwargs = {k:v for k,v in kwargs.items() if v is not None}
    if axis == 'None': kwargs['axis'] = None
    res = stats.hmean(a, **kwargs)

Posted in Excel, Link to Python, NumPy and SciPy, PyXLL, UDFs, VBA | Tagged , , , , , | Leave a comment

Using Python optional arguments from Excel with pyxll

Python functions allow optional arguments to be specified by allocating a default value to each optional argument. The pyxll add-in allows Python functions called from Excel to work in the same way, so any argument with a default value may be omitted, in a similar way to using the “optional” key-word in VBA.

This raises the question, if an Excel function is being used to call a Python function with default arguments, and both functions require a default value to be specified, how do we ensure that the default values are consistent?

The obvious solution is to specify all the Excel defaults as “None”, so that the Python function applies its specified defaults; however this does not work. “None” is not the same as “nothing”, so the Python function treats the argument value as being “None”, rather than applying the default. An example is the Numpy “linspace” function, where the boolean “endpoint” argument has as default value of True, but setting all defaults to None will result in a default of False, so the resulting series will have one less value, with an increased space between the numbers.

Solutions that do work include:

  1. Assign all the Python function default values to the Excel function.
  2. Pass the optional arguments to the Excel function as a single dictionary, using the Python argument names. It is then not necessary to specify default values in the Excel function. Any missing arguments will then not be passed to the Python function, and the Python defaults will be used.
  3. Assign a default value of “None” to all the optional arguments in the Excel function, then form a dictionary in the Excel code, of any arguments that have a value other than “None”.

Option 1 provides simple coding, but requires extra work in extracting the correct default value from the documentation (which is not always clearly stated).

Option 2 is also simple to code, but does not allow the creation of help for each argument when using the Excel function wizard.

Option 3 requires some extra coding, but allows the Python help for each argument to be copied to the function docstring, from where it can be accessed in Excel using the function wizard.

Typical code for a Scipy Stats function using Option 3 is shown below:

# pyxll decorators, allowing the function to be called from Excel
@xl_func
@xl_arg('a', 'numpy_array', ndim = 1)
@xl_arg('numbins', 'int')
@xl_arg('defaultreallimits', 'numpy_array', ndim = 1)
@xl_arg('weights', 'numpy_array', ndim = 1)
@xl_return('numpy_array')
# assign the default None to all optional arguments
def py_relfreq(a, numbins = None, defaultreallimits = None, weights = None):

#  Help that will appear in the Excel function wizard
    """
Return a relative frequency histogram, using the histogram function.
    :param a:        Input array.
    :param numbins:        The number of bins to use for the histogram. Default is 10.
    :param defaultreallimits:        The lower and upper values for the range of the histogram. If no value is given, a range slightly larger than the range of the values in a is used. Specifically ``(a.min() - s, a.max() + s)``, where ``s = (1/2)(a.max() - a.min()) / (numbins - 1)``.
    :param weights:        The weights for each value in `a`. Default is None, which gives each value a weight of 1.0
    """

    #  create a dictionary of each optional argument name, with the argument value
    kwargs = {'numbins': numbins, 'defaultreallimits': defaultreallimits, 'weights': weights}

    # extract the arguments that have a value other than None
    kwargs = {k:v for k,v in kwargs.items() if v is not None}

    # call the function, passing the dictionary using **kwargs
    rf = stats.relfreq(a, **kwargs)

    # convert the results to a 2D array that can be passed back to Excel
    ncols = len(rf[0])
    binwidth = rf[2]
    nrf = np.zeros((2,ncols))
    nrf[0,:] = rf[0]
    nrf[1,0] = rf[1]+binwidth
    for i in range(1, ncols):
        nrf[1,i] = nrf[1,i-1] + binwidth
    return nrf 

Use of this function from the Excel function wizard is shown in the screen-shot below (click the image for full-size view):

This code will generate the correct function defaults, but creates a problem in the case where “None” is a valid option, but not the default, since entering None as the argument value will result in the default value being used.  The next post will look at how to handle that situation. 

Posted in Excel, Link to Python, PyXLL, UDFs | Tagged , , , , , , | 1 Comment

Units4Excel Update

As discussed here, I have updated the Units4Excel spreadsheet, with the list of non-SI units updated from the latest Wikipedia table. The EvalU user defined function (UDF) has also been modified with three output options, as shown in the screen-shot below:

The revised spreadsheet, including full open-source code, can be downloaded from:

Units4Excel.zip

Posted in Excel, Maths, Newton, UDFs, VBA | Tagged , , , , | 5 Comments