time

time

Functions and classes for handling time

Hierarchy of TimePars: TimePar # All time parameters ├── dur # All durations, units of time │ ├── days # Duration with units of days │ ├── weeks │ ├── months │ ├── years │ └── datedur # Calendar durations └── Rate # All rates, units of per (e.g. per time or per event) ├── per # Probability rates over time (e.g., death rate per year) │ ├── perday │ ├── perweek │ ├── permonth │ └── peryear ├── prob # Unitless probability (e.g., probability of death per infection) │ ├── probperday │ ├── probperweek │ ├── probpermonth │ └── probperyear └── freq # Number of events (e.g., number of acts per year) ├── freqperday ├── freqperweek ├── freqpermonth └── freqperyear

Classes

Name Description
DateArray Lightweight wrapper for an array of dates
DateConverter Starsim date conversion interface for matplotlib
Factors Define time unit conversion factors for all factors
FloatYearFormatter Formatter that renders float years as calendar dates
FloatYearLocator Locator to calculate tick positions for float year axes
MockAxis Emulator for Axis.get_view_internal() to return converted values
Rate Store a value per unit time e.g., 2 per day
TimePar Parent class for all TimePars – dur, Rate, etc.
UnitFactors Define conversion factors for a given unit, e.g. ‘years’
date Define a point in time, based on pd.Timestamp
datedur Date based duration e.g., if requiring a week to be 7 calendar days later
dur Base class for durations
freq Class for the number of events (rather than probability) in a specified period
per A per represents an instantaneous rate of an event occurring. Rates
prob prob represents the probability of an event occurring during a

DateArray

time.DateArray()

Lightweight wrapper for an array of dates

Attributes

Name Description
subdaily Check if the array has sub-daily timesteps
years Represent the dates as floating point years

Methods

Name Description
is_ Checks if the DateArray is comprised of ss.date objects
to_array Force conversion to an array
to_date Convert to ss.date
to_float Convert to a float, returning a new DateArray unless inplace=True
to_human Return the most human-friendly (i.e. plotting-friendly) version of the dates,
is_
time.DateArray.is_(which)

Checks if the DateArray is comprised of ss.date objects

to_array
time.DateArray.to_array(*args, **kwargs)

Force conversion to an array

to_date
time.DateArray.to_date(inplace=False, day_round=None, die=True)

Convert to ss.date

Parameters
Name Type Description Default
inplace bool whether to modify in place False
day_round bool whether to round dates to the nearest day (otherwise, keep timestamp); if None, round if and only if the span of the first timestep is at least one day None
die bool if False, then fall back to float if conversion to date fails (e.g. year 0) True
to_float
time.DateArray.to_float(inplace=False, to_numpy=False)

Convert to a float, returning a new DateArray unless inplace=True

to_human
time.DateArray.to_human()

Return the most human-friendly (i.e. plotting-friendly) version of the dates, i.e. ss.date if possible, float otherwise

DateConverter

time.DateConverter()

Starsim date conversion interface for matplotlib

This class gets registered with matplotlib as the converter for Starsim date and DateArray types, allowing them to be used directly as axis values. This achieves two outcomes

  1. When Starsim date or DateArray values are plotted, the x-axis values are automatically converted to float years for plotting. If this class is not used, matplotlib will instead convert them to days since 1970-01-01, which is the default behaviour for pd.Timestamp.
  2. If the first plotting command used on an axis has date/DateArray as the x-axis, our standard custom date locators and formatters will be used, so that the float years will automatically be labelled with dates.

Together, this means that users can freely plot both Starsim date/DateArray values and plain float years on the same axis without needing any conversions, and they’ll also automatically get date-based ticks without needing to set the locator, even if they are using custom plotting code rather than ss.Result.plot().

Methods

Name Description
axisinfo Specify which locators and formatters should be used if an instance of a registered class is the first one plotted
axisinfo
time.DateConverter.axisinfo(unit, axis)

Specify which locators and formatters should be used if an instance of a registered class is the first one plotted

Factors

time.Factors()

Define time unit conversion factors for all factors

FloatYearFormatter

time.FloatYearFormatter()

Formatter that renders float years as calendar dates

This class provides the same formatting as sc.ScirisDateFormatter, but works with axis values that are float years, rather than matplotlib date values (days since 1970-01-01)

FloatYearLocator

time.FloatYearLocator(unit)

Locator to calculate tick positions for float year axes

Matplotlib’s AutoDateLocator is the standard way to calculating date-based tick positions, incorporating a lot of functionality around sensibly setting the tick intervals e.g., to whole months. This class wraps AutoDateLocator to allow it to be used with float year axis values, by converting the float year values to matplotlib date values before calling the locator, and then converting the calculated tick positions back to float years before returning them.

MockAxis

time.MockAxis(axis)

Emulator for Axis.get_view_internal() to return converted values

Matplotlib’s date locators (AutoDateLocator, YearLocator, MonthLocator etc.) call Axis.get_view_internal() to get the current axis limits. These locators all expect the values to be returned in matplotlib dates (i.e., days since 1970-01-01). This helper class is bound to the actual axis, and emulates Axis.get_view_interval() performing the conversion to return the values in the expected units. That way, the Locator instances can be used directly without needing any other changes.

When this class is instantiated, it is bound to an actual Axis instance. The axis instance would have our custom date units and values in floating point years. Then MockAxis.get_view_interval() converts these years to standard matplotlib date values before they get passed to the Matplotlib locators.

Methods

Name Description
get_view_interval Return converted axis limits
get_view_interval
time.MockAxis.get_view_interval()

Return converted axis limits

:return: Tuple with (min, max) axis limits in matplotlib date units (days since 1970-01-01)

Rate

time.Rate(value, unit=None)

Store a value per unit time e.g., 2 per day - self.value - the numerator (e.g., 2) - a scalar float - self.unit - the denominator (e.g., 1 day) - a dur object

Methods

Name Description
set_default_dur Set the default duration, e.g. module.dt, so .to_prob() can be used with no input
to_events Simple multiplication: calculate the number of events over the time period
to_prob Convert from one time probability to another
set_default_dur
time.Rate.set_default_dur(dur)

Set the default duration, e.g. module.dt, so .to_prob() can be used with no input

to_events
time.Rate.to_events(dur=None)

Simple multiplication: calculate the number of events over the time period

to_prob
time.Rate.to_prob(dur=None, scale=1.0)

Convert from one time probability to another

Parameters
Name Type Description Default
dur ss.dur the duration over which to convert the probability to None
scale float an optional additional mutliplicative scale factor to incorporate in the calculation 1.0

Example:

p_month = ss.probpermonth(0.05)
p_year = p_month.to_prob(ss.year) # Slightly less than 0.05*12

TimePar

time.TimePar()

Parent class for all TimePars – dur, Rate, etc.

Methods

Name Description
to Convert this TimePar to one of a different class
to_array Force conversion to an array
to_base Class method to convert another TimePar object to this TimePar’s base units; in most cases
to
time.TimePar.to(unit)

Convert this TimePar to one of a different class

to_array
time.TimePar.to_array(*args, **kwargs)

Force conversion to an array

to_base
time.TimePar.to_base(other)

Class method to convert another TimePar object to this TimePar’s base units; in most cases

UnitFactors

time.UnitFactors(base_factors, unit)

Define conversion factors for a given unit, e.g. ‘years’

date

time.date()

Define a point in time, based on pd.Timestamp

Parameters

Name Type Description Default
date int / float / str / datetime Any type of date input (ints and floats will be interpreted as years) required
allow_zero bool if True, allow a year 0 by creating a datedur instead; if False, raise an exception; if None, give a warning required
kwargs dict passed to pd.Timestamp() required

Examples:

ss.date(2020) # Returns <2020-01-01>
ss.date(year=2020) # Returns <2020-01-01>
ss.date(year=2024.75) # Returns <2024-10-01>
ss.date('2024-04-04') # Returns <2024-04-04>
ss.date(year=2024, month=4, day=4) # Returns <2024-04-04>

Attributes

Name Description
years Return the date as a number of years

Methods

Name Description
arange Construct an array of dates
disp Show the full object
from_array Convert an array of float years into an array of date instances
from_json Reconstruct a date from a JSON; reverse of to_json()
from_year Convert an int or float year to a date.
replace Returns a new ss.date(); pd.Timestamp is immutable
round Round to a given interval (by default a day)
subdaily Check if a subdaily timestep is used
to_json Returns a JSON representation of the date
to_pandas Convert to a standard pd.Timestamp instance
to_year Convert a date to a floating-point year
arange
time.date.arange(
    start,
    stop,
    step=1.0,
    inclusive=True,
    day_round=None,
    allow_zero=True,
)

Construct an array of dates

Functions similarly to np.arange, but returns date objects

Example usage:

date.arange(2020, 2025) array([<2020.01.01>, <2021.01.01>, <2022.01.01>, <2023.01.01>, <2024.01.01>, <2025.01.01>], dtype=object)

Parameters
Name Type Description Default
start float / ss.date / ss.dur Lower bound - can be a date or a numerical year required

stop (float/ss.date/ss.dur): Upper bound - can be a date or a numerical year step (float/ss.dur): Assumes 1 calendar year steps by default inclusive (bool): Whether to include “stop” in the output day_round (bool): Whether to round to the nearest day (by default, True if step > 1 day) allow_zero (bool): if True, allow a year 0 by creating a datedur instead; if False, raise an exception; if None, give a warning

Returns
Name Type Description
An array of date instances
disp
time.date.disp(**kwargs)

Show the full object

from_array
time.date.from_array(array, day_round=True, allow_zero=True, date_type=None)

Convert an array of float years into an array of date instances

Parameters
Name Type Description Default
array array An array of float years required
day_round bool Whether to round to the nearest day True
allow_zero bool if True, allow a year 0 by creating a datedur instead; if False, raise an exception; if None, give a warning True
date_type type Optionally convert to a class other than ss.date (e.g. ss.datedur) None
Returns
Name Type Description
An array of date instances
from_json
time.date.from_json(json)

Reconstruct a date from a JSON; reverse of to_json()

from_year
time.date.from_year(year, day_round=True, allow_zero=None)

Convert an int or float year to a date.

Parameters
Name Type Description Default
year float the year to round required
day_round bool whether to round to the nearest day True
allow_zero bool whether to allow year 0 (if so, return ss.datedur instead) None

Examples:

ss.date.from_year(2020) # Returns <2020-01-01>
ss.date.from_year(2024.75) # Returns <2024-10-01>
replace
time.date.replace(*args, **kwargs)

Returns a new ss.date(); pd.Timestamp is immutable

round
time.date.round(to='d')

Round to a given interval (by default a day)

subdaily
time.date.subdaily(years)

Check if a subdaily timestep is used

A date has no concept of a timestep, but add this as a convienence method since this is required by other methods (e.g. ss.date.arange()).

to_json
time.date.to_json()

Returns a JSON representation of the date

to_pandas
time.date.to_pandas()

Convert to a standard pd.Timestamp instance

to_year
time.date.to_year()

Convert a date to a floating-point year

Examples:

ss.date('2020-01-01').to_year() # Returns 2020.0
ss.date('2024-10-01').to_year() # Returns 2024.7486

datedur

time.datedur(*args, **kwargs)

Date based duration e.g., if requiring a week to be 7 calendar days later

Attributes

Name Description
days Shortcut to datedur.to(‘days’)
months Shortcut to datedur.to(‘months’)
weeks Shortcut to datedur.to(‘weeks’)
years Shortcut to datedur.to(‘years’)

Methods

Name Description
round_duration Round a dictionary of duration values by overflowing remainders
scale Scale a pd.DateOffset by a factor
to Return approximate conversion into the provided quantity
to_array Convert to a Numpy array (NB, different than to_numpy() which converts to fractional years
to_dict Convert to a dictionary
to_dur Convert to the smallest non-zero value, e.g. ss.datedur(years=1, days=10).to_dur() = ss.days(375)
round_duration
time.datedur.round_duration(vals=None, **kwargs)

Round a dictionary of duration values by overflowing remainders

The input can be - A numpy array of length ss.time.factors containing values in key order - A pd.DateOffset instance - A dictionary with keys from ss.time.factors

The output will be a pd.DateOffset with integer values, where non-integer values have been handled by overflow using the factors in ss.time.factors. For example, 2.5 weeks would first become 2 weeks and 0.57 = 3.5 days, and then become 3 days + 0.524 = 12 hours.

Negative values are supported - -1.5 weeks for example will become (-1w, -3d, -12h)

Returns
Name Type Description
A pd.DateOffset
scale
time.datedur.scale(dateoffset, scale)

Scale a pd.DateOffset by a factor

This function will automatically cascade remainders to finer units using ss.time.factors so for example 2.5 weeks would first become 2 weeks and 0.57 = 3.5 days, and then become 3 days + 0.524 = 12 hours.

Parameters
Name Type Description Default
dateoffset A pd.DateOffset instance required
scale A float scaling factor (must be positive) required
Returns
Name Type Description
A pd.DateOffset instance scaled by the requested amount
to
time.datedur.to(unit)

Return approximate conversion into the provided quantity

This allows interoperability with years objects (which would typically be expected to occur if module parameters have been entered with datedur durations, but the simulation timestep is in years units).

The conversion is based on ss.time.factors which defines the conversion from each time unit to the next.

Parameters
Name Type Description Default
unit str the unit to convert to: years, months, weeks, or days required
Returns
Name Type Description
A float representing the duration in that unit
to_array
time.datedur.to_array(*args, **kwargs)

Convert to a Numpy array (NB, different than to_numpy() which converts to fractional years

to_dict
time.datedur.to_dict()

Convert to a dictionary

to_dur
time.datedur.to_dur()

Convert to the smallest non-zero value, e.g. ss.datedur(years=1, days=10).to_dur() = ss.days(375)

dur

time.dur(value=1, base=None)

Base class for durations

Note: this class should not be used by the user directly; instead, use ss.years(), ss.days(), etc. These classes can be interconverted using .to(), e.g. ss.years(3).to('days').

Methods

Name Description
arange Construct an array of dur instances
arange
time.dur.arange(start, stop, step=1.0, inclusive=True)

Construct an array of dur instances

Parameters
Name Type Description Default
start float / ss.dur Starting point, e.g., ss.years(0) required
stop float / ss.dur Ending point, e.g. ss.years(20) required
step float / ss.dur Step size, e.g. ss.years(2) 1.0

freq

time.freq(value, unit=None)

Class for the number of events (rather than probability) in a specified period

Identical to ss.per, except by default multiplication returns a number of events rather than a probability. Use ss.freq for events (e.g., number of acts per year) and ss.per for probabilities (e.g., probability of death per year).

Attributes

Name Description
rate Alias to self.value

per

time.per(value, unit=None)

A per represents an instantaneous rate of an event occurring. Rates must be non-negative, but need not be less than 1.

Through multiplication, rate can be modified or converted to a probability, depending on the data type of the object being multiplied.

When a per is multiplied by a scalar or array, the rate is simply scaled. Such multiplication occurs frequently in epidemiological models, where the base rate is multiplied by “rate ratio” or “relative rate” to represent agents experiencing higher (multiplier > 1) or lower (multiplier < 1) event rates.

Alternatively, when a per is multiplied by a duration (type ss.dur), a probability is calculated. The conversion from rate to probability on multiplication by a duration is 1 - np.exp(-rate/factor), where factor is the ratio of the multiplied duration to the original period (denominator).

For example, consider >>> p = ss.per(0.8, ss.years(1)) When multiplied by a duration of 1 year, the calculated probability is 1 - np.exp(-0.8), which is approximately 55%. >>> p*ss.years(1)

When multiplied by a scalar, the rate is simply scaled. >>> p*2

The difference between prob and per is subtle, but important. per works directly with the instantaneous rate of an event occurring. In contrast, prob starts with a probability and a duration, and the underlying rate is calculated. On multiplication by a duration, * per: rate -> probability * prob: probability -> rate -> probability

The behavior of both classes is depending on the data type of the object being multiplied.

ss.per is identical to ss.freq, except by default multiplication returns a probability rather than a number of events. Use ss.per for probabilities (e.g., probability of death per year), and ss.freq for events (e.g., number of acts per year).

Attributes

Name Description
rate Alias to self.value

prob

time.prob(value=None, unit=None, rate=None)

prob represents the probability of an event occurring during a specified period of time.

The class is designed to allow conversion of a probability from one duration to another through multiplication. However, the behavior of this conversion depends on the data type of the the object being multiplied.

When multiplied by a duration (type ss.dur), the underlying constant rate is calculated as rate = -np.log(1 - self.value). Then, the probability over the new duration is p = 1 - np.exp(-rate/factor), where factor is the ratio of the new duration to the original duration.

For example, >>> p = ss.prob(0.8, ss.years(1)) indicates a 80% chance of an event occurring in one year.

p*ss.years(1) When multiplied by the original denominator, 1 year in this case, the probability remains unchanged, 80%.

p * ss.years(2) Multiplying p by ss.years(2) does not simply double the probability to 160% (which is not possible), but rather returns a new probability of 96% representing the chance of the event occurring at least once over the new duration of two years.

However, the behavior is different when a prob object is multiplied by a scalar or array. In this case, the probability is simply scaled. This scaling may result in a value greater than 1, which is not valid. For example, >>> p * 2 raises an AssertionError because the resulting probability (160%) exceeds 100%.

Use per instead if prob if you would prefer to directly specify the instantaneous rate.

Methods

Name Description
to_prob Convert from one time probability to another
to_prob
time.prob.to_prob(dur=None, scale=1.0)

Convert from one time probability to another

Parameters
Name Type Description Default
dur ss.dur the duration over which to convert the probability to None
scale float an optional additional mutliplicative scale factor to incorporate in the calculation 1.0

Example:

p_month = ss.probpermonth(0.05)
p_year = p_month.to_prob(ss.year) # Slightly less than 0.05*12

Functions

Name Description
approx_compare Floating-point issues are common working with dates, so allow approximate matching
assume_cal_year Whether a value should be interpreted as a calendar year – by default, a number greater than 1900
get_dur_class Helper function to get a Dur class
get_rate_class Helper function to get a Rate class
get_timepar_class Helper function to get any Timepar class (either Dur or Rate)
get_unit_class Take a string or class and return the corresponding TimePar class
normalize_unit Allow either e.g. ‘year’ or ‘years’ as input – i.e. add an ‘s’
rate Backwards compatibility function for Rate
rate_prob Backwards compatibility function for per
time_prob Backwards compatibility function for prob

approx_compare

time.approx_compare(a, op='==', b=None, **kwargs)

Floating-point issues are common working with dates, so allow approximate matching

Specifically, this replaces e.g. “a <= b” with “a < b or np.isclose(a,b)”

Parameters

Name Type Description Default
a int / float the first value to compare required
op str the operation: ==, <, or > '=='
b int / float the second value to compare None
**kwargs dict passed to np.isclose() {}

assume_cal_year

time.assume_cal_year(val)

Whether a value should be interpreted as a calendar year – by default, a number greater than 1900

get_dur_class

time.get_dur_class(unit)

Helper function to get a Dur class

get_rate_class

time.get_rate_class(unit)

Helper function to get a Rate class

get_timepar_class

time.get_timepar_class(unit)

Helper function to get any Timepar class (either Dur or Rate)

get_unit_class

time.get_unit_class(which, unit)

Take a string or class and return the corresponding TimePar class

normalize_unit

time.normalize_unit(base)

Allow either e.g. ‘year’ or ‘years’ as input – i.e. add an ‘s’

rate

time.rate(value, unit=None)

Backwards compatibility function for Rate

rate_prob

time.rate_prob(value, unit=None)

Backwards compatibility function for per

time_prob

time.time_prob(value, unit=None)

Backwards compatibility function for prob