In dieser Projektarbeit analysieren wir die Bewegungsmuster und Führungsdynamiken bei Brieftauben anhand hochaufgelösten GPS-Daten in R (R Core Team, 2024). Dazu verwenden wir einen Datensatz von (Santos et al., 2014) mit fünf Heimflügen mit je 9-10 Individuen. Die Bewegungsmuster Ähnlichkeit, Geschwindigkeit und Sinuosität werden sowohl innerhalb eines Flugs als auch flugübergreifend untersucht und verglichen.
Die vier untersuchten Ähnlichkeitsmasse zeigen, dass sich sowohl die Trajektorien der Individuen auf einem Flug als auch die Trajektorien aller Flüge in zwei bis drei grobe Cluster einteilen und sich somit Ähnlichkeiten gut berechnen lassen. Sowohl Geschwindigkeit als auch Sinuosität verändern sich im Verlauf der Flüge und lassen auf eine Orientierungsphase schliessen. Nach dieser Phase nimmt die Sinuosität der Trajektorien ab und die Fluggeschwindigkeit nimmt zu. Das Führungsverhalten zeigt, dass manche Individuen deutlich öfter eine Führungsposition einnehmen als andere.
Unsere Ergebnisse bestätigen weitgehend die in der Literatur beschriebenen Muster einer Orientierungsphase und anschliessendem beschleunigtem, geradlinigem Heimflug.
Einleitung
GPS-basierte Bewegungsdaten ermöglichen es, das Verhalten von Menschen und Tieren mit hoher raumzeitlicher Auflösung zu analysieren. Mit hochaufgelösten GPS-Daten können auch Bewegungsmustern und Führungsdynamiken von sich schnell bewegenden Arten wie der Brieftaube (Columba livia) erfasst werden (Guilford et al., 2004), (Laube, 2014). Methodische Ansätze der computergestützten Bewegungsanalyse nutzen diese Daten, um Flugmuster quantifizierbar zu machen (Laube et al., 2007), etwa anhand von Sinuosität, Geschwindigkeit oder Trajektorienähnlichkeit.
Bekannt ist, dass Brieftauben nach dem Freilassen zu Beginn des Heimflugs zunächst ein ungerichtetes, kurviges Bewegungsmuster zeigen (Orientierungsphase), bevor sie in eine zielgerichtete Heimflugphase übergehen (Schiffner & Wiltschko, 2009). Diese Übergänge spiegeln sich in Metriken wie Sinuosität oder Geschwindigkeit wider. Ergänzend lassen sich über Ähnlichkeitsmasse Vergleiche innerhalb und zwischen Flügen anstellen, etwa durch Dynamic Time Warping, Fréchet-Distance oder Edit Distance (Laube, 2014).
Auch das soziale Verhalten spielt beim Heimflug eine wichtige Rolle. Die Führungsrollen im Taubenschwarm werden wiederholt von denselben Individuen eingenommen (Nagy et al., 2010), (Santos et al., 2014). Solche hierarchischen Strukturen können über zeitlich versetzte Richtungsentscheidungen und Flugpositionen identifiziert werden (Santos et al., 2014).
Im Zentrum dieser Analyse steht nicht nur das Verhalten der Brieftauben, sondern insbesondere auch die Anwendung methodischer Ansätze zur Analyse raumzeitlicher Bewegungsdaten.
Forschungsfragen
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 Führungsdynamik identifiziert werden?
Methoden
Daten
Wir verwenden einen Datensatz mit Bewegungsdaten zum Flugverhalten von Brieftauben aus dem Paper von (Santos et al., 2014), welcher öffentlich über die Movebank Plattform (Kays et al., 2022) erhältlich ist. 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 Brieftauben wurden ungefähr 15 km östlich von ihrem Heimatschlag in Seuzach freigelassen. Die Flugrichtung aller fünf Flüge ist von Ost nach West.
Der Datensatz enthält zusätzlich für jede Messung Angaben zur Flughöhe über dem Referenzellipsoid und der Fluggeschwindigkeit. Die Fluggeschwindigkeit aus den Daten verwenden wir jedoch nicht und leiten diese zur maximalen Kontrolle und Transparenz in der Datenvorverarbeitung selbst her und vergleichen diese mit der gegebenen Fluggeschwindigkeit (ground-speed) der GPS-Logger. Für die Berechnung der Fluggeschwindigkeit und der weiteren Bewegungsmuster beschränken wir uns auf den zweidimensionalen Raum.
Für unsere Auswertungen werden keine zusätzlichen Daten benötigt.
Abbildung 1: Übersichtsplan Flugtrajektorien des Individuums S
Datenaufbereitung
Resampling
Wir arbeiteten mit hochaufgelösten GPS-Daten, die alle 0,25 Sekunden eine Messung enthalten. Für unsere statistischen Analysen reduzierten wir die Datenmenge durch Resampling auf ein Intervall von einer Sekunde. Dabei berücksichtigten wir nur jene Zeitpunkte, die bei allen Individuen eines Flugs gleichzeitig vorlagen, um eine synchrone und vergleichbare zeitliche Basis sicherzustellen.
Code
# CSV-Datei importierenFlugdaten =read_delim("Leadership in homing pigeon flocks.csv")# Aufteilung in separate FlügeFlugliste =split(Flugdaten, Flugdaten$comments)for (name innames(Flugliste)) {assign(paste0(name), Flugliste[[name]])}# Umbenennen der Data Framesalte_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-Intervalleresample <-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ügefor (n inls(pattern ="^flug[1-5]$")) {assign(paste0(n, "_resample"), resample(get(n)))}
Der Code für das Resampling wurde unter Beizug von ChatGPT und der Unterrichtsunterlagen erstellt
Begrenzung der Daten auf Flug 4 und Individuum S
Zur Fokussierung der Analyse wählten wir aus den fünf verfügbaren Flügen zufällig einen Flug aus (Flug 4), um die Flugtrajektorien der einzelnen Individuen innerhalb dieses Flugs miteinander zu vergleichen. Für den flugübergreifenden Vergleich bestimmten wir ebenfalls per Zufallsprinzip ein Individuum (Individuum S). Diese Auswahl ermöglichte eine exemplarische Analyse innerhalb des gegebenen zeitlichen und methodischen Rahmens.
Code
# -----------------------------------------------------------------------------# Filtern auf Flug 4 und Individuum S# -----------------------------------------------------------------------------# Analyse wird vereinfacht durchgeführt mit: Flug 4, Individuum "S"Alle <-read_delim("Alle_Fluege_resample.csv")Flug_4 <- Alle |>filter(comments =="homing flight 4")Ind_S <- Alle |>filter(`individual-local-identifier`=="S")
Ableitung Attribut Geschwindigkeit
Wir nehmen für unsere Ableitung der Geschwindigkeit Bezug auf die Literatur von (Laube, 2014). Dabei wurde die Geschwindigkeit in verschiedenen Massstäben analysiert und diese unterschiedlichen Werte verglichen.
Abbildung 2: Skalenübergreifende Ableitung von Bewegungsdaten Quelle: (Laube, 2014). a) Systematische Variation der Intervalloperator-Breite w zwischen den Aufnahmepunkten bei Bewegungsberechnungen. b) Reduktion der Geschwindigkeitswerte bei grösseren Berechnungsintervallen (in m·s⁻¹).
In unseren Skalen gingen wir von folgenden Intervalloperator-Breiten aus:
Zwei Sekunden (speed_2s)
Vier Sekunden (speed_4s)
Acht Sekunden (speed_8s)
Unsere daraus 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 Berechnung der Geschwindigkeit im Vergleich zum GPS-Logger erklärt werden. Die Unterschiede der Geschwindigkeiten innerhalb der drei verwendeten Intervalloperator-Breiten sind jedoch gering. Die mittlere Geschwindigkeit vonspeed_2s nimmt im Vergleich zu speed_8s geringfügig ab, wie es (Laube, 2014) in Abbildung 2 festgehalten hat.
Code
# -----------------------------------------------------------------------------# Berechnung der Geschwindigkeiten anhand der drei Intervalle# -----------------------------------------------------------------------------Alle <-read_delim("Alle_Fluege_resample.csv")# Functions moving windowsdifftime_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) )}Alle_geschw <- Alle |>st_as_sf(coords =c('location-lat', 'location-long'), crs =4326) |>select('event-id', timestamp)# -----------------------------------------------------------------------------# Intervall 1: Zwei Sekunden (Offset 1)# -----------------------------------------------------------------------------# 1. timelag:now <- Alle_geschw$timestampprev <-lag(now)later <-lead(now)Alle_geschw <- Alle_geschw |>mutate(timelag =difftime_secs(later, prev) )# 2. steplenght:now <- Alle_geschw$geometryprev <-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) )# -----------------------------------------------------------------------------# Intervall 2: Vier Sekunden (Offset 2)# -----------------------------------------------------------------------------# 1. timelag:now <- Alle_geschw$timestampprev <-lag(lag(now))later <-lead(lead(now))Alle_geschw <- Alle_geschw |>mutate(timelag2 =difftime_secs(later, prev) )# 2. steplenght:now <- Alle_geschw$geometryprev <-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) )# -----------------------------------------------------------------------------# Intervall 3: Acht Sekunden (Offset 4)# -----------------------------------------------------------------------------# 1. timelag:now <- Alle_geschw$timestampprev <-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$geometryprev <-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) )# -----------------------------------------------------------------------------# Vergleich der Geschwindigkeiten# -----------------------------------------------------------------------------Alle_geschw_2 <- Alle_geschw |>st_drop_geometry() |>select(timestamp, speed_2s, speed_4s, speed_8s)Alle_geschw_long <- Alle_geschw_2 |>pivot_longer(c(speed_2s, speed_4s, speed_8s))Alle_geschw_subset <- Alle_geschw |>st_drop_geometry() |>select('event-id', speed_2s, speed_4s, speed_8s)# Left Join anhand der event-idAlle <- 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)# Plot:Alle_geschw_long <- Alle |>pivot_longer(c('ground-speed', speed_2s, speed_4s, speed_8s))ggplot(Alle_geschw_long, aes(name, value)) +geom_boxplot(outliers =FALSE) +labs(x ="Type of speed", y ="speed [m/s]") +theme_classic()
Abbildung 3: Vergleich GPS ground-speed zu abgeleiteten und skalierten Geschwindigkeitsberechnungen
Für die weiteren Analysen verwendeten wir die Geschwindigkeit des Berechnungsintervalls von vier Sekunden speed_4s.
Code
Alle <- Alle |>mutate(`ground-speed`= speed_4s) |>select(-starts_with("speed_"))
Der Code zum Vergleich der Intervalle zur Geschwindigkeitsberechnung basiert auf der Übung 2B des Moduls Patterns & Trends in Environmental Data FS25.
Ähnlichkeit
Zur Analyse der Trajektorienähnlichkeit setzten wir vier Ähnlichkeitsmasse ein: DTW, Edit Distance, Fréchet und LCSS. Die GPS-Daten jeder Trajektorie reduzierten wir auf eine feste Punktzahl (n = 250). Die Berechnungen erfolgte mit dem R-Paket SimilarityMeasures(Toohey, 2015). DTW und EditDist erfassen die kumulierte Punktdistanz entlang optimierter Vergleichspfade während Fréchet die maximale Abweichung entlang des räumlichen Kurvenverlaufs zweier Trajektorien bestimmt. LCSS zählt die Anzahl übereinstimmender Punkte entlang monotoner Pfade, wobei wir für den Flug 4 aufgrund der sehr ähnlichen Trajektorien einen kleinen räumlichen Toleranzwert von ε = 0.1 m verwendeten. Die Anwendung von LCSS auf mehrere Flüge erwies sich als zu rechenintensiv und wurde deshalb nicht durchgeführt.
Der Code zur Analyse der Trajektorienähnlichkeit wurde unter Beizug von ChatGPT erstellt.
Geschwindigkeit
Die verschiedenen Flüge des Individuums “S” Ind_S sowie alle Individuen aus Flug 4 Flug_4 haben wir jeweils in gleiche Zeitsegmente eingeteilt. Dazu setzten wir die Funktion detect_segments_limited() ein, die für jeden Flug oder jedes Individuum die relative Zeit (time_rel) ermittelt und daraus gleich grosse Segmente bildet. Die Flüge haben wir jeweils in acht Segmenten unterteilt, wodurch die Flugverläufe unterschiedlicher Dauer vergleichbar gemacht wurden. Für die Visualisierung wählten wir zwei Ansätze:
Zum einen kartierten wir die Trajektorien der Flüge, wobei die mittlere Geschwindigkeit pro Segment farblich abgestuft dargestellt wurde.
Zum anderen stellten wir die mittlere Segmentgeschwindigkeit in Abhängigkeit der relativen Flugpositionen (Start bis Ende) auf Scatterplots dar. Zur Glättung der Geschwindigkeitsverläufe verwendeten wir ein loess-Glätter, ein nichtparametrisches Regressionsverfahren, das lokale lineare Fits anwendet.
Code
# -----------------------------------------------------------------------------# Segmentierung der Trajektorien aller fünf Flüge des Individuums S# -----------------------------------------------------------------------------Flug_4 <- Alle |>filter(comments =="homing flight 4")Ind_S <- Alle |>filter(`individual-local-identifier`=="S")# Folgend werden die Geschwindigkeitsmuster für die erste Analyseeinheit (alle Flüge des Individuums "S") berechnet:# Max. Anzahl Segmente pro Flugmax_segments <-8max_cpts <- max_segments -1# Liste der Flügefluege <-unique(Ind_S$comments)# Funktion zur Einteilung der Flüge in gleiche Zeitsegmentedetect_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 aufteilenseg_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))# -----------------------------------------------------------------------------# Übersicht Geschwindigkeitsverteilung (Boxplots) aller fünf Flüge des Individuums S# -----------------------------------------------------------------------------ggplot(Ind_S, aes(x = comments, y =`ground-speed`)) +geom_boxplot(fill ="lightblue", alpha =0.7) +coord_flip() +labs(x ="Flug", y ="Geschwindigkeit (m/s)") +theme_minimal()
Abbildung 4: Übersicht Geschwindigkeitsverteilung der Trajektorien aller fünf Flüge des Individuums S
Code
# -----------------------------------------------------------------------------# Plot der Segmentierung der Trajektorien aller fünf Flüge des Individuums S# -----------------------------------------------------------------------------# Plot: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(x ="Zeit", y ="Geschwindigkeit (m/s)", color ="Segment") +theme_minimal(base_size =14)
Abbildung 5: Segmentierung der Trajektorien aller fünf Flüge des Individuums S
Code
# -----------------------------------------------------------------------------# Segmentierung der Trajektorien der Trajektorien aller Individuen auf Flug 4# -----------------------------------------------------------------------------# Folgend werden die Geschwindigkeitsmuster für die zweite Analyseeinheit (Alle Individuen des Fluges 4) berechnet:# Max. Anzahl Segmente pro Flugmax_segments <-8max_cpts <- max_segments -1# Liste der Flügefluege <-unique(Flug_4$`individual-local-identifier`)# Funktion zur Einteilung der Flüge in gleiche Zeitsegmentedetect_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 aufteilenseg_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))# -----------------------------------------------------------------------------# Übersicht Geschwindigkeitsverteilung (Boxplots) aller Individuen auf Flug 4# ----------------------------------------------------------------------------- ggplot(Flug_4, aes(x =`individual-local-identifier`, y =`ground-speed`)) +geom_boxplot(fill ="lightblue", alpha =0.7) +coord_flip() +labs(x ="ID", y ="Geschwindigkeit (m/s)") +theme_minimal()
Abbildung 6: Übersicht Geschwindigkeitsverteilung der Trajektorien aller Individuen auf Flug 4
Code
# -----------------------------------------------------------------------------# Plot der Segmentierung der Trajektorien aller Individuen auf Flug 4# -----------------------------------------------------------------------------# Plot: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(x ="Zeit", y ="Geschwindigkeit (m/s)", color ="Segment") +theme_minimal(base_size =14)
Abbildung 7: Segmentierung der Trajektorien aller Individuen auf Flug 4
Der Code zur Analyse der Geschwindigkeit bzw. der Segmentierung wurde unter Beizug von ChatGPT erstellt.
Sinuosität
Wir berechneten die Sinuosität als Mass für die Richtungsabweichung innerhalb von Flugtrajektorien. Sie ergab sich aus dem Verhältnis der tatsächlich zurückgelegten Strecke zur Luftliniendistanz zwischen Start- und Endpunkt eines Segments. Die Berechnung erfolgte segmentweise für jeweils acht vordefinierte Abschnitte pro Flug. Innerhalb jedes Segments summierten wir die Haversine-Distanzen zwischen aufeinanderfolgenden Punkten zur Ermittlung der Streckenlänge (Funktion distHaversine(), Paket geosphere) und bestimmten die Luftliniendistanz ebenfalls mit distHaversine().
Code
# -------------------------## Daten einlesen# -------------------------## CSV-Dateien ladenflug4 <-read.csv("Flug4_Segmente.csv")alle_fluege <-read.csv("Alle_Fluege_Segmente.csv")# Spaltennamen vereinheitlichennames(flug4) <-gsub("-", ".", names(flug4))names(alle_fluege) <-gsub("-", ".", names(alle_fluege))# Zeitformat konvertierenflug4$timestamp <-as.POSIXct(flug4$timestamp, format ="%Y-%m-%dT%H:%M:%SZ", tz ="UTC")alle_fluege$timestamp <-as.POSIXct(alle_fluege$timestamp, format ="%Y-%m-%dT%H:%M:%SZ", tz ="UTC")# -------------------------## Funktion zur Sinuositätsberechnung# -------------------------## Sinuosität = zurückgelegte Strecke / Luftlinieberechne_sinuositaet <-function(df) { df <- df |>arrange(timestamp) coords <- df[, c("location.long", "location.lat")]if (nrow(coords) <2) return(NA) luftlinie <-distHaversine(coords[1, ], coords[nrow(coords), ]) strecke <-sum(distHaversine(coords[-nrow(coords), ], coords[-1, ]), na.rm =TRUE)if (luftlinie ==0) return(NA)return(strecke / luftlinie)}# -------------------------## Vergleich innerhalb des Flugs 4# -------------------------## Sinuosität pro Individuum und Segment berechnenflug4_summary <- flug4 |>group_by(tag.local.identifier, segment) |>summarise(sinuosity =berechne_sinuositaet(cur_data()), .groups ="drop") |>filter(!is.na(sinuosity))# Mittelwerte der Sinuosität pro Segment berechnenmittelwerte_flug4 <- flug4_summary |>group_by(segment) |>summarise(mean_sinuosity =mean(sinuosity), .groups ="drop")# Plot mit Segment 3ggplot(flug4_summary, aes(x =as.factor(segment), y = sinuosity, fill = tag.local.identifier)) +geom_bar(stat ="identity", position =position_dodge(width =0.8), width =0.7) +geom_line(data = mittelwerte_flug4,aes(x =as.factor(segment), y = mean_sinuosity, group =1, color ="Mittelwert"),inherit.aes =FALSE, linewidth =1.2) +geom_point(data = mittelwerte_flug4,aes(x =as.factor(segment), y = mean_sinuosity, color ="Mittelwert"),inherit.aes =FALSE, size =2) +labs(x ="Flugsegmente", y ="Sinuosität (log2)", fill ="Individuen", color ="") +scale_y_continuous(trans ="log2") +scale_fill_d3("category20") +scale_color_manual(values ="black", labels ="Mittelwert") +theme_minimal(base_size =14) +theme(legend.position ="right") +ggtitle("Sinuosität Flug 4 - Übersichtsplot")# Segment 3 ausschließenflug4_summary_filtered <- flug4_summary |>filter(segment !=3)mittelwerte_flug4_filtered <- mittelwerte_flug4 |>filter(segment !=3)# Plot ohne Segment 3ggplot(flug4_summary_filtered, aes(x =as.factor(segment), y = sinuosity, fill = tag.local.identifier)) +geom_bar(stat ="identity", position =position_dodge(width =0.8), width =0.7) +geom_line(data = mittelwerte_flug4_filtered,aes(x =as.factor(segment), y = mean_sinuosity, group =1, color ="Mittelwert"),inherit.aes =FALSE, linewidth =1.2) +geom_point(data = mittelwerte_flug4_filtered,aes(x =as.factor(segment), y = mean_sinuosity, color ="Mittelwert"),inherit.aes =FALSE, size =2) +labs(x ="Flugsegmente", y ="Sinuosität (log2)", fill ="Individuen", color ="") +scale_y_continuous(trans ="log2", breaks =c(1, 1.25, 1.5, 1.75, 2), limits =c(1, 2)) +scale_fill_d3("category20") +scale_color_manual(values ="black", labels ="Mittelwert") +theme_minimal(base_size =14) +theme(legend.position ="right") +ggtitle("Sinuosität Flug 4 - fokussierter Plot")# -------------------------## Sinuosität des Individuums S auf Flug 4 - Karte# -------------------------## Filter nach Flug 4 und Individuum Sind_s_flug4 <- flug4 |>filter(tag.local.identifier =="S") |>arrange(timestamp)# Sinuosität pro Segment berechnens_sinuo <- ind_s_flug4 |>group_by(segment) |>summarise(sinuosity =berechne_sinuositaet(cur_data()), .groups ="drop")# Sinuositätswerte mit Segmentdaten verknüpfenind_s_flug4 <- ind_s_flug4 |>left_join(s_sinuo, by ="segment")# Farbverlauf und Skalierung festlegenfarben <-c("blue", "skyblue", "lightblue", "mistyrose", "red")max_sinuo <-max(ind_s_flug4$sinuosity, na.rm =TRUE)werte <-c(1, 1.15, 1.5, 2, max_sinuo)werte_log <- scales::rescale(log(werte), to =c(0, 1))# Mittelpunkte für Segmentbeschriftung berechnenlabels <- ind_s_flug4 |>group_by(segment) |>summarise(lon =mean(location.long),lat =mean(location.lat),label =as.character(segment),.groups ="drop" )# Plotggplot(ind_s_flug4, aes(x = location.long, y = location.lat, group = segment, color = sinuosity)) +geom_path(linewidth =1.2) +geom_text(data = labels,aes(x = lon, y = lat, label = label),inherit.aes =FALSE,size =4.5, fontface ="bold", color ="black") +coord_equal() +scale_color_gradientn(colours = farben,values = werte_log,trans ="log2",limits =c(1, max_sinuo),breaks =c(1, 2, 5, 10, round(max_sinuo)),labels =c("1", "2", "5", "10", as.character(round(max_sinuo))),oob = squish,name ="Sinuosität (log2)" ) +labs(x ="Longitude", y ="Latitude") +theme_minimal(base_size =14)# -------------------------## Individuum S über alle Flüge# -------------------------## Filter nach Individuum Sind_s <- alle_fluege |>filter(tag.local.identifier =="S")# Sinuosität pro Segment und Flug berechnens_summary <- ind_s |>group_by(comments, segment) |>summarise(sinuosity =berechne_sinuositaet(cur_data()), .groups ="drop") |>filter(!is.na(sinuosity) & sinuosity >=1)# Flugbezeichnungen umcodierens_summary <- s_summary |>mutate(comments =recode(comments,"homing flight 1"="Flug 1","homing flight 2"="Flug 2","homing flight 3"="Flug 3","homing flight 4"="Flug 4","homing flight 5"="Flug 5" ))# Mittelwerte pro Segment berechnenmittelwerte_s <- s_summary |>group_by(segment) |>summarise(sinuosity =mean(sinuosity), .groups ="drop") |>mutate(comments ="Mittelwert")# Plotdaten zusammenführens_plotdata <-bind_rows(s_summary, mittelwerte_s)# Plotggplot(s_plotdata, aes(x = segment, y = sinuosity, color = comments, group = comments)) +geom_line(linewidth =1.2) +geom_point(size =2) +scale_color_manual(values =c(setNames(RColorBrewer::brewer.pal(5, "Set1"), paste0("Flug ", 1:5)),"Mittelwert"="black" ) ) +scale_x_continuous(breaks =1:8, labels =as.character(1:8)) +scale_y_continuous(trans ="log2",breaks =c(1, 1.5, 2, 4, 8, 16),labels =c("1", "1.5", "2", "4", "8", "16") ) +labs(x ="Flugsegmente",y ="Sinuosität (log2)",color ="Flug" ) +theme_minimal(base_size =14)# -------------------------## Karte: Individuum S über alle Flüge# -------------------------## Filter nach Individuum Sind_s_all <- alle_fluege |>filter(tag.local.identifier =="S") |>arrange(comments, timestamp)# Sinuosität pro Segment und Flug berechnensinuo_s_all <- ind_s_all |>group_by(comments, segment) |>summarise(sinuosity =berechne_sinuositaet(cur_data()), .groups ="drop") |>filter(!is.na(sinuosity))# Sinuositätswerte mit Segmentdaten verknüpfenind_s_all <- ind_s_all |>left_join(sinuo_s_all, by =c("comments", "segment"))# Farbverlauf und Skalierung definierenfarben <-c("blue", "skyblue", "lightblue", "mistyrose", "red")werte <-c(1, 1.25, 1.5, 2, 22)werte_log <- scales::rescale(log2(werte), to =c(0, 1))# Farbskalen-Beschriftungen definierenbreaks_custom <-c(1, 2, 5, 10, 22)labels_custom <-as.character(breaks_custom)# Plotggplot(ind_s_all, aes(x = location.long, y = location.lat, group =interaction(comments, segment))) +geom_path(aes(color = sinuosity), linewidth =0.8, alpha =0.95) +scale_color_gradientn(colours = farben,values = werte_log,trans ="log2",limits =c(1, 22),breaks = breaks_custom,labels = labels_custom,oob = squish,name ="Sinuosität (log2)" ) +coord_equal() +labs(x ="Longitude", y ="Latitude") +theme_minimal(base_size =14)
Der Code für die Analyse der Sinuosität wurde unter Beizug von ChatGPT erstellt.
Führungsdynamik
Nach Sortierung des Datensatzes des Fluges Nr. 4 berechneten wir für jedes Individuum den Positionsunterschied zwischen aufeinanderfolgenden Zeitpunkten, indem wir die geografische Koordinate des jeweils nächsten Punktes (location-long, location-lat) ermittelten. So konnten wir die Bewegungsrichtung der einzelnen Individuen durch Berechnung der Differenzwerte dx und dy festlegen.
Im nächsten Schritt berechneten wir den mittleren Schwarmvektor für jeden Zeitstempel, um die allgemeine Flugrichtung des gesamten Schwarms zu bestimmen. Dazu bestimmten wir den Mittelwert der Bewegungsdifferenzen (dx, dy) aller Individuen zu einem bestimmten Zeitpunkt. Um zu erfassen, wie sich jedes Individuum im Verhältnis zum Schwarm verhält, berechneten wir für jedes Individuum den relativen Positionsvektor zum Schwarmmittelpunkt. Mit diesem Vektor berechneten wir die Projektion auf die Schwarmbewegungsrichtung, um zu ermitteln, wie stark jedes Individuum in Richtung des Schwarms fliegt.
Anschliessend identifizierten wir für jeden Zeitstempel das Individuum, dessen Projektionswert den höchsten Wert aufwies, was darauf hinweist, dass es sich in diesem Zeitraum in der Führungsposition befand.
Zur Visualisierung des Führungsverhaltens stellten wir die Führungsanteile über die Zeit in einem Histogramm dar. Zusätzlich visualisierten wir die Führungspositionen auf einer Karte mit leaflet, wobei wir die Individuen durch unterschiedliche Farben darstellten.
Der Code zur Analyse der Führungsdynamik wurde unter Beizug von ChatGPT erstellt.
Resultate
Ähnlichkeitmasse
Dynamic Time Warping (DTW)
Für Flug 4 identifizieren wir mithilfe hierarchischen Clustern auf Basis der DTW-Distanzen drei Gruppen von Trajektorien mit ähnlichem räumlichen Verlauf (siehe Abbildung 8). Die Individuen N und Q bilden einen eigenständigen räumlichen Cluster mit deutlich abweichenden Trajektorien, die jedoch untereinander einen ähnlichen Verlauf haben. Die weiteren beiden Cluster, welche untereinander ähnliche räumliche Trajektorien aufweisen, sind zum einen R, S und U und zum anderen M, O, P und T. Der Vergleich der Flüge des Individuum S (Abbildung 9) zeigt, dass sich der räumliche Verlauf der Trajektorien der Flüge 3, 4 und 5 sehr ähnlich sind, während sich die Flüge 1 und 2 von den anderen Flügen unterscheiden und insbesondere voneinander sehr unterschiedlich sind.
Abbildung 8: Normiertes Ähnlichkeitsmass DTW der Trajektorien aller Individuen auf Flug 4
Abbildung 9: Normiertes Ähnlichkeitsmass DTW der Trajektorien aller fünf Flüge des Individuums S
Edit Distance
Für Flug 4 lassen sich die Trajektorien wie bereits bei DTW in drei grobe Cluster einteilen, basierend auf der raum-zeitlichen Ähnlichkeit der GPS-Messpunkte (Abbildung 10). Die Trajektorien der Individuen N und Q weichen erneut deutlich von den übrigen ab, ähneln sich jedoch untereinander. Auch die beiden anderen Cluster - R, S und U sowie M, O, P und T – entsprechen den zuvor mit DTW ermittelten Gruppen. Beim flugübergreifenden Vergleich berechnet Edit Distance durchweg maximale Distanzen (Abbildung 11). Daraus schliessen wir, dass diese Methode keine raum-zeitliche Ähnlichkeit zwischen den Flügen erkennt.
Abbildung 10: Normiertes Ähnlichkeitsmass EditDist der Trajektorien aller Individuen auf Flug 4
Abbildung 11: Normiertes Ähnlichkeitsmass EditDist der Trajektorien aller fünf Flüge des Individuums S
Fréchet Distance
Anders als bei den bisherigen Ähnlichkeitsmassen lassen sich die Trajektorien für den Flug 4 in zwei grobe Cluster einteilen, einerseits die Individuen P, R, S und U, andererseits M, N, O, Q und T (Abbildung 12). Die Fréchet-Distance als Methode unterscheidet sich von den bisherigen Ähnlichkeitsmassen stark und definiert Ähnlichkeit über den grössten minimalen Abstand entlang der gesamten Trajektorie. Der Vergleich zwischen den Flügen (Abbildung 13) zeigt jedoch ein ähnliches Muster wie DTW: Der grösste minimale Abstand zwischen den Flügen 3, 4 und 5 ist vergleichsweise gering. Bei den Flügen 1 und 2 ist er im Vergleich mit den anderen Flügen hoch und zwischen den Flügen 1 und 2 besonders hoch.
Abbildung 12: Normiertes Ähnlichkeitsmass Fréchet der Trajektorien aller Individuen auf Flug 4
Abbildung 13: Normiertes Ähnlichkeitsmass Fréchet der Trajektorien aller fünf Flüge des Individuums S
Longest Common Subsequence (LCSS)
Für Flug 4 lassen sich die Trajektorien auf Basis der Länge von gemeinsamen Teilsequenzen wiederum in die drei Cluster einteilen (Abbildung 14), welche mit den Clustern von DTW und EditDist übereinstimmen. Auffällig ist erneut, dass sich die Trajektorien N und Q deutlich von den anderen Trajektorien unterscheiden, untereinander jedoch eine hohe Ähnlichkeit aufweisen. Den flugübergreifenden Vergleich konnten wir aufgrund des hohen Rechenaufwands dieser Methode nicht durchführen.
Abbildung 14: Normiertes Ähnlichkeitsmass LCSS der Trajektorien aller Individuen auf Flug 4
Geschwindigkeit
In den Abbildungen (vgl. Abbildung 16 und Abbildung 18) ist erkennbar, dass die Geschwindigkeit der Brieftauben typischerweise einem wiederkehrenden Muster folgt: Zu Beginn des Fluges ist die Geschwindigkeit meist gering, was vermutlich auf eine Orientierungsphase in der Nähe der Freilassungsstelle zurückzuführen ist. Im weiteren Verlauf steigt die Geschwindigkeit an und erreicht häufig im mittleren Abschnitt des Fluges ein Maximum. Gegen Ende des Fluges zeigt sich bei vielen Flügen wieder ein leichter Abfall der Geschwindigkeit. Dieses Muster ist sowohl bei der gruppenübergreifenden Betrachtung verschiedener Flüge (Abbildung 16) als auch bei den Einzelflügen aller Individuen des Fluges Nr. 4 (Abbildung 18) zu erkennen.
Code
# -----------------------------------------------------------------------------# Erneutes Durchführen der Segmentierung (bereinigte Datengrundlage) - Individuum S# -----------------------------------------------------------------------------# Liste der Flügefluege <-unique(Ind_S$comments)# Funktion zur Einteilung der Flüge in gleiche Zeitsegmentedetect_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 aufteilenseg_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))# -----------------------------------------------------------------------------# Trajektorien einfärben auf Karte nach der mittleren Segment-Geschwindigkeit# -----------------------------------------------------------------------------# Plot: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(x ="Längengrad", y ="Breitengrad", color ="Mittlere Geschwindigkeit (m/s)") +theme_minimal(base_size =14) +theme(legend.position ="bottom")
Abbildung 15: Flugtrajektorien aller fünf Flüge des Individuums S mit mittlerer Segment-Geschwindigkeit
Code
# -----------------------------------------------------------------------------# Scatterplot: Mittlere Segment-Geschwindigkeit aller fünf Flüge des Individuums S relativ zur Flugposition# -----------------------------------------------------------------------------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(x ="Relative Position im Flug (0 = Start, 1 = Ende)",y ="Mittlere Geschwindigkeit (m/s)", color ="Flug") +theme_minimal(base_size =14)
Abbildung 16: Mittlere Segment-Geschwindigkeit aller fünf Flüge des Individuums S relativ zur Flugposition
Code
# -----------------------------------------------------------------------------# Erneutes Durchführen der Segmentierung (bereinigte Datengrundlage) - Flug 4# -----------------------------------------------------------------------------# Liste der Flügefluege <-unique(Flug_4$`individual-local-identifier`)# Funktion zur Einteilung der Flüge in gleiche Zeitsegmentedetect_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 aufteilenseg_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))# -----------------------------------------------------------------------------# Trajektorien einfärben nach der mittleren Segment-Geschwindigkeit - Facet-Wrap# -----------------------------------------------------------------------------# Plot: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(x ="Längengrad", y ="Breitengrad", color ="Mittlere Geschwindigkeit (m/s)") +theme_minimal(base_size =14) +theme(legend.position ="bottom")
Abbildung 17: Flugtrajektorien aller Individuen auf Flug 4 mit mittlerer Segment-Geschwindigkeit
Code
# -----------------------------------------------------------------------------# Scatterplot: Mittlere Segment-Geschwindigkeit aller Individuen auf Flug 4 relativ zur Flugposition# -----------------------------------------------------------------------------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(x ="Relative Position im Flug (0 = Start, 1 = Ende)",y ="Mittlere Geschwindigkeit (m/s)", color ="Flug") +theme_minimal(base_size =14)
Abbildung 18: Mittlere Segment-Geschwindigkeit aller Individuen auf Flug 4 relativ zur Flugposition
Der Code zur Darstellung der Resultate der Geschwindigkeitsanalysen wurde unter Beizug von ChatGPT erstellt.
Sinuosität
Die Sinuosität im Flug 4 ist insbesondere in den Flugsegmenten 1 und 3, also zu Beginn des Fluges, sehr hoch (Abbildung 19). Im weiteren Verlauf des Fluges nimmt die Sinuosität deutlich ab. Um die Unterschiede der Sinuosität der Individuen besser darstellen zu können, haben wir in einer zweiten Abbildung (Abbildung 20) das Segment 3 entfernt, da dieses die Grafik stark verzerrt. In der Abbildung ist nun ersichtlich, dass sich die Sinuositätsverläufe der einzelnen Individuen stark ähneln und nur minimale Abweichungen auftreten.
Code
# -------------------------## Vergleich innerhalb des Flugs 4# -------------------------## Plot mit Segment 3ggplot(flug4_summary, aes(x =as.factor(segment), y = sinuosity, fill = tag.local.identifier)) +geom_bar(stat ="identity", position =position_dodge(width =0.8), width =0.7) +geom_line(data = mittelwerte_flug4,aes(x =as.factor(segment), y = mean_sinuosity, group =1, color ="Mittelwert"),inherit.aes =FALSE, linewidth =1.2) +geom_point(data = mittelwerte_flug4,aes(x =as.factor(segment), y = mean_sinuosity, color ="Mittelwert"),inherit.aes =FALSE, size =2) +labs(x ="Flugsegmente", y ="Sinuosität (log2)", fill ="Individuen", color ="") +scale_y_continuous(trans ="log2") +scale_fill_d3("category20") +scale_color_manual(values ="black", labels ="Mittelwert") +theme_minimal(base_size =14) +theme(legend.position ="right") +ggtitle("Sinuosität Flug 4 - Übersichtsplot")# Plot ohne Segment 3ggplot(flug4_summary_filtered, aes(x =as.factor(segment), y = sinuosity, fill = tag.local.identifier)) +geom_bar(stat ="identity", position =position_dodge(width =0.8), width =0.7) +geom_line(data = mittelwerte_flug4_filtered,aes(x =as.factor(segment), y = mean_sinuosity, group =1, color ="Mittelwert"),inherit.aes =FALSE, linewidth =1.2) +geom_point(data = mittelwerte_flug4_filtered,aes(x =as.factor(segment), y = mean_sinuosity, color ="Mittelwert"),inherit.aes =FALSE, size =2) +labs(x ="Flugsegmente", y ="Sinuosität (log2)", fill ="Individuen", color ="") +scale_y_continuous(trans ="log2", breaks =c(1, 1.25, 1.5, 1.75, 2), limits =c(1, 2)) +scale_fill_d3("category20") +scale_color_manual(values ="black", labels ="Mittelwert") +theme_minimal(base_size =14) +theme(legend.position ="right") +ggtitle("Sinuosität Flug 4 - fokussierter Plot")
Abbildung 19: Logarithmierte Sinuosität der Trajektorien aller Individuen in Flug 4, aufgeteilt nach Flugsegmenten
Abbildung 20: Logarithmierte Sinuosität der Trajektorien aller Individuen in Flug 4, aufgeteilt nach Flugsegmenten, ohne Flugsegment 3 mit sehr hoher Sinuosität
Die Karte der Flugtrajektorie von Flug 4, Individuum S, zeigt, dass die hohe Sinuosität in Segment 3 durch eine geflogene Schleife verursacht wird (Abbildung 21). Weiter ist die hohe Sinuosität im Segment 1 erkennbar.
Code
# -------------------------## Sinuosität des Individuums S auf Flug 4 - Karte# -------------------------## Plotggplot(ind_s_flug4, aes(x = location.long, y = location.lat, group = segment, color = sinuosity)) +geom_path(linewidth =1.2) +geom_text(data = labels,aes(x = lon, y = lat, label = label),inherit.aes =FALSE,size =4.5, fontface ="bold", color ="black") +coord_equal() +scale_color_gradientn(colours = farben,values = werte_log,trans ="log2",limits =c(1, max_sinuo),breaks =c(1, 2, 5, 10, round(max_sinuo)),labels =c("1", "2", "5", "10", as.character(round(max_sinuo))),oob = squish,name ="Sinuosität (log2)" ) +labs(x ="Longitude", y ="Latitude") +theme_minimal(base_size =14)
Abbildung 21: Karte der Sinuosität der Trajektorien aller Individuen in Flug 4, aufgeteilt nach Flugsegmenten
Die Sinuosität der Flugtrajektorien von Individuum S über alle Flüge hinweg bestätigt das im Flug 4 beobachtete Muster: Zu Beginn des Flugs ist die Sinuosität hoch und nimmt im weiteren Verlauf deutlich ab (Abbildung 22). Besonders in den Flügen 2 und 4 ist die erhöhte Sinuosität in den ersten Segmenten ausgeprägt. Eine Ausnahme bildet Flug 1, bei dem zu Beginn keine erhöhte Sinuosität erkennbar ist.
Code
# -------------------------## Individuum S über alle Flüge# -------------------------## Plotggplot(s_plotdata, aes(x = segment, y = sinuosity, color = comments, group = comments)) +geom_line(linewidth =1.2) +geom_point(size =2) +scale_color_manual(values =c(setNames(RColorBrewer::brewer.pal(5, "Set1"), paste0("Flug ", 1:5)),"Mittelwert"="black" ) ) +scale_x_continuous(breaks =1:8, labels =as.character(1:8)) +scale_y_continuous(trans ="log2",breaks =c(1, 1.5, 2, 4, 8, 16),labels =c("1", "1.5", "2", "4", "8", "16") ) +labs(x ="Flugsegmente",y ="Sinuosität (log2)",color ="Flug" ) +theme_minimal(base_size =14)
Abbildung 22: Logarithmierte Sinuosität der Trajektorien des Individuums S über alle Flüge, aufgeteilt nach Flugsegmenten
Das gleiche Muster der hohen Sinuosität zu Beginn des Fluges ist wiederum in der Karte der Sinuosität der Flugtrajektorien des Individuum S ersichtlich (Abbildung 23).
Code
# -------------------------## Karte: Individuum S über alle Flüge# -------------------------## Plotggplot(ind_s_all, aes(x = location.long, y = location.lat, group =interaction(comments, segment))) +geom_path(aes(color = sinuosity), linewidth =0.8, alpha =0.95) +scale_color_gradientn(colours = farben,values = werte_log,trans ="log2",limits =c(1, 22),breaks = breaks_custom,labels = labels_custom,oob = squish,name ="Sinuosität (log2)" ) +coord_equal() +labs(x ="Longitude", y ="Latitude") +theme_minimal(base_size =14)
Abbildung 23: Karte der Sinuosität der Trajektorien des Individuums S über alle Flüge, aufgeteilt nach Flugsegmenten
Zur Erstellung der Plots wurde ChatGPT beigezogen, um die Gestaltung zu optimieren und eine Logarithmierung der Achsen umzusetzen.
Führungsverhalten
Die Auswertung der Führungszeiten (Abbildung 24) zeigt eine deutliche Hierarchie: Das Individuum «M» übernimmt mit Abstand am häufigsten die Führungsposition, gefolgt von den Individuen «O» und «T». Demgegenüber zeigen andere Individuen wie «N» oder «R» nur selten Führungsverhalten. Die Resultate deuten darauf hin, dass bestimmte Individuen überproportional häufig die Navigation des Schwarms übernehmen.
Code
# -----------------------------------------------------------------------------# Führungsdynamik aller Individuen auf Flug 4 - Führungsanteile über die Zeit verteilt# -----------------------------------------------------------------------------# 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 Schwarmvektorschwarm <- 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ückFlug_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 Identifikationsmerkmalcolor_palette <-scale_fill_manual(values = RColorBrewer::brewer.pal(length(unique(Flug_4$`individual-local-identifier`)), "Set1"))# -----------------------------------------------------------------------------# Plot - Führungsanteile über die Zeit verteilt# -----------------------------------------------------------------------------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(x ="Taube", y ="Anzahl Sekunden in Führung") +theme_minimal() +theme(legend.position ="none")
Abbildung 24: Führungsdynamik aller Individuen auf Flug 4 - Führungsanteile über die Zeit verteilt
Code
# -----------------------------------------------------------------------------# Plot - Führungszeitpunkte je Individuum# -----------------------------------------------------------------------------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(x ="Zeit", y ="Anzahl Zeitpunkte (pro Bin)") +theme_minimal() +theme(legend.position ="bottom")
Abbildung 25: Führungsdynamik aller Individuen auf Flug 4 - Führungszeitpunkte je Individuum
Code
# -----------------------------------------------------------------------------# Interaktive Karte - Übersicht Führung je Schwarmmittelpunkt und Zeitpunkt# -----------------------------------------------------------------------------# Farbzuordnung für jedes individuelle Identifikationsmerkmalcolor_palette <-colorFactor(palette ="Set1", domain =unique(Flug_4$`individual-local-identifier`))# Visualisierung mit Leaflet: Nur die Punkte der Führer einfärbenleaflet(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" )
Abbildung 26: Führungsdynamik aller Individuen auf Flug 4 - Übersicht Führung je Schwarmmittelpunkt und Zeitpunkt
Der Code zur Darstellung der Resultate der Führungsdynamik wurde unter Beizug von ChatGPT erstellt.
Diskussion
Unsere Analysen bestätigen weitgehend die in der Literatur beschriebenen Muster im Bewegungsverhalten von Brieftauben. Sowohl Geschwindigkeits- als auch Sinuositätsverläufe zeigen eine zwei- bzw. dreiphasige Flugstruktur. Nach einer initialen Orientierungsphase mit geringer Geschwindigkeit und erhöhter Sinuosität folgt ein zielgerichteter, geradliniger und beschleunigter Heimflug. In der Geschwindigkeit ist zudem gegen Ende des Fluges eine leichte Verlangsamung erkennbar. Dasselbe Muster wurde durch (Schiffner & Wiltschko, 2009) identifiziert, die einen deutlichen Anstieg der Linearität und Fluggeschwindigkeit als Entscheidungszeitpunkt für den Heimflug identifizieren. Unsere Annahme einer initialen Orientierungsphase wird durch(Laube et al., 2007) ebenfalls gestützt und durch die Möglichkeit einer Schwarmkoordination ergänzt.
In unseren Schlussfolgerungen zu der Flugstruktur muss berücksichtigt werden, dass die tatsächlichen Start- und Endphasen in den zugrundeliegenden Daten von (Santos et al., 2014) entfernt wurden. (Santos et al., 2014) interessierten sich in ihrer Auswertung ausschliesslich für das Verhalten der Brieftauben im Gruppenflug, weshalb die Flugsegmente, in denen sich die Individuen aufteilten, entfernt wurden. Daher können keine belastbaren Aussagen über spezifische Muster, insbesondere im letzten Flugabschnitt, gemacht werden.
Zu beachten ist weiterhin, dass die Geschwindigkeitsberechnungen ausschliesslich auf zweidimensionaler Basis erfolgten, wodurch insbesondere bei vertikalen Flugbewegungen Verzerrungen nicht ausgeschlossen werden können.
Auch unsere Analyse der Führungsdynamik zeigt ein Muster, welches in der Literatur wiederholt beschrieben wurde. Bestimmte Individuen, in Flug 4 insbesondere das Individuum M, nehmen wiederholt eine führende Position ein, was auf stabile Führungsstrukturen im Schwarmverhalten hinweist, wie sie auch von (Santos et al., 2014) und (Nagy et al., 2010) identifiziert wurden. Beiden Studien zeigen, dass sich in Taubenschwärmen konsistente Leader-Follower-Beziehungen herausbilden können, die unabhängig von der Flugroute bestehen bleiben. Unsere Methode zur Bestimmung der Führung erfolgt über die Projektion individueller Bewegungsrichtungen auf den mittleren Schwarmvektor. Dieses Verfahren erlaubt jedoch keine Aussage darüber, welches Individuum wem konkret folgt und inwiefern Richtungsentscheidungen beeinflusst werden, sondern identifiziert lediglich jenes Individuum, das sich zu einem gegebenen Zeitpunkt in Richtung und Distanz am stärksten in Flugrichtung vom Schwarmmittelpunkt entfernt hat. Es kann daher nicht ausgeschlossen werden, dass es sich beim als führend identifizierten Individuum um ein ausreissendes Tier handelt, das nicht zwangsläufig eine leitende Rolle im Schwarmverhalten einnimmt. Eine solche Interpretation steht im Einklang mit (Pettit et al., 2015), die darauf hinweisen, dass Führung nicht zwingend durch Navigationsfähigkeit motiviert sein muss, sondern auch durch individuelle Initiative oder Positionierung entstehen kann.
Guilford, T., Roberts, S., Biro, D., & Rezek, I. (2004). Positional entropy during pigeon homing II: navigational interpretation of Bayesian latent state models. Journal of Theoretical Biology, 227(1), 25–38. https://doi.org/10.1016/j.jtbi.2003.07.003
Kays, R., Davidson, S. C., Berger, M., Bohrer, G., Fiedler, W., Flack, A., Hirt, J., Hahn, C., Gauggel, D., Russell, B., Kölzsch, A., Lohr, A., Partecke, J., Quetting, M., Safi, K., Scharf, A., Schneider, G., Lang, I., Schaeuffelhut, F., … Wikelski, M. (2022). The Movebank system for studying global animal movement and demography. Methods in Ecology and Evolution, 13(2), 419–431. https://doi.org/10.1111/2041-210X.13767
Laube, P., Dennis, T., Forer, P., & Walker, M. (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., Ákos, Z., Biro, D., & Vicsek, T. (2010). Hierarchical group dynamics in pigeon flocks. Nature, 464(7290), 890–893. https://doi.org/10.1038/nature08891
Pettit, B., Ákos, Z., Vicsek, T., & Biro, D. (2015). Speed Determines Leadership and Leadership Determines Learning during Pigeon Flocking. Current Biology, 25(23), 3132–3137. https://doi.org/10.1016/j.cub.2015.10.044
R Core Team. (2024). A language and environment for statistical computing. R Foundation for Statistical Computing. https://www.r-project.org/
Santos, C. D., Neupert, S., Lipp, H.-P., Wikelski, M., & Dechmann, D. K. N. (2014). Temporal and Contextual Consistency of Leadership in Homing Pigeon Flocks. PLoS ONE, 9(7), e102771. https://doi.org/10.1371/journal.pone.0102771
Schiffner, I., & Wiltschko, R. (2009). Point of decision: when do pigeons decide to head home? Naturwissenschaften, 96(2), 251–258. https://doi.org/10.1007/s00114-008-0476-7