Read my previous posts Summary Aggregate and Spatial Filters and Advanced Aggregate Expressions to Automate QA to learn more about the powerful aggregate function.

The aggregate function in QGIS was designed to work with 2 separate input vector layers, but we can also make it work with a single layer. Essentially performing spatial queries for features within the layer.

Consider this problem. In a layer containing zip codes, we want to find all neighboring zip codes for every polygon. This can be done very easily by creating a new field with an expression with the aggregate function – using the layer itself as the parent layer.

Below is the Zip Codes shapefile from the City of Seattle Open Data Portal. Our task is to add another field which contains all neighboring zip codes for each feature. Note the layer name is Zip_Codes

Open the Attribute Table for the layer. Note the field name containing the 5-digit zip code is ZIPCODE . Open Field Calculator.

Create a new field with the following expression. The key here is the spatial filter touches($geometry, geometry(@parent)) that finds all polygons from the layer that touch the feature being processed.

 layer:= 'Zip_Codes',
 concatenator:=', ',
 filter:=touches($geometry, geometry(@parent))

Note: If you are not getting accurate results, try the following

  • Change ‘touches’ to ‘intersects’.
  • Instead of aggregating using the same layer, duplicate the layer by right-clicking the layer and selecting ‘Duplicate’. And when running the aggregate function, use the duplicate layer name instead of the original layer.

You will now have a new field that has the neighbors zip codes for every feature in the layer!

There are many type of aggregate that you can use. If you want to count numbering zip codes, you can use the count aggregate instead of concatenate. The following expression would calculate the number of neighboring polygons.

 layer:= 'Zip_Codes',
 filter:=touches($geometry, geometry(@parent))

15 thoughts on “Find Neighbor Polygons using Summary Aggregate Function in QGIS

  1. Hi Ujaval, thanks for sharing this post.

    I have tried your example with the world borders shapefile ( and QGIS returns some strange results (e.g. “Cuba” touches “USA”?!! and other countries that share a common border but they don’t appear in the results).

    Any suggestions to overcome this problem? By the way, I am using QGIS 3.4.7. The code I used can be seen below:

    layer:= ‘TM_WORLD_BORDERS-0.3′,
    concatenator:=’, ‘,
    filter:=touches($geometry, geometry(@parent))

    ps1. When I use the count function, it returns zero for all the countries.
    ps2. When I use the Zip Codes shapefile, the same issue appears (e.g. in the zip code 98406, where should be 6 neighbors, but only 3 appear).


      1. Thanks for the answer. I will have to review some topics on how to create valid geometries (or correct invalid ones) to have this function working properly. And using the Natural Earth Data, the function (almost) works right (e.g. Venezuela appears without neighbors).


  2. Thanks for the super helpful article!

    For anyone else who might be using a shapefile that has polygons which theoretically *should* touch but which do not actually share points (which is what the `touches` function expects), you can use this trick: first, select all your features, then select Vector > Geoprocessing Tools > Buffer and add a small buffer (e.g. 1m, but adjust as you need). This will cause the polygons to expand in size by 1m, and, hence, overlap. You can then use the aggregation code in the article, but replace the `touches` function with `intersects`, since adjacent features now intersect each other: `filter:=intersects($geometry, geometry(@parent))`.

    NB: this will cause polygons that are “kitty corner” to each other (e.g. Utah and New Mexico, to use the example of US states) to show up as adjacent. For my purposes, this is not a problem; it might be for you.


    1. Thanks Frank. FWIW, you can also do this operation inline without creating a new layer, filter:=intersects($geometry, buffer(geometry(@parent), 1))`.


  3. Hi Ujaval,
    thanks for this, because it is the closest I was able to find as a solution to my problem, nevertheless it comes up with empty cells.
    So, I have a grid of my study area, and I want to look into the autocorrelation between the neighbouring quadrants. In my case the field code is the id of the quadrants and I’m was expecting a string of respective neighbouring ids, yet this – seemingly ideal – solution of yours results NULL.
    Is there something I miss?
    Or is this not a solution for my problem?


      1. Cheers. It works now!!!
        Though not flawless: in the middle of the grid it sometimes produces 8, sometimes less neighbouring cells. Tried increasing buffer size, did not solved anythin.
        Any suggestion?
        Once again thank you for your help.


    1. I found a solution. Instead of using the aggregate function on the same layer, the process gives correct results if you duplicate the layer and run the computation.
      1. Rename your layer to ‘original’.
      2. Right-click and select ‘Duplicate’. Rename it to ‘copy’.
      3. Add a attribute to the ‘copy’ layer with the following expression. I made a change to remove the id of the polygon from the list of neighbors.

      layer:= 'original',
      filter:=intersects($geometry, geometry(@parent))
      ), ','), "id2" ), ',')

      See the result at


  4. Hello Chinmay! This process is very slow in computing the number neighbouring polygons. I have around 41000 features and it looks like it will take forever to do that!


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.