Gis, Qgis, ArcGis  Experts Just a Click Away

ArcPy Zonal Statistics in Python: A Practical ArcGIS Pro Guide

ArcPy’s ZonalStatisticsAsTable tool summarizes raster values inside defined zones and writes the results to a table. You can use an integer zone raster, a polygon feature class, or another feature layer to calculate values such as the mean, minimum, maximum, sum, standard deviation, and count.

This tutorial shows how to calculate zonal statistics in ArcGIS Pro with Python. It includes a minimal example, polygon zones, batch processing, result handling, NoData behavior, cell alignment, and common errors.

Important: ZonalStatisticsAsTable is a Spatial Analyst tool. Your ArcGIS installation must have access to the Spatial Analyst or Image Analyst extension. 1

Quick example: calculate the mean for each raster zone

The following example calculates the mean value of precipitation.tif for each zone in the integer raster landuse_zones.tif.

import arcpy
from arcpy.sa import ZonalStatisticsAsTable

arcpy.env.workspace = r"C:\GIS_Data\Project"
arcpy.env.overwriteOutput = True

zone_raster = r"C:\GIS_Data\Project\landuse_zones.tif"
value_raster = r"C:\GIS_Data\Project\precipitation.tif"
output_table = r"C:\GIS_Data\Project\precipitation_by_landuse.dbf"

arcpy.CheckOutExtension("Spatial" )

try:
    ZonalStatisticsAsTable(
        in_zone_data=zone_raster,
        zone_field="VALUE",
        in_value_raster=value_raster,
        out_table=output_table,
        ignore_nodata="DATA",
        statistics_type="MEAN"
    )
    print(f"Created: {output_table}")
finally:
    arcpy.CheckInExtension("Spatial")

For an integer zone raster, VALUE identifies the zones. The output table contains one record for each zone that has usable value-raster cells.

What are zonal statistics?

Zonal statistics summarize the cells of a value raster within areas defined by a zone dataset. A zone is a group of cells or features that share the same zone identifier. Zones do not need to be contiguous.

Examples include:

  • average elevation within watersheds;
  • total rainfall within administrative districts;
  • minimum and maximum temperature by land-use class;
  • mean NDVI within agricultural fields; and
  • standard deviation of pollution values around monitoring areas.

ZonalStatisticsAsTable creates a table. If you need a raster output instead, use the related ZonalStatistics tool.

Prerequisites

Before running the script, confirm that you have:

  • ArcGIS Pro with access to Spatial Analyst or Image Analyst;
  • ArcPy available in the Python environment used by ArcGIS Pro;
  • a zone raster or feature class;
  • a value raster containing the measurements to summarize; and
  • write access to the output geodatabase or folder.

An integer zone raster is required when the zone input is raster data. A feature zone can use an integer or text field, provided the field identifies the zones appropriately. 1

Use polygon features as zones

When the zones are polygons, pass the feature class and a field that identifies each zone. Do not use VALUE unless the polygon layer really contains a field with that name.

import arcpy
from arcpy.sa import ZonalStatisticsAsTable

zone_polygons = r"C:\GIS_Data\Project\watersheds.gdb\watersheds"
value_raster = r"C:\GIS_Data\Project\avg_temperature.tif"
output_table = r"C:\GIS_Data\Project\watersheds.gdb\temperature_stats"
zone_field = "WATERSHED_ID"

arcpy.CheckOutExtension("Spatial")

try:
    ZonalStatisticsAsTable(
        in_zone_data=zone_polygons,
        zone_field=zone_field,
        in_value_raster=value_raster,
        out_table=output_table,
        ignore_nodata="DATA",
        statistics_type="ALL"
    )
finally:
    arcpy.CheckInExtension("Spatial")

ArcGIS Pro converts feature zones to a raster internally. The value raster supplies the cell size, cell alignment, and spatial reference for that conversion unless you set analysis environments. A small polygon may be missing from the result when no cell center falls inside it. Use an appropriate cellSize environment when the zone boundaries are smaller than the value-raster cells. 1 2

Choose the statistic to calculate

The statistics_type argument controls the fields generated in the output table. Common choices include:

  • MEAN for the average;
  • MINIMUM and MAXIMUM for the range endpoints;
  • SUM for the total of the cell values;
  • STD for standard deviation; and
  • ALL for all supported statistics.

The supported statistics depend on whether the value raster is integer or floating point. For example, majority and variety statistics apply to integer data, while a floating-point raster supports statistics such as mean, minimum, maximum, median, percentile, range, standard deviation, and sum. 1

Use a valid statistic name rather than treating AREA as a statistic. The tool does not create an AREA statistic field. If you need polygon area, calculate it from the feature geometry in a projected coordinate system. If you need an approximate number of raster cells, use the COUNT output field and document the cell-size assumption.

Ignore or preserve NoData cells

ignore_nodata="DATA" is the usual choice when NoData cells should be excluded from the calculation. If you use ignore_nodata="NODATA", any NoData cell within a zone causes the result for that zone to be NoData.

Choose the setting based on the meaning of the missing data:

  • Use DATA when missing cells should not contribute to the statistic.
  • Use NODATA when a missing cell means that the entire zone has insufficient information for a valid result.

This choice can materially change the output, so state it in reproducible analysis documentation. 1

Process several rasters in a batch

The following function calculates one statistic for multiple value rasters. It uses os.path.splitext instead of removing only .tif, so it also works with other raster extensions.

import os
import arcpy
from arcpy.sa import ZonalStatisticsAsTable


def batch_zonal_statistics(zone_data, zone_field, value_rasters, output_gdb,
                           statistic="MEAN"):
    """Calculate one zonal statistic for several value rasters."""
    results = {}

    for value_raster in value_rasters:
        raster_name = os.path.splitext(os.path.basename(value_raster))[0]
        safe_name = arcpy.ValidateTableName(f"zonal_{raster_name}", output_gdb)
        output_table = os.path.join(output_gdb, safe_name)

        ZonalStatisticsAsTable(
            in_zone_data=zone_data,
            zone_field=zone_field,
            in_value_raster=value_raster,
            out_table=output_table,
            ignore_nodata="DATA",
            statistics_type=statistic
        )
        results[value_raster] = output_table

    return results


zone_data = r"C:\GIS_Data\Project\climate_regions.gdb\regions"
value_rasters = [
    r"C:\GIS_Data\Project\temperature.tif",
    r"C:\GIS_Data\Project\precipitation.tif",
    r"C:\GIS_Data\Project\humidity.tif",
]
output_gdb = r"C:\GIS_Data\Project\results.gdb"

arcpy.CheckOutExtension("Spatial")
try:
    outputs = batch_zonal_statistics(
        zone_data=zone_data,
        zone_field="REGION_ID",
        value_rasters=value_rasters,
        output_gdb=output_gdb,
        statistic="MEAN"
    )
    for source, output in outputs.items():
        print(f"{source} -> {output}")
finally:
    arcpy.CheckInExtension("Spatial")

A file geodatabase is usually preferable to a folder of dBASE tables because it handles field names and output management more reliably.

Read the output table with a cursor

The exact fields depend on the statistic selected. For a MEAN calculation, read the zone field, COUNT, and MEAN. Do not assume an AREA field exists.

import arcpy

results_table = r"C:\GIS_Data\Project\results.gdb\temperature_stats"
fields = ["WATERSHED_ID", "COUNT", "MEAN"]

with arcpy.da.SearchCursor(results_table, fields) as cursor:
    for watershed_id, count, mean_value in cursor:
        print(
            f"{watershed_id}: {mean_value} "
            f"({count} valid raster cells)"
        )

If you need to join the statistics to the original polygons, join using the same zone identifier field used in the tool:

arcpy.management.JoinField(
    in_data=zone_polygons,
    in_field="WATERSHED_ID",
    join_table=results_table,
    join_field="WATERSHED_ID",
    fields=["COUNT", "MEAN"]
)

Make sure the join fields have matching values and compatible data types. For a safer workflow, write the joined result to a copy of the original feature class rather than modifying the source data in place.

Set analysis environments deliberately

Misaligned rasters can produce unexpected results because the tool may resample data internally. When reproducibility matters, set the analysis environments explicitly:

arcpy.env.cellSize = value_raster
arcpy.env.snapRaster = value_raster
arcpy.env.outputCoordinateSystem = arcpy.Describe(value_raster).spatialReference

If polygon zones are small compared with the value-raster cells, consider setting a suitable smaller cellSize for the zone conversion. A smaller cell size can capture more feature zones, but it does not create new detail in the value raster and may increase processing time. 1 2

Use a projected coordinate system when measuring distance or area. Do not interpret a geographic-coordinate-system area as square metres or square feet without an appropriate projection.

Common errors and fixes

The output table is missing some zones

Check whether the missing features overlap the value raster and whether their size is large enough to contain a cell center after feature-to-raster conversion. Also check the processing extent and cell size.

The script says that the Spatial Analyst license is unavailable

Check the extension status before running the tool and check the extension back in with arcpy.CheckInExtension("Spatial") in a finally block. The account or ArcGIS Pro license must have the required extension available.

Results are unexpectedly NoData

Inspect NoData cells in the value raster and confirm the ignore_nodata setting. With NODATA, one NoData cell can invalidate the result for its zone.

Statistics look different after reprojection

Check the cell size, snap raster, spatial reference, and resampling behavior. Resampling can change the cell values used in the calculation, especially when the two inputs are not aligned.

A field cannot be found

Confirm that the zone field exists and use the exact field name. VALUE is appropriate for many integer raster zones, but it is not a universal field name for polygon data.

Frequently asked questions

Can ArcPy calculate zonal statistics for polygons?

Yes. Pass a polygon feature class as the zone data and specify an integer or text field that identifies the zones. ArcGIS Pro converts the features to a raster internally before calculating the statistics.

What is the difference between ZonalStatistics and ZonalStatisticsAsTable?

ZonalStatistics creates a raster output. ZonalStatisticsAsTable creates one table record per zone and is usually the better choice when you need to join results to polygons or export them for reporting.

How do I calculate area with ArcPy zonal statistics?

AREA is not a ZonalStatisticsAsTable statistic option. Calculate feature area with geometry in an appropriate projected coordinate system, or use raster cell counts with a documented cell-area assumption.

Why should I set a snap raster?

The snap raster controls cell alignment. Setting it to the value raster helps make the analysis grid explicit and reduces surprises when the zone and value datasets use different grids.

Can I use ALL with a floating-point raster?

Yes, but the fields produced are limited to statistics supported by the value-raster data type. Integer and floating-point rasters do not support exactly the same statistics. 1

Summary

The core ArcPy workflow is simple: define the zones, define the value raster, choose a valid zone field and statistic, and write the output table. The results become more reliable when you also document NoData handling, verify that the inputs overlap, and control cell size, alignment, and projection.

For most workflows, start with MEAN or another specific statistic rather than ALL, then expand the output only when the additional fields are useful.

References

Understand the main parameters

The function signature is easier to use when each parameter has a clear role:

ZonalStatisticsAsTable(
    in_zone_data,
    zone_field,
    in_value_raster,
    out_table,
    ignore_nodata,
    statistics_type
 )

in_zone_data identifies the areas to summarize. It can be an integer raster or a feature layer. zone_field identifies individual zones. For an integer raster, this is commonly VALUE; for polygons, it is usually a field such as ZONE_ID, COUNTY_ID, or WATERSHED_ID.

in_value_raster contains the measurements. For example, it could contain elevation, rainfall, land-surface temperature, or NDVI values. out_table is the destination for the results. If you write to a file geodatabase, provide a table name inside that geodatabase. If you write to a folder, ArcGIS may create a dBASE table.

The ignore_nodata option accepts DATA or NODATA. The statistics_type option accepts a supported statistic such as MEAN, SUM, MINIMUM, MAXIMUM, STD, or ALL. Using keyword arguments makes scripts easier to review and reduces mistakes caused by putting positional arguments in the wrong order.

A complete polygon workflow

This example shows a practical workflow for summarizing elevation by watershed, checking the result, and saving the output in a geodatabase.

import os
import arcpy
from arcpy.sa import ZonalStatisticsAsTable

project_gdb = r"C:\GIS_Data\WatershedProject\analysis.gdb"
watersheds = os.path.join(project_gdb, "watersheds")
elevation = r"C:\GIS_Data\WatershedProject\elevation.tif"
stats_table = os.path.join(project_gdb, "watershed_elevation_stats")

arcpy.env.overwriteOutput = True
arcpy.env.snapRaster = elevation
arcpy.env.cellSize = elevation

if not arcpy.Exists(watersheds):
    raise FileNotFoundError(f"Zone features not found: {watersheds}")
if not arcpy.Exists(elevation):
    raise FileNotFoundError(f"Value raster not found: {elevation}")

arcpy.CheckOutExtension("Spatial")
try:
    ZonalStatisticsAsTable(
        in_zone_data=watersheds,
        zone_field="WATERSHED_ID",
        in_value_raster=elevation,
        out_table=stats_table,
        ignore_nodata="DATA",
        statistics_type="MIN_MAX_MEAN"
    )

    row_count = int(arcpy.management.GetCount(stats_table)[0])
    print(f"Created {stats_table} with {row_count} rows")
finally:
    arcpy.CheckInExtension("Spatial")

The output should normally contain one row per represented watershed. Compare the output row count with the number of input watersheds. If the counts differ, investigate overlap, cell size, processing extent, and zones that are too small to contain a value-raster cell center.

Export results for reporting

A geodatabase table can be converted to a CSV file for use in pandas, Excel, or another reporting system. Use TableToTable or TableToTable_conversion according to the ArcGIS Pro version and your project’s coding standards.

import os
import arcpy

input_table = r"C:\GIS_Data\WatershedProject\analysis.gdb\watershed_elevation_stats"
csv_folder = r"C:\GIS_Data\WatershedProject\exports"
os.makedirs(csv_folder, exist_ok=True)

arcpy.conversion.TableToTable(
    in_rows=input_table,
    out_path=csv_folder,
    out_name="watershed_elevation_stats.csv"
)

For automated reports, read the table with a cursor and write only the fields that the report needs. This avoids exposing implementation fields and makes the resulting CSV easier to maintain.

import csv
import arcpy

input_table = r"C:\GIS_Data\WatershedProject\analysis.gdb\watershed_elevation_stats"
output_csv = r"C:\GIS_Data\WatershedProject\exports\watershed_summary.csv"
fields = ["WATERSHED_ID", "COUNT", "MIN", "MAX", "MEAN"]

with open(output_csv, "w", newline="", encoding="utf-8") as file:
    writer = csv.writer(file)
    writer.writerow(fields)
    with arcpy.da.SearchCursor(input_table, fields) as rows:
        writer.writerows(rows)

Field names can vary with the workspace and statistic selection. Always inspect arcpy.ListFields(input_table) before hard-coding field names in a reusable production script.

Calculate several statistics efficiently

If you need multiple statistics for the same zone and value raster, calculate them in one tool call rather than running the tool once for each statistic. For example, MIN_MAX_MEAN creates the minimum, maximum, and mean fields in a single operation.

ZonalStatisticsAsTable(
    in_zone_data=watersheds,
    zone_field="WATERSHED_ID",
    in_value_raster=elevation,
    out_table=stats_table,
    ignore_nodata="DATA",
    statistics_type="MIN_MAX_MEAN"
)

This approach can reduce repeated raster reads. On the other hand, do not use ALL automatically when you need only one or two measures. A smaller result table is easier to interpret and may reduce unnecessary processing.

When to use circular statistics

Arithmetic statistics are not suitable for every raster. Aspect is a common example because 0 degrees and 360 degrees represent the same direction. A normal arithmetic mean of 1 degree and 359 degrees is 180 degrees, which is not the meaningful direction.

For directional data such as aspect, wind direction, or time-of-day values, use circular statistics when supported by the ArcGIS Pro version and input data type. Set the circular calculation option and provide an appropriate wrap value, such as 360 for degrees. Review the ArcGIS Pro tool documentation for the exact Python parameter names available in your installed release. 1

Do not apply circular statistics to ordinary elevation, rainfall, population, or temperature rasters. Those variables are not cyclic and should use arithmetic statistics.

Performance tips for large datasets

Zonal statistics can be expensive when the value raster is large, the zone layer contains many features, or the script processes many rasters. The following practices help keep runs manageable:

  1. Limit the processing extent to the study area when appropriate.
  2. Use a value raster with a resolution that matches the question being asked.
  3. Store intermediate and final tables in a file geodatabase.
  4. Use a specific statistic instead of ALL when possible.
  5. Process a list of rasters in a controlled loop and log failures by filename.
  6. Avoid repeatedly converting the same features when a prepared zone raster can be reused.
  7. Set a parallel-processing factor only after testing memory use and output consistency.
  8. Build raster statistics and pyramids when later display or analysis requires them.

A smaller cell size does not automatically create more accurate results. It can help preserve small polygon zones during rasterization, but the value raster still limits the information available. For reproducibility, record the cell size, snap raster, projection, extent, and NoData rule alongside the output.

ArcGIS Pro and ArcMap compatibility

The overall ArcPy pattern is similar in ArcGIS Pro and ArcMap, but the Python environment, available parameters, and documentation can differ by release. ArcGIS Pro uses Python 3, while older ArcMap workflows commonly use Python 2.7. Code written for one environment may therefore require changes before it runs in the other.

Use the documentation for the exact ArcGIS release installed on the machine running the script. In particular, verify newer statistics, circular-statistics parameters, raster-analysis alternatives, and licensing behavior before deploying a script across multiple workstations.

A good compatibility practice is to record the ArcGIS Pro version, Python version, Spatial Analyst availability, and input data types in the project README. This makes future reruns and troubleshooting much easier.

Quality-control checks before trusting results

A successful tool execution does not guarantee that the result answers the intended question. Add a short quality-control checklist to the workflow:

  • Confirm that the zone field is unique when each feature should represent one zone.
  • Compare the input and output zone counts.
  • Check that the zone and value datasets overlap.
  • Review the value raster’s units and NoData definition.
  • Confirm the coordinate system and cell size are appropriate.
  • Inspect a sample of zones manually on the map.
  • Compare one or two zones with an independent calculation when the result is important.
  • Record the tool parameters and ArcGIS version.

For example, a mean elevation value is meaningful only if the elevation raster’s vertical units are understood and the zones contain enough valid cells. A sum of rainfall values may not represent total rainfall volume unless the calculation accounts for cell area and the variable’s units.

How to write a reproducible project note

A short project note should state the zone dataset, zone field, value raster, statistic, NoData policy, cell size, snap raster, coordinate system, output table, and software version. This information lets another analyst reproduce the result without reverse-engineering the script.

For example:

Mean elevation was calculated for each watershed using ZonalStatisticsAsTable in ArcGIS Pro. The watershed polygons were identified by WATERSHED_ID. NoData cells were ignored, the elevation raster supplied the cell size and snap raster, and the output was written to watershed_elevation_stats in the project geodatabase.

This level of documentation is more valuable than simply stating that zonal statistics were performed.

Leave a Reply

Gabby Jones

Typically replies within a minute

Hello, Welcome to the site. Please click below button for chating me throught WhatsApp.