Election Surveys with Plotly. Stacked Bars or Divergent Bars? | by Darío Weitz | Towards Data Science

During the year 2021, national legislative elections will be held in Argentina. The PASO (Primarias Abiertas Simultáneas y Obligatorias, Simultaneous and Mandatory Open Primary Elections) will be held on September 12, while the general elections will be held on November 14. The Chamber of Deputies will renew 127 of its 257 seats and the Senate will renew 24 of its 54 seats. It is considered a transcendental election because the ruling party is only 7 deputies away from obtaining its own majority in the Chamber of Deputies. Since it already has a majority in the Senate, a favorable result would give it total legislative control.
The electoral campaign has not yet begun because all the candidates that will compete for the different electoral coalitions have not yet been defined. Despite this, several polls are published every week in an attempt to establish the future behavior of voters. Some of these surveys focus on the image of the main political referents of the only two electoral forces with a chance of winning the election, as a proxy to predict the voter’s behavior at the moment of casting his vote.
One of the main pollsters asked about the image of the current president, the two previous presidents, the two main governors in office, and the president of the main political party in the opposition. Of course, the best way to display the results is through some kind of data visualization technique.
In the following, I indicate 2 alternatives that Plotly allows to display the results of electoral surveys.
It is considered that a survey should be symmetrical in the sense of having an equal number of positive or favorable responses as negative or unfavorable ones. Of course, the neutral response should not be absent (so as not to force the respondent to choose any side when his or her position is strictly neutral). Nor should the “Don’t Know-Don’t Answer” option be absent in order to include the level of ignorance of the respondents of the item under study.
One of the most commonly used charts to display survey results is the 100% Stacked Horizontal Bar Chart. A stacked bar chart shows a set of bars or rectangles, each of which represents a main category divided into subcategories. The numerical value of each subcategory is indicated by the length of rectangular segments that are stacked one after the other. In the particular case of survey results, the rectangles are oriented horizontally. In addition, in the case of 100% stacked bars, each segment represents the percentage of each subcategory, with the precaution that the sum of the percentages must equal 100.
The following 100% Stacked Bar Chart shows the image level of the main Argentine politicians. The graph is sorted in descending order according to positive image. The order of the stacked bars is as follows: positive image; neutral image; negative image; ignorance of the politician.
The code to obtain Fig.1 is as follows:
First, we import Plotly Express as px, the plotly.graph_objects module as go, and the Pandas and Colorlover libraries as pd and cl respectively:
import plotly.express as pximport plotly.graph_objects as goimport pandas as pdimport colorlover as cl
We then created the dataframe with the survey results, and sorted it according to the level of the positive image:
surv = {‘Pol_List’: [‘PB’, ‘HRL’, ‘AF’, ‘CFK’, ‘MM’, ‘AK’],        ‘Posit’: [43.9, 39.8, 26.8, 26.7, 24.8, 21.7],        ‘Neutr’: [14.3, 25.5, 10.6, 6.0, 26.4, 9.1],        ‘No_Op’: [4.8, 4.2, 0.4, 0.5, 0.7, 2.1],        ‘Negat’: [37.0, 30.5, 62.2, 66.8, 48.1, 67.1]        }df_surv = pd.DataFrame(surv)df_surv = df_surv.sort_values(by=’Posit’, ascending = True)images  = [‘Negat’, ‘No_Op’, ‘Neutr’, ‘Posit’]
Now, we are ready to create our graph.
The usual procedure with plotly is to use the method .add_trace(go.Bar()) to generate the figure and then manipulate it with the methods .update_layout() and .update_xaxes. Finally, display the diagram with .show() and export it with .write_image().
We need to place the .add_trace(go.Bar()) method inside a for loop to traverse the dataframe extracting the initials of the politicians (y = df_surv[‘Pol_List’]) and the quantitative values recorded in the survey (x = df_surv[column]). With orientation = ‘h’ we indicate that our bars will be oriented horizontally, and with text and textposition we will include the numerical values inside the rectangular segments. cl.scales selects a color scale that allows to quickly discern the positive values from the negative ones.
fig.update.layout allows us to set the title text and font, as well as the orientation and location of the legends. It is very important to include barmode = ‘stack’ to ensure that the bar segments are stacked one after the other.
Finally, we indicate that in the x-axis we do not want to place the scale or the grid as they are not necessary to our storytelling.
fig = go.Figure()for column in df_surv.columns[1:5]:    fig.add_trace(go.Bar(                  x = df_surv[column],                  y = df_surv[‘Pol_List’],                  name = column,                  orientation = ‘h’,                  text = df_surv[column],                  textposition = ‘auto’,                    marker_color=cl.scales[                                 str(len(images))]’div’]’RdBu’]                                             images.index(column)]                 ))fig.update_layout(                  title = ‘Image of Argentinian Politicians’,                  title_font= dict(family = ‘Arial’, size = 30),                  barmode = ‘stack’,                  legend_orientation =’h’,                  legend_traceorder=”normal”,                  legend_x = 0.10, legend_y = 1.1,                  )fig.update_xaxes(                 visible = False,                 showgrid = False)fig.write_image(path + ‘Survey1.png’)fig.show()
The diverging stacked bar charts are an extension of the previously described stacked bars with the addition of a vertical baseline. The conceptual idea is that negative responses are placed to the left of that baseline, while positive responses are placed to the right of it. This particular design allows for easy comparison between positive or favorable responses and unfavorable or negative responses for a wide variety of categories.
Each bar is separated into horizontal rectangular segments, stacked one after the other. As is characteristic of all bar charts, the length of each segment is proportional to the numerical value to be represented. This numerical value can be absolute or percentage depending on the characteristics of the data set to be plotted.
In order to place the negative responses to the left of the baseline we proceed to convert the corresponding column into negative values and sort according to that column:
df_surv['Negat'] = df_surv['Negat'] * -1df_surv = df_surv.sort_values(by=['Negat'], ascending = False)
We proceed to draw the graph with the same method .add_trace(go.Bar()) using a code equivalent to the one previously indicated with the 100% stacked horizontal bars.
fig2 = go.Figure()for column in df_surv.columns[1:5]:    fig2.add_trace(go.Bar(                   x = df_surv[column],                   y = df_surv['Pol_List'],                   name = column,                   orientation = 'h',                   text = df_surv[column],                   textposition = 'auto',                   marker_color=cl.scales[                                 str(len(images))]’div’]’RdBu’]                                             images.index(column)]                 ))
We must make two changes in the update.layout method: now we indicate barmode = ‘relative’ and legend_traceorder=”reversed”.
fig2.update_layout(                   title   = 'Image of Argentinian Politicians',                   title_font= dict(family = 'Arial', size = 30),                   barmode = 'relative',                   legend_orientation ='h',                   legend_traceorder="rerversed",                   legend_x = 0.10, legend_y = 1.1,                   )
Then we draw the central vertical line that characterizes the divergent stacked bars:
# Draw a central vertical linefig2.add_shape(type='line',               x0 = 0.5, y0 = -0.5,               x1 = 0.5, y1 = 5.5,               line=dict(color = 'yellow', width = 3))
On the x-axis we leave the default options to show the numerical values and the grid that now do enhance the storytelling:
fig2.update_xaxes(                  visible  = True,                  showgrid = True)fig2.write_image( path + 'Divergent1.png')fig2.show()
A common alternative among the divergent stacked bars is to place the neutral responses between the positive and negative responses and to draw the vertical line in the center of the segment representing the neutral responses. Fig. 3 shows a diagram made with Matplotlib with these characteristics: SD: Strongly Disagree; D: Disagree; N: Neutral; A: Agree; SA: Strongly Agree. It is evident that this layout makes it difficult to perform a direct comparison between positive and negative responses since they do not begin at the same baseline.
It’s up to you whether alternative you prefer to tell your story.
If you find this article of interest, please read my previous (https://medium.com/@dar.wtz):
Lollipop & Dumbbell Charts with Plotly, Mean or Median?
towardsdatascience.com
Slope Charts, Why & How, Storytelling with Slopes