Skip to content

Commit

Permalink
[FEATURE] Aggregates for expressions
Browse files Browse the repository at this point in the history
This commit adds a number of different forms of aggregates to
the expression engine.

1. Aggregates within the current layer, eg sum("passengers")
Supports sub expressions (ie sum("passengers"/2) ), group by
( sum("passengers", group_by:="line_segment") ), and optional
filters ( sum("passengers", filter:= "station_class" > 3 ) )

2. Relational aggregates, which calculate an aggregate over
all matching child features from a relation, eg
relation_aggregate( 'my_relation', 'mean', "some_child_field" )

3. A summary aggregate function, for calculating aggregates
on other layers. Eg aggregate('rail_station_layer','sum',"passengers")
The summary aggregate function supports an optional filter,
making it possible to calculate things like:

aggregate('rail_stations','sum',"passengers",
  intersects(@atlas_geometry, $geometry ) )

for calculating the total number of passengers for the stations
inside the current atlas feature

In all cases the calculations are cached inside the expression
context, so they only need to be calculated once for each
set of expression evaluations.

Sponsored by Kanton of Zug, Switzerland
  • Loading branch information
nyalldawson committed May 17, 2016
1 parent ea06659 commit 307aabd
Show file tree
Hide file tree
Showing 39 changed files with 1,198 additions and 45 deletions.
34 changes: 34 additions & 0 deletions python/core/qgsaggregatecalculator.sip
Expand Up @@ -37,6 +37,24 @@ class QgsAggregateCalculator
InterQuartileRange, //!< Inter quartile range (IQR) (numeric fields only)
StringMinimumLength, //!< Minimum length of string (string fields only)
StringMaximumLength, //!< Maximum length of string (string fields only)
StringConcatenate, //! Concatenate values with a joining string (string fields only). Specify the delimiter using setDelimiter().
};

//! A bundle of parameters controlling aggregate calculation
struct AggregateParameters
{
/** Optional filter for calculating aggregate over a subset of features, or an
* empty string to use all features.
* @see QgsAggregateCalculator::setFilter()
* @see QgsAggregateCalculator::filter()
*/
QString filter;

/** Delimiter to use for joining values with the StringConcatenate aggregate.
* @see QgsAggregateCalculator::setDelimiter()
* @see QgsAggregateCalculator::delimiter()
*/
QString delimiter;
};

/** Constructor for QgsAggregateCalculator.
Expand All @@ -48,6 +66,11 @@ class QgsAggregateCalculator
*/
QgsVectorLayer* layer() const;

/** Sets all aggregate parameters from a parameter bundle.
* @param parameters aggregate parameters
*/
void setParameters( const AggregateParameters& parameters );

/** Sets a filter to limit the features used during the aggregate calculation.
* @param filterExpression expression for filtering features, or empty string to remove filter
* @see filter()
Expand All @@ -59,6 +82,17 @@ class QgsAggregateCalculator
*/
QString filter() const;

/** Sets the delimiter to use for joining values with the StringConcatenate aggregate.
* @param delimiter string delimiter
* @see delimiter()
*/
void setDelimiter( const QString& delimiter );

/** Returns the delimiter used for joining values with the StringConcatenate aggregate.
* @see setDelimiter()
*/
QString delimiter() const;

/** Calculates the value of an aggregate.
* @param aggregate aggregate to calculate
* @param fieldOrExpression source field or expression to use as basis for aggregated values.
Expand Down
14 changes: 14 additions & 0 deletions python/core/qgsrelation.sip
Expand Up @@ -97,6 +97,8 @@ class QgsRelation
* @param feature A feature from the referenced (parent) layer
*
* @return An iterator with all the referenced features
* @see getRelatedFeaturesRequest()
* @see getRelatedFeaturesFilter()
*/
QgsFeatureIterator getRelatedFeatures( const QgsFeature& feature ) const;

Expand All @@ -107,9 +109,21 @@ class QgsRelation
* @param feature A feature from the referenced (parent) layer
*
* @return A request for all the referencing features
* @see getRelatedFeatures()
* @see getRelatedFeaturesFilter()
*/
QgsFeatureRequest getRelatedFeaturesRequest( const QgsFeature& feature ) const;

/** Returns a filter expression which returns all the features on the referencing (child) layer
* which have a foreign key pointing to the provided feature.
* @param feature A feature from the referenced (parent) layer
* @return expression filter string for all the referencing features
* @note added in QGIS 2.16
* @see getRelatedFeatures()
* @see getRelatedFeaturesRequest()
*/
QString getRelatedFeaturesFilter( const QgsFeature& feature ) const;

/**
* Creates a request to return the feature on the referenced (parent) layer
* which is referenced by the provided feature.
Expand Down
5 changes: 5 additions & 0 deletions python/core/qgsstatisticalsummary.sip
Expand Up @@ -120,6 +120,11 @@ class QgsStatisticalSummary
*/
int count() const;

/** Returns the number of missing (null) values
* @note added in QGIS 2.16
*/
int countMissing() const;

/** Returns calculated sum of values
*/
double sum() const;
Expand Down
5 changes: 2 additions & 3 deletions python/core/qgsvectordataprovider.sip
Expand Up @@ -158,16 +158,15 @@ class QgsVectorDataProvider : QgsDataProvider
* but subclasses can override this method to handoff calculation of aggregates to the provider.
* @param aggregate aggregate to calculate
* @param index the index of the attribute to calculate aggregate over
* @param filter optional filter for calculating aggregate over a subset of features, or an
* empty string to use all features
* @param parameters parameters controlling aggregate calculation
* @param context expression context for filter
* @param ok will be set to true if calculation was successfully performed by the data provider
* @return calculated aggregate value
* @note added in QGIS 2.16
*/
virtual QVariant aggregate( QgsAggregateCalculator::Aggregate aggregate,
int index,
const QString& filter,
const QgsAggregateCalculator::AggregateParameters& parameters,
QgsExpressionContext* context,
bool& ok );

Expand Down
5 changes: 2 additions & 3 deletions python/core/qgsvectorlayer.sip
Expand Up @@ -1278,16 +1278,15 @@ class QgsVectorLayer : QgsMapLayer
/** Calculates an aggregated value from the layer's features.
* @param aggregate aggregate to calculate
* @param fieldOrExpression source field or expression to use as basis for aggregated values.
* @param filter optional filter for calculating aggregate over a subset of features, or an
* empty string to use all features
* @param parameters parameters controlling aggregate calculation
* @param context expression context for expressions and filters
* @param ok if specified, will be set to true if aggregate calculation was successful
* @return calculated aggregate value
* @note added in QGIS 2.16
*/
QVariant aggregate( QgsAggregateCalculator::Aggregate aggregate,
const QString& fieldOrExpression,
const QString& filter = QString(),
const QgsAggregateCalculator::AggregateParameters& parameters = QgsAggregateCalculator::AggregateParameters(),
QgsExpressionContext* context = nullptr,
bool* ok = nullptr );

Expand Down
19 changes: 19 additions & 0 deletions resources/function_help/json/aggregate
@@ -0,0 +1,19 @@
{
"name": "aggregate",
"type": "function",
"description": "Returns an aggregate value calculated using features from another layer.",
"arguments": [
{"arg":"layer", "description":"a string, representing either a layer name or layer ID"},
{"arg":"aggregate", "description":"a string corresponding to the aggregate to calculate. Valid options are:<br /><ul><li>count</li><li>count_distinct</li><li>count_missing</li><li>min</li><li>max</li><li>sum</li><li>mean</li><li>median</li><li>stdev</li><li>stdevsample</li><li>range</li><li>minority</li><li>majority</li><li>q1: first quartile</li><li>q3: third quartile</li><li>iqr: inter quartile range</li><li>min_length: minimum string length</li><li>max_length: maximum string length</li><li>concatenate: join strings with a concatenator</li></ul>"},
{"arg":"calculation", "description":"sub expression or field name to aggregate"},
{"arg":"filter", "optional":true, "description":"optional filter expression to limit the features used for calculating the aggregate"},
{"arg":"concatenator", "optional":true, "description":"optional string to use to join values for 'concatenate' aggregate"}
],
"examples": [
{ "expression":"aggregate(layer:='rail_stations',aggregate:='sum',expression:=\"passengers\")", "returns":"sum of all values from the passengers field in the rail_stations layer"},
{ "expression":"aggregate('rail_stations','sum', \"passengers\"/7)", "returns":"calculates a daily average of \"passengers\" by dividing the \"passengers\" field by 7 before summing the values"},
{ "expression":"aggregate(layer:='rail_stations',aggregate:='sum',expression:=\"passengers\")", "returns":"sum of all values from the passengers field in the rail_stations layer"},
{ "expression":"aggregate(layer:='rail_stations',calculation:='sum',expression:=\"passengers\",filter:=\"class\">3)", "returns":"sums up all values from the \"passengers\" field from features where the \"class\" attribute is greater than 3 only"},
{ "expression":"aggregate(layer:='rail_stations',calculation:='concatenate', expression:=\"name\", concatenator:=',')", "returns":"comma separated list of the name field for all features in the rail_stations layer"}
]
}
14 changes: 14 additions & 0 deletions resources/function_help/json/concatenate
@@ -0,0 +1,14 @@
{
"name": "concatenate",
"type": "function",
"description": "Returns the all aggregated strings from a field or expression joined by a delimiter.",
"arguments": [
{"arg":"expression", "description":"sub expression of field to aggregate"},
{"arg":"group_by", "optional":true, "description":"optional expression to use to group aggregate calculations"},
{"arg":"filter", "optional":true, "description":"optional expression to use to filter features used to calculate aggregate"},
{"arg":"concatenator", "optional":true, "description":"optional string to use to join values"}
],
"examples": [
{ "expression":"concatenate(\"town_name\",group_by:=\"state\",concatenator:=',')", "returns":"comma separated list of town_names, grouped by state field"}
]
}
13 changes: 13 additions & 0 deletions resources/function_help/json/count
@@ -0,0 +1,13 @@
{
"name": "count",
"type": "function",
"description": "Returns the count of matching features.",
"arguments": [
{"arg":"expression", "description":"sub expression of field to aggregate"},
{"arg":"group_by", "optional":true, "description":"optional expression to use to group aggregate calculations"},
{"arg":"filter", "optional":true, "description":"optional expression to use to filter features used to calculate aggregate"}
],
"examples": [
{ "expression":"count(\"stations\",group_by:=\"state\")", "returns":"count of stations, grouped by state field"}
]
}
13 changes: 13 additions & 0 deletions resources/function_help/json/count_distinct
@@ -0,0 +1,13 @@
{
"name": "count_distinct",
"type": "function",
"description": "Returns the count of distinct values.",
"arguments": [
{"arg":"expression", "description":"sub expression of field to aggregate"},
{"arg":"group_by", "optional":true, "description":"optional expression to use to group aggregate calculations"},
{"arg":"filter", "optional":true, "description":"optional expression to use to filter features used to calculate aggregate"}
],
"examples": [
{ "expression":"count_distinct(\"stations\",group_by:=\"state\")", "returns":"count of distinct stations values, grouped by state field"}
]
}
13 changes: 13 additions & 0 deletions resources/function_help/json/count_missing
@@ -0,0 +1,13 @@
{
"name": "count_missing",
"type": "function",
"description": "Returns the count of missing (null) values.",
"arguments": [
{"arg":"expression", "description":"sub expression of field to aggregate"},
{"arg":"group_by", "optional":true, "description":"optional expression to use to group aggregate calculations"},
{"arg":"filter", "optional":true, "description":"optional expression to use to filter features used to calculate aggregate"}
],
"examples": [
{ "expression":"count_missing(\"stations\",group_by:=\"state\")", "returns":"count of missing (null) station values, grouped by state field"}
]
}
13 changes: 13 additions & 0 deletions resources/function_help/json/iqr
@@ -0,0 +1,13 @@
{
"name": "iqr",
"type": "function",
"description": "Returns the calculated inter quartile range from a field or expression.",
"arguments": [
{"arg":"expression", "description":"sub expression of field to aggregate"},
{"arg":"group_by", "optional":true, "description":"optional expression to use to group aggregate calculations"},
{"arg":"filter", "optional":true, "description":"optional expression to use to filter features used to calculate aggregate"}
],
"examples": [
{ "expression":"iqr(\"population\",group_by:=\"state\")", "returns":"inter quartile range of population value, grouped by state field"}
]
}
13 changes: 13 additions & 0 deletions resources/function_help/json/majority
@@ -0,0 +1,13 @@
{
"name": "majority",
"type": "function",
"description": "Returns the aggregate majority of values (most commonly occurring value) from a field or expression.",
"arguments": [
{"arg":"expression", "description":"sub expression of field to aggregate"},
{"arg":"group_by", "optional":true, "description":"optional expression to use to group aggregate calculations"},
{"arg":"filter", "optional":true, "description":"optional expression to use to filter features used to calculate aggregate"}
],
"examples": [
{ "expression":"majority(\"class\",group_by:=\"state\")", "returns":"most commonly occurring class value, grouped by state field"}
]
}
13 changes: 13 additions & 0 deletions resources/function_help/json/max_length
@@ -0,0 +1,13 @@
{
"name": "max_length",
"type": "function",
"description": "Returns the maximum length of strings from a field or expression.",
"arguments": [
{"arg":"expression", "description":"sub expression of field to aggregate"},
{"arg":"group_by", "optional":true, "description":"optional expression to use to group aggregate calculations"},
{"arg":"filter", "optional":true, "description":"optional expression to use to filter features used to calculate aggregate"}
],
"examples": [
{ "expression":"max_length(\"town_name\",group_by:=\"state\")", "returns":"maximum length of town_name, grouped by state field"}
]
}
13 changes: 13 additions & 0 deletions resources/function_help/json/maximum
@@ -0,0 +1,13 @@
{
"name": "maximum",
"type": "function",
"description": "Returns the aggregate maximum value from a field or expression.",
"arguments": [
{"arg":"expression", "description":"sub expression of field to aggregate"},
{"arg":"group_by", "optional":true, "description":"optional expression to use to group aggregate calculations"},
{"arg":"filter", "optional":true, "description":"optional expression to use to filter features used to calculate aggregate"}
],
"examples": [
{ "expression":"maximum(\"population\",group_by:=\"state\")", "returns":"maximum population value, grouped by state field"}
]
}
13 changes: 13 additions & 0 deletions resources/function_help/json/mean
@@ -0,0 +1,13 @@
{
"name": "mean",
"type": "function",
"description": "Returns the aggregate mean value from a field or expression.",
"arguments": [
{"arg":"expression", "description":"sub expression of field to aggregate"},
{"arg":"group_by", "optional":true, "description":"optional expression to use to group aggregate calculations"},
{"arg":"filter", "optional":true, "description":"optional expression to use to filter features used to calculate aggregate"}
],
"examples": [
{ "expression":"mean(\"population\",group_by:=\"state\")", "returns":"mean population value, grouped by state field"}
]
}
13 changes: 13 additions & 0 deletions resources/function_help/json/median
@@ -0,0 +1,13 @@
{
"name": "median",
"type": "function",
"description": "Returns the aggregate median value from a field or expression.",
"arguments": [
{"arg":"expression", "description":"sub expression of field to aggregate"},
{"arg":"group_by", "optional":true, "description":"optional expression to use to group aggregate calculations"},
{"arg":"filter", "optional":true, "description":"optional expression to use to filter features used to calculate aggregate"}
],
"examples": [
{ "expression":"median(\"population\",group_by:=\"state\")", "returns":"median population value, grouped by state field"}
]
}
13 changes: 13 additions & 0 deletions resources/function_help/json/min_length
@@ -0,0 +1,13 @@
{
"name": "min_length",
"type": "function",
"description": "Returns the minimum length of strings from a field or expression.",
"arguments": [
{"arg":"expression", "description":"sub expression of field to aggregate"},
{"arg":"group_by", "optional":true, "description":"optional expression to use to group aggregate calculations"},
{"arg":"filter", "optional":true, "description":"optional expression to use to filter features used to calculate aggregate"}
],
"examples": [
{ "expression":"min_length(\"town_name\",group_by:=\"state\")", "returns":"minimum length of town_name, grouped by state field"}
]
}
13 changes: 13 additions & 0 deletions resources/function_help/json/minimum
@@ -0,0 +1,13 @@
{
"name": "minimum",
"type": "function",
"description": "Returns the aggregate minimum value from a field or expression.",
"arguments": [
{"arg":"expression", "description":"sub expression of field to aggregate"},
{"arg":"group_by", "optional":true, "description":"optional expression to use to group aggregate calculations"},
{"arg":"filter", "optional":true, "description":"optional expression to use to filter features used to calculate aggregate"}
],
"examples": [
{ "expression":"minimum(\"population\",group_by:=\"state\")", "returns":"minimum population value, grouped by state field"}
]
}
13 changes: 13 additions & 0 deletions resources/function_help/json/minority
@@ -0,0 +1,13 @@
{
"name": "minority",
"type": "function",
"description": "Returns the aggregate minority of values (least occurring value) from a field or expression.",
"arguments": [
{"arg":"expression", "description":"sub expression of field to aggregate"},
{"arg":"group_by", "optional":true, "description":"optional expression to use to group aggregate calculations"},
{"arg":"filter", "optional":true, "description":"optional expression to use to filter features used to calculate aggregate"}
],
"examples": [
{ "expression":"minority(\"class\",group_by:=\"state\")", "returns":"least occurring class value, grouped by state field"}
]
}
13 changes: 13 additions & 0 deletions resources/function_help/json/q1
@@ -0,0 +1,13 @@
{
"name": "q1",
"type": "function",
"description": "Returns the calculated first quartile from a field or expression.",
"arguments": [
{"arg":"expression", "description":"sub expression of field to aggregate"},
{"arg":"group_by", "optional":true, "description":"optional expression to use to group aggregate calculations"},
{"arg":"filter", "optional":true, "description":"optional expression to use to filter features used to calculate aggregate"}
],
"examples": [
{ "expression":"q1(\"population\",group_by:=\"state\")", "returns":"first quartile of population value, grouped by state field"}
]
}
13 changes: 13 additions & 0 deletions resources/function_help/json/q3
@@ -0,0 +1,13 @@
{
"name": "q3",
"type": "function",
"description": "Returns the calculated third quartile from a field or expression.",
"arguments": [
{"arg":"expression", "description":"sub expression of field to aggregate"},
{"arg":"group_by", "optional":true, "description":"optional expression to use to group aggregate calculations"},
{"arg":"filter", "optional":true, "description":"optional expression to use to filter features used to calculate aggregate"}
],
"examples": [
{ "expression":"q3(\"population\",group_by:=\"state\")", "returns":"third quartile of population value, grouped by state field"}
]
}
13 changes: 13 additions & 0 deletions resources/function_help/json/range
@@ -0,0 +1,13 @@
{
"name": "range",
"type": "function",
"description": "Returns the aggregate range of values (maximum - minimum) from a field or expression.",
"arguments": [
{"arg":"expression", "description":"sub expression of field to aggregate"},
{"arg":"group_by", "optional":true, "description":"optional expression to use to group aggregate calculations"},
{"arg":"filter", "optional":true, "description":"optional expression to use to filter features used to calculate aggregate"}
],
"examples": [
{ "expression":"range(\"population\",group_by:=\"state\")", "returns":"range of population values, grouped by state field"}
]
}

0 comments on commit 307aabd

Please sign in to comment.