If you work with temporal or raster data, you may find yourself in a position where you have multiple columns of data for a single feature. I came across this issue recently for a map I am working on, and it took me some time to figure it out.
In ArcPro, we can use Arcade (very similar to JavaScript) to create a script. Variables come in handy here, but QGIS seems to use its own language that requires a function to declare a variable.
Read more: QGIS: How to Calculate the Max Value across Multiple ColumnsWorkflow Note
This workflow assumes that you have a basic understanding of QGIS 3.44.
Background
I have a forest type raster layer from the USDA. I ran Zonal Statistics Histogram to calculate the number of pixels (or cells) by forest type that fell into the National Park boundaries. This resulted in over 100 columns indicating the number of pixels per park for each tree species.

However, I wanted to know which forest type was the most common for each national park. To do this manually, I would need to sort each column in descending order and compare them one by one. Or I could use QGIS’s form view in the attribute table to view each feature’s attributes in a vertical layout. More convenient, but still tedious and error-prone.

Building the Code
Step 1 – max()
max()
In the Expression Builder, the max() function will find the highest value for our feature class.

max("field01", "field02", "field03", ...)
Adding multiple field names will return the highest value from the supplied fields at parameters. If we only have a handful of fields, this is not a bad option. As mentioned earlier, we have over 100 fields to compare. Adding them manually would be tedious and error-prone.

Step 2 – attributes()
attributes()
We need a way to automatically feed the max() function (or similar function) all the field names. We can collect all of the field names using the attributes() function. However, we don’t need all the attributes in the table and the attributes() function returns a map (imho, this is poorly named). A map, in this instance, is similar to a dictionary in Python, a key-value pair “list”.

Step 3 – map_avals()
map_avals(attributes())
To return only the values (instead of the keys), we use the map_avals() function with the attributes() function as the parameter, as shown above. This gives us an array of ALL the values for all of the attributes for each feature.

Step 4 – array_slice()
array_slice(map_avals(attributes()), 11, 148)
To return just the attributes we are interested in, we use the array_slice() function. The slice will return the values by index position, based on the number 0. For example, if you want the first 10 attribute columns, we would use an index range of 0-9.
The first parameter in the array_slice() function is the array. In our case, the array is the map_avals(attributes()) code. The next two parameters are the index starting position and ending position. The end position is included in the slice, whereas some languages exclude it.
array_slice(map_akeys(attributes()), 11, 148)

There is no way around this part, but do the math. A tip I found helpful is to change the map_avals() function to map_akeys() to see the list of attribute columns returned in the preview. If you right-click on the preview, we can copy the values and paste them into a text doc to ensure we have the right columns. In this scenario, we want the 11th and 148th indexes.
Step 5 – array_max()
array_max(array_slice(map_avals(attributes()), 11, 148))
Now that we have our list of attributes, we can take a shortcut and use the array_max() function to return the highest value from the array for each feature. From here, we can use this code to create a label or to populate the attribute table using the Field Calculator.

Step 6 – map_akeys()
map_akeys(attributes())[array_find(map_avals(attributes()), array_max(array_slice(map_avals(attributes()), 10, 148)))]
Unfortunately, we don’t know what attribute this value is associated with. This portion gets a little complicated, but essentially, we are using the array_find() function to return the index position of our max value. Not ideal for all scenarios, but in this case, I think it is fine. We use the index in the map_akeys() function to return the field name of the max value based on our earlier code.

This code can be used in the label expression builder or the field calculator to return the field name of our max value.
Step 7 – concat()
concat(map_akeys(attributes())[array_find(map_avals(attributes()), array_max(array_slice(map_avals(attributes()), 11, 148)))], ': ' , round(array_max(array_slice(map_avals(attributes()), 16, 148))*0.222395), ' acres')
To combine our two short codes into a single label, we can use the concat() function. Separate each code with a comma. Here are the parameters split into four chunks:
map_akeys(attributes())[array_find(map_avals(attributes()), array_max(array_slice(map_avals(attributes()), 11, 148)))]
': '
I added a round() function and converted the value to acres by multiplying 0.222395 by the pixel count. The cell size is 30 m2 (or 900 m) divided by 4047 to convert m2 to acres.
round(array_max(array_slice(map_avals(attributes()), 16, 148))*0.222395)
Add additional text at the end of the label for more context.
' acres'

Label Result

We still need to decode the label, but that step is much easier than sorting through multiple columns to find the max value.
Table Result
Alternatively, if you can populate the table using the codes we created and use these fields to label the features.

References
https://docs.qgis.org/3.44/en/docs/user_manual/expressions/functions_list.html
Support
If you found any of these workflows helpful, consider buying me a coffee using the QR Code or link below. Thanks!

