Show multiple columns values on labels with squarify.plot - pandas

I have a dataframe that I'd like to plot a tree map with squarify. I'd like to show the country_name and counts on the chart by editing the labels parameter but it seems only taking one value.
Example data
import squarify
import pandas as pd
from matplotlib import pyplot as plt
d = {'country_name':['USA', 'UK', 'Germany'], 'counts':[100, 200, 300]}
dd = pd.DataFrame(data=d)
fig = plt.gcf()
ax = fig.add_subplot()
fig.set_size_inches(16, 4.5)
norm = matplotlib.colors.Normalize(vmin=min(dd.counts), vmax=max(dd.counts))
colors = [matplotlib.cm.Blues(norm(value)) for value in dd.counts]
squarify.plot(label=dd.country_name, sizes=dd.counts, alpha=.7, color=colors)
plt.axis('off')
plt.show()
Expected output will have both counts and country_name on the chart.

You can create a list of labels by looping simultaneously through both columns and composing combined strings. For example:
import squarify
import pandas as pd
from matplotlib import pyplot as plt
import matplotlib
d = {'country_name': ['USA', 'UK', 'Germany'], 'counts': [100, 200, 300]}
dd = pd.DataFrame(data=d)
labels = [f'{country}\n{count}' for country, count in zip(dd.country_name, dd.counts)]
fig = plt.gcf()
ax = fig.add_subplot()
fig.set_size_inches(16, 4.5)
norm = matplotlib.colors.Normalize(vmin=min(dd.counts), vmax=max(dd.counts))
colors = [matplotlib.cm.Blues(norm(value)) for value in dd.counts]
squarify.plot(label=labels, sizes=dd.counts, alpha=.7, color=colors)
plt.axis('off')
plt.show()

Related

How make scatterplot in pandas readable

I've been playing with Titanic dataset and working through some visualisations in Pandas using this tutorial. https://www.kdnuggets.com/2023/02/5-pandas-plotting-functions-might-know.html
I have a visual of scatterplot having used this code.
import pandas as pd
%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
df = pd.read_csv('train.csv')
I was confused by bootstrap plot result so went on to scatterplot.
pd.plotting.scatter_matrix(df, figsize=(10,10), )
plt.show()
I can sort of interpret it but I'd like to put the various variables at top and bottom of every column. Is that doable?
You can use:
fig, ax = plt.subplots(4, 3, figsize=(20, 15))
sns.scatterplot(x = 'bedrooms', y = 'price', data = dataset, whis=1.5, ax=ax[0, 0])
sns.scatterplot(x = 'bathrooms', y = 'price', data = dataset, whis=1.5, ax=ax[0, 1])

who to plot stats.probplot in a grid?

I have a data frame with four columns I would like to plot the normality test for each column in a 2*2 grid, but it only plot one figure, and the else is empty.
import random
import pandas as pd
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
fig, axs = plt.subplots(2,2, figsize=(15, 6), facecolor='w', edgecolor='k')
fig.subplots_adjust(hspace = .5, wspace=.001)
data = {'col1': [random.randrange(1, 50, 1) for i in range(1000)], 'col2': [random.randrange(1, 50, 1) for i in range(1000)],'col3':[random.randrange(1, 50, 1) for i in range(1000)]
,'col4':[random.randrange(1, 50, 1) for i in range(1000)]}
df = pd.DataFrame(data)
for ax, d in zip(axs.ravel(), df):
ax=stats.probplot(df[d], plot=plt)
#ax.set_title(str(d))
plt.show()
is there a way to construct the subplot and the stats.probplot within a loop?
In your code, you need to change the for loop to this:
for ax, d in zip(axs.ravel(), df):
stats.probplot(df[d], plot=ax)
#ax.set_titl(str(d))
plt.show()
I hope this will help you move on.

Plotting Pandas dataframe subplots with different linestyles

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

Pandas histogram df.hist() group by

How to plot a histogram with pandas DataFrame.hist() using group by?
I have a data frame with 5 columns: "A", "B", "C", "D" and "Group"
There are two Groups classes: "yes" and "no"
Using:
df.hist()
I get the hist for each of the 4 columns.
Now I would like to get the same 4 graphs but with blue bars (group="yes") and red bars (group = "no").
I tried this withouth success:
df.hist(by = "group")
Using Seaborn
If you are open to use Seaborn, a plot with multiple subplots and multiple variables within each subplot can easily be made using seaborn.FacetGrid.
import numpy as np; np.random.seed(1)
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
df = pd.DataFrame(np.random.randn(300,4), columns=list("ABCD"))
df["group"] = np.random.choice(["yes", "no"], p=[0.32,0.68],size=300)
df2 = pd.melt(df, id_vars='group', value_vars=list("ABCD"), value_name='value')
bins=np.linspace(df2.value.min(), df2.value.max(), 10)
g = sns.FacetGrid(df2, col="variable", hue="group", palette="Set1", col_wrap=2)
g.map(plt.hist, 'value', bins=bins, ec="k")
g.axes[-1].legend()
plt.show()
This is not the most flexible workaround but will work for your question specifically.
def sephist(col):
yes = df[df['group'] == 'yes'][col]
no = df[df['group'] == 'no'][col]
return yes, no
for num, alpha in enumerate('abcd'):
plt.subplot(2, 2, num)
plt.hist(sephist(alpha)[0], bins=25, alpha=0.5, label='yes', color='b')
plt.hist(sephist(alpha)[1], bins=25, alpha=0.5, label='no', color='r')
plt.legend(loc='upper right')
plt.title(alpha)
plt.tight_layout(pad=0.4, w_pad=0.5, h_pad=1.0)
You could make this more generic by:
adding a df and by parameter to sephist: def sephist(df, by, col)
making the subplots loop more flexible: for num, alpha in enumerate(df.columns)
Because the first argument to matplotlib.pyplot.hist can take
either a single array or a sequency of arrays which are not required
to be of the same length
...an alternattive would be:
for num, alpha in enumerate('abcd'):
plt.subplot(2, 2, num)
plt.hist((sephist(alpha)[0], sephist(alpha)[1]), bins=25, alpha=0.5, label=['yes', 'no'], color=['r', 'b'])
plt.legend(loc='upper right')
plt.title(alpha)
plt.tight_layout(pad=0.4, w_pad=0.5, h_pad=1.0)
I generalized one of the other comment's solutions. Hope it helps someone out there. I added a line to ensure binning (number and range) is preserved for each column, regardless of group. The code should work for both "binary" and "categorical" groupings, i.e. "by" can specify a column wherein there are N number of unique groups. Plotting also stops if the number of columns to plot exceeds the subplot space.
import numpy as np
import matplotlib.pyplot as plt
def composite_histplot(df, columns, by, nbins=25, alpha=0.5):
def _sephist(df, col, by):
unique_vals = df[by].unique()
df_by = dict()
for uv in unique_vals:
df_by[uv] = df[df[by] == uv][col]
return df_by
subplt_c = 4
subplt_r = 5
fig = plt.figure()
for num, col in enumerate(columns):
if num + 1 > subplt_c * subplt_r:
continue
plt.subplot(subplt_c, subplt_r, num+1)
bins = np.linspace(df[col].min(), df[col].max(), nbins)
for lbl, sepcol in _sephist(df, col, by).items():
plt.hist(sepcol, bins=bins, alpha=alpha, label=lbl)
plt.legend(loc='upper right', title=by)
plt.title(col)
plt.tight_layout()
return fig
TLDR oneliner;
It won't create the subplots but will create 4 different plots;
[df.groupby('group')[i].plot(kind='hist',title=i)[0] and plt.legend() and plt.show() for i in 'ABCD']
Full working example below
import numpy as np; np.random.seed(1)
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
df = pd.DataFrame(np.random.randn(300,4), columns=list("ABCD"))
df["group"] = np.random.choice(["yes", "no"], p=[0.32,0.68],size=300)
[df.groupby('group')[i].plot(kind='hist',title=i)[0] and plt.legend() and plt.show() for i in 'ABCD']

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