Skip to content

Commit

Permalink
Merge pull request #231 from sciris/rc1.2.3
Browse files Browse the repository at this point in the history
Rc1.2.3
  • Loading branch information
cliffckerr committed Aug 27, 2021
2 parents 292c275 + fd80c15 commit b4e0363
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 22 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ All notable changes to this project will be documented in this file.
By import convention, components of the Sciris library are listed beginning with ``sc.``, e.g. ``sc.odict()``.


Version 1.2.3 (2021-08-27)
--------------------------
#. Fixed a bug with ``sc.asd()`` failing for ``verbose > 1``. (Thanks to Nick Scott and Romesh Abeysuriya.)
#. Added ``sc.rolling()`` as a shortcut to pandas' rolling average function.
#. Added a ``die`` argument to ``sc.findfirst()`` and ``sc.findlast()``, to allow returning no indices without error.


Version 1.2.2 (2021-08-21)
--------------------------

Expand Down
2 changes: 1 addition & 1 deletion sciris/sc_asd.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ def consistentshape(userinput, origshape=False):
flag = '--' # Marks no change
if np.isnan(fvalnew):
if verbose >= 1: print('ASD: Warning, objective function returned NaN')
if verbose > 0 and not (count % int(1.0/verbose)): # Print out every 1/verbose steps
if verbose > 0 and not (count % max(1, int(1.0/verbose))): # Print out every 1/verbose steps
orig, best, new, diff = ut.sigfig([fvalorig, fvalold, fvalnew, fvalnew-fvalold])
print(offset + label + f' step {count} ({time()-start:0.1f} s) {flag} (orig:{orig} | best:{best} | new:{new} | diff:{diff})')

Expand Down
81 changes: 62 additions & 19 deletions sciris/sc_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def safedivide(numerator=None, denominator=None, default=None, eps=None, warn=Fa
return output


def findinds(arr, val=None, eps=1e-6, first=False, last=False, **kwargs):
def findinds(arr, val=None, eps=1e-6, first=False, last=False, die=True, **kwargs):
'''
Little function to find matches even if two things aren't eactly equal (eg.
due to floats vs. ints). If one argument, find nonzero values. With two arguments,
Expand All @@ -89,23 +89,23 @@ def findinds(arr, val=None, eps=1e-6, first=False, last=False, **kwargs):
eps (float): the precision for matching (default 1e-6, equivalent to np.isclose's atol)
first (bool): whether to return the first matching value
last (bool): whether to return the last matching value
die (bool): whether to raise an exception if first or last is true and no matches were found
kwargs (dict): passed to np.isclose()
**Examples**::
sc.findinds(rand(10)<0.5) # e.g. array([2, 4, 5, 9])
sc.findinds([2,3,6,3], 6) # e.g. array([2])
sc.findinds(rand(10)<0.5) # returns e.g. array([2, 4, 5, 9])
sc.findinds([2,3,6,3], 3) # returs array([1,3])
sc.findinds([2,3,6,3], 3, first=True) # returns 1
New in version 1.2.3: "die" argument
'''

# Handle first or last
if first and last:
raise ValueError('Can use first or last but not both')
elif first:
ind = 0
elif last:
ind = -1
else:
ind = None
if first and last: raise ValueError('Can use first or last but not both')
elif first: ind = 0
elif last: ind = -1
else: ind = None

# Handle kwargs
atol = kwargs.pop('atol', eps) # Ensure atol isn't specified twice
Expand All @@ -131,13 +131,20 @@ def findinds(arr, val=None, eps=1e-6, first=False, last=False, **kwargs):
warnings.warn(warnmsg, category=RuntimeWarning, stacklevel=2)

# Process output
if arr.ndim == 1: # Uni-dimensional
output = output[0] # Return an array rather than a tuple of arrays if one-dimensional
if ind is not None:
output = output[ind] # And get the first element
else:
if ind is not None:
output = [output[i][ind] for i in range(arr.ndim)]
try:
if arr.ndim == 1: # Uni-dimensional
output = output[0] # Return an array rather than a tuple of arrays if one-dimensional
if ind is not None:
output = output[ind] # And get the first element
else:
if ind is not None:
output = [output[i][ind] for i in range(arr.ndim)]
except IndexError as E:
if die:
errormsg = 'No matching values found; use die=False to return None instead of raising an exception'
raise IndexError(errormsg) from E
else:
output = None

return output

Expand Down Expand Up @@ -330,7 +337,7 @@ def isprime(n, verbose=False):
### Other functions
##############################################################################

__all__ += ['perturb', 'normsum', 'normalize', 'inclusiverange', 'smooth', 'smoothinterp', 'randround', 'cat']
__all__ += ['perturb', 'normsum', 'normalize', 'inclusiverange', 'rolling', 'smooth', 'smoothinterp', 'randround', 'cat']


def perturb(n=1, span=0.5, randseed=None, normal=False):
Expand Down Expand Up @@ -445,6 +452,42 @@ def inclusiverange(*args, **kwargs):
return x


def rolling(data, window=7, operation='mean', **kwargs):
'''
Alias to Pandas' rolling() (window) method to smooth a series.
Args:
data (list/arr): the 1D or 2D data to be smoothed
window (int): the length of the window
operation (str): the operation to perform: 'mean' (default), 'median', 'sum', or 'none'
kwargs (dict): passed to pd.Series.rolling()
**Example**::
data = [5,5,5,0,0,0,0,7,7,7,7,0,0,3,3,3]
rolled = sc.rolling(data)
'''
import pandas as pd # Optional import

# Handle the data
data = np.array(data)
data = pd.Series(data) if data.ndim == 1 else pd.DataFrame(data)

# Perform the roll
roll = data.rolling(window=window, **kwargs)

# Handle output
if operation in [None, 'none']: output = roll
elif operation == 'mean': output = roll.mean().values
elif operation == 'median': output = roll.median().values
elif operation == 'sum': output = roll.sum().values
else:
errormsg = f'Operation "{operation}" not recognized; must be mean, median, sum, or none'
raise ValueError(errormsg)

return output


def smooth(data, repeats=None):
'''
Very simple function to smooth a 2D array -- slow but simple and easy to use.
Expand Down
4 changes: 2 additions & 2 deletions sciris/sc_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@

__all__ = ['__version__', '__versiondate__', '__license__']

__version__ = '1.2.2'
__versiondate__ = '2021-08-04'
__version__ = '1.2.3'
__versiondate__ = '2021-08-27'
__license__ = f'Sciris {__version__} ({__versiondate__}) – © 2021 by Sciris.org'
4 changes: 4 additions & 0 deletions tests/test_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import numpy as np
import pylab as pl
import sciris as sc
import pytest


if 'doplot' not in locals(): doplot = False
Expand Down Expand Up @@ -69,6 +70,9 @@ def test_find():
print('Testing sc.findfirst(), sc.findlast()')
found.first = sc.findfirst(pl.rand(10))
found.last = sc.findlast(pl.rand(10))
sc.findlast([1,2,3], 4, die=False)
with pytest.raises(IndexError):
sc.findlast([1,2,3], 4)

print('Testing sc.findnearest()')
found.nearest = sc.findnearest([0,2,5,8], 3)
Expand Down

0 comments on commit b4e0363

Please sign in to comment.