Figure Friday 2024 - week 44

In Figure Friday week 44 we’ll take a look at German (Federal) elections from 1953 to 2021. The data includes number of eligible voters, valid votes, invalid votes, and share of votes per political party.

If you’d like to explore data sets on local and state elections in Germany, you can find them on the GERDA data files page.

Things to consider:

  • can you improve the sample figure built?
  • would a different figure tell the data story better?
  • can you create a Dash app instead?

Sample figure:

Code for sample figure:
import pandas as pd
import plotly.express as px

df = pd.read_csv("https://raw.githubusercontent.com/plotly/Figure-Friday/refs/heads/main/2024/week-44/federal_cty_unharm.csv")
df_by_year = df.groupby('year')[['invalid_votes', 'valid_votes', 'eligible_voters', 'number_voters']].sum().reset_index()

fig = px.line(df_by_year, x="year", y=["number_voters", "eligible_voters"], text="year")
fig.update_traces(textposition="bottom right")
fig.add_vline(x=1989, line_width=3, line_dash="dash", line_color="green", annotation_text="The Berlin Wall fell in 1989.")
fig.show()

Participation Instructions:

  • Create - use the weekly data set to build your own Plotly visualization or Dash app. Or, enhance the sample figure provided in this post, using Plotly or Dash.
  • Submit - post your creation to LinkedIn or Twitter with the hashtags #FigureFriday and #plotly by midnight Thursday, your time zone. Please also submit your visualization as a new post in this thread.
  • Celebrate - join the Figure Friday sessions to showcase your creation and receive feedback from the community.

:point_right: If you prefer to collaborate with others on Discord, join the Plotly Discord channel.

Data Source:

Thank you to GERDA for the data. Please cite the accompanying paper when using this dataset:

Heddesheimer, Vincent, Hanno Hilbig, Florian Sichart, & Andreas Wiedemann. 2024. “German Election Database”.

@article{Heddesheimer2024GermanElections,
  author = {Heddesheimer Vincent, and Hanno Hilbig, and Florian Sichart and Andreas Wiedemann},
  title = {German Election Database},
  year = {2024},
  url = {https://osf.io/preprints/socarxiv/q28ex},
  doi = {https://doi.org/10.31235/osf.io/q28ex}
}

Hello “Plotlyers”. I’m being playing around for a while until I’ve found you and I was engaged immediately. Thank you @adamschroeder for your incredible altruism and contribution.
Maybe I’m not that expert ‘yet’ but I promise myself that I’ll get it. Until then, here it’s just a minimal trend in turnout across years and I’ll try to finish a choroplet before thursday!

I don’t know If I pasted the code right, if not, please sorry…

import pandas as pd
import plotly.express as px

df = pd.read_csv("https://raw.githubusercontent.com/plotly/Figure-Friday/refs/heads/main/2024/week-44/federal_cty_unharm.csv")

gr_demo = df.groupby(by=['year', 'state_name']).agg({
    'turnout': 'mean',
    'valid_votes': 'sum',
}).reset_index()

px.line(gr_demo, x='year', y='turnout', color='state_name', template='gridon',
        title='Turnout by States')
4 Likes

nice job @JuanG and welcome to the community :wave: we’re happy to have you.

You just need to wrap the code, using the button Preformatted text, which looks like </>. Can you try to edit your code in the post?

Your graph shows a clear declining trend in voter turnout from the late 70’s to the mid aughts. But what are those purple and green lines that start around 1998 and 2002?

1 Like

Done!
As for your questions, seems like there wasn’t data in those years or some mising data. In fact, in the new barplot that I’m working on, there are some NaN that I fullfilling with zeros that represents (I suppose) those parties didn’t get any votes. By the way, you guys have a new election at your doors! Nice to plot!!
This plot needs a slider. I’ll try it next…

2 Likes

Here we have the percentage of votes by party. I’ve filtered only three states to unload the bars, my idea is use this in a layout with a dropdown menu where you can select a few states to plot in. There is a loot of room for improvements…

import pandas as pd
import plotly.express as px


df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/Figure-Friday/refs/heads/main/2024/week-44/federal_cty_unharm.csv")
# df = pd.read_csv(r'data/federal_cty_unharm.csv')

party_columns = ['cdu', 'csu', 'cdu_csu', 'spd', 'gruene', 'fdp', 'linke_pds',
                 'far_right', 'far_left', 'far_left_w_linke']
party_vote_share_by_year_state = df.groupby(by=['year', 'state_name'])[
    party_columns].mean() * 100
party_vote_share_by_year_state.fillna(0, inplace=True)

gr_2021 = party_vote_share_by_year_state.loc[2021]
# gr_2021
gr_to_barplot = (gr_2021
                 .reset_index()
                 .melt(id_vars='state_name', var_name='party', value_name='share')
                 )

sample_states = ['Niedersachsen', 'Bremen', 'Schleswig-Holstein']
fig_bar = px.bar(gr_to_barplot[gr_to_barplot['state_name'].isin(sample_states)], x='party', y='share', color='state_name',
                 text='share', template='gridon',
                 title='Percentage of Votes discriminated by Party',
                 )
# y => represent share proportion
fig_bar.update_traces(texttemplate="%{y:.2f}")
fig_bar.show()
2 Likes

hi @JuanG This bar chart represents all years or just 2021? If it’s only 2021, we should let the user know, maybe in the layout. You could probably shorten the title to: Percentage of Votes by Party (2021)

1 Like

Yes, you’re right. I’ve already corrected. Here I dropped a choroplet map. It shows the ‘Turnout’ by State (maybe for German inhabitants it is much representative), but there some interesting tips that you could notice and it’s related to the “Wall fell”, as you @adamschroeder mention before in your first figure.
Before 1990 there wasn’t data related to western states and turnout was above average in Eastern states.
Starting 1990, besides the appearance of data, turnout started to decline below average that it’s pretty high indeed (almost 0,8 of valid voters).
In all election Western states (as were known before the wall felt) have less turnout that Eastern’s.

German_election2

import pandas as pd
import json
import plotly.express as px


df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/Figure-Friday/refs/heads/main/2024/week-44/federal_cty_unharm.csv")
# df = pd.read_csv(r'data/federal_cty_unharm.csv')

# <p>This idx.rows have 'inf' as values. They will be replaced by the mean of the year-corresponding-election turnout.
# Kept this as a reference: Index([5442, 5461, 5496, 5521, 5891, 5910], dtype='int64')</p>
# df[df['turnout'].isnull()].index
df.loc[5442, 'turnout'] = 0.671801
df.loc[5891, 'turnout'] = 0.714955
df.loc[5910, 'turnout'] = 0.731389
df.loc[5461, 'turnout'] = 0.672186
df.loc[5496, 'turnout'] = 0.702586
df.loc[5521, 'turnout'] = 0.694358


gr_turnout = df.groupby(by=['year', 'state_name'])[
    ['turnout']].mean().reset_index()


# This repo is where I've found the GeoJson for Germany. It's worth mentioning!
# https://github.com/isellsoap/deutschlandGeoJSON/blob/main/README.md
# https://github.com/isellsoap/deutschlandGeoJSON/blob/main/2_bundeslaender/2_hoch.geo.json

mapping_states = {
    'Bavaria': 'Bayern',
    'Hesse': 'Hessen',
    'North Rhine-Westphalia': 'Nordrhein-Westfalen',
    'Rhineland-Palatinate': 'Rheinland-Pfalz',
    'Saxony': 'Sachsen',
    'Saxony-Anhalt': 'Sachsen-Anhalt',
    'Thuringia': 'Thüringen',
}
# this replacement is neccesary to match the GeoJson
gr_turnout_replaced = gr_turnout.replace(mapping_states)

# This is my local repo: 'data/...'
with open(r"data/Germany_geo.json", mode="r", encoding="utf-8") as read_file:
    data2 = json.load(read_file)

avg_turnout = gr_turnout_replaced['turnout'].mean()

fig_map2 = px.choropleth(
    gr_turnout_replaced,
    geojson=data2,
    featureidkey='properties.name',
    color='turnout',
    range_color=(0.6, 1),
    color_continuous_scale=px.colors.diverging.BrBG,
    color_continuous_midpoint=avg_turnout,
    locations='state_name',
    scope='europe',
    labels={'turnout': 'Turnout prop'},
    animation_frame='year'
)
fig_map2.update_geos(fitbounds='locations', visible=False)
fig_map2.show()
3 Likes

Very nice work @JuanG - your contributions show me that you are an expert.

1 Like

I don’t have a visualization to contribute this week. All of the contributions thus far are brilliant, I can’t think of anything that would add value to what has already been shared.

When @adamschroeder first posted the graph of eligible and actual voters per year, I wondered why the number of voters did not increase by much after the Berlin Wall fell. As this led to the reunification of East and West Germany, the significant population increase should have had a proportional increase in the number of voters, or so one would think.

The Choropleth map from @JuanG comparing 1987 to 1990 shows that the lower turnout was mainly within East Germany. Perhaps the citizens of East Germany were not used to voting. This trend continues long after the fall of the Berlin Wall, most prominent in Saxony / Sachsen.

I guess since I can’t explain why voter turnout was low in the United States 2 days ago, I wouldn’t get far trying to figure out why it was low in East Germany. @adamschroeder & @JuanG , thank you for great contributions this week and for making me just a little bit smarter.


3 Likes

BTW, the screenshots I posted were produced using code from @JuanG. I added height and width to px.choropleth for better scaling.

1 Like

Hello Mike…
Same as you working with this dataset made me read more about the reunion of Germany (Wikipedia has several great articles about it) and as you said, I can’t even imagine what German people had to go through. Anyway and following the subject, have you seen the AP dashboard about US Election?

2 Likes

That’s a good observation @Mike_Purtell .

I saw the AP dashboard that @JuanG posted. I wonder if we should try to replicate it at next week’s figure Friday. Would you all be interested? I’ll bring this up at the figure Friday session today.

2 Likes

Thanks @JuanG for sharing the AP dashboard. They did a nice job on this.