I'm trying to plot a subplot but there are two problems.
#1 The panel_ratio setting (6,1) is unnoticed.
#2 The y axis of the top panel juts down and overlaps the y axis of the bottom panel, so that the bars are trimmed in the top panel
What is wrong with the code?
import pandas as pd
import numpy as np
from matplotlib.animation import FuncAnimation
import mplfinance as mpf
times = pd.date_range(start='2022-01-01', periods=50, freq='ms')
def get_rsi(df, rsi_period):
chg = df['close'].diff(1)
gain = chg.mask(chg<0,0)
loss = chg.mask(chg>0,0)
avg_gain = gain.ewm(com=rsi_period-1, min_periods=rsi_period).mean()
avg_loss = loss.ewm(com=rsi_period-1, min_periods=rsi_period).mean()
rs = abs(avg_gain/avg_loss)
rsi = 100 - (100/(1+rs))
return rsi
df = pd.DataFrame(np.random.randint(3000, 3100, (50, 1)), columns=['open'])
df['high'] = df.open+5
df['low'] = df.open-2
df['close'] = df.open
df['rsi14'] = get_rsi(df, 14)
df.set_index(times, inplace=True)
lows_peaks = df.low.nsmallest(5).index
fig = mpf.figure(style="charles",figsize=(7,8))
ax1 = fig.add_subplot(1,1,1)
ax2 = fig.add_subplot(2,1,2)
ap0 = [ mpf.make_addplot(df['rsi14'],color='g', ax=ax2, ylim=(10,90), panel=1) ]
mpf.plot(df, ax=ax1, ylim=(2999,3104), addplot=ap0, panel_ratios=(6,1))
mpf.show()
In this case, it is easier to use a panel instead of an external axis. I tried your code and could not improve it. For a detailed reference on panels, see here.
# fig = mpf.figure(style="charles", figsize=(7,8))
# ax1 = fig.add_subplot(1,1,1)
# ax2 = fig.add_subplot(2,1,2)
ap0 = mpf.make_addplot(df[['rsi14']], color='g', ylim=(10,90), panel=1)
mpf.plot(df[['open','high', 'low','close']], addplot=ap0, ylim=(2999,3104), panel_ratios=(6,1), style='charles')
mpf.show()
Related
I'm trying to remake an existing animated line graph I made where each line has a uniquely scaled y-axis - one on the left, one on the right. The graph is comparing the value of two cryptocurrencies that have vastly different sizes (eth/btc), which is why I need multiple scales to actually see changes.
My data has been formatted in a pd df (numbers here are random):
Date ETH Price BTC Price
0 2020-10-30 00:00:00 0.155705 1331.878496
1 2020-10-31 00:00:00 0.260152 1337.174272
.. ... ... ...
290 2021-08-15 16:42:09 0.141994 2846.719819
[291 rows x 3 columns]
And code is roughly:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as ani
color = ['cyan', 'orange', 'red']
fig = plt.figure()
plt.xticks(rotation=45, ha="right", rotation_mode="anchor")
plt.subplots_adjust(bottom = 0.2, top = 0.9)
plt.ylabel('Coin Value (USD)')
plt.xlabel('Date')
def buildChart(i=int):
df1 = df.set_index('Date', drop=True)
plt.legend(["ETH Price", "BTC Price"])
p = plt.plot(df1[:i].index, df1[:i].values)
for i in range(0,2):
p[i].set_color(color[i])
animator = ani.FuncAnimation(fig, buildChart, interval = 10)
plt.show()
Resulting Animation
I tried to create a second axis with a twin x to the first axis.
color = ['cyan', 'orange', 'blue']
fig, ax1 = plt.subplots() #Changes over here
plt.xticks(rotation=45, ha="right", rotation_mode="anchor")
plt.subplots_adjust(bottom = 0.2, top = 0.9)
plt.ylabel('Coin Value (USD)')
plt.xlabel('Date')
def buildChart(i=int):
df1 = df.set_index('Date', drop=True)
plt.legend(["ETH Price", "Bitcoin Price"])
data1 = df1.iloc[:i, 0:1] # Changes over here
# ------------- More Changes Start
ax2 = ax1.twinx()
ax2.set_ylabel('Cost of Coin (USD)')
data2 = df1.iloc[:i, 1:2]
ax2.plot(df1[:i].index, data2)
ax2.tick_params(axis='y')
# -------------- More Changes End
p = plt.plot(df1[:i].index, data1)
for i in range(0,1):
p[i].set_color(color[i])
import matplotlib.animation as ani
animator = ani.FuncAnimation(fig, buildChart, interval = 10)
plt.show()
Resulting Animation After Changes
Current issues:
X-Axis start at ~1999 rather than late 2020
---- Causes all changes on the y-axis to be a nearly vertical line
Left Y-Axis label is on a scale of 0-1?
Right y-axis labels are recurring, overlapping, moving.
I believe my approach to making a second scale must have been wrong to get this many errors, but this seems like the way to do it.
I re-structured your code in order to easily set up a secondary axis animation.
Here the code of the animation with a single y axis:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
df = pd.DataFrame({'date': pd.date_range(start = '2020-01-01', end = '2020-04-01', freq = 'D')})
df['ETH'] = 2*df.index + 300 + 100*np.random.randn(len(df))
df['BTC'] = 5*df.index + 13000 + 200*np.random.randn(len(df))
def update(i):
ax.cla()
ax.plot(df.loc[:i, 'date'], df.loc[:i, 'ETH'], label = 'ETH Price', color = 'red')
ax.plot(df.loc[:i, 'date'], df.loc[:i, 'BTC'], label = 'BTC Price', color = 'blue')
ax.legend(frameon = True, loc = 'upper left', bbox_to_anchor = (1.15, 1))
ax.set_ylim(0.9*min(df['ETH'].min(), df['BTC'].min()), 1.1*max(df['ETH'].max(), df['BTC'].max()))
ax.tick_params(axis = 'x', which = 'both', top = False)
ax.tick_params(axis = 'y', which = 'both', right = False)
plt.setp(ax.xaxis.get_majorticklabels(), rotation = 45)
ax.set_xlabel('Date')
ax.set_ylabel('ETH Coin Value (USD)')
plt.tight_layout()
fig, ax = plt.subplots(figsize = (6, 4))
ani = FuncAnimation(fig = fig, func = update, frames = len(df), interval = 100)
plt.show()
Starting from the code above, you should twin the axis out of the update function: if you keep ax.twinx() inside the function, this operation will be repeated in each iteration and you will get a new axis each time.
Below the code for an animation with a secondary axis:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
df = pd.DataFrame({'date': pd.date_range(start = '2020-01-01', end = '2020-04-01', freq = 'D')})
df['ETH'] = 2*df.index + 300 + 100*np.random.randn(len(df))
df['BTC'] = 5*df.index + 13000 + 200*np.random.randn(len(df))
def update(i):
ax1.cla()
ax2.cla()
line1 = ax1.plot(df.loc[:i, 'date'], df.loc[:i, 'ETH'], label = 'ETH Price', color = 'red')
line2 = ax2.plot(df.loc[:i, 'date'], df.loc[:i, 'BTC'], label = 'BTC Price', color = 'blue')
lines = line1 + line2
labels = [line.get_label() for line in lines]
ax1.legend(lines, labels, frameon = True, loc = 'upper left', bbox_to_anchor = (1.15, 1))
ax1.set_ylim(0.9*df['ETH'].min(), 1.1*df['ETH'].max())
ax2.set_ylim(0.9*df['BTC'].min(), 1.1*df['BTC'].max())
ax1.tick_params(axis = 'x', which = 'both', top = False)
ax1.tick_params(axis = 'y', which = 'both', right = False, colors = 'red')
ax2.tick_params(axis = 'y', which = 'both', right = True, labelright = True, left = False, labelleft = False, colors = 'blue')
plt.setp(ax1.xaxis.get_majorticklabels(), rotation = 45)
ax1.set_xlabel('Date')
ax1.set_ylabel('ETH Coin Value (USD)')
ax2.set_ylabel('BTC Coin Value (USD)')
ax1.yaxis.label.set_color('red')
ax2.yaxis.label.set_color('blue')
ax2.spines['left'].set_color('red')
ax2.spines['right'].set_color('blue')
plt.tight_layout()
fig, ax1 = plt.subplots(figsize = (6, 4))
ax2 = ax1.twinx()
ani = FuncAnimation(fig = fig, func = update, frames = len(df), interval = 100)
plt.show()
I'm working with a large model ensemble. I'm calculating KDE probability distribution functions with pandas - at least for now it is the most feasible option since it automatically determines the (optimal?) bandwith. I'm comparing observations with a subset of models. Basically, I want the same observed pdf in 12 different sub panels so I can compare models and pdf better. This is my minimal example
import numpy as np
import pandas as pd
import xarray as xr
fig = plt.figure(0,figsize=(8.2,10.2))
fig.subplots_adjust(hspace=0.2)
fig.subplots_adjust(wspace=0.36)
fig.subplots_adjust(right=0.94)
fig.subplots_adjust(left=0.13)
fig.subplots_adjust(bottom=0.1)
fig.subplots_adjust(top=0.95)
plt.rcParams['text.usetex'] = False
plt.rcParams['axes.labelsize'] = 12
plt.rcParams['font.size'] = 11
plt.rcParams['legend.fontsize'] = 12
plt.rcParams['xtick.labelsize'] = 11
plt.rcParams['ytick.labelsize'] = 11
ax1 = fig.add_subplot(6,2,1)
ax2 = fig.add_subplot(6,2,2)
ax3 = fig.add_subplot(6,2,3)
ax4 = fig.add_subplot(6,2,4)
ax5 = fig.add_subplot(6,2,5)
ax6 = fig.add_subplot(6,2,6)
ax7 = fig.add_subplot(6,2,7)
ax8 = fig.add_subplot(6,2,8)
ax9 = fig.add_subplot(6,2,9)
ax10 = fig.add_subplot(6,2,10)
ax11 = fig.add_subplot(6,2,11)
ax12 = fig.add_subplot(6,2,12)
obs = np.array([448.2, 172.0881, 118.9816, 5.797349, 2, 0.7, 0.7, 0.1, 0.7, 14,
41.78181, 94.99255])
df= pd.DataFrame()
df['obs'] = obs
axes = [ax1,ax2,ax3,ax4,ax5,ax6,ax7,ax8,ax9,ax10,ax11,ax12]
for a in axes:
a = df['obs'].plot.kde(ax=a, lw=2.0)
plt.show()
Is there any way I can 'copy/ duplicate' my first subplot - so
ax1 = df['obs'].plot.kde(ax=ax1, lw=2.0)
into the other panels without repeating the calculation? Alternatively can I somehow grab the values calculated? The reason why I don't want to repeat the computation is because it takes a lot of computing time with the original data.
Alternatively can I somehow grab the values calculated?
You can extract the line with Axes.get_lines() and its values with Line2D.get_data():
# plot KDE onto axes[0] (once)
df['obs'].plot.kde(ax=axes[0], lw=2.0)
# extract x and y from axes[0]
x, y = axes[0].get_lines()[0].get_data()
# plot x and y on remaining axes[1:]
for a in axes[1:]:
a.plot(x, y)
I'm using Matplotlib and Seaborn to plot four bar graphs with one shared legend. However, I can't make the legend to be horizontal and at the lower center. I tried to set the numbers in this line:
ax.legend(bbox_to_anchor=(0.99, -0.15),
loc=1,
fontsize=13,
# ncol=2
)
but if the legend goes to the middle, then the distance between the two subplot columns would increase as well making it not good.
Here is my code:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import pdb
import pyautogui
import multiprocessing
from time import sleep
from matplotlib import patches as mpatches
def convert_to_grouped_bar_chart_format(data,
col_1_name, col_2_name, col_3_name):
"""
Parameters
----------
data: Pandas dataframe. Format:
Method Class1 Class2 Class3
0 Method_1 0.1 0.2 0.3
1 Method_2 0.6 0.5 0.4
Returns
-------
data_grouped: Pandas dataframe.
"""
cls_list = data.columns[1:].tolist()
col_1 = []
col_2 = []
col_3 = []
(num_of_rows, num_of_cols) = data.shape
for row_idx in range(num_of_rows):
for cls_idx, cls in enumerate(cls_list):
col_1.append(data.iloc[row_idx, 0])
col_2.append(cls)
col_3.append(data.iloc[row_idx, cls_idx+1])
pass
pass
data_grouped_dict = {
col_1_name: col_1,
col_2_name: col_2,
col_3_name: col_3
}
data_grouped = pd.DataFrame(data_grouped_dict, columns = [col_1_name, col_2_name, col_3_name])
return data_grouped
def draw_four_bar_graph_seaborn():
file_list = [
['Measure1_ED.csv', 'Measure1_ES.csv'],
['Measure2_ED.csv', 'Measure2_ES.csv']
]
n_rows = len(file_list)
n_cols = len(file_list[0])
fig, axes = plt.subplots(n_rows, n_cols)
for idx_row in range(n_rows):
# if idx_row > 0:
# continue
for idx_col in range(n_cols):
file_name = file_list[idx_row][idx_col]
data = pd.read_csv(file_name)
col_1_name = 'Method'
col_2_name = 'Class'
col_3_name = file_name.split('_')[0]
data_type = file_name.split('_')[1][:-4]
ax = axes[idx_row, idx_col]
# ax =axes[idx_col]
data_grouped = convert_to_grouped_bar_chart_format(data,
col_1_name, col_2_name, col_3_name)
splot = sns.barplot(
# ax=axes[idx_row, idx_col],
ax=ax,
x=col_2_name,
y=col_3_name,
hue=col_1_name,
palette="magma",
# palette=my_pal,
# sharey=False,
data=data_grouped)
splot.set_xlabel("",fontsize=1)
splot.set_ylabel(col_3_name,fontsize=13)
splot.tick_params(labelsize=13)
title_subplot = 'Title 1'
ax.set_title(title_subplot, fontsize=13)
if col_3_name == 'Measure1':
ax.set_ylim(0, 1.10)
else:
ax.set_ylim(0, 2.25)
for p1 in splot.patches:
splot.annotate('%.3f' % p1.get_height(),
(p1.get_x() + p1.get_width() / 2., p1.get_height()),
ha = 'center', va = 'center',
size=13,
xytext = (0, 8),
textcoords = 'offset points')
if (idx_row == 1) and (idx_col == 0):
ax.legend(
bbox_to_anchor=(1.2, -0.15),
loc=1,
fontsize=13,
# ncol=2
)
else:
splot.get_legend().remove()
# Change width size
# ax = axes[idx_row, idx_col]
new_value = 0.35
for patch in ax.patches :
current_width = patch.get_width()
diff = current_width - new_value
# we change the bar width
patch.set_width(new_value)
# we recenter the bar
patch.set_x(patch.get_x() + diff * .5)
plt.tight_layout(pad=0)
mng = plt.get_current_fig_manager()
mng.window.state('zoomed') #works fine on Windows!
plt.show()
fig.savefig('out.pdf')
plt.close()
def draw_graph_then_save_and_close_automatically(func=None, args=[]):
coords_close_graph = (1365, 12) # Auto click to close graph
multiprocessing.Process(target=func, args=args).start()
sleep(10)
pyautogui.moveTo(coords_close_graph)
pyautogui.click()
def main():
draw_graph_then_save_and_close_automatically(
func=draw_four_bar_graph_seaborn,
args=[])
if __name__ == '__main__':
main()
Please help me, thank you very much.
Use a figure-legend instead of place on on one of your axes and set the number of columns that the legend should have to the number of legend entries. Here is an example (I did find your's to be minimal enough^^)
import numpy as np
from matplotlib import pyplot as plt
# create random data
y = np.random.randint(0,100,size=(10, 3))
# open a figure with two axes
fig,axs = plt.subplots(1,2)
# plot something in the axes
axs[0].plot(y[:,0])
axs[1].plot(y[:,1:])
# define the name of the
legendEntries = ("a","bcdefg","h")
# set figure legend entries, number of columns, location
fig.legend(legendEntries,ncol=len(legendEntries),loc="upper center")
Here is a doc-example, emphasizing to use the argument ncol to force matplotlib to expand the legend horizontally. And here is a tutorial/example how you can place the legend of an axis outside the region of the axis.
i created a dataframe with random columns and values. now i am trying to interate with an loop over "time" window" (maybe there is a more elegant solution than mine). i try to plot the calculated correlations in a heatmap and then interate furhter and show the next result in the same figure. Like this
https://datasoaring.blogspot.com/2018/07/gdp-correlation-matrix-top-10-economies.html
The current code plot a new figure for each correlation...
Thanks for ideas and help!
Creates Dataframe
import pandas as pd
import numpy as np
%matplotlib inline
import matplotlib.pyplot as plt
import time
import seaborn as sns
sns.set_style('white')
plt.style.use('dark_background')
index = pd.date_range('01/01/2010',periods=num_days, freq='D')
data_KW = pd.DataFrame(np.random.randint(0,250,size=(250, 10)), columns=list('ABCDEFGHIJ'), index=index)
data_KW.head()
interate and plot (wrong :))
# Calculate the lenght of the Dataframe
end = 10 #len(data_KW.index)
# is the variable for the rolling window
var_start = 0
var_end = 5
#Set up the matplotlib figure
f, ax = plt.subplots(figsize=(5, 5))
while var_end <= end:
window = data_KW.iloc[var_start : var_end]
# Compute the correlation matrix
corr = window.corr()
# Generate a mask for the upper triangle
mask = np.zeros_like(corr, dtype=np.bool)
mask[np.triu_indices_from(mask)] = True
# Generate a custom diverging colormap
cmap = sns.diverging_palette(220, 10, as_cmap=True)
# Draw the heatmap with the mask and correct aspect ratio
sns.heatmap(corr, mask=mask, cmap=cmap, vmax=1, center=0,
square=True, linewidths=.5, cbar_kws={"shrink": .5})
#plt.pause(3)
plt.show()
time.sleep(2)
#time.sleep(5)
var_start = var_start + 1
var_end = var_end + 1
print(var_start)
My current Pandas / python plot looks like this:
What I like to have:
I want to get rid of the 1e7 and 1e9 on both y-axes. The values of the two time series are in the millions and billions, so a delimiter for the number would be a plus for readability.
I like to have a (light) grid in the background and at least normal lines on the axes.
I like to have a monthly scaling, not every 6 months on the x-axis
How can I add the legend below?
The current code is (transactions 1 and 2 are time series of trading volumes):
ax = data.transactions1.plot(figsize=(12, 3.5))
data.transactions2.plot(secondary_y=True)
The following code :
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.ticker as ticker
import datetime
from matplotlib.ticker import ScalarFormatter
base = datetime.datetime.today()
numdays = 365
date_list = [base - datetime.timedelta(days=x) for x in range(0, numdays)]
x = np.arange(0, numdays, 1)
values1 = 0.05 * x**2*1e9
values2 = -1*values1*1e7
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
lns1 = ax1.plot(date_list, values1, 'g-', label='Foo')
lns2 = ax2.plot(date_list, values2, 'b-', label='Bar')
# We set the date format
dareFmt = mdates.DateFormatter('%b %Y')
# We then apply the format
ax1.xaxis.set_major_formatter(dareFmt)
ax1.set_xlabel('Dates')
#used to give the inclination
fig.autofmt_xdate()
# Dsiplay the grid
ax1.grid(True)
# To get rid of the 1eX on top i divide the values of the y axis by the exponent value
y_values = ax1.get_yticks().tolist()
y_values = [x / 1e12 for x in y_values]
ax1.set_yticklabels(y_values)
ax1.set_ylabel('10e12')
y_values = ax2.get_yticks().tolist()
y_values = [x / 1e19 for x in y_values]
ax2.set_yticklabels(y_values)
ax2.set_ylabel('10e19')
lns = lns1 + lns2
labs = [l.get_label() for l in lns]
ax1.legend(lns, labs,bbox_to_anchor=(0., -0.25, 1., .102), loc=3,
ncol=2, mode="expand", borderaxespad=0.)
plt.show()
gives you :