QGIS API Documentation 4.3.0-Master (45633be667c)
Loading...
Searching...
No Matches
qgsalgorithmstatisticsbycategories.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmstatisticsbycategories.cpp
3 ------------------------------
4 begin : September 2026
5 copyright : (C) 2026 by Alexander Bruy
6 email : alexander dot bruy at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19
23
24#include <QString>
25
26using namespace Qt::StringLiterals;
27
29
30QString QgsStatisticsByCategoriesAlgorithm::name() const
31{
32 return u"statisticsbycategories"_s;
33}
34
35QString QgsStatisticsByCategoriesAlgorithm::displayName() const
36{
37 return QObject::tr( "Statistics by categories" );
38}
39
40QStringList QgsStatisticsByCategoriesAlgorithm::tags() const
41{
42 return QObject::tr(
43 "groups,stats,statistics,table,layer,sum,maximum,minimum,mean,average,standard,deviation,count,distinct,unique,variance,median,quartile,range,majority,minority,histogram,distinct,summary"
44 )
45 .split( ',' );
46}
47
48QString QgsStatisticsByCategoriesAlgorithm::group() const
49{
50 return QObject::tr( "Vector analysis" );
51}
52
53QString QgsStatisticsByCategoriesAlgorithm::groupId() const
54{
55 return u"vectoranalysis"_s;
56}
57
58QString QgsStatisticsByCategoriesAlgorithm::shortHelpString() const
59{
60 return QObject::tr(
61 "This algorithm calculates statistics of fields depending on a parent class. Numeric, date, time and string fields are supported. The statistics "
62 "returned will depend on the field type.\n\n"
63 "NOTE that numeric NULL values are ignored when calculating statistics."
64 );
65}
66
67QString QgsStatisticsByCategoriesAlgorithm::shortDescription() const
68{
69 return QObject::tr( "Calculates statistics of fields depending on a parent class." );
70}
71
72QgsStatisticsByCategoriesAlgorithm *QgsStatisticsByCategoriesAlgorithm::createInstance() const
73{
74 return new QgsStatisticsByCategoriesAlgorithm();
75}
76
77void QgsStatisticsByCategoriesAlgorithm::initAlgorithm( const QVariantMap & )
78{
79 auto inputParam = std::make_unique<QgsProcessingParameterFeatureSource>( u"INPUT"_s, QObject::tr( "Input layer" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::Vector ) );
80 inputParam->setHelp( QObject::tr( "Input vector layer containing values to categorize and analyze." ) );
81 addParameter( inputParam.release() );
82
83 auto valueFieldParam = std::make_unique<
84 QgsProcessingParameterField>( u"VALUES_FIELD_NAME"_s, QObject::tr( "Field to calculate statistics on" ), QVariant(), u"INPUT"_s, Qgis::ProcessingFieldParameterDataType::Any, false, true );
85 valueFieldParam->setHelp( QObject::tr( "Feld containing the values to analyze. If left empty, the algorithm will only compute the total feature count for each unique category." ) );
86 addParameter( valueFieldParam.release() );
87
88 auto categoryFieldsParam
89 = std::make_unique<QgsProcessingParameterField>( u"CATEGORIES_FIELD_NAME"_s, QObject::tr( "Field(s) with categories" ), QVariant(), u"INPUT"_s, Qgis::ProcessingFieldParameterDataType::Any, true, false );
90 categoryFieldsParam->setHelp(
91 QObject::tr( "One or more fields to group features by. A separate row of statistical summary values will be generated for every unique combination of these category values." )
92 );
93 addParameter( categoryFieldsParam.release() );
94
95 auto outputParam = std::make_unique<QgsProcessingParameterFeatureSink>( u"OUTPUT"_s, QObject::tr( "Statistics by category" ), Qgis::ProcessingSourceType::Vector, QVariant() );
96 outputParam->setHelp( QObject::tr( "Output table where the calculated summary statistics per category will be saved." ) );
97 addParameter( outputParam.release() );
98}
99
100QVariantMap QgsStatisticsByCategoriesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
101{
102 QGS_MARK_ALGORITHM_SOURCE
103
104 std::unique_ptr<QgsProcessingFeatureSource> source( parameterAsSource( parameters, u"INPUT"_s, context ) );
105 if ( !source )
106 {
107 throw QgsProcessingException( invalidSourceError( parameters, u"INPUT"_s ) );
108 }
109
110 const QString valueFieldName = parameterAsString( parameters, u"VALUES_FIELD_NAME"_s, context );
111 const int valueFieldIndex = source->fields().lookupField( valueFieldName );
112 if ( !valueFieldName.isEmpty() && valueFieldIndex < 0 )
113 {
114 throw QgsProcessingException( QObject::tr( "Field “%1” does not exist." ).arg( valueFieldName ) );
115 }
116 const QgsField valueField = valueFieldIndex >= 0 ? source->fields().at( valueFieldIndex ) : QgsField();
117
118 const QStringList categoryFieldNames = parameterAsStrings( parameters, u"CATEGORIES_FIELD_NAME"_s, context );
119
120 QList<int> categoryFieldIndexes;
121 categoryFieldIndexes.reserve( categoryFieldNames.size() );
122 QgsFields outputFields;
123 for ( const QString &fieldName : categoryFieldNames )
124 {
125 const int fieldIndex = source->fields().lookupField( fieldName );
126 if ( fieldIndex < 0 )
127 {
128 throw QgsProcessingException( QObject::tr( "Field “%1” does not exist." ).arg( fieldName ) );
129 }
130 categoryFieldIndexes.append( fieldIndex );
131 outputFields.append( source->fields().at( fieldIndex ) );
132 }
133
134 // Adds a field to the output, keeping the same data type as the valueField
135 auto addFieldWithSameType = [&]( const QString &name ) {
136 QgsField field( valueField );
137 field.setName( name );
138 outputFields.append( field );
139 };
140
141 if ( valueFieldIndex < 0 )
142 {
143 outputFields.append( QgsField( u"count"_s, QMetaType::Type::Int ) );
144 }
145 else if ( valueField.isNumeric() )
146 {
147 outputFields.append( QgsField( u"count"_s, QMetaType::Type::Int ) );
148 outputFields.append( QgsField( u"unique"_s, QMetaType::Type::Int ) );
149 outputFields.append( QgsField( u"min"_s, QMetaType::Type::Double ) );
150 outputFields.append( QgsField( u"max"_s, QMetaType::Type::Double ) );
151 outputFields.append( QgsField( u"range"_s, QMetaType::Type::Double ) );
152 outputFields.append( QgsField( u"sum"_s, QMetaType::Type::Double ) );
153 outputFields.append( QgsField( u"mean"_s, QMetaType::Type::Double ) );
154 outputFields.append( QgsField( u"median"_s, QMetaType::Type::Double ) );
155 outputFields.append( QgsField( u"stddev"_s, QMetaType::Type::Double ) );
156 outputFields.append( QgsField( u"minority"_s, QMetaType::Type::Double ) );
157 outputFields.append( QgsField( u"majority"_s, QMetaType::Type::Double ) );
158 outputFields.append( QgsField( u"q1"_s, QMetaType::Type::Double ) );
159 outputFields.append( QgsField( u"q3"_s, QMetaType::Type::Double ) );
160 outputFields.append( QgsField( u"iqr"_s, QMetaType::Type::Double ) );
161 }
162 else if ( valueField.isDateOrTime() )
163 {
164 outputFields.append( QgsField( u"count"_s, QMetaType::Type::Int ) );
165 outputFields.append( QgsField( u"unique"_s, QMetaType::Type::Int ) );
166 outputFields.append( QgsField( u"empty"_s, QMetaType::Type::Int ) );
167 outputFields.append( QgsField( u"filled"_s, QMetaType::Type::Int ) );
168 // min and max fields should have the same data type as valueField
169 addFieldWithSameType( u"min"_s );
170 addFieldWithSameType( u"max"_s );
171 }
172 else
173 {
174 outputFields.append( QgsField( u"count"_s, QMetaType::Type::Int ) );
175 outputFields.append( QgsField( u"unique"_s, QMetaType::Type::Int ) );
176 outputFields.append( QgsField( u"empty"_s, QMetaType::Type::Int ) );
177 outputFields.append( QgsField( u"filled"_s, QMetaType::Type::Int ) );
178 // min and max fields should have the same data type as valueField
179 addFieldWithSameType( u"min"_s );
180 addFieldWithSameType( u"max"_s );
181 outputFields.append( QgsField( u"min_length"_s, QMetaType::Type::Int ) );
182 outputFields.append( QgsField( u"max_length"_s, QMetaType::Type::Int ) );
183 outputFields.append( QgsField( u"mean_length"_s, QMetaType::Type::Double ) );
184 }
185
186 QgsFeatureRequest request;
188
189 QList<int> attributeIndexes;
190 if ( valueFieldIndex >= 0 )
191 {
192 attributeIndexes.append( valueFieldIndex );
193 }
194 attributeIndexes.append( categoryFieldIndexes );
195 request.setSubsetOfAttributes( attributeIndexes );
197 long long count = 0;
198 const double step = source->featureCount() > 0 ? 50.0 / source->featureCount() : 0.0;
199
200 QMap<QVariantList, int> countValues;
201 QMap<QVariantList, QList<double>> numericValues;
202 QMap<QVariantList, QVariantList> dateTimeValues;
203 QMap<QVariantList, QStringList> stringValues;
204
205 QgsFeature f;
206 while ( it.nextFeature( f ) )
207 {
208 if ( feedback->isCanceled() )
209 {
210 break;
211 }
212
213 feedback->setProgress( static_cast<int>( count * step ) );
214
215 QVariantList categoryKey;
216 categoryKey.reserve( categoryFieldIndexes.size() );
217 for ( int i : std::as_const( categoryFieldIndexes ) )
218 {
219 categoryKey.append( f.attribute( i ) );
220 }
221
222 if ( valueFieldIndex < 0 )
223 {
224 countValues[categoryKey]++;
225 }
226 else if ( valueField.isNumeric() )
227 {
228 const QVariant value = f.attribute( valueFieldIndex );
229 if ( !QgsVariantUtils::isNull( value ) )
230 {
231 numericValues[categoryKey].append( value.toDouble() );
232 }
233 }
234 else if ( valueField.isDateOrTime() )
235 {
236 const QVariant value = f.attribute( valueFieldIndex );
237 dateTimeValues[categoryKey].append( QgsVariantUtils::isNull( value ) ? QVariant() : value );
238 }
239 else
240 {
241 const QVariant value = f.attribute( valueFieldIndex );
242 stringValues[categoryKey].append( QgsVariantUtils::isNull( value ) ? QString() : value.toString() );
243 }
244
245 count++;
246 }
247
248 QString destId;
249 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u"OUTPUT"_s, context, destId, outputFields, Qgis::WkbType::NoGeometry, QgsCoordinateReferenceSystem() ) );
250 if ( !sink )
251 {
252 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT"_s ) );
253 }
254
255 if ( valueFieldIndex < 0 )
256 {
257 saveCounts( parameters, countValues, sink.get(), feedback );
258 }
259 else if ( valueField.isNumeric() )
260 {
261 calculateNumericStatistics( parameters, numericValues, sink.get(), feedback );
262 }
263 else if ( valueField.isDateOrTime() )
264 {
265 calculateDateTimeStatistics( parameters, dateTimeValues, sink.get(), feedback );
266 }
267 else
268 {
269 calculateStringStatistics( parameters, stringValues, sink.get(), feedback );
270 }
271
272 sink->finalize();
273 feedback->featureSinkFinalized( u"OUTPUT"_s );
274
275 QVariantMap outputs;
276 outputs.insert( u"OUTPUT"_s, destId );
277 return outputs;
278}
279
280void QgsStatisticsByCategoriesAlgorithm::saveCounts( const QVariantMap &parameters, const QMap<QVariantList, int> &values, QgsFeatureSink *sink, QgsProcessingFeedback *feedback )
281{
282 const double step = values.isEmpty() ? 0 : 50.0 / values.size();
283 long long count = 0;
284
285 for ( auto it = values.constBegin(); it != values.constEnd(); ++it )
286 {
287 if ( feedback->isCanceled() )
288 {
289 break;
290 }
291
292 feedback->setProgress( static_cast<int>( count * step ) + 50 );
293
294 QgsFeature f;
295 QVariantList attrs = it.key();
296 attrs.append( it.value() );
297 f.setAttributes( attrs );
298 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
299 {
300 throw QgsProcessingException( writeFeatureError( sink, parameters, u"OUTPUT"_s ) );
301 }
302 else
303 {
304 feedback->featureAddedToSink( u"OUTPUT"_s );
305 }
306
307 count++;
308 }
309}
310
311void QgsStatisticsByCategoriesAlgorithm::calculateNumericStatistics( const QVariantMap &parameters, const QMap<QVariantList, QList<double>> &values, QgsFeatureSink *sink, QgsProcessingFeedback *feedback )
312{
314 const double step = values.isEmpty() ? 0 : 50.0 / values.size();
315 long long count = 0;
316
317 for ( auto it = values.constBegin(); it != values.constEnd(); ++it )
318 {
319 if ( feedback->isCanceled() )
320 {
321 break;
322 }
323
324 feedback->setProgress( static_cast<int>( count * step ) + 50 );
325
326 stat.calculate( it.value() );
327
328 QgsFeature f;
329 QVariantList attrs = it.key();
330 attrs
331 << stat.count()
332 << stat.variety()
333 << stat.min()
334 << stat.max()
335 << stat.range()
336 << stat.sum()
337 << stat.mean()
338 << stat.median()
339 << stat.stDev()
340 << stat.minority()
341 << stat.majority()
342 << stat.firstQuartile()
343 << stat.thirdQuartile()
344 << stat.interQuartileRange();
345
346 f.setAttributes( attrs );
347 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
348 {
349 throw QgsProcessingException( writeFeatureError( sink, parameters, u"OUTPUT"_s ) );
350 }
351 else
352 {
353 feedback->featureAddedToSink( u"OUTPUT"_s );
354 }
355
356 count++;
357 }
358}
359
360void QgsStatisticsByCategoriesAlgorithm::calculateDateTimeStatistics( const QVariantMap &parameters, const QMap<QVariantList, QVariantList> &values, QgsFeatureSink *sink, QgsProcessingFeedback *feedback )
361{
363 const double step = values.isEmpty() ? 0 : 50.0 / values.size();
364 long long count = 0;
365
366 for ( auto it = values.constBegin(); it != values.constEnd(); ++it )
367 {
368 if ( feedback->isCanceled() )
369 {
370 break;
371 }
372
373 feedback->setProgress( static_cast<int>( count * step ) + 50 );
374
375 stat.calculate( it.value() );
376
377 QgsFeature f;
378 QVariantList attrs = it.key();
379 attrs << stat.count() << stat.countDistinct() << stat.countMissing() << stat.count() - stat.countMissing() << stat.min() << stat.max();
380
381 f.setAttributes( attrs );
382 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
383 {
384 throw QgsProcessingException( writeFeatureError( sink, parameters, u"OUTPUT"_s ) );
385 }
386 else
387 {
388 feedback->featureAddedToSink( u"OUTPUT"_s );
389 }
390
391 count++;
392 }
393}
394
395void QgsStatisticsByCategoriesAlgorithm::calculateStringStatistics( const QVariantMap &parameters, const QMap<QVariantList, QStringList> &values, QgsFeatureSink *sink, QgsProcessingFeedback *feedback )
396{
398 const double step = values.isEmpty() ? 0 : 50.0 / values.size();
399 long long count = 0;
400
401 for ( auto it = values.constBegin(); it != values.constEnd(); ++it )
402 {
403 if ( feedback->isCanceled() )
404 {
405 break;
406 }
407
408 feedback->setProgress( static_cast<int>( count * step ) + 50 );
409
410 stat.calculate( it.value() );
411
412 QgsFeature f;
413 QVariantList attrs = it.key();
414 attrs << stat.count() << stat.countDistinct() << stat.countMissing() << stat.count() - stat.countMissing() << stat.min() << stat.max() << stat.minLength() << stat.maxLength() << stat.meanLength();
415
416 f.setAttributes( attrs );
417 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
418 {
419 throw QgsProcessingException( writeFeatureError( sink, parameters, u"OUTPUT"_s ) );
420 }
421 else
422 {
423 feedback->featureAddedToSink( u"OUTPUT"_s );
424 }
425
426 count++;
427 }
428}
429
@ Vector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition qgis.h:3757
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2360
@ SkipGeometryValidityChecks
Invalid geometry checks should always be skipped. This flag can be useful for algorithms which always...
Definition qgis.h:3954
@ NoGeometry
No geometry.
Definition qgis.h:312
Represents a coordinate reference system (CRS).
Calculator for summary statistics and aggregates for a list of datetimes.
void calculate(const QVariantList &values)
Calculates summary statistics for a list of variants.
QDateTime min() const
Returns the minimum (earliest) non-null datetime value.
int count() const
Returns the calculated count of values.
int countMissing() const
Returns the number of missing (null) datetime values.
int countDistinct() const
Returns the number of distinct datetime values.
QDateTime max() const
Returns the maximum (latest) non-null datetime value.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(Qgis::FeatureRequestFlags flags)
Sets flags that affect how features will be fetched.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
An interface for objects which accept features via addFeature(s) methods.
virtual bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags())
Adds a single feature to the sink.
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:65
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
bool isDateOrTime
Definition qgsfield.h:60
bool isNumeric
Definition qgsfield.h:59
Container of fields for a vector layer.
Definition qgsfields.h:45
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
Definition qgsfields.cpp:75
Contains information about the context in which a processing algorithm is executed.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
void featureAddedToSink(const QString &output)
Reports that a feature was added to the the sink associated with the specified algorithm output.
void featureSinkFinalized(const QString &output)
Reports that a feature sink has been finalized.
void setHelp(const QString &help)
Sets the help for the parameter.
A vector layer or feature source field parameter for processing algorithms.
Calculator for summary statistics for a list of doubles.
double firstQuartile() const
Returns the first quartile of the values.
void calculate(const QList< double > &values)
Calculates summary statistics for a list of values.
double sum() const
Returns calculated sum of values.
double mean() const
Returns calculated mean of values.
double majority() const
Returns majority of values.
double interQuartileRange() const
Returns the inter quartile range of the values.
double median() const
Returns calculated median of values.
double minority() const
Returns minority of values.
double min() const
Returns calculated minimum from values.
double stDev() const
Returns population standard deviation.
double thirdQuartile() const
Returns the third quartile of the values.
int count() const
Returns calculated count of values.
double range() const
Returns calculated range (difference between maximum and minimum values).
double max() const
Returns calculated maximum from values.
int variety() const
Returns variety of values.
Calculator for summary statistics and aggregates for a list of strings.
QString max() const
Returns the maximum (non-null) string value.
QString min() const
Returns the minimum (non-null) string value.
int countMissing() const
Returns the number of missing (null) string values.
int count() const
Returns the calculated count of values.
int countDistinct() const
Returns the number of distinct string values.
int minLength() const
Returns the minimum length of strings.
int maxLength() const
Returns the maximum length of strings.
void calculate(const QStringList &values)
Calculates summary statistics for an entire list of strings at once.
double meanLength() const
Returns the mean length of strings.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.