7

In QGIS 2.18 there is a shapefile with three fields accordingly

id Type Value
1 a 5
2 b NULL
3 c 1
4 d 6
5 e NULL

I want to get the total sum of all values in the field "Value". How can I achieve that in the Python Console?

The field "Value" maintains two types of data int and NULL.

I was trying with a while-loop but it does not work

layer = qgis.utils.iface.activeLayer()

if not layer.isValid(): raise Exception('Layer is not valid')

features = layer.getFeatures()

for feat in features: attr = feat.attributes()[2]

if attr == NULL:
    continue

else:
    total = 0
    index = 0
    while index < layer.featureCount():
        total = total + int(attr)
        index = index + 1

return(total)

How to achieve one number that gives the total sum of a whole field exclusive NULL-values?

After executing @ahmadhanb's solution I received a correct number but somehow with a strange output type, when print (type(sum(total))) it gives me <type 'NoneType'>. What could be a problem?


References:

Germán Carrillo
  • 36,307
  • 5
  • 123
  • 178
Taras
  • 32,823
  • 4
  • 66
  • 137
  • Try to place the print(total) out of the loop. This will give you one value which is the total value. – ahmadhanb Feb 28 '19 at 13:10
  • @Taras you can replace print(0) with continue to skip null values. – ahmadhanb Feb 28 '19 at 13:17
  • 1
    Taras - What layer and field type is it and which QGIS version are you using? Would it be possible to share a sample of this data online so that others could test it? The one-liner from @Bera works for me in QGIS 2.18 and 3.4. – Joseph Feb 28 '19 at 15:37
  • @Taras - Your sample dataset is missing a .shx file so it cannot be loaded. – Joseph Mar 01 '19 at 10:28
  • 1
    @Taras - Your sample data works fine if I use int(sum(filter(None,[f['Value'] for f in layer.getFeatures()]))) which returns the value 441. – Joseph Mar 01 '19 at 15:39

3 Answers3

7

In programming, there are several ways of solving a problem.

That's also the case for summing values of a field in QGIS. But there is one that is the recommended way, truly leveraging the PyQGIS API.

Aggregating field values using PyQGIS

To get the sum of a field you can use:

total_sum = layer.aggregate(QgsAggregateCalculator.Sum, "my_field_name")

Simpler and faster.

Note: Since the result is a tuple (result_value, boolean_result), you just call total_sum[0] and you are done.


Read the docs to know the list of aggregate types you can use.

You can even pass filter expressions to the aggregates. Really powerful.


Note: this is available since QGIS 2.16.

Germán Carrillo
  • 36,307
  • 5
  • 123
  • 178
  • Thank you for such an explicit answer. I just checked and seems like the sum inside the tuple is a float type, is not it? – Taras May 22 '21 at 08:23
  • That's right @Taras. – Germán Carrillo May 22 '21 at 13:29
  • @Taras, is that somehow relevant to you? – Germán Carrillo May 25 '21 at 14:10
  • thank you, yes it is! – Taras May 25 '21 at 14:11
  • @Taras, which answer would you like new users to use? :) – Germán Carrillo Aug 09 '21 at 16:25
  • The one with most reputation – Taras Aug 09 '21 at 16:31
  • Disagree, since simpler is better, but I'll accept your choice. – Germán Carrillo Aug 09 '21 at 17:02
  • I also disagree and at the same time agree with you. You are absolutely right that if there is an efficient/simple solution why not to use it. But as you can see I was(am) not that much advanced in PyQGIS, therefore we are talking about different sides of the "simplicity". Your approach is by any means better as it requires just one(max two) string(s) of code, mine is more naїve to understand where step by step you comprehend what exactly going on i.e. it is a mixture of first-steps in Python and basic things in PyQGIS. Also, at that time I simply did not know about .aggregate() method. – Taras Aug 10 '21 at 06:38
  • It's Ok @Taras. I see GIS.SE as a source of documentation and I think it should guide readers directly to the recommended response. But of course, you've got a point there. – Germán Carrillo Aug 10 '21 at 15:16
5

In fact your code provides a double number to the actual value. For example, if the total number is 50, your code provides 100. I think the correct code should be something like this:

layer = qgis.utils.iface.activeLayer()

if not layer.isValid():
    raise Exception('Layer is not valid')

features = layer.getFeatures()

total = []
for feat in features:
    attr = feat.attributes()[2]

    if attr == NULL:
        continue

    else:
        total.append((int(attr)))
print(sum(total))

Add a total empty list outside the loop, and if the attr == NULL, it will skip the null values, and if there is a value in the attr it will be appended to the list. Then you can print the sum of the list outside the loop to get one value only.

Here is the screenshot of the output:

enter image description here

ahmadhanb
  • 40,826
  • 5
  • 51
  • 105
  • @Taras But I did not write print(type(sum(total))), I wrote print(sum(total)) which will provide an integer value. The output print(type(sum(total))) is <class 'int'> not <type 'NoneType'> – ahmadhanb Feb 28 '19 at 13:46
  • That is strange. Why do you get <type 'NoneType'>? I am getting integer output type. – ahmadhanb Feb 28 '19 at 14:06
  • Shouldnt if attr == NULL be if attr is None? – BERA Feb 28 '19 at 14:11
  • @BERA Using if attr is None gives an error for integer field whereas if attr == NULL works well. – ahmadhanb Feb 28 '19 at 14:22
5

In QGIS 3, QgsVectorLayerUtils.getValues() returns all values from a specified field name or expression.

You can use to sum values in one field in that way:

layer = iface.activeLayer()
v = QgsVectorLayerUtils.getValues(layer, 'field_name')[0]
v = list(filter(None, v))
s = sum(v)

NOTE: NULL is None is False (both have different objects in the memory). NULL == None is True (both have the same value). Therefore, we can use None here.

Taras
  • 32,823
  • 4
  • 66
  • 137
Kadir Şahbaz
  • 76,800
  • 56
  • 247
  • 389