Plotting Pandas dataframe subplots with different linestyles - pandas

I am plotting a figure with 6 sets of axes, each with a series of 3 lines from one of 2 Pandas dataframes (1 line per column).
I have been using matplotlib .plot:
import pandas as pd
import matplotlib.pyplot as plt
idx = pd.DatetimeIndex(start = '2013-01-01 00:00', periods =24,freq = 'H')
df1 = pd.DataFrame(index = idx, columns = ['line1','line2','line3'])
df1['line1']= df1.index.hour
df1['line2'] = 24 - df1['line1']
df1['line3'] = df1['line1'].mean()
df2 = df1*2
df3= df1/2
df4= df2+df3
fig, ax = plt.subplots(2,2,squeeze=False,figsize = (10,10))
ax[0,0].plot(df1.index, df1, marker='', linewidth=1, alpha=1)
ax[0,1].plot(df2.index, df2, marker='', linewidth=1, alpha=1)
ax[1,0].plot(df3.index, df3, marker='', linewidth=1, alpha=1)
ax[1,1].plot(df4.index, df4, marker='', linewidth=1, alpha=1)
fig.show()
It's all good, and matplotlib automatically cycles through a different colour for each line, but uses the same colours for each plot, which is what i wanted.
However, now I want to specify more details for the lines: choosing specific colours for each line, and / or changing the linestyle for each line.
This link shows how to pass multiple linestyles to a Pandas plot. e.g. using
ax = df.plot(kind='line', style=['-', '--', '-.'])
So I need to either:
pass lists of styles to my subplot command above, but style is not recognised and it doesn't accept a list for linestyle or color. Is there a way to do this?
or
Use df.plot:
fig, ax = plt.subplots(2,2,squeeze=False,figsize = (10,10))
ax[0,0] = df1.plot(style=['-','--','-.'], marker='', linewidth=1, alpha=1)
ax[0,1] = df2.plot(style=['-','--','-.'],marker='', linewidth=1, alpha=1)
ax[1,0] = df3.plot( style=['-','--','-.'],marker='', linewidth=1, alpha=1)
ax[1,1] = df4.plot(style=['-','--','-.'], marker='', linewidth=1, alpha=1)
fig.show()
...but then each plot is plotted as a seperate figure. I can't see how to put multiple Pandas plots on the same figure.
How can I make either of these approaches work?

using matplotlib
Using matplotlib, you may define a cycler for the axes to loop over color and linestyle automatically. (See this answer).
import numpy as np; np.random.seed(1)
import pandas as pd
import matplotlib.pyplot as plt
f = lambda i: pd.DataFrame(np.cumsum(np.random.randn(20,3),0))
dic1= dict(zip(range(3), [f(i) for i in range(3)]))
dic2= dict(zip(range(3), [f(i) for i in range(3)]))
dics = [dic1,dic2]
rows = range(3)
def set_cycler(ax):
ax.set_prop_cycle(plt.cycler('color', ['limegreen', '#bc15b0', 'indigo'])+
plt.cycler('linestyle', ["-","--","-."]))
fig, ax = plt.subplots(3,2,squeeze=False,figsize = (8,5))
for x in rows:
for i,dic in enumerate(dics):
set_cycler(ax[x,i])
ax[x,i].plot(dic[x].index, dic[x], marker='', linewidth=1, alpha=1)
plt.show()
using pandas
Using pandas you can indeed supply a list of possible colors and linestyles to the df.plot() method. Additionally you need to tell it in which axes to plot (df.plot(ax=ax[i,j])).
import numpy as np; np.random.seed(1)
import pandas as pd
import matplotlib.pyplot as plt
f = lambda i: pd.DataFrame(np.cumsum(np.random.randn(20,3),0))
dic1= dict(zip(range(3), [f(i) for i in range(3)]))
dic2= dict(zip(range(3), [f(i) for i in range(3)]))
dics = [dic1,dic2]
rows = range(3)
color = ['limegreen', '#bc15b0', 'indigo']
linestyle = ["-","--","-."]
fig, ax = plt.subplots(3,2,squeeze=False,figsize = (8,5))
for x in rows:
for i,dic in enumerate(dics):
dic[x].plot(ax=ax[x,i], style=linestyle, color=color, legend=False)
plt.show()

Related

How to add labels to sets of seaborn boxplot

I have 2 sets of boxplots, one set in blue color and another in red color. I want the legend to show the label for each set of boxplots, i.e.
Legend:
-blue box- A, -red box- B
Added labels='A' and labels='B' within sns.boxplot(), but didn't work with error message "No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument". How do I add the labels?
enter image description here
code for the inserted image:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
x = list(range(1,13))
n = 40
index = [item for item in x for i in range(n)]
np.random.seed(123)
df = pd.DataFrame({'A': np.random.normal(30, 2, len(index)),
'B': np.random.normal(10, 2, len(index))},
index=index)
red_diamond = dict(markerfacecolor='r', marker='D')
blue_dot = dict(markerfacecolor='b', marker='o')
plt.figure(figsize=[10,5])
ax = plt.gca()
ax1 = sns.boxplot( x=df.index, y=df['A'], width=0.5, color='red', \
boxprops=dict(alpha=.5), flierprops=red_diamond, labels='A')
ax2 = sns.boxplot( x=df.index, y=df['B'], width=0.5, color='blue', \
boxprops=dict(alpha=.5), flierprops=blue_dot, labels='B')
plt.ylabel('Something')
plt.legend(loc="center", fontsize=8, frameon=False)
plt.show()
Here are the software versions I am using: seaborn version 0.11.2. matplotlib version 3.5.1. python version 3.10.1
The following approach sets a label via the boxprops, and creates a legend using part of ax.artists. (Note that ax, ax1 and ax2 of the question's code are all pointing to the same subplot, so here only ax is used.)
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
x = np.arange(1, 13)
index = np.repeat(x, 40)
np.random.seed(123)
df = pd.DataFrame({'A': np.random.normal(30, 2, len(index)),
'B': np.random.normal(10, 2, len(index))},
index=index)
red_diamond = dict(markerfacecolor='r', marker='D')
blue_dot = dict(markerfacecolor='b', marker='o')
plt.figure(figsize=[10, 5])
ax = sns.boxplot(data=df, x=df.index, y='A', width=0.5, color='red',
boxprops=dict(alpha=.5, label='A'), flierprops=red_diamond)
sns.boxplot(data=df, x=df.index, y='B', width=0.5, color='blue',
boxprops=dict(alpha=.5, label='B'), flierprops=blue_dot, ax=ax)
ax.set_ylabel('Something')
handles, labels = ax.get_legend_handles_labels()
handles = [h for h, lbl, prev in zip(handles, labels, [None] + labels) if lbl != prev]
ax.legend(handles=handles, loc="center", fontsize=8, frameon=False)
plt.show()
Alternative approaches could be:
pd.melt the dataframe to long form, so hue could be used; a problem here is that then the legend wouldn't take the alpha from the boxprops into account; also setting different fliers wouldn't be supported
create a legend from custom handles

Plot multiple mplfinance plots sharing x axis

I am trying to plot 5 charts one under the other with mplfinance.
This works:
for coin in coins:
mpf.plot(df_coins[coin], title=coin, type='line', volume=True, show_nontrading=True)
However each plot is a separate image in my Python Notebook cell output. And the x-axis labelling is repeated for each image.
I try to make a single figure containing multiple subplot/axis, and plot one chart into each axis:
from matplotlib import pyplot as plt
N = len(df_coins)
fig, axes = plt.subplots(N, figsize=(20, 5*N), sharex=True)
for i, ((coin, df), ax) in zip(enumerate(df_coins.items()), axes):
mpf.plot(df, ax=ax, title=coin, type='line', volume=True, show_nontrading=True)
This displays subfigures of the correct dimensions, however they are not getting populated with data. Axes are labelled from 0.0 to 1.0 and the title is not appearing.
What am I missing?
There are two ways to subplot. One is to set up a figure with mplfinance objects. The other way is to use your adopted matplotlib subplot to place it.
yfinace data
import matplotlib.pyplot as plt
import mplfinance as mpf
import yfinance as yf
tickers = ['AAPL','GOOG','TSLA']
data = yf.download(tickers, start="2021-01-01", end="2021-03-01", group_by='ticker')
aapl = data[('AAPL',)]
goog = data[('GOOG',)]
tsla = data[('TSLA',)]
mplfinance
fig = mpf.figure(style='yahoo', figsize=(12,9))
#fig.subplots_adjust(hspace=0.3)
ax1 = fig.add_subplot(3,1,1, sharex=ax3)
ax2 = fig.add_subplot(3,1,2, sharex=ax3)
ax3 = fig.add_subplot(3,1,3)
mpf.plot(aapl, type='line', ax=ax1, axtitle='AAPL', xrotation=0)
mpf.plot(goog, type='line', ax=ax2, axtitle='GOOG', xrotation=0)
mpf.plot(tsla, type='line', ax=ax3, axtitle='TSLA', xrotation=0)
ax1.set_xticklabels([])
ax2.set_xticklabels([])
matplotlib
N = len(tickers)
fig, axes = plt.subplots(N, figsize=(20, 5*N), sharex=True)
for df,t,ax in zip([aapl,goog,tsla], tickers, axes):
mpf.plot(df, ax=ax, axtitle=t, type='line', show_nontrading=True)# volume=True
In addition to the techniques mentioned by #r-beginners there is another technique that may work for you in the case where all plots share the same x-axis. That is to use mpf.make_addplot().
aps = []
for coin in coins[1:]:
aps.append(mpf.make_addplot(df_coins[coin]['Close'], title=coin, type='line'))
coin = coins[0]
mpf.plot(df_coins[coin],axtitle=coin,type='line',volume=True,show_nontrading=True,addplot=aps)
If you choose to do type='candle' instead of 'line', then change
df_coins[coin]['Close']
to simply
df_coins[coin]

Visualize 1-dimensional data in a sequential colormap

I have a pandas series containing numbers ranging between 0 and 100. I want to visualise it in a horizontal bar consisting of 3 main colours.
I have tried using seaborn but all I can get is a heatmap matrix. I have also tried the below code, which is producing what I need but not in the way I need it.
x = my_column.values
y = x
t = x
fig, (ax1, ax2) = plt.subplots(1, 2)
ax1.scatter(x, y, c=t, cmap='brg')
ax2.scatter(x, y, c=t, cmap='brg')
plt.show()
What I'm looking for is something similar to the below figure, how can I achieve that using matplotlib or seaborn?
The purpose of this is not quite clear, however, the following would produce an image like the one shown in the question:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
x = np.linspace(100,0,101)
fig, ax = plt.subplots(figsize=(6,1), constrained_layout=True)
cmap = LinearSegmentedColormap.from_list("", ["limegreen", "gold", "crimson"])
ax.imshow([x], cmap=cmap, aspect="auto",
extent=[x[0]-np.diff(x)[0]/2, x[-1]+np.diff(x)[0]/2,0,1])
ax.tick_params(axis="y", left=False, labelleft=False)
plt.show()

Arrange two plots horizontally

As an exercise, I'm reproducing a plot from The Economist with matplotlib
So far, I can generate a random data and produce two plots independently. I'm struggling now with putting them next to each other horizontally.
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
df1 = pd.DataFrame({"broadcast": np.random.randint(110, 150,size=8),
"cable": np.random.randint(100, 250, size=8),
"streaming" : np.random.randint(10, 50, size=8)},
index=pd.Series(np.arange(2009,2017),name='year'))
df1.plot.bar(stacked=True)
df2 = pd.DataFrame({'usage': np.sort(np.random.randint(1,50,size=7)),
'avg_hour': np.sort(np.random.randint(0,3, size=7) + np.random.ranf(size=7))},
index=pd.Series(np.arange(2009,2016),name='year'))
plt.figure()
fig, ax1 = plt.subplots()
ax1.plot(df2['avg_hour'])
ax2 = ax1.twinx()
ax2.bar(left=range(2009,2016),height=df2['usage'])
plt.show()
You should try using subplots. First you create a figure by plt.figure(). Then add one subplot(121) where 1 is number of rows, 2 is number of columns and last 1 is your first plot. Then you plot the first dataframe, note that you should use the created axis ax1. Then add the second subplot(122) and repeat for the second dataframe. I changed your axis ax2 to ax3 since now you have three axis on one figure. The code below produces what I believe you are looking for. You can then work on aesthetics of each plot separately.
%matplotlib inline
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
df1 = pd.DataFrame({"broadcast": np.random.randint(110, 150,size=8),
"cable": np.random.randint(100, 250, size=8),
"streaming" : np.random.randint(10, 50, size=8)},
index=pd.Series(np.arange(2009,2017),name='year'))
ax1 = fig.add_subplot(121)
df1.plot.bar(stacked=True,ax=ax1)
df2 = pd.DataFrame({'usage': np.sort(np.random.randint(1,50,size=7)),
'avg_hour': np.sort(np.random.randint(0,3, size=7) + np.random.ranf(size=7))},
index=pd.Series(np.arange(2009,2016),name='year'))
ax2 = fig.add_subplot(122)
ax2.plot(df2['avg_hour'])
ax3 = ax2.twinx()
ax3.bar(left=range(2009,2016),height=df2['usage'])
plt.show()

"panel barchart" in matplotlib

I would like to produce a figure like this one using matplotlib:
(source: peltiertech.com)
My data are in a pandas DataFrame, and I've gotten as far as a regular stacked barchart, but I can't figure out how to do the part where each category is given its own y-axis baseline.
Ideally I would like the vertical scale to be exactly the same for all the subplots and move the panel labels off to the side so there can be no gaps between the rows.
I haven't exactly replicated what you want but this should get you pretty close.
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
#create dummy data
cols = ['col'+str(i) for i in range(10)]
ind = ['ind'+str(i) for i in range(10)]
df = pd.DataFrame(np.random.normal(loc=10, scale=5, size=(10, 10)), index=ind, columns=cols)
#create plot
sns.set_style("whitegrid")
axs = df.plot(kind='bar', subplots=True, sharey=True,
figsize=(6, 5), legend=False, yticks=[],
grid=False, ylim=(0, 14), edgecolor='none',
fontsize=14, color=[sns.xkcd_rgb["brownish red"]])
plt.text(-1, 100, "The y-axis label", fontsize=14, rotation=90) # add a y-label with custom positioning
sns.despine(left=True) # get rid of the axes
for ax in axs: # set the names beside the axes
ax.lines[0].set_visible(False) # remove ugly dashed line
ax.set_title('')
sername = ax.get_legend_handles_labels()[1][0]
ax.text(9.8, 5, sername, fontsize=14)
plt.suptitle("My panel chart", fontsize=18)