Skip To Content ArcGIS for Developers Sign In Dashboard

ArcGIS API for Python

Visualizing Spatial Data

The Spatially Enabled Dataframe has a plot() method that uses a syntax and symbology similar to matplotlib for visualizing features on a map. With this functionality, you can easily visualize aspects of your data both on a map and on a matplotlib chart using the same symbology!

Some unique characteristics of working with the visualization capabalities on the SDF:

  • Uses Pythonic syntax
  • Uses the same syntax as visualizing charts on Pandas DataFrames
  • Uses symbology familiar to users of matplotlib
  • Works on features and attributes simultaneously, eliminating to a great extent the need to iterate over all features (rows)
  • Handles reading and writing to multiple formats aiding data conversion

Checkout the Introduction to Spatially Enabled DataFrame guide to learn how to create a Spatially Enabled DataFrame.

Quickstart

Let us read a census data on major cities and load that into a Spatially Enabled DataFrame

In [1]:
from arcgis import GIS
gis = GIS()
# create an anonymous connection to ArcGIS Online and get a public item
item = gis.content.get("85d0ca4ea1ca4b9abf0c51b9bd34de2e")
flayer = item.layers[0]

# Specify a SQL query and get a sub-set of the original data as a DataFrame
df = flayer.query(where="AGE_45_54 < 1500").sdf

# Visualize the top 5 records
df.head()
Out[1]:
FID NAME CLASS ST STFIPS PLACEFIPS CAPITAL POP_CLASS POPULATION POP2010 ... MARHH_NO_C MHH_CHILD FHH_CHILD FAMILIES AVE_FAM_SZ HSE_UNITS VACANT OWNER_OCC RENTER_OCC SHAPE
0 1 Ammon city ID 16 1601990 6 15181 13816 ... 1131 106 335 3352 3.61 4747 271 3205 1271 {"x": -12462673.723706165, "y": 5384674.994080...
1 2 Blackfoot city ID 16 1607840 6 11946 11899 ... 1081 174 381 2958 3.31 4547 318 2788 1441 {"x": -12506251.313993266, "y": 5341537.793529...
2 4 Burley city ID 16 1611260 6 10727 10345 ... 861 139 358 2499 3.37 3885 241 2183 1461 {"x": -12667411.402393516, "y": 5241722.820606...
3 6 Chubbuck city ID 16 1614680 6 14655 13922 ... 1281 172 370 3586 3.40 4961 229 3324 1408 {"x": -12520053.904151963, "y": 5300220.333409...
4 12 Jerome city ID 16 1641320 6 11403 10890 ... 779 210 385 2640 3.44 3985 292 2219 1474 {"x": -12747828.64784961, "y": 5269214.8197742...

5 rows × 50 columns

Note: If the above cell looks new to you, please checkout the Introduction to Spatially Enabled DataFrame guide.

Let us create a map of the United States

In [7]:
m1 = GIS().map("United States")
m1
Out[7]:
In [3]:
m1.zoom = 4
m1.center = [39,-98]

Plotting the DataFrame

You can quickly visualize the points by calling the plot() method off the DataFrame's spatial accessor and passing the map you created above.

In [4]:
df.spatial.plot(map_widget= m1)
Out[4]:
True

You can customize the symbols, their color, shape, border etc. like you would in a typical matplotlib plot. The rest of this guide talks about such customizations and suggestions to visualize your spatial and non-spatial data.

The code below plots the same set of points on a new map using a common structure used amongst many different Python packages for defining symbology. It is built off of the matplotlib libraries for simple, straightforward plotting. We'll explain some of the parameters below, and the plot() API Reference outlines more options.

In [11]:
m2= GIS().map('United States', zoomlevel=4)
m2
Out[11]:
In [9]:
m2.center=[39,-98]
df.spatial.plot(map_widget=m2,
                renderer_type='s',
                symbol_type='simple',
                symbol_style='d', # d - for diamonds
                colors='Reds_r',
                cstep=50,
                outline_color='Blues',
                marker_size=10)
Out[9]:
True

If this type of plotting is new to you, this is a good time to checkout matplotlib.pyplot documentation and Pandas dataframe plot documentation to get an idea.

Understanding renderers

In the snippets above, you visualized all the features with the same symbology. While this helps understand the geographic distribution of your data, your Spatially Enabled DataFrame object can do much more. For instance, you can group the features based on certain attributes that you choose and symbolize each group by a certain color or size or both. To help with this, let us understand the concept of renderers.

Renderers define how to visually represent a feature layer by defining symbols to represent individual features. The SeDF provides you with functionality to control the way features appear by choosing the symbol the renderer uses.

Previous versions of the ArcGIS API for Python provided a method to specify a renderer manually, but you had to know details about the renderer before you drew your data. The map.add_layer() method did not provide access to all options avialble for rendering datasets. The new visualization capabilities provided by the SEDF allow you to draw spatial data quickly and easily with access to more rendering options.

Supported renderers

The renderering options below are documented in further detail in the DatFrame Reference. At a high level, you have the following renderers:

Renderer Syntax Explanation
Simple 's' renders using one symbol only (the examples in quickstart above)
Unique 'u' renders each unique value with a different symbol. Suitable for categorical columns
Unique 'u-a' renders each unique value with a different symbol using arcade expressions. Suitable for categorical columns
Class breaks 'c' renders each group of values with a different color or size. Suitable for numerical columns
Heatmap 'h' renders density of point data as raster of varying colors

Visualizing unique values

Let us explore the major cities census data further and explore if there are any categorical columns

In [12]:
df.columns
Out[12]:
Index(['FID', 'NAME', 'CLASS', 'ST', 'STFIPS', 'PLACEFIPS', 'CAPITAL',
       'POP_CLASS', 'POPULATION', 'POP2010', 'WHITE', 'BLACK', 'AMERI_ES',
       'ASIAN', 'HAWN_PI', 'HISPANIC', 'OTHER', 'MULT_RACE', 'MALES',
       'FEMALES', 'AGE_UNDER5', 'AGE_5_9', 'AGE_10_14', 'AGE_15_19',
       'AGE_20_24', 'AGE_25_34', 'AGE_35_44', 'AGE_45_54', 'AGE_55_64',
       'AGE_65_74', 'AGE_75_84', 'AGE_85_UP', 'MED_AGE', 'MED_AGE_M',
       'MED_AGE_F', 'HOUSEHOLDS', 'AVE_HH_SZ', 'HSEHLD_1_M', 'HSEHLD_1_F',
       'MARHH_CHD', 'MARHH_NO_C', 'MHH_CHILD', 'FHH_CHILD', 'FAMILIES',
       'AVE_FAM_SZ', 'HSE_UNITS', 'VACANT', 'OWNER_OCC', 'RENTER_OCC',
       'SHAPE'],
      dtype='object')

The ST column contains state names in abbreviation is a good candidate. We could symbolize the points such that all which fall under the same state get the same symbol:

In [13]:
df[['ST', 'NAME']].head()
Out[13]:
ST NAME
0 ID Ammon
1 ID Blackfoot
2 ID Burley
3 ID Chubbuck
4 ID Jerome
In [16]:
gis = GIS()
m3 = gis.map("United States", zoomlevel=4)
m3
Out[16]:
In [15]:
df.spatial.plot(map_widget = m3,
                renderer_type='u', # specify the unique value renderer using its notation 'u'
                col='ST'  # column to get unique values from
               )
Out[15]:
True

By default, the API picks a unique color from the jet colormap for each category (US State names in this case) and assigns it. You can override this by choosing your own colormap (prism for instance) and customizing the symbol style, symbol type, outline color, thickness, transparency etc. Keep reading to learn more about these customizations.

It is a good cartographic practice to not have too many unique values in your legend. Human eyes can only preceive so many colors and there are only a limited number of colors to choose from in a given color map before the colors become indistinguishable.

As a safe practice, the Python API caps the maximum number of unique values to 256, the remaining are generalized and plotted with a default color. If you have several unique values, you should consider picking a different column that better represents your data or consider using a different renderer.

Visualizing Unique Values with Arcade Expressions

Arcade is an expression language that can be used across the ArcGIS Platform. Whether writing simple scripts to control how features are rendered, or expressions to control label text, Arcade provides a simple scripting syntax to deliver these capabilities.

In the sense of visualization, Arcade expressions are used to create rich and dynamic symbology. This example will follow the JavaScript guide.

Obtain the Data

Access the enterprise to gain access to the FeatureLayer information and query it into a Spatially Enabled DataFrame.

In [17]:
item = gis.content.get("8444e275037549c1acab02d2626daaee")
flayer = item.layers[0]
df2 = flayer.query().sdf
In [18]:
fset = flayer.query()
In [19]:
from arcgis.geometry import Geometry
g = Geometry(fset.features[0].geometry)
g.as_arcpy

Write out the Arcade Expressions and Stops

Arcade expressions require the stops to be manually provided. In this case, we first create an opacity visual variable based on a percent of dominant parties in registered citizens.

In [20]:
opacity_expression = ("var republican = $feature.MP06025a_B;var democrat = $feature.MP06024a_B;"
                      "var independent = $feature.MP06026a_B;var parties = [republican, democrat, independent];"
                      "var total = Sum(parties);var max = Max(parties);return (max / total) * 100;")
opacity_stops = [
    { "value": 33, "transparency": 0.05 * 255, "label": "< 33%" },
    { "value": 44, "transparency": 1.0 * 255, "label": "> 44%" }]

Next we develop another Arcade expression to obtain the majority party in a given county.

In [21]:
arcade_expression = ("var republican = $feature.MP06025a_B;var democrat = $feature.MP06024a_B;"
                     "var independent = $feature.MP06026a_B;var parties = [republican, democrat, independent];"
                     "return Decode( Max(parties),republican, 'republican', democrat, 'democrat',independent, "
                     "'independent','n/a' );")
uv = [{"label":"Democrat","symbol":{"type":"esriSFS","color":[0,195,255,255],
                                    "outline":{"type":"esriSLS","color":[0,0,0,51],
                                               "width":0.5,"style":"esriSLSSolid"},
                                    "style":"esriSFSSolid"},"value":"democrat"},
      {"label":"Republican","symbol":{"type":"esriSFS","color":[255,0,46,255],"outline":{
          "type":"esriSLS","color":[0,0,0,51],"width":0.5,"style":"esriSLSSolid"},
                                      "style":"esriSFSSolid"},"value":"republican"},
      {"label":"Independent/other party","symbol":{"type":"esriSFS","color":[250,255,0,255],
                                                   "outline":{"type":"esriSLS","color":[0,0,0,51],
                                                              "width":0.5,"style":"esriSLSSolid"},
                                                   "style":"esriSFSSolid"},"value":"independent"}]
In [24]:
gis = GIS()
m3_ua = gis.map('United States', zoomlevel=4)
m3_ua
Out[24]: