avatarAmit Kulkarni

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

10978

Abstract

plt.subplots(<span class="hljs-number">2</span>,<span class="hljs-number">2</span>, figsize=(<span class="hljs-number">12</span>,<span class="hljs-number">6</span>), sharex=True, sharey=True) <span class="hljs-keyword">base</span> = states.plot(ax=axes[<span class="hljs-number">0</span>,<span class="hljs-number">0</span>]) axes[<span class="hljs-number">0</span>,<span class="hljs-number">0</span>].set_title(<span class="hljs-string">"Standard plot"</span>)

<span class="hljs-keyword">base</span> = states.boundary.plot(ax=axes[<span class="hljs-number">0</span>,<span class="hljs-number">1</span>]) axes[<span class="hljs-number">0</span>,<span class="hljs-number">1</span>].set_title(<span class="hljs-string">"Boundary plot"</span>)

<span class="hljs-keyword">base</span> = states.plot(cmap = <span class="hljs-string">"magma"</span>,ax=axes[<span class="hljs-number">1</span>,<span class="hljs-number">0</span>]) axes[<span class="hljs-number">1</span>,<span class="hljs-number">0</span>].set_title(<span class="hljs-string">"magma Theme"</span>)

<span class="hljs-keyword">base</span> = states.plot(cmap = <span class="hljs-string">"Pastel1"</span>,ax=axes[<span class="hljs-number">1</span>,<span class="hljs-number">1</span>]) axes[<span class="hljs-number">1</span>,<span class="hljs-number">1</span>].set_title(<span class="hljs-string">"Paste1 theme"</span>)</pre></div><figure id="0ba4"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*cFk4-7fUQcQNWu623KwbsA.png"><figcaption>Source: Author</figcaption></figure><p id="3467">We can also focus on a specific state for example California.</p><div id="d660"><pre>states[states[<span class="hljs-string">'NAME'</span>] == <span class="hljs-string">'California'</span>].plot()</pre></div><figure id="bdd5"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*HAFTtp7j78s4AEUI0AXCKw.png"><figcaption>Source: Author</figcaption></figure><p id="a050">The maps are certainly impressive, and adding the labels of the states would provide valuable context. Moreover, is there functionality to emphasize a particular group of states using a distinct color? Our intention is to spotlight the states in the mid-west region.</p><div id="4654"><pre>midwest = states[states[<span class="hljs-string">'region'</span>] == <span class="hljs-string">'Midwest'</span>]

fig = plt.figure(<span class="hljs-number">1</span>, figsize=(<span class="hljs-number">12</span>,<span class="hljs-number">12</span>)) ax = fig.add_subplot() states.apply(<span class="hljs-keyword">lambda</span> x: ax.annotate(text = <span class="hljs-built_in">str</span>(x.STUSPS), xy=x.geometry.centroid.coords[<span class="hljs-number">0</span>], ha=<span class="hljs-string">'center'</span>, fontsize=<span class="hljs-number">14</span>),axis=<span class="hljs-number">1</span>) states.boundary.plot(ax=ax, color=<span class="hljs-string">'Black'</span>, linewidth=<span class="hljs-number">.4</span>) states.plot(ax=ax, color=<span class="hljs-string">'ivory'</span>, figsize=(<span class="hljs-number">12</span>, <span class="hljs-number">12</span>))

midwest.plot(ax=ax) ax.text(-<span class="hljs-number">0.05</span>, <span class="hljs-number">0.5</span>, transform=ax.transAxes, fontsize=<span class="hljs-number">20</span>, color=<span class="hljs-string">'gray'</span>, alpha=<span class="hljs-number">0.2</span>, ha=<span class="hljs-string">'center'</span>, va=<span class="hljs-string">'center'</span>, rotation=<span class="hljs-string">'90'</span>)</pre></div><figure id="9c2d"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*ASJV7m5DAUrxrzoV6N3E9w.png"><figcaption>Source: Author</figcaption></figure><p id="be82">In the same way, we can segment different regions like below eg: west, southwest, southeast, midwest, and northeast.</p><div id="2231"><pre>west = states[states[<span class="hljs-string">'region'</span>] == <span class="hljs-string">'West'</span>] southwest = states[states[<span class="hljs-string">'region'</span>] == <span class="hljs-string">'Southwest'</span>] southeast = states[states[<span class="hljs-string">'region'</span>] == <span class="hljs-string">'Southeast'</span>] midwest = states[states[<span class="hljs-string">'region'</span>] == <span class="hljs-string">'Midwest'</span>] northeast = states[states[<span class="hljs-string">'region'</span>] == <span class="hljs-string">'Northeast'</span>]

fig = plt.figure(<span class="hljs-number">1</span>, figsize=(<span class="hljs-number">12</span>,<span class="hljs-number">12</span>)) ax = fig.add_subplot() states.apply(<span class="hljs-keyword">lambda</span> x: ax.annotate(text = <span class="hljs-built_in">str</span>(x.STUSPS), xy=x.geometry.centroid.coords[<span class="hljs-number">0</span>], ha=<span class="hljs-string">'center'</span>, fontsize=<span class="hljs-number">14</span>),axis=<span class="hljs-number">1</span>) states.boundary.plot(ax=ax, color=<span class="hljs-string">'Black'</span>, linewidth=<span class="hljs-number">.4</span>) <span class="hljs-comment"># states.plot(ax=ax, color='ivory', figsize=(12, 12))</span> southwest.plot(ax=ax) west.plot(ax=ax,color=<span class="hljs-string">"MistyRose"</span>) southwest.plot(ax=ax, color=<span class="hljs-string">"PaleGoldenRod"</span>) southeast.plot(ax=ax, color=<span class="hljs-string">"Plum"</span>) midwest.plot(ax=ax, color=<span class="hljs-string">"PaleTurquoise"</span>) northeast.plot(ax=ax, color=<span class="hljs-string">"ivory"</span>) ax.text(-<span class="hljs-number">0.05</span>, <span class="hljs-number">0.5</span>, transform=ax.transAxes, fontsize=<span class="hljs-number">20</span>, color=<span class="hljs-string">'gray'</span>, alpha=<span class="hljs-number">0.2</span>, ha=<span class="hljs-string">'center'</span>, va=<span class="hljs-string">'center'</span>, rotation=<span class="hljs-string">'90'</span>)</pre></div><figure id="547d"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*EiUDTI8TBmUmXanZXCUvkw.png"><figcaption>Source: Author</figcaption></figure><p id="c7a4">Up to this point, our exploration has encompassed the creation of visual representations and spatial segmentation, coupled with the integration of data into these visualizations. Traditionally, global or regional cartographic depictions serve as foundational frameworks, upon which analytical insights are projected to convey narratives effectively. In the preliminary blog post titled “<a href="https://amitvkulkarni.medium.com/introduction-to-geospatial-analysis-with-python-de92d4849beb"><b><i>Initiation into Geospatial Analysis Using Python</i></b></a>,” our focus centered on the incorporation of seismic activity data onto a global map. In the next section, we will pivot to tornado-related data, seamlessly superimposing our analytical observations onto the foundational map developed within this phase.</p><p id="76a1">We will use the tornado data from the US and the skills we learned from the previous section to visualize the data. Let’s load the tornado data and explore it. There are 63K rows and 23 columns.</p><div id="4eeb"><pre>torn = geopandas.read_file(<span class="hljs-string">"1950-2018-torn-initpoint.shp"</span>)

torn.shape OUTPUT: (<span class="hljs-number">63645</span>, <span class="hljs-number">23</span>)

torn.columns OUTPUT: Index([<span class="hljs-string">'om'</span>, <span class="hljs-string">'yr'</span>, <span class="hljs-string">'mo'</span>, <span class="hljs-string">'dy'</span>, <span class="hljs-string">'date'</span>, <span class="hljs-string">'time'</span>, <span class="hljs-string">'tz'</span>, <span class="hljs-string">'st'</span>, <span class="hljs-string">'stf'</span>, <span class="hljs-string">'stn'</span>, <span class="hljs-string">'mag'</span>,<span class="hljs-string">'inj'</span>, <span class="hljs-string">'fat'</span>, <span class="hljs-string">'loss'</span>, <span class="hljs-string">'closs'</span>, <span class="hljs-string">'slat'</span>, <span class="hljs-string">'slon'</span>, <span class="hljs-string">'elat'</span>, <span class="hljs-string">'elon'</span>, <span class="hljs-string">'len'</span>,<span class="hljs-string">'wid'</span>, <span class="hljs-string">'fc'</span>, <span class="hljs-string">'geometry'</span>], dtype=<span class="hljs-string">'object'</span>)

torn.head()

OUTPUT: om yr mo dy <span class="hljs-built_in">date</span> <span class="hljs-built_in">time</span> tz st stf stn ... loss closs slat slon elat elon <span class="hljs-built_in">len</span> wid fc geometry <span class="hljs-number">0</span> <span class="hljs-number">1</span> <span class="hljs-number">1950</span> <span class="hljs-number">1</span> <span class="hljs-number">3</span> <span class="hljs-number">1950</span><span class="hljs-number">-01</span><span class="hljs-number">-03</span> <span class="hljs-number">11</span>:<span class="hljs-number">00</span>:<span class="hljs-number">00</span> <span class="hljs-number">3</span> MO <span class="hljs-number">29</span> <span class="hljs-number">1</span> ... <span class="hljs-number">6.0</span> <span class="hljs-number">0.0</span> <span class="hljs-number">38.77</span> <span class="hljs-number">-90.22</span> <span class="hljs-number">38.8300</span> <span class="hljs-number">-90.0300</span> <span class="hljs-number">9.5</span> <span class="hljs-number">150</span> <span class="hljs-number">0</span> POINT (<span class="hljs-number">-90.22000</span> <span class="hljs-number">38.77000</span>) <span class="hljs-number">1</span> <span class="hljs-number">2</span> <span class="hljs-number">1950</span> <span class="hljs-number">1</span> <span class="hljs-number">3</span> <span class="hljs-number">1950</span><span class="hljs-number">-01</span><span class="hljs-number">-03</span> <span class="hljs-number">11</span>:<span class="hljs-number">55</span>:<span class="hljs-number">00</span> <span class="hljs-number">3</span> IL <span class="hljs-number">17</span> <span class="hljs-number">2</span> ... <span class="hljs-number">5.0</span> <span class="hljs-number">0.0</span> <span class="hljs-number">39.10</span> <span class="hljs-number">-89.30</span> <span class="hljs-number">39.1200</span> <span class="hljs-number">-89.2300</span> <span class="hljs-number">3.6</span> <span class="hljs-number">130</span> <span class="hljs-number">0</span> POINT (<span class="hljs-number">-89.30000</span> <span class="hljs-number">39.10000</span>) <span class="hljs-number">2</span> <span class="hljs-number">3</span> <span class="hljs-number">1950</span> <span class="hljs-number">1</span> <span class="hljs-number">3</span> <span class="hljs-number">1950</span><span class="hljs-number">-01</span><span class="hljs-number">-03</span> <span class="hljs-number">16</span>:<span class="hljs-number">00</span>:<span class="hljs-number">00</span> <span class="hljs-number">3</span> OH <span class="hljs-number">39<

Options

/span> <span class="hljs-number">1</span> ... <span class="hljs-number">4.0</span> <span class="hljs-number">0.0</span> <span class="hljs-number">40.88</span> <span class="hljs-number">-84.58</span> <span class="hljs-number">40.8801</span> <span class="hljs-number">-84.5799</span> <span class="hljs-number">0.1</span> <span class="hljs-number">10</span> <span class="hljs-number">0</span> POINT (<span class="hljs-number">-84.58000</span> <span class="hljs-number">40.88000</span>) <span class="hljs-number">3</span> <span class="hljs-number">4</span> <span class="hljs-number">1950</span> <span class="hljs-number">1</span> <span class="hljs-number">13</span> <span class="hljs-number">1950</span><span class="hljs-number">-01</span><span class="hljs-number">-13</span> <span class="hljs-number">05</span>:<span class="hljs-number">25</span>:<span class="hljs-number">00</span> <span class="hljs-number">3</span> AR <span class="hljs-number">5</span> <span class="hljs-number">1</span> ... <span class="hljs-number">3.0</span> <span class="hljs-number">0.0</span> <span class="hljs-number">34.40</span> <span class="hljs-number">-94.37</span> <span class="hljs-number">34.4001</span> <span class="hljs-number">-94.3699</span> <span class="hljs-number">0.6</span> <span class="hljs-number">17</span> <span class="hljs-number">0</span> POINT (<span class="hljs-number">-94.37000</span> <span class="hljs-number">34.40000</span>) <span class="hljs-number">4</span> <span class="hljs-number">5</span> <span class="hljs-number">1950</span> <span class="hljs-number">1</span> <span class="hljs-number">25</span> <span class="hljs-number">1950</span><span class="hljs-number">-01</span><span class="hljs-number">-25</span> <span class="hljs-number">19</span>:<span class="hljs-number">30</span>:<span class="hljs-number">00</span> <span class="hljs-number">3</span> MO <span class="hljs-number">29</span> <span class="hljs-number">2</span> ... <span class="hljs-number">5.0</span> <span class="hljs-number">0.0</span> <span class="hljs-number">37.60</span> <span class="hljs-number">-90.68</span> <span class="hljs-number">37.6300</span> <span class="hljs-number">-90.6500</span> <span class="hljs-number">2.3</span> <span class="hljs-number">300</span> <span class="hljs-number">0</span> POINT (<span class="hljs-number">-90.68000</span> <span class="hljs-number">37.60000</span>) <span class="hljs-number">5</span> rows × <span class="hljs-number">23</span> columns</pre></div><p id="a4c5">Let’s look at the CRS of this data and we observe that the CRS is the same as that of the USA map we had in the earlier sections.</p><div id="1be0"><pre><span class="hljs-string">torn.crs</span>

<span class="hljs-attr">OUTPUT:</span> <span class="hljs-string"><Geographic</span> <span class="hljs-attr">2D CRS:</span> <span class="hljs-string">EPSG:4326></span> <span class="hljs-attr">Name:</span> <span class="hljs-string">WGS</span> <span class="hljs-number">84</span> <span class="hljs-string">Axis</span> <span class="hljs-string">Info</span> [<span class="hljs-string">ellipsoidal</span>]<span class="hljs-string">:</span> <span class="hljs-bullet">-</span> <span class="hljs-string">Lat[north]:</span> <span class="hljs-string">Geodetic</span> <span class="hljs-string">latitude</span> <span class="hljs-string">(degree)</span> <span class="hljs-bullet">-</span> <span class="hljs-string">Lon[east]:</span> <span class="hljs-string">Geodetic</span> <span class="hljs-string">longitude</span> <span class="hljs-string">(degree)</span> <span class="hljs-attr">Area of Use:</span> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">World.</span> <span class="hljs-bullet">-</span> <span class="hljs-attr">bounds:</span> <span class="hljs-string">(-180.0,</span> <span class="hljs-number">-90.0</span><span class="hljs-string">,</span> <span class="hljs-number">180.0</span><span class="hljs-string">,</span> <span class="hljs-number">90.0</span><span class="hljs-string">)</span> <span class="hljs-attr">Datum:</span> <span class="hljs-string">World</span> <span class="hljs-string">Geodetic</span> <span class="hljs-string">System</span> <span class="hljs-number">1984 </span><span class="hljs-string">ensemble</span> <span class="hljs-bullet">-</span> <span class="hljs-attr">Ellipsoid:</span> <span class="hljs-string">WGS</span> <span class="hljs-number">84</span> <span class="hljs-bullet">-</span> <span class="hljs-attr">Prime Meridian:</span> <span class="hljs-string">Greenwich</span></pre></div><p id="12a7">Let's explore the tornado by plotting it on the map.</p><div id="0b5f"><pre>torn.plot( figsize=(12,9), color=<span class="hljs-string">'red'</span>, marker=<span class="hljs-string">'x'</span>, markersize=1)</pre></div><figure id="8b20"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*a6ka3xH-nVtc8pS2pzWeZg.png"><figcaption>Source: Author</figcaption></figure><p id="56de">From the visuals, it looks like there are 3 more additional states in tornado data that are missing in the base map. Let’s check</p><div id="1722"><pre><span class="hljs-built_in">len</span>(list(states.STUSPS.unique())) OUTPUT: <span class="hljs-number">49</span>

<span class="hljs-built_in">len</span>(<span class="hljs-built_in">list</span>(torn.st.<span class="hljs-built_in">unique</span>())) OUTPUT: <span class="hljs-number">52</span></pre></div><p id="1341">For the sake of simplicity, let’s remove these regions from the analysis and plot the filtered data on the map.</p><div id="3173"><pre>torn_states_filtered = torn[torn['st'].isin(list(states.STUSPS.unique()))]

<span class="hljs-comment"># Verify</span> len(list(torn_states_filtered.st.unique())) <span class="hljs-section">OUTPUT: </span> 49</pre></div><div id="d726"><pre>fig = plt.figure(<span class="hljs-number">1</span>, figsize=(<span class="hljs-number">25</span>,<span class="hljs-number">15</span>)) ax = fig.add_subplot() states.apply(<span class="hljs-keyword">lambda</span> x: ax.annotate(text = <span class="hljs-built_in">str</span>(x.STUSPS), xy=x.geometry.centroid.coords[<span class="hljs-number">0</span>], ha=<span class="hljs-string">'center'</span>, fontsize=<span class="hljs-number">14</span>),axis=<span class="hljs-number">1</span>) states.boundary.plot(ax=ax, color=<span class="hljs-string">'black'</span>, linewidth=<span class="hljs-number">.4</span>) states.plot(ax=ax, color = <span class="hljs-string">'ivory'</span>, figsize=(<span class="hljs-number">12</span>, <span class="hljs-number">12</span>)) torn_states_filtered.plot(ax=ax, color=<span class="hljs-string">'red'</span>,alpha = <span class="hljs-number">0.5</span>, marker=<span class="hljs-string">'.'</span>, markersize=<span class="hljs-number">1</span>) ax.text(-<span class="hljs-number">0.05</span>, <span class="hljs-number">0.5</span>, transform=ax.transAxes, fontsize=<span class="hljs-number">20</span>, color=<span class="hljs-string">'white'</span>, alpha=<span class="hljs-number">0.2</span>, ha=<span class="hljs-string">'center'</span>, va=<span class="hljs-string">'center'</span>, rotation=<span class="hljs-string">'90'</span>)</pre></div><figure id="3681"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*ZGdPmTlgSU9cRXSOCx-7ew.png"><figcaption>Source: Author</figcaption></figure><p id="d84a">We can see that there are fewer tornadoes in the western region compared to the eastern part. Tornadoes are more frequent in the southeast and midwest areas. To confirm this observation, let’s verify it using the available data.</p><div id="3fb5"><pre>torn[<span class="hljs-string">'cnt'</span>] = <span class="hljs-number">1</span> torn_sub = torn<span class="hljs-string">[['st','cnt']]</span>.groupby(<span class="hljs-string">'st'</span>).count() torn_sub = torn_sub.sort_values(<span class="hljs-string">'cnt'</span>, ascending=True).tail(<span class="hljs-number">20</span>) torn_sub.plot.barh()</pre></div><figure id="10af"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*-HwsDbkibfzkddzUHrh7fw.png"><figcaption>Source: Author</figcaption></figure><p id="cb64">Well, Texas has the highest number of tornadoes followed by KS and OK. We can carry on with further analysis by region, specific states, etc but the objective here is to set the baseline approach and analysis. It is also up to the analysts to use their imagination and data for creative visualization.</p><h1 id="c950">Conclusion</h1><p id="c247">The geospatial data visualization using GeoPandas in Python opens up a world of possibilities for unlocking insights and communicating information in a visually captivating manner. Throughout this guide, we’ve explored the core concepts of geospatial data, from understanding Coordinate Reference Systems (CRS) to mastering the capabilities of the GeoPandas library. Data visualization isn’t just about creating visually appealing maps it’s about telling stories and conveying meaningful insights hence it is recommended that one experiment with different data sources, explore various map styles, and experiment with interactive elements like pop-ups and tooltips for compelling storytelling.</p><p id="8954">I hope you liked the article and found it helpful.</p><p id="c400">You can connect with me — on <a href="http://www.linkedin.com/in/amitvkulkarni2"><b><i>Linkedin</i></b></a> and <a href="https://github.com/amitvkulkarni/Blogs/tree/main/GIS"><b><i>Github</i></b></a></p><h2 id="df9a">My other Geospatial Analysis blogs</h2><p id="5310"><a href="https://readmedium.com/introduction-to-geospatial-analysis-with-python-de92d4849beb"><i>Introduction To Geospatial Analysis With Python</i></a><i> <a href="https://readmedium.com/18b9e90e161f"></a></i><a href="https://readmedium.com/18b9e90e161f">Interactive Geospatial Maps using Folium in Python</a></p><h1 id="e850">References</h1><p id="92be"><a href="https://geopandas.org/en/stable/docs/user_guide.html">GeoPandas</a></p><p id="33bb"><a href="https://matplotlib.org/stable/index.html">Matplotlib</a></p><p id="0f0f"><a href="https://www.spc.noaa.gov/gis/svrgis/">https://www.spc.noaa.gov/gis/svrgis/</a></p><h1 id="d76b">In Plain English</h1><p id="2bf0"><i>Thank you for being a part of our community! Before you go:</i></p><ul><li><i>Be sure to <b>clap</b> and <b>follow</b> the writer! 👏</i></li><li><i>You can find even more content at <a href="https://plainenglish.io/"><b>PlainEnglish.io</b></a><b> 🚀</b></i></li><li><i>Sign up for our <a href="http://newsletter.plainenglish.io/"><b>free weekly newsletter</b></a>. 🗞️</i></li><li><i>Follow us on <a href="https://twitter.com/inPlainEngHQ"><b>Twitter</b></a></i>, <a href="https://www.linkedin.com/company/inplainenglish/"><b><i>LinkedIn</i></b></a>, <a href="https://www.youtube.com/channel/UCtipWUghju290NWcn8jhyAw"><b><i>YouTube</i></b></a>, and <a href="https://discord.gg/GtDtUAvyhW"><b><i>Discord</i></b></a><b><i>.</i></b></li></ul></article></body>

Visualizing Geospatial Information using GeoPandas in Python

Learn how to leverage the GeoPandas library to create interactive and dynamic geospatial visualizations in your Python projects

Source: Author

In this blog, we will cover the below topics

  • Introduction
  • Understanding Coordinate Reference Systems (CRS)
  • Plotting Geospatial Data with Matplotlib and GeoPandas
  • Customizing Maps: Colors, Legends, Labels
  • Conclusion

Introduction

In today’s data-driven world, understanding the geographical aspects of your information is essential for making informed decisions. From analyzing the spread of diseases to plotting the locations of your favorite coffee shops, geospatial data visualization provides valuable insights by bringing data to life on interactive maps.

In this article, we’ll delve into the world of geospatial analysis and visualization, focusing on how to harness the capabilities of GeoPandas to create stunning maps that not only display your data but also allow for seamless exploration and interaction. Whether you’re a seasoned data analyst or a curious beginner, this guide will equip you with the skills you need to transform your raw location-based data into visually appealing, informative, and user-friendly maps.

We’ll start with the basics, introducing you to the fundamental concepts of geospatial data and Coordinate Reference Systems (CRS). Then, we’ll dive into the GeoPandas library, a popular Python package that simplifies the process of creating maps with just a few lines of code. With its user-friendly interface and powerful features, GeoPandas empowers you to visualize data points, overlay layers, and even add custom markers, pop-ups, and tooltips to enhance the viewer’s understanding.

Understanding Coordinate Reference Systems (CRS)

Coordinate Reference Systems (CRS) are a fundamental concept in geospatial analysis and cartography, serving as a framework for defining and locating geographic positions on the Earth’s surface. They provide a standardized way to accurately represent spatial data, allowing different datasets to be integrated and analyzed seamlessly.

The Earth is three-dimensional, but maps and spatial data are two-dimensional representations. Coordinate Reference Systems address this by establishing a mapping between Earth’s curved surface and a flat plane. This mapping specifies how geographic coordinates (latitude and longitude) on the Earth’s surface are projected onto a two-dimensional Cartesian plane.

There are two main types of Coordinate Reference Systems:

  1. Geographic Coordinate Systems (GCS): Geographic Coordinate Systems use latitude and longitude to define points on the Earth’s surface. Latitude measures the north-south position, while longitude measures the east-west position. The most common GCS is the World Geodetic System 1984 (WGS 84), used by the Global Positioning System (GPS) and many other mapping technologies. GCS is based on an ellipsoid model of the Earth’s shape. Here is a list of common ones.
  • “EPSG:4326” WGS84 Latitude/Longitude, used in GPS
  • “EPSG:3395” Spherical Mercator. Google Maps, OpenStreetMap, Bing Maps
  • “EPSG:32633” UTM Zones (North)
  • “EPSG:32733” UTM Zones (South)
  1. Projected Coordinate Systems (PCS): Projected Coordinate Systems are used to represent geographic locations on a flat surface, often referred to as a map. They involve a mathematical transformation from the spherical or ellipsoidal coordinates of the GCS to a Cartesian coordinate system. This transformation introduces distortion, which varies based on the chosen projection. Different projections minimize specific types of distortion depending on the area of interest. Examples of map projections include Mercator, Lambert Conformal Conic, and Albers Equal Area.

A combination of parameters defines CRS:

  • Datum: A reference model of the Earth’s shape and size. Different datums can result in varying accuracy of spatial data, especially when comparing data across different CRS.
  • Projection: The mathematical method used to project the three-dimensional Earth onto a two-dimensional plane. Projections are chosen based on the intended use and area of analysis.
  • Units: The units of measurement for distances and coordinates, such as meters, feet, or degrees.
  • Origin: The point where the coordinate system’s origin is located.
  • False Easting and False Northing: Offsets are added to the x and y coordinates to avoid negative values and to ensure all coordinates are positive within a specific region.

Feel free to check the blog Introduction To Geospatial Analysis With Python. It covers the basics of geospatial analysis using GeoPandas and Matplotlib for visualizing geospatial data.

Visualization with Matplotlib and GeoPandas

Let’s start with the plotting of the US map using the census data. It has 58 rows and 11 columns with each state representing a state information.

states = geopandas.read_file("data/usa-states-census-2014.shp")
print(states.shape)
print(states.columns)

OUTPUT:
(58, 11)
Index(['STATEFP', 'STATENS', 'AFFGEOID', 'GEOID', 'STUSPS', 'NAME', 'LSAD',
       'ALAND', 'AWATER', 'region', 'geometry'],
      dtype='object')

Let’s look at the data; our focus will be on a few key factors like state, region, and geometry.

STUSPS NAME region geometry
0 CA California West MULTIPOLYGON Z (((-118.59397 33.46720 0.00000,...
1 DC District of Columbia Northeast POLYGON Z ((-77.11976 38.93434 0.00000, -77.04...
2 FL Florida Southeast MULTIPOLYGON Z (((-81.81169 24.56874 0.00000, ...
3 GA Georgia Southeast POLYGON Z ((-85.60516 34.98468 0.00000, -85.47...
4 ID Idaho West POLYGON Z ((-117.24303 44.39097 0.00000, -117....

It will essential to note the coordinate system (CRS). The data is in EPSG:4326. We will discuss the CRS and its importance in the later sections.

states.crs

OUTPUT:
<Geographic 2D CRS: EPSG:4326>
Name: WGS 84
Axis Info [ellipsoidal]:
- Lat[north]: Geodetic latitude (degree)
- Lon[east]: Geodetic longitude (degree)
Area of Use:
- name: World.
- bounds: (-180.0, -90.0, 180.0, 90.0)
Datum: World Geodetic System 1984 ensemble
- Ellipsoid: WGS 84
- Prime Meridian: Greenwich

Now, let's start with basic visualization of the maps. There are various colors, and boundaries that can be used. Here are a few examples.

fig, axes = plt.subplots(2,2, figsize=(12,6), sharex=True, sharey=True)
base = states.plot(ax=axes[0,0])
axes[0,0].set_title("Standard plot")

base = states.boundary.plot(ax=axes[0,1])
axes[0,1].set_title("Boundary plot")

base = states.plot(cmap = "magma",ax=axes[1,0])
axes[1,0].set_title("magma Theme")

base = states.plot(cmap = "Pastel1",ax=axes[1,1])
axes[1,1].set_title("Paste1 theme")
Source: Author

We can also focus on a specific state for example California.

states[states['NAME'] == 'California'].plot()
Source: Author

The maps are certainly impressive, and adding the labels of the states would provide valuable context. Moreover, is there functionality to emphasize a particular group of states using a distinct color? Our intention is to spotlight the states in the mid-west region.

midwest = states[states['region'] == 'Midwest']

fig = plt.figure(1, figsize=(12,12)) 
ax = fig.add_subplot()
states.apply(lambda x: ax.annotate(text = str(x.STUSPS), 
              xy=x.geometry.centroid.coords[0], ha='center', 
                      fontsize=14),axis=1)
states.boundary.plot(ax=ax, color='Black', linewidth=.4)
states.plot(ax=ax, color='ivory', figsize=(12, 12))

midwest.plot(ax=ax)
ax.text(-0.05, 0.5, transform=ax.transAxes,
        fontsize=20, color='gray', alpha=0.2,
        ha='center', va='center', rotation='90')
Source: Author

In the same way, we can segment different regions like below eg: west, southwest, southeast, midwest, and northeast.

west = states[states['region'] == 'West']
southwest = states[states['region'] == 'Southwest']
southeast = states[states['region'] == 'Southeast']
midwest = states[states['region'] == 'Midwest']
northeast = states[states['region'] == 'Northeast']

fig = plt.figure(1, figsize=(12,12)) 
ax = fig.add_subplot()
states.apply(lambda x: ax.annotate(text = str(x.STUSPS), 
  xy=x.geometry.centroid.coords[0], ha='center', fontsize=14),axis=1)
states.boundary.plot(ax=ax, color='Black', linewidth=.4)
# states.plot(ax=ax, color='ivory', figsize=(12, 12))
southwest.plot(ax=ax)
west.plot(ax=ax,color="MistyRose")
southwest.plot(ax=ax, color="PaleGoldenRod")
southeast.plot(ax=ax, color="Plum")
midwest.plot(ax=ax, color="PaleTurquoise")
northeast.plot(ax=ax, color="ivory")
ax.text(-0.05, 0.5, transform=ax.transAxes,
        fontsize=20, color='gray', alpha=0.2,
        ha='center', va='center', rotation='90')
Source: Author

Up to this point, our exploration has encompassed the creation of visual representations and spatial segmentation, coupled with the integration of data into these visualizations. Traditionally, global or regional cartographic depictions serve as foundational frameworks, upon which analytical insights are projected to convey narratives effectively. In the preliminary blog post titled “Initiation into Geospatial Analysis Using Python,” our focus centered on the incorporation of seismic activity data onto a global map. In the next section, we will pivot to tornado-related data, seamlessly superimposing our analytical observations onto the foundational map developed within this phase.

We will use the tornado data from the US and the skills we learned from the previous section to visualize the data. Let’s load the tornado data and explore it. There are 63K rows and 23 columns.

torn = geopandas.read_file("1950-2018-torn-initpoint.shp")

torn.shape
OUTPUT:
(63645, 23)

torn.columns
OUTPUT:
Index(['om', 'yr', 'mo', 'dy', 'date', 'time', 'tz', 'st', 'stf', 
        'stn', 'mag','inj', 'fat', 'loss', 'closs', 'slat', 'slon', 
        'elat', 'elon', 'len','wid', 'fc', 'geometry'],
      dtype='object')

torn.head()

OUTPUT:
om yr mo dy date time tz st stf stn ... loss closs slat slon elat elon len wid fc geometry
0 1 1950 1 3 1950-01-03 11:00:00 3 MO 29 1 ... 6.0 0.0 38.77 -90.22 38.8300 -90.0300 9.5 150 0 POINT (-90.22000 38.77000)
1 2 1950 1 3 1950-01-03 11:55:00 3 IL 17 2 ... 5.0 0.0 39.10 -89.30 39.1200 -89.2300 3.6 130 0 POINT (-89.30000 39.10000)
2 3 1950 1 3 1950-01-03 16:00:00 3 OH 39 1 ... 4.0 0.0 40.88 -84.58 40.8801 -84.5799 0.1 10 0 POINT (-84.58000 40.88000)
3 4 1950 1 13 1950-01-13 05:25:00 3 AR 5 1 ... 3.0 0.0 34.40 -94.37 34.4001 -94.3699 0.6 17 0 POINT (-94.37000 34.40000)
4 5 1950 1 25 1950-01-25 19:30:00 3 MO 29 2 ... 5.0 0.0 37.60 -90.68 37.6300 -90.6500 2.3 300 0 POINT (-90.68000 37.60000)
5 rows × 23 columns

Let’s look at the CRS of this data and we observe that the CRS is the same as that of the USA map we had in the earlier sections.

torn.crs

OUTPUT:
<Geographic 2D CRS: EPSG:4326>
Name: WGS 84
Axis Info [ellipsoidal]:
- Lat[north]: Geodetic latitude (degree)
- Lon[east]: Geodetic longitude (degree)
Area of Use:
- name: World.
- bounds: (-180.0, -90.0, 180.0, 90.0)
Datum: World Geodetic System 1984 ensemble
- Ellipsoid: WGS 84
- Prime Meridian: Greenwich

Let's explore the tornado by plotting it on the map.

torn.plot( figsize=(12,9), color='red', marker='x', markersize=1)
Source: Author

From the visuals, it looks like there are 3 more additional states in tornado data that are missing in the base map. Let’s check

len(list(states.STUSPS.unique()))
OUTPUT: 49

len(list(torn.st.unique()))
OUTPUT: 52

For the sake of simplicity, let’s remove these regions from the analysis and plot the filtered data on the map.

torn_states_filtered = torn[torn['st'].isin(list(states.STUSPS.unique()))]

# Verify
len(list(torn_states_filtered.st.unique()))
OUTPUT: 
49
fig = plt.figure(1, figsize=(25,15)) 
ax = fig.add_subplot()
states.apply(lambda x: ax.annotate(text = str(x.STUSPS), 
  xy=x.geometry.centroid.coords[0], ha='center', fontsize=14),axis=1)
states.boundary.plot(ax=ax, color='black', linewidth=.4)
states.plot(ax=ax, color = 'ivory', figsize=(12, 12))
torn_states_filtered.plot(ax=ax, color='red',alpha = 0.5, marker='.', 
  markersize=1)
ax.text(-0.05, 0.5, transform=ax.transAxes,
        fontsize=20, color='white', alpha=0.2,
        ha='center', va='center', rotation='90')
Source: Author

We can see that there are fewer tornadoes in the western region compared to the eastern part. Tornadoes are more frequent in the southeast and midwest areas. To confirm this observation, let’s verify it using the available data.

torn['cnt'] = 1
torn_sub = torn[['st','cnt']].groupby('st').count()
torn_sub = torn_sub.sort_values('cnt', ascending=True).tail(20)
torn_sub.plot.barh()
Source: Author

Well, Texas has the highest number of tornadoes followed by KS and OK. We can carry on with further analysis by region, specific states, etc but the objective here is to set the baseline approach and analysis. It is also up to the analysts to use their imagination and data for creative visualization.

Conclusion

The geospatial data visualization using GeoPandas in Python opens up a world of possibilities for unlocking insights and communicating information in a visually captivating manner. Throughout this guide, we’ve explored the core concepts of geospatial data, from understanding Coordinate Reference Systems (CRS) to mastering the capabilities of the GeoPandas library. Data visualization isn’t just about creating visually appealing maps it’s about telling stories and conveying meaningful insights hence it is recommended that one experiment with different data sources, explore various map styles, and experiment with interactive elements like pop-ups and tooltips for compelling storytelling.

I hope you liked the article and found it helpful.

You can connect with me — on Linkedin and Github

My other Geospatial Analysis blogs

Introduction To Geospatial Analysis With Python Interactive Geospatial Maps using Folium in Python

References

GeoPandas

Matplotlib

https://www.spc.noaa.gov/gis/svrgis/

In Plain English

Thank you for being a part of our community! Before you go:

Geospatial
Python
Geopandas
Visualization
Matplotlib
Recommended from ReadMedium