timescales 0.6.0
Breaking: crosswalk functions split by task
-
calendar_map(x, from, to)now maps between two timeframes of one calendar (through the shared core); the map between two calendars on the base grid, formerlycalendar_map(from, to, year), iscalendar_map_between(from, to, year). The registry follows the same split:register_calendar_map(x, from, to, map)/get_calendar_map(x, from, to)for timeframes of one calendar,register_calendar_map_between()/get_calendar_map_between()for two calendars.list_calendar_maps()reports both;clear_calendar_maps(registry = TRUE)clears both. No deprecation aliases. - The shared core package is now called nestedscales (formerly multiscales);
data/calendars.rdawas rebuilt against it.
timescales 0.5.0.9000
The shared core
- The
recast()generic is now owned bynestedscales, the dimension-agnostic core, and re-exported here.timescales::recastresolves exactly as before and dispatches onCalendarexactly as before – the object behind the name is simply shared withgeoscalesrather than owned by this package. Nothing changes for callers. -
calendar_ancestry(),calendar_share(),calendar_coverage()and the navigation verbs (calendar_children(),calendar_parents(),calendar_descendants(),calendar_ancestors()) now compute throughnestedscales. Their output is unchanged. - Rules registered with
register_calendar_rule()are kept in thenestedscalesregistry under the scope"calendar", sonestedscales::recast_scale()on a Calendar finds them too. A rule registered there without a scope now also applies to calendars. -
recast_calendar()aggregates throughnestedscales::recast_crosswalk(). Results are unchanged. Panel data no longer builds an in-memory grid of every identifier combination times the crosswalk before joining. -
expand_calendar()anddatetime_to_timeslice()label a sampled calendar correctly when it carries no recorded tokens (one rebuilt from a leaftable, as energyRt’s sampled calendars are). Before, every grid point came backNA: day numbers were measured against the sampled days rather than the whole year. -
calendar_family()returns an empty table, not a 1 x 1 logical matrix, whenparent=/child=select no timeframe pair. -
nestedscalesis added toImports. It brings no new external dependency: its own imports (dplyr,rlang,S7,stats,utils) were already ours.
Timeslice conversions
-
tsl2hour(),tsl2yday(),tsl2month(),hour2HOUR(),yday2YDAY(),tsl2dtm()anddtm2tsl()read a timeframe off a timeslice, and convert between timeslices and datetimes. They move here from energyRt, where they parsed the label text with regular expressions. - Each takes the calendar the timeslices belong to – as an object or by name,
tsl2hour(tsl, "d365_h24"). The calendar is what replaces the guessing: the answer is a join againstcalendar_leaftable()when the calendar holds the timeframe,calendar_map()when it does not, and the timeslice’s instant when no token claims it. - A timeframe is available when it is a function of the calendar’s timeslices.
tsl2hour(tsl, "m12")is an error naming the reason – a month spans 24 hours, so there is no single one to return – rather than aNULL. - This reads vocabularies the regular expressions could not:
m12a("JAN".."DEC"),wd7("MON".."SUN"),q4,s4,hp3, and any token added withregister_calendar_token(). It also fixes a wrong answer:h168labels hours of the week as"h000".."h167", which a pattern forhplus digits read as hours of the day. -
as_timeframe()is the verb underneath, and now accepts timeslice labels and integers as well as datetimes – soas_timeframe(0:23, "HOUR", format = "token")gives"h00".."h23", and acalendarargument selects that calendar’s own spelling over the canonical one. -
yearis required for the weekday-derived timeframes (WDAY,MWEEK,DAYTYPE,WHOUR, andYEAR), whose value depends on which year the timeslices fall in.MONTH,YDAY,QUARTER,SEASON,WEEKandMDAYare the same in every year and need no year. - Sampled calendars are handled throughout. A calendar filtered to a few representative days holds a SUBSET of its parent’s timeslices, and its labels keep the meaning they had in the parent:
tsl2yday("d015_h00")is day 15, not the first day the calendar happens to hold. The number is read from the timeframe’s full vocabulary (meta$tokens, or inferred from the labels for a hand-built calendar) rather than from the calendar’s own member list. -
tsl2dtm()dates a sampled calendar’s timeslices against its parent, becauseexpand_calendar()leaves a filtered calendar’s grid unlabelled. A timeframe that no crosswalk can reach falls back to the timeslice’s instant, so a sampled calendar still resolvesMONTH.
Calendars and rules
New rule
"share": share within parent. Each source timeslice’s value over its parent-timeslice total, keyed by the source calendar –recast_calendar(x, cal, to = "YDAY", rule = "share")gives each hour’s share within its day, summing to 1 per parent. The parent isto(a calendar or timeframe name,"ANNUAL"included), or the newparent=argument (default:frompruned to its second-finest timeframe); the source timeslices must nest within it."logshare"is the same computation with a log-scale display intent. Mirrored in geoscales.The calendar figures’ data fills accept both share rules: every row is coloured by each timeslice’s share within the row above it (ANNUAL by its share of the year), legend “Share” –
"share"on a fixed linear 0..1 scale,"logshare"on a fixed log10 percent scale.summary()output now prints with its formatted view from user code: the localprintbinding S7 leaves in the namespace had captured theS3method()registration ofprint.summary_Calendar, so the method was invisible outside the package. Registered against base’sprintin.onLoad(same fix in geoscales).Remotes:added to DESCRIPTION so CI andpakusers can resolve the GitHub-only energypal Suggests from GitHub (the packages are not on CRAN/r-universe yet). Drop the field at CRAN submission time.
Base-generic methods on Calendar
-
summary()— the quantitative complement ofprint(): member counts, sampled coverage (with the parent’s name), catalog classification, share/weight ranges. Returns a"summary_Calendar"with its own print. -
names()— the timeframe names (identical tocalendar_timeframes()). -
as.data.frame()andggplot2::fortify()— the leaftable, soggplot(cal) + geom_*()works directly. All mirrored by the geoscales twins.
timescales 0.5.0
Hard-break release: the sibling APIs of timescales and geoscales were harmonized against each other (the pairing table and naming rules live in the stack-wide CONVENTIONS.md, “Sibling API mirror”). NO deprecation aliases are kept – old names are gone, not wrapped.
Breaking changes
- Registries follow
register_<class>_<thing>everywhere:register_rule/get_rule/list_rules/clear_rulesare nowregister_calendar_rule/get_calendar_rule/list_calendar_rules/clear_calendar_rules; the conversion registry isregister_calendar_conversion/get_calendar_conversion/list_calendar_conversions/clear_calendar_conversions; the token registry isregister_calendar_token/get_calendar_token/list_calendar_tokens.RECAST_RULESis nowCALENDAR_RULES. (ALIGNMENT_RULESand thecalendar_mapregistry family already complied and are unchanged.) - The deprecated shim family is REMOVED:
instant_to_slice,instant_to_timeslice,calendar_at_level,calendar_join,calendar_recast,calendar_from_leaves(archived underdrafts/). - The structure-object argument is
xeverywhere (wascalendarorobject): navigation/queries,filter_calendar,prune_calendar,expand_calendar, the wall family, andcalendar_autoplot. Data-first verbs (join_calendar,recast_*,datetime_to_timeslice) keepx= data and the structure ascalendar. -
recast_to_timebase(weight = )is nowattach_weight =(matchinggeoscales::recast_to_geoatoms()). -
calendar_autoplot(rule = )defaults to"weighted_mean"(wasNULL), matchinggeoscale_autoplot(). - The catalog metadata field
coverageis renamedcoverage_class("complete"/"truncated"/"representative", incalendar_catalog()andmeta$coverage_class) –meta$coveragenow belongs to sample bookkeeping (below), mirroring geoscales.
Sampled calendars are book-kept (the geoscales convention)
filter_calendar() now records what a sample IS: the result is renamed "base[timeframe:labels-or-hash]" – so two different samples of one parent never collide in registries or joins – with the root parent’s name and totals in meta$parent_name/meta$parent_totals and the surviving fraction of share/weight in meta$coverage (validated against the leaftable at 1e-8; read it with the new calendar_coverage()). Filter-of-filter composes against the root; a filter that keeps everything is now a true no-op. prune_calendar() records its immediate parent in meta$parent_name alongside its existing "base@timeframe" renaming. meta$year_fraction behaves as before.
New
-
calendar_coverage(x, weight = NULL)– surviving fraction of the root parent, per built-in weight column. -
calendar_leaftable(x)– exported accessor for the leaf table (stop reaching forx@leaftable). -
calendar_ancestry(x)– all ancestor-descendant pairs across every ordered timeframe pair (twin ofgeoscales::geoscale_ancestry();calendar_family()remains the adjacent-pairs view). -
calendar_timeframes(x, finest = TRUE)returns just the atom layer (twin ofgeoscales::geoscale_geoframes()).
timescales 0.4.2
The development line rewrites the 0.1 skeleton around a conserving conversion core, a curated calendar catalog, and a ggplot2 viz layer, sharing one naming convention with the sibling package geoscales.
Breaking changes
-
recast_calendar(rule = "sum")conserves totals;"weighted_mean"weights by the declaredshares. Previously values were broadcast to every grid instant and added. - A value column with neither
rule=nor aregister_rule()entry is now an error; the silentweighted_meanfallback is gone. - Uncovered grid points are no longer dropped silently:
na_action = c("drop", "error", "keep")("drop"warns;"keep"retains an explicitNAtimeslice row so totals conserve). -
join_calendar()attaches only a label column named after the calendar by default;timeframes = TRUE/meta = TRUErestore the timeframe and share/weight columns, now"<name>."-prefixed. Existing columns are never overwritten (error). -
Calendar@levelsis now@membersand@leavesis@leaftable, matching geoscales;calendar_from_leaftable()replacescalendar_from_leaves(). - The time dimension is
timeslice(wasslice) in every output and key default;calendar_catalog()columnn_slicesis nown_timeslices. -
expand_calendar()gains ayearoutput column and accepts a vector of years. - The
h168token lives on the newWHOURtimeframe (hour of week, Monday-first) and now maps datetimes correctly.
New features
Conversion
-
recast_calendar(x, from, to, year, rule)converts values between any two calendars via the base datetime grid; identifier (panel) columns are preserved as groups, andto =accepts a timeframe name ("ANNUAL"= the root) for within-calendar aggregation. -
recast()is an S7 generic dispatching on the scale object, so pipelines chain across packages:x |> recast(cal_a, cal_b) |> recast(gs, to = "country"). -
recast_to_timebase()/recast_from_timebase()expose the route halves; their composition equalsrecast_calendar(). -
calendar_map(from, to, year)materialises the conversion as a small crosswalk table;register_calendar_map()installs exact crosswalks andregister_conversion()functional overrides, both keyed by calendar names. - All converters run over
data.frame, tibble,data.table, dtplyr, and arrow inputs; results come back in the input’s class, and lazy inputs return the uncollected query unlesscollect = TRUE. -
join_calendar()supports several calendars on one dataset (the pair of label columns is itself a crosswalk); keys auto-detect from a calendar-named,timeslice, or POSIXctdatetimecolumn. -
base_calendar(years, by, tz)enumerates the cached multi-year datetime grid;datetime_to_timeslice()maps datetimes to timeslice IDs under per-timeframe alignment rules (ALIGNMENT_RULES:exact,drop_last,drop_feb29,repeat_last). -
meta$year_startandmeta$utc_offset_minutesare honoured throughout: the model year spans[anchor(y), anchor(y+1))and local time = UTC + offset. - Rules
"copy"and"sd"joinRECAST_RULES; per-column defaults viaregister_rule()/get_rule()/list_rules()/clear_rules().
Calendars and catalog
-
calendar_catalog()lists 43 curated designs; all ship pre-built in thecalendarsdataset with duration-proportional shares (January is 31/365 of a year, not 1/12). - Six April-start fiscal designs:
fy04_m12,fy04_m12_h24,fy04_q4,fy04_q4_h24,fy04_d365,fy04_d365_h24. The anchoredYEARis the starting Gregorian year (“FY 2021-22” -> 2021); labels stay Gregorian (m04is April) while the member order starts at the anchor. Catalog entries may carryyear_start/utc_offset_minutes(caller arguments win), e.g.calendar("fy04_m12", utc_offset_minutes = 330L)for IST. - A nontrivial
year_startrotates the MONTH/QUARTER member order incalendar_build()(fiscal axes read April-first everywhere). - New timeframes
SEASON,DAYTYPE,HOURTYPE(with tokenss4,wk2,hp3) are fully datetime-convertible. - Navigation and subsetting:
calendar_timeframes(),calendar_timeslices()(withqualified = TRUEnode IDs),calendar_rank(),calendar_family(),calendar_children()/_parents()/_descendants()/_ancestors(),calendar_share(),filter_calendar()/cal[timeframe, labels],prune_calendar(). -
merra2_citiesdataset: hourly 2019 weather for Helsinki, Lima, and Sydney (NASA MERRA-2).
Visualization
-
theme_calendar()draws a solid white plot background (transparent figures are illegible on dark-mode pages); article figures build on a solid background site-wide. - Composable layers
geom_calendar()(datetime mode) andgeom_calendar_tile()(timeslice mode) withtheme_calendar(); facet columns ride throughby=;calendar_breaks(n)thins dense discrete axes while keeping the end values. - Wall calendars:
calendar_wall_plot()(month facets in member order, single-letter weekday headers, year-labelled facets — a fiscal wall reads APR 2019 .. MAR 2020), withcalendar_wall_layout()andcalendar_weekdays()(weekday, week-of-month, anchored week-of-year) underneath. - Structure figures:
calendar_autoplot()(icicle;autoplot()/plot()dispatch here) andcalendar_plot()(heatmap), over the exportedcalendar_layout()geometry.calendar_autoplot(type = "stack")draws the layer-stack view: one plane per timeframe,ANNUALon top, segments at their true duration shares – withviewpresets (oblique/top-down/cavalier/cabinet/military/isometric/ dimetric/trimetric/perspective),angle/ratioobliques,rotate=,direction=, and an almost-touching default spacing.frame=draws each plane’s outline (“sheet”),frame_fill=fills the sheets (best mostly transparent), andconnectors=adds dashed corner guides between planes;colour=/linewidth=style segment borders per plane (defaults"grey35"/0.2, ggplot2’s own sf polygon border), and the canvas hugs the content (tight limits, label room sized to the timeframe names). The stack also takes data:data/zcolour every plane by a timeslice-keyed value, recast to each plane’s timeframe (rule=,year=; base gridby = "hour") so the whole stack shares one continuous scale; fortype = "stack"labels=names timeframes whose member names are drawn on the plane, andpalette = NULLadds no fill scale (bring your own). Mirrored ingeoscales::geoscale_autoplot(type = "stack")(deliberate differences: oblique defaults, palette letter,annual=here vs geometry-onlyprecision=there). - The structure icicle carries data too:
calendar_autoplot(data =, z =, rule =, year =)fills every band with the value recast to that band’s timeframe (dense bands binned with width-weighted means) – the 2D twin of the stack’s data fill. -
merra2_citiesgrew from 3 to all 12 cities of the source extract and from 5 to 11 columns (addslocid– the MERRA-2 grid-cell id bridging to the space dimension – plusW10M,WDIR,ALBEDO,PRECTOTCORR,RHOA). ~353 KB compressed. - README rewritten around a real-data hero (Reykjavik wind on
m12_h24, twinned with the geoscales Iceland map hero) and a five-point “What timescales offers” intro; all README demos now run onmerra2_cities. - Crosswalk registry rounded out:
get_calendar_map()andlist_calendar_maps()joinregister_calendar_map()/clear_calendar_maps()(parity with the geoscales registry), and the register/clear pair gained examples.
Deprecations
Old names warn and forward; removal before 1.0: calendar_recast() -> recast_calendar(), calendar_join() -> join_calendar(), calendar_at_level() -> prune_calendar(), instant_to_timeslice() / instant_to_slice() -> datetime_to_timeslice(), calendar_from_leaves() -> calendar_from_leaftable().
Bug fixes
-
recast_calendar()preserves identifier columns (a city x timeslice panel previously returned only the first group) and no longer sweepsfrom’s timeframe columns into the auto-detected values. -
calendar_build()forwards named...tometaas documented (previously dropped silently); collisions with construction arguments error. -
m12aand other full-cardinality enum vocabularies map datetimes (label match with positional fallback) instead of returningNA.
Documentation
- The intro is the package-named
vignette("timescales"), surfaced as the site’s top-level “Get started” item; all articles sit directly in the Articles menu; superseded URLs redirect. - New
vignette("data-manipulation")(attach, recast, crosswalks, backends) andvignette("visualization")(the ggplot2 integration contract and plot-type tour; absorbs the weather-data vignette). -
vignette("calendars")presents the catalog by family, one icicle per family; the shared *scales glossary ships invignette("concepts"). Vignette code follows the stack-wide tidyverse +|>style.
timescales 0.1.0.9000
- Phase 1 skeleton:
CalendarS7 class with validator, token registry (register_token(),get_token(),list_tokens()), three-layer constructors (calendar(),calendar_build(),calendar_from_leaves()),as_timeframe(), first-generationinstant_to_timeslice()/expand_calendar()/recast(), three vignettes, pkgdown site, CI.
