Merge pull request #5154 from nyalldawson/stats

[processing] Improve Stats by Categories algorithm, remove duplicate algs
This commit is contained in:
Nyall Dawson 2017-09-11 15:50:16 +10:00 committed by GitHub
commit 7f5bd00cbf
19 changed files with 1098 additions and 151 deletions

View File

@ -28,6 +28,8 @@ __revision__ = '$Format:%H$'
from qgis.core import (QgsProcessingParameterFeatureSource, from qgis.core import (QgsProcessingParameterFeatureSource,
QgsStatisticalSummary, QgsStatisticalSummary,
QgsDateTimeStatisticalSummary,
QgsStringStatisticalSummary,
QgsFeatureRequest, QgsFeatureRequest,
QgsProcessingParameterField, QgsProcessingParameterField,
QgsProcessingParameterFeatureSink, QgsProcessingParameterFeatureSink,
@ -36,13 +38,16 @@ from qgis.core import (QgsProcessingParameterFeatureSource,
QgsWkbTypes, QgsWkbTypes,
QgsCoordinateReferenceSystem, QgsCoordinateReferenceSystem,
QgsFeature, QgsFeature,
QgsFeatureSink) QgsFeatureSink,
QgsProcessing,
NULL)
from qgis.PyQt.QtCore import QVariant from qgis.PyQt.QtCore import QVariant
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from collections import defaultdict
class StatisticsByCategories(QgisAlgorithm): class StatisticsByCategories(QgisAlgorithm):
INPUT = 'INPUT' INPUT = 'INPUT'
VALUES_FIELD_NAME = 'VALUES_FIELD_NAME' VALUES_FIELD_NAME = 'VALUES_FIELD_NAME'
CATEGORIES_FIELD_NAME = 'CATEGORIES_FIELD_NAME' CATEGORIES_FIELD_NAME = 'CATEGORIES_FIELD_NAME'
@ -56,13 +61,16 @@ class StatisticsByCategories(QgisAlgorithm):
def initAlgorithm(self, config=None): def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
self.tr('Input vector layer'))) self.tr('Input vector layer'),
types=[QgsProcessing.TypeVector]))
self.addParameter(QgsProcessingParameterField(self.VALUES_FIELD_NAME, self.addParameter(QgsProcessingParameterField(self.VALUES_FIELD_NAME,
self.tr('Field to calculate statistics on'), self.tr(
parentLayerParameterName=self.INPUT, type=QgsProcessingParameterField.Numeric)) 'Field to calculate statistics on (if empty, only count is calculated)'),
parentLayerParameterName=self.INPUT, optional=True))
self.addParameter(QgsProcessingParameterField(self.CATEGORIES_FIELD_NAME, self.addParameter(QgsProcessingParameterField(self.CATEGORIES_FIELD_NAME,
self.tr('Field with categories'), self.tr('Field(s) with categories'),
parentLayerParameterName=self.INPUT, type=QgsProcessingParameterField.Any)) parentLayerParameterName=self.INPUT,
type=QgsProcessingParameterField.Any, allowMultiple=True))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Statistics by category'))) self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Statistics by category')))
@ -75,49 +83,213 @@ class StatisticsByCategories(QgisAlgorithm):
def processAlgorithm(self, parameters, context, feedback): def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsSource(parameters, self.INPUT, context) source = self.parameterAsSource(parameters, self.INPUT, context)
value_field_name = self.parameterAsString(parameters, self.VALUES_FIELD_NAME, context) value_field_name = self.parameterAsString(parameters, self.VALUES_FIELD_NAME, context)
category_field_name = self.parameterAsString(parameters, self.CATEGORIES_FIELD_NAME, context) category_field_names = self.parameterAsFields(parameters, self.CATEGORIES_FIELD_NAME, context)
value_field_index = source.fields().lookupField(value_field_name) value_field_index = source.fields().lookupField(value_field_name)
category_field_index = source.fields().lookupField(category_field_name) if value_field_index >= 0:
value_field = source.fields().at(value_field_index)
else:
value_field = None
category_field_indexes = [source.fields().lookupField(n) for n in category_field_names]
features = source.getFeatures(QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry)) # generate output fields
total = 100.0 / source.featureCount() if source.featureCount() else 0 fields = QgsFields()
values = {} for c in category_field_indexes:
fields.append(source.fields().at(c))
def addField(name):
"""
Adds a field to the output, keeping the same data type as the value_field
"""
field = value_field
field.setName(name)
fields.append(field)
if value_field is None:
field_type = 'none'
fields.append(QgsField('count', QVariant.Int))
elif value_field.isNumeric():
field_type = 'numeric'
fields.append(QgsField('count', QVariant.Int))
fields.append(QgsField('unique', QVariant.Int))
fields.append(QgsField('min', QVariant.Double))
fields.append(QgsField('max', QVariant.Double))
fields.append(QgsField('range', QVariant.Double))
fields.append(QgsField('sum', QVariant.Double))
fields.append(QgsField('mean', QVariant.Double))
fields.append(QgsField('median', QVariant.Double))
fields.append(QgsField('stddev', QVariant.Double))
fields.append(QgsField('minority', QVariant.Double))
fields.append(QgsField('majority', QVariant.Double))
fields.append(QgsField('q1', QVariant.Double))
fields.append(QgsField('q3', QVariant.Double))
fields.append(QgsField('iqr', QVariant.Double))
elif value_field.type() in (QVariant.Date, QVariant.Time, QVariant.DateTime):
field_type = 'datetime'
fields.append(QgsField('count', QVariant.Int))
fields.append(QgsField('unique', QVariant.Int))
fields.append(QgsField('empty', QVariant.Int))
fields.append(QgsField('filled', QVariant.Int))
# keep same data type for these fields
addField('min')
addField('max')
else:
field_type = 'string'
fields.append(QgsField('count', QVariant.Int))
fields.append(QgsField('unique', QVariant.Int))
fields.append(QgsField('empty', QVariant.Int))
fields.append(QgsField('filled', QVariant.Int))
# keep same data type for these fields
addField('min')
addField('max')
fields.append(QgsField('min_length', QVariant.Int))
fields.append(QgsField('max_length', QVariant.Int))
fields.append(QgsField('mean_length', QVariant.Double))
request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry)
if value_field is not None:
attrs = [value_field_index]
else:
attrs = []
attrs.extend(category_field_indexes)
request.setSubsetOfAttributes(attrs)
features = source.getFeatures(request)
total = 50.0 / source.featureCount() if source.featureCount() else 0
if field_type == 'none':
values = defaultdict(lambda: 0)
else:
values = defaultdict(list)
for current, feat in enumerate(features): for current, feat in enumerate(features):
if feedback.isCanceled(): if feedback.isCanceled():
break break
feedback.setProgress(int(current * total)) feedback.setProgress(int(current * total))
attrs = feat.attributes() attrs = feat.attributes()
try: cat = tuple([attrs[c] for c in category_field_indexes])
value = float(attrs[value_field_index]) if field_type == 'none':
cat = attrs[category_field_index] values[cat] += 1
if cat not in values: continue
values[cat] = [] if field_type == 'numeric':
values[cat].append(value) if attrs[value_field_index] == NULL:
except: continue
pass else:
value = float(attrs[value_field_index])
fields = QgsFields() elif field_type == 'string':
fields.append(source.fields().at(category_field_index)) if attrs[value_field_index] == NULL:
fields.append(QgsField('min', QVariant.Double)) value = ''
fields.append(QgsField('max', QVariant.Double)) else:
fields.append(QgsField('mean', QVariant.Double)) value = str(attrs[value_field_index])
fields.append(QgsField('stddev', QVariant.Double)) elif attrs[value_field_index] == NULL:
fields.append(QgsField('sum', QVariant.Double)) value = NULL
fields.append(QgsField('count', QVariant.Int)) else:
value = attrs[value_field_index]
values[cat].append(value)
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, QgsWkbTypes.NoGeometry, QgsCoordinateReferenceSystem()) fields, QgsWkbTypes.NoGeometry, QgsCoordinateReferenceSystem())
stat = QgsStatisticalSummary(QgsStatisticalSummary.Min | QgsStatisticalSummary.Max | if field_type == 'none':
QgsStatisticalSummary.Mean | QgsStatisticalSummary.StDevSample | self.saveCounts(values, sink, feedback)
QgsStatisticalSummary.Sum | QgsStatisticalSummary.Count) elif field_type == 'numeric':
self.calcNumericStats(values, sink, feedback)
for (cat, v) in list(values.items()): elif field_type == 'datetime':
stat.calculate(v) self.calcDateTimeStats(values, sink, feedback)
f = QgsFeature() else:
f.setAttributes([cat, stat.min(), stat.max(), stat.mean(), stat.sampleStDev(), stat.sum(), stat.count()]) self.calcStringStats(values, sink, feedback)
sink.addFeature(f, QgsFeatureSink.FastInsert)
return {self.OUTPUT: dest_id} return {self.OUTPUT: dest_id}
def saveCounts(self, values, sink, feedback):
total = 50.0 / len(values) if values else 0
current = 0
for cat, v in values.items():
if feedback.isCanceled():
break
feedback.setProgress(int(current * total) + 50)
f = QgsFeature()
f.setAttributes(list(cat) + [v])
sink.addFeature(f, QgsFeatureSink.FastInsert)
current += 1
def calcNumericStats(self, values, sink, feedback):
stat = QgsStatisticalSummary()
total = 50.0 / len(values) if values else 0
current = 0
for cat, v in values.items():
if feedback.isCanceled():
break
feedback.setProgress(int(current * total) + 50)
stat.calculate(v)
f = QgsFeature()
f.setAttributes(list(cat) + [stat.count(),
stat.variety(),
stat.min(),
stat.max(),
stat.range(),
stat.sum(),
stat.mean(),
stat.median(),
stat.stDev(),
stat.minority(),
stat.majority(),
stat.firstQuartile(),
stat.thirdQuartile(),
stat.interQuartileRange()])
sink.addFeature(f, QgsFeatureSink.FastInsert)
current += 1
def calcDateTimeStats(self, values, sink, feedback):
stat = QgsDateTimeStatisticalSummary()
total = 50.0 / len(values) if values else 0
current = 0
for cat, v in values.items():
if feedback.isCanceled():
break
feedback.setProgress(int(current * total) + 50)
stat.calculate(v)
f = QgsFeature()
f.setAttributes(list(cat) + [stat.count(),
stat.countDistinct(),
stat.countMissing(),
stat.count() - stat.countMissing(),
stat.statistic(QgsDateTimeStatisticalSummary.Min),
stat.statistic(QgsDateTimeStatisticalSummary.Max)
])
sink.addFeature(f, QgsFeatureSink.FastInsert)
current += 1
def calcStringStats(self, values, sink, feedback):
stat = QgsStringStatisticalSummary()
total = 50.0 / len(values) if values else 0
current = 0
for cat, v in values.items():
if feedback.isCanceled():
break
feedback.setProgress(int(current * total) + 50)
stat.calculate(v)
f = QgsFeature()
f.setAttributes(list(cat) + [stat.count(),
stat.countDistinct(),
stat.countMissing(),
stat.count() - stat.countMissing(),
stat.min(),
stat.max(),
stat.minLength(),
stat.maxLength(),
stat.meanLength()
])
sink.addFeature(f, QgsFeatureSink.FastInsert)
current += 1

View File

@ -1,47 +0,0 @@
##Vector analysis=group
#inputs
##Input=source
##Fields=field multiple Input
##Frequency=sink table
from processing.tools.vector import TableWriter
from collections import defaultdict
from qgis.core import QgsProcessingUtils, QgsFields, QgsField, QgsWkbTypes, QgsFeature
from qgis.PyQt.QtCore import QVariant
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
inputFields = Input.fields()
fieldIdxs = []
out_fields = QgsFields()
for f in Fields:
idx = inputFields.indexFromName(f)
if idx == -1:
raise GeoAlgorithmExecutionException('Field not found:' + f)
fieldIdxs.append(idx)
out_fields.append(inputFields.at(idx))
out_fields.append(QgsField('FREQ', QVariant.Int))
(sink, Frequency) = self.parameterAsSink(parameters, 'Frequency', context,
out_fields)
counts = {}
feats = Input.getFeatures()
nFeats = Input.featureCount()
counts = defaultdict(int)
for i, feat in enumerate(feats):
feedback.setProgress(int(100 * i / nFeats))
if feedback.isCanceled():
break
attrs = feat.attributes()
clazz = tuple([attrs[i] for i in fieldIdxs])
counts[clazz] += 1
for c in counts:
f = QgsFeature()
f.setAttributes(list(c) + [counts[c]])
sink.addFeature(f)

View File

@ -1,52 +0,0 @@
##Vector analysis=group
# inputs
##input=source
##class_field=field input
##value_field=field input
##N_unique_values=sink
from qgis.PyQt.QtCore import QVariant
from qgis.core import QgsFeature, QgsField, QgsProcessingUtils
fields = input.fields()
fields.append(QgsField('UNIQ_COUNT', QVariant.Int))
(sink, N_unique_values) = self.parameterAsSink(parameters, 'N_unique_values', context,
fields, input.wkbType(), input.sourceCrs())
class_field_index = input.fields().lookupField(class_field)
value_field_index = input.fields().lookupField(value_field)
outFeat = QgsFeature()
classes = {}
feats = input.getFeatures()
nFeat = input.featureCount()
for n, inFeat in enumerate(feats):
if feedback.isCanceled():
break
feedback.setProgress(int(100 * n / nFeat))
attrs = inFeat.attributes()
clazz = attrs[class_field_index]
value = attrs[value_field_index]
if clazz not in classes:
classes[clazz] = []
if value not in classes[clazz]:
classes[clazz].append(value)
feats = input.getFeatures()
for n, inFeat in enumerate(feats):
if feedback.isCanceled():
break
feedback.setProgress(int(100 * n / nFeat))
inGeom = inFeat.geometry()
outFeat.setGeometry(inGeom)
attrs = inFeat.attributes()
clazz = attrs[class_field_index]
attrs.append(len(classes[clazz]))
outFeat.setAttributes(attrs)
sink.addFeature(outFeat)

View File

@ -0,0 +1,48 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>stats_by_cat_date</Name>
<ElementPath>stats_by_cat_date</ElementPath>
<GeometryType>100</GeometryType>
<DatasetSpecificInfo>
<FeatureCount>4</FeatureCount>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>date</Name>
<ElementPath>date</ElementPath>
<Type>String</Type>
<Width>10</Width>
</PropertyDefn>
<PropertyDefn>
<Name>count</Name>
<ElementPath>count</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>unique</Name>
<ElementPath>unique</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>empty</Name>
<ElementPath>empty</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>filled</Name>
<ElementPath>filled</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>min</Name>
<ElementPath>min</ElementPath>
<Type>String</Type>
<Width>10</Width>
</PropertyDefn>
<PropertyDefn>
<Name>max</Name>
<ElementPath>max</ElementPath>
<Type>String</Type>
<Width>10</Width>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy><gml:null>missing</gml:null></gml:boundedBy>
<gml:featureMember>
<ogr:stats_by_cat_date fid="stats_by_cat_date.0">
<ogr:date>2016/11/30</ogr:date>
<ogr:count>1</ogr:count>
<ogr:unique>1</ogr:unique>
<ogr:empty>0</ogr:empty>
<ogr:filled>1</ogr:filled>
<ogr:min>2016/11/30</ogr:min>
<ogr:max>2016/11/30</ogr:max>
</ogr:stats_by_cat_date>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_date fid="stats_by_cat_date.1">
<ogr:date>2016/11/10</ogr:date>
<ogr:count>1</ogr:count>
<ogr:unique>1</ogr:unique>
<ogr:empty>0</ogr:empty>
<ogr:filled>1</ogr:filled>
<ogr:min>2016/11/10</ogr:min>
<ogr:max>2016/11/10</ogr:max>
</ogr:stats_by_cat_date>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_date fid="stats_by_cat_date.2">
<ogr:count>1</ogr:count>
<ogr:unique>0</ogr:unique>
<ogr:empty>1</ogr:empty>
<ogr:filled>0</ogr:filled>
</ogr:stats_by_cat_date>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_date fid="stats_by_cat_date.3">
<ogr:date>2014/11/30</ogr:date>
<ogr:count>1</ogr:count>
<ogr:unique>1</ogr:unique>
<ogr:empty>0</ogr:empty>
<ogr:filled>1</ogr:filled>
<ogr:min>2014/11/30</ogr:min>
<ogr:max>2014/11/30</ogr:max>
</ogr:stats_by_cat_date>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -0,0 +1,86 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>stats_by_cat_float</Name>
<ElementPath>stats_by_cat_float</ElementPath>
<GeometryType>100</GeometryType>
<DatasetSpecificInfo>
<FeatureCount>5</FeatureCount>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>name</Name>
<ElementPath>name</ElementPath>
<Type>String</Type>
<Width>2</Width>
</PropertyDefn>
<PropertyDefn>
<Name>count</Name>
<ElementPath>count</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>unique</Name>
<ElementPath>unique</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>min</Name>
<ElementPath>min</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>max</Name>
<ElementPath>max</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>range</Name>
<ElementPath>range</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>sum</Name>
<ElementPath>sum</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>mean</Name>
<ElementPath>mean</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>median</Name>
<ElementPath>median</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>stddev</Name>
<ElementPath>stddev</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>minority</Name>
<ElementPath>minority</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>majority</Name>
<ElementPath>majority</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>q1</Name>
<ElementPath>q1</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>q3</Name>
<ElementPath>q3</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>iqr</Name>
<ElementPath>iqr</ElementPath>
<Type>Real</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>

View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy><gml:null>missing</gml:null></gml:boundedBy>
<gml:featureMember>
<ogr:stats_by_cat_float fid="stats_by_cat_float.0">
<ogr:name>aa</ogr:name>
<ogr:count>2</ogr:count>
<ogr:unique>2</ogr:unique>
<ogr:min>3.33</ogr:min>
<ogr:max>44.123456</ogr:max>
<ogr:range>40.793456</ogr:range>
<ogr:sum>47.453456</ogr:sum>
<ogr:mean>23.726728</ogr:mean>
<ogr:median>23.726728</ogr:median>
<ogr:stddev>20.396728</ogr:stddev>
<ogr:minority>3.33</ogr:minority>
<ogr:majority>3.33</ogr:majority>
<ogr:q1>3.33</ogr:q1>
<ogr:q3>44.123456</ogr:q3>
<ogr:iqr>40.793456</ogr:iqr>
</ogr:stats_by_cat_float>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_float fid="stats_by_cat_float.1">
<ogr:name>dd</ogr:name>
<ogr:count>1</ogr:count>
<ogr:unique>1</ogr:unique>
<ogr:min>0</ogr:min>
<ogr:max>0</ogr:max>
<ogr:range>0</ogr:range>
<ogr:sum>0</ogr:sum>
<ogr:mean>0</ogr:mean>
<ogr:median>0</ogr:median>
<ogr:stddev>0</ogr:stddev>
<ogr:minority>0</ogr:minority>
<ogr:majority>0</ogr:majority>
<ogr:q1>0</ogr:q1>
<ogr:q3>0</ogr:q3>
<ogr:iqr>0</ogr:iqr>
</ogr:stats_by_cat_float>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_float fid="stats_by_cat_float.2">
<ogr:name>bb</ogr:name>
<ogr:count>4</ogr:count>
<ogr:unique>1</ogr:unique>
<ogr:min>0.123</ogr:min>
<ogr:max>0.123</ogr:max>
<ogr:range>0</ogr:range>
<ogr:sum>0.492</ogr:sum>
<ogr:mean>0.123</ogr:mean>
<ogr:median>0.123</ogr:median>
<ogr:stddev>0</ogr:stddev>
<ogr:minority>0.123</ogr:minority>
<ogr:majority>0.123</ogr:majority>
<ogr:q1>0.123</ogr:q1>
<ogr:q3>0.123</ogr:q3>
<ogr:iqr>0</ogr:iqr>
</ogr:stats_by_cat_float>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_float fid="stats_by_cat_float.3">
<ogr:count>1</ogr:count>
<ogr:unique>1</ogr:unique>
<ogr:min>-100291.43213</ogr:min>
<ogr:max>-100291.43213</ogr:max>
<ogr:range>0</ogr:range>
<ogr:sum>-100291.43213</ogr:sum>
<ogr:mean>-100291.43213</ogr:mean>
<ogr:median>-100291.43213</ogr:median>
<ogr:stddev>0</ogr:stddev>
<ogr:minority>-100291.43213</ogr:minority>
<ogr:majority>-100291.43213</ogr:majority>
<ogr:q1>-100291.43213</ogr:q1>
<ogr:q3>-100291.43213</ogr:q3>
<ogr:iqr>0</ogr:iqr>
</ogr:stats_by_cat_float>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_float fid="stats_by_cat_float.4">
<ogr:name>cc</ogr:name>
<ogr:count>1</ogr:count>
<ogr:unique>1</ogr:unique>
<ogr:min>0.123</ogr:min>
<ogr:max>0.123</ogr:max>
<ogr:range>0</ogr:range>
<ogr:sum>0.123</ogr:sum>
<ogr:mean>0.123</ogr:mean>
<ogr:median>0.123</ogr:median>
<ogr:stddev>0</ogr:stddev>
<ogr:minority>0.123</ogr:minority>
<ogr:majority>0.123</ogr:majority>
<ogr:q1>0.123</ogr:q1>
<ogr:q3>0.123</ogr:q3>
<ogr:iqr>0</ogr:iqr>
</ogr:stats_by_cat_float>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -0,0 +1,26 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>stats_by_cat_no_value</Name>
<ElementPath>stats_by_cat_no_value</ElementPath>
<GeometryType>100</GeometryType>
<DatasetSpecificInfo>
<FeatureCount>6</FeatureCount>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>intval</Name>
<ElementPath>intval</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>name</Name>
<ElementPath>name</ElementPath>
<Type>String</Type>
<Width>2</Width>
</PropertyDefn>
<PropertyDefn>
<Name>count</Name>
<ElementPath>count</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy><gml:null>missing</gml:null></gml:boundedBy>
<gml:featureMember>
<ogr:stats_by_cat_no_value fid="stats_by_cat_no_value.0">
<ogr:intval>1</ogr:intval>
<ogr:name>aa</ogr:name>
<ogr:count>2</ogr:count>
</ogr:stats_by_cat_no_value>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_no_value fid="stats_by_cat_no_value.1">
<ogr:name>dd</ogr:name>
<ogr:count>2</ogr:count>
</ogr:stats_by_cat_no_value>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_no_value fid="stats_by_cat_no_value.2">
<ogr:intval>1</ogr:intval>
<ogr:name>bb</ogr:name>
<ogr:count>3</ogr:count>
</ogr:stats_by_cat_no_value>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_no_value fid="stats_by_cat_no_value.3">
<ogr:intval>120</ogr:intval>
<ogr:count>1</ogr:count>
</ogr:stats_by_cat_no_value>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_no_value fid="stats_by_cat_no_value.4">
<ogr:name>cc</ogr:name>
<ogr:count>1</ogr:count>
</ogr:stats_by_cat_no_value>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_no_value fid="stats_by_cat_no_value.5">
<ogr:intval>2</ogr:intval>
<ogr:name>bb</ogr:name>
<ogr:count>1</ogr:count>
</ogr:stats_by_cat_no_value>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -0,0 +1,62 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>stats_by_cat_string</Name>
<ElementPath>stats_by_cat_string</ElementPath>
<GeometryType>100</GeometryType>
<DatasetSpecificInfo>
<FeatureCount>4</FeatureCount>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>intval</Name>
<ElementPath>intval</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>count</Name>
<ElementPath>count</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>unique</Name>
<ElementPath>unique</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>empty</Name>
<ElementPath>empty</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>filled</Name>
<ElementPath>filled</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>min</Name>
<ElementPath>min</ElementPath>
<Type>String</Type>
<Width>2</Width>
</PropertyDefn>
<PropertyDefn>
<Name>max</Name>
<ElementPath>max</ElementPath>
<Type>String</Type>
<Width>2</Width>
</PropertyDefn>
<PropertyDefn>
<Name>min_length</Name>
<ElementPath>min_length</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>max_length</Name>
<ElementPath>max_length</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>mean_length</Name>
<ElementPath>mean_length</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy><gml:null>missing</gml:null></gml:boundedBy>
<gml:featureMember>
<ogr:stats_by_cat_string fid="stats_by_cat_string.0">
<ogr:intval>1</ogr:intval>
<ogr:count>5</ogr:count>
<ogr:unique>2</ogr:unique>
<ogr:empty>0</ogr:empty>
<ogr:filled>5</ogr:filled>
<ogr:min>aa</ogr:min>
<ogr:max>bb</ogr:max>
<ogr:min_length>2</ogr:min_length>
<ogr:max_length>2</ogr:max_length>
<ogr:mean_length>2</ogr:mean_length>
</ogr:stats_by_cat_string>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_string fid="stats_by_cat_string.1">
<ogr:count>3</ogr:count>
<ogr:unique>2</ogr:unique>
<ogr:empty>0</ogr:empty>
<ogr:filled>3</ogr:filled>
<ogr:min>cc</ogr:min>
<ogr:max>dd</ogr:max>
<ogr:min_length>2</ogr:min_length>
<ogr:max_length>2</ogr:max_length>
<ogr:mean_length>2</ogr:mean_length>
</ogr:stats_by_cat_string>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_string fid="stats_by_cat_string.2">
<ogr:intval>120</ogr:intval>
<ogr:count>1</ogr:count>
<ogr:unique>1</ogr:unique>
<ogr:empty>1</ogr:empty>
<ogr:filled>0</ogr:filled>
<ogr:min></ogr:min>
<ogr:max></ogr:max>
<ogr:min_length>0</ogr:min_length>
<ogr:max_length>0</ogr:max_length>
<ogr:mean_length>0</ogr:mean_length>
</ogr:stats_by_cat_string>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_string fid="stats_by_cat_string.3">
<ogr:intval>2</ogr:intval>
<ogr:count>1</ogr:count>
<ogr:unique>1</ogr:unique>
<ogr:empty>0</ogr:empty>
<ogr:filled>1</ogr:filled>
<ogr:min>bb</ogr:min>
<ogr:max>bb</ogr:max>
<ogr:min_length>2</ogr:min_length>
<ogr:max_length>2</ogr:max_length>
<ogr:mean_length>2</ogr:mean_length>
</ogr:stats_by_cat_string>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -0,0 +1,91 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>stats_by_cat_two_fields</Name>
<ElementPath>stats_by_cat_two_fields</ElementPath>
<GeometryType>100</GeometryType>
<DatasetSpecificInfo>
<FeatureCount>6</FeatureCount>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>intval</Name>
<ElementPath>intval</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>name</Name>
<ElementPath>name</ElementPath>
<Type>String</Type>
<Width>2</Width>
</PropertyDefn>
<PropertyDefn>
<Name>count</Name>
<ElementPath>count</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>unique</Name>
<ElementPath>unique</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>min</Name>
<ElementPath>min</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>max</Name>
<ElementPath>max</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>range</Name>
<ElementPath>range</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>sum</Name>
<ElementPath>sum</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>mean</Name>
<ElementPath>mean</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>median</Name>
<ElementPath>median</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>stddev</Name>
<ElementPath>stddev</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>minority</Name>
<ElementPath>minority</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>majority</Name>
<ElementPath>majority</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>q1</Name>
<ElementPath>q1</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>q3</Name>
<ElementPath>q3</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>iqr</Name>
<ElementPath>iqr</ElementPath>
<Type>Real</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy><gml:null>missing</gml:null></gml:boundedBy>
<gml:featureMember>
<ogr:stats_by_cat_two_fields fid="stats_by_cat_two_fields.0">
<ogr:intval>1</ogr:intval>
<ogr:name>aa</ogr:name>
<ogr:count>2</ogr:count>
<ogr:unique>2</ogr:unique>
<ogr:min>3.33</ogr:min>
<ogr:max>44.123456</ogr:max>
<ogr:range>40.793456</ogr:range>
<ogr:sum>47.453456</ogr:sum>
<ogr:mean>23.726728</ogr:mean>
<ogr:median>23.726728</ogr:median>
<ogr:stddev>20.396728</ogr:stddev>
<ogr:minority>3.33</ogr:minority>
<ogr:majority>3.33</ogr:majority>
<ogr:q1>3.33</ogr:q1>
<ogr:q3>44.123456</ogr:q3>
<ogr:iqr>40.793456</ogr:iqr>
</ogr:stats_by_cat_two_fields>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_two_fields fid="stats_by_cat_two_fields.1">
<ogr:name>dd</ogr:name>
<ogr:count>1</ogr:count>
<ogr:unique>1</ogr:unique>
<ogr:min>0</ogr:min>
<ogr:max>0</ogr:max>
<ogr:range>0</ogr:range>
<ogr:sum>0</ogr:sum>
<ogr:mean>0</ogr:mean>
<ogr:median>0</ogr:median>
<ogr:stddev>0</ogr:stddev>
<ogr:minority>0</ogr:minority>
<ogr:majority>0</ogr:majority>
<ogr:q1>0</ogr:q1>
<ogr:q3>0</ogr:q3>
<ogr:iqr>0</ogr:iqr>
</ogr:stats_by_cat_two_fields>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_two_fields fid="stats_by_cat_two_fields.2">
<ogr:intval>1</ogr:intval>
<ogr:name>bb</ogr:name>
<ogr:count>3</ogr:count>
<ogr:unique>1</ogr:unique>
<ogr:min>0.123</ogr:min>
<ogr:max>0.123</ogr:max>
<ogr:range>0</ogr:range>
<ogr:sum>0.369</ogr:sum>
<ogr:mean>0.123</ogr:mean>
<ogr:median>0.123</ogr:median>
<ogr:stddev>0</ogr:stddev>
<ogr:minority>0.123</ogr:minority>
<ogr:majority>0.123</ogr:majority>
<ogr:q1>0.123</ogr:q1>
<ogr:q3>0.123</ogr:q3>
<ogr:iqr>0</ogr:iqr>
</ogr:stats_by_cat_two_fields>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_two_fields fid="stats_by_cat_two_fields.3">
<ogr:intval>120</ogr:intval>
<ogr:count>1</ogr:count>
<ogr:unique>1</ogr:unique>
<ogr:min>-100291.43213</ogr:min>
<ogr:max>-100291.43213</ogr:max>
<ogr:range>0</ogr:range>
<ogr:sum>-100291.43213</ogr:sum>
<ogr:mean>-100291.43213</ogr:mean>
<ogr:median>-100291.43213</ogr:median>
<ogr:stddev>0</ogr:stddev>
<ogr:minority>-100291.43213</ogr:minority>
<ogr:majority>-100291.43213</ogr:majority>
<ogr:q1>-100291.43213</ogr:q1>
<ogr:q3>-100291.43213</ogr:q3>
<ogr:iqr>0</ogr:iqr>
</ogr:stats_by_cat_two_fields>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_two_fields fid="stats_by_cat_two_fields.4">
<ogr:name>cc</ogr:name>
<ogr:count>1</ogr:count>
<ogr:unique>1</ogr:unique>
<ogr:min>0.123</ogr:min>
<ogr:max>0.123</ogr:max>
<ogr:range>0</ogr:range>
<ogr:sum>0.123</ogr:sum>
<ogr:mean>0.123</ogr:mean>
<ogr:median>0.123</ogr:median>
<ogr:stddev>0</ogr:stddev>
<ogr:minority>0.123</ogr:minority>
<ogr:majority>0.123</ogr:majority>
<ogr:q1>0.123</ogr:q1>
<ogr:q3>0.123</ogr:q3>
<ogr:iqr>0</ogr:iqr>
</ogr:stats_by_cat_two_fields>
</gml:featureMember>
<gml:featureMember>
<ogr:stats_by_cat_two_fields fid="stats_by_cat_two_fields.5">
<ogr:intval>2</ogr:intval>
<ogr:name>bb</ogr:name>
<ogr:count>1</ogr:count>
<ogr:unique>1</ogr:unique>
<ogr:min>0.123</ogr:min>
<ogr:max>0.123</ogr:max>
<ogr:range>0</ogr:range>
<ogr:sum>0.123</ogr:sum>
<ogr:mean>0.123</ogr:mean>
<ogr:median>0.123</ogr:median>
<ogr:stddev>0</ogr:stddev>
<ogr:minority>0.123</ogr:minority>
<ogr:majority>0.123</ogr:majority>
<ogr:q1>0.123</ogr:q1>
<ogr:q3>0.123</ogr:q3>
<ogr:iqr>0</ogr:iqr>
</ogr:stats_by_cat_two_fields>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -11,6 +11,16 @@
<ElementPath>id2</ElementPath> <ElementPath>id2</ElementPath>
<Type>Integer</Type> <Type>Integer</Type>
</PropertyDefn> </PropertyDefn>
<PropertyDefn>
<Name>count</Name>
<ElementPath>count</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>unique</Name>
<ElementPath>unique</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn> <PropertyDefn>
<Name>min</Name> <Name>min</Name>
<ElementPath>min</ElementPath> <ElementPath>min</ElementPath>
@ -21,24 +31,54 @@
<ElementPath>max</ElementPath> <ElementPath>max</ElementPath>
<Type>Integer</Type> <Type>Integer</Type>
</PropertyDefn> </PropertyDefn>
<PropertyDefn>
<Name>range</Name>
<ElementPath>range</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>sum</Name>
<ElementPath>sum</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn> <PropertyDefn>
<Name>mean</Name> <Name>mean</Name>
<ElementPath>mean</ElementPath> <ElementPath>mean</ElementPath>
<Type>Real</Type> <Type>Real</Type>
</PropertyDefn> </PropertyDefn>
<PropertyDefn>
<Name>median</Name>
<ElementPath>median</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn> <PropertyDefn>
<Name>stddev</Name> <Name>stddev</Name>
<ElementPath>stddev</ElementPath> <ElementPath>stddev</ElementPath>
<Type>Real</Type> <Type>Real</Type>
</PropertyDefn> </PropertyDefn>
<PropertyDefn> <PropertyDefn>
<Name>sum</Name> <Name>minority</Name>
<ElementPath>sum</ElementPath> <ElementPath>minority</ElementPath>
<Type>Integer</Type> <Type>Integer</Type>
</PropertyDefn> </PropertyDefn>
<PropertyDefn> <PropertyDefn>
<Name>count</Name> <Name>majority</Name>
<ElementPath>count</ElementPath> <ElementPath>majority</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>q1</Name>
<ElementPath>q1</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>q3</Name>
<ElementPath>q3</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>iqr</Name>
<ElementPath>iqr</ElementPath>
<Type>Integer</Type> <Type>Integer</Type>
</PropertyDefn> </PropertyDefn>
</GMLFeatureClass> </GMLFeatureClass>

View File

@ -9,34 +9,58 @@
<gml:featureMember> <gml:featureMember>
<ogr:stats_by_category fid="stats_by_category.0"> <ogr:stats_by_category fid="stats_by_category.0">
<ogr:id2>2</ogr:id2> <ogr:id2>2</ogr:id2>
<ogr:count>2</ogr:count>
<ogr:unique>2</ogr:unique>
<ogr:min>1</ogr:min> <ogr:min>1</ogr:min>
<ogr:max>4</ogr:max> <ogr:max>4</ogr:max>
<ogr:mean>2.5</ogr:mean> <ogr:range>3</ogr:range>
<ogr:stddev>2.12132034355964</ogr:stddev>
<ogr:sum>5</ogr:sum> <ogr:sum>5</ogr:sum>
<ogr:count>2</ogr:count> <ogr:mean>2.5</ogr:mean>
<ogr:median>2.5</ogr:median>
<ogr:stddev>1.5</ogr:stddev>
<ogr:minority>1</ogr:minority>
<ogr:majority>1</ogr:majority>
<ogr:q1>1</ogr:q1>
<ogr:q3>4</ogr:q3>
<ogr:iqr>3</ogr:iqr>
</ogr:stats_by_category> </ogr:stats_by_category>
</gml:featureMember> </gml:featureMember>
<gml:featureMember> <gml:featureMember>
<ogr:stats_by_category fid="stats_by_category.1"> <ogr:stats_by_category fid="stats_by_category.1">
<ogr:id2>1</ogr:id2> <ogr:id2>1</ogr:id2>
<ogr:count>2</ogr:count>
<ogr:unique>2</ogr:unique>
<ogr:min>2</ogr:min> <ogr:min>2</ogr:min>
<ogr:max>5</ogr:max> <ogr:max>5</ogr:max>
<ogr:mean>3.5</ogr:mean> <ogr:range>3</ogr:range>
<ogr:stddev>2.12132034355964</ogr:stddev>
<ogr:sum>7</ogr:sum> <ogr:sum>7</ogr:sum>
<ogr:count>2</ogr:count> <ogr:mean>3.5</ogr:mean>
<ogr:median>3.5</ogr:median>
<ogr:stddev>1.5</ogr:stddev>
<ogr:minority>2</ogr:minority>
<ogr:majority>2</ogr:majority>
<ogr:q1>2</ogr:q1>
<ogr:q3>5</ogr:q3>
<ogr:iqr>3</ogr:iqr>
</ogr:stats_by_category> </ogr:stats_by_category>
</gml:featureMember> </gml:featureMember>
<gml:featureMember> <gml:featureMember>
<ogr:stats_by_category fid="stats_by_category.2"> <ogr:stats_by_category fid="stats_by_category.2">
<ogr:id2>0</ogr:id2> <ogr:id2>0</ogr:id2>
<ogr:count>5</ogr:count>
<ogr:unique>5</ogr:unique>
<ogr:min>3</ogr:min> <ogr:min>3</ogr:min>
<ogr:max>9</ogr:max> <ogr:max>9</ogr:max>
<ogr:mean>6.6</ogr:mean> <ogr:range>6</ogr:range>
<ogr:stddev>2.30217288664427</ogr:stddev>
<ogr:sum>33</ogr:sum> <ogr:sum>33</ogr:sum>
<ogr:count>5</ogr:count> <ogr:mean>6.6</ogr:mean>
<ogr:median>7</ogr:median>
<ogr:stddev>2.0591260281974</ogr:stddev>
<ogr:minority>3</ogr:minority>
<ogr:majority>3</ogr:majority>
<ogr:q1>6</ogr:q1>
<ogr:q3>8</ogr:q3>
<ogr:iqr>2</ogr:iqr>
</ogr:stats_by_category> </ogr:stats_by_category>
</gml:featureMember> </gml:featureMember>
</ogr:FeatureCollection> </ogr:FeatureCollection>

View File

@ -3476,3 +3476,98 @@ tests:
name: expected/collect_two_fields.gml name: expected/collect_two_fields.gml
type: vector type: vector
- algorithm: qgis:statisticsbycategories
name: Stats by cat (float field)
params:
CATEGORIES_FIELD_NAME:
- name
INPUT:
name: dissolve_polys.gml
type: vector
VALUES_FIELD_NAME: floatval
results:
OUTPUT:
name: expected/stats_by_cat_float.gml
type: vector
pk: name
compare:
fields:
fid: skip
- algorithm: qgis:statisticsbycategories
name: Stats by cat (string field)
params:
CATEGORIES_FIELD_NAME:
- intval
INPUT:
name: dissolve_polys.gml
type: vector
VALUES_FIELD_NAME: name
results:
OUTPUT:
name: expected/stats_by_cat_string.gml
type: vector
pk: intval
compare:
fields:
fid: skip
- algorithm: qgis:statisticsbycategories
name: Stats by cat (two category fields)
params:
CATEGORIES_FIELD_NAME:
- intval
- name
INPUT:
name: dissolve_polys.gml
type: vector
VALUES_FIELD_NAME: floatval
results:
OUTPUT:
name: expected/stats_by_cat_two_fields.gml
type: vector
pk:
- intval
- name
compare:
fields:
fid: skip
- algorithm: qgis:statisticsbycategories
name: Stats by cat (no value field)
params:
CATEGORIES_FIELD_NAME:
- intval
- name
INPUT:
name: dissolve_polys.gml
type: vector
results:
OUTPUT:
name: expected/stats_by_cat_no_value.gml
type: vector
pk:
- intval
- name
compare:
fields:
fid: skip
- algorithm: qgis:statisticsbycategories
name: Stats by cat (date field)
params:
CATEGORIES_FIELD_NAME:
- date
INPUT:
name: custom/datetimes.tab
type: vector
VALUES_FIELD_NAME: date
results:
OUTPUT:
name: expected/stats_by_cat_date.gml
type: vector
pk: date
compare:
fields:
fid: skip

View File

@ -105,7 +105,11 @@ class TestCase(_TestCase):
def sort_by_pk_or_fid(f): def sort_by_pk_or_fid(f):
if 'pk' in kwargs and kwargs['pk'] is not None: if 'pk' in kwargs and kwargs['pk'] is not None:
return f[kwargs['pk']] key = kwargs['pk']
if isinstance(key, list) or isinstance(key, tuple):
return [f[k] for k in key]
else:
return f[kwargs['pk']]
else: else:
return f.id() return f.id()

View File

@ -2301,6 +2301,9 @@ bool QgsProcessingParameterField::checkValueIsAcceptable( const QVariant &input,
{ {
if ( !mAllowMultiple ) if ( !mAllowMultiple )
return false; return false;
if ( input.toList().isEmpty() && !( mFlags & FlagOptional ) )
return false;
} }
else if ( input.type() == QVariant::String ) else if ( input.type() == QVariant::String )
{ {

View File

@ -2258,6 +2258,8 @@ void TestQgsProcessing::parameterLayerList()
QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( true ) );
QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) );
QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) );
QVERIFY( !def->checkValueIsAcceptable( QStringList() ) );
QVERIFY( def->checkValueIsAcceptable( QStringList() << "layer12312312" << "layerB" ) ); QVERIFY( def->checkValueIsAcceptable( QStringList() << "layer12312312" << "layerB" ) );
QVERIFY( def->checkValueIsAcceptable( QVariantList() << "layer12312312" << "layerB" ) ); QVERIFY( def->checkValueIsAcceptable( QVariantList() << "layer12312312" << "layerB" ) );
QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) );
@ -3183,6 +3185,8 @@ void TestQgsProcessing::parameterField()
QVERIFY( def->checkValueIsAcceptable( QVariantList() << "a" << "b" ) ); QVERIFY( def->checkValueIsAcceptable( QVariantList() << "a" << "b" ) );
QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) );
QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
QVERIFY( !def->checkValueIsAcceptable( QStringList() ) );
QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) );
params.insert( "non_optional", QString( "a;b" ) ); params.insert( "non_optional", QString( "a;b" ) );
fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context ); fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context );