In this post: Interactive Stock Chart, step by step animation with keyboard arrows, with Matplolib, I wrote a code, in which the user Zephyr brilliantly fixed, that interactively simulate a stock using keyboard arrows.
It turned out that I found a way of doing the same thing in Jupyter, using the module ipywidgets. The code works, but unfortunately the same chart is plotted twice. I have no idea why this is happening. Can someone help? I just want to show one plot (notice that the second plot does not move as I use the slider).
Here is the code:
%matplotlib inline
from ipywidgets import interactive
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
df = pd.read_csv('all_stocks_5yr.csv')
df_apple = df[df['Name'] == 'AAPL'].copy()
df_apple['date'] = pd.to_datetime(df_apple['date'])
df_apple.reset_index(inplace = True)
bars_to_display = 60
step = widgets.IntSlider(value=0, min=0, max=len(df_apple)-bars_to_display)
val_array = []
for idx, val in df_apple.iterrows():
val_array.append(val)
x = np.arange(0, len(df_apple))
fig, (ax, ax2) = plt.subplots(2, figsize = (12, 8), gridspec_kw = {'height_ratios': [4, 1]}, sharex = True)
def f(step):
ax.cla()
ax2.cla()
for i in range(step, bars_to_display + step):
color = '#2CA453'
if val_array[i]['open'] > val_array[i]['close']: color = '#F04730'
ax.plot([x[i], x[i]], [val_array[i]['low'], val_array[i]['high']], color = color)
ax.plot([x[i], x[i] - 0.1], [val_array[i]['open'], val_array[i]['open']], color = color)
ax.plot([x[i], x[i] + 0.1], [val_array[i]['close'], val_array[i]['close']], color = color)
ax2.bar(x[i], val_array[i]['volume'], color = 'lightgrey')
display(fig)
display(step)
out = widgets.interactive_output(f, {'step': step})
display(out)
The line:
fig, (ax, ax2) = plt.subplots(2, figsize = (12, 8), gridspec_kw = {'height_ratios': [4, 1]}, sharex = True)
draws the first figure. Just add plt.close() after that.
Complete Code
from IPython.display import display
from ipywidgets import interactive, widgets
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
%matplotlib inline
df = pd.read_csv('all_stocks_5yr.csv')
df_apple = df[df['Name'] == 'AAPL'].copy()
df_apple['date'] = pd.to_datetime(df_apple['date'])
df_apple.reset_index(inplace = True)
bars_to_display = 60
step = widgets.IntSlider(value = 0, min = 0, max = len(df_apple) - bars_to_display)
val_array = []
for idx, val in df_apple.iterrows():
val_array.append(val)
x = np.arange(0, len(df_apple))
fig, (ax, ax2) = plt.subplots(2, figsize = (12, 8), gridspec_kw = {'height_ratios': [4, 1]}, sharex = True)
plt.close()
def f(step):
ax.cla()
ax2.cla()
for i in range(step, bars_to_display + step):
color = '#2CA453'
if val_array[i]['open'] > val_array[i]['close']: color = '#F04730'
ax.plot([x[i], x[i]], [val_array[i]['low'], val_array[i]['high']], color = color)
ax.plot([x[i], x[i] - 0.1], [val_array[i]['open'], val_array[i]['open']], color = color)
ax.plot([x[i], x[i] + 0.1], [val_array[i]['close'], val_array[i]['close']], color = color)
ax2.bar(x[i], val_array[i]['volume'], color = 'lightgrey')
display(fig)
display(step)
out = widgets.interactive_output(f, {'step': step})
display(out)
Related
Suppose I want to show three simulations by reading three .xlsx files.
Next, I want to design a slider to choose which simulation to show.
If I move the slider to 0, then 0 will be the input to the function "update()". The first simulation will be shown.
The following is the code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.widgets import Slider
import pandas as pd
import ipywidgets as wg
# input files
rm = pd.read_excel("test_3d.xlsx", header = None)
rm1 = pd.read_excel("test_3d1.xlsx", header = None)
rm2 = pd.read_excel("test_3d2.xlsx", header = None)
rec = np.shape(rm)
X = np.arange(1,rec[1]+1,1)
Y = np.arange(1,rec[0]+1,1)
x , y = np.meshgrid(X,Y)
# Set 3D plots
fig = plt.figure()
ax1 = fig.add_axes([0, 0, 1, 0.8], projection = '3d')
# Choose which 3D plots to show
def update(val):
if val == 0:
ax1.cla()
ax1.plot_surface(x, y, rm, cmap = cm.coolwarm, linewidth = 0, antialiased = False)
elif val == 1:
ax1.cla()
ax1.plot_surface(x, y, rm1, cmap = cm.coolwarm, linewidth = 0, antialiased = False)
elif val == 2:
ax1.cla()
ax1.plot_surface(x, y, rm2, cmap = cm.coolwarm, linewidth = 0, antialiased = False)
ax1.set_zlim(-110, -80)
# Design a slider to choose which simulation to show
slider = wg.IntSlider(value=1, min=0, max=2, description='this is slider')
slideroutput = wg.Output()
display(slider, slideroutput)
numberonslider = []
def on_value_change(change):
with slideroutput:
numberonslider.append(change['new'])
print(numberonslider[-1])
ddd = slider.observe(on_value_change, names='value')
update(ddd)
If I move the slider, "ddd" gives you a list of 0, 1 or 2.
However, 3D-simulation does not show up. How to modify the code?
I'm using JupyterLab. I need %matplotlib widget for any kind of interactive matplotlib plot. The below code works fine, but won't work without %matplotlib widget.
warning: %matplotlib widget is not the same as import matplotlib.widget
You didn't provide any sample data, so I just made up some data. Basically the structure of your code was not correct, the if part should be inside the def on_value_change(change):. See the code below:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
import ipywidgets as wg
%matplotlib widget
X = np.arange(5)
Y = np.arange(5)
x, y = np.meshgrid(X, Y)
rm = np.sin(x)
rm1 = np.cos(x)
rm2 = y
fig = plt.figure()
ax1 = fig.add_axes([0, 0, 1, 0.8], projection = '3d')
# Design a slider to choose which simulation to show
slider = wg.IntSlider(value=1, min=0, max=2, description='this is slider')
slideroutput = wg.Output()
display(slider, slideroutput)
ax1.cla()
plt.title(f"This is slider number {str(slider.value)}")
ax1.plot_surface(x, y, rm1, cmap = cm.coolwarm, linewidth = 10, antialiased = False)
numberonslider = []
def on_value_change(change):
with slideroutput:
numberonslider.append(change['new'])
if numberonslider[-1] == 0:
ax1.cla()
plt.title(f"This is slider number {str(slider.value)}")
ax1.plot_surface(x, y, rm, cmap = cm.coolwarm, linewidth = 10, antialiased = False)
elif numberonslider[-1] == 1:
ax1.cla()
plt.title(f"This is slider number {str(slider.value)}")
ax1.plot_surface(x, y, rm1, cmap = cm.coolwarm, linewidth = 10, antialiased = False)
elif numberonslider[-1] == 2:
ax1.cla()
plt.title(f"This is slider number {str(slider.value)}")
ax1.plot_surface(x, y, rm2, cmap = cm.coolwarm, linewidth = 10, antialiased = False)
slider.observe(on_value_change, names='value')
Output:
ask again if my explanation is somehow unclear.
I am aiming to animate a df in the middle of an annotate function. I can get the arrow to animate and the first value of the df to appear but not animate with the updated coordinates. To do this I changed label.set_text to (Number[i+1]) but this just displays the Number in the right place for the first frame. The position doesn't update as the new coordinates aren't called upon. I tried to run this code to update the coordinates but it doesn't display anything?
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
import random
from functools import partial
import pandas as pd
one_sample = partial(random.sample, range(100), 10)
a_data = [one_sample() for _ in range(1000)]
b_data = [one_sample() for _ in range(1000)]
df = pd.DataFrame(np.random.randint(0,100,size=(100, 1)), columns=list('A'))
fig, ax = plt.subplots(figsize = (8,6))
ax.set_xlim(0,100)
ax.set_ylim(0,100)
arrow = ax.annotate('', xy = (a_data[0][0], b_data[0][0]), xytext = (a_data[0][1],b_data[0][1]), arrowprops = {'arrowstyle': "<->", 'color':'black'}, ha = 'center')
Number = df[A']
label = plt.text(a_data[0][0], b_data[0][0], Number, fontsize = 8, ha = 'center')
def animate(i) :
arrow_start = (a_data[0+i][0], b_data[0+i][0])
arrow_end = (a_data[0+i][1], b_data[0+i][1])
arrow.set_position(arrow_start)
arrow.xy = arrow_end
label.set_text(a_data[0+i][0], b_data[0+i][0])
ani = animation.FuncAnimation(fig, animate,
interval = 500, blit = False)
plt.draw()
Although you could use plt.text to display the label, you don't need it. ax.annotate can generate the label as well as the arrow. You can specify the label string as the first argument to ax.annotate,
arrow = ax.annotate(Number[0], xy=(a_data[0][0], b_data[0][0]), ...
and you can change the label by calling arrow.set_text:
arrow.set_text(Number[i])
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
import random
from functools import partial
import pandas as pd
one_sample = partial(random.sample, range(100), 10)
a_data = [one_sample() for _ in range(1000)]
b_data = [one_sample() for _ in range(1000)]
df = pd.DataFrame(np.random.randint(0, 100, size=(100, 1)), columns=list('A'))
fig, ax = plt.subplots(figsize=(8, 6))
ax.set_xlim(0, 100)
ax.set_ylim(0, 100)
Number = df['A']
arrow = ax.annotate(Number[0], xy=(a_data[0][0], b_data[0][0]),
xytext=(a_data[0][1], b_data[0][1]),
arrowprops={'arrowstyle': "<->", 'color': 'black'}, ha='center')
def animate(i):
arrow_start = (a_data[0 + i][0], b_data[0 + i][0])
arrow_end = (a_data[0 + i][1], b_data[0 + i][1])
arrow.set_position(arrow_start)
arrow.xy = arrow_end
arrow.set_text(Number[i])
return [arrow]
ani = animation.FuncAnimation(fig, animate, interval=500, blit=True)
plt.show()
To place the label in the middle of the arrow, I believe you would need to use plt.text (or a second call to ax.annotate). To move the label generated by plt.text, call label.set_position:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
import random
import math
from functools import partial
import pandas as pd
one_sample = partial(random.sample, range(100), 10)
a_data = [one_sample() for _ in range(1000)]
b_data = [one_sample() for _ in range(1000)]
df = pd.DataFrame(np.random.randint(0, 100, size=(100, 1)), columns=list('A'))
Number = df['A']
data = np.stack([a_data, b_data], axis=2)
# a_data and b_data contain more data than we are actually using,
# so let's crop `data` to make the following code simpler:
data = data[:, :2, :]
middle = data.mean(axis=1)
# find the direction perpendicular to the arrow
perp_dir = (data[:, 0] - data[:, 1]).astype('float')
perp_dir = np.array((-perp_dir[:, 1], perp_dir[:, 0]))
perp_dir /= np.sqrt((perp_dir**2).sum(axis=0))
perp_dir = perp_dir.T
# shift middle by a little bit in the perpendicular direction
offset = 3.0
middle += offset * perp_dir
fig, ax = plt.subplots(figsize=(8, 6))
ax.set_xlim(0, 100)
ax.set_ylim(0, 100)
arrow = ax.annotate('', xy=data[0, 0],
xytext=data[0, 1],
arrowprops={'arrowstyle': "<->", 'color': 'black'},
ha='center')
label = plt.text(middle[0, 0], middle[0, 1], Number[0], fontsize = 8,
ha = 'center')
def animate(i):
arrow_start = data[i, 0]
arrow_end = data[i, 1]
arrow.set_position(arrow_start)
arrow.xy = arrow_end
label.set_text(Number[i])
label.set_position(middle[i])
return [arrow, label]
ani = animation.FuncAnimation(fig, animate, interval=500, blit=True)
plt.show()
Here is my code :
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
array = np.array([[1,5,9],[3,5,7]])
df = pd.DataFrame(data=array, index=['Positive', 'Negative'])
f, ax = plt.subplots(figsize=(8, 6))
current_palette = sns.color_palette('colorblind')
ax_pos = sns.barplot(x = np.arange(0,3,1), y = df.loc['Positive'].to_numpy(), color = current_palette[2], alpha = 0.66)
ax_neg = sns.barplot(x = np.arange(0,3,1), y = df.loc['Negative'].to_numpy(), color = current_palette[4], alpha = 0.66)
plt.xticks(np.arange(0,3,1), fontsize = 20)
plt.yticks(np.arange(0,10,1), fontsize = 20)
plt.legend((ax_pos[0], ax_neg[0]), ('Positive', 'Negative'))
plt.tight_layout()
Unfortunately, I have this error :
TypeError: 'AxesSubplot' object does not support indexing
I would like to know why calling legend like this (plt.legend(ax[0]...) is not possible with seaborn whereas with matplotlib it is.
In the end, I just want the legend in the upper left corner.
I figured out that barplot has "label" function :
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
array = np.array([[1,5,9],[3,5,7]])
df = pd.DataFrame(data=array, index=['Positive', 'Negative'])
f, ax = plt.subplots(figsize=(8, 6))
current_palette = sns.color_palette('colorblind')
sns.barplot(x = np.arange(0,3,1), y = df.loc['Positive'].to_numpy(), color = current_palette[2], alpha = 0.66, label = "Positive")
sns.barplot(x = np.arange(0,3,1), y = df.loc['Negative'].to_numpy(), color = current_palette[4], alpha = 0.66, label = "Negative")
plt.xticks(np.arange(0,3,1), fontsize = 20)
plt.yticks(np.arange(0,10,1), fontsize = 20)
plt.legend(frameon = False)
plt.tight_layout()
If I set shade_lowest = False, the colorbar still contains the lowest level (purple-ish). Is there any generic way to remove it entirely?
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
a = np.random.normal(0, 1, 100)
b = np.random.normal(0, 1, 100)
fig, ax = plt.subplots()
sns.kdeplot(a, b, shade = True, shade_lowest = False, cmap = "viridis", cbar = True, n_levels = 4, ax = ax)
plt.show()
A solution is for sure to not create this level from the beginning.
Here we choose maximally 5 levels according to a locator and remove the lowest one when calling the contourf plot, such that this level does not even exist in the first place. Then the automatic colorbar creation works flawlessly.
import numpy as np; np.random.seed(5)
import matplotlib.pyplot as plt
from matplotlib import ticker
from scipy import stats
x = np.random.normal(3, 1, 100)
y = np.random.normal(0, 2, 100)
X, Y = np.mgrid[x.min():x.max():100j, y.min():y.max():100j]
positions = np.vstack([X.ravel(),Y.ravel()])
values = np.vstack([x,y])
kernel = stats.gaussian_kde(values)
Z = np.reshape(kernel(positions).T, X.shape)
N=4
locator = ticker.MaxNLocator(N + 1, min_n_ticks=N)
lev = locator.tick_values(Z.min(), Z.max())
fig, ax = plt.subplots()
c = ax.contourf(X,Y,Z,levels=lev[1:])
ax.scatter(x,y, s=9, c="k")
fig.colorbar(c)
plt.show()
I want to produce in python with matplotlib/pyplot
a bar chart with a fill depending on the value.
legend color bar
while keeping module dependencies at a minimum.
Is there something simpler than:
import matplotlib.pyplot as plt
def color_gradient ( val, beg_rgb, end_rgb, val_min = 0, val_max = 1):
val_scale = (1.0 * val - val_min) / (val_max - val_min)
return ( beg_rgb[0] + val_scale * (end_rgb[0] - beg_rgb[0]),
beg_rgb[1] + val_scale * (end_rgb[1] - beg_rgb[1]),
beg_rgb[2] + val_scale * (end_rgb[2] - beg_rgb[2]))
# -----------------------------------------------
x_lbls = [ "09:00", "09:15", "10:10"]
y_vals = [ 7, 9, 5]
plt_idx = np.arange( len( x_lbls))
bar_wd = 0.35
grad_beg, grad_end = ( 0.5, 0.5, 0.5), (1, 1, 0)
col_list = [ color_gradient( val,
grad_beg,
grad_end,
min( y_vals),
max( y_vals)) for val in y_vals]
plt.bar( plt_idx, y_vals, color = col_list)
plt.xticks( plt_idx + bar_wd, x_lbls)
plt.show()
this is still missing the legend color bar
my solution in R with ggplot would be:
library(ggplot2)
df = data.frame( time = 1:10, vals = abs(rnorm( n = 10)))
ggplot( df, aes( x = time, y = vals, fill = vals)) +
geom_bar(stat = "identity") +
scale_fill_gradient(low="#888888",high="#FFFF00")
and produces the desired output:
I couldn't figure out how to get the colorbar to work without plotting something else and then clearing it, so it's not the most elegant solution.
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
y = np.array([1, 4, 3, 2, 7, 11])
colors = cm.hsv(y / float(max(y)))
plot = plt.scatter(y, y, c = y, cmap = 'hsv')
plt.clf()
plt.colorbar(plot)
plt.bar(range(len(y)), y, color = colors)
plt.show()
You can use Normalize and ScalarMappable without plotting a scatter. For example:
import matplotlib mpl
import matplotlib.pyplot as plt
from matplotlib import cm
f,(ax1,ax2) = plt.subplots(2)
#ax1 --> plot here your bar chart
norm = mpl.colors.Normalize(vmin=0, vmax=1)
mpl.colorbar.ColorbarBase(ax2, cmap=cm.RdBu,
norm=norm,
orientation='horizontal')
Finally, add the desired format to the colorbar.