HiveBrain v1.2.0
Get Started
← Back to all entries
debugpythonMinor

Function that generates steps time series from user given values

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
generatesuserfunctiontimethatvaluesseriesfromgivensteps

Problem

I created simple function, that generate values of series representing repeating sequence of steps in time. User can define:

  • step values



  • width of the steps



  • how many times the step sequence should repeat



  • or the size of returned series



If the size is not defined, the size of returned data should be determined by number of repeats.

So the call

steps(2, [1, 2, 3], repeat=2)


should return

[1 1 2 2 3 3 1 1 2 2 3 3]


The function follows

def steps(step_width, values, repeat=1, size=None):
    """
    This function generates steps from given values.

    **Args:**

    * `step_width` - desired width of every step (int)

    * `values` - values for steps (1d array) 

    **Kwargs:**

    * `repeat` - number of step sequence repetions (int), this value is used,
      if the `size` is not defined

    * `size` - size of output data in samples (int), if the `size` is used,
      the `repeat` is ignored.

    **Returns:**

    * array of values representing desired steps (1d array)
    """
    try:
        step_width = int(step_width)
    except:
        raise ValueError('Step width must be an int.') 
    try:
        repeat = int(repeat)
    except:
        raise ValueError('Repeat arg must be an int.') 
    try:
        values = np.array(values)
    except:
        raise ValueError('Values must be a numpy array or similar.') 
    # generate steps
    x = np.repeat(values, step_width)          
    if size is None:
        # repeat according to the desired repetions
        x_full = np.tile(x, repeat)      
    else:
        try:
            repeat = int(repeat)
        except:
            raise ValueError('Repeat arg must be an int.') 
        # repeat till size is reached and crop the data to match size
        repeat = int(np.ceil(size / float(len(x))))
        x_full = np.tile(x, repeat)[:size]
    return x_full


I would appreciate any feedback. Especially I am not sure about effectivity of the error raising as it is implemented ri

Solution

Your type checks are distracting, they are probably better extracted to a separate function.

def check_type_or_raise(obj, expected_type, obj_name):
    if not isinstance(obj, expected_type):
        raise TypeError(
            "'{}' must be {}, not {}".format(
                obj_name,
                expected_type.__name,
                obj.__class__.__name__)


Note that this has slightly different behaviour than your code: you would e.g. accept a step_width of 2.5 and convert it to a 2. This is typically not what you want, so I think it is better to raise for such cases.

If you don't mind the conversion, then you probably don't need to wrap your calls to int() in a try and re-raise, as you will already get a nice error, e.g.:

>>> int('abc')
ValueError: invalid literal for int() with base 10: 'abc'


I would also refactor your code to have the output creation happen in a single, common place, after some manipulation of the arguments:

def steps(step_width, values, repeat=1, size=None):
    check_type_or_raise(step_width, int, 'step_width')
    check_type_or_raise(repeat, int, 'repeat')
    values = np.asarray(values)
    if size is not None:
        check_type_or_raise(size, int, 'size')
        # This does rounded up integer division without floating point
        repeat = (size - 1) // (len(values) * step_width) + 1
    return np.tile(np.repeat(values, step_width), repeat)[:size]

Code Snippets

def check_type_or_raise(obj, expected_type, obj_name):
    if not isinstance(obj, expected_type):
        raise TypeError(
            "'{}' must be {}, not {}".format(
                obj_name,
                expected_type.__name,
                obj.__class__.__name__)
>>> int('abc')
ValueError: invalid literal for int() with base 10: 'abc'
def steps(step_width, values, repeat=1, size=None):
    check_type_or_raise(step_width, int, 'step_width')
    check_type_or_raise(repeat, int, 'repeat')
    values = np.asarray(values)
    if size is not None:
        check_type_or_raise(size, int, 'size')
        # This does rounded up integer division without floating point
        repeat = (size - 1) // (len(values) * step_width) + 1
    return np.tile(np.repeat(values, step_width), repeat)[:size]

Context

StackExchange Code Review Q#163081, answer score: 5

Revisions (0)

No revisions yet.