Bewegungsmuster und Führungsdynamik bei Brieftauben

Projektarbeit im Modul Patterns & Trends in Environmental Data FS25

Autor:in

Dominik Erni und Sven Krieg

Veröffentlichungsdatum

29. April 2025

Verwendete R-Pakete
library(dplyr)
library(tidyverse)
library(future.apply)
library(geosphere)
library(ggplot2)
library(ggsci)
library(pheatmap)
library(readr)
library(scales)
library(sf)
library(SimilarityMeasures)
library(stars)
library(tidyverse)
library(tmap)
library(purrr)
library(ggspatial)
library(viridis)
library(RColorBrewer)
library(leaflet)

Zusammenfassung

XX

Einleitung

Students have mastered the theory and use it for their argument, with references • Students show understanding of theory

What has been done in terms of literature review?

In dieser Projektarbeit untersuchen wir das Flugverhalten von Brieftauben auf Grundlage hochaufgelöster GPS-Daten. Mithilfe von Sinuosität, Geschwindigkeitsanalysen und Ähnlichkeitsmassen vergleichen wir die unterschiedlichen Flugphasen und Flugrouten. Zusätzlich analysieren wir das «Lead and Follow»-Muster innerhalb des Taubenschwarms. Ziel ist es, wiederkehrende Bewegungsmuster durch die Anwendung verschiedener Analysekonzepte zu identifizieren.

Forschungsfragen

Research questions are clear, appropriate, realistic, addressable

Are study design choices motivated from the theory CMA?

Originality? Is it a simple extension of the exercises or are there own ideas?

Das Ziel der Projektarbeit ist die Untersuchung der folgenden Forschungsfragen:

  • Wie lassen sich Flugbahnen von Brieftauben anhand von Sinuosität und Ähnlichkeitsmassen konzeptualisieren und vergleichen?

  • Gibt es wiederkehrende Muster in der Fluggeschwindigkeit von Brieftauben über unterschiedliche Flugphasen und wie können diese quantifiziert werden?

  • Kann in den Bewegungsmustern von Brieftauben eine «Lead and Follow»-Dynamik identifiziert werden?

Erwartungen

Wir erwarten, dass die Brieftauben in der Nähe der Freilassungsstelle und des Taubenschlags eine kurvenreiche Flugbahn mit hoher Sinuosität zeigen (Laube u. a. 2007) und nach einer Anfangsphase geradliniger und mit höherer Geschwindigkeit fliegen (Schiffner und Wiltschko 2009). Trotz der leicht unterschiedlichen Freilassungsstellen und variierenden Flugrouten wird er- wartet, dass die aufgenommenen Flüge aufgrund ähnlicher Flugmuster hohe Ähnlichkeitsmasse aufweisen. Zudem erwarten wir, dass es innerhalb des Taubenschwarms Individuen gibt, welche deutlich häufiger die Führung des Schwarms übernehmen (Nagy u. a. 2010).

Material und Methoden

Für die Bearbeitung der Forschungsfragen gehen wir wie folgt vor: 1. Datenaufbereitung und Resampling (Auflösung auf eine Sekunde hochskalieren) 2. Filterung des Datensatz auf die zu untersuchenden Individuen und Flüge: - Flug Nr. 4 zum Vergleich innerhalb eines Fluges - Individuum S zum Vergleich der Flüge 3. Aufteilung und Bearbeitung der Forschungsfragen in R in die vier Teilanalysen zu den Themen Sinuosität, Ähnlichkeit, Geschwindigkeit und «Lead and Follow»

Daten

Wir verwenden einen Datensatz mit Bewegungsdaten zum Flugverhalten von Brieftauben aus dem Paper von (Santos u. a. 2014). Der Datensatz enthält GPS-Daten von fünf Heimflügen, die jeweils mit denselben 9-10 Individuen über einen Zeitraum von 12 Tagen durchgeführt wurden. Die Daten sind über die gesamte Flugzeit von durchschnittlich 15 bis 20 Minuten hochaufgelöst mit einer Lokalisierung jede Viertelsekunde pro Individuum. Die Fragestellungen beantworten wir mit den folgenden Attributen aus dem beschriebenen Datensatz: Zeitstempel, Koordinaten, Flugnummer und Individuum-Nummer. Der Datensatz enthält zusätzlich für jede Messung Angaben zur Flughöhe über dem Referenzellipsoid und der Fluggeschwindigkeit.

Flugrouten (pink) der Brieftauben aus dem Datensatz

Flugrouten (pink) der Brieftauben aus dem Datensatz

Resampling kurz begründen/erklären.

Die angegebene Fluggeschwindigkeit wird zur maximalen Kontrolle und Transparenz (Laube 2014) nicht verwendet und selbst aus den Daten berechnet.

Begrenzung auf Flug 4 und Individuum «S» begründen / erklären.

Begrenzung unserer Berechnungen auf den zweidimensionalen Raum

Weitere Informationen / Erkenntnisse unsererseits zu den Daten: - Flugrichtung - Start und Ende der Flüge wurden aus dem Datensatz entfernt.

Für die Auswertung werden keine zusätzlichen Daten benötigt.

Der Datensatz von Santos et al. 2014 ist öffentlich über die Movebank Plattform (Kays u. a. 2022) erhältlich.

Datenvorverarbeitung

Resampling

Resampling
# CSV-Datei importieren
Flugdaten = read_delim("data/Leadership in homing pigeon flocks (Columba livia) (data from Santos et al. 2014).csv")

# Aufteilung in separate Flüge
unique(Flugdaten$comments)
# Resultat: homing flight 1, homing flight 2, homing flight 4, homing flight 5, homing flight 3

Flugliste = split(Flugdaten, Flugdaten$comments)
for (name in names(Flugliste)) {
  assign(paste0(name), Flugliste[[name]])
}

# Umbenennen der Data Frames
alte_namen <- ls(pattern = "^homing flight [1-5]$")

for (name in alte_namen) {
  nummer <- strsplit(name, " ")[[1]][3]
  neuer_name <- paste0("flug", nummer)
  assign(neuer_name, get(name))
  rm(list = name)
}

# Resample auf 1-Sekunden-Intervalle
resample <- function(df) {
  gemeinsame_zeiten <- Reduce(intersect, split(df$timestamp, df$`tag-local-identifier`))
  df |> 
    filter(timestamp %in% gemeinsame_zeiten) |> 
    group_by(`tag-local-identifier`) |> 
    arrange(timestamp) |> 
    mutate(rn = row_number()) |> 
    filter(rn %% 4 == 1)
    ungroup() |> 
    select(-rn) |> 
    arrange(`tag-local-identifier`, timestamp)
}

# Anwenden auf alle Flüge
for (n in ls(pattern = "^flug[1-5]$")) {
  assign(paste0(n, "_resample"), resample(get(n)))
}

# # CSV-Dateien exportieren
# resample_namen <- ls(pattern = "^flug[1-5]_resample$")
# 
# for (name in resample_namen) {
#   df <- get(name)
#   write_csv(df, paste0(name, ".csv"))
# }

Ableitung Attribut Geschwindigkeit

Code Herleitung Speed anhand Moving Window
Alle <- read_delim("data/Resample_Flüge/Alle_Fluege_resample.csv")

# Functions moving windows

difftime_secs <- function(x, y){
  as.numeric(difftime(x, y, units = "secs"))
}

distance_by_element <- function(later, now){
  as.numeric(
    st_distance(later, now, by_element = TRUE)
  )
}


# SF Object ---------------------

Alle_geschw <- Alle |>
  st_as_sf(coords = c('location-lat', 'location-long'), crs = 4326) |> 
  select('event-id', timestamp)


# Calculate speed at scale 1 (2s = offset 1) ---------------


# 1. timelag:
now <- Alle_geschw$timestamp
prev <- lag(now)
later <- lead(now)

Alle_geschw <- Alle_geschw |> 
  mutate(
        timelag = difftime_secs(later, prev)
        )

# 2. steplenght:
now <- Alle_geschw$geometry
prev <- lag(now)
later <- lead(now)

Alle_geschw <- Alle_geschw |> 
  mutate(
        steplenght = distance_by_element(later, prev)
        )

# 3. speed:
Alle_geschw <- Alle_geschw |> 
  mutate(
        speed_2s = steplenght/timelag,
        speed_2s = ifelse(speed_2s < 0, NA, speed_2s)
        )

## Calculate speed at scale 2 (4s = offset 2) --------------


# 1. timelag:
now <- Alle_geschw$timestamp
prev <- lag(lag(now))
later <- lead(lead(now))

Alle_geschw <- Alle_geschw |> 
  mutate(
        timelag2 = difftime_secs(later, prev)
        )

# 2. steplenght:
now <- Alle_geschw$geometry
prev <- lag(lag(now))
later <- lead(lead(now))

Alle_geschw <- Alle_geschw |> 
  mutate(
        steplenght2 = distance_by_element(later, prev)
        )

# 3. speed:
Alle_geschw <- Alle_geschw |> 
  mutate(
        speed_4s = steplenght2/timelag2,
        speed_4s = ifelse(speed_4s < 0, NA, speed_4s)
        )



## Calculate speed at scale 3 (8s = offset 4) -------------


# 1. timelag:
now <- Alle_geschw$timestamp
prev <- lag(now, n = 4) # use n = ... rather than lag(lag(...)).
later <- lead(now, n = 4)

Alle_geschw <- Alle_geschw |> 
  mutate(
        timelag3 = difftime_secs(later, prev)
        )

# 2. steplenght:
now <- Alle_geschw$geometry
prev <- lag(now, n = 4)
later <- lead(now, n = 4)

Alle_geschw <- Alle_geschw |> 
  mutate(
        steplenght3 = distance_by_element(later, prev)
        )

# 3. speed:
Alle_geschw <- Alle_geschw |> 
  mutate(
        speed_8s = steplenght3/timelag3,
        speed_8s = ifelse(speed_8s < 0, NA, speed_8s)
        )

## Compare speed across scales ---------------


# Alle_geschw |> 
#   st_drop_geometry() |> 
#   select(timestamp, speed_2s, speed_4s, speed_8s)

# To compare with ggplot (Boxplots), we need a long format:
Alle_geschw_2 <- Alle_geschw |> # simplify dataframe
  st_drop_geometry() |> 
  select(timestamp, speed_2s, speed_4s, speed_8s)

Alle_geschw_long <- Alle_geschw_2 |>  # pivot long
  pivot_longer(c(speed_2s, speed_4s, speed_8s))

# # ggplot:
# ggplot(Alle_geschw_long, aes(name, value)) +
#   # we remove outliers to increase legibility, analogue
#   # Laube and Purves (2011)
#   geom_boxplot(outliers = FALSE) +
#   labs(x = "Type of speed-(window)",  y = "speed [m/s]") +
#   theme_classic()


# Join back to Alle Dataset:

Alle_geschw_subset <- Alle_geschw |> 
  st_drop_geometry() |> 
  select('event-id', speed_2s, speed_4s, speed_8s)

# Left Join anhand der event-id
Alle <- Alle |> 
  left_join(Alle_geschw_subset, by = 'event-id')

Alle_geschw_comp <- Alle |> 
  select('event-id', timestamp, 'ground-speed', 'individual-local-identifier', speed_2s, speed_4s, speed_8s)

# ggplot:

Alle_geschw_long <- Alle |>  # pivot long
  pivot_longer(c('ground-speed', speed_2s, speed_4s, speed_8s))

Die berechneten Geschwindigkeiten (speed_2s bis speed_8s) unterschieden sich signifikant von der Logger-Geschwindigkeit (ground-speed). Diese Differenz kann durch die vernachlässigte Dimension der Höhe sowie die unterschiedliche Schätzung/Berechnung der Geschwindigkeit im Vergleich zum GPS-Logger erklärt werden.

Code Vergleich Ground Speed
ggplot(Alle_geschw_long, aes(name, value)) +
  # we remove outliers to increase legibility, analogue
  # Laube and Purves (2011)
  geom_boxplot(outliers = FALSE) +
  labs(x = "Type of speed",  y = "speed [m/s]") +
  theme_classic()

Die durch die “Moving-Window-Methode” berechneten Geschwindigkeiten unterscheiden sich untereinander nicht signifikant. Die mittlere Geschwindigkeit nimmt vom “Window” von zwei Sekunden im Vergleich zum “Window” von acht Sekunden geringfügig etwas ab.

Übertragung Ground Speed aus Herleitung
Alle <- Alle |> 
  mutate(`ground-speed` = speed_4s) |>    # ground-speed überschreiben
  select(-starts_with("speed_"))             # alle anderen speed-Spalten löschen

Für die weitere Analysen wird mit der Geschwindigkeit vom “Moving-Window” von vier Sekunden weitergearbeitet.

Begrenzung der Daten auf Flug 4 und Individuum S

Code Begrenzung des Datensatzes
# Analyse wird vereinfacht durchgeführt mit: Flug 4, Individum "S"

Flug_4 <- Alle |> 
  filter(comments == "homing flight 4")
Ind_S <- Alle |> 
  filter(`individual-local-identifier` == "S")

Ähnlichkeit

Geschwindigkeit

Die verschiedenen Flüge des Individuums “S” Ind_S sowie alle Individuen aus Flug 4 Flug_4 wurden jeweils in gleiche Zeitsegmente eingeteilt. Dazu wurde die Funktion detect_segments_limited() eingesetzt, die auf jedem Flug oder Individuum die relative Zeit (time_rel) ermittelt und daraus gleich grosse Segmente bildet. Die Flüge wurden je in eine Anzahl von acht Segmenten unterteilt. Hierdurch konnten Flugverläufe unterschiedlicher Dauer vergleichbar gemacht werden. Für die Visualisierung wurden zwei Ansätze gewählt:

  1. Zum einen wurden Trajektorien der Flüge kartiert, wobei die mittlere Geschwindigkeit pro Segment farblich abgestuft dargestellt wurde.

  2. Zum anderen wurde die mittlere Segmentgeschwindigkeit in Abhängigkeit von der relativen Flugposition (Start bis Ende) geplottet. Zur Glättung der Geschwindigkeitsverläufe wurde ein loess-Glätter verwendet, ein nichtparametrisches Regressionsverfahren, das lokale lineare Fits anwendet.

Code Segmentierung alle Flüge des Individuums S
# Folgend werden die Geschwindigkeitsmuster für die erste Analyseeinheit (alle Flüge des Individuums "S") berechnet:


# Max. Anzahl Segmente pro Flug
max_segments <- 8
max_cpts <- max_segments - 1

# Liste der Flüge
fluege <- unique(Ind_S$comments)


# Funktion zur Einteilung der Flüge in gleiche Zeitsegmente
detect_segments_limited <- function(flug_name) {
  df <- Ind_S |> 
    filter(comments == flug_name) |> 
    arrange(timestamp)
  
  if (nrow(df) < 5) return(NULL)  # Optional: Wenn zu wenig Daten
  
  df <- df |> 
    mutate(
      total_time = as.numeric(difftime(max(timestamp), min(timestamp), units = "secs")),
      time_rel = as.numeric(difftime(timestamp, min(timestamp), units = "secs")) / total_time,
      segment = cut(time_rel, breaks = seq(0, 1, length.out = max_segments + 1),
                    labels = FALSE, include.lowest = TRUE),
      flug = flug_name
    )
  
  return(df)
}

# Alle Flüge in Segmente aufteilen
seg_df_limited <- map_df(fluege, detect_segments_limited)



# Segmentindex als Grundlage für diverse Plots:
seg_summary <- seg_df_limited |>
  group_by(flug, segment) |>
  summarise(mean_speed = mean(`ground-speed`, na.rm = T),
            segment_index = first(segment),
            n_segments = n_distinct(segment),
            .groups = "drop") |>
  group_by(flug) |>
  mutate(segment_position = segment_index / max(segment_index))



ggplot(Ind_S, aes(x = comments, y = `ground-speed`)) +
  geom_boxplot(fill = "lightblue", alpha = 0.7) +
  coord_flip() +
  labs(title = "Geschwindigkeitsverteilung pro Flug",
       x = "Flug", y = "Geschwindigkeit (m/s)") +
  theme_minimal()

Code Segmentierung alle Flüge des Individuums S
# Plot Segmentierung über alle Flüge inkl. Geschwindigkeitsverläufe:
ggplot(seg_df_limited, aes(x = timestamp, y = `ground-speed`)) +
  geom_line(aes(group = segment, color = as.factor(segment)), size = 1) +
  geom_vline(data = seg_df_limited |>
               group_by(flug, segment) |>
               summarise(x = min(timestamp), .groups = "drop"),
             aes(xintercept = as.numeric(x)),
             color = "black", linetype = "dashed") +
  geom_segment(data = seg_df_limited |>
                 group_by(flug, segment) |>
                 summarise(x_start = min(timestamp),
                           x_end = max(timestamp),
                           y = mean(`ground-speed`, na.rm = T), .groups = "drop"),
               aes(x = x_start, xend = x_end, y = y, yend = y),
               inherit.aes = FALSE,
               color = "black", linetype = "solid", size = 0.8) +
  facet_wrap(~ flug, scales = "free_x", ncol = 1) +
  scale_color_brewer(palette = "Set2") +
  labs(title = "Relative Segmentierung mit 8 Segmenten pro Flug",
       x = "Zeit", y = "Geschwindigkeit (m/s)", color = "Segment") +
  theme_minimal(base_size = 14)

Code Segmentierung alle Individuen des Fluges Nr 4
# Folgend werden die Geschwindigkeitsmuster für die zweite Analyseeinheit (Alle Individuen des Fluges 4) berechnet:



# Max. Anzahl Segmente pro Flug
max_segments <- 8
max_cpts <- max_segments - 1

# Liste der Flüge
fluege <- unique(Flug_4$`individual-local-identifier`)


# Funktion zur Einteilung der Flüge in gleiche Zeitsegmente
detect_segments_limited <- function(indiv_name) {
  df <- Flug_4 |> 
    filter(`individual-local-identifier` == indiv_name) |>
    arrange(timestamp)
  
  if (nrow(df) < 5) return(NULL)  # Optional: Wenn zu wenig Daten
  
  df <- df |> 
    mutate(
      total_time = as.numeric(difftime(max(timestamp), min(timestamp), units = "secs")),
      time_rel = as.numeric(difftime(timestamp, min(timestamp), units = "secs")) / total_time,
      segment = cut(time_rel, breaks = seq(0, 1, length.out = max_segments + 1),
                    labels = FALSE, include.lowest = TRUE),
      indiv = indiv_name
    )
  
  return(df)
}

# Alle Flüge in Segmente aufteilen
seg_df_limited <- map_df(fluege, detect_segments_limited)



# Segmentindex als Grundlage für diverse Plots:
seg_summary <- seg_df_limited |>
  group_by(indiv, segment) |>
  summarise(mean_speed = mean(`ground-speed`, na.rm = T),
            segment_index = first(segment),
            n_segments = n_distinct(segment),
            .groups = "drop") |>
  group_by(indiv) |>
  mutate(segment_position = segment_index / max(segment_index))
 

ggplot(Flug_4, aes(x = `individual-local-identifier`, y = `ground-speed`)) +
  geom_boxplot(fill = "lightblue", alpha = 0.7) +
  coord_flip() +
  labs(title = "Geschwindigkeitsverteilung pro Individuum",
       x = "ID", y = "Geschwindigkeit (m/s)") +
  theme_minimal()

Code Segmentierung alle Individuen des Fluges Nr. 4
# Plot Segmentierung über alle Flüge inkl. Geschwindigkeitsverläufe:
ggplot(seg_df_limited, aes(x = timestamp, y = `ground-speed`)) +
  geom_line(aes(group = segment, color = as.factor(segment)), size = 1) +
  geom_vline(data = seg_df_limited |>
               group_by(indiv, segment) |>
               summarise(x = min(timestamp), .groups = "drop"),
             aes(xintercept = as.numeric(x)),
             color = "black", linetype = "dashed") +
  geom_segment(data = seg_df_limited |>
                 group_by(indiv, segment) |>
                 summarise(x_start = min(timestamp),
                           x_end = max(timestamp),
                           y = mean(`ground-speed`, na.rm = T), .groups = "drop"),
               aes(x = x_start, xend = x_end, y = y, yend = y),
               inherit.aes = FALSE,
               color = "black", linetype = "solid", size = 0.8) +
  facet_wrap(~ indiv, scales = "free_x", ncol = 1) +
  scale_color_brewer(palette = "Set2") +
  labs(title = "Relative Segmentierung mit 8 Segmenten pro Individuum",
       x = "Zeit", y = "Geschwindigkeit (m/s)", color = "Segment") +
  theme_minimal(base_size = 14)

ChatGPT wurde beigezogen.

Sinuosität

Führungsdynamik

Der Datensatz des Fluges Nr. 4 aller Individuen wurde zunächst nach der individuellen Kennung individual-local-indentifier der Tauben und dem Zeitstempel timestamp sortiert, um eine klare zeitliche Reihenfolge zu gewährleisten. Anschliessend wurde für jedes Individuum der Positionsunterschied zwischen aufeinander folgenden Zeitpunkten berechnet, indem die geographische Koordinate des nächsten Punktes (location-long, location-lat) für jede Taube ermittelt wurde. Dies ermöglichte es, die Bewegungsrichtung der einzelnen Individuen durch Berechnung der Differenzwerte dx und dy festzulegen.

Im nächsten Schritt wurde der mittlere Schwarmvektor für jeden Zeitstempel berechnet, um die allgemeine Flugrichtung des gesamten Schwarms zu bestimmen. Dieser Schwarmvektor setzte sich aus dem Mittelwert der Bewegungsdifferenzen (dx, dy) aller Individuen zu einem bestimmten Zeitpunkt zusammen. Um zu erfassen, wie sich jedes Individuum im Verhältnis zum Schwarm verhält, wurde für jedes Individuum der relative Positionsvektor zum Schwarmmittelpunkt berechnet. Mit diesem Vektor wurde die Projektion auf die Schwarmbewegungsrichtung berechnet, um zu ermitteln, wie stark jedes Individuum in die Richtung des Schwarms fliegt.

Anschliessend wurde für jeden Zeitstempel das Individuum identifiziert, dessen Projektionswert den höchsten Wert aufwies, was darauf hinweist, dass es sich in diesem Zeitraum in der Führungsposition befand. Diese Informationen ermöglichten es, zu bestimmen, welche Taube zu welchem Zeitpunkt die Führungsrolle übernahm.

Zur Visualisierung des Führungsverhaltens wurden die Führungsanteile über die Zeit in einem Histogramm dargestellt. Zusätzlich wurden die Führungspositionen auf einer Karte mit leaflet visualisiert, wobei die Individuen durch unterschiedliche Farben dargestellt wurden. Eine statische Darstellung mit ggplot2 und sf ermöglichte eine detaillierte Analyse der Führungsmarkierungen über den gesamten Flugverlauf hinweg.

ChatGPT wurde beigezogen.

Resultate

What was achieved overall?

How well are the results presented?

How well are the results discussed in the light of the theory?

Ähnlichkeit

Geschwindigkeit

Code Geschwindigkeitsanalysen des Individuums S
# Liste der Flüge
fluege <- unique(Ind_S$comments)


# Funktion zur Einteilung der Flüge in gleiche Zeitsegmente
detect_segments_limited <- function(flug_name) {
  df <- Ind_S |> 
    filter(comments == flug_name) |> 
    arrange(timestamp)
  
  if (nrow(df) < 5) return(NULL)  # Optional: Wenn zu wenig Daten
  
  df <- df |> 
    mutate(
      total_time = as.numeric(difftime(max(timestamp), min(timestamp), units = "secs")),
      time_rel = as.numeric(difftime(timestamp, min(timestamp), units = "secs")) / total_time,
      segment = cut(time_rel, breaks = seq(0, 1, length.out = max_segments + 1),
                    labels = FALSE, include.lowest = TRUE),
      flug = flug_name
    )
  
  return(df)
}

# Alle Flüge in Segmente aufteilen
seg_df_limited <- map_df(fluege, detect_segments_limited)



# Segmentindex als Grundlage für diverse Plots:
seg_summary <- seg_df_limited |>
  group_by(flug, segment) |>
  summarise(mean_speed = mean(`ground-speed`, na.rm = T),
            segment_index = first(segment),
            n_segments = n_distinct(segment),
            .groups = "drop") |>
  group_by(flug) |>
  mutate(segment_position = segment_index / max(segment_index))



# Plot: Trajektorien einfärben nach der mittleren Segment-Geschwindigkeit
seg_avg_speed <- seg_df_limited |>
  group_by(flug, segment) |>
  summarise(avg_speed = mean(`ground-speed`, na.rm = TRUE)) |>
  ungroup()
seg_df_limited <- seg_df_limited |>
  left_join(seg_avg_speed, by = c("comments" = "flug", "segment" = "segment"))

ggplot(seg_df_limited |> 
         arrange(timestamp),
       aes(x = `location-long`, y = `location-lat`, color = avg_speed)) +  
  geom_path(aes(group = comments), size = 1.2) +
  scale_color_gradientn(colors = c("blue", "lightblue", "mistyrose", "red")) +  
  labs(title = "Flugtrajektorien mit mittlerer Segment-Geschwindigkeit", 
       x = "Längengrad", y = "Breitengrad", color = "Mittlere Geschwindigkeit (m/s)") +
  theme_minimal(base_size = 14) +
  theme(legend.position = "right")

Code Geschwindigkeitsanalysen des Individuums S
ggplot(seg_summary, aes(x = segment_position, y = mean_speed, color = flug)) +
  geom_point(size = 3) +
  geom_smooth(method = "loess", se = FALSE) +
  scale_color_brewer(palette = "Set1") +
  labs(title = "Mittlere Segmentgeschwindigkeit relativ zur Flugposition",
       x = "Relative Position im Flug (0 = Start, 1 = Ende)",
       y = "Mittlere Geschwindigkeit (m/s)", color = "Flug") +
  theme_minimal(base_size = 14)

Code Geschwindigkeitsanalysen des Fluges Nr 4
# Liste der Flüge
fluege <- unique(Flug_4$`individual-local-identifier`)


# Funktion zur Einteilung der Flüge in gleiche Zeitsegmente
detect_segments_limited <- function(indiv_name) {
  df <- Flug_4 |> 
    filter(`individual-local-identifier` == indiv_name) |>
    arrange(timestamp)
  
  if (nrow(df) < 5) return(NULL)  # Optional: Wenn zu wenig Daten
  
  df <- df |> 
    mutate(
      total_time = as.numeric(difftime(max(timestamp), min(timestamp), units = "secs")),
      time_rel = as.numeric(difftime(timestamp, min(timestamp), units = "secs")) / total_time,
      segment = cut(time_rel, breaks = seq(0, 1, length.out = max_segments + 1),
                    labels = FALSE, include.lowest = TRUE),
      indiv = indiv_name
    )
  
  return(df)
}

# Alle Flüge in Segmente aufteilen
seg_df_limited <- map_df(fluege, detect_segments_limited)



# Segmentindex als Grundlage für diverse Plots:
seg_summary <- seg_df_limited |>
  group_by(indiv, segment) |>
  summarise(mean_speed = mean(`ground-speed`, na.rm = T),
            segment_index = first(segment),
            n_segments = n_distinct(segment),
            .groups = "drop") |>
  group_by(indiv) |>
  mutate(segment_position = segment_index / max(segment_index))


# Plot: Trajektorien einfärben nach der mittleren Segment-Geschwindigkeit
seg_avg_speed <- seg_df_limited |>
  group_by(indiv, segment) |>
  summarise(avg_speed = mean(`ground-speed`, na.rm = TRUE)) |>
  ungroup()
seg_df_limited <- seg_df_limited |>
  left_join(seg_avg_speed, by = c("individual-local-identifier" = "indiv", "segment" = "segment"))

ggplot(seg_df_limited, 
       aes(x = `location-long`, y = `location-lat`, color = avg_speed)) +  
  geom_path(aes(group = `individual-local-identifier`), size = 1.2) +
  facet_wrap(~`individual-local-identifier`) +
  scale_color_gradientn(colors = c("blue", "lightblue", "mistyrose", "red")) +  
  labs(title = "Flugtrajektorien mit mittlerer Segment-Geschwindigkeit", 
       x = "Längengrad", y = "Breitengrad", color = "Mittlere Geschwindigkeit (m/s)") +
  theme_minimal(base_size = 14) +
  theme(legend.position = "right")

Code Geschwindigkeitsanalysen des Fluges Nr 4
ggplot(seg_summary, aes(x = segment_position, y = mean_speed, color = indiv)) +
  geom_point(size = 3) +
  geom_smooth(method = "loess", se = FALSE) +
  scale_color_brewer(palette = "Set1") +
  labs(title = "Mittlere Segmentgeschwindigkeit relativ zur Flugposition",
       x = "Relative Position im Flug (0 = Start, 1 = Ende)",
       y = "Mittlere Geschwindigkeit (m/s)", color = "Flug") +
  theme_minimal(base_size = 14)

Sinuosität

Führungsdynamik

Code Führungsdynamik Flug 4
# Anwendung auf Flug 4 - braucht mehrere Individuen zur Analyse.


Flug_4 <- Flug_4 |>
  arrange(`individual-local-identifier`, timestamp) |>
  group_by(`individual-local-identifier`) |>
  mutate(
    lon_next = lead(`location-long`),
    lat_next = lead(`location-lat`),
    dx = lon_next - `location-long`,
    dy = lat_next - `location-lat`
  ) |>
  ungroup()

# Berechne pro Zeitstempel den mittleren Schwarmvektor
schwarm <- Flug_4 |>
  group_by(timestamp) |>
  summarise(
    mean_dx = mean(dx, na.rm = TRUE),
    mean_dy = mean(dy, na.rm = TRUE),
    center_lon = mean(`location-long`, na.rm = TRUE),
    center_lat = mean(`location-lat`, na.rm = TRUE)
  )

# Join Schwarmrichtung wieder zurück
Flug_4 <- Flug_4 |>
  left_join(schwarm, by = "timestamp") |>
  rowwise() |>
  mutate(
    rel_lon = `location-long` - center_lon,
    rel_lat = `location-lat` - center_lat,
    projection = (rel_lon * mean_dx + rel_lat * mean_dy) /
                 sqrt(mean_dx^2 + mean_dy^2)
  ) |>
  ungroup()

# Wer hat pro Zeit den höchsten Projektionswert?
Flug_4 <- Flug_4 |>
  group_by(timestamp) |>
  mutate(
    is_leader = projection == max(projection, na.rm = TRUE)
  )


# Farbzuordnung für jedes individuelle Identifikationsmerkmal
color_palette <- scale_fill_manual(values = RColorBrewer::brewer.pal(length(unique(Flug_4$`individual-local-identifier`)), "Set1"))


Flug_4 |>
  filter(is_leader) |>
  count(`individual-local-identifier`) |>
  ggplot(aes(x = reorder(`individual-local-identifier`, -n), y = n, fill = `individual-local-identifier`)) +
  geom_col() +
  color_palette +  
  labs(title = "Führungsanteile über die Zeit", x = "Taube", y = "Anzahl Sekunden in Führung") +
  theme_minimal() +
  theme(legend.position = "none")  

Code Führungsdynamik Flug 4
Flug_4 |>
  filter(is_leader) |>
  count(timestamp, `individual-local-identifier`) |>
  ggplot(aes(x = timestamp, fill = `individual-local-identifier`)) +
  geom_histogram(binwidth = 5, position = "stack") +
  color_palette +  
  labs(title = "Führungszeitpunkte je Taube", x = "Zeit", y = "Anzahl Zeitpunkte (pro Bin)") +
  theme_minimal() +
  theme(legend.position = "bottom")

Code Führungsdynamik Flug 4
# Farbzuordnung für jedes individuelle Identifikationsmerkmal
color_palette <- colorFactor(palette = "Set1", domain = unique(Flug_4$`individual-local-identifier`))

# Visualisierung mit Leaflet: Nur die Punkte der Führer einfärben
leaflet(data = Flug_4 |> filter(is_leader)) |>
  addTiles() |>
  addCircleMarkers(
    lng = ~`location-long`, 
    lat = ~`location-lat`,
    color = ~color_palette(`individual-local-identifier`),  
    radius = 5,
    stroke = FALSE,
    fillOpacity = 0.7,
    label = ~paste("Leader: ", `individual-local-identifier`, 
                   "<br>Time: ", timestamp)
  ) |>
  addLegend(
    position = "bottomright", 
    pal = color_palette,
    values = unique(Flug_4$`individual-local-identifier`),
    title = "Individuen"
  )
Code Führungsdynamik Flug 4
# Konvertiere die Daten in ein sf-Objekt für ggplot2
Flug_4_sf <- Flug_4 |> 
  filter(is_leader) |>
  st_as_sf(coords = c("location-long", "location-lat"), crs = 4326)

Diskussion

Are the conclusions drawn from the project adequate and coherent?

Are problems that showed up been reported and alternative solutions proposed?

Have data science choices been discussed in the light of the theory?

XX

Anhang

How was research implemented addressing the Research questions?

What about problems and limitations and strategies overcoming these?

Simple solutions vs. own original techniques

Is the code properly commented/annotated?

Verwendung von AI transparent dokumentiert?

XX

Wordcount

Code
wordcountaddin::word_count("Report_Layout_Uebertragung_erni.qmd")
[1] 1129

How much effort was put in documenting the work?

Is the structure reasonable and clear? Length of report (approx. 15000 char (incl. spaces, incl. References list, excl. Code listing), 20000 char max)

Is the language clear and correct?

Are references used, correctly cited and listed?

If used, is the use of Generative AI (e.g. ChatGPT) documented and transparent?

Are figures and tables clear and produced to high standards?

Overall effort and investment

Literatur

Kays, Roland, Sarah C. Davidson, Matthias Berger, Gil Bohrer, Wolfgang Fiedler, Andrea Flack, Julian Hirt, u. a. 2022. „The Movebank System for Studying Global Animal Movement and Demography“. Methods in Ecology and Evolution 13 (2): 419–31. https://doi.org/10.1111/2041-210X.13767.
Laube, Patrick. 2014. Computational Movement Analysis. SpringerBriefs in Computer Science. Cham: Springer International Publishing. https://doi.org/10.1007/978-3-319-10268-9.
Laube, Patrick, Todd Dennis, Pip Forer, und Mike Walker. 2007. „Movement Beyond the Snapshot Dynamic Analysis of Geospatial Lifelines“. Computers, Environment and Urban Systems 31 (5): 481–501. https://doi.org/10.1016/j.compenvurbsys.2007.08.002.
Nagy, Máté, Zsuzsa Ákos, Dora Biro, und Tamás Vicsek. 2010. „Hierarchical Group Dynamics in Pigeon Flocks“. Nature 464 (7290): 890–93. https://doi.org/10.1038/nature08891.
Santos, Carlos D., Stefanie Neupert, Hans-Peter Lipp, Martin Wikelski, und Dina K. N. Dechmann. 2014. „Temporal and Contextual Consistency of Leadership in Homing Pigeon Flocks“. Herausgegeben von Gonzalo G. De Polavieja. PLoS ONE 9 (7): e102771. https://doi.org/10.1371/journal.pone.0102771.
Schiffner, Ingo, und Roswitha Wiltschko. 2009. „Point of Decision: When Do Pigeons Decide to Head Home?“ Naturwissenschaften 96 (2): 251–58. https://doi.org/10.1007/s00114-008-0476-7.