Source code for movekit.plot

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
from .feature_extraction import *
import seaborn as sns
import folium
from tqdm import tqdm
import warnings
# import moviepy.editor as mp
from pandas.api.types import is_numeric_dtype, is_string_dtype



[docs]def plot_movement(data, frm, to): """ Plot 'x' and 'y' attributes for given Pandas DataFrame in specified time frame. :param data: Pandas DataFrame (should be sorted by 'time' attribute). :param frm: Starting from time step. Note that if time is stored as a date (if input data has time not stored as numeric type it is automatically converted to datetime) parameter has to be set using an datetime format: mkit.plot_movement(data, "2008-01-01", "2010-10-01") :param to: Ending to time step. :return: None. """ sns.relplot(x='x', y='y', data=data.loc[(data['time'] >= frm) & (data['time'] <= to), :], hue='animal_id', palette='tab10') plt.title("Plotting 'x' and 'y' coordinates") plt.xlabel("'x' coordinate") plt.ylabel("'y' coordinate") plt.grid() plt.show() return None
[docs]def animate_movement(data, viewsize): """ Animated version of plot_movement function. Animates 'x' and 'y' attributes for given Pandas DataFrame in specified time frame. :param data: Pandas DataFrame (should be sorted by 'time' attribute). :param viewsize: Int. Define how many time steps/frames should be visible in the animation. """ xmin = data['x'].min() xmax = data['x'].max() ymin = data['y'].min() ymax = data['y'].max() fig = plt.figure() ax = plt.axes(xlim=(xmin, xmax), ylim=(ymin, ymax)) # check if time format has to be converted if not is_numeric_dtype(data['time']): time_values = np.array(data['time']) time_values = np.unique(time_values) indices = np.sort(time_values) converter = {} for i in range(len(time_values)): converter[indices[i]] = i data = data.replace({'time': converter}) scat = ax.scatter(x='x', y='y', data=data.loc[(data['time'] >= 0) & (data['time'] <= viewsize), :]) # first frame def animate(i): # update frame sequentially subset = data.loc[(data['time'] >= i) & (data['time'] <= i+viewsize), :] x = subset['x'] y = subset['y'] scat.set_offsets(np.c_[x, y]) anim = animation.FuncAnimation( fig, animate, interval=100, frames=data['time'].max()) plt.draw() plt.show() return anim # need to return anim otherwise it gets eaten by garbage collector and animation is not updated
[docs]def plot_animal_timesteps(data): """ Plot the number of time steps for each 'animal_id' :param data_animal_id_groups: DataFrame containing movement records. :return: None """ # Initialize Python 3.X dict to hold number of time steps # for each animals- animals_timesteps = {} data_animal_id_groups = grouping_data(data) for aid in data_animal_id_groups.keys(): animals_timesteps[aid] = data_animal_id_groups[aid]['time'].count() # Sort 'animals_timesteps' dict in ascending order- # sorted((val, key) for (key, val) in animals_timesteps.items()) # [(43201, 312), (43201, 511), (43201, 607), (43201, 811), (43201, 905)] sns.barplot(x=list(animals_timesteps.keys()), y=list(animals_timesteps.values())) plt.xticks(range(len(animals_timesteps)), list(animals_timesteps.keys())) plt.title("Barchart: number of timesteps versus animals") plt.xlabel("Animal IDs") plt.ylabel("Number of time steps") plt.yticks(rotation=20) plt.show() return None
[docs]def plot_pace(avg_speed_data, feature="speed"): """ Plot average speed extracted feature for each animal. :param avg_speed_data: pandas Dataframe including average speed feature. :return: None. """ warnings.simplefilter("ignore") if feature == "speed": g = sns.FacetGrid(avg_speed_data, row="animal_id") g.map(sns.lineplot, "time", "average_speed", alpha=.7) g.add_legend() g.set_titles("Average speed for each animal") for x in g.axes_dict.keys(): g.axes_dict[x].set_title(f"Animal ID: {x}") g.axes_dict[x].set(xlabel='number of time steps', ylabel='avg speed') plt.show() if feature == "acceleration": g = sns.FacetGrid(avg_speed_data, row="animal_id") g.map(sns.lineplot, "time", "average_acceleration", alpha=.7) g.add_legend() g.set_titles("Average acceleration for each animal") for x in g.axes_dict.keys(): g.axes_dict[x].set_title(f"Animal ID: {x}") g.axes_dict[x].set(xlabel='number of time steps', ylabel='avg acc') plt.show() return None
[docs]def plot_animal(inp_data, animal_id): """ Plot individual animal's 'x' and 'y' coordinates. :param inp_data: DataFrame containing 'x' & 'y' attributes. :param animal_id: ID of animal to be plotted. :return: None. """ data_animal_id_groups = grouping_data(inp_data) if animal_id not in data_animal_id_groups.keys(): print( "\nError!! animal id: {0} does NOT exist in the provided data!\n". format(animal_id)) return None sns.relplot(x=data_animal_id_groups[animal_id]['x'], y=data_animal_id_groups[animal_id]['y']) plt.title("Plotting animal id: {0}".format(animal_id), y=0.985) plt.xlabel("x coordinates") plt.ylabel("y coordinates") plt.show() return None
[docs]def plot_geodata(data, latitude_colname = "location-lat", longitude_colname = "location-long", animal_list=[], movement_lines=False): """ Function to plot geo data on an interactive map using Open Street Maps. :param data: DataFrame containing the movement records :param latitude_colname: name of the column containing the latitude of each movement record :param longitude_colname: name of the column containing the longitude of each movement record :param animal_list: list containing animal_id's of all animals to be plotted (Default: every animal in data is plotted) :param movement_lines: Boolean whether movement lines between different location markers of animals are plotted return: map Object containing markers for each tracked animal position """ warnings.warn("As plotting the geodata on an interactive map is very time-consuming, it is recommended to reduce the size of the data as much as possible.") df = grouping_data(data) if animal_list != []: map = folium.Map(location=[df[animal_list[0]].loc[0, latitude_colname], data.loc[0, longitude_colname]], zoom_start=15) # start location is determined by first animal in specified animal_list for aid in tqdm(animal_list, position=0, desc="Plotting geo data for specified animals"): animal_data = df[aid] for i in range(len(animal_data)): folium.Marker(location=[animal_data.loc[i,latitude_colname], animal_data.loc[i,longitude_colname]], tooltip=f'Animal ID: {animal_data.loc[i,"animal_id"]}', popup=f'Animal ID: {animal_data.loc[i,"animal_id"]}\nTime: {animal_data.loc[i,"time"]}', icon = folium.Icon(color="blue")).add_to(map) if movement_lines: try: folium.PolyLine(locations=[[animal_data.loc[i-1,latitude_colname], animal_data.loc[i-1,longitude_colname]], [animal_data.loc[i,latitude_colname], animal_data.loc[i,longitude_colname]]], tooltip=i,popup=f'Time Range of movement: From {animal_data.loc[i-1,"time"]} to {animal_data.loc[i,"time"]}').add_to(map) except: pass else: map = folium.Map(location=[data.loc[0, latitude_colname], data.loc[0, longitude_colname]], zoom_start=15) # start location is determined by first observation for aid in tqdm(df.keys(),position=0, desc="Plotting geo data for all animals"): animal_data = df[aid] for i in range(len(animal_data)): folium.Marker(location=[animal_data.loc[i, latitude_colname], animal_data.loc[i, longitude_colname]], tooltip=f'Animal ID: {animal_data.loc[i, "animal_id"]}', popup=f'Animal ID: {animal_data.loc[i, "animal_id"]}\nTime: {animal_data.loc[i, "time"]}', icon=folium.Icon(color="blue")).add_to(map) if movement_lines: try: folium.PolyLine(locations=[[animal_data.loc[i-1,latitude_colname], animal_data.loc[i-1,longitude_colname]], [animal_data.loc[i,latitude_colname], animal_data.loc[i,longitude_colname]]], tooltip=i,popup=f'Time Range of movement: From {animal_data.loc[i-1,"time"]} to {animal_data.loc[i,"time"]}').add_to(map) except: pass return map
[docs]def save_geodata_map(map, filename): """ save the created geodata map as a file :param map: map object to be saved. :param filename: name of the new created file containing the map. """ try: map.save(filename) except: warnings.warn("Map could not be saved. Please try another file extension, f.e. '.html'")
[docs]def save_animation_plot(animation_object, filename): """ save animation as gif in working directory. (mp4 file is not working at the moment as moviepy import error) :param animation_object: created animation object :param filename: name of the two files which are created """ # save as gif writergif = animation.PillowWriter(fps=30) animation_object.save(f'{filename}.gif', writer=writergif) """ # save as mp4 clip = mp.VideoFileClip(f'{filename}.gif') clip.write_videofile(f'{filename}.mp4') """
[docs]def plot_heatmap(data, time0_start, time0_end, round_digits=1, font_size=10, linewidth=0.5): """ Plot a heatmap for the mover for user defined time interval. :param data: data frame returned by function getis_ord(): Data frame containing xy- interval coordinates and respective Getis-Ord statistic. :param time0_start: beginning time of the earliest interval included in the heatmap. :param time0_end: beginning time of the latest interval included in the heatmap. :param round_digits: for clear axis description the xy-values of the displayed intervals are rounded to have user defined number of digits. :param font_size: for clear axis description font size of the axis ticks can be defined. :param linewidth: width of the line dividing each cell in heatmap. """ pd.options.mode.chained_assignment = None # disable warning that we set values on slice of original dataframe data = data.loc[(data['time0'] >= time0_start) & (data['time0'] <= time0_end), :] data.loc[:, ['x0', 'x1', 'y0', 'y1']] = data[['x0', 'x1', 'y0', 'y1']].apply(lambda x: round(x, round_digits)) data = pd.pivot_table(data, values='Getis-Ord Score', index=['y0', 'y1'], columns=['x0', 'x1'], aggfunc=np.mean) ax = sns.heatmap(data, linewidth=linewidth, cmap='Reds') ax.tick_params(labelsize=font_size) plt.show()