Dokument po difoltu prikazuje rezultate (izlaz); kod je sakriven. Klikni na dugme „Code” (gore desno, ili pored svakog bloka) da vidiš kod koji ga je proizveo.
Učitavanje i, po potrebi, instalacija svih paketa korišćenih u analizi (obrada podataka, prostorne operacije, vizuelizacija, kartogram i animacija).
pkgs <- c("readxl", "readr", "dplyr", "tidyr", "stringr",
"sf", "ggplot2", "cartogram", "scales", "patchwork",
"RColorBrewer", "rmapshaper", "gganimate", "transformr",
"gifski", "ragg")
to_install <- pkgs[!pkgs %in% rownames(installed.packages())]
if (length(to_install)) install.packages(to_install)
invisible(lapply(pkgs, library, character.only = TRUE))
Učitavaju se rezultati na nivou biračkog mesta iz
Excel fajla (list Rezultati_BM). Fajl sadrži kolone
lista_1 … lista_30; u list_names se tim
oznakama pridružuju stvarni nazivi lista (koristi se u legendi i
paletama).
xlsx_path <- "15_10_53_04.11.2026.xlsx" # <<< putanja do rezultata
bm <- read_excel(xlsx_path, sheet = "Rezultati_BM")
list_names <- c(
lista_3 = "Studentska lista - Studenti pobeđuju",
lista_1 = "Ujedinjena Srbija",
lista_2 = "Faktor Stabilnosti",
lista_5 = "Savez Vojvodjanskih Mađara",
lista_10 = "SDA Sandžaka - Čuvari Sandžaka",
lista_17 = "Albanska koalicija"
# ... dopuni; nedefinisane ostaju "lista_n"
)
list_cols <- grep("^lista_", names(bm), value = TRUE)
Glasovi se sabiraju sa biračkih mesta na nivo
opštine (po sifra_opstine). Za svaku opštinu se
zatim određuje pobednička lista (ona sa najviše
glasova), njen udeo u važećim glasovima i margina nad
drugoplasiranom.
op <- bm %>%
mutate(sifra_opstine = as.character(sifra_opstine)) %>%
group_by(sifra_opstine) %>%
summarise(
opstina = first(opstina),
upisani = sum(upisani_biraci, na.rm = TRUE),
glasali = sum(glasali_biraci, na.rm = TRUE),
vazeci = sum(vazeci_glasovi, na.rm = TRUE),
across(all_of(list_cols), ~ sum(.x, na.rm = TRUE)),
.groups = "drop"
) %>%
mutate(izlaznost = glasali / upisani)
win <- op %>%
select(sifra_opstine, vazeci, all_of(list_cols)) %>%
pivot_longer(all_of(list_cols), names_to = "lista", values_to = "glasovi") %>%
group_by(sifra_opstine) %>%
arrange(desc(glasovi), .by_group = TRUE) %>%
summarise(
pobednik = first(lista),
g1 = first(glasovi),
g2 = dplyr::nth(glasovi, 2),
vazeci = first(vazeci),
.groups = "drop"
) %>%
mutate(
udeo_pobednika = g1 / vazeci,
margina = (g1 - g2) / vazeci,
pobednik_ime = ifelse(pobednik %in% names(list_names),
list_names[pobednik], pobednik)
)
op <- op %>% left_join(
select(win, sifra_opstine, pobednik, pobednik_ime, udeo_pobednika, margina),
by = "sifra_opstine")
Granice opština se čitaju iz CSV-a sa WKT kolonom
geometry. Sloj uključuje opštine na KiM.
Koordinate su u UTM 34N (EPSG:32634), pa se CRS samo dodeljuje.
csv_path <- "opstine.csv" # <<< putanja do granica opština
geo <- read_csv(csv_path, show_col_types = FALSE) %>%
mutate(sifra_opstine = as.character(opstina_maticni_broj))
srb <- st_as_sf(geo, wkt = "geometry", crs = 32634)
srb <- st_make_valid(srb)
Rezultati i geometrija se spajaju po matičnom broju opštine — egzaktno, bez poklapanja imena. Ispis pokazuje eventualne opštine bez rezultata (npr. delovi KiM) i rezultate bez geometrije (kontrola kvaliteta spajanja).
map_df <- srb %>% left_join(st_drop_geometry(op), by = "sifra_opstine")
message("Opštine bez rezultata: ",
paste(map_df$opstina_ime[is.na(map_df$pobednik)], collapse = ", "))
message("Rezultati bez geometrije: ",
paste(op$opstina[!op$sifra_opstine %in% srb$sifra_opstine], collapse = ", "))
Paleta boja po pobedničkoj listi. Eksplicitno su definisane boje za glavne liste; za ostale (manjinske/lokalne pobednike) boje se dodeljuju automatski.
pal <- c(
"Studentska lista - Studenti pobeđuju" = "#1FA187",
"Ujedinjena Srbija" = "#4C72D4",
"Faktor Stabilnosti" = "#B2182B",
"Savez Vojvodjanskih Mađara" = "#8C7B1F",
"SDA Sandžaka - Čuvari Sandžaka" = "#E05A4F",
"Albanska koalicija" = "#D94801"
)
lev <- sort(unique(na.omit(map_df$pobednik_ime)))
extra <- lev[!lev %in% names(pal)]
if (length(extra)) pal <- c(pal, setNames(scales::hue_pal()(length(extra)), extra))
Šta prikazuje: svaka opština obojena je prema listi koja je u njoj pobedila. Ovo je geografski prikaz — površina opštine diktira vizuelni utisak (velike ruralne opštine „naduvavaju” svoju boju bez obzira na broj glasača).
m1 <- ggplot(map_df) +
geom_sf(aes(fill = pobednik_ime), color = "white", linewidth = 0.15) +
scale_fill_manual(values = pal, name = "Pobednička lista", na.value = "grey85") +
labs(title = "Pobednik po opštinama",
subtitle = "Geografski prikaz — „zemlja ne glasa\"") +
theme_void(base_size = 13) + theme(legend.position = "right")
bb <- st_bbox(map_df)
m1 + annotate("text",
x = (bb["xmin"] + bb["xmax"]) / 2,
y = (bb["ymin"] + bb["ymax"]) / 2,
label = "@aleksandarM",
size = 16, color = "grey50", alpha = 0.3, fontface = "bold")
ggsave("mapa1_pobednik.png", m1, width = 9, height = 9, dpi = 200, bg = "white")
Šta prikazuje: iste opštine kao gore, ali svaka je
krug srazmeran broju glasača (glasali),
obojen po pobedničkoj listi. Teritorija više ne dominira — vidi se gde
su glasovi stvarno koncentrisani (gradski centri
postaju veliki krugovi).
cart_src <- map_df %>% filter(!is.na(glasali), glasali > 0)
dorling <- cartogram_dorling(cart_src, weight = "glasali", k = 0.6)
m2 <- ggplot(dorling) +
geom_sf(aes(fill = pobednik_ime), color = "white", linewidth = 0.2) +
scale_fill_manual(values = pal, name = "Pobednička lista") +
labs(title = "Pobednik ponderisan brojem glasača",
subtitle = "Dorling kartogram — „...ali ljudi glasaju\"") +
theme_void(base_size = 13) + theme(legend.position = "right")
m2 + annotate("text",
x = (bb["xmin"] + bb["xmax"]) / 2,
y = (bb["ymin"] + bb["ymax"]) / 2,
label = "@aleksandarM",
size = 16, color = "grey50", alpha = 0.3, fontface = "bold")
ggsave("mapa2_dorling.png", m2, width = 9, height = 9, dpi = 200, bg = "white")
Šta prikazuje: dve prethodne mape jedna pored druge — levo geografija („zemlja”), desno glasači („ljudi”). Direktno poređenje otkriva koliko se slika menja kada se sa površine pređe na broj glasača.
steps <- (m1 + theme(legend.position = "none") + labs(subtitle = "Start: geografija")) +
(m2 + labs(subtitle = "End: glasači")) +
patchwork::plot_layout(ncol = 2, guides = "collect") +
patchwork::plot_annotation(title = "Zemlja ne glasa, ljudi glasaju",
subtitle = "Rezultati po opštinama, Srbija (sa KiM)")
steps + annotate("text",
x = (bb["xmin"] + bb["xmax"]) / 2,
y = (bb["ymin"] + bb["ymax"]) / 2,
label = "@aleksandarM",
size = 16, color = "grey50", alpha = 0.3, fontface = "bold")
ggsave("steps.png", steps, width = 16, height = 9, dpi = 200, bg = "white")
Šta prikazuje: animirani prelaz iz geografske mape u
Dorling kartogram. Boja je prednost liste 3 nad listom
1 ((lista_3 − lista_1)/vazeci): tirkizno = vodi
lista 3, plavo = vodi lista 1, belo = tesno. U pokretu se vidi kako se
velike ruralne opštine skupljaju (malo glasača), a gradovi šire.
# base <- map_df %>%
# filter(!is.na(vazeci), vazeci > 0) %>%
# mutate(prednost_l3 = (lista_3 - lista_1) / vazeci)
#
# base <- ms_simplify(base, keep = 0.02, keep_shapes = TRUE) # ubrzanje
#
# geo_s <- base %>% mutate(state = "1. Zemlja")
#
# if (file.exists("dorling.rds")) { # keširaj spor Dorling
# dor_s <- readRDS("dorling.rds")
# } else {
# dor_s <- cartogram_dorling(base, weight = "glasali", k = 0.6) %>%
# mutate(state = "2. Ljudi")
# saveRDS(dor_s, "dorling.rds")
# }
#
# anim_df <- rbind(
# geo_s %>% select(sifra_opstine, prednost_l3, state, geometry),
# dor_s %>% select(sifra_opstine, prednost_l3, state, geometry)
# )
#
# p <- ggplot(anim_df) +
# geom_sf(aes(fill = prednost_l3, group = sifra_opstine), color = NA) +
# scale_fill_gradient2(low = "#4C72D4", mid = "grey95", high = "#1FA187",
# midpoint = 0, guide = "none") + # usklađeno sa pal
# theme_void() +
# transition_states(state, transition_length = 3, state_length = 1.5) +
# ease_aes("cubic-in-out")
#
# animate(p, nframes = 50, fps = 20, bg = "white",
# device = "ragg_png", width = 700, height = 550, units = "px", res = 100,
# renderer = gifski_renderer("animacija_srbija.gif"))
base <- map_df %>%
filter(!is.na(vazeci), vazeci > 0) %>%
mutate(prednost_l3 = (lista_3 - lista_1) / vazeci)
base <- ms_simplify(base, keep = 0.02, keep_shapes = TRUE) # ubrzanje
geo_s <- base %>% mutate(state = "1. Zemlja")
if (file.exists("dorling.rds")) { # keširaj spor Dorling
dor_s <- readRDS("dorling.rds")
} else {
dor_s <- cartogram_dorling(base, weight = "glasali", k = 0.6) %>%
mutate(state = "2. Ljudi")
saveRDS(dor_s, "dorling.rds")
}
anim_df <- rbind(
geo_s %>% select(sifra_opstine, prednost_l3, state, geometry),
dor_s %>% select(sifra_opstine, prednost_l3, state, geometry)
)
bb <- st_bbox(anim_df) # za poziciju žiga
p <- ggplot(anim_df) +
geom_sf(aes(fill = prednost_l3, group = sifra_opstine), color = NA) +
scale_fill_gradient2(low = "#4C72D4", mid = "grey95", high = "#1FA187",
midpoint = 0, guide = "none") + # usklađeno sa pal
annotate("text",
x = (bb["xmin"] + bb["xmax"]) / 2, # horizontalni centar
y = bb["ymin"], # dole
label = "@aleksandarM", vjust = -0.5, size = 5,
color = "grey50", alpha = 0.4, fontface = "bold") +
theme_void() +
transition_states(state, transition_length = 3, state_length = 1.5) +
ease_aes("cubic-in-out")
animate(p, nframes = 50, fps = 20, bg = "white",
device = "ragg_png", width = 700, height = 550, units = "px", res = 100,
renderer = gifski_renderer("animacija_srbija.gif"))
Šta prikazuje: udeo liste 3 (republički pobednik) u važećim glasovima, na binovanoj skali usklađenoj sa paletom pobednika (tirkizno = visok udeo liste 3, plavo liste 1 = nizak, centar na 50%). Levo geografija, desno Dorling — ista skala na obe.
baseR <- map_df %>%
filter(!is.na(vazeci), vazeci > 0) %>%
mutate(udeo_l3 = 100 * lista_3 / vazeci)
dorR <- cartogram_dorling(baseR, weight = "glasali", k = 0.6)
div_cols <- colorRampPalette(c("#4C72D4", "grey95", "#1FA187"))(9) # lista_1 -> lista_3
fill_l3 <- scale_fill_stepsn(
colours = div_cols,
breaks = c(35, 40, 45, 50, 55, 60, 65),
limits = c(25, 75),
name = "Procenat glasova za listu 3 (Studenti pobeđuju)",
na.value = "grey85",
guide = guide_colorsteps(barwidth = 20, barheight = 0.6,
title.position = "top", title.hjust = 0.5)
)
m_geo <- ggplot(baseR) + geom_sf(aes(fill = udeo_l3), color = NA) +
fill_l3 + theme_void()
m_dor <- ggplot(dorR) + geom_sf(aes(fill = udeo_l3), color = "white", linewidth = 0.1) +
fill_l3 + theme_void()
fig <- (m_geo + m_dor) + plot_layout(guides = "collect") &
theme(legend.position = "bottom")
fig + annotate("text",
x = (bb["xmin"] + bb["xmax"]) / 2,
y = (bb["ymin"] + bb["ymax"]) / 2,
label = "@aleksandarM",
size = 14, color = "grey50", alpha = 0.3, fontface = "bold")
ggsave("steps_republika_l3.png", fig, width = 16, height = 7, dpi = 200, bg = "white")