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
- 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.
- 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
pbyss.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