Moving text annotation to middle of arrowprop [duplicate] - python

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()

Related

How to filter data while drawing?

I have a dataframe which I drawed as you can see the figure and codes below;
import pandas as pd
from matplotlib import pyplot as plt
import numpy as np
df = pd.read_excel('nötronn.xlsx')
fig, ax = plt.subplots(figsize=(20,40))
ax1 = plt.subplot2grid((1,5), (0,0), rowspan=1, colspan = 1)
ax1.plot(df["N/F*10"], df['Depth'], color = "green", linewidth = 0.5)
ax1.set_xlabel("Porosity")
ax1.xaxis.label.set_color("green")
ax1.set_xlim(10, 50)
ax1.set_ylabel("Depth (m)")
ax1.tick_params(axis='x', colors="green")
ax1.spines["top"].set_edgecolor("green")
ax1.title.set_color('green')
ax1.set_xticks([10, 20, 30, 40, 50])
I want to filter data so that I can realize the differences better. I tried these:
z = np.polyfit(df["N/F*10"], df['Depth'], 2)
p = np.poly1d(z)
plt.plot(df["N/F*10"], p(df["N/F*10"]))
But it gives :LinAlgError: SVD did not converge in Linear Least Squares
How can I solve it? Thanks.
Output expectation:
This works!
import pandas as pd
from matplotlib import pyplot as plt
import numpy as np
from statsmodels.nonparametric.smoothers_lowess import lowess
data = pd.read_excel('nötronn.xlsx')
sub_data = data[data['Depth'] > 21.5]
result = lowess(sub_data['Eksi'], sub_data['Depth'].values)
x_smooth = result[:,0]
y_smooth = result[:,1]
tot_result = lowess(data['Eksi'], data['Depth'].values, frac=0.01)
x_tot_smooth = tot_result[:,0]
y_tot_smooth = tot_result[:,1]
fig, ax = plt.subplots(figsize=(20, 8))
##ax.plot(data.depth.values, data['N/F*10'], label="raw")
ax.plot(x_tot_smooth, y_tot_smooth, label="lowess 1%", linewidth=3, color="g")
ax.plot(data['GR-V121B-ETi'])
ax.plot(data['Caliper'], linestyle = 'dashed')

Interactive Stock Chart, step by step animation with a slider. Matplolib & Jupyter

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)

Matplotlib - Implement multiple y-axis scales in animated line graph

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()

Matplotlib animated histogram colormap/gradient

I am trying to animate a histogram using matplotlib and I want to show the different bars using a colormap, e.g:
I have this working when I clear the complete figure every frame and then redraw everything. But this is very slow, so I am trying out the example by matplotlib itself.
This works and is very fast, but unfortunately I have no idea on how to specify a colormap because it is using the patches.PathPatch object to draw the histogram now. I can only get it to work with the same single color for every individual bar.
How can I specify a gradient or colormap to achieve the desired result shown above?
Here is an example of a working animation with a single color which I am currently using.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.path as path
import matplotlib.animation as animation
# Fixing random state for reproducibility
np.random.seed(19680801)
# histogram our data with numpy
data = np.random.randn(1000)
n, bins = np.histogram(data, 100)
# get the corners of the rectangles for the histogram
left = np.array(bins[:-1])
right = np.array(bins[1:])
bottom = np.zeros(len(left))
top = bottom + n
nrects = len(left)
nverts = nrects * (1 + 3 + 1)
verts = np.zeros((nverts, 2))
codes = np.ones(nverts, int) * path.Path.LINETO
codes[0::5] = path.Path.MOVETO
codes[4::5] = path.Path.CLOSEPOLY
verts[0::5, 0] = left
verts[0::5, 1] = bottom
verts[1::5, 0] = left
verts[1::5, 1] = top
verts[2::5, 0] = right
verts[2::5, 1] = top
verts[3::5, 0] = right
verts[3::5, 1] = bottom
patch = None
def animate(i):
# simulate new data coming in
data = np.random.randn(1000)
n, bins = np.histogram(data, 100)
top = bottom + n
verts[1::5, 1] = top
verts[2::5, 1] = top
return [patch, ]
fig, ax = plt.subplots()
barpath = path.Path(verts, codes)
patch = patches.PathPatch(
barpath, facecolor='green', edgecolor='yellow', alpha=0.5)
ax.add_patch(patch)
ax.set_xlim(left[0], right[-1])
ax.set_ylim(bottom.min(), top.max())
ani = animation.FuncAnimation(fig, animate, 100, repeat=False, blit=True)
plt.show()
I recommend u using BarContainer, you can change bar color individually. In your example, the path is single object, matplotlib seems not to support gradient color for a single patch (not sure though).
import numpy as np
import matplotlib.pyplot as plt
# Fixing random state for reproducibility
np.random.seed(19680801)
# histogram our data with numpy
data = np.random.randn(1000)
colors = plt.cm.coolwarm(np.linspace(0, 1, 100))
def animate(i):
data = np.random.randn(1000)
bc = ax.hist(data, 100)[2]
for i, e in enumerate(bc):
e.set_color(colors[i])
return bc
fig, ax = plt.subplots(1, 1, figsize=(7.2, 7.2))
ani = animation.FuncAnimation(fig, animate, 100, repeat=False, blit=True)

add legend seaborn barplot

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()

Categories

Resources