<b>scikit-mobility</b>: A <i>Python</i> Library for the Analysis, Generation, and Risk Assessment of Mobility Data

La última década ha sido testigo de la aparición de conjuntos de datos de movilidad masiva, como pistas generadas por dispositivos GPS, registros de detalles de llamadas y publicaciones geoetiquetadas de plataformas de redes sociales. Estos conjuntos de datos han fomentado una vasta producción científica sobre diversas aplicaciones del análisis de movilidad, que van desde la epidemiología computacional hasta la planificación urbana y la ingeniería del transporte. Una línea de literatura aborda los problemas de limpieza de datos relacionados con las trayectorias espaciotemporales brutas, mientras que la segunda línea de investigación se centra en descubrir las

section_content: The last decade has witnessed the emergence of massive datasets of digital traces that portray human movements at an unprecedented scale and detail. Examples include tracks generated by GPS devices embedded in personal smartphones (Zheng, Wang, Zhang, Xie, and Ma 2008), private vehicles (Pappalardo, Rinzivillo, Qu, Pedreschi, and Giannotti 2013) or boats (Fernandez Arguedas, Pallotta, and Vespe 2018) ; call detail records produced as a by-product of the communication between cellular phones and the mobile phone network (González, Hidalgo, and Barabási 2008; Barlacchi et al. 2015) ; geotagged posts from the most disparate social media platforms (Noulas, Scellato, Lambiotte, Pontil, and Mascolo 2012) ; even traces describing the sports activity of amateurs or professional athletes (Rossi, Pappalardo, Cintia, Iaia, Fernández, and Medina 2018). The availability of digital mobility data has attracted enormous interests from scientists of diverse disciplines, fueling advances in several applications, from computational health (Tizzoni et al. 2012; Barlacchi, Perentis, Mehrotra, Musolesi, and Lepri 2017) to the estimation of air pollution (Nyhan, Kloog, Britter, Ratti, and Koutrakis 2018; Bohm, Nanni, and Pappalardo 2021), from the design of recommender systems (Wang, Pedreschi, Song, Giannotti, and Barabasi 2011) to the optimization of mobile and wireless networks (Karamshuk, Boldrini, Conti, and Passarella 2011; Tomasini, Mahmood, Zambonelli, Brayner, and Menezes 2017), from transportation engineering and urban planning (Zhao, Tarkoma, Liu, and Vo 2016) to the estimation of migratory flows (Simini, González, Maritan, and Barabási 2012; Ahmed et al. 2016 ) and people's place of residence (Pappalardo, Ferres, Sacasa, Cattuto, and Bravo 2021), from the well-being status of municipalities, regions, and countries (Pappalardo, Vanhoof, Gabrielli, Smoreda, Pedreschi, and Giannotti 2016b; Voukelatou et al. 2020) to the prediction of traffic and future displacements (Zhang, Zheng, and Qi 2017; Rossi, Barlacchi, Bianchini, and Lepri 2019). 

It is hence not surprising that the last decade has also witnessed a vast scientific production on various aspects of human mobility (Luca, Barlacchi, Lepri, and Pappalardo 2021; Wang, Kong, Xia, and Sun 2019; Blondel, Decuyper, and Krings 2015; Barbosa et al. 2018). The first strand of literature addresses data pre-processing issues related to mobility data, such as how to extract meaningful locations from raw spatiotemporal trajectories, how to filter, reconstruct, compress and segment them, or how to cluster and classify them (Zheng 2015). As a result, in the literature, there is a vast repertoire of techniques that allow scientists and professionals to improve the quality of their mobility data. 

The second line of research focuses on discovering the statistical laws that govern human mobility. These studies document that, far from being random, human mobility is characterized by predictable patterns, such as a stunning heterogeneity of human travel patterns (González et al. 2008) ; a strong tendency to routine and a high degree of predictability of individuals' future whereabouts (Song, Qu, Blumm, and Barabási 2010b) ; the presence of the returners and explorers dichotomy (Pappalardo, Simini, Rinzivillo, Pedreschi, Giannotti, and Barabási 2015) ; a conservative quantity in the number of locations actively visited by individuals (Alessandretti, Sapiezynski, Sekara, Lehmann, and Baronchelli 2018), and more (Barbosa et al. 2018; Luca et al. 2021). These quantifiable patterns are universal across different territories and data sources and are usually referred to as the "laws" of human mobility. 

The third strand of literature focuses on designing generative algorithms, i. e., models that can generate synthetic trajectories able to reproduce, realistically, the laws of human mobility. A class of algorithms aim to reproduce spatial properties of mobility (Song, Koren, Wang, and Barabási 2010a; Pappalardo, Rinzivillo, and Simini 2016a) ; another one focuses on the accurate representation of the time-varying behavior of individuals (Barbosa, de Lima-Neto, Evsukoff, and Menezes 2015; Alessandretti et al. 2018). More recently, some approaches rely on machine learning to propose generative algorithms that are realistic with respect to both spatial and temporal properties of human mobility (Pappalardo and Simini 2018; Jiang, Yang, Gupta, Veneziano, Athavale, and González 2016; Luca et al. 2021). Although the generation of realistic trajectories is a complex and still open problem, the existing algorithms act as baselines for the evaluation of new approaches. 

Finally, a line of research addresses the crucial problem of privacy: people's movements might reveal confidential personal information or allow the re-identification of individuals in a database, creating serious privacy risks (de Montjoye, Hidalgo, Verleysen, and Blondel 2013; Fiore et al. 2020). Since 2018, the EU General Data Protection Regulation (GDPR) explicitly imposes on data controllers an assessment of the impact of data protection for the riskiest data analyses. Driven by these sensitive issues, in recent years, researchers have developed algorithms, methodologies, and frameworks to estimate and mitigate the individual privacy risks associated with the analysis of digital data in general (Monreale, Rinzivillo, Pratesi, Giannotti, and Pedreschi 2014) and mobility records in particular (Pellungrini, Pappalardo, Pratesi, and Monreale 2017; Pellungrini, Pappalardo, Simini, and Monreale 2022; de Montjoye et al. 2013 de Montjoye et al., 2018)). Despite the increasing importance of mobility analysis for many scientific and industrial domains, there is no statistical software that can support scientists and practitioners with all the aspects of mobility analysis mentioned above (Section 9). 

To fill this gap, we propose scikit-mobility (Pappalardo, Simini, Barlacchi, and Pellungrini 2022), a Python (Van Rossum et al. 2011 ) library that has the ambition of providing scientists and practitioners with an environment to reproduce existing research and perform analysis of mobility data. In particular, the library allows the user to:

1. Load and represent mobility data, both at the individual and the collective level, through easy-to-use data structures ('TrajDataFrame' and 'FlowDataFrame') based on the standard Python libraries numpy (Oliphant 2006), pandas (McKinney 2010) and geopandas (Jordahl et al. 2019 ) (Section 2), as well as to visualize trajectories and flows on interactive maps based on the Python libraries folium (Fernandes et al. 2019) and matplotlib (Hunter 2007 ) (Section 4). 

2. Clean and pre-process mobility data using state-of-the-art techniques, such as trajectory clustering, compression, segmentation, and filtering. The library also provides the user with a way to track all the operations performed on the original data (Section 3). 

3. Analyze mobility data by using the main measures characterizing mobility patterns both at the individual and at the collective level (Section 5), such as the computation of travel and characteristic distances, object and location entropies, location frequencies, waiting times, origin-destination matrices, and more. 

4. Run the most popular mechanistic generative models to simulate individual mobility, such as the exploration and preferential return model (EPR) and its variants (Section 6), and commuting and migratory flows, such as the gravity model and the radiation model (Section 7). 

5. Estimate the privacy risk associated with the analysis of a given mobility dataset through the simulation of the re-identification risk associated with a vast repertoire of privacy attacks (Section 8). 

Next-location prediction, i. e., predicting the next location(s) an individual will visit given their mobility history (Luca et al. 2021; Wu, Luo, Shao, Tian, and Peng 2018), is a relevant mobility-related task not covered in the current version of scikit-mobility. We plan to include location prediction algorithms in future versions of the library. 

Note that, while scikit-mobility has been conceived for human movement analysis and the privacy module makes sense for human mobility data only, most features can be applied to other types of mobility (e. g., boats, animal movements, boat trips). scikit-mobility is designed to deal with spatiotemporal trajectories and mobility flows and functions to deal with other types of mobility-related data, such as accelerometer data from wearable devices are not currently covered in this library. 

The methods currently implemented have been chosen by the authors based mostly on their expertise and are not meant to be exhaustive. In future library releases, we plan to expand the range of methods and models. 

scikit-mobility is publicly available on GitHub at: https://scikit-mobility. github. io/ scikit-mobility/. Tutorials on how to use the library for mobility analysis are available at: https://github. com/scikit-mobility/tutorials. The documentation describing all the classes and functions of scikit-mobility is available at: https://scikit-mobility. github. io/scikit-mobility/.
section_name: Introduction

section_content: scikit-mobility provides two data structures to deal with raw trajectories and flows between places. Both the data structures are an extension of the 'DataFrame' implemented in the data analysis library pandas (McKinney 2010). Thus, both 'TrajDataFrame' and 'FlowDataFrame' inherit all the functionalities provided by the 'DataFrame' and all the efficient optimizations for reading and writing tabular data (e. g., mobility datasets). This choice allows broad compatibility of scikit-mobility with other Python libraries and machine learning tools, such as scikit-learn (Pedregosa et al. 2011). 

The current version of the library is designed to work with the latitude and longitude system (epsg:4326), the most used in practical scenarios of mobility analysis. Therefore, the Haversine formula is the default when the library's functions compute distances. We plan to extend the library to deal with other reference systems, even user-defined ones. This extension would imply associating a custom distance function to a reference system.
section_name: Data structures

section_content: Mobility data describe the movements of a set of moving objects during a period of observation. The objects may represent individuals (González et al. 2008), animals (Ramos-Fernández, Mateos, Miramontes, Cocho, Larralde, and Ayala-Orozco 2004), private vehicles (Pappalardo et al. 2015), boats (Fernandez Arguedas et al. 2018), or even players on a sports field (Rossi et al. 2018). Mobility data are generally collected in an automatic way as a by-product of human activity on electronic devices (e. g., mobile phones, GPS devices, so- cial networking platforms, video cameras) and stored as trajectories, a temporally ordered sequence of spatiotemporal points where an object stopped in or went through. In the literature of mobility analytics, a trajectory is often formally defined as follows (Luca et al. 2021; Zheng, Capra, Wolfson, and Yang 2014; Zheng 2015) : Definition 2. 1 (Trajectory). The trajectory of an object u is a temporally ordered sequence of tuples T u = ⟨(l 1, t 1 ), (l 2, t 2 ),. . ., (l n, t n )⟩, where l i = (x i, y i ) is a location, x i and y i are the coordinates of the location, and t i is the corresponding timestamp, with t i < t j if i < j. 

In scikit-mobility, a set of trajectories is described by a 'TrajDataFrame' (Figure 1 ), an extension of the pandas 'DataFrame' that has specific columns names and data types. A row in the 'TrajDataFrame' represents a point of the trajectory, described by three mandatory fields (aka columns): latitude (type: float), longitude (type: float) and datetime (type: datetime). 

Additionally, two optional columns can be specified. The first one is uid: it identifies the object associated with the trajectory point and can be of any type (string, int, or float). If uid is not present, scikit-mobility assumes that the 'TrajDataFrame' contains trajectories associated with a single moving object. The second one is tid (any type) and specifies the trajectory's identifier to which the point belongs. If tid is not present, scikit-mobility assumes that all the rows in the 'TrajDataFrame' associated with a uid belong to the same trajectory. Note that, besides the mandatory columns, the user can add to a 'TrajDataFrame' as many columns as they want since the data structures in scikit-mobility inherit all the pandas 'DataFrame' functionalities. 

Each 'TrajDataFrame' object also has two mandatory attributes:

• crs (type: dictionary): indicates the coordinate reference system associated with the trajectories. By default it is epsg:4326 (the latitude/longitude reference system). 

• parameters (type: dictionary): indicates the operations that have been applied to the 'TrajDataFrame'. This attribute is a dictionary the key of which is the signature of the function applied (see Section 3 for more details). 

scikit-mobility provides functions to create a 'TrajDataFrame' from mobility data stored in different formats (e. g., dictionaries, lists, pandas 'DataFrame's). To load a 'TrajDataFrame' from a file, we first import the library:
section_name: Trajectory

section_content: Then, we use the method from_file of the 'TrajDataFrame' class to load the mobility data from the file path:

>>> tdf = skmob. TrajDataFrame. from_file('geolife_sample. txt. gz')

Note that the values corresponding to the lat, lng, and datetime columns must be necessarily float, float and datetime, respectively, otherwise the library raises an exception. 1

The crs attribute of the loaded 'TrajDataFrame' provides the coordinate reference system, while the parameters attribute provides a dictionary with meta-information about the data. 

When we load the data from a file, scikit-mobility adds to the parameters attribute the key from_file, which indicates the file's path. 

>>> print(tdf. crs) The TrajDataFrame constructor forces the conversion of the three mandatory columns' values to the preset types. If the conversion fails, it raises an exception. For example, the constructor can successfully convert string "39. 1432" to float 39. 1432, but it cannot convert (hence raising an exception) string "39. 2ui2".
section_name: >>> import skmob

section_content: Origin-destination matrices, aka flows, are another common representation of mobility data. While trajectories refer to single objects' movements, flows refer to aggregated movements of objects between a set of locations. An example of flows is the daily commuting flows between the neighborhoods of a city. Formally, we define an origin-destination matrix as: Definition 2. 2 (Origin-destination matrix or flows). An origin-destination matrix T is a n × m matrix where n is the number of distinct origin locations, m is the number of distinct destination locations, T ij is the number of objects traveling from location i to location j. 

In scikit-mobility, an origin-destination matrix is described by the 'FlowDataFrame' structure. A 'FlowDataFrame' is an extension of the pandas 'DataFrame' with specific column names and data types. A row in a 'FlowDataFrame' represents a flow of objects between two locations, described by three mandatory columns: origin (any type), destination (any type) and flow (type: integer). The user can add to a 'FlowDataFrame' as many columns as they want. 

In mobility tasks, the territory is often discretized by mapping the coordinates to a spatial tessellation, i. e., a covering of the bi-dimensional space using a countable number of geometric shapes (e. g., squares, hexagons) called tiles, with no overlaps and no gaps. For instance, for the analysis or prediction of mobility flows, a spatial tessellation is used to aggregate flows of people moving among locations (the tiles of the tessellation). For this reason, each 'FlowDataFrame' is associated with a spatial tessellation, a geopandas 'GeoDataFrame' that contains two mandatory columns: tile_ID (any type) indicates the identifier of a location; geometry indicates the geometric shape that describes the location on a territory (e. g., a square, a hexagon, the shape of a neighborhood). 2 Note that each location identifier in the origin and destination columns of a 'FlowDataFrame' must be present in the associated spatial tessellation. Otherwise, the library raises an exception. Similarly, scikit-mobility raises an exception if the type of the origin and destination columns in the 'FlowDataFrame' and the type of the tile_ID column in the associated tessellation are different. 

The code below loads a spatial tessellation and a 'FlowDataFrame' from the corresponding files. First, we import the scikit-mobility and the geopandas libraries.
section_name: Flows

section_content: Then, we load the spatial tessellation and the 'FlowDataFrame' using the from_file method of the classes 'GeoDataFrame' and 'TrajDataFrame', respectively. Note that from_file requires to specify the associated spatial tessellation through the tessellation argument. 

>>> tessellation = gpd. GeoDataFrame. from_file('NY_counties_2011. geojson') >>> fdf = skmob. FlowDataFrame. from_file('NY_commuting_flows_2011. csv', \. . . tessellation = tessellation, tile_id = 'tile_id')

The spatial tessellation and 'FlowDataFrame' have the structure shown below. 

>>> print(tessellation. head())
section_name: >>> import skmob >>> import geopandas as gpd

section_content: As any analytical process, mobility data analysis requires data cleaning and pre-processing steps (Zheng 2015). The preprocessing module allows the user to perform noise filtering, stop detection, and trajectory compression. Note that if a 'TrajDataFrame' contains multiple trajectories from multiple objects, the pre-processing methods automatically apply to the single trajectory and, when necessary, to the single object. Table 1 lists the available methods for trajectory pre-processing.
section_name: Trajectory pre-processing

section_content: Trajectory data are generally noisy, usually because of recording errors like poor signal reception. When the error associated with the coordinates of points is large, the best solution is to filter out these points. In scikit-mobility, the method filter filters out a point if the speed from the previous point is higher than the parameter max_speed, which is by default set to 500km/h. To use the filter function, we first import the preprocessing module:

>>> import skmob >>> from skmob import preprocessing Then, we apply the filtering, setting max speed as 10 km/h, on a 'TrajDataFrame' containing GPS trajectories: For each object, filter out the noise or outlier points (Zheng 2015). 

'TrajDataFrame' object, max_speed_kmh = 500. 0, include_loops = False, speed_kmh = 5. 0, max_loop = 6, ratio_max = 0. 25 As we can see, 108,874 points out of 217,653 are filtered out. The intensity of the filter is controlled by the max_speed parameter. The lower the value, the more intense the filter is.
section_name: Noise filtering

section_content: Some points in a trajectory can represent points-of-interest (POIs) such as schools, restaurants, and bars or represent individual-specific places such as home and work locations. These points are usually called stay locations or stops, and they can be detected in different ways. A common approach is to apply spatial clustering algorithms to cluster trajectory points by looking at their spatial proximity (Hariharan and Toyama 2004). In scikitmobility, the stay_locations function, contained in the detection module, finds the stay points visited by an object. For instance, to identify the stay locations where the object spent at least minutes_for_a_stop minutes within a distance spatial_radius_km × stop_radius_factor, from a given point, we can use the following code:
section_name: Stop detection

section_content: Trajectory compression aims to reduce the number of trajectory points while preserving the trajectory structure. This step is generally applied right after the stop detection step, significantly reducing the number of trajectory points. In scikit-mobility, we can use the method compression. compress under the preprocessing module. For instance, to merge all the points closer than 0. 2km from each other, we can use the following code: Once compressed, the trajectory presents a smaller number of points, allowing easy plotting using the data visualization functionalities of scikit-mobility described in Section 4.
section_name: Trajectory compression

section_content: One of the use cases for scikit-mobility is the exploratory data analysis of mobility datasets, which includes the visualization of trajectories and flows. To this end, both 'TrajDataFrame' and 'FlowDataFrame' have methods that allow the user to produce interactive visualizations generated using the library folium (Fernandes et al. 2019). The choice of folium is motivated by the fact that, given the complexity of mobility data, the user may need to zoom in/out and interact with the components of trajectories, flows, and tessellations. This interaction would be impossible with static plotting libraries, such as matplotlib (Hunter 2007 ). The user can save an interactive plot in an HTML file or take a screenshot to save it as a PNG file. Table 2 lists the plotting functions available in the library.
section_name: Plotting

section_content: A 'TrajDataFrame' object has three main plotting methods: plot_trajectory plots a line connecting the trajectory points on a map; plot_stops plots the stay locations (stops) on a map; and plot_diary plots the sequence of visited locations over time.
section_name: Visualizing trajectories

section_content: The plot_trajectory method for 'TrajDataFrame' objects plots the time-ordered trajectory points connected by straight lines on a map. If the column uid is present and contains more than one object, the trajectory points are first grouped by uid and then sorted by datetime. 

Large 'TrajDataFrame's with many points can be computationally intensive to visualize. Two arguments can be used to reduce the amount of data to plot: max_users (type: int, default: 10) limits the number of objects whose trajectories should be plotted, while max_points (type: int, default: 1000) limits the number of trajectory points per object to plot, i. e., if necessary, an object's trajectory will be down-sampled and at most max_points points will be plotted. 

The plot style can be customized via arguments to specify the color, weight, and opacity of the trajectory lines and the type of map tiles to use. The user can also plot markers denoting the trajectory's starting and ending points. 

The plot_trajectory method, as well as all the other plotting methods, return an object of class 'folium. Map', which can be used by other folium and scikit-mobility functions to visualize additional data on the same map. A 'folium. Map' object can be passed to a plotting Table 2 : Plotting methods currently implemented in scikit-mobility. For each method, we provide a description, its arguments and their default values (if any). 

method via the argument map_f (default: None, i. e., the mobility data are plotted on a new map). 

The following code generates a plot by the plot_trajectory method (see Figure 2 ):

>>> import skmob >>> tdf = skmob. TrajDataFrame. from_file('geolife_sample. txt. gz') >>> map_f = tdf. plot_trajectory(max_users = 1, hex_color = '#000000') >>> map_f Note that if trajectories represent abstract mobility, such as movements extracted from social media posts or mobile phone calls, straight lines may appear that do not consider walls, buildings, and similar structures on the road network. 

By default, a 'TrajDataFrame' object represents the full mobility of a set of individuals, i. e., covering the entire observation period (e. g., one month). The user can split the trajectory of an individual using pre-processing functions, such as the detection. stay_locations function (Section 3), and then split the whole trajectory into sub-trajectories, adding a proper column to identify them (i. e., the tid column). At this point, the user may visualize the portion of the 'TrajDataFrame' selecting for values of the created column.
section_name: Plot trajectories

section_content: The plot_stops method for 'TrajDataFrame' objects plots the stay locations as markers on a map. This method requires a 'TrajDataFrame' object with a constants. LEAVING_DATETIME column, which is created by the scikit-mobility functions to detect stay locations (see Section 3). The argument max_users (type: int, default: 10) limits the number of objects whose stay locations should be plotted. The plot style can be customized via arguments to specify the color, radius, opacity of the markers, and the type of map tiles to use. The argument popup (default: False) allows enhancing the plot's interactivity displaying popup windows that appear when the user clicks on a marker. A stay location's popup window includes coordinates, uid, arrival, and leaving times. 

We show below the code for generating a plot with the plot_stops method (see Figure 3 ). Note that if the cluster column is present in the 'TrajDataFrame' object, as it happens when the cluster method is applied, the stay locations are automatically colored according to the value of that column (to identify different clusters of stay locations). 

>>> from skmob. preprocessing import detection, clustering >>> tdf = skmob. TrajDataFrame. from_file('geolife_sample. txt. gz')
section_name: Plot stops

section_content: The plot_diary method for 'TrajDataFrame' objects plots the time series of the locations visited by an object. If the column uid is present, one object identifier must be specified via the argument user. This method requires a 'TrajDataFrame' with the column constants. CLUSTER, which is created by the scikit-mobility functions to cluster stay locations (see Section 3). 

The plot displays time on the x axis and shows a series of rectangles of different colors representing the object's visits to the various stay locations. The length of a rectangle denotes the visit's duration: the left edge marks the arrival time, the right edge marks the leaving time. The color of a rectangle denotes the stop's cluster: visits to stay locations that belong to the same cluster have the same color (the color code is consistent with the one used by the method plot_stops). A white rectangle indicates that the object is moving. 

We show below the code to generate a plot by the plot_diary method (see Figure 4 ):

>>> cstdf. plot_diary(user = 1, legend = True)

The user can compare multiple moving objects by plotting their diaries next to each other (see Figure 5 ):

>>> ax = cstdf. plot_diary(1) >>> ax = cstdf. plot_diary(5, legend = True)
section_name: Plot diary

section_content: A 'FlowDataFrame' object has two main plotting methods: plot_tessellation plots the tessellation's tiles on a geographic map and plot_flows plots, on a geographic map, the lines connecting the centroids of the tessellation's tiles between which flows are present.
section_name: Visualizing flows

section_content: The plot_tessellation method for 'FlowDataFrame' objects plots the 'GeoDataFrame' associated with a 'FlowDataFrame' on a geographic map. Large tessellations with many tiles can be computationally intensive to visualize. The argument maxitems can be used to limit the number of tiles to plot (default: -1, which means that all tiles are displayed). 

The plot style can be customized via arguments to specify the color, opacity of the tiles, and the type of map tiles to use. The argument popup_features (type: list, default:

[constants. TILE_ID]) allows enhancing the plot's interactivity displaying popup windows that appear when the user clicks on a tile and includes information contained in the columns of the tessellation's 'GeoDataFrame' specified in the argument's list. 

We show below the code to generate a plot by the plot_tessellation method (see Figure 6 ):

>>> import geopandas as gpd >>> from skmob import FlowDataFrame >>> tessellation = gpd. GeoDataFrame. from_file('NY_counties_2011. geojson') >>> fdf = FlowDataFrame. from_file('NY_commuting_flows_2011. csv', \. . . tessellation = tessellation, tile_id = 'tile_id')
section_name: Plot tessellation

section_content: The plot_flows method for 'FlowDataFrame' objects plots the flows on a geographic map as lines between the centroids of the tiles in the tessellation of the 'FlowDataFrame' object. 

Large 'FlowDataFrame's with many origin-destination pairs can be computationally intensive to visualize. The argument min_flow (type: integer, default: 0) can be used to specify that only flows larger than min_flow should be displayed. The thickness of each line is a function of the flow and can be specified via the arguments flow_weight, flow_exp and style_function. The plot style can be further customized via arguments to specify the color, opacity of the flow lines, and the type of map tiles to use. The arguments flow_popup and tile_popup allow enhancing the plot's interactivity displaying popup windows that appear when the user clicks on a flow line or a circle in an origin location, respectively, and include information on the flow or the flows from a location. 

We show below the code to generate a plot with the plot_flows method (see Figure 7 ):
section_name: Plot flows

section_content: The user can also visualize the tessellation and the flows in the same plot (see Figure 8 ):

>>> map_f = fdf. plot_tessellation(popup_features = ['tile_id', \. . . 'population'], style_func_args = {'color':'red', 'fillColor': 'red'}) >>> fdf. plot_flows(map_f = map_f, min_flow = 50)
section_name: >>> fdf.plot_flows(min_flow = 50)

section_content: In the last decade, several measures have been proposed to capture human mobility patterns, both at the individual and collective levels (Barbosa et al. 2018; Luca et al. 2021). Individual measures summarize the mobility patterns of a single moving object, while collective measures summarize the mobility patterns of a population as a whole. For instance, the so-called radius of gyration (González et al. 2008 Table 3 : Individual measures currently implemented in scikit-mobility (part 1). For each method, we provide a description, its arguments and their default values (if any). 

scikit-mobility provides a wide set of mobility measures, each implemented as a function that takes as input a 'TrajDataFrame' and outputs a pandas 'DataFrame'. In the modules skmob. measure. individual and skmob. measures. collective, individual and collective measures are implemented, respectively. Tables 3 and 4 list the available individual measures; Table 5 lists the available collective measures. 

The code below computes the distances traveled by the objects and their radius of gyration. Then, we invoke the two functions on the 'TrajDataFrame' object, respectively.
section_name: Mobility measures

section_content: The functions' output is a pandas 'DataFrame' with two columns: uid contains the moving object's identifier; the second column, the name of which corresponds to the name of the invoked function, contains the moving object's computed measure. For example, in the 'DataFrame' jl_df, the column jump_length contains a list of all distances traveled by that object. 

>>> print(jl_df. head()) Note that, if the optional column uid is not present in the input 'TrajDataFrame', a simple Python structure is outputted instead of the pandas 'DataFrame' (e. g., a list for function jump_lengths and a float for function radius_of_gyration). 

Collective measures are used similarly. The code below computes a collective measure -the number of visits per location (by any object). First, we import the function.
section_name: >>> jl_df = jump_lengths(tdf) >>> rg_df = radius_of_gyration(tdf)

section_content: Then, we invoke the function on the 'TrajDataFrame'.
section_name: >>> from skmob.measures.collective import visits_per_location

section_content: As for the individual measures, the output of the functions is a pandas 'DataFrame'. The format of this 'DataFrame' depends on the measures. For example, in the 'DataFrame' vpl_df there are three columns: lat and lng indicate the coordinates of a location, and n_visits indicates the number of visits to that location in the 'TrajDataFrame'. 

>>> print(vpl_df. head())
section_name: >>> vpl_df = visits_per_location(tdf)

section_content: Generative mobility algorithms aim to create a population of agents whose mobility patterns are statistically indistinguishable from those of real individuals (Luca et al. 2021). A generative algorithm typically generates a synthetic trajectory corresponding to a single moving object, generally assuming that an object is independent of the others. 

scikit-mobility implements the most common mechanistic individual generative algorithms, such as the exploration and preferential return model or EPR (Song et al. 2010a ) and its variants (Pappalardo et al. 2016a), and DITRAS (Diary-based trajectory simulator modeling framework) (Pappalardo and Simini 2018). Generative individual mobility models based on artificial intelligence (Luca et al. 2021) are not included in the library's current version and will be included in later versions. Table 6 lists the individual generative models currently implemented in scikit-mobility. 

Each generative algorithm is a Python class with a generate method to start the generation of synthetic trajectories. Below we show the code to generate a 'TrajDataFrame' describing the synthetic trajectory of 1000 agents that move on a spatial tessellation for a period specified in the input. First, we import the generative algorithm class ('DensityEPR') from the library. 

>>> import skmob >>> import pandas as pd >>> import geopandas as gpd >>> from skmob. models. epr import DensityEPR

Then, we load the spatial tessellation on which the agents have to move from a file, and we specify the start and end times of the simulation as pandas 'datetime' objects. 

>>> tessellation = gpd. GeoDataFrame. from_file('NY_counties_2011. geojson') >>> start_time = pd. to_datetime('2019/01/01 08:00:00') >>> end_time = pd. to_datetime('2019/01/14 08:00:00')

Finally, we instantiate the 'DensityEPR' model and start the simulation through the generate method, which takes as input the start and end times, the spatial tessellation, the number of agents, and other model-specific parameters. The output of the simulation is a 'TrajDataFrame' object containing the trajectory of the 1000 agents. 

>>> The argument random_state in method generate allows the user to set a seed that guarantees the results of the generation process are reproducible. Since random_state is None by default, if the user does not specify a value for random_state, every time they run the generate method the synthetic trajectories would be different each time.
section_name: Individual generative algorithms

section_content: Collective generative algorithms estimate spatial flows between a set of discrete locations. 

Examples of spatial flows estimated with collective generative algorithms include commuting trips between neighborhoods, migration flows between municipalities, freight shipments between states, and phone calls between regions (Barbosa et al. 2018). 

In scikit-mobility, a collective generative algorithm takes as input a spatial tessellation, which should be a 'GeoDataFrame' with two columns geometry and relevance. These columns are necessary to compute the two variables used by collective algorithms: the distance between tiles and the importance (aka "attractiveness") of each tile. A collective algorithm produces a 'FlowDataFrame' that contains the generated flows and the spatial tessellation of which is the one specified as the algorithm's input. 

scikit-mobility implements the most common collective generative algorithms: the gravity model (Zipf 1946; Wilson 1971 ) and the radiation model (Simini et al. 2012). 

We illustrate how to work with generative algorithms in scikit-mobility with an example based on the gravity model. The gravity model, inspired by an analogy with Newton's law of gravitation (Zipf 1946), is based on the assumption that the number of travelers between two locations (flow) increases with the locations' populations while the number decreases with the distance between them. Formally, the gravity model can be formalized as T i,j = Km i m j f (r i,j ) with T i,j denoting the flow between locations i and j, K representing a constant, m i and m j the populations of locations i and j respectively and f (r i,j ) is a deterrence function that decreases as the distance r i,j between i and j increases and it is usually a power law or an exponential. For more details on the gravity models, see Barbosa et al. (2018).
section_name: Collective generative algorithms

section_content: Density exploration and preferential return model (Pappalardo et al. 2016a; Barbosa et al. 2018). Radiation model for human mobility (Simini et al. 2012).
section_name: Generative model Description Arguments DensityEPR

section_content: Table 6 : Generative models currently implemented in scikit-mobility. For each method, we provide a description, its arguments and their default values (if any). 

The class 'Gravity', implementing the gravity model, has two main methods: fit, which calibrates the model's parameters using a 'FlowDataFrame'; and generate, which generates the flows on a given tessellation. The following code shows how to estimate the commuting flows between the counties in New York. First, we load the tessellation from a file: Gravity(name="Gravity model", deterrence_func_type="power_law", deterrence_func_args=[-2. 0], origin_exp=1. 0, destination_exp=1. 0, gravity_type="singly constrained")

Then we call the method fit to fit the parameters from the previously loaded 'FlowDataFrame':

>>> gravity. fit(fdf, relevance_column = 'population') >>> print(gravity)

Gravity(name="Gravity model", deterrence_func_type="power_law", deterrence_func_args=[-1. 99471520], origin_exp=1. 0, destination_exp=0. 64717595, gravity_type="singly constrained")

Finally, we use the fitted model to generate the flows on the same tessellation. Setting the argument out_format = 'probabilities' we specify that in the column flow of the returned 'FlowDataFrame' we want the probability to observe a unit flow (trip) between two tiles. 

>>>
section_name: name = 'Radiation model'

section_content: Mobility data are sensitive since individuals' movements can reveal confidential personal information or allow the re-identification of individuals in a database, creating serious privacy risks (de Montjoye et al. 2013 (de Montjoye et al., 2018)). The General Data Protection Regulation (GDPR) explicitly imposes an assessment of the impact of data protection for the riskiest data analyses. For this reason, scikit-mobility provides scientists in the field of mobility analysis with tools to estimate the privacy risk associated with the analysis of a given dataset. In the literature, privacy risk assessment relies on the concept of re-identification of a moving object in a database through an attack by a malicious adversary (Pellungrini et al. 2017 (Pellungrini et al., 2022)). 

A common framework for privacy risk assessment (Pratesi, Monreale, Trasarti, Giannotti, Pedreschi, and Yanagihara 2018) assumes that during the attack, a malicious adversary acquires, in some way, access to an anonymized mobility dataset, i. e., a mobility dataset in which the moving object associated with a trajectory is not known. Moreover, it assumes that the malicious adversary acquires information about (a portion of) a trajectory of an individual represented in the dataset. Based on this information, the risk of re-identification of that individual is computed estimating how unique that individual's mobility data are with respect to the mobility data of the other individuals represented in the dataset (Pellungrini et al. 2017). 

scikit-mobility provides several attack models, each implemented as a Python class. In Table 7, we list the privacy attacks currently available in the library. For example, in a location attack model, implemented in the 'LocationAttack' class, the malicious adversary knows a certain number of locations visited by an individual, but they do not know the temporal order of the visits (Pellungrini et al. 2017). The following code instantiates a 'LocationAttack' object:

>>> import skmob >>> from skmob. privacy import attacks >>> at = attacks. LocationAttack(knowledge_length = 2)

The argument knowledge_length specifies how many locations the malicious adversary knows of each object's movement. The re-identification risk is computed based on the worst possible combination of knowledge_length locations out of all possible combinations of locations. 

To assess the re-identification risk associated with a 'TrajDataFrame', we specify it as input to the assess_risk method, which returns a pandas 'DataFrame' that contains the uid of each object in the 'TrajDataFrame' and the associated re-identification risk as the column risk (type: float, range: [0, 1] where 0 indicates minimum risk and 1 maximum risk). 

>>> tdf = skmob. knowledge_length tolerance = 0. 0
section_name: Privacy risk assessment

section_content: The two most visited locations in the trajectory of an object. 

Table 7 : List of privacy attacks currently implemented in scikit-mobility. For each method, we provide a description, its arguments and their default values (if any). 

Since risk assessment may be time-consuming for more massive datasets, scikit-mobility provides the option to focus only on a subset of the objects with the argument targets. For example, in the following code, we compute the re-identification risk for the objects with uid 1 and 2 only:

During the computation, not necessarily all combinations of locations are evaluated when assessing the re-identification risk of a moving object: when the combination with maximum re-identification risk (e. g., risk 1) is found for a moving object, all the other combinations are not computed, so as to make the computation faster. The result is a pandas 'DataFrame' that contains a reference number of each combination under the attribute instance and, for each instance, the risk and each of the locations comprising that instance indicated by the attribute instance_elem.
section_name: HomeWorkAttack

section_content: This section briefly describes some of the existing libraries and tools that provide functionalities for movement data management. Overall, none of the other packages are tailored explicitly for human mobility, and none of them includes functions for privacy risk assessment. Table 8 gives a summary of the packages and their functionalities.
section_name: Existing tools

section_content: A review of state of the art (Joo, Boone, Clay, Patrick, Clusella-Trullas, and
section_name: R

section_content: The packages under the adehabitat family (Calenge 2006 ) cover methods and functions to manage animal movement and habitat selection. The original package has been split into smaller packages: adehabitatHR deals with home-range analysis, adehabitatHM deals with habitat selection analysis, adehabitatLT deals with animal trajectory analysis, and adehabi-tatMA deals with maps. Many of the functions presented in these packages are specific to animal movement. adehabitatLT (Calenge 2020) is the most similar library to scikit-mobility. 

While adehabitatLT can handle both regular and irregular trajectories, its design has been optimized for handling regular ones, as specified in the package's documentation. Moreover, the main trajectory class of adehabitatLT has been designed to handle bursts of movements to better model animals' behavior, which tends to be one of alternating rests and movements. scikit-mobility, while focusing on human trajectories, is agnostic to the sampling of the points in the trajectories or their periodicity.
section_name: adehabitat

section_content: TrajDataMining (Monteiro 2018) provides methods for trajectory data preparation, such as filtering, compression, and clustering. It also provides some pattern recognition tools to extract recurrent movement behaviors from the trajectories. However, it does not implement generative models nor advanced plotting functionalities.
section_name: TrajDataMining

section_content: As for Python, some libraries have been proposed to manage and manipulate mobility data. 

In this section, we revise the most similar libraries in their purpose to what we propose in this paper, highlighting the differences between them and scikit-mobility. 

bandicoot bandicoot (de Montjoye, Rocher, and Pentland 2016) is a Python library for the analysis of mobile phone metadata that provides the users with functions to compute features related to mobile phone usage. These features are grouped into three categories: (i) individual features describe an individual's mobile phone usage and interactions with their contacts; (ii) spatial features describe an individual's mobility patterns; (iii) social network features describe an individual's social network. The principal limit of bandicoot is that it is specifically designed for managing a specific data type, namely mobile phone data. This design choice makes bandicoot unsuitable for analyzing movements that cannot be captured by mobile phone data, such as car travels, movements of animals, or boat trips. In contrast, scikit-mobility gives the user the possibility to deal with a diverse set of mobility data sources (e. g., GPS data, social media data, mobile phone data) and provides to the use a complete set of standard mobility measures ready to be used. Moreover, scikit-mobility provides a module dedicated to the privacy risk assessment of any mobility data source, a module to create interactive geographic plots, and a module dedicated to generative models of individual and collective mobility, all features that are absent in bandicoot. 

movingpandas movingpandas (Graser 2019 ) is an extension of Python library pandas (McKinney 2010) and its spatial extension geopandas (Jordahl et al. 2019) to add functionality for dealing with trajectory data. In movingpandas, a trajectory is a time-ordered series of geometries. These geometries and associated attributes are stored in a 'GeoDataFrame', a data structure provided by the geopandas library. Since movingpandas is based on geopandas, it allows the user to perform several operations on trajectories, such as clipping them with polygons and computing intersections with polygons. However, since it focuses on the concept of trajectory, movingpandas does not implement any features specific to mobility analysis, such as statistical laws of mobility, generative models, standard pre-processing functions, and methods to assess privacy risk in mobility data.
section_name: Python

section_content: In this paper, we presented scikit-mobility, a Python library for the analysis, generation, and privacy risk assessment of mobility data. scikit-mobility allows the user to manage two types of mobility data -trajectories and flows -and it provides several modules, each dedicated to a specific aspect of mobility data analysis. 

scikit-mobility has the advantage of providing, in a single environment, functions to deal with various aspects of mobility analysis, such as data pre-processing, cleaning, and visualization, computation of mobility metrics, generation of synthetic trajectories and flows, and the assessment of privacy risk. The current version of the library has some limitations, too. For example, since pandas 'DataFrame's must be fully loaded in memory, the size of the mobility dataset that can be analyzed is limited by the capacity of the memory of the user's machine. Moreover, although the library could be easily adapted to work with any geographic reference system, it is currently designed to work with the latitude and longitude reference system only. 

We imagine two future directions for the development of scikit-mobility. On one side, we plan to add more modules to cover a more extensive range of aspects regarding mobility data analysis. For example, we plan to include algorithms for predicting the next location visited by an individual (Luca et al. 2021; Wu et al. 2018 ). We will also consider including a module for performing map matching, i. e., assigning the points of a trajectory to the street network, and a module to compute the similarity between trajectories. 

On the other hand, we plan to improve the library from a computational point of view. 

Although in its current version scikit-mobility is easy to use and efficient on mobility datasets in the order of gigabytes, it is not scalable to massive mobility data in the order of terabytes or more. Since new Python libraries similar to pandas but more computationally efficient are developed every year such as dask (Rocklin 2015), we plan to re-implement crucial functions in scikit-mobility so that they can exploit the computational efficiency of these libraries. This aspect will become crucial when the scientific community largely adopts the library.
section_name: Conclusion and future developments

section_name: Acknowledgments

section_content: Gianni Barlacchi has done the work prior to joining Amazon. Luca Pappalardo and Roberto Pellungrini have been supported by EU project SoBigData++ RI grant # 871042 and by EU project Track&Know H2020 grant # 780754. Filippo Simini has been partially supported by EPSRC First Grant EP/P012906/1. We thank Anita Graser for the useful discussions, and Michele Ferretti, Giuliano Cornacchia, and Massimiliano Luca for their support in the development of the library. L. P. developed modules, performed experiments, developed code examples, made the documentation, and structured the paper. F. S. performed experiments, tested the code and developed modules. G. B. performed the code, the system design and developed modules. R. P. performed experiments and developed modules. All the authors contributed to writing of the manuscript.
section_name: Acknowledgments