Skip to content

Commit

Permalink
Merge pull request #316 from sciris/rc1.4.0-psl-small-fixes
Browse files Browse the repository at this point in the history
Rc1.4.0 psl small fixes
  • Loading branch information
cliffckerr committed Aug 11, 2022
2 parents fc9930a + 740b5f7 commit 1694376
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 78 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ Bugfixes
Improvements
~~~~~~~~~~~~
#. If a copy/deepcopy is not possible, ``sc.cp()``/``sc.dcp()`` now raise an exception by default (previously, they silenced it).
#. ``sc.daterange()`` now accepts ``datedelta`` arguments, e.g. ``sc.daterange('2022-02-22', weeks=2)``.

Housekeeping
~~~~~~~~~~~~
#. ``DeprecationWarning``s have been changed to ``FutureWarning``s.
#. Most ``DeprecationWarning``s have been changed to ``FutureWarning``s.
Regression information
~~~~~~~~~~~~~~~~~~~~~~
Expand Down
92 changes: 52 additions & 40 deletions sciris/sc_colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def hsv2rgb(colors=None):
__all__ += ['vectocolor', 'arraycolors', 'gridcolors', 'midpointnorm', 'colormapdemo']


def vectocolor(vector, cmap=None, asarray=True, reverse=False, minval=None, maxval=None, midpoint=None, norm=None):
def vectocolor(vector, cmap=None, asarray=True, reverse=False, minval=None, maxval=None, midpoint=None):
"""
This function converts a vector (i.e., 1D array) of N values into an Nx3 matrix
of color values according to the current colormap. It automatically scales the
Expand Down Expand Up @@ -242,6 +242,10 @@ def arraycolors(arr, **kwargs):
of vectocolor() for multidimensional arrays; see that function for additional
arguments.
Args:
arr (array): a multidimensional array to be converted to an array of colors
kwargs(dict): passed to ``sc.vectocolor()``
**Example**::
n = 1000
Expand Down Expand Up @@ -269,29 +273,35 @@ def arraycolors(arr, **kwargs):

def gridcolors(ncolors=10, limits=None, nsteps=20, asarray=False, ashex=False, reverse=False, hueshift=0, basis='default', demo=False):
"""
Create a qualitative "color map" by assigning points according to the maximum pairwise distance in the
color cube. Basically, the algorithm generates n points that are maximally uniformly spaced in the
[R, G, B] color cube.
Arguments:
ncolors: the number of colors to create
limits: how close to the edges of the cube to make colors (to avoid white and black)
nsteps: the discretization of the color cube (e.g. 10 = 10 units per side = 1000 points total)
asarray: whether to return the colors as an array instead of as a list of tuples
doplot: whether or not to plot the color cube itself
basis: what basis to use -- options are 'colorbrewer', 'kelly', 'default', or 'none'
Create a qualitative "color map" by assigning points according to the maximum
pairwise distance in the color cube. Basically, the algorithm generates n points
that are maximally uniformly spaced in the [R, G, B] color cube.
By default, if there are <=9 colors, use Colorbrewer colors; if there are
10-19 colors, use Kelly's colors; if there are >=20 colors, use uniformly
spaced grid colors.
Args:
ncolors (int) : the number of colors to create
limits (float) : how close to the edges of the cube to make colors (to avoid white and black)
nsteps (int) : the discretization of the color cube (e.g. 10 = 10 units per side = 1000 points total)
ashex (bool) : whether to return colors in hexadecimal representation
asarray (bool) : whether to return the colors as an array instead of as a list of tuples
reverse (bool) : whether to reverse the list of colors
hueshift (float) : whether to shift the hue (hueshift > 0 and <=1) or not (0)
demo (bool) : whether or not to plot the color cube itself
basis (str) : what basis to use -- options are 'colorbrewer', 'kelly', 'default', or 'none'
**Example**::
from pylab import *
from sciris import gridcolors
import pylab as pl
import sciris as sc
ncolors = 10
piedata = rand(ncolors)
colors = gridcolors(ncolors)
figure()
pie(piedata, colors=colors)
gridcolors(ncolors, demo=True)
show()
piedata = pl.rand(ncolors)
colors = sc.gridcolors(ncolors)
pl.pie(piedata, colors=colors)
sc.gridcolors(ncolors, demo=True)
pl.show()
Version: 2018oct30
"""
Expand Down Expand Up @@ -534,23 +544,25 @@ def bicolormap(gap=0.1, mingreen=0.2, redbluemix=0.5, epsilon=0.01, demo=False,
of a color gap there is between the red scale and the blue one.
Args:
gap: sets how big of a gap between red and blue color scales there is (0=no gap; 1=pure red and pure blue)
mingreen: how much green to include at the extremes of the red-blue color scale
redbluemix: how much red to mix with the blue and vice versa at the extremes of the scale
epsilon: what fraction of the colormap to make gray in the middle
gap (float): sets how big of a gap between red and blue color scales there is (0=no gap; 1=pure red and pure blue)
mingreen (float): how much green to include at the extremes of the red-blue color scale
redbluemix (float): how much red to mix with the blue and vice versa at the extremes of the scale
epsilon (float): what fraction of the colormap to make gray in the middle
demo (bool): whether to plot a demo bicolormap or not
apply (bool): whether to apply this colormap to the current figure
**Examples**::
bicolormap(gap=0,mingreen=0,redbluemix=1,epsilon=0) # From pure red to pure blue with white in the middle
bicolormap(gap=0,mingreen=0,redbluemix=0,epsilon=0.1) # Red -> yellow -> gray -> turquoise -> blue
bicolormap(gap=0.3,mingreen=0.2,redbluemix=0,epsilon=0.01) # Red and blue with a sharp distinction between
sc.bicolormap(gap=0, mingreen=0, redbluemix=1, epsilon=0) # From pure red to pure blue with white in the middle
sc.bicolormap(gap=0, mingreen=0, redbluemix=0, epsilon=0.1) # Red -> yellow -> gray -> turquoise -> blue
sc.bicolormap(gap=0.3, mingreen=0.2, redbluemix=0, epsilon=0.01) # Red and blue with a sharp distinction between
Version: 2013sep13
"""
mng=mingreen; # Minimum amount of green to add into the colors
mix=redbluemix; # How much red to mix with the blue an vice versa
eps=epsilon; # How much of the center of the colormap to make gray
omg=1-gap # omg = one minus gap
mng = mingreen # Minimum amount of green to add into the colors
mix = redbluemix # How much red to mix with the blue an vice versa
eps = epsilon # How much of the center of the colormap to make gray
omg = 1-gap # omg = one minus gap

cdict = {'red': ((0.00000, 0.0, 0.0),
(0.5-eps, mix, omg),
Expand All @@ -570,28 +582,28 @@ def bicolormap(gap=0.1, mingreen=0.2, redbluemix=0.5, epsilon=0.01, demo=False,
(0.5+eps, omg, mix),
(1.00000, 0.0, 0.0))}

cmap = mplc.LinearSegmentedColormap('bi',cdict,256)
cmap = mplc.LinearSegmentedColormap('bi', cdict, 256)
if apply:
pl.set_cmap(cmap)

def demoplot(): # pragma: no cover
from pylab import figure, subplot, imshow, colorbar, rand, show

maps=[]
maps = []
maps.append(bicolormap()) # Default ,should work for most things
maps.append(bicolormap(gap=0,mingreen=0,redbluemix=1,epsilon=0)) # From pure red to pure blue with white in the middle
maps.append(bicolormap(gap=0,mingreen=0,redbluemix=0,epsilon=0.1)) # Red -> yellow -> gray -> turquoise -> blue
maps.append(bicolormap(gap=0.3,mingreen=0.2,redbluemix=0,epsilon=0.01)) # Red and blue with a sharp distinction between
nexamples=len(maps)

figure(figsize=(5*nexamples,4))
pl.figure(figsize=(5*nexamples, 4))
for m in range(nexamples):
subplot(1,nexamples,m+1)
imshow(rand(20,20),cmap=maps[m],interpolation='none');
colorbar()
show()
pl.subplot(1, nexamples, m+1)
pl.imshow(np.random.rand(20,20), cmap=maps[m], interpolation='none')
pl.colorbar()
pl.show()

if demo: demoplot()
if demo:
demoplot()

return cmap

Expand Down
43 changes: 27 additions & 16 deletions sciris/sc_datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,18 @@
__all__ = ['now', 'getdate', 'readdate', 'date', 'day', 'daydiff', 'daterange', 'datedelta', 'datetoyear']


def now(astype='dateobj', timezone=None, utc=False, dateformat=None):
def now(astype='dateobj', timezone=None, utc=False, tostring=False, dateformat=None):
'''
Get the current time as a datetime object, optionally in UTC time.
``sc.now()`` is similar to ``sc.getdate()``, but ``sc.now()`` returns a datetime
object by default, while ``sc.getdate()`` returns a string by default.
Args:
astype (str): what to return; choices are "dateobj", "str", "float"; see ``sc.getdate()`` for more
timezone (str): the timezone to set the itme to
utc (bool): whether the time is specified in UTC time
dateformat (str): if ``astype`` is ``'str'``, use this output format
astype (str) : what to return; choices are "dateobj", "str", "float"; see ``sc.getdate()`` for more
timezone (str) : the timezone to set the itme to
utc (bool) : whether the time is specified in UTC time
dateformat (str) : if ``astype`` is ``'str'``, use this output format
**Examples**::
Expand All @@ -51,6 +51,10 @@ def now(astype='dateobj', timezone=None, utc=False, dateformat=None):
if timezone is not None: tzinfo = du.tz.gettz(timezone) # Timezone is a string
elif utc: tzinfo = du.tz.tzutc() # UTC has been specified
else: tzinfo = None # Otherwise, do nothing
if tostring:
warnmsg = 'sc.now() argument "tostring" will be deprecated soon'
warnings.warn(warnmsg, category=FutureWarning, stacklevel=2)
astype='str'
timenow = dt.datetime.now(tzinfo)
output = getdate(timenow, astype=astype, dateformat=dateformat)
return output
Expand Down Expand Up @@ -396,38 +400,45 @@ def daydiff(*args):
return output


def daterange(start_date=None, end_date=None, interval=None, inclusive=True, as_date=False, readformat=None, outformat=None, **kwargs):
def daterange(start_date=None, end_date=None, interval=None, inclusive=True, as_date=False,
readformat=None, outformat=None, **kwargs):
'''
Return a list of dates from the start date to the end date. To convert a list
of days (as integers) to dates, use ``sc.date()`` instead.
Note: instead of an end date, can also pass one or more of days, months, weeks,
or years, which will be added on to the start date via ``sc.datedelta()``.
Args:
start_date (int/str/date): the starting date, in any format
end_date (int/str/date): the end date, in any format
interval (int/str/dict): if an int, the number of days; if 'week', 'month', or 'year', one of those; if a dict, passed to ``dt.relativedelta()``
inclusive (bool): if True (default), return to end_date inclusive; otherwise, stop the day before
as_date (bool): if True, return a list of datetime.date objects instead of strings (note: you can also use "asdate" instead of "as_date")
readformat (str): passed to date()
outformat (str): passed to date()
start_date (int/str/date) : the starting date, in any format
end_date (int/str/date) : the end date, in any format
interval (int/str/dict) : if an int, the number of days; if 'week', 'month', or 'year', one of those; if a dict, passed to ``dt.relativedelta()``
inclusive (bool) : if True (default), return to end_date inclusive; otherwise, stop the day before
as_date (bool) : if True, return a list of datetime.date objects instead of strings (note: you can also use "asdate" instead of "as_date")
readformat (str) : passed to date()
outformat (str) : passed to date()
days (int) : optional
**Examples**::
dates1 = sc.daterange('2020-03-01', '2020-04-04')
dates2 = sc.daterange('2020-03-01', '2022-05-01', interval=dict(months=2), asdate=True)
dates3 = sc.daterange('2020-03-01', weeks=5)
| New in version 1.0.0.
| New in version 1.3.0: "interval" argument
| New in version 2.0.0: ``sc.datedelta()`` arguments
'''

# Handle inputs
start_date = kwargs.pop('startdate', start_date) # Handle with or without underscore
end_date = kwargs.pop('enddate', end_date) # Handle with or without underscore
as_date = kwargs.pop('asdate', as_date) # Handle with or without underscore
if len(kwargs):
errormsg = f'Unexpected arguments: {scu.strjoin(kwargs.keys())}'
raise ValueError(errormsg)
end_date = datedelta(start_date, **kwargs)
start_date = date(start_date, readformat=readformat)
end_date = date(end_date, readformat=readformat)
end_date = date(end_date, readformat=readformat)

if interval in [None, 'day']: interval = dict(days=1)
elif interval == 'week': interval = dict(weeks=1)
elif interval == 'month': interval = dict(months=1)
Expand Down
3 changes: 3 additions & 0 deletions sciris/sc_fileio.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,9 +391,11 @@ def makefilepath(filename=None, folder=None, ext=None, default=None, split=False
default (str or list) : a name or list of names to use if filename is None
split (bool) : whether to return the path and filename separately
aspath (bool) : whether to return a Path object
abspath (bool) : whether to conver to absolute path
makedirs (bool) : whether or not to make the folders to save into if they don't exist
checkexists (bool) : if False/True, raises an exception if the path does/doesn't exist
sanitize (bool) : whether or not to remove special characters from the path; see ``sc.sanitizefilename()`` for details
die (bool) : whether or not to raise an exception if cannot create directory failed (otherwise, return a string)
verbose (bool) : how much detail to print
Returns:
Expand Down Expand Up @@ -1099,6 +1101,7 @@ def loadspreadsheet(filename=None, folder=None, fileobj=None, sheet=0, header=1,
fileobj (obj): load from file object rather than path
sheet (str/int/list): name or number of sheet(s) to use (default 0)
asdataframe (bool): whether to return as a pandas/Sciris dataframe (default True)
header (bool): whether the 0-th row is to be read as the header
method (str): how to read (default 'pandas', other choices 'openpyxl' and 'xlrd')
kwargs (dict): passed to pd.read_excel(), openpyxl(), etc.
Expand Down
23 changes: 18 additions & 5 deletions sciris/sc_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,12 +284,24 @@ def getvaliddata(data=None, filterdata=None, defaultind=0): # pragma: no cover
def sanitize(data=None, returninds=False, replacenans=None, die=True, defaultval=None, label=None, verbose=True):
'''
Sanitize input to remove NaNs. Warning, does not work on multidimensional data!!
Returns an array with the sanitized data. If replacenans=True, the sanitized array is
of the same length/size as data. If replacenans=False, the sanitized array
may be shorter than data.
Args:
data (arr/list) : the array with data to be sanitized
returninds (bool) : whether to return the indices of the non-NaN values in data
replacenans (bool/float) : whether to replace the NaNs with a value, or using interpolation ``sc.smoothinterp()``
defaultval (float/int) : value to return if the sanitized array is empty.
label (str) : human readable label for data
die (bool) : whether to raise an exception if sanitization fails.
**Examples**::
sanitized,inds = sanitize(array([3,4,nan,8,2,nan,nan,nan,8]), returninds=True)
sanitized = sanitize(array([3,4,nan,8,2,nan,nan,nan,8]), replacenans=True)
sanitized = sanitize(array([3,4,nan,8,2,nan,nan,nan,8]), replacenans=0)
data = [3, 4, np.nan, 8, 2, np.nan, np.nan, 8]
sanitized1, inds = sc.sanitize(data, returninds=True)
sanitized2 = sc.sanitize(data, replacenans=True)
sanitized3 = sc.sanitize(data, replacenans=0)
'''
try:
data = np.array(data,dtype=float) # Make sure it's an array of float type
Expand All @@ -310,8 +322,8 @@ def sanitize(data=None, returninds=False, replacenans=None, die=True, defaultval
else:
sanitized = 0.0
if verbose: # pragma: no cover
if label is None: label = 'this parameter'
print(f'sc.sanitize(): no data entered for {label}, assuming 0')
if label is None: label = 'these input data'
print(f'sc.sanitize(): no valid values found for {label}. Returning 0.')
except Exception as E: # pragma: no cover
if die:
errormsg = f'Sanitization failed on array: "{repr(E)}":\n{data}'
Expand Down Expand Up @@ -439,6 +451,7 @@ def perturb(n=1, span=0.5, randseed=None, normal=False):
Args:
n (int): number of points
span (float): width of distribution on either side of 1
randseed (int): seed passed to the reseed numpy's legacy MT19937 BitGenerator
normal (bool): whether to use a normal distribution instead of uniform
**Example**::
Expand Down
4 changes: 2 additions & 2 deletions sciris/sc_nested.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def flattendict(nesteddict, sep=None, _prefix=None):
Returns:
A flat dictionary where no values are dicts
New in version 1.4.0: handle non-string keys.
New in version 2.0.0: handle non-string keys.
"""
output_dict = {}
for k, v in nesteddict.items():
Expand Down Expand Up @@ -297,4 +297,4 @@ def nestedloop(inputs, loop_order):
out = [None] * len(loop_order)
for i in range(len(item)):
out[loop_order[i]] = item[i]
yield out
yield out
3 changes: 3 additions & 0 deletions sciris/sc_parallel.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ def parallelcmd(cmd=None, parfor=None, returnval=None, maxload=None, interval=No
parfor (dict): a dictionary of lists of the variables to loop over
returnval (str): the name of the output variable
maxload (float): the maximum CPU load, used in ``sc.loadbalancer()``
interval (float): the time delay to poll to see if CPU load is OK, used in ``sc.loadbalancer()``
kwargs (dict): variables to pass into the code
**Example**::
Expand Down Expand Up @@ -348,6 +349,8 @@ def parallel_progress(fcn, inputs, num_workers=None, show_progress=True, initial
fcn (function): Function object to call, accepting one argument, OR a function with zero arguments in which case inputs should be an integer
inputs (list): A collection of inputs that will each be passed to the function OR a number, if the fcn() has no input arguments
num_workers (int): Number of processes, defaults to the number of CPUs
show_progress (bool): Whether to show a progress bar
initializer (func): A function that each worker process will call when it starts
Returns:
A list of outputs
Expand Down

0 comments on commit 1694376

Please sign in to comment.