bvv-lennart-exploration

Published

November 17, 2025

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