# Daily to monthly
"t.rast.aggregate",
gs.run_command(input="lst_daily",
="average",
method="1 months",
granularity="lst_avg",
basename="lst_monthly",
output="gran",
suffix=4) nprocs
Time series aggregation
In this second time series tutorial, we will go through different ways of performing aggregations. Temporal aggregation is a very common task when working with time series as it allows us to summarize information and find patterns of change both in space and time.
This tutorial can be run locally or in Google Colab. However, make sure you install GRASS GIS 8.4+, download the sample data and set up your project as explained in the first time series tutorial.
There are two main tools to do time series aggregations in GRASS: t.rast.aggregate and t.rast.series. We’ll demonstrate their usage in the upcoming sections.
Aggregation with granularity
Granularity is the greatest common divisor of the temporal extents (and possible gaps) of all maps in a space-time dataset. To perform time series aggregation with granularity, we use t.rast.aggregate. This tool allows us to aggregate our time series into larger granularities, i.e., from hourly to daily, from daily to weekly, monthly, etc. It also permits to aggregate with ad hoc granularities like 3 minutes, 5 days, 3 months, etc. Supported aggregate methods include average, minimum, maximum, median, etc. See the r.series manual page for more details.
If you are working with data representing absolute time, t.rast.aggregate will shift the start date for each aggregation process depending on the provided temporal granularity as follows:
- years: will start at the first of January, hence 14-08-2012 00:01:30 will be shifted to 01-01-2012 00:00:00
- months: will start at the first day of a month, hence 14-08-2012 will be shifted to 01-08-2012 00:00:00
- weeks: will start at the first day of a week (Monday), hence 14-08-2012 01:30:30 will be shifted to 13-08-2012 01:00:00
- days: will start at the first hour of a day, hence 14-08-2012 00:01:30 will be shifted to 14-08-2012 00:00:00
- hours: will start at the first minute of a hour, hence 14-08-2012 01:30:30 will be shifted to 14-08-2012 01:00:00
- minutes: will start at the first second of a minute, hence 14-08-2012 01:30:30 will be shifted to 14-08-2012 01:30:00
The specification of the temporal relation between the aggregation intervals and the raster map layers to be aggregated is always formulated from the aggregation interval viewpoint. By default, it is set to the contains relation to aggregate all maps that are temporally within an aggregation interval.
Temporal topology refers to the temporal relations among time intervals or instances in different time series. They are mostly useful when sampling one STRDS with another (see t.sample manual). Some examples of temporal relations are start, equal, during, contain, overlap, etc. See below a graphic representation of relations and sampling taken from Gebbert and Pebesma (2014). Note that A1 starts in S1, B3 is during S3, B4 equals S4, B1 overlaps S1.
Monthly and seasonal averages
To demonstrate the basic usage of t.rast.aggregate, let’s create monthly and seasonal time series starting from the lst_daily
time series we created in the time series management tutorial.
# Daily to (sort of) seasonal
"t.rast.aggregate",
gs.run_command(input="lst_daily",
="average",
method="3 months",
granularity="lst_avg",
basename="lst_seasonal",
output="gran",
suffix=4) nprocs
If we would like to follow the so called meteorological seasons, i.e., that spring started with March, then we could have used the where option to shift the starting point of the aggregation period as follows: where="start_time >= '2013-03-01 00:00:00'"
.
Let’s compare the granularities using the timeline plot…
!g.gui.timeline lst_monthly,lst_seasonal
and use grass.jupyter
to create a nice animation of the seasonal time series:
= gj.TimeSeriesMap()
lstseries "lst_seasonal", fill_gaps=False)
lstseries.add_raster_series(="black", at=(5, 50, 2, 6))
lstseries.d_legend(color lstseries.show()
# Optionally, write out to animated GIF
"lstseries.gif") lstseries.save(
For astronomical seasons, i.e., those defined by solstices and equinoxes, we can use t.rast.aggregate.ds as in this example, or for an approximate result:
"t.rast.aggregate",
gs.run_command(input="lst_daily",
="average",
method="start_time >= '2013-03-21 00:00:00'",
where="3 months",
granularity="lst_avg",
basename="lst_seasonal",
output="gran",
suffix=4) nprocs
# Check info
"t.info", input="lst_seasonal") gs.read_command(
# Check raster maps in the STRDS
"t.rast.list", input="lst_seasonal") gs.run_command(
Spring warming
We define spring warming as the velocity with which temperature increases from winter into spring and we can approximate it as the linear regression slope among LST values of February, March and April. Let’s see how to use t.rast.aggregate to estimate yearly spring warming values.
# Define list of months
= [f"{m:02d}" for m in range(2, 5)]
months print(months)
# Annual spring warming
"t.rast.aggregate",
gs.run_command(input="lst_daily",
="annual_spring_warming",
output="spring_warming",
basename="gran",
suffix="slope",
method="1 years",
granularity=f"strftime('%m',start_time)='{months[0]}' or strftime('%m',start_time)='{months[1]}' or strftime('%m', start_time)='{months[2]}'") where
# Check raster maps in the STRDS
"t.rast.list", input="annual_spring_warming") gs.run_command(
= gj.TimeSeriesMap()
spring_warming "annual_spring_warming", fill_gaps=False)
spring_warming.add_raster_series(="black", at=(5, 50, 2, 6))
spring_warming.d_legend(color spring_warming.show()
How would you obtain the average spring warming over the whole time series?
# Average spring warming
"t.rast.series",
gs.run_command(input="annual_spring_warming",
="avg_spring_warming",
output="average")
method
# Display raster map with interactive class
= gj.InteractiveMap(width = 500, use_region=True, tiles="CartoDark")
spw_map "avg_spring_warming")
spw_map.add_raster(= "bottomright")
spw_map.add_layer_control(position spw_map.show()
Full series aggregation
The tool t.rast.series allows us to aggregate complete time series (or only selected parts) with a certain method. Available aggregation methods include average, minimum, maximum, median, etc. See the r.series manual page for details.
Maximum and minimum LST
We’ll demonstrate how to aggregate the whole lst_daily
time series to obtain the maximum and minimum LST value for the period 2014-2018. Note that the input is a STRDS object while the output is a single map in which pixel values correspond to the maximum or minimum value of the time series they represent.
# Get maximum and minimum LST in the STRDS
= ["maximum", "minimum"]
methods
for m in methods:
"t.rast.series",
gs.run_command(input="lst_daily",
=f"lst_{m}",
output=m,
method=4) nprocs
# Change color palette to Celsius
"r.colors",
gs.run_command(map="lst_minimum,lst_maximum",
="celsius") color
Let’s use the InteractiveMap
class from grass.jupyter
to visualize the maximum and minimum LST maps.
# Plot
=gj.InteractiveMap()
lst_map"lst_minimum")
lst_map.add_raster("lst_maximum")
lst_map.add_raster( lst_map.show()
Long term aggregations
If we want to know the temperature on a “typical” January, February and so on, we estimate the so called long-term averages or climatologies for each month. These are usually computed over 20 or 30 years of data, but for the purpose of demonstrating the usage of t.rast.series for long term aggregations, we will use our 5-year time series here.
We will use what we learned in the listing and selection examples in the first time series tutorial, i.e., we need to select all maps for each month to perform the aggregation. So, the input will be the whole time series, but we will use only some maps to estimate the long term monthly average, minimum and maximum values. Let’s see how to do all that with a simple for loop over months and methods:
# Estimate long term aggr for all months and methods
= [f"{m:02d}" for m in range(1, 13)]
months = ["average", "minimum", "maximum"]
methods
for month in months:
print(f"Aggregating all {month} maps")
"t.rast.series",
gs.run_command(input="lst_daily",
=methods,
method=f"strftime('%m', start_time)='{month}'",
where=[f"lst_{method}_{month}" for method in methods]) output
# List newly created maps
= gs.list_grouped(type="raster", pattern="*{average,minimum,maximum}*")['italy_LST_daily'] map_list
Let’s create an animation of the long term average LST.
# List of average maps
= gs.list_grouped(type="raster", pattern="*_average_*")['italy_LST_daily']
map_list
# Animation with SeriesMap class
= gj.SeriesMap()
series
series.add_rasters(map_list)
series.d_barscale() series.show()
Why didn’t we list with t.rast.list?
Bioclimatic variables
Perhaps you have heard of Worldclim or CHELSA bioclimatic variables? Well, these are 19 variables that represent potentially limiting conditions for species. They derive from the combination of temperature and precipitation long term aggregations. Let’s use those that we estimated in the previous example to estimate the bioclimatic variables that include temperature. GRASS has a nice extension, r.bioclim, to estimate bioclimatic variables.
# Install extension
"g.extension", extension="r.bioclim") gs.run_command(
# Get lists of maps needed
= gs.list_grouped(type="raster", pattern="lst_minimum_??")["italy_LST_daily"]
tmin = gs.list_grouped(type="raster", pattern="lst_maximum_??")["italy_LST_daily"]
tmax = gs.list_grouped(type="raster", pattern="lst_average_??")["italy_LST_daily"]
tavg
print(tmin, tmax, tavg)
# Estimate temperature related bioclimatic variables
"r.bioclim",
gs.run_command(=tmin,
tmin=tmax,
tmax=tavg,
tavg="worldclim_") output
# List output maps
type="raster", pattern="worldclim*")["italy_LST_daily"] gs.list_grouped(
Let’s have a look at some of the maps we just created:
# Display raster map with interactive class
= gj.InteractiveMap()
bio_map "worldclim_bio01")
bio_map.add_raster("worldclim_bio02")
bio_map.add_raster( bio_map.show()
Let’s assume a certain insect species can only survive where winter mean temperatures are above 5 degrees. How would you determine suitable areas? We’ll see that in the upcoming tutorial, stay tuned!
If you are curious, maybe you want to have a look at t.rast.mapcalc and t.rast.algebra and try to come up with a solution.
References
- Gebbert, S., Pebesma, E. 2014. TGRASS: A temporal GIS for field based environmental modeling. Environmental Modelling & Software 53, 1-12. DOI.
- Gebbert, S., Pebesma, E. 2017. The GRASS GIS temporal framework. International Journal of Geographical Information Science 31, 1273-1292. DOI.
- Temporal data processing wiki page.
The development of this tutorial was funded by the US National Science Foundation (NSF), award 2303651.