bvv-lennart-exploration
Wer stellt die meisten Anfragen?
Unter denen, die die meisten Anfragen stellen, sind viele AfD-Abgeordnete.
| Top 10 Submitters | |||
|---|---|---|---|
| By number of requests | |||
| Name | District | Party | Number of Requests |
| Rackow, Johannes | TK | AfD | 82 |
| Simon, Oliver | PK | FDP | 82 |
| Müller, Christian | SP | Sonstige | 78 |
| Henkel, Denis | TK | AfD | 74 |
| Doering, Uwe | TK | Linke | 68 |
| Bittner, Denise | PK | CDU | 67 |
| Hudler, Benjamin | LI | CDU | 47 |
| Gerlof, Hannah | MT | Linke | 45 |
| Paul, David | PK | CDU | 44 |
| Egginger-Gonzalez, Dennis | SZ | Sonstige | 42 |
Auffällig ist, dass nicht nur klassische Oppositionsparteien (wie die Linke oder AfD) von ihrem Anfragenrecht Gebrauch machen: In den letzten 5 Jahren hat auch die Anzahl der Anfragen aus der CDU stark zugenommen.
Gemessen an der Zahl ihrer Abgeordneten stellt jedoch die Linke am meisten Anfragen:
Die Anzahl der gestellten Anfragen unterscheidet sich je nach Bezirk jedoch stark: In Treptow-Köpenick, Marzahn-Hellersdorf stellt die AfD einen Großteil der Anfragen während die Linke in FHain-Kreuzberg besorders aktiv ist. Hier spielen Wohn- und Mietthemen eine besondere Rolle.
Bearbeitungszeiten
Zeitreihen
In Mitte, Marzahn und Neukölln ist die Median-Bearbeitungszeit in den letzten Jahren gesteiegen. Das hängt nicht unbedingt mit steigendem Anfragenaufkommen zusammen: Zwar steigt in allen Bezirken die Anzahl der Anfragen, jedoch scheinen Bezirke wie Treptow-Köpenick, Spandau und Lichtenberg damit ohne Erhöhung der durchschnittlichen Bearbeitungszeit klarzukommen.
#gesamtentwicklung
ggplot(timeseries_slope_alldistricts, aes(x = year, y = request_perc)) +
geom_line(color = "steelblue") +
geom_line(aes(y = time_perc), color = "coral") +
# facet_wrap(~ district) +
labs(
title = "Weniger Anfragen, längere Bearbeitsungszeit",
subtitle = "Entwicklung der Anfragenzahl (blau) und der Median-Bearbeitungszeit (Orange) seit 2014",
x = "Year",
y = "Median processing time"
) +
theme_minimal()Themenanalyse (WORK IN PROGRESS, WORTFILTER NOCH SEHR BASIC)
Die häufigsten Wörter in BVV-Anfragen
seit Beginn der Aufzeichnungen (2007). Die häufigsten Begriffe sind verwaltungstechnisch. Viele Anfragen beziehen sich auf konkrete Orte, Straßen und Bauvorhaben.
Sprachgebrauch nach Parteien
# Words used by AfD / Greens
# 1. Filter only Grüne and AfD
word_freq_parties_select <- word_freq_parties %>%
filter(party %in% c("Grüne", "AfD")) %>%
select(-normalized_count)
# 2. Pivot to wide format
word_freq_parties_select_wide <- word_freq_parties_select %>%
pivot_wider(names_from = party, values_from = n, values_fill = 0)
# 3. Calculate difference metrics
word_freq_parties_diff <- word_freq_parties_select_wide %>%
mutate(
abs_diff = abs(Grüne - AfD),
total = Grüne + AfD,
rel_diff = ifelse(total > 0, abs(Grüne - AfD) / total, 0),
log_ratio = log2((Grüne + 1) / (AfD + 1)) # to avoid log(0)
) %>%
arrange(desc(rel_diff)) # or use `abs(log_ratio)` for another ranking
# 4. Show top striking words
top_differences <- word_freq_parties_diff %>%
select(word, Grüne, AfD, rel_diff, log_ratio) %>%
filter(Grüne >= 5 | AfD >= 5) %>%
arrange(desc(rel_diff))
differences_party_wording <- top_differences %>%
filter(word %in% c("innen", "baumpflege", "schuljahr", "kultur", "gesundheit", "demokratie", "grundschule", "corona", "leben", "flüchtlinge")) %>%
arrange(desc(Grüne)) %>%
write_csv("export/words_splitbars_gruene_afd.csv")
print(top_differences)# A tibble: 474 × 5
word Grüne AfD rel_diff log_ratio
<chr> <int> <int> <dbl> <dbl>
1 b90 182 0 1 7.52
2 innen 43 0 1 5.46
3 beschluss 28 0 1 4.86
4 baumpflege 26 0 1 4.75
5 schuljahr 25 0 1 4.70
6 haushaltsplanaufstellung 24 0 1 4.64
7 luv 22 0 1 4.52
8 zweckentfremdung 20 0 1 4.39
9 abteilung 19 0 1 4.32
10 kultur 16 0 1 4.09
# ℹ 464 more rows
View(top_differences)# parliament charts for selected words
selected_words <- c("innen", "baum", "kultur", "corona", "flüchtlinge")
selected_words_by_party <- word_freq_parties %>%
filter(word %in% selected_words) %>%
ungroup() %>% # add missing combinations
complete(party = factor(party, levels = party_order),
word = selected_words,
fill = list(n = 1, normalized_count = 0)
) %>%
mutate(party = factor(party, levels = party_order)) %>%
filter(!party %in% c("Piraten", "Sonstige", "BSW")) %>%
group_by(word) %>%
mutate(chart_ratio = n / (sum(n) / 6))
# add dummy parties to fake half circle
dummy_parties <- paste0("Dummy_", 1:6)
full_party_order <- c(party_order, dummy_parties)
dummy_data <- expand.grid(
party = dummy_parties,
word = selected_words,
n = 0,
normalized_count = 0,
chart_ratio = 0
)
chart_parties_halfcircle <- selected_words_by_party %>%
bind_rows(dummy_data) %>%
mutate(party = factor(party, levels = full_party_order)) %>%
arrange(word, party)# Plot partlimaent charts
# Create the base plot
ggplot(chart_parties_halfcircle, aes(x = party, y = chart_ratio, fill = party, color = party)) +
facet_wrap( ~word) +
geom_bar(stat = "identity", width = 1, alpha = 0.4, size = 0.2) +
geom_hline(yintercept = 1, color = "black", linetype = "solid", linewidth = 0.25) +
coord_polar(theta = "x", start = pi + pi/2 , direction = -1, clip = "off") +
scale_fill_manual(values = party_colors) +
scale_color_manual(values = party_colors) +
ylim(0, max(selected_words_by_party$chart_ratio)) +
theme_minimal() +
theme(
axis.title = element_blank(),
axis.text.x = element_blank(), # ⬅ remove party labels
axis.text.y = element_blank(),
axis.ticks = element_blank(),
panel.grid = element_blank(),
legend.position = "none"
)Warning in geom_bar(stat = "identity", width = 1, alpha = 0.4, size = 0.2):
Ignoring unknown parameters: `size`
# circles instead
ggplot(selected_words_by_party, aes(y = word, x = party, fill = party, color = party, size = n)) +
geom_point(shape = 21, stroke = 1, alpha = 0.6) + # shape 21 allows fill and stroke separately
geom_text(aes(label = n), color = "black", size = 3, vjust = 0.5) + # label each point with n
scale_fill_manual(values = party_colors) +
scale_color_manual(values = party_colors) +
scale_size(range = c(1, 20)) +
scale_x_discrete(position = "top") + # move x-axis labels to the top
labs(
title = "",
x = "Binned X Variable",
y = "Party Will Vote"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
axis.title = element_text(size = 12),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
legend.position = "none" # remove all legends
)## Topic Classification
topic_keywords <- list(
kriminalität = c("gewalt", "kriminalität"),
drogen = c("junkie", "drogen", "konsum", "substitution"),
müll = c("müll", "graffiti", "schmier"),
verkehr = c("parkplatz", "parkraum", "radweg", "parken", "verkehr", "baustelle"),
schulen = c("schule", "gymnasium", "unterrricht", "lehrer"),
corona = c("corona", "masken", "impf"),
sozial = c("jobcenter", "soziall", "sozialamt"),
familien = c("familie", "kind"),
geflüchtete = c("flüchtling", "asyl", "unterbringung", "geflüchtet", "wilkommens"),
wohnungslosigkeit = c("wohnungslos", "obdachlos", "bettel"),
finanzen = c("finanz", "geld", "mittel"),
gendern = c("gendern"),
rassismus = c("rassismus"),
antisemitismus = c("antisemit", "juden")
)
classify_title <- function(title, keyword_list) {
matched_categories <- names(keyword_list)[
map_lgl(keyword_list, ~ any(str_detect(str_to_lower(title), str_c(.x, collapse = "|"))))
]
matched_categories
}
# 3. Classify titles, return as list column
bvv_classified <- bvv_fork %>%
mutate(topics = map(title, ~ classify_title(.x, topic_keywords)))
# View the result
View(bvv_classified)
bvv_classified_long <- bvv_classified %>%
mutate(year = year(date_opened)) %>%
unnest(topics)
topic_summary <- bvv_classified_long %>%
count(year, topics, name = "n") %>%
arrange(year, desc(n)) %>%
group_by(year) %>%
mutate(share = round(n / sum(n) * 100, 2)) %>%
filter(year >= 2015 & year < 2025)
#View(bvv_classified_long)
write_csv(topic_summary, "export/topic_bump_chart_v1.csv")
topic_summary %>%
filter(year == 2024) %>%
write_csv("export/topic_treemap.csv")