PlotlyJS.jl: grouped + stacked bar charts

Hi,
I need to create a plot like the one below using PlotlyJS.jl. I have done my best and extensively searched this forum and others online. Still, there seems to be very little information about stacked + grouped bar charts in Plotly to produce this type of plot in the Julia environment (there is some discussion on this issue, for example, here and here, but apparently it was never settled as a standard functionality in Plotly). For me, whether the bars are vertically or horizontally oriented is irrelevant.

Some help would be very much appreciated. Thank you.

2 Likes

In this thread is given an example of grouped stacked bars. https://discourse.julialang.org/t/use-mapbox-to-place-images-with-plotlyjs/86758/6.

@empet, thanks for your help. Actually, I was misled by one (or by some) of the threads I consulted with, as I have found out that the official documentation already includes an example of how to implement a grouped stacked bar plot using PlotlyJS.jl. See the last entry here.

The code is as follows. I am using PlotlyBase.jl inside Pluto:

let
	x = [
		["BB+", "BB+", "BB+", "BB", "BB", "BB", "BB-", "BB-", "BB-"],
    	[16, 17, 18, 16, 17, 18, 19 , 20, 21] ]
	
	y1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]   # to let direct labels
	y2 = [9, 8, 7, 6, 5, 4, 3, 2, 1]   # to let direct labels
	
	Plot([bar(x=x, y=y1, text=y1, textposition="auto"),
		  bar(x=x, y=y2, text=y2, textposition="auto")], 
		  Layout(barmode="stack"))
end

Your last example illustrates only stacked bars. The item 2. In my comment, included in the linked thread above, describes how from a specially defined daraframe we can get grouped and stacked bars.

Hi @empet,

Thanks for helping. I used your code and my data set (long version), but I am making some mistakes because the plot I got is not what I was looking for. The figure I get is the following:

And your code is (I am using PlotlyBase inside Pluto, so the function Plot instead of plot):

fig = Plot(long_data, kind="bar", x=:Year, y=:Percent, color=:Status, 
              Layout(barmode="stack" ))

In case you want to have a go and replicate that figure, my data is the following (I think you can save it into a txt file, I did it with your data example):

Year,Levels,Status,Percent
2011,Less than a High School Diploma,Employment,53.2
2011,High School Diploma ,Employment,66.9
2011,"Some College, No Degree ",Employment,70.4
2011,Associate Degree,Employment,76.2
2011,Bachelor’s Degree or Higher,Employment,81.5
2016,Less than a High School Diploma,Employment,56.1
2016,High School Diploma ,Employment,68.3
2016,"Some College, No Degree ",Employment,72.7
2016,Associate Degree,Employment,77.6
2016,Bachelor’s Degree or Higher,Employment,82.9
2021,Less than a High School Diploma,Employment,54.8
2021,High School Diploma ,Employment,66.6
2021,"Some College, No Degree ",Employment,71.3
2021,Associate Degree,Employment,75.9
2021,Bachelor’s Degree or Higher,Employment,82.9
2011,Less than a High School Diploma,Unemployment,9
2011,High School Diploma ,Unemployment,7.1
2011,"Some College, No Degree ",Unemployment,6.7
2011,Associate Degree,Unemployment,5.5
2011,Bachelor’s Degree or Higher,Unemployment,3.6
2016,Less than a High School Diploma,Unemployment,4.6
2016,High School Diploma ,Unemployment,3.8
2016,"Some College, No Degree ",Unemployment,3.4
2016,Associate Degree,Unemployment,2.9
2016,Bachelor’s Degree or Higher,Unemployment,2.1
2021,Less than a High School Diploma,Unemployment,5.1
2021,High School Diploma ,Unemployment,4.5
2021,"Some College, No Degree ",Unemployment,4.2
2021,Associate Degree,Unemployment,3.6
2021,Bachelor’s Degree or Higher,Unemployment,2.5
2011,Less than a High School Diploma,Not_in_LF,37.8
2011,High School Diploma ,Not_in_LF,26
2011,"Some College, No Degree ",Not_in_LF,22.9
2011,Associate Degree,Not_in_LF,18.3
2011,Bachelor’s Degree or Higher,Not_in_LF,14.9
2016,Less than a High School Diploma,Not_in_LF,39.3
2016,High School Diploma ,Not_in_LF,27.9
2016,"Some College, No Degree ",Not_in_LF,23.9
2016,Associate Degree,Not_in_LF,19.6
2016,Bachelor’s Degree or Higher,Not_in_LF,15
2021,Less than a High School Diploma,Not_in_LF,40.2
2021,High School Diploma ,Not_in_LF,28.9
2021,"Some College, No Degree ",Not_in_LF,24.5
2021,Associate Degree,Not_in_LF,20.4
2021,Bachelor’s Degree or Higher,Not_in_LF,14.6

The plot I was looking for is something like the following one:

Despite accomplishing the figure I needed, I wouldn’t say I liked the entire process because my code was inefficient. For example, if I want to insert the direct labels into the plot, I have to insert them manually; or if I want to change the nature of the groups (from Years to Levels), I have a lot of work to do. This is because to produce the figure above, I had to generate three individual plots (one for 2011, another for 2016, and one for 2021), and then merge them into a 1 x 3 subplot grid. This leads to another major trouble: I do not know how to impose a single set of legends that operates on all three subplots in the final output.

The code I used to produce the individual plots is the following (e.g., for 2021), and the data set is wide in this case (I am using Pluto and PlotlyBase.jl):

begin
	y1c = [54.8, 66.6, 71.3, 75.9, 82.9] # to let direct labels
	y2c = [ 5.1,  4.5,  4.2,  3.6,  2.5] # to let direct labels
	y3c = [40.2, 28.9, 24.5, 20.4, 14.6] # to let direct labels
	
	fig24 = Plot([bar(filter(row -> row.Year == 2021, data), 
		x="Years", y=y, name=string(y)) for y in [:Employment, :Unemployment, :Not] ])
	relayout!(fig24, barmode="stack", title_text="2021", title_x=0.5, xaxis=attr(
        tickmode = "array",
        tickvals = [0, 1, 2, 3, 4],
        ticktext = ["HS-", "HS", "Col-", "Pro", "Col+"] 
		))
	restyle!(fig24, marker_color=["teelBlue", "darkblue", "Salmon"], text= [y1c, y2c, y3c], textposition="auto", showlegend=true)
	fig24
end

Thanks for your concerns.

For the next 10 days I don’t have access to a computer. If it’s not too late I’ll try when I go back at home.

@empet, no problem I am not in a hurry. Thanks.

@VivMendes I think I can do it in a more efficient way.
Notice that according to the documentation you have just mentioned, multi-category axis charts can be only achieved using raw arrays and not DataFrames.
In fact, we cannot pass a vector of symbols to the x or the y kwargs inside the Plot() function to have two categories in one axis.
But you can always work with the columns of the DataFrame as arrays.
Here is my suggestion:

Plot(
	[bar(;
		x = [longdf.Year, longdf.Levels],
		y = filter(:Status => x -> x == "NotinLF",longdf).Percent,
		name = "Not in the Labor Force"),
	bar(;
		x = [longdf.Year, longdf.Levels],
		y = filter(:Status => x -> x == "Unemployment",longdf).Percent,
		name = "Unemployed"),
	bar(;
		x = [longdf.Year, longdf.Levels],
		y = filter(:Status => x -> x == "Employment",longdf).Percent,
		name = "Employed")],
    Layout(
		barmode="stack",
		height = 600
	)
)

Here is what I get:

And if you want them horizontal:

Plot(
	[bar(;
		x = filter(:Status => x -> x == "NotinLF",longdf).Percent,
		y = [longdf.Year, longdf.Levels],
		name = "Not in the Labor Force",
		orientation = "h"),
	bar(;
		x = filter(:Status => x -> x == "Unemployment",longdf).Percent,
		y = [longdf.Year, longdf.Levels],
		name = "Unemployed",
		orientation = "h"),
	bar(;
		x = filter(:Status => x -> x == "Employment",longdf).Percent,
		y = [longdf.Year, longdf.Levels],
		name = "Employed",
		orientation = "h")],
    Layout(
		barmode="stack",
		height = 600
	)
)

I have converted the values in column Year to strings, to make sure Plotly does not mess-up with dates.
Depending on your goal, it may also be wise to store in different variables the subsets of the DataFrame that you want to use later according to the :Status.

But let us wait for @empet, since she always has clever solutions. :slight_smile:

1 Like

@rgouveiamendes, thank you very much. This solves the problem. It seems easy after we see it done.