# Function to calculate percentage of each feature over the sum that feature's field using PyQGIS

I would like to calculate the percentage of each feature (i.e. each row in "HISTO_1") over the sum of that feature's field ("HISTO_1") as a new column (i.e. "Percent_HISTO_1") in an attribute table by creating a function using PyQGIS but I can't seems to do so.

I can do it easily using the expression in the field calculator as follow:

Result field:

Here is the function that I tried to create following this guide: https://docs.qgis.org/3.28/en/docs/user_manual/expressions/expression.html#function-editor

``````from qgis.core import *
from qgis.gui import *

@qgsfunction(args='auto', group='Custom')
def percent_feature(field1, feature, parent):

totalSum = aggregate(QgsAggregateCalculator.Sum, field1)

percent = (field1/totalSum)*int(100)

return percent
``````

I can't get it to work with the above function. Could someone please help me with this :)

• Do you want to calculate all fields percentage as new columns?
– BERA
Apr 17, 2023 at 11:04
• @BERA Yup this is what I wanted ;) Apr 17, 2023 at 11:08

A couple of problems. Firstly, the `aggregate` method you are using is method of the `QgsVectorLayer` class, so you need to call it on a `QgsVectorLayer` object. Then, the second argument needs to be a field name string, however the `field1` object is the value in the field you pass to your custom function, evaluated for each feature.

Try the following:

``````from qgis.core import *
from qgis.gui import *

@qgsfunction(args='auto', group='Custom', referenced_columns=[])
def feature_percent(field1, field_name, feature, parent, context):
layer_id = context.variable('layer_id')
layer = QgsProject.instance().mapLayer(layer_id)
totalSum = layer.aggregate(QgsAggregateCalculator.Sum, field_name)[0]
percent = (field1/totalSum)*100
return int(percent)
``````

If you want decimal places, change the last line e.g.

``````return round(percent, 2)
``````

The first argument of this custom function is the field (double quoted) which will be evaluated for each feature. The second argument is the name of the field as a string (single quoted) which will be passed to the aggregate method.

Example usage:

Result:

Honestly, I don't really see a huge advantage of a custom function over the field calculator expression. Perhaps you would prefer a small script which you could run in the Python console which will add and fill all your percentage fields in one go.

Try:

``````layer = QgsProject.instance().mapLayersByName('canopyChangeDistrict_2018-2022')[0]

field_names = ['HISTO_0',
'HISTO_1',
'HISTO_2',
'HISTO_3',
'HISTO_4',
'HISTO_5']

atts_map = {}

for fname in field_names:
new_field = QgsField(f'Percent_{fname}', QVariant.Double, len=5, prec=2)
layer.updateFields()

for ft in layer.getFeatures():
atts = {}
for fname in field_names:
fld_idx = layer.fields().lookupField(f'Percent_{fname}')
total_sum = layer.aggregate(QgsAggregateCalculator.Sum, fname)[0]
pcnt = (ft[fname]/total_sum)*100
atts[fld_idx] = pcnt
atts_map[ft.id()] = atts

#print(atts_map)
layer.dataProvider().changeAttributeValues(atts_map)
``````

Test result:

• Hi @Ben W, Thank you for your answer, I have tried to create the function from your code but I am running into an error: "Eval Error: int() can't convert non-string with explicit base" Do you know how I can fix this? Apr 17, 2023 at 13:03
• @Toboggan, the `int()` method converts a float or a string to an integer. That error message suggests that it is being passed a value that is neither. I am not sure why if your HISTO_0 field is numeric and contains all integers. Type the error into a search engine for more info. I tested the custom function with zeroes/NULLs etc. it worked fine for me. Try just returning `percent` or a rounded percent. It is late here I am going to bed. I can have another look tomorrow if you are still having trouble. Apr 17, 2023 at 13:36
• Ahaa I tried to return the percent object and it works ! Thanks so much for your help man ! Apr 17, 2023 at 13:44
• @Toboggan, I added a Python script which will add and fill all your percent fields at once. Apr 18, 2023 at 3:32
• Awesome thanks so much for all your help Ben ! I really appreciate it Apr 18, 2023 at 13:12