Interval Filter using PyXLL Oct 2024

Between functions
I built a set of functions demonstrating dynamic filtering and sorting using Excel and PyXLL. The functions determine whether a value is within a numeric interval (closed, open, or any combination).

Video: Interval Filter using PyXLL

The first part of the video shows the function in use, filtering a composite range of user-entered values.

The second part of the video is more interesting.

I have a list of 10 random probabilities which are generated using a PyXLL function, mix_func(). The list is returned to the spreadsheet — as a single object — in a single cell.

Then the filter_int_compare function returns only those values between 0.0 and 0.5, ‘CO’.

Notice that the dynamic ranges are dynamically formatted, using PyXLL.

Here are introductory screenshots.




These would have engineering use in filtering a list of values that fit (or do not fit) within a given range.

I start with a straightforward (naive) approach to writing the function and then add a more useful (maybe more sophisticated) approach to writing it.

I offer a look at my code.

Please note that the code shown is neither succinct nor final — it is written in a ‘long-hand’ expository form, with the intent being that it can be easily followed.

Naive

The first function is a boolean PyXLL function that returns a literal (closed, closed) interval check.

The second function is a boolean PyXLL function that does the logical complement of the literal (closed, closed) interval check.

@xl_func(“float bracket_number1, float bracket_number2, float number_entry: bool”)
def between_compare(bracket_number1, bracket_number2, number_entry):

@xl_func(“float bracket_number1, float bracket_number2, float number_entry: bool”)
def not_between_compare(bracket_number1, bracket_number2, number_entry):

from numbers import Number
@xl_func("float bracket_number1, float bracket_number2, float number_entry: bool")
def between_compare(bracket_number1, bracket_number2, number_entry):
    """NAIVE Given an Excel cell, filter the numeric value, retaining only if within the interval

    Args:
        bracket_number1: one extreme value in the interval
        bracket_number2: the other extreme value in the interval
        number_entry: a single Excel cell to be filtered

    Returns:
        Value if it meets the comparison specification. Otherwise, the string 'empty'

    """
    if not (isinstance(bracket_number1, Number) 
            and isinstance(bracket_number2, Number) 
            and isinstance(number_entry, Number)):
        raise ValueError("Expected all numeric values")
    if (bracket_number1 < number_entry < bracket_number2 or
        bracket_number2 < number_entry < bracket_number1):
        return True
    else:
        return False
    

@xl_func("float bracket_number1, float bracket_number2, float number_entry: bool")
def not_between_compare(bracket_number1, bracket_number2, number_entry):
    return not between_compare(bracket_number1, bracket_number2, number_entry)
    """complement of NAIVE between_compare()"""

~~~
Maybe more sophisticated

I added an input parameter to allow closed or open intervals (any combination).

Then I built a function abbreviation: ‘interval_compare’ is abbreviated to ‘int_comp’.

Then I built a function, ‘filter_int_comp’ incorporating sorted results and/or unique results.

Guide to interval types:

The interval type may be specified using either the character designation or the symbolic designation.

Character Symbol Meaning
CC [] closed at each end
CO [) closed at small number
open at large number
OC (] open at small number
closed at large number
OO () open at each end
@xl_func(“float bracket_number1, float bracket_number2, str interval_type, float number_entry: bool”, volatile=False)
def interval_compare(bracket_number1, bracket_number2, interval_type, number_entry):

@xl_func(“float bracket_number1, float bracket_number2, str interval_type, var number_entry_range[][], bool sortQ, bool reverseQ, bool unique_onlyQ: var”, volatile=False)
def filter_int_comp(bracket_number1, bracket_number2, interval_type, number_entry_range, sortQ=False, reverseQ=False, unique_onlyQ=False):

@xl_func(“int x: object”)
def mix_func(x):

@xl_func(“object x: var”)
def unmix_func(x):

from numbers import Number
@xl_func("float bracket_number1, float bracket_number2, str interval_type, float number_entry: bool", volatile=False)
def interval_compare(bracket_number1, bracket_number2, interval_type, number_entry):
    """Given an Excel cell, filter the numeric value, retaining only if within the interval

    Args:
        bracket_number1: one extreme value in the interval
        bracket_number2: the other extreme value in the interval
        interval_type: type of interval closure (closed/open and combinations)
        number_entry: a single Excel cell to be filtered
        sortQ: a boolean, True indicates an ascending sort
        reverseQ: a boolean, True indicates sorted descending
        unique_onlyQ: a boolean, indicates removal of duplicates

    Returns:
        List of values meeting the comparison specification, if any. Otherwise, the string 'empty'
        
    Raises:
        ValueError("Expected 2 numeric values and string interval_type")

    """
    
    if not (isinstance(bracket_number1, Number) 
            and isinstance(bracket_number2, Number) 
            and isinstance(interval_type, str)):
        raise ValueError("Expected 2 numeric values and string interval_type")

    ivupper = interval_type.upper()

    if not(ivupper in ['[]','[)','(]','()','CC','CO','OC','OO']):
        raise ValueError("Interval_type must be \n['[]','[)','(]','()','CC','CO','OC','OO']")
                                   
    if ivupper in ['[]','CC']:
        if (bracket_number1 < number_entry < bracket_number2 or
            bracket_number2 < number_entry < bracket_number1):
            return True
    elif ivupper in ['[)','CO']:
        if (bracket_number1 < number_entry <= bracket_number2 or
            bracket_number2 < number_entry <= bracket_number1):
            return True
    elif ivupper in ['(]','OC']:
        if (bracket_number1 <= number_entry < bracket_number2 or
            bracket_number2 <= number_entry < bracket_number1):
            return True
    elif ivupper in ['()','OO']:
        if (bracket_number1 <= number_entry <= bracket_number2 or
            bracket_number2 <= number_entry <= bracket_number1):
            return True
    else:
       return False
    

# this is a boolean PyXLL function suitable for a single value as input
@xl_func("float bracket_number1, float bracket_number2, str interval_type, float number_entry: bool", volatile=False)
def int_comp(bracket_number1, bracket_number2, interval_type, number_entry):
    return interval_compare(bracket_number1, bracket_number2, interval_type, number_entry)
    """alias for interval_compare()"""
# this is a boolean PyXLL function suitable for a range of values as input
@xl_func("float bracket_number1, float bracket_number2, str interval_type, var number_entry_range[][], bool sortQ, bool reverseQ, bool unique_onlyQ: var", volatile=False)
def filter_int_comp(bracket_number1, bracket_number2, interval_type, number_entry_range, sortQ=False, reverseQ=False, unique_onlyQ=False):
    """Given an Excel range, filter the numeric values, retaining only those that are within the interval

    Args:
        bracket_number1: one extreme value in the interval
        bracket_number2: the other extreme value in the interval
        interval_type: type of interval closure (closed/open and combinations)
        number_entry_range: an Excel RANGE to be filtered
        sortQ: a boolean, True indicates sorted ascending
        reverseQ: a boolean, True indicates sorted descending
        unique_onlyQ: a boolean, indicates removal of duplicates

    Returns:
        List of values meeting the comparison specification, if any. Otherwise, the string 'empty'
        
    Raises:
        string 'Error in multiple range input': If the function fails.

    """

    ret = []
    rg_flattened = itertools.chain.from_iterable(number_entry_range)
    for e in rg_flattened:
        e_value = None
        if e == '' or e is None:
            # no conversion
            e_value = ''
        else:
            try:
                e_value = e.Value
            except:
                e_value = e
        if type(e_value) == float:
            comparison = int_comp(bracket_number1, bracket_number2, interval_type, e_value)
            if comparison:
                ret.append(e_value)
    
    if unique_onlyQ:
        ret = list(set(ret))
    if sortQ:
        ret.sort()
    if reverseQ:
        ret.reverse()

    if len(ret) == 0:
        return "empty"
    else:
        return ret


from random import random
# @xl_func("int x: object")
@xl_func("int x: object")
def mix_func(x):
    if x == 0:
        # returned as a number to Excel
        return 0

    # return a list of values as an 'object'
    array = [random() for i in range(x)]
    return array


@xl_func("object x: var")
def unmix_func(x):
    try:
        return [[e] for e in x]
    except:
        return 'empty'

~~~

Here is the video.

Leave a Reply

Your email address will not be published. Required fields are marked *