Bewegungsmuster des Europäischen Aals (Anguilla anguilla) in kanalisierten Gewässern

Projekt - Patterns & Trends in Environmental Data / Computational Movement Analysis Geo 880

Autor:in

Yannick Moos & Mark Brandenberg

Gleiche Bewertung gewünscht

1 Einleitung

Der Europäische Aal (Anguilla anguilla) ist eine katadrome Fischart, deren Bestände in den letzten Jahrzente drastisch zurückgegangen sind (Piper u. a. 2017; Verhelst u. a. 2018). Aufgrund seines komplexen Lebenszyklus und der Vielzahl an anthropogenen Einflüssen, die seine Wanderungen behindern (z. B. Querbauwerke, Habitatverlust, Wasserverschmutzung), gilt der Aal heute als stark gefährdet (Halvorsen u. a. 2020; Piper u. a. 2013). Vor diesem Hintergrund ist ein vertieftes Verständnis seines Bewegungsverhaltens in verschiedenen Gewässertypen entscheidend für die Entwicklung wirksamer Schutz- und Managementstrategien. 

Ziel dieser Studie ist es, das Bewegungsverhalten der Aalen in einem kanalisierten Polder- und Flusssystem besser zu verstehen. Im Fokus stehen dabei sowohl individuelle als auch kollektive Verhaltensmuster, die durch akustische Telemetriedaten sichtbar gemacht werden. Daraus ergeben sich die folgenden zwei zentralen Forschungsfragen: erstens werden die zeitlichen Muster der Aale untersucht, wobei der Schwerpunkt auf ihrem zirkadianen Zyklus (Tag und Nacht) und den monatlichen Bewegungen liegt, um zu prüfen, ob es signifikante Unterschiede in ihren Bewegungen gibt; Zweitens wird untersucht, wie sich die Wahl des räumlichen Modells auf die Analyse des Aalbewegens auswirkt, indem der beschränkte Bewegungsraum (Constrained Movement Space, CMS), basierend auf einem entlang des hydrografischen Netzwerks verlaufenden Wegnetz, mit dem unbeschränkten Bewegungsraum (UCMS) verglichen wird, der auf linearen Entfernungen zwischen Vermessungspunkten basiert. Es soll geprüft werden, ob die Verwendung eines CMS einen Mehrwert liefert.

2 Methoden

Die Datengrundlage dieser Studie stammt aus dem Projekt 2012_LEOPOLDKANAAL des Research Institute for Nature and Forest (INBO) (Verhelst u. a. 2020). Über einen mehrmonatigen Zeitraum wurden 94 Individuen des Europäischen Aals mit akustischen Telemetriesendern markiert. Ihre Bewegungen wurden automatisch erfasst, sobald sie in den Erfassungsbereich von einem der 67 stationären Empfänger gelangten. Der daraus entstandene Datensatz umfasst rund 2,2 Millionen Registrierungspunkte und ist öffentlich über GBIF zugänglich (Verhelst u. a. 2024). Ergänzt wird dieser durch einen detaillierten Metadatensatz mit Informationen zu den besenderten Individuen. Für die Analyse benötigte Gewässerabschnitte wurden in QGIS vom Gewässernetz von Open Street Map (OpenStreetMap contributors 2017) selektiert, in R importiert und in ein ungerichtetes räumliches Netzwerk umgewandelt.

Die Bewegungen der Tiere wurden innerhalb eines beschränkten (Gewässernetz) und unbeschränktem Bewegungsraum untersucht. Darauf basierend wurden die Bewegungsparameter Geschwindigkeit und Migrationsdistanz berechnet.

Die Analyse des Bewegungsverhaltens erfolgte in R (R Core Team 2024), unter Verwendung der Pakete tidyverse (Wickham u. a. 2019), lubridate (Grolemund und Wickham 2011), skimr (Waring u. a. 2022), leaflet (Cheng u. a. 2024), janitor (Firke 2024), geosphere (Hijmans 2024), igraph (Csardi und Nepusz 2006), sf (Pebesma 2018; Pebesma und Bivand 2023), sfnetworks (Meer u. a. 2024), ggraph (Pedersen 2024), tmap (M. Tennekes 2018), und tmaptools (Martijn Tennekes 2025). Die massgeblichen Arbeits- und Analyseschritte wurden ohne generative Sprachmodelle erstellt. ChatGPT4o (OpenAI 2025) wurde jedoch als Coding- und Korrektur-Hilfe verwendet.

1.1 Packages

library(tidyverse)
library(lubridate)
library(skimr)
library(leaflet)
library(janitor)
library(geosphere)
library(igraph)
library(sf)
library(sfnetworks)
library(ggraph)
library(pheatmap)
library(tmap)
library(tmaptools)
library(report)

1.2 Datenimport

aalen <- read_delim("detections.csv")
sea_stations <- read_delim("abwanderung_true.csv", ",")
waterways_sf <- st_read("geodata/OSM_selected_010425_2.shp") |> 
  st_transform(3035) |> 
  select(full_id)
Reading layer `OSM_selected_010425_2' from data source 
  `C:\Users\markv\Desktop\Master\2 semester\Patterns and Trends in Environmental Data\semester_project\project_moos_brandenberg_FS25\geodata\OSM_selected_010425_2.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 64 features and 74 fields
Geometry type: LINESTRING
Dimension:     XY
Bounding box:  xmin: 2.94047 ymin: 51.20089 xmax: 3.795233 ymax: 51.34303
Geodetic CRS:  WGS 84

1.3 Filter: Aale die das Meer erreichten Da der Datensatz sehr umfangreich ist und viele Fische analysiert wurden, wurde beschlossen, sich nur auf die Aale zu konzentrieren, die das Meer erreicht haben.

sea_arrivers <- aalen |>
  mutate(animal_id = as.factor(animal_id)) |> 
  group_by(animal_id) |>
  filter(any(station_name == "bh-37")) |>
  ungroup()

1.4 DatenÜberblick Überblick über die Daten

# Kompakter Überblick über Spaltennamen, Datentypen und Beispielwerte
glimpse(sea_arrivers)
Rows: 423,945
Columns: 23
$ pk                        <dbl> 21986012, 21494419, 22624927, 21740163, 2138…
$ date_time                 <dttm> 2012-10-16 15:02:41, 2012-10-16 15:06:27, 2…
$ receiver_id               <chr> "VR2W-112285", "VR2W-112285", "VR2W-112285",…
$ application_type          <chr> "acoustic_telemetry", "acoustic_telemetry", …
$ network_project_code      <chr> "leopold", "leopold", "leopold", "leopold", …
$ tag_id                    <chr> "A69-1303-3552", "A69-1303-3552", "A69-1303-…
$ tag_fk                    <dbl> 209, 209, 209, 209, 209, 209, 209, 209, 209,…
$ animal_id                 <fct> 231, 231, 231, 231, 231, 231, 231, 231, 231,…
$ animal_project_code       <chr> "2012_leopoldkanaal", "2012_leopoldkanaal", …
$ scientific_name           <chr> "Anguilla anguilla", "Anguilla anguilla", "A…
$ station_name              <chr> "bh-31", "bh-31", "bh-31", "bh-31", "bh-31",…
$ deploy_latitude           <dbl> 51.29037, 51.29037, 51.29037, 51.29037, 51.2…
$ deploy_longitude          <dbl> 3.716447, 3.716447, 3.716447, 3.716447, 3.71…
$ sensor_type               <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ sensor_value              <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ sensor_unit               <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ sensor_value_depth        <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ sensor_value_acceleration <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ sensor_value_temperature  <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ signal_to_noise_ratio     <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ source_file               <chr> "inbo_data_file", "inbo_data_file", "inbo_da…
$ qc_flag                   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ deployment_fk             <dbl> 1416, 1416, 1416, 1416, 1416, 1416, 1416, 14…
# Ausführliche Zusammenfassung
skim(sea_arrivers)
Data summary
Name sea_arrivers
Number of rows 423945
Number of columns 23
_______________________
Column type frequency:
character 8
factor 1
logical 7
numeric 6
POSIXct 1
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
receiver_id 0 1 11 11 0 56 0
application_type 0 1 18 18 0 1 0
network_project_code 0 1 3 10 0 4 0
tag_id 0 1 13 14 0 33 0
animal_project_code 0 1 18 18 0 1 0
scientific_name 0 1 17 17 0 1 0
station_name 0 1 3 10 0 54 0
source_file 0 1 14 26 0 23 0

Variable type: factor

skim_variable n_missing complete_rate ordered n_unique top_counts
animal_id 0 1 FALSE 33 410: 139442, 403: 58318, 441: 36326, 414: 32913

Variable type: logical

skim_variable n_missing complete_rate mean count
sensor_type 423945 0 NaN :
sensor_value 423945 0 NaN :
sensor_unit 423945 0 NaN :
sensor_value_depth 423945 0 NaN :
sensor_value_acceleration 423945 0 NaN :
sensor_value_temperature 423945 0 NaN :
signal_to_noise_ratio 423945 0 NaN :

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
pk 0 1.0 24149375.63 6799330.35 20581624.00 21227280.00 21877117.00 22523802.00 78978072.00 ▇▁▁▁▁
tag_fk 0 1.0 225.60 29.34 155.00 227.00 236.00 240.00 263.00 ▂▁▁▇▂
deploy_latitude 0 1.0 51.28 0.04 51.00 51.24 51.26 51.33 51.42 ▁▁▇▅▅
deploy_longitude 0 1.0 3.73 0.06 3.54 3.75 3.75 3.77 5.11 ▇▁▁▁▁
qc_flag 381644 0.1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ▁▁▇▁▁
deployment_fk 0 1.0 1560.30 417.97 1327.00 1415.00 1415.00 1418.00 4495.00 ▇▁▁▁▁

Variable type: POSIXct

skim_variable n_missing complete_rate min max median n_unique
date_time 0 1 2012-07-19 16:08:35 2018-12-13 13:58:04 2013-01-11 00:35:21 380246
# Prüfung auf Duplikate
n_distinct(sea_arrivers$pk) == nrow(sea_arrivers)  # Sollte TRUE sein
[1] TRUE

Die hohe Anzahl an NA-Werten in den Sensorfeldern deutet darauf hin, dass viele Tags oder Empfänger keine erweiterten Sensordaten liefern. Entweder wurden einfache Tags verwendet oder entsprechende Sensoren waren nicht konfiguriert. Da der Fokus der Analyse auf Bewegungsmustern der Tiere liegt (z. B. Wanderungen, Verweildauer, Netzwerkbewegung), sind die erweiterten Sensorfelder (z. B. Temperatur, Beschleunigung) derzeit von untergeordneter Bedeutung. Zudem liegen sie für den Grossteil der Daten nicht vor. Daher werden diese Felder in den weiteren Analysen nicht berücksichtigt.

2.1 Netzwerkerstellung

Das Netzwerk wurde, orientiert an Meer u. a. (2024), vorbearbeitet und vereinfacht. Massgebende Schritte waren die Rundung von Koordinaten, Entfernung von mehrfach enthaltenen Kanten und Entfernung von Pseudo-Knoten. Für die stationären Empfänger wurden am jeweils nächstgelegenen Punkt des Netzwerks ein zusätzlicher Knoten eingefügt. Für die Knoten wurde schliesslich eine ungewichtete Origin-Destination-Matrix berechnet und darauf basierend zurückgelegte Distanz und Fortbewegungsgeschwindigkeiten für die Bewegungen der Aale kalkuliert.

Credits für Netzwerk-Workflow und Beschreibung der Funktionen: https://luukvdmeer.github.io/sfnetworks/. (Kursive Beschreibungen wurden) wortwörtlich übernommen.

2.1 Rounding coordinates

You might have a set of lines in which some endpoints are almost shared between two lines. However, the coordinates are stored with so much precision that there is a minor difference between the two points. When constructing a sfnetwork these lines will not be connected because the points are not exactly equal. We can pre-process the lines by reducing the precision of the coordinates such that the points become exactly equal.

st_geometry(waterways_sf) <- st_geometry(waterways_sf) |> 
  lapply(function(x) round(x, 0)) |> 
  st_sfc(crs = st_crs(waterways_sf))

# edge_colors = function(x) rep(sf.colors(12, categorical = TRUE)[-2], 2)[c(1:ecount(x))]

2.2 Netzwerkerstellung

waterways <- as_sfnetwork(waterways_sf, directed = FALSE)
waterways 
# A sfnetwork with 75 nodes and 64 edges
#
# CRS:  EPSG:3035 
#
# An undirected multigraph with 12 components with spatially explicit edges
#
# Node data: 75 × 1 (active)
           geometry
        <POINT [m]>
1 (3829501 3147118)
2 (3828706 3147442)
3 (3887668 3145688)
4 (3887350 3145853)
5 (3849184 3158465)
6 (3849129 3158764)
# ℹ 69 more rows
#
# Edge data: 64 × 4
   from    to full_id                                                   geometry
  <int> <int> <chr>                                             <LINESTRING [m]>
1     1     2 w780936346  (3829501 3147118, 3829305 3147185, 3829223 3147222, 3…
2     3     4 w1062436337 (3887668 3145688, 3887609 3145700, 3887591 3145704, 3…
3     5     6 w909184452                      (3849184 3158465, 3849129 3158764)
# ℹ 61 more rows

2.3 Netzwerkbereinigung

Simplify network

“A network may contain sets of edges that connect the same pair of nodes. Such edges can be called multiple edges. Also, it may contain an edge that starts and ends at the same node. Such an edge can be called a loop edge. In graph theory, a simple graph is defined as a graph that does not contain multiple edges nor loop edges. To obtain a simple version of our network, we can remove multiple edges and loop edges by calling tidygraphs edge filter functions tidygraph::edge_is_multiple() and tidygraph::edge_is_loop().”

Keine multiple Edges vorhanden, daher keine Änderung der Nodes- und Edges-Anzahl.

simplified <- waterways |>
  activate("edges") |>
  dplyr::arrange(edge_length()) |> 
  dplyr::filter(!tidygraph::edge_is_multiple()) |>
  dplyr::filter(!tidygraph::edge_is_loop())
simplified
# A sfnetwork with 75 nodes and 63 edges
#
# CRS:  EPSG:3035 
#
# An unrooted forest with 12 trees with spatially explicit edges
#
# Edge data: 63 × 4 (active)
   from    to full_id                                                geometry
  <int> <int> <chr>                                          <LINESTRING [m]>
1    34    43 w1078024209                  (3886694 3146147, 3886690 3146145)
2    19    20 w1039743777                  (3885294 3143943, 3885299 3143943)
3    14    63 w932092440                   (3853816 3149715, 3853829 3149728)
4     8    63 w932092441                   (3853804 3149701, 3853816 3149715)
5    32    48 w525661272                   (3886192 3146432, 3886183 3146449)
6    17    18 w610504848  (3880280 3147303, 3880296 3147304, 3880296 3147308)
# ℹ 57 more rows
#
# Node data: 75 × 1
           geometry
        <POINT [m]>
1 (3829501 3147118)
2 (3828706 3147442)
3 (3887668 3145688)
# ℹ 72 more rows

Subdivide edges

When constructing a sfnetwork from a set of sf linestrings, the endpoints of those linestrings become nodes in the network. If endpoints are shared between multiple lines, they become a single node, and the edges are connected. However, a linestring geometry can also contain interior points that define the shape of the line, but are not its endpoints. It can happen that such an interior point in one edge is exactly equal to either an interior point or endpoint of another edge. In the network structure, however, these two edges are not connected, because they don’t share endpoints. If this is unwanted, we need to split these two edges at their shared point and connect them accordingly. The function to_spatial_subdivision() subdivides edges at interior points whenever these interior points are equal to one or more interior points or endpoints of other edges, and recalculates network connectivity afterwards.

subdivision <- simplified |>
  tidygraph::convert(to_spatial_subdivision)
subdivision
# A sfnetwork with 83 nodes and 93 edges
#
# CRS:  EPSG:3035 
#
# An undirected multigraph with 2 components with spatially explicit edges
#
# Edge data: 93 × 5 (active)
   from    to full_id                             geometry .tidygraph_edge_index
  <int> <int> <chr>                       <LINESTRING [m]>                 <int>
1     1     2 w1078024209 (3886694 3146147, 3886690 31461…                     1
2     3     4 w1039743777 (3885294 3143943, 3885299 31439…                     2
3     5     6 w932092440  (3853816 3149715, 3853829 31497…                     3
4     5     7 w932092441  (3853804 3149701, 3853816 31497…                     4
5     8     9 w525661272  (3886192 3146432, 3886183 31464…                     5
6    10    11 w610504848  (3880280 3147303, 3880296 31473…                     6
# ℹ 87 more rows
#
# Node data: 83 × 2
           geometry .tidygraph_node_index
        <POINT [m]>                 <int>
1 (3886694 3146147)                    34
2 (3886690 3146145)                    43
3 (3885294 3143943)                    19
# ℹ 80 more rows
# ggplot2::autoplot(simplified)
# ggplot2::autoplot(subdivision)

Pseudo-Nodes

A network may contain nodes that have only one incoming and one outgoing edge. For tasks like calculating shortest paths, such nodes are redundant, because they don’t represent a point where different directions can possibly be taken. Sometimes, these type of nodes are referred to as pseudo nodes. Note that their equivalent in undirected networks is any node with only two incident edges, since incoming and outgoing does not have a meaning there. To reduce complexity of subsequent operations, we might want to get rid of these pseudo nodes.

Es ware viele Pseudo-Nodes vorhanden und daher deutliche Vereinfachung des Netzwerks.

smoothed <- subdivision |>
  tidygraph::convert(to_spatial_smooth)
smoothed # 42 Nodes, 51 Edges
# A sfnetwork with 42 nodes and 52 edges
#
# CRS:  EPSG:3035 
#
# An undirected multigraph with 2 components with spatially explicit edges
#
# Edge data: 52 × 5 (active)
   from    to full_id     .tidygraph_edge_index                         geometry
  <int> <int> <chr>       <list>                                <LINESTRING [m]>
1     2     3 w382434046  <int [1]>             (3882977 3148479, 3882990 31485…
2     4     4 w777822385  <int [1]>             (3849091 3145195, 3849091 31451…
3     4     5 w777822385  <int [1]>             (3849091 3145195, 3849073 31452…
4     9    10 <NA>        <int [1]>             (3873537 3147308, 3873428 31473…
5    11    12 w777822345  <int [1]>             (3829223 3147222, 3829198 31472…
6    14    14 w1062436267 <int [1]>             (3886803 3146231, 3886803 31462…
# ℹ 46 more rows
#
# Node data: 42 × 2
           geometry .tidygraph_node_index
        <POINT [m]>                 <int>
1 (3853829 3149728)                     6
2 (3882977 3148479)                    19
3 (3882990 3148519)                    20
# ℹ 39 more rows
# ggplot2::autoplot(smoothed)
# st_write(st_as_sf(activate(smoothed, "nodes")), "geodata/network.gpkg", layer = "smoothed")

2.4 Telemetriestationen blendern

For each POI, it finds the nearest location p on the nearest edge e. If p is an already existing node (i.e. p is an endpoint of e), it joins the information from the POI into that node. If p is not an already existing node, it subdivides e at p, adds p as a new node to the network, and joins the information from the POI into that new node. For this process, it does not matter if p is an interior point in the linestring geometry of e. The st_network_blend() function has a tolerance parameter, which defines the maximum distance a POI can be from the network in order to be blended in. Hence, only the POIs that are at least as close to the network as the tolerance distance will be blended, and all others will be ignored. The tolerance can be specified as a non-negative number. By default it is assumed its units are meters.

blender_tolerance <- 500 # meter

# Stationen als sf-Layer
stations_sf <- sea_arrivers |> 
  distinct(station_name, deploy_latitude, deploy_longitude)|> 
  st_as_sf(coords = c("deploy_longitude", "deploy_latitude"), crs = 4326) |>
  st_transform(3035)


blended <- smoothed |> 
  st_network_blend(st_geometry(stations_sf), tolerance = blender_tolerance)

# blended
# st_write(st_as_sf(activate(blended, "nodes")), "geodata/network.gpkg", layer = "blended")

2.5 OD-Matrix (Origin-Destination)

The shortest paths calculation is only supported for one-to-one and one-to-many routing. The alternative for many-to-many routing is the calculation of an origin-destination cost matrix. Instead of providing the individual paths, it returns a matrix in which entry i,j is the total cost (i.e. sum of weights) of the shortest path from node i to node j. The origin-destination cost matrix is usually an important starting point for further analysis. For example, it can serve as input to route optimization algorithms, spatial clustering algorithms and the calculation of statistical measures based on spatial proximity. The igraph function for this purpose is igraph::distances(), which in sfnetworks is wrapped by st_network_cost(), allowing again to provide sets of geospatial points as from and to locations. Note that the calculated costs refer to the paths between the nearest nodes of the input points. Their units are the same as the units of weights used in the calculation, in this case meters.

cost_matrix <- st_network_cost(blended)
# view(cost_matrix)

2.6 Antennenknoten bestimmen Jetzt möchten wir wissen, welche Nodes auch wirklich Antennenstandorte sind und nicht ursprüngliche Netzwerk-Nodes. Leider können keine Attribute direkt bei der st_network_blend-Funktion übernommen werden. Daher führen wir folgenden Workflow durch:

  1. Netzwerk-Edges mit der Blender-Toleranz buffern.
  2. Für Antennen innerhalb des Blender-Toleranz filtern. Da einige Antennen auch nicht von Interesse waren von uns (z.B. bereits im Meer) und daher auch nicht an das Netzwerk geblendert wurden.
  3. Nächste Nodes aller Antennen finden. Das kann leider nicht mit einem Column-Bind oder so durchgeführt werden, da nicht für alle Antennen eine neue Node erstellt wurde (z.B. wenn sich die Antenne am Ende eines Edges befanden, wurde kein neuer Nodes erstellt, da am nächtsten Punkt auf dem Netzwerk bereits ein ursprünglicher Node bestand).
netw_buf <- blended |> 
  activate("edges") |> 
  st_as_sf() |> 
  st_buffer(blender_tolerance)


blended_nodes_sf <- blended |> 
  activate("nodes") |> 
  st_as_sf() |> 
  mutate(
    row_num = row_number()
  )

# st_write(blended_nodes_sf, "geodata/network.gpkg", layer = "nodes", delete_layer = T)
# st_write(select(edges_sf, -".tidygraph_edge_index"), "geodata/network.gpkg", layer = "edges")
# st_write(stations_oi, "geodata/network.gpkg", layer = "stations_oi", delete_layer = T)

stations_oi <- stations_sf |>
  filter(lengths(st_within(geometry, netw_buf)) > 0) |> # nur Stationen in Buffer
  mutate(
    node_nr = st_nearest_feature(geometry, blended_nodes_sf) # nächste Node
  )

# tm_shape(netw_buf) +
#   tm_polygons() +
# tm_shape(stations_oi) +
#   tm_symbols() +
#   tm_shape(blended_nodes_sf)+
#   tm_symbols(col = "red")

Visualisierung

tmap_mode("view")

# Alle Nodes des Netzwerks
nodes_sf <- blended |> 
  activate("nodes") |>
  st_as_sf() |> 
  filter(
    !is.na(.tidygraph_node_index))

# Alle Edges des Netzwerks
edges_sf <- blended |> 
  activate("edges") |> 
  st_as_sf()

# st_write(edges_sf, "geodata/network.gpkg", layer = "edges_sf", delete_layer = T)



# Nodes für Stationen
station_nodes_sf <- blended |> 
  activate("nodes") |>
  st_as_sf() |> 
  filter(
    is.na(.tidygraph_node_index))
  

p_nw_uebersicht <- tm_shape(edges_sf) +
  tm_lines(col = "black") +
  tm_shape(nodes_sf) +
  tm_symbols(fill = "black", size = 0.3) +
  tm_shape(stations_oi) +
  tm_symbols(fill = "blue", fill_alpha = 1, size = 0.5, col = "black" ) +
  tm_shape(station_nodes_sf) +
  tm_symbols(fill = "red", fill_alpha = 0.1, size = 0.5, col = "blue")

Das resultierende Netzwerk, welches zu Analyse verwendet wurde, enthält schliesslich Knoten und Kanten, welche das Netzwerk repräsentieren und zusätzliche Knoten für die stationären Telemetrieaantennen (Abbildung 1).

Abbildung 1: Erstelltes Netzwerk mit Kanten und Knotenpunkten (schwarz) und hinzugefügten Knoten für die stationären Empfänger (blau).

2.2 Filtrierung von stationären Phasen und falschen Bewegungen

Vor der Bewegungsanalyse wurden Filter auf den Datensatz angewandt. Die Ziele der Filterabläufe waren die Rechenzeit zu verkürzen und Effekte der Signalübertragung in den Gewässern zu vermindern. Damit ist die Problematik gemeint, dass akustische Sender bei geeigneten Bedingungen (Umgebungsgeräusche Wassertiefe, Sohlenmaterial, etc.) ein Reichweite von mehreren hundert Metern erreichen können (InnovaSea Systems 2025).  Dies führt dazu, dass Individuen gleichzeitig von zwei oder mehr Empfängern registriert werden und unrealistische Bewegungsgeschwindigkeiten erreicht werden. 

  1. Um die Rechenzeit zu verkürzen wurden nur Individuen betrachtet, die ins Meer abgewandert und somit bei einem Empfänger im Meer registriert wurden. Die resultierte in einem Datensatz mit rund 424’000 Registrierungen. 

  2. Stationäre Phasen wurden zur Berechnung der Geschwindigkeiten entfernt.  Als mobile Phasen wurden sich wechselnde Empfänger-Registrierungen definiert. 

  3. Oszillierende Bewegungsmuster zwischen zwei Empfängern innerhalb des maximalen Sendeintervalls der Sender (160 s) wurden mit einem Filter entfernt.  

  4. Ein Filter für unrealistisch hohe Fortbewegungsgeschwindigkeiten wurde in Ergänzung zu Filter iii) angewandt um komplexe oszillierende Bewegungsmuster zwischen >2 Antennen zu eliminieren. Die maximale Geschwindigkeit wurde als 0.8 m/s festgelegt (Quintella u. a. 2010; Tudorache u. a. 2015)

Telemetriestationen-Namen und Netzwerkknoten zusammenführen

Damit auf die Kostenmatrix zugegriffen werden kann, müssen zuerst den Stationen die entsprechenden Nodes hinzugefügt werden.

# Node-Nr. zu Station_Name joinen
sea_arrivers_network <- sea_arrivers |> 
  left_join(select(st_drop_geometry(stations_oi),
                   station_name, node_nr), by = "station_name", keep = F) |> 
  group_by(animal_id) |> 
  mutate(
    days_since_deployment = as.numeric(days(date_time - min(date_time)))
  )

3.1 Kein Antennenwechsel/Bewegung

options(scipen = 999)

stationary_filter <- sea_arrivers_network |> # ehemals: pseudo_movement
  select(date_time, animal_id, station_name, node_nr, deploy_latitude, deploy_longitude) |> 
  group_by(animal_id) |> 
  arrange(date_time) |> 
  mutate(
    current_node = node_nr,
    before_node = lag(node_nr),
    before_datetime = lag(date_time),
    position_change = current_node != before_node) |> 
  filter(position_change == T) |> 
  ungroup()

3.2 Oszillierend/Unplausibles hin und herschwimmen

Sendeintervall der Tags ist auf max. 160 s eingestellt.

oszill_filter <- stationary_filter |> 
  group_by(animal_id) |> 
  arrange(date_time) |> 
  mutate(
    previous_ziel = lag(before_node),
    previous_time = lag(before_datetime),
    osz_timedif = seconds(date_time - previous_time),
    oszillierend = current_node == previous_ziel & osz_timedif < 165
  ) |> 
  filter(
    oszillierend != T
  )

3.3 Geschwindigkeitfilter

Die Distanzen zwischen den Nodes werden aus der OD-Matrix entnommen und darauf basierend die Geschwindigkeit berechnet.

speed_filtered <- oszill_filter |>
  select(date_time, animal_id, station_name, node_nr, deploy_latitude, deploy_longitude,  current_node, before_node, before_datetime) |> 
  group_by(animal_id) |> 
  arrange(date_time) |>
  mutate(
    distance_m = mapply(function(i, j) cost_matrix[i, j], current_node, before_node),
    time_difference_s = as.numeric(seconds(date_time - before_datetime)),
    speed_m_s = distance_m/time_difference_s
  ) |> 
  filter(
    distance_m != 0 & speed_m_s < 0.8
  ) |> 
  ungroup()

2.3 Berechnung von Bewegungsparametern und zeitlichen Mustern

Auf Basis der gefilterten Daten wurden die individuellen zurückgelegten Strecken und die Geschwindigkeiten berechnet. Dies wurde für den beschränkten und unbeschränkten Bewegunsraum durchgeführt. Zudem wurden räumliche Unterschiede in der Geschwindigkeit pro Netzwerkkante in beschränkten Bewegungsraum berechnet.Zeitliche Aktivitätsmuster wurden ohne räumlichen Bezug, jedoch mit dem gefilterten Datensatz berechnet.

4.1 Mittlere Geschwindigkeit beschränkter Bewegungsraum

speed_constrained_movement_space <- speed_filtered |> 
  select(date_time, animal_id, station_name,node_nr, deploy_latitude, deploy_longitude) |> 
  arrange(animal_id, date_time) |>
  group_by(animal_id) |> 
  mutate(
    current_node = node_nr,
    before_node = lag(node_nr),
    before_datetime = lag(date_time),
    distance_m = mapply(function(i, j) cost_matrix[i, j], current_node, before_node),
    time_diff_s = as.numeric(difftime(date_time, lag(date_time), units = "secs")),
    speed_m_s = distance_m/time_diff_s
  )


mean(speed_constrained_movement_space$speed_m_s, na.rm = T)
[1] 0.09595338
sum(speed_constrained_movement_space$distance_m, na.rm = T)
[1] 439378
individual_params_cms <- speed_constrained_movement_space |> 
  st_drop_geometry() |> 
  group_by(animal_id) |> 
  summarise(
    cms_mean_speed_m_s = mean(speed_m_s, na.rm = T),
    cms_distance_m = sum(distance_m, na.rm = T)
  )

4.2 Mittlere Geschwindigkeit ohne Constrained Movement

speed_open_space <- speed_filtered |> 
  st_as_sf(coords = c("deploy_longitude", "deploy_latitude"), crs = 4326) |> 
  st_transform(3035) |> 
  arrange(animal_id, date_time) |> 
  group_by(animal_id) |> 
  mutate(
    lag_geometry = lag(geometry),
    dist_m = st_distance(geometry, lag_geometry, by_element = TRUE),
    time_diff_s = as.numeric(difftime(date_time, lag(date_time), units = "secs")),
    speed_m_s = as.numeric(dist_m) / time_diff_s  # in m/s
  )

mean(speed_open_space$speed_m_s, na.rm = T)
[1] 0.09094664
sum(speed_open_space$dist_m, na.rm = T)
409280 [m]
individual_params_os <- speed_open_space |> 
  st_drop_geometry() |> 
  group_by(animal_id) |> 
  summarise(
    os_mean_speed = mean(speed_m_s, na.rm = T),
    os_distance_m = sum(dist_m, na.rm = T)
  )

4.3 Bewegungsparameter der räumlichen Modell zusammenführen

Noch “Anzahl Tage unterwegs” hinzufügen

days <- sea_arrivers_network |> 
  group_by(animal_id) |> 
  mutate(
    tage_unterwegs = difftime(max(date_time), min(date_time))
  ) |> 
  summarise(
    tage_unterwegs = max(tage_unterwegs)
  )

Parameters

# cms für Analyse mit Netzwerk
# os für Analyse über Luftlinie
movement_individuals <- cbind(individual_params_cms, individual_params_os[,2:3])
  
movement_individuals <- movement_individuals |> 
  mutate(
    cms_mean_speed_m_s = as.numeric(cms_mean_speed_m_s),
    os_mean_speed = as.numeric(os_mean_speed),
    cms_distance_m = as.numeric(cms_distance_m),
    os_distance_m = as.numeric(os_distance_m),
    speed_diff = cms_mean_speed_m_s - os_mean_speed,
    dist_diff = cms_distance_m - os_distance_m,
    dist_diff_dist = dist_diff/cms_distance_m
  ) |> 
  left_join(days, by = "animal_id") |> 
  mutate(
    
  )

# cor(movement_individuals$cms_distance_m, movement_individuals$dist_diff_dist, use = "complete.obs")

4.3.1 Distanzen

movement_distance_long <- pivot_longer(
  movement_individuals, 
  cols = c("cms_distance_m", "os_distance_m"),
  names_to = "movement_space", 
  values_to = "distance_m"
)

movement_distance_long$movement_space <- recode(movement_distance_long$movement_space,
                                                "cms_distance_m" = "CMS",
                                                "os_distance_m" = "OS")
# Boxplot erstellen
p_distanz <- ggplot(movement_distance_long, aes(x = movement_space, y = distance_m, fill = movement_space)) +
  geom_boxplot() +
  labs(x = "Movement Space", y = "Distanz (m)") +
  theme_minimal() +
  theme(legend.position = "none")

Statistischer Test

t.test(distance_m ~ movement_space, data = movement_distance_long)

    Welch Two Sample t-test

data:  distance_m by movement_space
t = 0.23235, df = 53.795, p-value = 0.8172
alternative hypothesis: true difference in means between group CMS and group OS is not equal to 0
95 percent confidence interval:
 -8201.312 10351.167
sample estimates:
mean in group CMS  mean in group OS 
         15692.07          14617.14 

4.3.2 Geschwindigkeit

movement_speed_long <- pivot_longer(movement_individuals, 
                        cols = c("cms_mean_speed_m_s", "os_mean_speed"),
                        names_to = "movement_space", 
                        values_to = "mean_speed_m_s")

movement_speed_long$movement_space <- recode(movement_speed_long$movement_space,
                             "cms_mean_speed_m_s" = "CMS",
                             "os_mean_speed" = "OS")


# Boxplot erstellen
p_geschwindigkeit <- ggplot(movement_speed_long, aes(x = movement_space, y = mean_speed_m_s, fill = movement_space)) +
  geom_boxplot() +
  labs(x = "Movement Space",
       y = "Mittlere Geschwindigkeit (m/s)") +
  theme_minimal() +
  theme(legend.position = "none")

Statistischer Test

t.test(mean_speed_m_s ~ movement_space, data = movement_speed_long)

    Welch Two Sample t-test

data:  mean_speed_m_s by movement_space
t = 0.20731, df = 47.816, p-value = 0.8367
alternative hypothesis: true difference in means between group CMS and group OS is not equal to 0
95 percent confidence interval:
 -0.0308116  0.0378949
sample estimates:
mean in group CMS  mean in group OS 
       0.06257870        0.05903705 
# Edges die beim Movement überquert wurden.
for (i in 1:nrow(speed_constrained_movement_space)) {
  path <- st_network_paths(
    blended,
    from = speed_filtered$before_node[i],
    to = speed_filtered$current_node[i]
  )
  speed_filtered$path_edges[[i]] <- path$edge_paths
}

edges_sf <- blended |> 
  activate("edges") |> 
  st_as_sf() |> 
  mutate(
    edge_ID = row_number()
  )

speed_filtered_long <- speed_filtered |> 
  mutate(path_edges = map(path_edges, ~ flatten_int(.x))) |> 
  unnest_longer(path_edges) |> 
  rename(edge_ID = path_edges)

edge_speeds <- speed_filtered_long |>
  filter(
    speed_m_s != Inf
  ) |>
  group_by(edge_ID) |>
  summarise(
    mean_speed_m_s = mean(speed_m_s)
  )

# st_write(edge_speeds, "geodata/network.gpkg", layer = "edge_speeds", delete_layer = T)

edges_sf <- edges_sf |>
  select(from, to, edge_ID) |> 
  left_join(edge_speeds, by = "edge_ID")

# st_write(edges_sf, "geodata/network.gpkg", layer = "edges_sf", delete_layer = T)

p_edge_speeds <- tm_shape(edges_sf) +
  tm_lines(col = "mean_speed_m_s", palette = "viridis", lwd = 2, title.col = "Mittlere Geschwindigkeit (m/s)") 

6.1 Aktivität nach Tageszeit

# Gefilterter Datensatz verwenden.
sea_arrivers <- speed_filtered

p_tag_nacht <- sea_arrivers |>
  mutate(tageszeit = if_else(hour(date_time) %in% 6:18, "Tag", "Nacht")) |>
  count(tageszeit) |>
  ggplot(aes(x = tageszeit, y = n, fill = tageszeit)) +
  geom_col() +
  labs(title = "Aktivitäten nach Tageszeiten", x = "Tageszeit", y = "Aktivitäts-Detektionen") +
  theme_minimal()

# p_tag_nacht

Statistischer Test

tageszeit_counts <- sea_arrivers |>
  mutate(tageszeit = if_else(hour(date_time) %in% 6:18, "Tag", "Nacht")) |>
  count(tageszeit)

# Wilcox test
wilcox.test(n ~ tageszeit, data = tageszeit_counts)

    Wilcoxon rank sum exact test

data:  n by tageszeit
W = 1, p-value = 1
alternative hypothesis: true location shift is not equal to 0

6.2 Aktivität nach Monaten

p_monate <- sea_arrivers |>
  mutate(monat = month(date_time, label = TRUE)) |>
  count(monat) |>
  ggplot(aes(x = monat, y = n)) +
  geom_col() +
  labs(title = "Aktivitäten nach Monaten", x = "Monat", y = "Aktivitäts-Detektionen") +
  theme_minimal()

p_monate

Statistischer Test

monat_counts <- sea_arrivers |>
  mutate(monat = month(date_time, label = TRUE)) |>
  count(monat)

# Kruskal test
kruskal.test(n ~ monat, data = monat_counts)

    Kruskal-Wallis rank sum test

data:  n by monat
Kruskal-Wallis chi-squared = 5, df = 5, p-value = 0.4159

6.2 Aktivität nach Jahreszeiten

p_saisons <- sea_arrivers |> 
  mutate(monat = month(date_time),
         jahreszeit = case_when(
           monat %in% c(12,1,2) ~ "Winter",
           monat %in% c(3,4,5) ~ "Frühling",
           monat %in% c(6,7,8) ~ "Sommer",
           monat %in% c(9,10,11) ~ "Herbst"
         )) |> 
  count(jahreszeit) |> 
  ggplot(aes(x = jahreszeit, y = n, fill = jahreszeit)) +
  geom_col() +
  labs(title = "Aktivitäten nach Jahreszeiten", x = "Jahreszeit", y = "Aktivitäts-Detektionen") +
  theme_minimal()

Statistischer Test

jahreszeit_counts <- sea_arrivers |> 
  mutate(monat = month(date_time),
         jahreszeit = case_when(
           monat %in% c(12,1,2) ~ "Winter",
           monat %in% c(3,4,5) ~ "Frühling",
           monat %in% c(6,7,8) ~ "Sommer",
           monat %in% c(9,10,11) ~ "Herbst"
         )) |> 
  count(jahreszeit)

# Kruskal test
kruskal.test(n ~ jahreszeit, data = jahreszeit_counts)

    Kruskal-Wallis rank sum test

data:  n by jahreszeit
Kruskal-Wallis chi-squared = 2, df = 2, p-value = 0.3679

3 Resultate

3.1 Muster der Aalbewegungen

Die Anzahl der Stationenwechsel während des Tages und der Nacht wurde erfasst. Es zeigte sich, dass die Aale im Vergleich zum Tag eine höhere Anzahl von Stationenwechseln in der Nacht aufwiesen (Abbildung 2).

Abbildung 2: Anzahl Stationenwechsel während dem Tag und der Nacht.

Die Anzahl der Stationenwechsel wurde auch aufgeteilt auf die Monate. Hier zeigt sich, dass im Oktober eine höhere Anzahl an Wechseln stattfand als in den anderen Monaten (Abbildung 3).

Abbildung 3: Anzahl Stationenwechsel aufgeteilt auf die Monate.

Die Anzahl der Stationenwechsel wurde zusätzlich aufgeteilt auf die Jahreszeiten. Es wurde eine höhere Anzahl an Stationenwechseln im Herbst im Vergleich zum Winter und Sommer festgestellt (Abbildung 4).

Abbildung 4: Anzahl Stationenwechsel aufgeteilt auf die Saisons

Die mittlere Geschwindigkeit der Aale wurde je nach Netzwerkkante untersucht. Die Aale wiesen unterschiedliche mittlere Geschwindigkeiten auf, die sich je nach Netzwerkkante unterschieden (Abbildung 5).

Abbildung 5: Mittlere Geschwindigkeit je nach Netzwerkkante.

3.2 Einfluss des räumlichen Modells auf die Analyse der Aalbewegungen

Die zurückgelegte Distanz aller Individuen wurde für den beschränkten Bewegungsraum (CMS) und den unbeschränkten Bewegungsraum (UCMS) verglichen. Die Aale legten im CMS tendenziell eine grössere Distanz zurück als im UCMS (Abbildung 6).

Abbildung 6: Zurückgelegte Distanz aller Indiviuen je nach Bewegungsraum.

In einer weiteren Analyse wurde die mittlere Geschwindigkeit aller Individuen im CMS und UCMS untersucht, wobei die Aale auch hier im CMS leicht höhere Werte hatten als im UCMS (Abbildung 7)

Abbildung 7: Mittlere Bewegungsgeschwindigkeit aller Indiviuen je nach Bewegungsraum.

4 Diskussion

4.1 Zeitliche Muster

Die Analyse ergab eine höhere Aktivität der Aale während der Nacht als während des Tages und eine deutliche Spitze in den Herbstmonaten. Diese Ergebnisse stehen im Einklang mit dem, was über das Abwanderungsverhalten des Europäischen Aals bekannt ist, nämlich dass er eine nachtaktive Art ist (Travade u. a. 2010; Vøllestad u. a. 1986; Verhelst u. a. 2018) und seine Abwanderung ins Meer hauptsächlich im Herbst beginnt (Bruijs und Durif 2009; Sandlund u. a. 2017; Verhelst u. a. 2018).

Auch wenn die statistischen Tests keine starken signifikanten Unterschiede zwischen Tag und Nacht (p = 0.057) oder den Jahreszeiten (p = 0.056) zeigten, deuten die beobachteten Tendenzen auf wichtige ökologische Muster hin, die für den Schutz der Art von Bedeutung sind. Zudem zeigen die erhaltenen Resultate einen klaren Trend, der auf relevante Variationen im Wanderverhalten hindeutet, auch wenn diese statistisch nicht solide bestätigt sind.

Diese Muster sind von grosser Bedeutung für den Schutz des Europäischen Aals, da sie helfen, die entscheidenden Zeiträume zu identifizieren, in denen Schutzmassnahmen verstärkt werden sollten, um die Wanderungen der Aale zu erleichtern und ihre Überlebenschancen zu erhöhen (Verhelst u. a. 2018).

4.2 Einfluss des räumlichen Modells auf die Analyse der Aalbewegung: CMS vs. UCMS

Die zurückgelegte Strecke sowie die Geschwindigkeit der Aale wurde in einem beschränkten (CMS) und unbeschränkten Bewegungsraum (UCMS) berechnet. Von Relevanz ist dabei die Strecke. Die Geschwindigkeit ist direkt davon abhängig, da die zeitlichen Dimension für beide Bewegungsräume gleich ist.

Die Ergebnisse zeigen, dass die mit dem CMS-Modell ermittelte Strecke leicht höher waren als die mit dem UCMS-Modell berechneten Strecke. Dies ist zu erwarten, da bei der UCMS-Methode die Luftlinie zwischen zwei Empfängern verwendet wird, was der kürzest möglichen Strecke entspricht. Diese Unterschiede erwiesen sich jedoch nicht als statistisch signifikant (Geschwindigkeit: p = 0.83; Strecke: p = 0.81). Das Gewässernetz, auf welchem das Netzwerk erstellt wurde, ist stark begradigt und enthält auch künstlich geschaffene Kanäle. In diesem Kontext ist die Verwendung eines CMS daher nicht notwendig. Falls das Gewässernetz stark mäandrierende Gewässer enthielte, wäre der Nutzen eines CMS grösser.

Beide Modelle eignen sich jedoch beide für die Verfolgung von migrierenden aquatischen Organismen.

5 Bibliographie

Bruijs, M. C., und C. M. Durif. 2009. „Silver eel migration and behaviour“. In Spawning migration of the European eel: Reproduction index, a useful tool for conservation management, 65–95. Springer Netherlands. https://doi.org/10.1007/978-1-4020-9911-4_5.
Cheng, J., B. Schloerke, B. Karambelkar, und Y. Xie. 2024. „leaflet: Create Interactive Web Maps with the JavaScript ’Leaflet’ Library“. https://CRAN.R-project.org/package=leaflet.
Csardi, G., und T. Nepusz. 2006. „The igraph software package for complex network research“. InterJournal, Complex Systems. https://igraph.org.
Firke, S. 2024. „janitor: Simple Tools for Examining and Cleaning Dirty Data“. https://CRAN.R-project.org/package=janitor.
Grolemund, G., und H. Wickham. 2011. „Dates and Times Made Easy with lubridate“. Journal of Statistical Software 40 (3): 1–25. https://www.jstatsoft.org/v40/i03/.
Halvorsen, S., L. Korslund, P. Ø. Gustavsen, und A. Slettan. 2020. „Environmental DNA analysis indicates that migration barriers are decreasing the occurrence of European eel (Anguilla anguilla) in distance from the sea“. Global Ecology and Conservation 24: e01245.
Hijmans, R. 2024. „geosphere: Spherical Trigonometry“. https://CRAN.R-project.org/package=geosphere.
InnovaSea Systems, Inc. 2025. „How much detection range can I expect from my tags and receivers?“ https://support.fishtracking.innovasea.com/s/article/How-much-detection-range-can-I-expect-from-my-tags-and-receivers.
Meer, L. van der, L. Abad, A. Gilardi, und R. Lovelace. 2024. „sfnetworks: Tidy Geospatial Networks“. https://CRAN.R-project.org/package=sfnetworks.
OpenAI. 2025. „ChatGPT (Version 4o) [Computer software]“.
OpenStreetMap contributors. 2017. „Planet dump“. https://planet.osm.org.
Pebesma, E. 2018. „Simple Features for R: Standardized Support for Spatial Vector Data“. The R Journal 10 (1): 439–46. https://doi.org/10.32614/RJ-2018-009.
Pebesma, E., und R. Bivand. 2023. Spatial Data Science: With applications in R. Chapman; Hall/CRC. https://doi.org/10.1201/9780429459016.
Pedersen, T. 2024. „ggraph: An Implementation of Grammar of Graphics for Graphs and Networks“. https://CRAN.R-project.org/package=ggraph.
Piper, A. T., J. C. Svendsen, R. M. Wright, und P. S. Kemp. 2017. „Movement patterns of seaward migrating European eel (Anguilla anguilla) at a complex of riverine barriers: Implications for conservation“. Ecology of Freshwater Fish 26 (1): 87–98.
Piper, A. T., R. M. Wright, A. M. Walker, und P. S. Kemp. 2013. „Escapement, route choice, barrier passage and entrainment of seaward migrating European eel, Anguilla anguilla, within a highly regulated lowland river“. Ecological Engineering 57: 88–96.
Quintella, B. R., C. S. Mateus, J. L. Costa, I. Domingos, und P. R. Almeida. 2010. „Critical swimming speed of yellow- and silver-phase European eel (Anguilla anguilla, L.)“. Journal of Applied Ichthyology 26 (3): 432–35. https://doi.org/10.1111/j.1439-0426.2010.01457.x.
R Core Team. 2024. „R: A Language and Environment for Statistical Computing“. Vienna, Austria: R Foundation for Statistical Computing. https://www.R-project.org/.
Sandlund, O. T., O. H. Diserud, R. Poole, K. Bergesen, M. Dillane, G. Rogan, und L. A. Vøllestad. 2017. „Timing and pattern of annual silver eel migration in two European watersheds are determined by similar cues“. Ecology and Evolution 7 (15): 5956–66. https://doi.org/10.1002/ece3.3099.
Tennekes, M. 2018. „tmap: Thematic Maps in R“. Journal of Statistical Software 84 (6): 1–39. https://doi.org/10.18637/jss.v084.i06.
Tennekes, Martijn. 2025. tmaptools: Thematic Map Tools. https://doi.org/10.32614/CRAN.package.tmaptools.
Travade, F., M. Larinier, S. Subra, P. Gomes, und E. De-Oliveira. 2010. „Behaviour and passage of European silver eels (Anguilla anguilla) at a small hydropower plant during their downstream migration“. Knowledge and Management of Aquatic Ecosystems 398: 01.
Tudorache, C., E. Burgerhout, S. Brittijn, und G. van den Thillart. 2015. „Comparison of swimming capacity and energetics of migratory European eel (Anguilla anguilla) and New Zealand short-finned eel (A. australis)“. Frontiers in Physiology 6: 256. https://doi.org/10.3389/fphys.2015.00256.
Verhelst, P., R. Baeyens, J. Reubens, J.-P. Benitez, J. Coeck, P. Goethals, M. Ovidio, J. Vergeynst, T. Moens, und A. Mouton. 2018. „European silver eel (Anguilla anguilla L.) migration behaviour in a highly regulated shipping canal“. Fisheries Research.
Verhelst, P., D. Buysse, R. Baeyens, N. De Maerteleire, P. Desmet, E. Gelaude, Y. Jacobs, u. a. 2020. „2012_LEOPOLDKANAAL - Acoustic telemetry data for European eel (Anguilla anguilla) in a polder area in Flanders (Belgium)“. https://doi.org/10.14284/428.
———, u. a. 2024. „2012_LEOPOLDKANAAL - Acoustic telemetry data for European eel (Anguilla anguilla) in a polder area in Flanders (Belgium)“. https://doi.org/10.14284/428.
Vøllestad, L. A., B. Jonsson, N. A. Hvidsten, T. F. Næsje, Ø. Haraldstad, und J. Ruud-Hansen. 1986. „Environmental Factors Regulating the Seaward Migration of European Silver Eels (Anguilla anguilla)“. Canadian Journal of Fisheries and Aquatic Sciences 43 (10): 1909–16. https://doi.org/10.1139/f86-236.
Waring, E., M. Quinn, A. McNamara, E. Arino de la Rubia, H. Zhu, und S. Ellis. 2022. „skimr: Compact and Flexible Summaries of Data“. https://CRAN.R-project.org/package=skimr.
Wickham, H., M. Averick, J. Bryan, W. Chang, L. D. McGowan, R. François, G. Grolemund, u. a. 2019. „Welcome to the tidyverse“. Journal of Open Source Software 4 (43): 1686. https://doi.org/10.21105/joss.01686.