I'm using QGIS 3.2.
I have 4 fields ("kk1"-"kk4") where at least one of the fields contains a number (real).
I want to calculate the mean of these 1-4 fields and save the result (the average) in another field called "mkviskrans".
I calculate the average (based on this answer) of these 1-4 fields using this function in the Function Editor window in the Layer Properties:
@qgsfunction(args=-1, group='Custom')
def mean_col(cols, feature, parent):
vals = [feature[i] for i in cols if feature[i] is not None]
return sum(vals)/float(len(vals))
and calling the function from the Expression window in the widget's Default value option in Layer Properties:
mean_col('kk1','kk2','kk3','kk4')
UPDATE: I tried return 2.3
instead of return sum(vals)/float(len(vals))
. In this case the attribute table is updated automatically with the value 2.3. This indicates that the values typed in by the user in "kk1"-"kk4" actually have to be updated/evaluated/"commited" before returning the mean value, i.e.the division-expression will always return "None"? I have tried to update the feature/field values of "kk1"-"kk4" before return, but without any luck so far.
However, the mean value does not automatically appear in the "mkviskrans" "Text Edit" widget (alias "Midlere kvistkransavstand (dm)"):
Nor in the attribute table:
When I click in a cell in the attribute table, then the mean value appears:
Is it possible to get/show/save the value "on the fly"? I have tried different variants of update and/or commit within the function:
@qgsfunction(args=-1, group='Custom')
def mean_col(cols, feature, parent):
layer = qgis.utils.iface.activeLayer()
fieldIndex = layer.fields().indexFromName( 'mkviskrans' )
vals = [feature[i] for i in cols if feature[i] is not None]
"""return sum(vals)/float(len(vals))"""
mv = sum(vals)/float(len(vals))
layer.changeAttributeValue(feature.id(), fieldIndex, mv)
layer.commitChanges()
If I save the edits without clicking in a cell in the attribute table, the average (the field "mkvistkrans") will not contain any mean values.
(I may of course calculate the mean values after the field work, but I want the user to see the mean value "on the fly" – if possible.)
Best Answer
NULL and None
Fun fact:
NULL is not None
With this in mind it's easy to fix the code
Do not write to layers in expressions
Additional advice: never write to layers in expressions. Expressions are often executed in threads and in different contexts and using
layer.changeAttributeValue()
and similar functions may result in undefined behavior (read: crash).Using values instead of attribute names
Another interesting experiment for educational purpose.
Instead of providing column names to the function, you can also provide values.
And then call it with
The interesting result is: it will return NULL as soon as at least one column is NULL. How comes? QGIS assumes the result for an expression function is NULL as soon as at least one parameter is NULL for performance reasons. It's possible to control this behavior, but unfortunately the
@qgsfunction
decorator does not support this to date (QGIS 3.2).To workaround this behavior we can either provide a full-blown
QgsExpressionFunction
implementation which is out of scope for this answer. Or just take a little detour and use an array instead.and then call it with