Reputation: 73
I am working on an assignment that I have pretty much already completed, but I wanted to add a small touch to it that attempts to fill the area between the two lines with a colormap based on temperature instead of just a simple color. The way the lines are plotted makes them separate entities essentially, so I know that I'll likely need two colormaps that meet each other or overlap to accomplish this but I'm not too sure how to accomplish this. Any assistance is greatly appreciated.
from datetime import datetime
import pandas as pd
import numpy as np
import matplotlib.colors as mcol
import matplotlib.cm as cm
bin = 400
hash = 'fb441e62df2d58994928907a91895ec62c2c42e6cd075c2700843b89'
Temp = pd.read_csv('fb441e62df2d58994928907a91895ec62c2c42e6cd075c2700843b89.csv'.format(bin, hash))
Temp['Date'] = pd.to_datetime(Temp['Date'])
#Only doing this here because the mplleaflet in my personal jupyter notebook is bugged
#will take longer to execute, will take more lines of code for conversions and ultimately is less efficient than simply doing it with pandas.
#print(datetime.strptime(Temp['Date'].to_json(), '%y-%m-%d')) = datetime.strptime(Temp['Date'], format)
Temp['Y'] = Temp['Date'].dt.year
Temp['M'] = Temp['Date'].dt.month
Temp['D'] = Temp['Date'].dt.day
Temp['DV'] = Temp['Data_Value'].div(10)
Temp['E'] = Temp['Element']
Temp = Temp[~((Temp['M']==2) & (Temp['D']==29))]
GrMin = Temp[(Temp['E']=='TMIN') & (Temp['Y']>=2005) & (Temp['Y']<2015)].groupby(['M','D']).agg({'DV':np.min})
FinMin = Temp[(Temp['E']=='TMIN') & (Temp['Y']==2015)].groupby(['M','D']).agg({'DV':np.min})
GrMax = Temp[(Temp['E']=='TMAX') & (Temp['Y']>=2005) & (Temp['Y']<2015)].groupby(['M','D']).agg({'DV':np.max})
FinMax = Temp[(Temp['E']=='TMIN') & (Temp['Y']==2015)].groupby(['M','D']).agg({'DV':np.max})
#x = GrMax
#y = GrMin
#X, Y = np.meshgrid(x,y)
#Z = f(X, Y)
AnomMin = FinMin[FinMin['DV'] < GrMin['DV']]
AnomMax = FinMax[FinMax['DV'] > GrMax['DV']]
#temps = range(-30,40)
plt.figure(figsize=(18, 10), dpi = 80)
red = '#FF0000'
blue = '#0800FF'
cm1 = mcol.LinearSegmentedColormap.from_list('Temperature Map',[blue, red])
cnorm = mcol.Normalize(vmin=min(GrMin['DV']),vmax=max(GrMax['DV']))
cpick = cm.ScalarMappable(norm=cnorm,cmap=cm1)
cpick.set_array([])
plt.title('Historical Temperature Analysis In Ann Arbor Michigan')
plt.xlabel('Month')
plt.ylabel('Temperature in Celsius')
plt.plot(GrMax.values, c = red, linestyle = '-', label = 'Highest Temperatures (2005-2014)')
#plt.scatter(AnomMax, FinMax.iloc[AnomMax], c = red, s=5, label = 'Anomolous High Readings (2015)')
plt.plot(GrMin.values, c = blue, linestyle = '-', label = 'Lowest Temperatures (2005-2014)')
#plt.scatter(AnomMin, FinMin.iloc[AnomMin], c = blue, s=5, label = 'Anomolous Low Readings (2015)')
plt.xticks(np.linspace(0,60 + 60*11, num=12),(r'January',r'February',r'March',r'April',r'May',r'June',r'July',r'August',r'September',r'October',r'November',r'December'))
#Failed Attempt
#plt.contourf(X, Y, Z, 20, cmap = cm1)
#for i in temps
# plt.fill_between(len(GrMin['DV']), GrMin['DV'], i ,cmap = cm1)
#for i in temps
# plt.fill_between(len(GrMin['DV']), i ,GrMax['DV'], cmap = cm1)
#Kind of Close but doesn't exactly create the colormap
plt.gca().fill_between(range(len(GrMin.values)), GrMin['DV'], GrMax['DV'], cmap = cm1)
plt.legend(loc = '0', title='Temperature Guide')
plt.colorbar(cpick, label='Temperature in Celsius')
plt.show()
Upvotes: 2
Views: 2619
Reputation: 73
After correcting some functional errors within my code then applying the code provided by JohanC as well as asking for some other much-needed guidance, I was able to successfully complete the colormap. It would probably be more visually appealing if the upper and lower line plots were a different color but as far as the colormap is concerned, mission accomplished. Thanks again for the assistance!
from datetime import datetime
import pandas as pd
import numpy as np
import matplotlib.colors as mcol
import matplotlib.cm as cm
bin = 400
hash = 'fb441e62df2d58994928907a91895ec62c2c42e6cd075c2700843b89'
Temp = pd.read_csv('fb441e62df2d58994928907a91895ec62c2c42e6cd075c2700843b89.csv'.format(bin, hash))
Temp['Date'] = pd.to_datetime(Temp['Date'])
#Only doing this here because the mplleaflet in my personal jupyter notebook is bugged
#will take longer to execute, will take more lines of code for conversions and ultimately is less efficient than simply doing it with pandas.
#print(datetime.strptime(Temp['Date'].to_json(), '%y-%m-%d')) = datetime.strptime(Temp['Date'], format)
Temp['Y'] = Temp['Date'].dt.year
Temp['M'] = Temp['Date'].dt.month
Temp['D'] = Temp['Date'].dt.day
Temp['DV'] = Temp['Data_Value'].div(10)
Temp['E'] = Temp['Element']
Temp = Temp[~((Temp['M']==2) & (Temp['D']==29))]
GrMin = Temp[(Temp['E']=='TMIN') & (Temp['Y']>=2005) & (Temp['Y']<2015)].groupby(['M','D']).agg({'DV':np.min})
FinMin = Temp[(Temp['E']=='TMIN') & (Temp['Y']==2015)].groupby(['M','D']).agg({'DV':np.min})
GrMax = Temp[(Temp['E']=='TMAX') & (Temp['Y']>=2005) & (Temp['Y']<2015)].groupby(['M','D']).agg({'DV':np.max})
FinMax = Temp[(Temp['E']=='TMAX') & (Temp['Y']==2015)].groupby(['M','D']).agg({'DV':np.max})
GrMax = GrMax.reset_index()
GrMin = GrMin.reset_index()
FinMax = FinMax.reset_index()
FinMin = FinMin.reset_index()
#x = GrMax
#y = GrMin
#X, Y = np.meshgrid(x,y)
#Z = f(X, Y)
AnomMin = FinMin[FinMin['DV'] < GrMin['DV']]
AnomMax = FinMax[FinMax['DV'] > GrMax['DV']]
#temps = range(-30,40)
plt.figure(figsize=(18, 10), dpi = 160)
red = '#FF0000'
blue = '#0800FF'
cm1 = mcol.LinearSegmentedColormap.from_list('Temperature Map',[blue, red])
cnorm = mcol.Normalize(vmin=min(GrMin['DV']),vmax=max(GrMax['DV']))
cpick = cm.ScalarMappable(norm=cnorm,cmap=cm1)
cpick.set_array([])
plt.title('Historical Temperature Analysis In Ann Arbor Michigan')
plt.xlabel('Month')
plt.ylabel('Temperature in Celsius')
plt.plot(GrMax['DV'], c = red, linestyle = '-', label = 'Highest Temperatures (2005-2014)')
plt.scatter(AnomMax.index, AnomMax['DV'], c = red, s=2, label = 'Anomolous High Readings (2015)')
plt.plot(GrMin['DV'], c = blue, linestyle = '-', label = 'Lowest Temperatures (2005-2014)')
plt.scatter(AnomMin.index, AnomMin['DV'], c = blue, s=2, label = 'Anomolous Low Readings (2015)')
plt.xticks(np.linspace(0,365,12, endpoint = True),(r'January',r'February',r'March',r'April',r'May',r'June',r'July',r'August',r'September',r'October',r'November',r'December'))
#Start: Assisted from StackOverFlow user JohanC v
x = np.arange(len(GrMin['DV'].fillna(0).astype('float64').ravel()))
y1 = GrMax['DV'].fillna(0).astype('float64').ravel()
y2 = GrMin['DV'].fillna(0).astype('float64').ravel()
polygon = plt.fill_between(x, y1, y2, lw=0, color='none')
xlim = (x.min(), x.max())
ylim = plt.ylim()
verts = np.vstack([p.vertices for p in polygon.get_paths()])
gradient = plt.imshow(np.linspace(1, 0, 256).reshape(-1, 1), cmap=cm1, aspect='auto',
extent=[verts[:, 0].min(), verts[:, 0].max(), verts[:, 1].min(), verts[:, 1].max()])
gradient.set_clip_path(polygon.get_paths()[0], transform=plt.gca().transData)
plt.xlim(xlim)
plt.ylim(ylim)
#Finish: Assisted from StackOverFlow user JohanC ^
#Failed Attempt at gradient fill with colormap
#plt.contourf(X, Y, Z, 20, cmap = cm1)
#for i in temps
# plt.fill_between(len(GrMin['DV']), GrMin['DV'], i ,cmap = cm1)
#for i in temps
# plt.fill_between(len(GrMin['DV']), i ,GrMax['DV'], cmap = cm1)
#Kind of Close but doesn't exactly create the colormap
#plt.gca().fill_between(range(len(GrMin.values)), GrMin['DV'], GrMax['DV'], facecolor = 'grey', alpha = 0.10)
plt.legend(loc = 'lower center', title='Temperature Guide')
plt.colorbar(cpick, label='Temperature in Celsius')
plt.show()
Upvotes: 0
Reputation: 80449
You could draw a colored rectangle covering the curves. And use the polygon created by fill_between
to clip that rectangle:
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
import numpy as np
x = np.linspace(0, 10, 200)
y1 = np.random.normal(0.02, 1, 200).cumsum() + 20
y2 = np.random.normal(0.05, 1, 200).cumsum() + 50
cm1 = LinearSegmentedColormap.from_list('Temperature Map', ['blue', 'red'])
polygon = plt.fill_between(x, y1, y2, lw=0, color='none')
xlim = (x.min(), x.max())
ylim = plt.ylim()
verts = np.vstack([p.vertices for p in polygon.get_paths()])
gradient = plt.imshow(np.linspace(0, 1, 256).reshape(-1, 1), cmap=cm1, aspect='auto', origin='lower',
extent=[verts[:, 0].min(), verts[:, 0].max(), verts[:, 1].min(), verts[:, 1].max()])
gradient.set_clip_path(polygon.get_paths()[0], transform=plt.gca().transData)
plt.xlim(xlim)
plt.ylim(ylim)
plt.show()
A more complicated alternative, would color such that the upper curve corresponds to red and the lower curve to blue:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 200)
y1 = np.random.normal(0.02, 1, 200).cumsum() + 20
y2 = np.random.normal(0.05, 1, 200).cumsum() + 50
polygon = plt.fill_between(x, y1, y2, lw=0, color='none')
ylim = plt.ylim()
verts = np.vstack([p.vertices for p in polygon.get_paths()])
ymin, ymax = verts[:, 1].min(), verts[:, 1].max()
gradient = plt.imshow(np.array([np.interp(np.linspace(ymin, ymax, 200), [y1i, y2i], np.arange(2))
for y1i, y2i in zip(y1, y2)]).T,
cmap='turbo', aspect='auto', origin='lower', extent=[x.min(), x.max(), ymin, ymax])
gradient.set_clip_path(polygon.get_paths()[0], transform=plt.gca().transData)
plt.ylim(ylim)
plt.show()
A variant could be to smooth out the color values in the horizontal direction (but still clip using the original curves):
from scipy.ndimage import gaussian_filter
gradient = plt.imshow(np.array([np.interp(np.linspace(ymin, ymax, 200), [y1i, y2i], np.arange(2))
for y1i, y2i in zip(gaussian_filter(y1, 4, mode='nearest'),
gaussian_filter(y2, 4, mode='nearest'))]).T,
cmap='turbo', aspect='auto', origin='lower', extent=[x.min(), x.max(), ymin, ymax])
Upvotes: 3