def _brief(self): ''' Return a one-line description of a sim -- used internally and by repr(); see sim.brief() for the user version. ''' # Try to get a detailed description of the sim... try: if self.results_ready: infections = self.summary['cum_infections'] deaths = self.summary['cum_deaths'] results = f'{infections:n}⚙, {deaths:n}☠' else: results = 'not run' # Set label string labelstr = f'"{self.label}"' if self.label else '<no label>' start = sc.date(self['start_day'], as_date=False) if self['end_day']: end = sc.date(self['end_day'], as_date=False) else: end = sc.date(self['n_days'], start_date=start) pop_size = self['pop_size'] pop_type = self['pop_type'] string = f'Sim({labelstr}; {start} to {end}; pop: {pop_size:n} {pop_type}; epi: {results})' # ...but if anything goes wrong, return the default with a warning except Exception as E: string = sc.objectid(self) string += f'Warning, sim appears to be malformed:\n{str(E)}' return string
def date(self, ind, *args, dateformat=None, as_date=False): ''' Convert one or more integer days of simulation time to a date/list of dates -- by default returns a string, or returns a datetime Date object if as_date is True. See also cv.date(), which provides a partly overlapping set of date conversion features. Args: ind (int, list, or array): the index day(s) in simulation time (NB: strings and date objects are accepted, and will be passed unchanged) args (list): additional day(s) dateformat (str): the format to return the date in as_date (bool): whether to return as a datetime date instead of a string Returns: dates (str, Date, or list): the date(s) corresponding to the simulation day(s) **Examples**:: sim = cv.Sim() sim.date(34) # Returns '2020-04-04' sim.date([34, 54]) # Returns ['2020-04-04', '2020-04-24'] sim.date([34, '2020-04-24']) # Returns ['2020-04-04', '2020-04-24'] sim.date(34, 54, as_date=True) # Returns [datetime.date(2020, 4, 4), datetime.date(2020, 4, 24)] ''' # Handle inputs if not isinstance(ind, list): # If it's a number, string, or dateobj, convert it to a list ind = sc.promotetolist(ind) ind.extend(args) if dateformat is None: dateformat = '%Y-%m-%d' # Do the conversion dates = [] for raw in ind: if sc.isnumber(raw): date_obj = sc.date(self['start_day'], as_date=True) + dt.timedelta(days=int(raw)) else: date_obj = sc.date(raw, as_date=True) if as_date: dates.append(date_obj) else: dates.append(date_obj.strftime(dateformat)) # Return a string rather than a list if only one provided if len(ind)==1: dates = dates[0] return dates
def load_data(datafile, calculate=True, check_date=True, verbose=True, start_day=None, **kwargs): ''' Load data for comparing to the model output, either from file or from a dataframe. Args: datafile (str or df): if a string, the name of the file to load (either Excel or CSV); if a dataframe, use directly calculate (bool): whether to calculate cumulative values from daily counts check_date (bool): whether to check that a 'date' column is present start_day (date): if the 'date' column is provided as integer number of days, consider them relative to this kwargs (dict): passed to pd.read_excel() Returns: data (dataframe): pandas dataframe of the loaded data ''' # Load data if isinstance(datafile, Path): # Convert to a string datafile = str(datafile) if isinstance(datafile, str): df_lower = datafile.lower() if df_lower.endswith('csv'): data = pd.read_csv(datafile, **kwargs) elif df_lower.endswith('xlsx') or df_lower.endswith('xls'): data = pd.read_excel(datafile, **kwargs) elif df_lower.endswith('json'): data = pd.read_json(datafile, **kwargs) else: errormsg = f'Currently loading is only supported from .csv, .xls/.xlsx, and .json files, not "{datafile}"' raise NotImplementedError(errormsg) elif isinstance(datafile, pd.DataFrame): data = datafile else: # pragma: no cover errormsg = f'Could not interpret data {type(datafile)}: must be a string or a dataframe' raise TypeError(errormsg) # Calculate any cumulative columns that are missing if calculate: columns = data.columns for col in columns: if col.startswith('new'): cum_col = col.replace('new_', 'cum_') if cum_col not in columns: data[cum_col] = np.cumsum(data[col]) if verbose: print(f' Automatically adding cumulative column {cum_col} from {col}') # Ensure required columns are present and reset the index if check_date: if 'date' not in data.columns: errormsg = f'Required column "date" not found; columns are {data.columns}' raise ValueError(errormsg) else: if data['date'].dtype == np.int64: # If it's integers, treat it as days from the start day data['date'] = sc.date(data['date'].values, start_date=start_day) else: # Otherwise, use Pandas to convert it data['date'] = pd.to_datetime(data['date']).dt.date data.set_index('date', inplace=True, drop=False) # Don't drop so sim.data['date'] can still be accessed return data
def date_formatter(start_day=None, dateformat=None, ax=None): ''' Create an automatic date formatter based on a number of days and a start day. Wrapper for Matplotlib's date formatter. Note, start_day is not required if the axis uses dates already. To be used in conjunction with setting the x-axis tick label formatter. Args: start_day (str/date): the start day, either as a string or date object dateformat (str): the date format ax (axes): if supplied, automatically set the x-axis formatter for this axis **Example**:: formatter = date_formatter(start_day='2020-04-04', dateformat='%Y-%m-%d') ax.xaxis.set_major_formatter(formatter) ''' # Set the default -- "Mar-01" if dateformat is None: dateformat = '%b-%d' # Convert to a date object start_day = sc.date(start_day) @ticker.FuncFormatter def mpl_formatter(x, pos): if sc.isnumber(x): return (start_day + dt.timedelta(days=x)).strftime(dateformat) else: return x.strftime(dateformat) if ax is not None: ax.xaxis.set_major_formatter(mpl_formatter) return mpl_formatter
def date_formatter(start_day=None, dateformat=None, interval=None, start=None, end=None, ax=None, sim=None): ''' Create an automatic date formatter based on a number of days and a start day. Wrapper for Matplotlib's date formatter. Note, start_day is not required if the axis uses dates already. To be used in conjunction with setting the x-axis tick label formatter. Args: start_day (str/date): the start day, either as a string or date object dateformat (str): the date format (default '%b-%d') interval (int): if supplied, the interval between ticks (must supply an axis also to take effect) start (str/int): if supplied, the lower limit of the axis end (str/int): if supplied, the upper limit of the axis ax (axes): if supplied, automatically set the x-axis formatter for this axis sim (Sim): if supplied, get the start day from this **Examples**:: # Automatically configure the axis with default option cv.date_formatter(sim=sim, ax=ax) # Manually configure ax = pl.subplot(111) ax.plot(np.arange(60), np.random.random(60)) formatter = cv.date_formatter(start_day='2020-04-04', interval=7, start='2020-05-01', end=50, dateformat='%Y-%m-%d', ax=ax) ax.xaxis.set_major_formatter(formatter) ''' # Set the default -- "Mar-01" if dateformat is None: dateformat = '%b-%d' # Convert to a date object if start_day is None and sim is not None: start_day = sim['start_day'] if start_day is None: errormsg = 'If not supplying a start day, you must supply a sim object' raise ValueError(errormsg) start_day = sc.date(start_day) @ticker.FuncFormatter def mpl_formatter(x, pos): return (start_day + dt.timedelta(days=int(x))).strftime(dateformat) # Set initial tick marks (intervals and limits) if ax is not None: # Handle limits xmin, xmax = ax.get_xlim() if start: xmin = sc.day(start, start_day=start_day) if end: xmax = sc.day(end, start_day=start_day) ax.set_xlim((xmin, xmax)) # Set the x-axis intervals if interval: ax.set_xticks(np.arange(xmin, xmax + 1, interval)) # Set the formatter ax.xaxis.set_major_formatter(mpl_formatter) return mpl_formatter