color bar issue with cartopy, 'GeoAxesSubplot' object has no attribute 'get_array' - geopandas

I'm using pandas, geopandas, and cartopy to make spatial plots for data points.
Everything works fine, except for when I try to add color bar.
Below is my code and the error. Any help is appreciated.
fig = plt.figure()
ax = plt.axes(projection=ccrs.PlateCarree())
reader = shpreader.Reader('countyl010g.shp')
counties = list(reader.geometries())
COUNTIES = cfeature.ShapelyFeature(counties, ccrs.PlateCarree())
ax.add_feature(COUNTIES, facecolor='none', edgecolor='gray')
ax.coastlines()
ax.add_feature(cartopy.feature.STATES)
dp=pd.read_csv('Arash.csv',index_col=False)
def remove_minutes(state):
state=datetime.datetime.strptime(state, '%Y-%m-%d %H:%M:%S')
state= state.replace(minute=0)
return state
dp['TIMESTAMP']=dp['TIMESTAMP'].apply(remove_minutes)
dp.set_index(['TIMESTAMP'], inplace=True)
dp= dp[dp.index.day == 28]
dp['coordinates'] = dp[['Longitude', 'Latitude']].values.tolist()
dp['coordinates'] = dp['coordinates'].apply(Point)
dp = gpd.GeoDataFrame(dp, geometry='coordinates')
ac=dp.plot(ax=ax,column='CO_CMAQ',markersize=0.05,cmap='turbo')
ax.set_xlim(-119,-117)
ax.set_ylim(33.5,34.5)
fig.colorbar(ac,ax=ax)
And here is the error:
File "C:\Python-practice\GHG\spatial_plot_mobile.py", line 102, in
fig.colorbar(ac,ax=ax)
File
"C:\Users\akash\anaconda3\lib\site-packages\matplotlib\figure.py",
line 2343, in colorbar cb = cbar.colorbar_factory(cax, mappable,
**cb_kw)
File
"C:\Users\akash\anaconda3\lib\site-packages\matplotlib\colorbar.py",
line 1734, in colorbar_factory cb = Colorbar(cax, mappable, **kwargs)
File
"C:\Users\akash\anaconda3\lib\site-packages\matplotlib\colorbar.py",
line 1202, in init
if mappable.get_array() is not None: AttributeError: 'GeoAxesSubplot'
object has no attribute 'get_array'
Thank you again,

Old question, but I landed on it because I had an analogous error, so in case it helps anyone else in future:
The problem is that ac here is a GeoAxesSubplot object within a Matplotlib figure. When Matplotlib is given fig.colorbar(ac,ax=ax), it doesn't know what to do with ac.
As per the Geopandas documentation, the information about the colorbar needs to go into the Geopandas .plot() itself, so that the relevant line above would look something like:
ac=dp.plot(ax=ax,column='CO_CMAQ',markersize=0.05,cmap='turbo',
legend=True, legend_kwds={'label':"",'orientation':""})
with the legend_kwds filled in as desired, e.g. orientation="horizontal".
More advice on modifying Geopandas colorbars here.

Related

Trying to update graph using CSV

I have a question. I am trying to plot a live graph while continuously updating a CSV file continuously from a LIDAR sensor. However when I call animation.FuncAnimation(... the graph does not continuously update. If I rerun, I see the graph updated. When I view the CSV file, I see has been updating.
class SecondGraph:
def animate(i):
graph_data = open(NameofCSV,'r').read()
lines = graph_data.split('\n')
xs = []
ys = []
for line in lines:
if len(line) > 0:
x, y = line.split(' ')
xs.append(float(x))
ys.append(float(y))
ax1.clear()
ax1.plot(ys, xs)
ani = animation.FuncAnimation(fig, SecondGraph.animate, interval=10)
When someone has a chance, can someone guide me in the right direction here. I do not think I need to plot my full code, but if you need to see it. Let me know.
Disregard. I think I know the answer. The CSV does not update automatically, which may be the problem. I do not think there is anything wrong with my code that I posted.

Is it possible to change the number of columns of a legend created with FacetGrid.add_legend()?

The following code creates a legend of a facegrid and positions it above:
g = sns.FacetGrid(df, col="col", col_wrap=2, hue='key')
g = g.map(sns.lineplot, 'x', 'y')
g.add_legend()
g._legend.set_bbox_to_anchor((0.5, 1.05))
Is there a way to control the number columns? It has a _ncol attribute but I haven't seen a method to change it. Changing it manually doesn't do the job.
I found the definition of add_legend in the seaborn source code. It accepts **kwargs and hands them to self.figure.legend(...) or ax.legend(...).
So one can simply pass ncol=2 as parameter. This should also be a better was to define the position of the legend by using loc and/or bbox_to_anchor arguments.
Here would is an example:
import seaborn as sns
tips = sns.load_dataset("tips")
g = sns.FacetGrid(tips, col="sex", hue="smoker")
g.map(sns.scatterplot, "total_bill", "tip", alpha=.7)
g.add_legend(ncol=2, bbox_to_anchor=(0.5,1.05));

Is my topojson file structured properly in order to render a map on folium?

I've downloaded the us-counties shapefile from the US census bureau and converted it into topojson file using mapshaper.com. Unfortunately, I have to parse through the topojson quite a bit to get the FIPS county code. I'm using Folium to render the map but keep getting an error.
I've taken my dataframe and made it into a series of FIPS_codes and $amounts. Using the style_function, I call the FIPS_codes from the topojson file and compare that value to the series to render a map of us-counties.
import branca
colorscale = branca.colormap.linear.YlOrRd_09.scale(0, 50e3)
def style_function(feature):
county_dict = cms_2017_grouped_series.get(
features['objects']['tl_2017_us_county']['geometries']['properties']['GEOID'], None)
return {
'fillOpacity': 0.5,
'weight': 0,
'fillColor': '#black' if employed is None else colorscale(employed)
}
The error I'm getting is AttributeError: 'list' object has no attribute 'get'
The rest of code needed to render the map is below
m = folium.Map(
location=[48, -102],
tiles='cartodbpositron',
zoom_start=3
)
folium.TopoJson(
json.load(open(county_geo)),
'objects.tl_2017_us_county.geometries.properties.GEOID',
style_function=style_function
).add_to(m)
I followed your steps to create the topojson and good news, it checks out. Just need to change up a couple of things with your code
I created some mock user data first. I'm using geopandas and the topjson file to make it easy on myself, but you would just use your pandas dataframe that contains county and employment numbers
import geopandas as gpd
gdf = gpd.read_file('tl_2017_us_county.json')
gdf['employed'] = np.random.randint(low=1, high=100000, size= len(gdf))
Create a Series using your dataframe. This will be used in the style func to "bind" your data to the map
cms_2017_grouped_series = gdf.set_index('GEOID')['employed']
print(cms_2017_grouped_series.head())
GEOID
31039 54221
53069 68374
35011 8477
31109 2278
31129 40247
Name: employed, dtype: int64
This is pretty close to your style function. I've just changed the line with the .get() to use the corrected dict keys of feature. Oh and I'm using the return value(employed) in the fillColor below
import branca
colorscale = branca.colormap.linear.YlOrRd_09.scale(0, 50e3)
def style_function(feature):
employed = cms_2017_grouped_series.get(feature['properties']['GEOID'], None)
return {
'fillOpacity': 0.5,
'weight': 0,
'fillColor': '#black' if employed is None else colorscale(employed)
}
Slight mod of the object_path is next. I'm also saving the map and then opening it in Chrome since it wouldn't render in my notebook due to the size
m = folium.Map(
location=[48, -102],
tiles='cartodbpositron',
zoom_start=3
)
folium.TopoJson(open('tl_2017_us_county.json'), 'objects.tl_2017_us_county',
style_function=style_function).add_to(m)
m.save('map.html')

Changing title on a subplot of Lmplot

lm = sns.lmplot(x='early_mean_zscores', y='late_mean_zscores', col='cat', data=combo)
fig = lm.fig
fig.suptitle("Improvement from Early to Late assignments")
a1 = fig.axes[1]
a1.title("adsadas")
I would like to set the title of the subplots generated by hand, i.e. not based on the col='cat'. This code, and many permutations of it that I have tried don't seem to work.
I don't understand the timing of when the plot is fixed and displayed. Is it on the first line, lm = ... in which case it's not surprising that I cannot, retroactively change the title.
I could use some help on both fixing the code, and a clarification on the sequencing and theory of what is going wrong.
The command to set a title is ax.set_title(). Using that, the code should work as expected.
A minimal example:
import matplotlib.pyplot as plt
import seaborn as sns
tips = sns.load_dataset("tips")
lm = sns.lmplot(x="total_bill", y="tip", col="smoker", data=tips)
fig = lm.fig
fig.suptitle("Custom Super Title")
a1 = fig.axes[1]
a1.set_title("Custom Title")
plt.show()
depending on what you need, you could just use:
lm = sns.lmplot(x="total_bill", y="tip", col="smoker", data=tips).set_titles("{col_name}") # {col_name} is special parameter of actual column name

How to choose which figure on which to display an image with matplotlib

I have a python script with multiple figures that I would like to update during a loop. Some will be images, and others will be line/scatter plots. I am having trouble getting the image to display on the correct figure. (the line and scatter data are showing up on the right figures, but the image seems to always be going on the figure that was created last, eventually I'll be displaying more than one image figure, so I can't just create the image figure last)
Here is roughly the code I have so far, the 3D scatter plot is showing up on Figure 1, but both the image and the line plots are showing up on Figure 3, with Figure 2 blank:
import matplotlib.pyplot as plt
from collections import deque
class Bla():
def __init__( self ):
self.pc_fig = plt.figure(1)
self.pc_ax = self.pc_fig.add_subplot(111, projection='3d')
self.pc_ax.set_xlim3d([0, 50])
self.pc_ax.set_ylim3d([0, 50])
self.pc_ax.set_zlim3d([0, 20])
self.pc_ax.hold(False)
self.vts_fig = plt.figure(2)
self.vts_ax = self.vts_fig.add_subplot(111)
self.em_fig = plt.figure(3)
self.em_ax = self.em_fig.add_subplot(111)
self.em_ax.hold(True)
self.image_data = deque()
self.motion_data = deque()
plt.ion()
plt.show()
def run( self ):
em_prev_xy = ( 0, 0 )
while True:
if len( self.motion_data ) > 0:
data1 = self.motion_data.popleft()
em_xy = data1.get_em()
self.em_ax.plot( [ em_prev_xy[0], em_xy[0] ], [ em_prev_xy[1], em_xy[1] ],'b')
pc = self.get_pc()
pc_index = nonzero(pc>.002)
pc_value = pc[pc_index] * 100
self.pc_ax.scatter(pc_index[0],pc_index[1],pc_index[2],s=pc_value)
self.pc_ax.set_xlim3d([0, 50])
self.pc_ax.set_ylim3d([0, 50])
self.pc_ax.set_zlim3d([0, 20])
plt.pause( 0.0001 ) # This is needed for the display to update
if len( self.image_data ) > 0:
im = self.image_data.popleft()
plt.imshow( im, cmap=plt.cm.gray, axes=self.vts_ax )
plt.pause( 0.0001 )
def main():
bla = Bla()
bla.run()
if __name__ == "__main__":
main()
Basically I have some queues that get populated in a callback when new data arrives, and I want this data to be displayed as it arrives.
I am new to matplotlib, so any help with my image display issue or tips for better ways of using matplotlib to display figures in general will be much appreciated
You are mixing the OO and state machine interfaces. See this answer for an explanation of what is going on.
Replace this line:
plt.imshow( im, cmap=plt.cm.gray, axes=self.vts_ax )
with
the_axes_you_want.imshow(...)
which should fix your image issue.

Resources