6

I have a polygon shapefile and I need to generate n new fields reporting the n largest values across k variables. In this particular case n = 3, so that I would like to create three new fields: "max_area_1", "max_area_2", "max_area_3", with the highest, the second and third largest, values across variables x1, x2, ..., xk.

To make things clearer, consider this simpliefied version of my data:

x1  x2  x3  x4
10  31  73  14
 7  69  55   2
91   9  87  13

What I would like to get is:

x1  x2  x3  x4  max_area_1  max_area_2  max_area_3
10  31  73  14      73          31          14
 7  69  55   2      69          55           7
91   9  87  13      91          87          13

Using the field calculator in Python console of QGIS, I can compute the highest value using the max function. My code is:

crops = [x1, ..., xk]
processing.run("qgis:fieldcalculator", {
    "INPUT": input_layer,
    "FIELD_NAME": "max_area_1",
    "FIELD_TYPE": 0,
    "NEW_FIELD": True,
    "FORMULA": 'max(crops)',
    "OUTPUT":  output_layer})

Any idea on how I can compute, in PyQGIS, three new fields reporting the three largest values across the relevant variables?

9

You can use this script. First, select a the layer, then run the script. It adds the result as a memory layer. You should save the result as a new file manually or using a code.

input_layer = iface.activeLayer()
max_area_x = ["max_area_1", "max_area_2", "max_area_3"]

for i in range(len(max_area_x)): # OR for i in [0, 1, 2]
    input_layer = processing.run("qgis:fieldcalculator",
        {"INPUT": input_layer,
        "FIELD_NAME": max_area_x[i],
        "FIELD_TYPE": 0,
        "NEW_FIELD": True,
        "FORMULA": 'array_sort(array("x1", "x2", "x3", "x4"), False)[' + str(i) + ']',
        "OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]

QgsProject.instance().addMapLayer(input_layer)

It works in QGIS 3.8. I have not tested in other versions.

2
  • I need to compute maximum values across fields, not within them. I've edited the question to make it clearer – Matteo Mar 20 '20 at 10:14
  • @Matteo I have changed the answer. Please review. – Kadir Şahbaz Mar 20 '20 at 11:46
6

You can also use the following script without any Processing tool. The script doesn't create any layer, it adds fields to the active layer and assigns values.

from PyQt5.QtCore import QVariant

lyr = iface.activeLayer()

# crops fields
cfs = ["x1","x2","x3","x4"]

# new fields list
nf = [QgsField("max_area_1", QVariant.Int),
      QgsField("max_area_2", QVariant.Int),
      QgsField("max_area_3", QVariant.Int)]

# add fields to layer
lyr.dataProvider().addAttributes(nf)
lyr.updateFields()

# new field indices
idx = [lyr.fields().indexOf(f.name()) for f in nf]

attr_map = {} # {id: {index: value}, id: {index: value}, ...}
for f in lyr.getFeatures():
    vals = sorted([f[c] for c in cfs], reverse=True)
    attr_map[f.id()] = {idx[0]: vals[0],
                        idx[1]: vals[1],
                        idx[2]: vals[2]}
        
lyr.dataProvider().changeAttributeValues(attr_map)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.