https://colab.research.google.com/assets/colab-badge.svg

EcoGraph#

Setup#

Ecoscope#

[ ]:
ECOSCOPE_RAW = "https://raw.githubusercontent.com/wildlife-dynamics/ecoscope/master"

# !pip install ecoscope
[ ]:
import os
import sys

import geopandas as gpd
import pandas as pd
import shapely
import sklearn.preprocessing

import ecoscope

ecoscope.init()

Google Drive Setup#

[ ]:
output_dir = "Ecoscope-Outputs"

if "google.colab" in sys.modules:
    from google.colab import drive

    drive.mount("/content/drive/", force_remount=True)
    output_dir = os.path.join("/content/drive/MyDrive/", output_dir)

os.makedirs(output_dir, exist_ok=True)

Load sample Relocations#

[ ]:
ecoscope.io.download_file(
    f"{ECOSCOPE_RAW}/tests/sample_data/vector/er_relocs.csv.zip",
    os.path.join(output_dir, "er_relocs.csv.zip"),
)
data = pd.read_csv(os.path.join(output_dir, "er_relocs.csv.zip"), header=0, index_col=0)
[ ]:
gdf = gpd.GeoDataFrame(data, geometry=data["geometry"].apply(lambda x: shapely.wkt.loads(x)), crs=4326)
relocs = ecoscope.base.Relocations.from_gdf(gdf)

Create Trajectory from Relocations#

[ ]:
traj = ecoscope.base.Trajectory.from_relocations(relocs)
traj

List unique Subjects in Trajectory:

[ ]:
traj["groupby_col"].unique()

Visualize:

[ ]:
traj.explore()

Filter:

[ ]:
traj_seg_filter = ecoscope.base.TrajSegFilter(
    min_length_meters=0.0,
    max_length_meters=50000,
)

traj.apply_traj_filter(traj_seg_filter, inplace=True)
traj.remove_filtered(inplace=True)
traj["geometry"].explore()

EcoGraph#

EcoGraph extends the concepts of applying network metrics to wildlife tracking data presented in Bastille-Roussseau et al. (2018) [https://esajournals.onlinelibrary.wiley.com/doi/abs/10.1002/eap.1697].

First, we need to create our EcoGraph object. We need to choose the pixel size (Default : 15 meters). A good value is the mean step length.

Note 1 : The computation of an EcoGraph can be significantly accelerated through the optional cutoff argument. It allows to obtain a more or less precise approximation of the betweenness index for each node of an EcoGraph (the higher the cutoff value, the more accurate the approximation will be). When a cutoff value is not specified by the user, it is set by default to None and the EcoGraph object will compute the true betweenness index values.

Note 2 : The user can also experiment with the optional radius argument. This parameter is used for the computation of the Collective Influence metric (see : https://www.nature.com/articles/nature14604). The default value is set to 2. See the paper for more info on how the radius parameters impacts the computation of the collective influence metric.

[ ]:
mean_step_length = traj["dist_meters"].mean()
print(f"Mean step length : {mean_step_length} meters")

ecograph = ecoscope.analysis.Ecograph(traj, resolution=mean_step_length, radius=2, cutoff=None, tortuosity_length=3)

The features computed by Ecograph are the following:

Movement covariates :

  • dot_product : Average cosine of the turning angle (radians) for each node

  • step_length : Average step length (meters) for each node

  • speed : Average speed (km/hr) for each node

  • sin_time : Average sine transform of the seconds past midnight of the recorded locations associated with each node. The sine transform of the seconds past midnight of a recorded location is denoted by

    \[sin\_time = \sin \left(2\pi \cdot \frac{t}{86400} \right)\]

    where \(t\) corresponds to the seconds past midnight of one recorded location.

  • cos_time : Average cosine transform of the seconds past midnight of the recorded locations associated with each node. The cosine transform of the seconds past midnight of a recorded location is denoted by

    \[cos\_time = \cos \left(2\pi \cdot \frac{t}{86400} \right)\]

    where \(t\) corresponds to the seconds past midnight of one recorded location.

  • tortuosity_1 : Average straightness index for each node, as described in (Benhamou, Journal of Theoretical Ecology, 2004). The straightness index of an \(N\) steps trajectory (where \(N\) is equal to the tortuosity_length parameter of the EcoGraph constructor) is denoted by

    \[straightness\_index = \frac{D}{L}\]

    where \(D\) is the beeline distance between the first and last points in the trajectory, and \(L\) is the path length travelled.

  • tortuosity_2 : Average path tortuosity for each node, as described in (Whittington et al., Ecology and Society, 2004). The path tortuosity of an \(N\) steps trajectory (where \(N\) is equal to the tortuosity_length parameter of the EcoGraph constructor) is denoted by

    \[path\_tortuosity = \log \left( \frac{L}{R^{2}} \right )\]

    where \(L\) is the temporal duration of the trajectory (in hours), and \(R\) is the path length travelled.

Network metrics :

  • weight : The weight of each node (How many times a location has been recorded in that node).

  • betweenness : The betweenness index of each node.

  • degree : The degree of each node.

  • collective_influence : The collective influence value of each node

The EcoGraph object can export a GeoTIFF raster for each of these metric through the to_geotiff function. It takes as an input four parameters:

  • feature: The feature of interest

  • output_path: The output path for writing the GeoTIFF file

  • individual (Optional) : The individual for which we want to output the node feature. The default value is set to all

  • interpolation (Optional) : The standard EcoGraph approach creates gaps in areas where no locations are observed (only pixels with gps locations in them have values). This can sometime limit interpretability or the visual appeal of the maps produced. To assist with this, the interpolation parameter allows to linearly interpolate each step (i.e. straight line) and assign the network metric of each starting location to the whole step. When multiples overlap in a pixel, a function is applied to summarize these steps, and the user has to choose which kind of summary they want. The default value is None (no interpolation applied), but the user can choose one of these four types of interpolation : mean, max, min or median.

The following code cells shows some examples of the usage of the to_geotiff function.

Betweenness#

Betweenness index raster, without interpolation#

[ ]:
ecograph.to_geotiff(
    "betweenness",
    os.path.join(output_dir, "salif_btwn_int.tif"),
    individual="1d22ff96-44d4-45c4-adc3-db1513acbe7d",
    interpolation=None,
)

# Create a GeoDataFrame from feature raster
gdf = ecoscope.analysis.ecograph.get_feature_gdf(os.path.join(output_dir, "salif_btwn_int.tif"))

# Visualize
gdf.explore(column="value", cmap="viridis")

Betweenness index raster, with interpolation#

[ ]:
ecograph.to_geotiff(
    "betweenness",
    os.path.join(output_dir, "salif_btwn_int.tif"),
    individual="1d22ff96-44d4-45c4-adc3-db1513acbe7d",
    interpolation="max",
)

# Create a geodataframe from the feature raster
gdf = ecoscope.analysis.ecograph.get_feature_gdf(os.path.join(output_dir, "salif_btwn_int.tif"))

# Visualize
gdf.explore(column="value", cmap="viridis")

Betweenness index raster, for all individuals, with interpolation#

[ ]:
ecograph.to_geotiff("betweenness", os.path.join(output_dir, "all_btwn_int.tif"), interpolation="max")

# Create a geodataframe from the feature raster
gdf = ecoscope.analysis.ecograph.get_feature_gdf(os.path.join(output_dir, "all_btwn_int.tif"))

# Visualize
gdf.explore(column="value", cmap="viridis")

Dot-Product#

[ ]:
# Dot product raster, with interpolation
ecograph.to_geotiff(
    "dot_product",
    os.path.join(output_dir, "ramata_dotprod.tif"),
    individual="cd26bcf8-5c50-4aea-a888-f7691e1bac81",
    interpolation="mean",
)

# Create a geodataframe from the feature raster
gdf = ecoscope.analysis.ecograph.get_feature_gdf(os.path.join(output_dir, "ramata_dotprod.tif"))

# Visualize
gdf.explore(column="value", cmap="viridis")

Degree#

[ ]:
# Degree raster, with interpolation
ecograph.to_geotiff(
    "degree",
    os.path.join(output_dir, "salif_degree_int.tif"),
    individual="1d22ff96-44d4-45c4-adc3-db1513acbe7d",
    interpolation="mean",
)

# Create a geodataframe from the feature raster
gdf = ecoscope.analysis.ecograph.get_feature_gdf(os.path.join(output_dir, "salif_degree_int.tif"))

# Visualize
gdf.explore(column="value", cmap="viridis")

Collective Influence#

[ ]:
# Collective influence raster, with interpolation
ecograph.to_geotiff(
    "collective_influence",
    os.path.join(output_dir, "salif_ci_int.tif"),
    individual="1d22ff96-44d4-45c4-adc3-db1513acbe7d",
    interpolation="max",
)

# Create a geodataframe from the feature raster
gdf = ecoscope.analysis.ecograph.get_feature_gdf(os.path.join(output_dir, "salif_ci_int.tif"))

# Visualize
gdf.explore(column="value", cmap="viridis")

Transform a GeoTIFF output#

Use the optional transform parameter of the to_geotiff function to apply one of the sklearn.preprocessing transform (see : https://scikit-learn.org/stable/modules/classes.html#module-sklearn.preprocessing) to the GeoTIFF output

[ ]:
# Degree raster, with interpolation and standardization
ecograph.to_geotiff(
    "degree",
    os.path.join(output_dir, "salif_degree_mean_std.tif"),
    individual="1d22ff96-44d4-45c4-adc3-db1513acbe7d",
    interpolation="mean",
    transform=sklearn.preprocessing.StandardScaler(),
)

# Create a geodataframe from the feature raster
gdf = ecoscope.analysis.ecograph.get_feature_gdf(os.path.join(output_dir, "salif_degree_mean_std.tif"))
gdf.to_feather(os.path.join(output_dir, "salif_degree_mean_std.feather"))

# Visualize
gdf.explore(column="value", cmap="viridis")
[ ]:
# Degree raster, with interpolation and Min-Max Scaling
ecograph.to_geotiff(
    "collective_influence",
    os.path.join(output_dir, "salif_ci_max_minmax.tif"),
    individual="1d22ff96-44d4-45c4-adc3-db1513acbe7d",
    interpolation="max",
    transform=sklearn.preprocessing.MinMaxScaler(),
)

# Create a geodataframe from the feature raster
gdf = ecoscope.analysis.ecograph.get_feature_gdf(os.path.join(output_dir, "salif_ci_max_minmax.tif"))

# Visualize
gdf.explore(column="value", cmap="viridis")

Transform a GeoTIFF output#

Troubleshooting#

The to_geotiff function will throw an exception if one the parameters is not correctly specified.

[ ]:
# Wrong feature name
try:
    ecograph.to_geotiff(
        "fjofdojodf",
        os.path.join(output_dir, "salif_ci_int.tif"),
        individual="1d22ff96-44d4-45c4-adc3-db1513acbe7d",
        interpolation="max",
    )
except ecoscope.analysis.ecograph.FeatureNameError as e:
    print(e)
[ ]:
# Wrong individual name
try:
    ecograph.to_geotiff(
        "collective_influence", os.path.join(output_dir, "salif_ci_int.tif"), individual="ofjoodjo", interpolation="max"
    )
except ecoscope.analysis.ecograph.IndividualNameError as e:
    print(e)
[ ]:
# Wrong interpolation type
try:
    ecograph.to_geotiff(
        "collective_influence",
        os.path.join(output_dir, "salif_ci_int.tif"),
        individual="1d22ff96-44d4-45c4-adc3-db1513acbe7d",
        interpolation="dofjojfs",
    )
except ecoscope.analysis.ecograph.InterpolationError as e:
    print(e)

Export an EcoGraph to csv#

For further data analyses on the metric values for each node (eg. clustering, classification, etc.), the to_csv function allows to output a CSV file of the feature values for each node in the EcoGraph of each individual in the dataset.

[ ]:
ecograph.to_csv(os.path.join(output_dir, "features_dataset.csv"))
df = pd.read_csv(os.path.join(output_dir, "features_dataset.csv"))
df