Related
I am trying to use classes/Methods to wrap around my code which otherwise works fine without OOP language.
Confusion arise for me when I dont know how to define the attribute correctly. For example, to set =None or =number etc?
import numpy as np
import matplotlib.pyplot as plt
from shapely.geometry.polygon import LinearRing
from shapely.geometry import Polygon
class cycloid_generation():
def __init__(self, axes=None, blank=None, plnt1=None, plnt2=None,
plnt3=None, pin_radius=10, pin_circle_radius=120,
eccentricity=5, p_pin_circle_radius=60, no_of_pins=16,
p_number_of_pins=15, contraction=1,
rolling_circle_line=None, rolling_circle=None):
self.plnt1 = plnt1
self.plnt2 = plnt2
self.plnt3 = plnt3
self.pin_radius = pin_radius
self.pin_circle_radius = pin_circle_radius
self.eccentricity = eccentricity
self.p_pin_circle_radius = p_pin_circle_radius
self.p_number_of_pins = p_number_of_pins
self.no_of_pins = no_of_pins
self.blank = blank
self.contraction = contraction
self.rolling_circle_line = rolling_circle_line
self.rolling_circle = rolling_circle
def setup_plot(self, amount):
fig1 = plt.figure(figsize=(8, 8))
ax = plt.axes(xlim=(-amount, amount), ylim=(-amount, amount))
plt.axis('off')
plt.ion()
plt.show()
return ax
def plots(self):
self.axes = self.setup_plot((self.pin_circle_radius+4*self.pin_radius))
self.contraction = self.rolling_circle_radius - self.eccentricity
self.cycloid_base = plt.Circle((0, 0),
self.cycloid_base_radius, fill=False,
linestyle='--', lw=2)
self.axes.add_patch(self.cycloid_base)
self.rolling_circle = plt.Circle((0, 0),
self.rolling_circle_radius,
fill=False, lw=2)
self.axes.add_patch(self.rolling_circle)
self.rolling_circle_line = plt.Line2D((0, 1), (0, 0), lw=2, color='red')
self.axes.add_line(self.rolling_circle_line)
return
def prmtrs(self, pin_radius, pin_circle_radius,
p_pin_circle_radius, number_of_pins, p_number_of_pins):
self.rolling_circle_radius = self.pin_circle_radius/self.no_of_pins
self.reduction_ratio = self.no_of_pins + 1 # reduction ratio
self.cycloid_base_radius = (self.reduction_ratio
*self.rolling_circle_radius)
self.p_rolling_circle_radius = (self.p_pin_circle_radius
/self.p_number_of_pins)
self.p_reduction_ratio = self.p_number_of_pins - 1
self.p_cycloid_base_radius = (self.p_reduction_ratio
*self.p_rolling_circle_radius)
return self.p_cycloid_base_radius
def geom(self):
for angle in range(0, 360):
# rotate rolling circle round the center of the cycloid
self.x = ((self.cycloid_base_radius - self.rolling_circle_radius)
* np.cos(np.radians(angle)))
self.y = ((self.cycloid_base_radius - self.rolling_circle_radius)
* np.sin(np.radians(angle)))
self.rolling_circle.center = (self.x, self.y)
self.point_x = (self.x +
(self.rolling_circle_radius - self.contraction)
*np.cos(np.radians(self.no_of_pins*-angle)))
self.point_y = (self.y +
(self.rolling_circle_radius - self.contraction)
*np.sin(np.radians(self.no_of_pins*-angle)))
self.rolling_circle_line.set_xdata((self.x, self.point_x))
self.rolling_circle_line.set_ydata((self.y, self.point_y))
self.epicycloid_points.append([self.point_x, self.point_y])
self.epicycloid.set_xy(self.epicycloid_points)
return
def polygons(self):
# polygon to hold the main epicycloid
self.epicycloid_points = []
self.epicycloid = plt.Polygon(self.blank, fill=False, closed=False,
color='red', lw=2)
self.axes.add_patch(self.epicycloid)
# polygon to hold the main planet1 epicycloid
self.plnt1_epicycloid_points = []
self.plnt1_epicycloid = plt.Polygon(self.blank, fill=False,
closed=False, color='red', lw=2)
self.axes.add_patch(self.plnt1_epicycloid)
# polygon to hold the main planet2 epicycloid
self.plnt2_epicycloid_points = []
self.plnt2_epicycloid = plt.Polygon(self.blank, fill=False,
closed=False, color='red', lw=2)
self.axes.add_patch(self.plnt2_epicycloid)
return
geom = cycloid_generation()
geom.prmtrs(10,120,60,16, 16)
geom.geom()
geom.polygons()
Error:
'NoneType' object has no attribute 'center'
Desired Output
Plots of geometry
Error occurs at this line self.rolling_circle.center = (self.x, self.y)
It telling you, your object rolling_circle is a None type and that's because you don't assign any other value to it.
When you declare self.rolling_circle in your constructor you assign it rolling_circle value which is None by default .
def __init__(self, ..., rolling_circle=None):
self.rolling_circle = rolling_circle
You have to make a cycloid_generation object and give a proper value to the rolling_circle otherwise it will be None and raise you the same error. geom = cycloid_generation(rolling_circle=something)
I am combining all defined function into a class and use if, elif to operate.
I will explain in the following.
First, I have a 3 types of plot, combo, line, and bar.
I know how to define function separately for these three plot.
Second, I want to combine these 3 plots together within a package using if.
The code I tried is:
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
class AP(object):
def __init__(self, dt, date, group, value, value2, value3, value4, value5, value6, TYPE):
self.dt = dt
self.date = date
self.group= carrier
self.value = value
self.col1 = col1
self.col2 = col2
self.col3 = col3
self.col4 = col4
self.TYPE = TYPE
if self.TYPE == "combo":
def ComboChart(self, dt, date, group, value, TYPE):
dataset = pd.read_csv(dt)
dataset['date'] = pd.to_datetime(dataset[date])
dataset['yq'] = pd.PeriodIndex(dataset['date'], freq='Q')
dataset['qtr'] = dataset['date'].dt.quarter
dataset = dataset.groupby([carrier, 'yq', 'qtr'])[value].sum().reset_index()
dataset['total.YQGR'] = dataset[value] / dataset.groupby(['qtr', carrier])[value].transform('shift') - 1
dataset = dataset[np.isfinite(dataset['total.YQGR'])]
dataset['total.R'] = dataset[value] / dataset.groupby(group)[value].transform('first')
dataset.yq = dataset.yq.astype(str)
fig, ax1 = plt.subplots(figsize=(12,7))
ax2=ax1.twinx()
sns.lineplot(x='yq',y='total.R', data=dataset, hue=group, ax=ax1, legend = None, palette = ('navy', 'r'), linewidth=5)
ax1.set_xticklabels(ax1.get_xticks(), rotation=45, fontsize=15, weight = 'heavy')
ax1.set_xlabel("", fontsize=15)
ax1.set_ylabel("")
ax1.set_ylim((0, max(dataset['total.R']) + 0.05))
sns.barplot(x='yq', y='total.YQGR', data=dataset, hue=group, ax=ax2, palette = ('navy', 'r'))
ax2.set_yticklabels(['{:.1f}%'.format(a*100) for a in ax2.get_yticks()])
ax2.set_ylabel("")
ax2.set_ylim((min(dataset['total.YQGR']) - 0.01, max(dataset['total.YQGR']) + 0.2))
ax2.get_legend().remove()
ax2.legend(bbox_to_anchor=(-0.35, 0.5), loc=2, borderaxespad=0., fontsize = 'xx-large')
for groups in ax2.containers:
for bar in groups:
if bar.get_height() >= 0:
ax2.text(
bar.get_xy()[0] + bar.get_width()/1.5,
bar.get_height() + 0.003,
'{:.1f}%'.format(round(100*bar.get_height(),2)),
color='black',
horizontalalignment='center',
fontsize = 12, weight = 'heavy'
)
else:
ax2.text(
bar.get_xy()[0] + bar.get_width()/1.5,
bar.get_height() - 0.008,
'{:.1f}%'.format(round(100*bar.get_height(),2)),
color='black',
horizontalalignment='center',
fontsize = 12, weight = 'heavy'
)
ax1.yaxis.set_visible(False)
ax2.yaxis.set_visible(False)
ax2.xaxis.set_visible(False)
ax1.spines["right"].set_visible(False)
ax1.spines["left"].set_visible(False)
ax1.spines["top"].set_visible(False)
ax1.spines["bottom"].set_visible(False)
ax2.spines["right"].set_visible(False)
ax2.spines["left"].set_visible(False)
ax2.spines["top"].set_visible(False)
ax2.spines["bottom"].set_visible(False)
ax1.set_title(TYPE, fontsize=20)
plt.show()
fig.savefig(TYPE, bbox_inches='tight', dpi=600)
elif self.TYPE == "line":
def line(self, dt, date, carrier, value, value2, TYPE):
dataset = pd.read_csv(dt)
dataset['date'] = pd.to_datetime(dataset[date])
dataset['yq'] = pd.PeriodIndex(dataset['date'], freq='Q')
dataset = dataset.groupby([group, 'yq'])[value, value2].sum().reset_index()
dataset['Arate'] = dataset[value2] / dataset[value]
dataset.yq = dataset.yq.astype(str)
fig, ax1 = plt.subplots(figsize=(12,7))
sns.lineplot(x='yq', y='Arate', data=dataset, hue=group, ax=ax1, linewidth=5)
ax1.set_xticklabels(dataset['yq'], rotation=45, fontsize = 15)
ax1.set_xlabel("")
ax1.set_ylabel("")
ax1.set_ylim((min(dataset['Arate']) - 0.05, max(dataset['Arate']) + 0.05))
ax1.set_yticklabels(['{:.1f}%'.format(a*100) for a in ax1.get_yticks()], fontsize = 18, weight = 'heavy')
ax1.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=2, borderaxespad=0., ncol = 6)
ax1.yaxis.grid(True)
ax1.spines["right"].set_visible(False)
ax1.spines["left"].set_visible(False)
ax1.spines["top"].set_visible(False)
ax1.spines["bottom"].set_visible(False)
ax1.set_title(TYPE, fontsize = 20)
plt.show()
fig.savefig(TYPE, bbox_inches='tight', dpi=600)
elif self.TYPE == "bar":
def Bar(self, dt, date, group, value3, value4, value5, value6, TYPE):
dataset = pd.read_csv(dt, sep = '|')
dataset['date'] = pd.to_datetime(dataset[date])
dataset['yq'] = pd.PeriodIndex(dataset['date'], freq='Q')
dataset = dataset.groupby([group, 'yq'])[value3, value4, value5, value6].sum().reset_index()
dataset = dataset.groupby([group]).tail(4)
dataset.yq = dataset.yq.astype(str)
dataset = pd.melt(dataset, id_vars = [group, 'yq'], value_vars = [value3, value4, value5, value6])
dataset = dataset.groupby(['variable', group]).value.sum().reset_index()
dataset['L4Qtr'] = dataset.value / dataset.groupby([group]).value.transform('sum')
fig, ax1 = plt.subplots(figsize=(12,7))
sns.barplot(x='variable', y='L4Qtr', data=dataset, hue=group, ax=ax1)
ax1.set_xticklabels(ax1.get_xticklabels(), fontsize=17.5, weight = 'heavy')
ax1.set_xlabel("", fontsize=15)
ax1.set_ylabel("")
ax1.yaxis.set_ticks(np.arange(0, max(dataset['L4Qtr']) + 0.1, 0.05), False)
ax1.set_yticklabels(['{:.1f}%'.format(a*100) for a in ax1.get_yticks()], fontsize = 18, weight = 'heavy')
ax1.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=2, borderaxespad=0., ncol = 6)
for groups in ax1.containers:
for bar in groups:
ax1.text(
bar.get_xy()[0] + bar.get_width()/2,
bar.get_height() + 0.005,
'{:.1f}%'.format(round(100*bar.get_height(),2)),
color=bar.get_facecolor(),
horizontalalignment='center',
fontsize = 16, weight = 'heavy'
)
ax1.spines["right"].set_visible(False)
ax1.spines["left"].set_visible(False)
ax1.spines["top"].set_visible(False)
ax1.spines["bottom"].set_visible(False)
ax1.set_title(TYPE, fontsize=20)
plt.show()
fig.savefig(TYPE, bbox_inches='tight', dpi=600)
Third, I hope others can simply use this module as below:
import sys
sys.path.append(r'\\users\desktop\module')
from AP import AP as ap
Finally, when someone assign TYPE, it will automatically plot and save it.
# This will plot combo chart
ap(r'\\users\desktop\dataset.csv', date = 'DATEVALUE', group = 'GRPS', value = 'total', TYPE = 'combo')
Above is the ideal thought. I do not need to pass value2 ~ value6 in it since combo does not use them.
When I want bar:
# This will plot bar chart
ap(r'\\users\desktop\dataset.csv', date = 'DATEVALUE', group = 'GRPS', value3 = 'col1', value4 = 'col2', value5 = 'col3', value6 = 'col4', TYPE = 'combo')
My code is incorrect since error happened. It seems that I need to pass all parameters in it.
However, even I passed all parameters in it. No error but no output.
Any suggestion?
could you explain, why you don't just create subclasses for the types? Wouldn't that be more straight-forward?
1.) One way would be to make the subclasses visible to the user and if you don't like this,
2.) you could just create a kind of interface class (eg AP that hides the class that is used behind the scenes and for example instanciates as soon as the type is set.
3.) you can work as you began, but then I guess you would have to make the methods visible to the user, because I guess the way you implemented it, the functions are only visible in the init method (maybe your indentaion is not quite correct). For example if your if statements are executed by the init method, then you could assign the methods to instance variables like self.ComboChart= ComboChart to be able to call the method from outside. But imho that would not be very pythonic and a bit more hacky/less object oriented.
So I'd suggest 1.) and if that is not possible for some reason, then I'd go for solution 2. Both solutions also allow you to form a clean class structure and reuse code that way, while you are still able to build your simplified interface class if you like.
An example (pseudo code) for method 1 would look like below. Please note, that I haven't tested it, it is only meant to give you an idea, about splitting logic in an object oriented way. I didn't check your whole solution and so I don't know for example, if you always group your data in the same way. I'd proabably also separate the presentation logic from the data logic. That would especially be a good idea if you plan to display the same data in more ways, because with the current logic, you would reread the csv file and reporcess the data each time you want another represenatiation. So not to make it more complicated while I just want to explain the basic principle I ignored this and gave an example for a base class "Chart" and a subclass "ComboChart". The "ComboChart" class knows how to read/group the data, because it inherits the methods from "Chart", so you only have to implement it once and thus if you find a bug or want to enhance it later, you only need to do it in one place. The draw_chart method then only needs to do what's different according to the chosen representation. A user would have to create the instance of the subclass according the chart type they want to display and call display_chart().
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
class Chart(object):
def __init__(self, dt, date, group, value, value2, value3, value4, value5, value6):
self.dt = dt
self.date = date
self.group= carrier
self.value = value
self.col1 = col1
self.col2 = col2
self.col3 = col3
self.col4 = col4
self.TYPE = TYPE
self.dataset= None
def _read_data_(self)
dataset = pd.read_csv(dt)
dataset['date'] = pd.to_datetime(dataset[self.date])
dataset['yq'] = pd.PeriodIndex(dataset['date'], freq='Q')
dataset['qtr'] = dataset['date'].dt.quarter
dataset = dataset.groupby([carrier, 'yq', 'qtr'])[value].sum().reset_index()
dataset['total.YQGR'] = dataset[value] / dataset.groupby(['qtr', carrier])[value].transform('shift') - 1
dataset = dataset[np.isfinite(dataset['total.YQGR'])]
dataset['total.R'] = dataset[value] / dataset.groupby(group)[value].transform('first')
dataset.yq = dataset.yq.astype(str)
self.dataset= dataset
return dataset
def get_data(self):
if self.dataset is None:
self._read_data_()
return self.dataset
def group_data(self):
dataset= self.get_data()
dataset = dataset.groupby([carrier, 'yq', 'qtr'])[value].sum().reset_index()
dataset['total.YQGR'] = dataset[value] / dataset.groupby(['qtr', carrier])[value].transform('shift') - 1
dataset = dataset[np.isfinite(dataset['total.YQGR'])]
dataset['total.R'] = dataset[value] / dataset.groupby(group)[value].transform('first')
dataset.yq = dataset.yq.astype(str)
return dataset
def draw_chart(self):
pass
class ComboChart(Chart):
def draw_chart(self):
dataset = self.group_data()
fig, ax1 = plt.subplots(figsize=(12,7))
ax2=ax1.twinx()
sns.lineplot(x='yq',y='total.R', data=dataset, hue=group, ax=ax1, legend = None, palette = ('navy', 'r'), linewidth=5)
ax1.set_xticklabels(ax1.get_xticks(), rotation=45, fontsize=15, weight = 'heavy')
ax1.set_xlabel("", fontsize=15)
ax1.set_ylabel("")
ax1.set_ylim((0, max(dataset['total.R']) + 0.05))
sns.barplot(x='yq', y='total.YQGR', data=dataset, hue=group, ax=ax2, palette = ('navy', 'r'))
ax2.set_yticklabels(['{:.1f}%'.format(a*100) for a in ax2.get_yticks()])
ax2.set_ylabel("")
ax2.set_ylim((min(dataset['total.YQGR']) - 0.01, max(dataset['total.YQGR']) + 0.2))
ax2.get_legend().remove()
ax2.legend(bbox_to_anchor=(-0.35, 0.5), loc=2, borderaxespad=0., fontsize = 'xx-large')
for groups in ax2.containers:
for bar in groups:
if bar.get_height() >= 0:
ax2.text(
bar.get_xy()[0] + bar.get_width()/1.5,
bar.get_height() + 0.003,
'{:.1f}%'.format(round(100*bar.get_height(),2)),
color='black',
horizontalalignment='center',
fontsize = 12, weight = 'heavy'
)
else:
ax2.text(
bar.get_xy()[0] + bar.get_width()/1.5,
bar.get_height() - 0.008,
'{:.1f}%'.format(round(100*bar.get_height(),2)),
color='black',
horizontalalignment='center',
fontsize = 12, weight = 'heavy'
)
ax1.yaxis.set_visible(False)
ax2.yaxis.set_visible(False)
ax2.xaxis.set_visible(False)
ax1.spines["right"].set_visible(False)
ax1.spines["left"].set_visible(False)
ax1.spines["top"].set_visible(False)
ax1.spines["bottom"].set_visible(False)
ax2.spines["right"].set_visible(False)
ax2.spines["left"].set_visible(False)
ax2.spines["top"].set_visible(False)
ax2.spines["bottom"].set_visible(False)
ax1.set_title(TYPE, fontsize=20)
plt.show()
fig.savefig(TYPE, bbox_inches='tight', dpi=600)
The second method (with the interface class) would just look the same, only that you have a forth class that is known to the user and knows how to call the real implementation. Like this:
class YourInterface:
def __init__(self, your_arguments, TYPE):
if TYPE == __ 'ComboChart':
self.client= ComboChart(your_arguments)
elif TYPE == ....
def display_chart(self):
self.client.display_chart()
But it's a pretty boring class, isnt't it?
I'd only do this if your class hierarchy is very technical and could change over time if you want to avoid that the users of your library build up dependencies on the real class hierarchy that would probably be broken as soon as you change your hierarchy. For most cases I guess, class hierarchies stay relatively stable, so you don't need such an extra level of abstraction created by an interface class.
EDIT:
THe issue was solved by using a loop
for i, x in enumerate(plt.cm.jet(np.linspace(0,1,N))):
plt.gca().get_zticklabels()[i].set_color(x)
where plt.cm.jet(np.linspace(0,1,N)) is used color palette
How can I match the Z ticklabels' color to the scatterpoints color?
I made a custom 3d Axis by merging these two answers together:
How to color a specific gridline/tickline in 3D Matplotlib Scatter Plot figure??
for the grid line color
and
https://stackoverflow.com/a/17927118/8881965 for the override _axinfo attribute
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.axis3d import Axis
import matplotlib.pyplot as plt
import matplotlib.projections as proj
from matplotlib.colors import colorConverter
custom_AXINFO = {
'x': {'i': 0, 'tickdir': 1, 'juggled': (1, 0, 2), #empty plane
'color': (0, 0, 0)},
'y': {'i': 1, 'tickdir': 0, 'juggled': (0, 1, 2), #date plane
'color': (0.756, 0.145, 0.184)},
'z': {'i': 2, 'tickdir': 0, 'juggled': (0, 2, 1),
'color': (0.835, 0.549, 0.164)},} #bottom plane
class axis3d_custom(Axis):
def __init__(self, adir, v_intervalx, d_intervalx, axes, *args, **kwargs):
Axis.__init__(self, adir, v_intervalx, d_intervalx, axes, *args, **kwargs)
self.gridline_colors = []
def set_gridline_color(self, *gridline_info):
'''Gridline_info is a tuple containing the value of the gridline to change
and the color to change it to. A list of tuples may be used with the * operator.'''
self.gridline_colors.extend(gridline_info)
def draw(self, renderer):
# filter locations here so that no extra grid lines are drawn
Axis.draw(self, renderer)
which_gridlines = []
if self.gridline_colors:
locmin, locmax = self.get_view_interval()
if locmin > locmax:
locmin, locmax = locmax, locmin
# Rudimentary clipping
majorLocs = [loc for loc in self.major.locator() if
locmin <= loc <= locmax]
for i, val in enumerate(majorLocs):
for colored_val, color in self.gridline_colors:
if val == colored_val:
which_gridlines.append((i, color))
colors = self.gridlines.get_colors()
for val, color in which_gridlines:
colors[val] = colorConverter.to_rgba(color)
self.gridlines.set_color(colors)
self.gridlines.draw(renderer, project=True)
class XAxis(axis3d_custom):
_AXINFO = custom_AXINFO
def get_data_interval(self):
'return the Interval instance for this axis data limits'
return self.axes.xy_dataLim.intervalx
class YAxis(axis3d_custom):
_AXINFO = custom_AXINFO
def get_data_interval(self):
'return the Interval instance for this axis data limits'
return self.axes.xy_dataLim.intervaly
class ZAxis(axis3d_custom):
_AXINFO = custom_AXINFO
def get_data_interval(self):
'return the Interval instance for this axis data limits'
return self.axes.zz_dataLim.intervalx
class Axes3D_custom(Axes3D):
"""
3D axes object.
"""
name = '3d_custom'
def _init_axis(self):
'''Init 3D axes; overrides creation of regular X/Y axes'''
self.w_xaxis = XAxis('x', self.xy_viewLim.intervalx,
self.xy_dataLim.intervalx, self)
self.xaxis = self.w_xaxis
self.w_yaxis = YAxis('y', self.xy_viewLim.intervaly,
self.xy_dataLim.intervaly, self)
self.yaxis = self.w_yaxis
self.w_zaxis = ZAxis('z', self.zz_viewLim.intervalx,
self.zz_dataLim.intervalx, self)
self.zaxis = self.w_zaxis
for ax in self.xaxis, self.yaxis, self.zaxis:
ax.init3d()
proj.projection_registry.register(Axes3D_custom)
bx.scatter(xs,ys,zs, zdir=zs,c=plt.cm.jet(np.linspace(0,1,N))) #scatter 3d graph
this is how the graph looks like
Thanks!
The issue was solved by using a loop
for i, x in enumerate(plt.cm.jet(np.linspace(0,1,N))):
plt.gca().get_zticklabels()[i].set_color(x)
where plt.cm.jet(np.linspace(0,1,N)) is the used color palette
I'd like to create a barplot in matplotlib:
fig, ax = plt.subplots()
oldbar = ax.bar(x=ind, height=y, width=width)
I'd then like to pickle this barplot to file (either the dictionary or the axes - I'm not sure which is correct):
pickle.dump(oldbar, file('oldbar.pkl', 'w'))
I'd then like to reload this file, and then plot the old bar onto alongside a new bar plot, so I can compare them on a single axes:
fig, ax = plt.subplots()
newbar = ax.bar(x=ind, height=y, width=width)
oldbar = pickle.load(file('oldbar.pkl'))
# I realise the line below doesn't work
ax.bar(oldbar)
plt.show()
Ideally, I'd then like to present them as below. Any suggestions of how I might go about this?
You would pickle the figure instead the artists in it.
import matplotlib.pyplot as plt
import numpy as np
import pickle
ind = np.linspace(1,5,5)
y = np.linspace(9,1,5)
width = 0.3
fig, ax = plt.subplots()
ax.bar(x=ind, height=y, width=width)
ax.set_xlabel("x label")
pickle.dump(fig, file('oldbar.pkl', 'w'))
plt.close("all")
ind2 = np.linspace(1,5,5)
y2 = np.linspace(8,2,5)
width2 = 0.3
fig2 = pickle.load(file('oldbar.pkl'))
ax2 = plt.gca()
ax2.bar(x=ind2+width, height=y2, width=width2, color="C1")
plt.show()
However pickling the data itself may make more sense here.
import matplotlib.pyplot as plt
import numpy as np
import pickle
ind = np.linspace(1,5,5)
y = np.linspace(9,1,5)
width = 0.3
dic = {"ind":ind, "y":y, "width":width}
pickle.dump(dic, file('olddata.pkl', 'w'))
### new data
ind2 = np.linspace(1,5,5)
y2 = np.linspace(8,2,5)
width2 = 0.3
olddic = pickle.load(file('olddata.pkl'))
fig, ax = plt.subplots()
ax.bar(x=olddic["ind"], height=olddic["y"], width=olddic["width"])
ax.bar(x=ind2+olddic["width"], height=y2, width=width2)
ax.set_xlabel("x label")
plt.show()
Maybe this will help:
import pickle as pkl
import matplotlib.pyplot as plt
import numpy as np
class Data_set(object):
def __init__(self, x=[], y=[], name='data', pklfile=None,
figure=None, axes=None):
"""
"""
if pklfile is None:
self.x = np.asarray(x)
self.y = np.asarray(y)
self.name = str(name)
else:
self.unpickle(pklfile)
self.fig = figure
self.ax = axes
self.bar = None
def plot(self, width=0, offset=0, figure=None, axes=None):
if self.fig is None:
if figure is None:
self.fig = plt.figure()
self.ax = self.fig.subplots(1, 1)
else:
self.fig = figure
if axes is None:
self.ax = self.fig.subplots(1, 1)
else:
self.ax = axes
# maybe there's no need to keep track of self.fig, .ax and .bar,
# but just in case...
if figure is not None:
fig_to_use = figure
if axes is not None:
ax_to_use = axes
else:
ax_to_use = fig_to_use.subplots(1, 1)
else:
fig_to_use = self.fig
ax_to_use = self.ax
if not width:
width = (self.x[1]-self.x[0]) / 2.
self.bar = ax_to_use.bar(x=self.x+offset, height=self.y, width=width)
return fig_to_use, ax_to_use, self.bar
def pickle(self, filename='', ext='.pkl'):
if filename == '':
filename = self.name
with open(filename+ext, 'w') as output_file:
pkl.dump((self.name, self.x, self.y), output_file)
def unpickle(self, filename='', ext='.pkl'):
if filename == '':
filename = self.name
with open(filename + ext, 'r') as input_file:
# the name should really come from the filename, but then the
# above would be confusing?
self.name, self.x, self.y = pkl.load(input_file)
class Data_set_manager(object):
def __init__(self, datasets={}):
self.datasets = datasets
def add_dataset(self, data_set):
self.datasets[data_set.name] = data_set
def add_dataset_from_file(self, filename, ext='.pkl'):
self.datasets[filename] = Data_set(name=filename)
self.datasets[filename].unpickle(filename=filename, ext=ext)
def compare(self, width=0, offset=0, *args):
self.fig = plt.figure()
self.ax = self.fig.subplots(1, 1)
if len(args) == 0:
args = self.datasets.keys()
args.sort()
n = len(args)
if n == 0:
return None, None
if width == 0:
min_dx = None
for dataset in self.datasets.values():
sorted_x = dataset.x.copy()
sorted_x.sort()
try:
new_min_dx = np.min(dataset.x[1:] - dataset.x[:-1])
except ValueError:
# zero-size array to reduction operation minimum which
# has no identity (empty array)
new_min_dx = None
if new_min_dx < min_dx or min_dx is None:
min_dx = new_min_dx
if min_dx is None:
min_dx = 1.
width = float(min_dx) / (n + 1)
offset = float(min_dx) / (n + 1)
offsets = offset*np.arange(n)
if n % 2 == 0:
offsets -= offsets[n/2] - offset/2.
else:
offsets -= offsets[n/2]
i = 0
for name in args:
self.datasets.get(name, Data_set()).plot(width=width,
offset=offsets[i],
figure=self.fig,
axes=self.ax)
i += 1
self.ax.legend(args)
return self.fig, self.ax
if __name__ == "__main__":
# test saving/loading
name = 'test'
to_pickle = Data_set(x=np.arange(10),
y=np.random.rand(10),
name=name)
to_pickle.pickle()
unpickled = Data_set(pklfile=name)
print unpickled.name == to_pickle.name
# test comparison
blorg = Data_set_manager({})
x_step = 1.
n_bars = 4 # also try an odd number
for n in range(n_bars):
blorg.add_dataset(Data_set(x=x_step * np.arange(n_bars),
y=np.random.rand(n_bars),
name='teste' + str(n)))
fig, ax = blorg.compare()
fig.show()
It should work with both even and odd number of bars:
And as long as you keep a record of the names you've used (tip:look in the folder where you are saving them) you can reload the data and compare it with the new one.
More checks could be made (to make sure the file exists, that the x axis is something that can be subtracted before trying to do so, etc.), and it could also use some documentation and proper testing - but this should do in a hurry.
Matplotlib axes have Major and Minor ticks. How do I add a third level of tick below Minor?
For example
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.ticker
t = np.arange(0.0, 100.0, 0.1)
s = np.sin(0.1*np.pi*t)*np.exp(-t*0.01)
fig, ax = plt.subplots()
plt.plot(t, s)
ax1 = ax.twiny()
ax1.plot(t, s)
ax1.xaxis.set_ticks_position('bottom')
majors = np.linspace(0, 100, 6)
minors = np.linspace(0, 100, 11)
thirds = np.linspace(0, 100, 101)
ax.xaxis.set_major_locator(matplotlib.ticker.FixedLocator(majors))
ax.xaxis.set_minor_locator(matplotlib.ticker.FixedLocator(minors))
ax1.xaxis.set_major_locator(matplotlib.ticker.FixedLocator([]))
ax1.xaxis.set_minor_locator(matplotlib.ticker.FixedLocator(thirds))
ax1.tick_params(which='minor', length=2)
ax.tick_params(which='minor', length=4)
ax.tick_params(which='major', length=6)
ax.grid(which='both',axis='x',linestyle='--')
plt.axhline(color='gray')
plt.show()
produces the effect I want using twinned x-axes.
Is there a better way?
As I stated that you can achieve what you want by deriving from some key classes, I decided to do so (but as I said, it's probably not worth the effort). Anyway, here is what I've got:
from matplotlib import pyplot as plt
from matplotlib import axes as maxes
from matplotlib import axis as maxis
import matplotlib.ticker as mticker
import matplotlib.cbook as cbook
from matplotlib.projections import register_projection
from matplotlib import ticker
import numpy as np
class SubMinorXAxis(maxis.XAxis):
def __init__(self,*args,**kwargs):
self.subminor = maxis.Ticker()
self.subminorTicks = []
self._subminor_tick_kw = dict()
super(SubMinorXAxis,self).__init__(*args,**kwargs)
def reset_ticks(self):
cbook.popall(self.subminorTicks)
##self.subminorTicks.extend([self._get_tick(major=False)])
self.subminorTicks.extend([maxis.XTick(self.axes, 0, '', major=False, **self._subminor_tick_kw)])
self._lastNumSubminorTicks = 1
super(SubMinorXAxis,self).reset_ticks()
def set_subminor_locator(self, locator):
"""
Set the locator of the subminor ticker
ACCEPTS: a :class:`~matplotlib.ticker.Locator` instance
"""
self.isDefault_minloc = False
self.subminor.locator = locator
locator.set_axis(self)
self.stale = True
def set_subminor_formatter(self, formatter):
"""
Set the formatter of the subminor ticker
ACCEPTS: A :class:`~matplotlib.ticker.Formatter` instance
"""
self.isDefault_minfmt = False
self.subminor.formatter = formatter
formatter.set_axis(self)
self.stale = True
def get_subminor_ticks(self, numticks=None):
'get the subminor tick instances; grow as necessary'
if numticks is None:
numticks = len(self.get_subminor_locator()())
if len(self.subminorTicks) < numticks:
# update the new tick label properties from the old
for i in range(numticks - len(self.subminorTicks)):
##tick = self._get_tick(major=False)
tick = maxis.XTick(self.axes, 0, '', major=False, **self._subminor_tick_kw)
self.subminorTicks.append(tick)
if self._lastNumSubminorTicks < numticks:
protoTick = self.subminorTicks[0]
for i in range(self._lastNumSubminorTicks, len(self.subminorTicks)):
tick = self.subminorTicks[i]
tick.gridOn = False
self._copy_tick_props(protoTick, tick)
self._lastNumSubminorTicks = numticks
ticks = self.subminorTicks[:numticks]
return ticks
def set_tick_params(self, which='major', reset=False, **kwargs):
if which == 'subminor':
kwtrans = self._translate_tick_kw(kwargs, to_init_kw=True)
if reset:
self.reset_ticks()
self._subminor_tick_kw.clear()
self._subminor_tick_kw.update(kwtrans)
for tick in self.subminorTicks:
tick._apply_params(**self._subminor_tick_kw)
else:
super(SubMinorXAxis, self).set_tick_params(which=which, reset=reset, **kwargs)
def cla(self):
'clear the current axis'
self.set_subminor_locator(mticker.NullLocator())
self.set_subminor_formatter(mticker.NullFormatter())
super(SubMinorXAxis,self).cla()
def iter_ticks(self):
"""
Iterate through all of the major and minor ticks.
...and through the subminors
"""
majorLocs = self.major.locator()
majorTicks = self.get_major_ticks(len(majorLocs))
self.major.formatter.set_locs(majorLocs)
majorLabels = [self.major.formatter(val, i)
for i, val in enumerate(majorLocs)]
minorLocs = self.minor.locator()
minorTicks = self.get_minor_ticks(len(minorLocs))
self.minor.formatter.set_locs(minorLocs)
minorLabels = [self.minor.formatter(val, i)
for i, val in enumerate(minorLocs)]
subminorLocs = self.subminor.locator()
subminorTicks = self.get_subminor_ticks(len(subminorLocs))
self.subminor.formatter.set_locs(subminorLocs)
subminorLabels = [self.subminor.formatter(val, i)
for i, val in enumerate(subminorLocs)]
major_minor = [
(majorTicks, majorLocs, majorLabels),
(minorTicks, minorLocs, minorLabels),
(subminorTicks, subminorLocs, subminorLabels),
]
for group in major_minor:
for tick in zip(*group):
yield tick
class SubMinorAxes(maxes.Axes):
name = 'subminor'
def _init_axis(self):
self.xaxis = SubMinorXAxis(self)
self.spines['top'].register_axis(self.xaxis)
self.spines['bottom'].register_axis(self.xaxis)
self.yaxis = maxis.YAxis(self)
self.spines['left'].register_axis(self.yaxis)
self.spines['right'].register_axis(self.yaxis)
register_projection(SubMinorAxes)
if __name__ == '__main__':
fig = plt.figure()
ax = fig.add_subplot(111,projection = 'subminor')
t = np.arange(0.0, 100.0, 0.1)
s = np.sin(0.1*np.pi*t)*np.exp(-t*0.01)
majors = np.linspace(0, 100, 6)
minors = np.linspace(0, 100, 11)
thirds = np.linspace(0, 100, 101)
ax.plot(t, s)
ax.xaxis.set_ticks_position('bottom')
ax.xaxis.set_major_locator(ticker.FixedLocator(majors))
ax.xaxis.set_minor_locator(ticker.FixedLocator(minors))
ax.xaxis.set_subminor_locator(ticker.FixedLocator(thirds))
##some things in set_tick_params are not being set correctly
##by default. For instance 'top=False' must be stated
##explicitly
ax.tick_params(which='subminor', length=2, top=False)
ax.tick_params(which='minor', length=4)
ax.tick_params(which='major', length=6)
ax.grid(which='both',axis='x',linestyle='--')
plt.show()
It's not perfect, but for the use case you provided it's working fine. I drew some ideas from this matplotlib example and by going through the source codes directly. The result looks like this:
I tested the code on both Python 2.7 and Python 3.5.
EDIT:
I noticed that the subminor gridlines would always be drawn if the grid is turned on (while I had intended for it not to be drawn at all). I rectified this in the code above, i.e. the subminor ticks should never produce grid lines. If gridlines should be implemented properly, some more work will be needed.