degreedays.api.data
¶
For specifying and receiving degree-day data and temperature data from the API.
If you are new to this module, we suggest you start by looking at
DataApi
.
Class summaries¶
Defines how a set of average degree days should be broken down, including the period in time they should cover. |
|
Contains a set of average degree-day data generated to fulfil an |
|
Defines a specification for a set of average data such as 5-year-average degree days. |
|
Defines how a set of degree days should be broken down (e.g. daily, weekly, or monthly), and the period in time they should cover. |
|
Defines how degree days should be calculated e.g. HDD or CDD to a specific base temperature. |
|
Provides easy, type-safe access to the API's data-related operations. |
|
Contains a set of degree-day data generated to fulfil a |
|
Contains all sets of data generated to fulfil the |
|
Defines a specification of a single set of degree-day data (or temperature data) in all aspects other than the location that the data should be generated for. |
|
Defines up to 120 sets of data that should be generated to fulfil a |
|
Contains a value (e.g. an HDD or CDD value) and an approximate indication of its accuracy. |
|
Defines how a set of dated degree days (in a |
|
Contains a set of dated data (e.g. daily/weekly/monthly degree days) generated to fulfil a |
|
Defines a specification for a set of dated data such as daily, weekly, or monthly degree days covering a specific period in time. |
|
Contains a degree-day value for a specific dated period (a single day or a range of days like a specific week, month, or year). |
|
Defines a location in terms of a longitude/latitude or postal/zip code, leaving it to the API to find the nearest good weather station. |
|
Defines a location for which degree days should be calculated. |
|
Defines a request for one or more sets of data from a particular location. |
|
Contains the data generated to fulfil a |
|
Indicates a |
|
Defines a request for info about the station(s) that would be used to fulfil an equivalent |
|
Contains the location/station-related info returned in response to a |
|
Defines the period in time that a set of degree days should cover. |
|
Contains basic information about a source of data that was used to satisfy a request. |
|
Indicates a |
|
Contains basic information about a weather station. |
|
Defines a temperature value, typically the base temperature of a degree-day calculation. |
|
Defines the units of temperature measurement with class constants |
|
Defines how time-series data should be calculated e.g. temperature data, with hourly figures, in Celsius. |
|
Contains a set of time-series data (e.g. hourly temperature data) generated to fulfil a |
|
Defines a specification for a set of time-series data such as hourly temperature data covering a specific period in time. |
|
Contains a value (e.g. a temperature value) for a specific point in time, and an approximate indication of its accuracy. |
|
Defines the interval (e.g. hourly) that time-series data should be calculated with. |
degreedays.api.data
¶
For specifying and receiving degree-day data and temperature data from the API.
If you are new to this module, we suggest you start by looking at
DataApi
.
- class degreedays.api.data.AverageBreakdown¶
Defines how a set of average degree days should be broken down, including the period in time they should cover.
This is the abstract superclass of the types of
Breakdown
that can be taken by anAverageDataSpec
.To create an
AverageBreakdown
object use thefullYears
static factory method. For example:fiveYearAverage = AverageBreakdown.fullYears(Period.latestValues(5))
AverageDataSpec
has more on how to actually fetch average degree-day data with your specifiedAverageBreakdown
.- static fullYears(period: Period) impl.FullYearsAverageBreakdown ¶
Returns a
FullYearsAverageBreakdown
object that specifies average degree days derived from data covering full calendar years determined by the specified period.- Parameters:
period (Period) – specifies the full calendar years of data that the average figures should be derived from. Typically you’d want to use
Period.latestValues
for this, specifying at least 2 values (since an average of 1 year of data is not very meaningful). But you can also usePeriod.dayRange
- in this case the period may be widened for calculation purposes to make it cover full calendar years.- Raises:
- class degreedays.api.data.AverageDataSet(firstYear: int, lastYear: int, annualAverage: DataValue, monthlyAverages: Sequence[DataValue])¶
Contains a set of average degree-day data generated to fulfil an
AverageDataSpec
for a specificLocation
.See
AverageDataSpec
for example code showing how to fetch anAverageDataSet
of average degree days from the API.- property firstYear: int¶
The first year of the continuous set of data that was used to calculate the average figures.
- property fullRange: DayRange¶
The
degreedays.time.DayRange
object indicating the period of time that is covered by thisDataSet
.
- property lastYear: int¶
The last year of the continuous set of data that was used to calculate the average figures.
- monthlyAverage(monthIndexWithJanAs1: int) DataValue ¶
The average value for the specified month (e.g. pass 1 for the average value for the month of January).
- Parameters:
monthIndexWithJanAs1 (int) – a number between 1 (for January) and 12 (for December).
- Raises:
TypeError – if monthIndexWithJanAs1 is not an int.
IndexError – if monthIndexWithJanAs1 is less than 1 or greater than 12.
- property numberOfYears: int¶
The number of years of data that the average figures were calculated from (e.g. for 5-year-average data this would return 5).
- property percentageEstimated: float¶
A number between 0 and 100 (both inclusive), indicating the overall extent to which this
DataSet
is based on estimated data.Generally speaking, data with a lower percentage-estimated figure is likely to be more reliable than data with a higher percentage-estimated figure.
- class degreedays.api.data.AverageDataSpec(calculation: Calculation, averageBreakdown: AverageBreakdown)¶
Defines a specification for a set of average data such as 5-year-average degree days.
To create an
AverageDataSpec
object useDataSpec.average
.An
AverageDataSpec
specifies a set of average degree days in terms of:the calculation process used to calculate the dated figures that the averages are derived from (heating or cooling, and the base temperature); and
its breakdown (which years of data to include in the averaged figures).
Example
AverageDataSpec
code:¶Here’s how you could specify 10-year average heating degree days with a base temperature of 70°F:
averageDataSpec = DataSpec.average( Calculation.heatingDegreeDays(Temperature.fahrenheit(70)), AverageBreakdown.fullYears(Period.latestValues(10)))
You could then send that
AverageDataSpec
to the API as part of aLocationDataRequest
, and get a response containing anAverageDataSet
back:api = DegreeDaysApi.fromKeys(AccountKey(yourStringAccountKey), SecurityKey(yourStringSecurityKey)) request = new LocationDataRequest( Location.postalCode('02630', 'US'), new DataSpecs(averageDataSpec)) response = api.dataApi.getLocationData(request) averageData = response.dataSets[averageDataSpec] for month in range(1, 12): print('Average HDD for month %i: %g' % (month, averageData.monthlyAverage(month).value)) print('Annual average HDD: %g' % averageData.annualAverage.value)
To request multiple sets of average data with different calculation processes (e.g. multiple different base temperatures) and/or different breakdowns, simply put multiple
AverageDataSpec
objects into theDataSpecs
object that you pass into yourLocationDataRequest
.See also:
DatedDataSpec
,TimeSeriesDataSpec
- class degreedays.api.data.Breakdown¶
Defines how a set of degree days should be broken down (e.g. daily, weekly, or monthly), and the period in time they should cover.
To create a
Breakdown
object, use the static factory methods on theDatedBreakdown
andAverageBreakdown
subclasses.
- class degreedays.api.data.Calculation¶
Defines how degree days should be calculated e.g. HDD or CDD to a specific base temperature.
To create a
Calculation
object you can use the static factory methods of this class. For example:hdd = Calculation.heatingDegreeDays(Temperature.celsius(12.5)) cdd = Calculation.coolingDegreeDays(Temperature.celsius(21))
- static coolingDegreeDays(baseTemperature: Temperature) impl.CoolingDegreeDaysCalculation ¶
Returns a
CoolingDegreeDaysCalculation
object with the specified base temperature.- Parameters:
baseTemperature (Temperature) – the base temperature that the cooling degree days should be calculated to.
- Raises:
TypeError – if baseTemperature is not a
Temperature
object.
- static heatingDegreeDays(baseTemperature: Temperature) impl.HeatingDegreeDaysCalculation ¶
Returns a
HeatingDegreeDaysCalculation
object with the specified base temperature.- Parameters:
baseTemperature (Temperature) – the base temperature that the heating degree days should be calculated to.
- Raises:
TypeError – if baseTemperature is not a
Temperature
object.
- class degreedays.api.data.DataApi(requestProcessor)¶
Provides easy, type-safe access to the API’s data-related operations.
To get a
DataApi
object and use it, create adegreedays.api.DegreeDaysApi
object, get theDataApi
object from that, then call the method you want. For example, to send aLocationDataRequest
to the API and get aLocationDataResponse
back:api = DegreeDaysApi.fromKeys( AccountKey(yourStringAccountKey), SecurityKey(yourStringSecurityKey)) response = api.dataApi.getLocationData(yourLocationDataRequest)
See
getLocationData(locationDataRequest)
andgetLocationInfo(locationInfoRequest)
for much more info and sample code.- getLocationData(locationDataRequest: LocationDataRequest) LocationDataResponse ¶
Sends your request for data (for a specified location) to the API servers, returning a response containing data you requested, or throwing an appropriate subclass of
degreedays.api.DegreeDaysApiError
if something goes wrong.- Parameters:
locationDataRequest (LocationDataRequest) – specifies the data you want and the location you want it for.
- Returns:
LocationDataResponse
containing the data you requested or as much of it as was available for the location you specified (given the rules explained further below).- Raises:
LocationError – if the request fails because of problems relating to the specified
Location
.ServiceError – if the request fails because of a problem with the API service (sorry!).
RateLimitError – if you hit the
degreedays.api.RateLimit
for your account’s plan, and need to wait a little while before it’s reset.InvalidRequestError – if the request that is sent to the API servers is invalid (e.g. if it is authenticated with invalid API access keys).
TransportError – if there’s a problem sending the request to the API servers, or a problem getting the API’s response back.
DegreeDaysApiError – the superclass of all the exceptions listed above.
TypeError – if locationDataRequest is not a
LocationDataRequest
object.
Sample
LocationDataRequest
/LocationDataResponse
code¶Here’s a simple example showing how to fetch monthly 65°F-base-temperature heating degree days, covering the 12 months of 2024, for an automatically-selected weather station near US zip code 02633 (which is on Cape Cod so you can use the free test API account). The HDD figures are output to the command line:
api = DegreeDaysApi.fromKeys( AccountKey('test-test-test'), SecurityKey('test-test-test-test-test-test-test-test-test-test-test-test-test')) dayRange = degreedays.time.DayRange( datetime.date(2024, 1, 1), datetime.date(2024, 12, 31)) hddSpec = DataSpec.dated( Calculation.heatingDegreeDays(Temperature.fahrenheit(65)), DatedBreakdown.monthly(Period.dayRange(dayRange))) request = LocationDataRequest( Location.postalCode('02532', 'US'), DataSpecs(hddSpec)) response = api.dataApi.getLocationData(request) hddData = response.dataSets[hddSpec] for v in hddData.values: print('%s: %g' % (v.firstDay, v.value))
And here is a more complex example that fetches HDD, CDD, and hourly temperature data, covering just the latest available day, for a longitude/latitude position on Cape Cod, Massachusetts (again chosen so you can try out this code using the free test API account):
api = DegreeDaysApi.fromKeys( AccountKey('test-test-test'), SecurityKey('test-test-test-test-test-test-test-test-test-test-test-test-test')) breakdown = DatedBreakdown.daily(Period.latestValues(1)) hddSpec = DataSpec.dated( Calculation.heatingDegreeDays(Temperature.celsius(15.5)), breakdown) cddSpec = DataSpec.dated( Calculation.coolingDegreeDays(Temperature.celsius(21)), breakdown) hourlyTempsSpec = DataSpec.timeSeries( TimeSeriesCalculation.hourlyTemperature(TemperatureUnit.CELSIUS), breakdown) request = LocationDataRequest( Location.longLat(degreedays.geo.LongLat(-70.305634, 41.695475)), DataSpecs(hddSpec, cddSpec, hourlyTempsSpec)) response = api.dataApi.getLocationData(request) hddData = response.dataSets[hddSpec] cddData = response.dataSets[cddSpec] hourlyTempsData = response.dataSets[hourlyTempsSpec] print('Station ID: %s' % response.stationId) for v in hddData.values: print('%s HDD: %g' % (v.firstDay, v.value)) for v in cddData.values: print('%s CDD: %g' % (v.firstDay, v.value)) for v in hourlyTempsData.values: print('%s: %g C' % (v.datetime, v.value))
Both examples above specify a location on Cape Cod, Massachusetts, so you can try them out using the free test API account. To access data for locations worldwide (whether station IDs, postal/zip codes, or longitude/latitude positions), you can sign up for a full API account on our website, swap your own account key and security key into the code samples above, then change the location however you wish.
Expanding on these examples¶
The examples above are just a starting point…
The
LocationDataRequest
is highly configurable:You can specify the
Location
you want data for as a weather-station ID or a geographic location (postal/zip code, or longitude/latitude position). For geographic locations the API will automatically select the best weather station to satisfy your request.There are various components that enable you to specify exactly what each set of data should contain. Each
DataSpec
can be either aDatedDataSpec
(for daily/weekly/monthly/yearly/custom degree days), anAverageDataSpec
(for average degree days), or aTimeSeriesDataSpec
(for hourly temperature data). Each of these is configured with objects that determine the dataCalculation
(orTimeSeriesCalculation
), theBreakdown
, and thePeriod
of coverage that you want.You can fetch multiple sets of data from a location in a single request. For example, you might want to fetch HDD and CDD with multiple base temperatures each. To do this just create the
DataSpecs
in your request with multiple differentDataSpec
items.
The
LocationDataResponse
also contains information about the weather station(s) used to generate the returned data. If you request data from a geographic location initially, you might want to use the station ID to fetch updates later. If you are specifying geographic locations, but storing data by station ID, you can avoid re-fetching data unnecessarily by usinggetLocationInfo(locationInfoRequest)
to get the station ID that would be returned by an equivalent call togetLocationData(locationDataRequest)
. We call this two-stage data fetching.Error handling would be important for production code. Catching
DegreeDaysApiException
will cover everything that you should be prepared for, but it’s often useful to get more detail. Check the list of exceptions for this method further above and the notes on getting DataSet objects out of a DataSets object to see exactly what subclasses ofDegreeDaysApiException
can be thrown.
Rules that govern what you can get in response¶
It’s worth understanding the rules that govern what you can get in response to a request for one or more sets of data from a particular location:
Stations that data can come from:
If your request specifies a
StationIdLocation
(created viaLocation.stationId
) then the API will only ever return data from that station. It will never substitute in data from another station.If your request specifies a
GeographicLocation
the API will choose which station(s) to use automatically. The choice will depend on the data you requested as well as the location you requested it for. Some stations have more data than others, and the quality of a station’s data can vary over time. The API will choose the station(s) that can best satisfy your specific request.The API will never send a
LocationDataResponse
for an inactive station. An inactive station is one that hasn’t sent any usable weather reports for roughly 10 days or more (10 being an approximate number that is subject to change). SeeLocationError.isDueToLocationNotSupported
for more information on inactive stations. If you request data from an inactive station, or for aGeographicLocation
for which no active station can be found, you will get aLocationError
to indicate that the location is not supported.
When you request more data than is available:
If your request specifies data for a location for which an active station exists, you should, barring other failures, get a
LocationDataResponse
. But, if any of theDataSpec
objects that you specified cannot be satisfied (either fully or partially), you will get aSourceDataError
each time you try to get the correspondingDataSet
from theDataSets
object held by the response.If there’s not enough data to fully satisfy your request, the API will return partial sets of data if it can. For example, you might request 10 years of data, but only get 5. Because data is only returned for active stations, you can rely on recent times being covered if requested (usually including yesterday’s data in the station’s time-zone, but always to within around 10 days or so of that day). But data from further back could be missing.
If you’d rather the API gave you a
SourceDataError
(see above) than any partially-completeDataSet
, make sure to specify the minimumNumberOfValues onPeriod.latestValues
or the minimumDayRange onPeriod.dayRange
for thePeriod
objects in your request. Unless you specify minimums, the API will return the best it can from within the specification you give.
Other data guarantees:
Excepting the widening rules that apply when a
DayRangePeriod
(created withPeriod.dayRange
) is specified imprecisely, the API will never return more data than you asked for, or data from outside of the range that you asked for.The API will never return data with gaps in it. No gaps, and no special error values like NaN, 0, -1, 9999 etc. If a station has small gaps in its source temperature data, the API will fill those gaps with estimates before calculating any degree days. But larger gaps are not tolerated, and the API will only ever use data from after such gaps. If a larger gap is ongoing (i.e. there is no good data after it), the station will be declared inactive (see above). This approach ensures that active stations will always have contiguous sets of data that run through to recent days.
- getLocationInfo(locationInfoRequest: LocationInfoRequest) LocationInfoResponse ¶
A lightweight alternative to
getLocationData(locationDataRequest)
that returns info about the station(s) that would be used to satisfy an equivalentLocationDataRequest
, but not the data itself. Typically you would specify aGeographicLocation
in theLocationInfoRequest
, using thisgetLocationInfo(locationInfoRequest)
method to map postal/zip codes or longitude/latitude positions to station IDs.- Parameters:
locationInfoRequest (LocationInfoRequest) – specifies the location you want data for and the data that you want (as this can affect the station-selection process for
GeographicLocation
types).- Returns:
LocationInfoResponse
containing info about the station(s) that would be used to satisfy an equivalentLocationDataRequest
.- Raises:
LocationError – if the request fails because of problems relating to the specified
Location
.ServiceError – if the request fails because of a problem with the API service (sorry!).
RateLimitError – if you hit the
degreedays.api.RateLimit
for your account’s plan, and need to wait a little while before it’s reset.InvalidRequestError – if the request that is sent to the API servers is invalid (e.g. if it is authenticated with invalid API access keys).
TransportError – if there’s a problem sending the request to the API servers, or a problem getting the API’s response back.
DegreeDaysApiError – the superclass of all the exceptions listed above.
TypeError – if locationDataRequest is not a
LocationDataRequest
object.
This
getLocationInfo(locationInfoRequest)
method can be useful if you have a database of data stored by station ID, but are usingGeographicLocation
types (postal/zip codes or longitude/latitude positions) to determine which station ID to use for each of your real-world locations. A call to thisgetLocationInfo(locationInfoRequest)
method will only ever take one request unit, whilst a call togetLocationData(locationDataRequest)
can take many more (depending on how much data it fetches), so it often makes sense to use this method to avoid the overhead of re-fetching data that you already have stored. We call this two-stage data fetching.Note, however, that this returns nothing that isn’t also returned by a call to
getLocationData(locationDataRequest)
. So, if you know you’ll be fetching data anyway, you might as well usegetLocationData(locationDataRequest)
from the start.Sample
LocationInfoRequest
/LocationInfoResponse
code¶Here’s a simple example showing how to find out what station ID the API would use to provide 10 recent calendar years’ worth of data for US zip code 02633 (which is on Cape Cod so you can use the free test API account). The station ID is output to the command line:
api = DegreeDaysApi.fromKeys( AccountKey(yourStringAccountKey), SecurityKey(yourStringSecurityKey)) hddSpec = DataSpec.dated( Calculation.heatingDegreeDays(Temperature.fahrenheit(65)), DatedBreakdown.yearly(Period.latestValues(10))) request = LocationInfoRequest( Location.postalCode('02532', 'US'), DataSpecs(hddSpec)) response = api.dataApi.getLocationInfo(request) print('Station ID: %s' % response.stationId)
Exceptions and data availability¶
The exceptions thrown by this
getLocationInfo(locationInfoRequest)
method are the same as those thrown bygetLocationData(locationDataRequest)
, and they are thrown under exactly the same circumstances.However, because the
LocationInfoResponse
returned by this method does not contain any data (i.e. noDataSets
), there’s no way to tell from this whether an equivalentLocationDataResponse
would actually contain any or all of the data you want. So, although you can call this method to determine what station ID the API would use for an equivalent call togetLocationData(LocationDataRequest)
, you would have to actually make that call to be sure that you could get all the data you wanted.
- class degreedays.api.data.DataSet¶
Contains a set of degree-day data generated to fulfil a
DataSpec
for a specificLocation
.- property fullRange: DayRange¶
The
degreedays.time.DayRange
object indicating the period of time that is covered by thisDataSet
.
- property percentageEstimated: float¶
A number between 0 and 100 (both inclusive), indicating the overall extent to which this
DataSet
is based on estimated data.Generally speaking, data with a lower percentage-estimated figure is likely to be more reliable than data with a higher percentage-estimated figure.
- class degreedays.api.data.DataSets(dataSpecsOrNone: DataSpecs | None, stringKeyToResultDict: Mapping[str, DataSet | api.Failure])¶
Contains all sets of data generated to fulfil the
DataSpecs
for a specificLocation
.How to get
DataSet
objects out of aDataSets
¶You need to keep hold of the
DataSpec
objects that you specified in your request. Then you can access theDataSet
object that corresponds to eachDataSpec
like so:datedDataSet = dataSets[yourDatedDataSpec] averageDataSet = dataSets[yourAverageDataSpec] timeSeriesDataSet = dataSets[yourTimeSeriesDataSpec]
In rare cases you might also want to access the
DataSet
objects using the keys that were set in theDataSpecs
sent with theLocationDataRequest
, likedataSets[stringKey]
.Watch out for
SourceDataError
¶If the API servers were unable to generate a particular
DataSet
(e.g. because the weather station did not have the weather-data records necessary to generate data to match yourDataSpec
), you’ll get aSourceDataError
when you try to get theDataSet
object out of theDataSets
. For example:try: dataSet = dataSets[yourDataSpec] except SourceDataError as e: print('Failed to get data for', yourDataSpec, e)
- class degreedays.api.data.DataSpec¶
Defines a specification of a single set of degree-day data (or temperature data) in all aspects other than the location that the data should be generated for.
A
DataSpec
defines a single set of data only (e.g. degree days in just one base temperature), but theDataSpecs
class will enable you to include multipleDataSpec
objects in a singleLocationDataRequest
.To create a
DataSpec
object you can use the static factory methods of this class. For example:thirtyMostRecentHddValues = DataSpec.dated( Calculation.heatingDegreeDays(Temperature.fahrenheit(55)), DatedBreakdown.daily(Period.latestValues(30))) fiveYearAverageCdd = DataSpec.average( Calculation.coolingDegreeDays(Temperature.fahrenheit(65)), AverageBreakdown.fullYears(Period.latestValues(5))) hourlyTempsForLastCalendarYear = DataSpec.timeSeries( TimeSeriesCalculation.hourlyTemperature(TemperatureUnit.CELSIUS), DatedBreakdown.yearly(Period.latestValues(1)))
See
DatedDataSpec
,AverageDataSpec
, andTimeSeriesDataSpec
for example code showing how to use each one individually to fetch data from the API. Though do remember that, as mentioned above, you can also fetch multiple sets of data in a single request.- static average(calculation: Calculation, averageBreakdown: AverageBreakdown) AverageDataSpec ¶
Returns an
AverageDataSpec
object with the specifiedCalculation
andAverageBreakdown
.- Parameters:
calculation (Calculation) – defines the way in which the degree days should be calculated in terms of their base temperature and whether they should be heating degree days or cooling degree days.
averageBreakdown (AverageBreakdown) – defines the way in which the data should be broken down and the period that it should cover.
- Raises:
TypeError – if calculation is not a
Calculation
object, or if averageBreakdown is not anAverageBreakdown
object.
See
AverageDataSpec
for more info and a code sample.
- static dated(calculation: Calculation, datedBreakdown: DatedBreakdown) DatedDataSpec ¶
Returns a
DatedDataSpec
object with the specifiedCalculation
andDatedBreakdown
.- Parameters:
calculation (Calculation) – defines the way in which the degree days should be calculated in terms of their base temperature and whether they should be heating degree days or cooling degree days.
datedBreakdown (DatedBreakdown) – defines the way in which the data should be broken down and the period that it should cover.
- Raises:
TypeError – if calculation is not a
Calculation
object, or if datedBreakdown is not aDatedBreakdown
object.
See
DatedDataSpec
for more info and a code sample.
- static timeSeries(timeSeriesCalculation: TimeSeriesCalculation, datedBreakdown: DatedBreakdown) TimeSeriesDataSpec ¶
Returns a
TimeSeriesDataSpec
object with the specifiedTimeSeriesCalculation
andDatedBreakdown
.- Parameters:
timeSeriesCalculation (TimeSeriesCalculation) – defines how the time-series data should be calculated (e.g. specifying hourly temperature data, in Celsius).
datedBreakdown (DatedBreakdown) – defines the period of time that the time-series data should cover (read more on how this works).
- Raises:
TypeError – if timeSeriesCalculation is not a
TimeSeriesCalculation
object or datedBreakdown is not aDatedBreakdown
object.
See
TimeSeriesDataSpec
for more info and a code sample.
- class degreedays.api.data.DataSpecs(*args: DataSpec | Iterable[DataSpec] | Mapping[str, DataSpec])¶
Defines up to 120 sets of data that should be generated to fulfil a
LocationDataRequest
.- Parameters:
*args – up to 120
DataSpec
objects, or one or more sequences (e.g. lists or tuples) containing a total of up to 120DataSpec
objects. In rare cases you may want to pass in aMapping[str, DataSpec]
of string keys toDataSpec
objects.- Raises:
TypeError – if you pass in anything that isn’t a
DataSpec
object, a sequence (e.g. list or tuple) ofDataSpec
objects, or a singleMapping[str, DataSpec]
(in the unlikely event that you are defining your own keys).ValueError – if you pass in zero
DataSpec
objects or more than 120 of them.
When requesting multiple sets of data for a particular location, it makes sense to use multiple
DataSpec
objects to retrieve as much data as possible in a single request. This is cheaper and faster than fetching the same data across multiple requests. For example, for a particular location you might want to request heating and cooling degree days at 5 different base temperatures each. This would require 10 differentDataSpec
objects in total. By putting all 10DataSpec
objects in a singleDataSpecs
object, you could fetch all 10 sets of data in a single request.For example, if
ds1
tods10
areDataSpec
objects you have created already:dataSpecs = DataSpecs(ds1, ds2, ds3, ds4, ds5, ds6, ds7, ds8, ds9, ds10)
For most use cases, all you will ever need to do with this class is pass in the
DataSpec
objects you want, then pass theDataSpecs
into aLocationDataRequest
object. However, there is a small chance that you could find it useful to understand about keys and the uniqueness ofDataSpec
objects:Keys¶
The XML request (which this Python library generates internally) requires that each
DataSpec
be assigned a unique string key attribute so that its correspondingDataSet
can be found in the XML response (which this Python library parses internally). ThisDataSpecs
class is central to the way that the keys are created and managed. It essentially gives you two options for key management:You can let this
DataSpecs
class generate and manage the keys entirely, such that you never have to see them or use them directly. You can fetch yourDataSet
objects out of theLocationDataResponse
using theDataSpec
objects that you created for the request (and kept on hand for this purpose). We recommend this approach in virtually all cases.You can specify your own keys explicitly. If this is the case, create a
DataSpecs
object by passing in aMapping
(e.g. adict
) with your own custom key-to-DataSpec
mappings.
Uniqueness of
DataSpec
objects¶There is no point in submitting a request containing multiple identical
DataSpec
objects, as it would lead to unnecessary duplication in the results and unnecessary bandwidth usage for the communication between the client and API servers.Unless you are specifying your own keys (by passing in a
Mapping[str, DataSpec]
), this class is set up to prevent duplicateDataSpec
objects from being submitted as part of a request. It does this by filtering and discarding all duplicateDataSpec
objects passed in when an instance is created. So, if you pass in 10DataSpec
objects, and only 5 of those are unique, then only the 5 uniqueDataSpec
objects will be stored internally and sent as part of the XML request.Because discarded duplicate
DataSpec
objects are identical to their retained counterparts, you can still use the duplicates to retrieve fetched data out of theLocationDataResponse
. In other words, the internal process of filtering and discarding duplicateDataSpec
objects should not affect the way in which you use this API.If you create your own keys, then duplicate
DataSpec
objects are not filtered. Filtering duplicateDataSpec
objects would also involve filtering the keys associated with the duplicates, and this could cause problems for code of yours that was expecting those keys in the XML response. You can rely on the fact that, if you define your ownDataSpec
keys, your XML response will contain entries for all of your keys, irrespective of whether some of the data associated with those keys is duplicated.See also:
DataApi.getLocationData(locationDataRequest)
- getKey(dataSpec: DataSpec) str ¶
Returns the non-empty string key associated with dataSpec.
- Parameters:
dataSpec (DataSpec) – a a data specification held by this
DataSpecs
.- Raises:
See also: keys
- property keys: Collection[str]¶
Gives access to the string keys that are assigned to the
DataSpec
objects stored within thisDataSpecs
.See also: keys
- class degreedays.api.data.DataValue(value: float, percentageEstimated: float)¶
Contains a value (e.g. an HDD or CDD value) and an approximate indication of its accuracy.
- property percentageEstimated: float¶
A number between 0 and 100 (both inclusive), indicating the extent to which the calculated
value
is based on estimated data.Generally speaking, a value with a lower percentage-estimated figure is likely to be more reliable than one with a higher percentage-estimated figure.
- class degreedays.api.data.DatedBreakdown¶
Defines how a set of dated degree days (in a
DatedDataSet
) should be broken down, including the period in time they should cover.This is the abstract superclass of the types of
Breakdown
that can be taken by aDatedDataSpec
.To create a
DatedBreakdown
object you can use the static factory methods of this class. For example:dailyBreakdownOverLast30Days = DatedBreakdown.daily( Period.latestValues(30)) weeklyBreakdownFor2024WithWeeksStartingOnMonday = DatedBreakdown.weekly( Period.dayRange(degreedays.time.DayRange( datetime.date(2024, 1, 1), datetime.date(2024, 12, 31))), DayOfWeek.MONDAY) monthlyBreakdownOverLast12Months = DatedBreakdown.monthly( Period.latestValues(12)) yearlyBreakdownOverLast5Years = DatedBreakdown.yearly( Period.latestValues(5))
As you can see from the above examples, defining the
Period
is an important part of defining aDatedBreakdown
. See that class for more information on the relevant options.- property allowPartialLatest: bool¶
True if the latest day range can be partially filled (i.e. incomplete); False otherwise (the default case).
When specifying time-series data (like hourly temperature data) via a
TimeSeriesDataSpec
, you can specify a breakdown with this allowPartialLatest property set to True, to tell the API to include values for the current day so far. For example:hourlyTempsIncludingToday = DataSpec.timeSeries( TimeSeriesCalculation.hourlyTemperature(TemperatureUnit.CELSIUS), DatedBreakdown.daily(Period.latestValues(31), allowPartialLatest=True))
If you requested the above-specified hourly temperature data at, say, 11:42 on any given day, you could expect it to include values for 00:00, 01:00, 02:00 through to 11:00 on that day (bearing in mind that some stations are slower to report than others so you won’t always get the absolute latest figures).
Please note that the most recent time-series data can be a little volatile, as weather stations sometimes send multiple reports for the same time, some delayed, and some marked as corrections for reports they sent earlier. Our system generates time-series data using all the relevant reports that each weather station has sent, but the generated figures may change if delayed or corrected reports come through later. If you are storing partial-latest time-series data we suggest you overwrite it later with figures generated after the day has completed and any delayed/corrected reports have had time to filter through.
This allowPartialLatest property exists mainly for time-series data, but you can also set it to True on a breakdown for degree days, to specify that the data can include a value for the latest partial week/month/year. For example:
monthlyHddIncludingPartialLatest = DataSpec.dated( Calculation.heatingDegreeDays(Temperature.fahrenheit(65)), DatedBreakdown.monthly(Period.latestValues(12), allowPartialLatest=True))
If you requested the above-specified monthly degree-day data on, say, June 22nd, you could expect the last of the 12 values returned to cover from the start of June 1st through to the end of June 21st (assuming a good weather station with frequent reporting and minimal delays). If you left allowPartialLatest with its default value of False, the last of the 12 values returned would be for the full month of May (i.e. from the start of May 1st to the end of May 31st), as the monthly figure for June wouldn’t become available until June ended.
Unlike for time-series data (specified with
TimeSeriesDataSpec
), this property will never cause degree days to be calculated for partial days. So for degree days this property will only make a difference onweekly
,monthly
,yearly
, andcustom
breakdowns with day ranges covering multiple days.Any partial-latest day range will always start on the usual day (i.e. it will never be cut short at the start), it’s only the end that can be cut short. This is true for both degree days and time-series data.
- static custom(dayRanges: degreedays.time.DayRanges, allowPartialLatest: bool = False) impl.CustomBreakdown ¶
Returns a
CustomBreakdown
object that specifies degree days broken down to match thedegreedays.time.DayRanges
passed in.- Parameters:
dayRanges (DayRanges) – specifying the dates that each degree-day figure should cover (typically matching the dates of your energy-usage records).
allowPartialLatest (bool) – True to specify that the latest specified day range can be partially filled (i.e. incomplete), or False if it must be complete (the default behaviour). Setting this to True is mainly useful if you are fetching time-series data (via
TimeSeriesDataSpec
) and you want it to include values for the current (incomplete) day. SeeallowPartialLatest
for more on this.
- Raises:
TypeError – if dayRanges is not a
degreedays.time.DayRanges
object, or if allowPartialLatest is not a bool.
- static daily(period: Period, allowPartialLatest: bool = False) impl.DailyBreakdown ¶
Returns a
DailyBreakdown
object that specifies daily degree days covering the specified period in time.- Parameters:
period (Period) – the period in time that the daily breakdown should cover.
allowPartialLatest (bool) – True to specify that the latest day of data can be partially filled (i.e. incomplete), or False if the latest day must be complete (the default behaviour). Setting this to True is mainly useful if you are fetching time-series data (via
TimeSeriesDataSpec
) and you want it to include values for the current (incomplete) day. SeeallowPartialLatest
for more on this.
- Raises:
TypeError – if period is not a
Period
object, or allowPartialLatest is not a bool.
- static monthly(period: Period, startOfMonth: degreedays.time.StartOfMonth = StartOfMonth(1), allowPartialLatest: bool = False) impl.MonthlyBreakdown ¶
Returns a
MonthlyBreakdown
object that specifies monthly degree days covering the specified period in time, with each “month” starting on the specified day of the month.- Parameters:
period (Period) – the period in time that the monthly breakdown should cover.
startOfMonth (StartOfMonth) – specifying which day (between 1 and 28 inclusive) should be taken as the first of each “month”.
StartOfMonth(1)
is the default value and specifies regular calendar months.allowPartialLatest (bool) – True to specify that the latest month of data can be partially filled (i.e. incomplete), or False if the latest month must be complete (the default behaviour). Setting this to True is mainly useful if you are fetching time-series data (via
TimeSeriesDataSpec
) and you want it to include the current (incomplete) month, including values for the current (incomplete) day. SeeallowPartialLatest
for more on this.
- Raises:
TypeError – if period is not a
Period
object, or if startOfMonth is not adegreedays.time.StartOfMonth
object, or if allowPartialLatest is not a bool.
- static weekly(period: Period, firstDayOfWeek: degreedays.time.DayOfWeek, allowPartialLatest: bool = False) impl.WeeklyBreakdown ¶
Returns a
WeeklyBreakdown
object that specifies weekly degree days covering the specified period in time.To avoid the potential for confusion, you must explicitly specify the day of the week that you want each “week” to start on. For example, if a “week” should run from Monday to Sunday (inclusive), specify the firstDayOfWeek parameter as
degreedays.time.DayOfWeek.MONDAY
.- Parameters:
period (Period) – the period in time that the weekly breakdown should cover.
firstDayOfWeek (DayOfWeek) – indicates which day should be taken as the first of each week.
allowPartialLatest (bool) – True to specify that the latest week of data can be partially filled (i.e. incomplete), or False if the latest week must be complete (the default behaviour). Setting this to True is mainly useful if you are fetching time-series data (via
TimeSeriesDataSpec
) and you want it to include the current (incomplete) week, including values for the current (incomplete) day. SeeallowPartialLatest
for more on this.
- Raises:
TypeError – if period is not a
Period
object, or if firstDayOfWeek is not adegreedays.time.DayOfWeek
object, or if allowPartialLatest is not a bool.
- static yearly(period: Period, startOfYear: degreedays.time.StartOfYear = StartOfYear(1, 1), allowPartialLatest: bool = False) impl.YearlyBreakdown ¶
Returns a
YearlyBreakdown
object that specifies yearly degree days covering the specified period in time, with each “year” starting on the specified day of the year.- Parameters:
period (Period) – the period in time that the yearly breakdown should cover.
startOfYear (StartOfYear) – specifying which day of the year (month and day of the month) should be taken as the first of each “year”.
StartOfYear(1, 1)
is the default value and specifies regular calendar years.allowPartialLatest (bool) – True to specify that the latest year of data can be partially filled (i.e. incomplete), or False if the latest year must be complete (the default behaviour). Setting this to True is mainly useful if you are fetching time-series data (via
TimeSeriesDataSpec
) and you want it to include the current (incomplete) year, including values for the current (incomplete) day. SeeallowPartialLatest
for more on this.
- Raises:
TypeError – if period is not a
Period
object, or if startOfYear is not adegreedays.time.StartOfYear
object, or if allowPartialLatest is not a bool.
- class degreedays.api.data.DatedDataSet(percentageEstimated: float, values: Sequence[DatedDataValue])¶
Contains a set of dated data (e.g. daily/weekly/monthly degree days) generated to fulfil a
DatedDataSpec
for a specificLocation
.See
DatedDataSpec
for example code showing how to fetch aDatedDataSet
of degree days from the API.See also:
AverageDataSet
,TimeSeriesDataSet
- property fullRange: DayRange¶
The
degreedays.time.DayRange
object indicating the period of time that is covered by thisDataSet
.
- property isContiguous: bool¶
True if each contained
DatedDataValue
starts the day after the previousDatedDataValue
ended (i.e. no gaps); False otherwise.This will always be True for daily, weekly, monthly, or yearly data returned by the API.
- property percentageEstimated: float¶
A number between 0 and 100 (both inclusive), indicating the overall extent to which this
DataSet
is based on estimated data.Generally speaking, data with a lower percentage-estimated figure is likely to be more reliable than data with a higher percentage-estimated figure.
- property values: Sequence[DatedDataValue]¶
A non-empty chronologically-ordered
Sequence
of theDatedDataValue
objects that make up thisDatedDataSet
.
- class degreedays.api.data.DatedDataSpec(calculation: Calculation, datedBreakdown: DatedBreakdown)¶
Defines a specification for a set of dated data such as daily, weekly, or monthly degree days covering a specific period in time.
To create a
DatedDataSpec
object useDataSpec.dated
.A
DatedDataSpec
specifies a set of degree days in terms of:its calculation process (heating or cooling, and the base temperature); and
its breakdown (e.g. daily, weekly, or monthly, and the period covered).
Example
DatedDataSpec
code:¶Here’s how you could specify monthly heating degree days with a base temperature of 15.5°C covering the whole of 2019:
datedDataSpec = DataSpec.dated( Calculation.heatingDegreeDays(Temperature.celsius(15.5)), DatedBreakdown.monthly(Period.dayRange(degreedays.time.DayRange( datetime.date(2019, 1, 1), datetime.date(2019, 12, 31)))))
You could then send that
DatedDataSpec
to the API as part of aLocationDataRequest
, and get a response containing anDatedDataSet
back:api = DegreeDaysApi.fromKeys(AccountKey(yourStringAccountKey), SecurityKey(yourStringSecurityKey)) request = LocationDataRequest( Location.stationId("KHYA"), DataSpecs(datedDataSpec)) response = api.dataApi.getLocationData(request) datedData = response.dataSets[datedDataSpec] for v in datedData.values: print('%s: %g' % (v.dayRange, v.value))
To request multiple sets of dated data with different calculation processes (e.g. multiple different base temperatures) and/or different breakdowns, simply put multiple
DatedDataSpec
objects into theDataSpecs
object that you pass into yourLocationDataRequest
.See also:
AverageDataSpec
,TimeSeriesDataSpec
- property breakdown: DatedBreakdown¶
The
DatedBreakdown
object that defines the way in which the degree days should be broken down and the period in time that they should cover.
- property calculation: Calculation¶
The
Calculation
object that defines the way in which the degree days should be calculated in terms of their base temperature and whether they should be heating degree days or cooling degree days.
- class degreedays.api.data.DatedDataValue(value: float, percentageEstimated: float, dayRange: DayRange)¶
Contains a degree-day value for a specific dated period (a single day or a range of days like a specific week, month, or year).
- property dayRange: DayRange¶
The
DayRange
object indicating the period in time that thisDatedDataValue
covers.
- property firstDay: date¶
Returns the first
datetime.date
of the period covered by thisDatedDataValue
.This is a convenience/performance property that should run faster (or at least not slower) than accessing
.dayRange.first
, since accessing thedayRange
property may result in a temporarydegreedays.time.DayRange
object being created. We say “may” because, although at the time of writing it does create a temporary object, this is an implementation detail that might change in the future.A large request for data (daily data in particular) can easily result in hundreds of thousands of these
DatedDataValue
objects, each of which occupies memory and typically needs to be used by code that is repeating its operations on every single value in a set. So we’ve tried to designDatedDataValue
to minimize memory footprint and enable fast access to its properties.If all you want is the first day of the range, then this is the optimal property to access. If you want the last day of the range, then
lastDay
is the optimal property to access. But if you need more information then don’t hesitate to access thedayRange
property as thedegreedays.time.DayRange
object is lightweight and creating it is a very fast operation. Choosing the optimal access pattern is very unlikely to make a practical difference unless performance is critically important to you and you’re iterating over large quantities of daily data for which thedayRange
property is largely irrelevant anyway (since for daily data theDayRange
would only cover a single day).
- property lastDay: date¶
The last
datetime.date
of the period covered by thisDatedDataValue
.See also:
firstDay
- property percentageEstimated: float¶
A number between 0 and 100 (both inclusive), indicating the extent to which the calculated
value
is based on estimated data.Generally speaking, a value with a lower percentage-estimated figure is likely to be more reliable than one with a higher percentage-estimated figure.
- class degreedays.api.data.GeographicLocation¶
Defines a location in terms of a longitude/latitude or postal/zip code, leaving it to the API to find the nearest good weather station.
To create a
GeographicLocation
object you can use theLocation.postalCode
andLocation.longLat
factory methods. For example:empireStateBuilding = Location.postalCode("10118", "US") trafalgarSquare = Location.longLat(LongLat(-0.12795, 51.50766))
Geographic locations provide a powerful way to request degree-day data. Manually selecting the best weather station to provide data for a specific building or region can be tricky, because different stations have different levels of data coverage and quality. With a geographic location, the API will automatically select the weather station that is best equipped to provide the data you want.
At some point the API might average data from multiple weather stations around the specified location if it thinks that would significantly improve results. If we do add this feature, it will be optional, and disabled by default, so the behaviour of your system won’t change unless you want it to.
Make sure to specify the full range of data that you want when using a geographic location… Some weather stations have less data than others so it’s important for the API to know the full range of interest when it’s choosing which station(s) to use.
Fetching more data for the same location (e.g. daily updates)¶
The
LocationDataResponse
contains a station ID that will enable you to fetch more data for the weather station(s) that were selected for your geographic location. If just one station was used (the normal case), then the station ID will simply be the canonical ID of that one station. If data from multiple stations was combined to satisfy your request (an option that isn’t available now but that we may cater for in future), then the station ID will be for a “virtual station” that represents the specific combination of weather stations used.Either way, you can use the station ID from the
LocationDataResponse
to fetch more data for the same location in the future. For example, you might initially want to fetch the last 5 years of data for a geographic location, and then use the returned station ID to fetch updates each day, week, or month going forward. If you’re storing the generated data, then doing it this way will ensure that your data updates are generated using the same weather station(s) as your initial request.However, if you’re following this model, bear in mind that a weather station that works today won’t necessarily be working next month or next year. It’s unusual for stations to go out of service, but it does happen. It might not be worth worrying about the possibility of stations going down if you’re only tracking data for a handful of locations, but it’s definitely worth planning for if you’re tracking data for hundreds or thousands of locations. To prepare for a station going down, it makes sense to store the details of the geographic location together with the station ID that you’re using for updates. That way, if the station fails, you can use the geographic location to find a replacement.
Note that a simple alternative is to use the geographic location to fetch a full set of data each time it is needed. You might not get data from the same station(s) each time (particularly if you vary the
Period
that you request data for), but the station-selection algorithm will ensure that you’re getting the best available data for each individual request that you make.Two-stage data fetching for geographic locations¶
If you are using geographic locations, but storing data by station ID (as described above), you may be able to save request units and improve the efficiency of your system with two-stage data fetching. When you want to add a new location into your system (e.g. if a new user signs up with a new address), you can do the following:
Stage 1: Call
DataApi.getLocationInfo
with aLocationInfoRequest
containing the geographic location and the specification of the data that you want. You won’t get any data back, but you should get the station ID that the system would use for an equivalentLocationDataRequest
. If you already have data stored for that station ID, use it; if not, progress to stage 2.Stage 2: Call
DataApi.getLocationData
with aLocationDataRequest
that specifies the station ID from stage 1. (You could use the geographic location again, but using the station ID will save a request unit, such that your two-stage fetch will take the same number of request units in total as it would if you had just submitted aLocationDataRequest
with the geographic location in the first place.)Two-stage fetching will only improve efficiency and save request units if/when you have enough geographic locations in your system that some of them end up sharing weather stations. But, if that is the case, two-stage fetching can really help your system to scale well as more and more geographic locations are added in.
- class degreedays.api.data.Location¶
Defines a location for which degree days should be calculated.
To create a
Location
object you can use the static factory methods of this class. For example:losAngelesAirportWeatherStation = Location.stationId("KLAX") empireStateBuilding = Location.postalCode("10118", "US") trafalgarSquare = Location.longLat(LongLat(-0.12795, 51.50766))
empireStateBuilding and trafalgarSquare are examples of geographic locations, which provide a powerful way to get data for locations around the world. See
GeographicLocation
for more on how these geographic locations work.- static longLat(longLat: degreedays.geo.LongLat) impl.LongLatLocation ¶
Returns a
LongLatLocation
object with the specified longitude and latitude position.- Parameters:
longLat (LongLat) – the longitude/latitude position.
- Raises:
TypeError – if longLat is not a
degreedays.geo.LongLat
object.
See also:
GeographicLocation
- static postalCode(postalCode: str, countryCode: str) impl.PostalCodeLocation ¶
Returns a
PostalCodeLocation
object with a postal code (or zip code, post code, or postcode) and a two-letter country code representing the country that the postal code belongs to.- Parameters:
postalCode (str) – the postal code (or zip code, post code, or postcode) of the location you want data for. Cannot be empty, cannot be longer than 16 characters (a length that we believe allows for all current postal codes worldwide), and cannot contain any characters other than
[- 0-9a-zA-Z]
.countryCode (str) – the ISO 3166-1-alpha-2 country code of the country that postalCode belongs to. It must be a two-character string comprised of only characters A-Z (i.e. upper case only). For example, pass “US” if postalCode is a US zip code, pass “GB” (for “Great Britain”) if postalCode is a UK post code, and pass “CA” if postalCode is a Canadian zip code.
- Raises:
TypeError – if postalCode or countryCode is not a
str
.ValueError – if tests indicate that postalCode or countryCode fails to match the specifications detailed above.
See also:
GeographicLocation
- static stationId(stationId: str) impl.StationIdLocation ¶
Returns a
StationIdLocation
object with the specified weather station ID.- Parameters:
stationId (str) – the ID of the weather station that the
StationIdLocation
should represent (i.e. the station you want data for). Cannot be empty, cannot contain contain any characters other than[-_0-9a-zA-Z]
, and cannot contain more than 60 characters (a limit that is significantly larger than the length of any station ID that we are currently aware of, but that is set high to allow for “virtual station IDs” to be introduced in the future, combining data from multiple stations).- Raises:
TypeError – if stationId is not a
str
.ValueError – if tests indicate that stationId fails to match the specification detailed above.
- class degreedays.api.data.LocationDataRequest(location: Location, dataSpecs: DataSpecs)¶
Defines a request for one or more sets of data from a particular location. A successfully-processed
LocationDataRequest
will result in aLocationDataResponse
containing the specified data.- Parameters:
- Raises:
TypeError – if location is not a subclass of
Location
, or if dataSpecs is not aDataSpecs
object.
Here’s an example showing how to create a request for one set of data from the EGLL weather station in London, UK. The request specifies heating degree days with a base temperature of 15.5°C, broken down on a daily basis and covering the whole of July 2024:
period = Period.dayRange(degreedays.time.DayRange( datetime.date(2024, 7, 1), datetime.date(2024, 7, 31))) breakdown = DatedBreakdown.daily(period) baseTemp = Temperature.celsius(15.5) calculation = Calculation.heatingDegreeDays(baseTemp) dataSpec = DataSpec.dated(calculation, breakdown) location = Location.stationId('EGLL') request = LocationDataRequest( location, DataSpecs(dataSpec))
Here’s an example showing how to create a request for two sets of data from Times Square in New York. We specify the location as a zip code (a type of
GeographicLocation
), leaving it to the API to choose the most appropriate weather station in the area. The request specifies heating degree days with a base temperature of 55°F, and cooling degree days with a base temperature of 65°F, both broken down on a monthly basis and covering the last 12 months:breakdown = DatedBreakdown.monthly(Period.latestValues(12)) hddSpec = DataSpec.dated( Calculation.heatingDegreeDays(Temperature.fahrenheit(55)), breakdown) cddSpec = DataSpec.dated( Calculation.coolingDegreeDays(Temperature.fahrenheit(65)), breakdown) request = LocationDataRequest( Location.postalCode('10036', 'US'), DataSpecs(hddSpec, cddSpec))
See
DataApi.getLocationData(locationDataRequest)
for an example of how to submit aLocationDataRequest
to the API and get aLocationDataResponse
back containing the data you requested.
- class degreedays.api.data.LocationDataResponse(metadata: api.ResponseMetadata, stationId: str, targetLongLat: degreedays.geo.LongLat, sources: Sequence[Source], dataSets: DataSets)¶
Contains the data generated to fulfil a
LocationDataRequest
.See
DataApi.getLocationData(locationDataRequest)
for more info and code samples.- property dataSets: DataSets¶
The
DataSets
object containing the sets of data generated to fulfil theDataSpecs
from theLocationDataRequest
that led to this response.
- property metadata¶
An object containing metadata sent back with every response from the API servers, including details of the account’s current rate limit.
- property sources: Sequence[Source]¶
The non-empty
Sequence
of source(s) (essentially weather stations) that were used to generate the data in this response.At the time of writing there will only be one source for any given response (so
sources[0]
) is the way to get it)… But at some point we might start combining data from multiple sources to satisfy requests for data fromGeographicLocation
types. If we do add this feature, it will be optional, and disabled by default, so the behaviour of your system won’t change unless you want it to.
- property stationId: str¶
The non-empty canonical ID of the weather station or combination of weather stations that were used to calculate the returned data.
If the
Location
in theLocationDataRequest
was aStationIdLocation
(created viaLocation.stationId
), this method will simply return the canonical form of that weather station’s ID. We say “canonical” because it’s possible for a station ID to be expressed in more than one way, like upper case or lower case. The canonical form of the station ID is the form that you should display in a UI or store in a database if appropriate.If the
Location
in theLocationDataRequest
was aGeographicLocation
, then:If the data was calculated using a single station (the usual case), this method will return the canonical form of that station’s ID.
If the data was calculated using multiple stations (something that the API might optionally start doing at some point in the future), this method will return the ID of a “virtual station” that represents the specific combination of weather stations used.
Either way, the station ID returned by this method can be used to fetch more data from the same station(s) that were used to generate the data in this response. For example, you might want to request data using a
GeographicLocation
initially, and then use the returned station ID to fetch updates each day, week, or month going forward.
- property targetLongLat: LongLat¶
The
degreedays.geo.LongLat
object that specifies the geographic position of theLocation
from theLocationDataRequest
that led to this response.If the
Location
from the request was aPostalCodeLocation
(created viaLocation.postalCode
), this will be theLongLat
that the API determined to be the central point of that postal code.If the
Location
from the request was aStationIdLocation
(created viaLocation.stationId
), this will be theLongLat
of that station (also accessible throughsources
).If the
Location
from the request was aLongLatLocation
(created viaLocation.longLat
), this will simply be theLongLat
that was originally specified. (Bear in mind that the longitude and latitude may have been rounded slightly between the request and the response. Such rounding would only introduce very small differences that would be insignificant as far as the real-world position is concerned, but it’s worth bearing this in mind in case you are comparing for equality the returnedLongLat
with theLongLat
from the request. The two positions will be close, but they might not be equal.)
- exception degreedays.api.data.LocationError(failureResponse: FailureResponse)¶
Indicates a
Failure
in the API’s processing of a request, caused by problems with theLocation
that the request specified.This exception corresponds to any failure code code starting with “Location”.
You can interrogate the isDueToXXX properties of this exception to find out more about the cause. But do note that it is possible for none of those properties to be True if a relevant new failure code is added into the API. Be prepared for this in your handling.
You might not need to pay any attention to the specific code behind the
LocationError
. For many use-cases, the best way to handle aLocationError
is just to try again with an alternative location. For example, if a request for data from a specificLocation.stationId
fails, you might want to try a request for theGeographicLocation
of the real-world building you ultimately want data for, so that the API can find a replacement station for you automatically.- property failure: Failure¶
The
Failure
object containing details of the failure on the API servers that led to this exception on the client.
- property isDueToLocationNotRecognized: bool¶
True if this failure came in response to a request for data from a location that the API did not recognize as a weather station or real-world geographic location; False otherwise.
This type of failure will occur if you specify a
Location.stationId
with an unrecognized ID, or aLocation.postalCode
with an unrecognized postal code.
- property isDueToLocationNotSupported: bool¶
True if this failure came in response to a request for data from a location that is recognized but not currently supported by the API; False otherwise.
This can happen if you request data from a
GeographicLocation
for which the API is unable to find a good weather station. In this instance it is possible that a weather station with usable data does exist in the area - you could try visiting www.degreedays.net and searching manually to see if you can find a good weather station to use for future API requests (referencing it by station ID). But you will probably have to look some distance from the location of interest to find a usable station.This can also happen if you request data from a weather station that has not sent any usable weather reports for roughly 10 days or more (10 being an approximate number that is subject to change). We call such stations “inactive”.
Some stations have never reported valid temperatures, and so have always been inactive/not supported.
It’s fairly rare for active stations to go inactive, but it does happen, and it’s best to be prepared for the possibility, particularly if you’re handling data from hundreds or thousands of locations. Stations can be taken down, they can break (and not be fixed in a timely manner), or they can be moved to another location and assigned a new ID. Unfortunately not even the best “airport” stations managed by organizations such as the NWS or UK Met Office are exempt from these sorts of problems.
Short periods of station inactivity should not result in this failure. Up to a point, the API will fill in missing data with estimates. It’s only when a station fails to send any usable data for roughly 10 days or more that the API will consider it inactive. (10 being an approximate number that is subject to change.)
It’s possible for an inactive station to become active (again) at some point in the future. However, if you get this failure for a station, you’re probably best off finding another nearby station to use as an alternative/replacement (e.g. by requesting data using the
GeographicLocation
of the real-world building of interest). If an active station goes inactive, and then later makes a revival, there will probably be a gap in its data coverage that will make it impractical for use in most forms of degree-day-based analysis until it has accumulated more uninterrupted data.
- property responseMetadata: ResponseMetadata¶
The metadata from the
FailureResponse
that brought details of this failure back from the API servers.
- class degreedays.api.data.LocationInfoRequest(location: Location, dataSpecs: DataSpecs)¶
Defines a request for info about the station(s) that would be used to fulfil an equivalent
LocationDataRequest
.- Parameters:
- Raises:
TypeError – if location is not a subclass of
Location
, or if dataSpecs is not aDataSpecs
object.
LocationInfoRequest
is effectively a lightweight alternative toLocationDataRequest
, primarily for use when you want to know what station ID the API would use for a givenGeographicLocation
andDataSpecs
, without the overhead of actually fetching the specified data. ALocationInfoRequest
will only ever take one request unit, whilst aLocationDataRequest
can take many more (depending on how much data it fetches).A successfully-processed
LocationInfoRequest
will result in aLocationInfoResponse
.See
DataApi.getLocationInfo(locationInfoRequest)
for an example of how to create aLocationInfoRequest
, submit it to the API, and get aLocationInfoResponse
back.
- class degreedays.api.data.LocationInfoResponse(metadata: api.ResponseMetadata, stationId: str, targetLongLat: degreedays.geo.LongLat, sources: Sequence[Source])¶
Contains the location/station-related info returned in response to a
LocationInfoRequest
.This mirrors
LocationDataResponse
, except it doesn’t actually contain any data (i.e. noDataSets
).See
DataApi.getLocationInfo(locationInfoRequest)
for more info and code samples.- property metadata¶
An object containing metadata sent back with every response from the API servers, including details of the account’s current rate limit.
- property sources: Sequence[Source]¶
The non-empty
Sequence
of source(s) (essentially weather stations) that would be used to generate data for an equivalentLocationDataResponse
.At the time of writing there will only be one source for any given response (so
sources[0]
) is the way to get it)… But at some point we might start combining data from multiple sources to satisfy requests for data fromGeographicLocation
types. If we do add this feature, it will be optional, and disabled by default, so the behaviour of your system won’t change unless you want it to.
- property stationId: str¶
The non-empty canonical ID of the weather station or combination of weather stations that would be used to generate data for an equivalent
LocationDataResponse
.If the
Location
in theLocationInfoRequest
was aStationIdLocation
(created viaLocation.stationId
), this method will simply return the canonical form of that weather station’s ID. We say “canonical” because it’s possible for a station ID to be expressed in more than one way, like upper case or lower case. The canonical form of the station ID is the form that you should display in a UI or store in a database if appropriate.If the
Location
in theLocationDataRequest
was aGeographicLocation
, then:If the data for an equivalent
LocationDataResponse
would be calculated using a single station (the usual case), this method will return the canonical form of that station’s ID.If the data for an equivalent
LocationDataResponse
would be calculated using multiple stations (something that the API might optionally start doing at some point in the future), this method will return the ID of a “virtual station” that represents the specific combination of weather stations used.
Either way, the station ID returned by this method can be used to fetch data from the same station(s) that would have been used to generate data in response to an equivalent
LocationDataRequest
. Typically you would use this to get the station ID to best represent aGeographicLocation
, and then use that ID in eachLocationDataRequest
for that location going forward.
- property targetLongLat: LongLat¶
The
degreedays.geo.LongLat
object that specifies the geographic position of theLocation
from theLocationInfoRequest
that led to this response.If the
Location
from the request was aPostalCodeLocation
(created viaLocation.postalCode
), this will be theLongLat
that the API determined to be the central point of that postal code.If the
Location
from the request was aStationIdLocation
(created viaLocation.stationId
), this will be theLongLat
of that station (also accessible throughsources
).If the
Location
from the request was aLongLatLocation
(created viaLocation.longLat
), this will simply be theLongLat
that was originally specified. (Bear in mind that the longitude and latitude may have been rounded slightly between the request and the response. Such rounding would only introduce very small differences that would be insignificant as far as the real-world position is concerned, but it’s worth bearing this in mind in case you are comparing for equality the returnedLongLat
with theLongLat
from the request. The two positions will be close, but they might not be equal.)
- class degreedays.api.data.Period¶
Defines the period in time that a set of degree days should cover.
To create a
Period
object you can use the static factory methods of this class. For example:latest7Values = Period.latestValues(7) yearOf2024 = Period.dayRange(degreedays.time.DayRange( datetime.date(2024, 1, 1), datetime.date(2024, 12, 31)))
- static dayRange(dayRange: degreedays.time.DayRange, minimumDayRangeOrNone: degreedays.time.DayRange | None = None) impl.DayRangePeriod ¶
Returns a
DayRangePeriod
object that specifies the period covered by dayRange.- Parameters:
dayRange (degreedays.time.DayRange) – the range of days that the period should cover.
minimiumDayRangeOrNone (degreedays.time.DayRange | None) – you can specify this if you would rather have a failure than a partial set of data covering less than your specified minimum range. Otherwise you may get back less data than you asked for if there aren’t enough weather-data records to generate a full set for your specified location. See
degreedays.api.data.impl.DayRangePeriod.minimumDayRangeOrNone
for more on this.
- Raises:
TypeError – if dayRange is not a
degreedays.time.DayRange
, or if minimumDayRangeOrNone is not adegreedays.time.DayRange
or None.ValueError – if minimumDayRangeOrNone is a
degreedays.time.DayRange
that extends earlier or later than dayRange.
- static latestValues(numberOfValues: int, minimumNumberOfValuesOrNone: int | None = None) impl.LatestValuesPeriod ¶
Returns a
LatestValuesPeriod
object that automatically resolves to a date range including the latest available data and the specified number of degree-day values.- Parameters:
numberOfValues (int) – a number, greater than zero, that specifies how many degree-day values the period should cover (see
degreedays.api.data.impl.LatestValuesPeriod
for an explanation of how the values in question can be daily, weekly, monthly, or yearly). This is effectively an upper bound: if there isn’t enough good data to cover the numberOfValues specified, the API will assemble and return what it can.minimumNumberOfValuesOrNone (int | None) – you can specify this if you would rather have a failure than a partial set of data with less than your specified minimum number of values. Otherwise you may get back less data than you asked for if there aren’t enough weather-data records to generate a full set for your specified location.
- Raises:
TypeError – if numberOfValues is not an int, or if minimumNumberOfValuesOrNone is not an int or None.
ValueError – if numberOfValues is less than 1, or if minimumNumberOfValuesOrNone is an int that is less than 1 or greater than numberOfValues.
- class degreedays.api.data.Source(station: Station, distanceFromTarget: Distance)¶
Contains basic information about a source of data that was used to satisfy a request.
On the surface this appears very similar to the
Station
object that it contains, but there is a key difference: the information contained in aSource
is specifically relevant to a particular request for data, whilst theStation
is independent of that. For example, theSource
contains thedistanceFromTarget
, which is the distance of theStation
from the target location that was specified in the request.
- exception degreedays.api.data.SourceDataError(failure: Failure)¶
Indicates a
Failure
to generate aDataSet
caused by problems with the source temperature data for theLocation
andPeriod
requested.For a
LocationDataRequest
containing specifications for multiple sets of data, it is possible for some to succeed and others to fail if they are sufficiently different (e.g. covering different periods in time).This exception corresponds to any failure code starting with “SourceData”.
You can interrogate the isDueToXXX properties of this exception to find out more about the cause. But do note that it is possible for none of those properties to be True if a relevant new failure code is added into the API. Be prepared for this in your handling.
- property failure: Failure¶
The
Failure
object containing details of the failure on the API servers that led to this exception on the client.
- property isDueToSourceDataCoverage: bool¶
True if the requested data could not be generated because the source weather station’s recorded temperature data did not cover the requested period in time; False otherwise.
This will arise if you request data too early (because the weather station hasn’t yet recorded the necessary temperature readings or they haven’t yet filtered into our system), or if the weather station didn’t exist or wasn’t recording for the requested period in time.
- property isDueToSourceDataErrors: bool¶
True if the requested data could not be generated because of errors in the recorded temperature data of the source weather station; False otherwise.
Generally speaking, if a request for data from a weather station results in this error, it is probably best to find an alternative weather station nearby. But a weather station with data errors is not necessarily totally useless - it may make a revival at some point in the future, and it’s possible that data requests covering a different period in time will work OK.
- property isDueToSourceDataGranularity: bool¶
True if the requested data could not be generated because the source weather station’s recorded temperature data was not fine-grained/detailed enough; False otherwise.
This will arise if you request detailed time-series data (e.g. hourly temperature data) for a station without sufficiently fine-grained weather reports.
- class degreedays.api.data.Station(id: str, longLat: LongLat, elevation: Distance, displayName: str)¶
Contains basic information about a weather station.
- property displayName: str¶
A string representing the name of the weather station.
The display name should not include the
id
, thelongLat
or theelevation
. So, for displaying the name of a station in a UI, at a minimum you’ll probably want to do something like:station.id + ': ' + station.displayName
You might also want to display the longitude/latitude and elevation too.
We’d love to be able to break the details within this name down further (e.g. offering separate fields for city, state, and country)… But the recorded details for weather-station names are generally too unreliable for this to make sense. Fields are often misspelled, mixed up (e.g. the state might have been entered into the city field), and required fields like country are often missing. And there’s little consistency in the naming (e.g. United Kingdom, Great Britain, GB, UK etc.). This, unfortunately, is a limitation of the original data sources - it’s typical for the IDs to be monitored and controlled closely, but it’s rare for the names to receive the same level of attention.
Please be aware that a weather station’s display name may change over time, both in content and format. We strongly suggest that you don’t try to parse these names. But please do let us know if there’s something in particular that you need access to.
- property elevation: Distance¶
The elevation of the weather station. The
degreedays.geo.Distance
object returned can easily be converted into units of your choice (typically metres or feet for an elevation).Note that the reliability of these measurements is variable. Note also that they may change over time as the underlying data sources are updated with more accurate measurements.
- property id: str¶
The canonical ID of the weather station. (See
LocationDataResponse.stationId
for an explanation of what “canonical” means in this context.)The ID can be used to request data from this station.
- property longLat: LongLat¶
The longitude/latitude location of the weather station.
Note that a weather station’s recorded location might not be a particularly accurate representation of its real-world location. Many stations have recorded longitude/latitude locations that are accurate to within a few metres, but there are some that are out by as much as a kilometre or so. This is worth bearing in mind when you’re calculating the distances between locations.
Note also that the recorded location of weather stations will often change over time as the underlying data sources are updated with more accurate measurements.
- class degreedays.api.data.Temperature(value: float, unit: TemperatureUnit)¶
Defines a temperature value, typically the base temperature of a degree-day calculation.
To create a
Temperature
object you can use the static factory methodscelsius
andfahrenheit
. For example:celsiusTemp = Temperature.celsius(15.5) fahrenheitTemp = Temperature.fahrenheit(65)
You can pass a temperature into a
Calculation
to define the base temperature that thatCalculation
should use to calculate degree days. Use a Celsius temperature for Celsius degree days, and a Fahrenheit temperature for Fahrenheit degree days.Rounding¶
This class enables temperatures to be defined to the nearest 0.1 degrees (either 0.1°C or 0.1°F). Given the limitations of temperature-recording equipment and methods, it is meaningless to consider temperature differences smaller than this.
When creating a
Temperature
object, you can pass in any float value within the maximum and minimum limits set by the temperature unit. But the value you pass in will always be rounded to the nearest 0.1 degrees.For example, a Celsius
Temperature
created with a value of 15.5 will be equal to one created with 15.456 or 15.543. And a FahrenheitTemperature
created with 65 will be equal to one created with 64.96 or 65.04.One benefit of this is that you can easily define a range of base temperatures in a loop without worrying too much about the inaccuracies of floating-point arithmetic. The automatic rounding should correct any such issues (e.g. rounding 69.9998 up to 70).
- static celsius(value: float) Temperature ¶
Returns a
Temperature
object with the specified Celsius temperature rounded to the nearest 0.1°C.- Parameters:
value (float) – a Celsius temperature that’s greater than or equal to -273°C (absolute zero) and less than or equal to 3000°C (hotter than the hottest blast furnaces). Note that the base-temperature range over which it would typically make sense to calculate degree days is much smaller than the range allowed by these limits.
TypeError – if value is not a float.
ValueError – if value is NaN, less than -273°C, or greater than 3000°C.
See also: Rounding,
fahrenheit
- static celsiusRange(firstValue: float, lastValue: float, step: float) Sequence[Temperature] ¶
Returns a low-to-high sorted
Sequence
ofTemperature
objects, with Celsius values running from firstValue to lastValue (inclusive), and the specified step between each consecutive temperature.For example, to get Celsius temperatures between 10°C and 30°C (inclusive), with each temperature being 5°C greater than the last (giving temperatures 10°C, 15°C, 20°C, 25°C, and 30°C):
temps = Temperature.celsiusRange(10, 30, 5)
- Parameters:
firstValue (float) – the Celsius value of the first
Temperature
to be included in the returnedSequence
. This must be greater than or equal to -273°C and less than or equal to 3000°C.lastValue (float) – the Celsius value of the last
Temperature
to be included in the returnedSequence
. This must be greater than or equal to firstValue. Also, like for firstValue, this must be greater than or equal to -273°C and less than or equal to 3000°C.step (float) – the Celsius temperature difference between each temperature value to be included in the returned
Sequence
. Cannot be NaN, and must be greater than zero. It must also be a multiple of 0.1 (the smallest temperature difference allowed), though allowances are made for floating-point imprecision (so for example a step of 0.4999999 will be treated as 0.5).
- Raises:
TypeError – if firstValue, lastValue, or step is not a float.
ValueError – if any of the parameters are NaN; if firstValue or lastValue are outside of their allowed range (-273°C to 3000°C inclusive); if lastValue is less than firstValue; if step is not positive; or if step is not a multiple of 0.1 (allowing for slight deviations caused by floating-point imprecision).
See also:
fahrenheitRange
,TemperatureUnit.range
- static fahrenheit(value: float) Temperature ¶
Returns a
Temperature
object with the specified Fahrenheit temperature rounded to the nearest 0.1°F.- Parameters:
value (float) – a Fahrenheit temperature that’s greater than or equal to -459.4°F (absolute zero) and less than or equal to 5432°F (hotter than the hottest blast furnaces). Note that the base-temperature range over which it would typically make sense to calculate degree days is much smaller than the range allowed by these limits.
TypeError – if value is not a float.
ValueError – if value is NaN, less than -459.4°F, or greater than 5432°F.
- static fahrenheitRange(firstValue: float, lastValue: float, step: float) Sequence[Temperature] ¶
Returns a low-to-high sorted
Sequence
ofTemperature
objects, with Fahrenheit values running from firstValue to lastValue (inclusive), and the specified step between each consecutive temperature.For example, to get Fahrenheit temperatures between 50°F and 70°F (inclusive), with each temperature being 5°F greater than the last (giving temperatures 50°F, 55°F, 60°F, 65°F, and 70°F):
temps = Temperature.fahrenheitRange(50, 70, 5)
- Parameters:
firstValue (float) – the Fahrenheit value of the first
Temperature
to be included in the returnedSequence
. This must be greater than or equal to -459.4°F and less than or equal to 5432°F.lastValue (float) – the Fahrenheit value of the last
Temperature
to be included in the returnedSequence
. This must be greater than or equal to firstValue. Also, like for firstValue, this must be greater than or equal to -459.4°F and less than or equal to 5432°F.step (float) – the Fahrenheit temperature difference between each temperature value to be included in the returned
Sequence
. Cannot be NaN, and must be greater than zero. It must also be a multiple of 0.1 (the smallest temperature difference allowed), though allowances are made for floating-point imprecision (so for example a step of 0.4999999 will be treated as 0.5).
- Raises:
TypeError – if firstValue, lastValue, or step is not a float.
ValueError – if any of the parameters are NaN; if firstValue or lastValue are outside of their allowed range (-459.4°F to 5432°F inclusive); if lastValue is less than firstValue; if step is not positive; or if step is not a multiple of 0.1 (allowing for slight deviations caused by floating-point imprecision).
See also:
celsiusRange
,TemperatureUnit.range
- property unit: TemperatureUnit¶
The unit of this temperature.
- class degreedays.api.data.TemperatureUnit¶
Defines the units of temperature measurement with class constants
TemperatureUnit.CELSIUS
andTemperatureUnit.FAHRENHEIT
.- CELSIUS: TemperatureUnit¶
For the Celsius temperature scale.
Access via:
TemperatureUnit.CELSIUS
- FAHRENHEIT: TemperatureUnit¶
For the Fahrenheit temperature scale.
Access via:
TemperatureUnit.FAHRENHEIT
- range(firstValue: float, lastValue: float, step: float) Sequence[Temperature] ¶
Returns a low-to-high sorted
Sequence
ofTemperature
objects, with values running from firstValue to lastValue (inclusive), and the specified step between each consecutive temperature.For example, to get Celsius temperatures between 10°C and 30°C (inclusive), with each temperature being 5°C greater than the last (giving temperatures 10°C, 15°C, 20°C, 25°C, and 30°C):
temps = TemperatureUnit.CELSIUS.range(10, 30, 5)
- Parameters:
firstValue (float) – the value of the first
Temperature
to be included in the returnedSequence
. If thisrange
method is called onTemperatureUnit.CELSIUS
, firstValue must be greater than or equal to -273°C and less than or equal to 3000°C; if it is called onTemperatureUnit.FAHRENHEIT
then firstValue must be greater than or equal to -459.4°F and less than or equal to 5432°F.lastValue (float) – the value of the last
Temperature
to be included in the returnedSequence
. This must be greater than or equal to firstValue. Also, like for firstValue, if thisrange
method is called onTemperatureUnit.CELSIUS
, lastValue must be greater than or equal to -273°C and less than or equal to 3000°C; if it is called onTemperatureUnit.FAHRENHEIT
then lastValue must be greater than or equal to -459.4°F and less than or equal to 5432°F.step (float) – the temperature difference between each temperature value to be included in the returned
Sequence
. Cannot be NaN, and must be greater than zero. It must also be a multiple of 0.1 (the smallest temperature difference allowed), though allowances are made for floating-point imprecision (so for example a step of 0.4999999 will be treated as 0.5).
- Raises:
TypeError – if firstValue, lastValue, or step is not a float.
ValueError – if any of the parameters are NaN; if firstValue or lastValue are outside of their allowed range (that’s -273°C to 3000°C inclusive if this
range
method is called onTemperatureUnit.CELSIUS
, and -459.4°F to 5432°F inclusive if it is called onTemperatureUnit.FAHRENHEIT
); if lastValue is less than firstValue; if step is not positive; or if step is not a multiple of 0.1 (allowing for slight deviations caused by floating-point imprecision).
See also:
Temperature.celsiusRange
,Temperature.fahrenheitRange
- class degreedays.api.data.TimeSeriesCalculation¶
Defines how time-series data should be calculated e.g. temperature data, with hourly figures, in Celsius.
To create a
TimeSeriesCalculation
object you can use the static factory methods of this class. For example:hourlyTempsCalculation = TimeSeriesCalculation.hourlyTemperature( TemperatureUnit.CELSIUS)
The docs for
TimeSeriesDataSpec
have more on how to actually fetch time-series data with your specifiedTimeSeriesCalculation
.Why is time-series data “calculated”?¶
You might wonder why time-series data such as hourly temperature data is “calculated” as opposed to just being returned…
The thing is that real-world weather stations hardly ever report exactly on the hour, every hour. Different stations have different reporting frequencies and schedules, they can change over time (e.g. if a station has its equipment upgraded), and gaps in routine reporting are fairly common too. Higher-quality stations do tend to report at least hourly, but it’s rarely exactly on the hour, and it’s rarely perfectly regular either. We take whatever data the weather stations do report, and use it to calculate (or interpolate) neat, perfectly-regular time-series data (such as hourly temperature data) with all gaps filled with estimated data (which is marked as such so you can easily identify it).
The end result is neat, regular data that is easy to store and easy to work with, but it actually takes a lot of processing to get it into that format. And this is why time-series data from our system is “calculated” as opposed to just being returned.
- static hourlyTemperature(temperatureUnit: TemperatureUnit) impl.TemperatureTimeSeriesCalculation ¶
Returns a
TemperatureTimeSeriesCalculation
object withTimeSeriesInterval.HOURLY
and the specifiedTemperatureUnit
.- Parameters:
temperatureUnit (TemperatureUnit) – specifies whether the data should be calculated in Celsius or Fahrenheit.
- Raises:
TypeError – if temperatureUnit is not a
TemperatureUnit
object.
- property interval: TimeSeriesInterval¶
The
TimeSeriesInterval
indicating the interval (e.g. hourly) that the time-series data should be calculated with.
- class degreedays.api.data.TimeSeriesDataSet(percentageEstimated: float, values: Sequence[TimeSeriesDataValue])¶
Contains a set of time-series data (e.g. hourly temperature data) generated to fulfil a
TimeSeriesDataSpec
for a specificLocation
.See
TimeSeriesDataSpec
for example code showing how to get aTimeSeriesDataSet
of hourly temperature data from the API.- property fullRange: DayRange¶
The
degreedays.time.DayRange
object indicating the period of time that is covered by thisDataSet
.
- getValuesWithin(dateOrDayRange: datetime.date | degreedays.time.DayRange) Sequence[TimeSeriesDataValue] ¶
Returns a possibly-empty chronologically-ordered
Sequence
of theTimeSeriesDataValue
objects from thisTimeSeriesDataSet
that fall within the specified dateOrDayRange.- Parameters:
dayRange (datetime.date | degreedays.time.DayRange) – the date or day range of interest.
- Raises:
TypeError – if dateOrDayRange is not a
datetime.date
ordegreedays.time.DayRange
.
- property percentageEstimated: float¶
A number between 0 and 100 (both inclusive), indicating the overall extent to which this
DataSet
is based on estimated data.Generally speaking, data with a lower percentage-estimated figure is likely to be more reliable than data with a higher percentage-estimated figure.
- property values: Sequence[TimeSeriesDataValue]¶
A non-empty chronologically-ordered
Sequence
of theTimeSeriesDataValue
objects that make up thisTimeSeriesDataSet
.
- class degreedays.api.data.TimeSeriesDataSpec(timeSeriesCalculation: TimeSeriesCalculation, datedBreakdown: DatedBreakdown)¶
Defines a specification for a set of time-series data such as hourly temperature data covering a specific period in time.
To create a
TimeSeriesDataSpec
object useDataSpec.timeSeries
.A
TimeSeriesDataSpec
specifies a set of time-series data in terms of:the calculation process used to calculate the time-series figures (find out why time-series data is “calculated”); and
a breakdown that determines the period in time that the time-series data should cover (more on this below).
Example
TimeSeriesDataSpec
code:¶Here’s how you could specify hourly temperature data, in Fahrenheit, covering the last 30 days:
timeSeriesDataSpec = DataSpec.timeSeries( TimeSeriesCalculation.hourlyTemperature(TemperatureUnit.FAHRENHEIT), DatedBreakdown.daily(Period.latestValues(30)))
You could then send that
TimeSeriesDataSpec
to the API as part of aLocationDataRequest
, and get a response containing aTimeSeriesDataSet
back:api = DegreeDaysApi.fromKeys(AccountKey(yourStringAccountKey), SecurityKey(yourStringSecurityKey)) request = LocationDataRequest( Location.postalCode("02630", "US"), DataSpecs(timeSeriesDataSpec)) response = api.dataApi.getLocationData(request) hourlyData = response.dataSets[timeSeriesDataSpec] for v in hourlyData.values: print('%s: %g' % (v.datetime, v.value))
To include figures for the current day (as opposed to the default behaviour of covering full days only), you can specify a
DatedBreakdown
with itsDatedBreakdown.allowPartialLatest
property set to True. For example:hourlyTempsIncludingToday = DataSpec.timeSeries( TimeSeriesCalculation.hourlyTemperature(TemperatureUnit.FAHRENHEIT), DatedBreakdown.daily(Period.latestValues(31), allowPartialLatest=True))
The docs for
DatedBreakdown.allowPartialLatest
explain more, including an important caution that the latest figures can be volatile, and, if stored, should be overwritten later when the weather stations have had time to send any delayed or corrected weather reports (which many weather stations send quite often).Why does
TimeSeriesDataSpec
take aDatedBreakdown
?¶It might seem strange that
TimeSeriesDataSpec
takes aDatedBreakdown
in the same way thatDatedDataSpec
(for degree days) does, because hourly data is broken down hourly, not by days, weeks, or months. However, it works this way to give you flexibility in how you specify the time-period that the time-series data should cover, and to make it easier for you to get time-series data that lines up with your degree days.With a
DatedBreakdown
you can easily specify that you want hourly data covering e.g. the last 30 days, or the last 12 full calendar months, or the last 5 full calendar years, just like you do when you are using aDatedBreakdown
to specify the degree days you want. You can take full advantage of widening rules if they make things more convenient for you. The data will always come back as hourly figures. Essentially a daily, weekly, monthly, or yearly breakdown in aTimeSeriesDataSpec
is used only to determine the overall period of time that the time-series data should cover.Custom breakdowns (made with
DatedBreakdown.custom
) deserve a special note, however… If you create aTimeSeriesDataSpec
with a custom breakdown (made up of your own custom-specifieddegreedays.time.DayRanges
), then your time-series data will cover only the day ranges you specify. If you specify day ranges with gaps between them, then your time-series data will have gaps too (just as your degree days would if you used the same breakdown for them). This is rather contrary to our general ethos of providing continuous data with no gaps, but it will only happen if you specifically request it (by specifying a custom breakdown with gaps between its day ranges).See also:
DatedDataSpec
,AverageDataSpec
- property breakdown: DatedBreakdown¶
The
DatedBreakdown
object that defines the period of time that the time-series data should cover (read more on how this works).
- property calculation: TimeSeriesCalculation¶
The
TimeSeriesCalculation
object that defines how the time-series data should be calculated. For example it could specify hourly temperature data, in Celsius.
- class degreedays.api.data.TimeSeriesDataValue(value: float, percentageEstimated: float, datetime: datetime)¶
Contains a value (e.g. a temperature value) for a specific point in time, and an approximate indication of its accuracy.
- property datetime: datetime¶
A
datetime.datetime
object indicating the YYYY-MM-DDThh:mm (i.e. minute precision) date-time of thisTimeSeriesDataValue
, in the local time-zone of the weather station.This holds the full detail of the date-time associated with this
TimeSeriesDataValue
, including its time-zone info (accessible via thetzinfo
property of thedatetime.datetime
object).
- property percentageEstimated: float¶
A number between 0 and 100 (both inclusive), indicating the extent to which the calculated
value
is based on estimated data.Generally speaking, a value with a lower percentage-estimated figure is likely to be more reliable than one with a higher percentage-estimated figure.
- class degreedays.api.data.TimeSeriesInterval¶
Defines the interval (e.g. hourly) that time-series data should be calculated with.
- HOURLY: TimeSeriesInterval¶
For regular hourly time-series data i.e. readings on the hour every hour.
Access via:
TimeSeriesInterval.HOURLY