Contact Tracing Using Less Than 30 Lines of Python Code | by Sara A. Metwalli | Towards Data Science

Contact tracing is the name of the process used to identify those who come into contact with people who have tested positive for contagious diseases — such as measles, HIV, and COVID-19. During a pandemic, performing contact tracing correctly can help reduce the number of people to get infected or speed up the process of treating infected people. Doing so can help save many lives.
Technology can help automate the process of contact tracing, producing more efficient and accurate results than if the procedure was performed manually. One technology that can help this process is Machine Learning. More precisely, clustering. Clustering is a subclass of Machine Learning algorithms used to divide data that share some characteristics in different clusters based on these characteristics.
towardsdatascience.com
There are various types of clustering algorithms, such as K-means, Mean-Shift, Spectral Clustering, BIRCH, DBSCAN, and so much more. These different algorithms can be divided into three categories:
Density-based Clustering: Clusters are formed based on the density of the region — examples of this type: DBSCAN (Density-Based Spatial Clustering of Applications with Noise) and OPTICS (Ordering Points to Identify Clustering Structure).Hierarchal-based Clustering: Clusters are formed using a tree-type structure. Some clusters are predefined and then used to create new clusters — examples of this type: CURE (Clustering Using Representatives), BIRCH (Balanced Iterative Reducing Clustering, and using Hierarchies).Partitioning-based Clustering: Clusters are formed by partitioning the input data into k clusters — examples of this type: K-means, CLARANS (Clustering Large Applications based upon Randomized Search).
Density-based Clustering: Clusters are formed based on the density of the region — examples of this type: DBSCAN (Density-Based Spatial Clustering of Applications with Noise) and OPTICS (Ordering Points to Identify Clustering Structure).
Hierarchal-based Clustering: Clusters are formed using a tree-type structure. Some clusters are predefined and then used to create new clusters — examples of this type: CURE (Clustering Using Representatives), BIRCH (Balanced Iterative Reducing Clustering, and using Hierarchies).
Partitioning-based Clustering: Clusters are formed by partitioning the input data into k clusters — examples of this type: K-means, CLARANS (Clustering Large Applications based upon Randomized Search).
For contact tracing, we need to use a density-based clustering algorithm. The reason is, diseases are transferred when an infected person comes in contact with others. So, more crowded — dense — areas will have more cases than less crowded ones.
To trace the movement of infected people, scientists often use GPS datasets that contain information about the time and location of a person in any given timeframe. The location data is often represented as longitude and latitude coordinates.
In order to build a contact tracing algorithm, we need to do three steps:
Get location data of different users within a specific time and place.Apply a density-based clustering algorithm on the data.Use the clusters to predict infected people.
Get location data of different users within a specific time and place.
Apply a density-based clustering algorithm on the data.
Use the clusters to predict infected people.
So, let’s get started...
We will write a Python code that uses the DBSCAN clustering algorithm to predict who might get infected because they came in contact with an infected person.
Unfortunately, we can’t obtain real-life data from GPS locations. So, we will build a mock dataset to apply our algorithm on. For this article, I used a Mock Data Generator to generate a JSON dataset containing 100 entries of the locations of 10 users. If you want to try another dataset, make sure the following conditions apply:
There is more than one entry for each user.The users are close in the distance to each other and within a timeframe (for example, a day or a specific number of hours).
There is more than one entry for each user.
The users are close in the distance to each other and within a timeframe (for example, a day or a specific number of hours).
First, let’s import all the libraries that we will use. We will need Pandas and Sklearn to process the data and Pygal to display it.
import pandas as pdimport pygalfrom sklearn.cluster import DBSCAN
Note: in case you don’t have one of these libraries, you can use pip to install each of them from the command line. Moreover, if you're using Jupyter Notebook, you need to add this cell to display Pygal plots:
from IPython.display import display, HTMLbase_html = """<!DOCTYPE html><html>  <head>  <script type="text/javascript" src="http://kozea.github.com/pygal.js/javascripts/svg.jquery.js"></script>  <script type="text/javascript" src="https://kozea.github.io/pygal.js/2.0.x/pygal-tooltips.min.js""></script>  </head>  <body>    <figure>      {rendered_chart}    </figure>  </body></html>"""
Now, we can load our dataset and show the first 5 rows to understand how it is built.
dataFrame = pd.read_json(r"Location_Of_Your_Dataset\MOCK_DATA.json")dataFrame.head()
To understand the data more, we will plot it using the Pygal scatter plot. We can extract the different locations of each user and store that in a dictionary and then use this dictionary to plot the data.
disp_dict = {}for index, row in dataFram.iterrows():    if row['User'] not in disp_dict.keys():        disp_dict[row['User']] = [(row['Latitude'], row['Longitude'])]    else:        disp_dict[row['User']].append((row['Latitude'], row['Longitude']))xy_chart = pygal.XY(stroke=False)[xy_chart.add(k,v) for k,v in sorted(disp_dict.items())]display(HTML(base_html.format(rendered_chart=xy_chart.render(is_unicode=True))))
Running this code, we get...
Awesome. Now that we have our dataset, we can apply the clustering algorithm on it and then use that to predict potential infections. To accomplish that, we will use the DBSCAN algorithm.
The DBSCAN algorithm views clusters as areas of high density separated by regions of low density. Because of this, clusters found by DBSCAN can be of any shape, as opposed to k-means, which assumes that all clusters are convex shaped.
Sklearn had a predefined DBSCAN algorithm; all you need to do to use it is know three parameters:
eps: This factor indicates the distance between the different points in the same cluster. In our case, we will use the recommended distance by the CDC, which is 6 feet (or 0.0018288 kilometers).min_samples: The minimum number of samples in the cluster. In the case of large, noisy datasets, increase this number.metric: This sets the distance metric between the data points. Sklearn has many distance metrics, such as euclidean, manhattan, and Minkowski. For our case, however, we need a distance measure that describes distance on a cipher (The Earth). The metric for that is called haversine.
eps: This factor indicates the distance between the different points in the same cluster. In our case, we will use the recommended distance by the CDC, which is 6 feet (or 0.0018288 kilometers).
min_samples: The minimum number of samples in the cluster. In the case of large, noisy datasets, increase this number.
metric: This sets the distance metric between the data points. Sklearn has many distance metrics, such as euclidean, manhattan, and Minkowski. For our case, however, we need a distance measure that describes distance on a cipher (The Earth). The metric for that is called haversine.
We can now apply our model to the dataset.
safe_distance = 0.0018288 # a radial distance of 6 feet in kilometersmodel = DBSCAN(eps=safe_distance, min_samples=2, metric='haversine').fit(dataFram[['Latitude', 'Longitude']])core_samples_mask = np.zeros_like(model.labels_, dtype=bool)core_samples_mask[model.core_sample_indices_] = Truelabels = model.labels_dataFram['Cluster'] = model.labels_.tolist()
Applying the model with these parameters leads to 18 clusters. We can display these clusters using this piece of code...
disp_dict_clust = {}for index, row in dataFram.iterrows():    if row['Cluster'] not in disp_dict_clust.keys():        disp_dict_clust[row['Cluster']] = [(row['Latitude'], row['Longitude'])]    else:        disp_dict_clust[row['Cluster']].append((row['Latitude'], row['Longitude']))print(len(disp_dict_clust.keys()))from pygal.style import LightenStyledark_lighten_style = LightenStyle('#F35548')xy_chart = pygal.XY(stroke=False, style=dark_lighten_style)[xy_chart.add(str(k),v) for k,v in disp_dict_clust.items()]display(HTML(base_html.format(rendered_chart=xy_chart.render(is_unicode=True))))
After the algorithm is done, if there were any data points without a cluster, they will be clustered as noise or cluster -1. Often, you will find that all users in this data set will be part of the -1 cluster in addition to other clusters.
If we have the name of an infected person, we can use that to get all clusters this person is a part of. From there, we can see other people in these clusters. These people will have a higher probability of getting infected than those who are not.
Obtain all clusters a specific person belongs to
Given a name inputName for example, William, we want all clusters that William is a part of.
inputName = "William"inputNameClusters = set()    for i in range(len(dataFrame)):        if dataFrame['User'][i] == inputName:            inputNameClusters.add(dataFrame['Cluster'][i])
After executing this code, the inputNameCluster will become {2, 4, 5, -1}.
Get people within a specific cluster.
Now, we want other people who belong to this specific set of clusters.
infected = set()    for cluster in inputNameClusters:        if cluster != -1:            namesInCluster = dataFrame.loc[dataFrame['Cluster'] == cluster, 'User']            for i in range(len(namesInCluster)):                name = namesInCluster.iloc[i]                if name != inputName:                    infected.add(name)
In both these sections, I am using sets to avoid having extra if-else statements when the inputName is in each cluster's names list.
Voilà, the code will return {‘Doreen’, ‘James’, ‘John’}, which means, those three people are potentially infected because they came into contact with William at some point in time and place.
I have put the core code into a function that takes a dataframe and a user’s name and perform contact tracing of this user and finally print potentially infected people. The function will first check if the inputName is valid; if not, it will raise an assertion error. On top of that, it's less than 30 lines of code!!
The full code for the contact tracing function:
Contact Tracing is one of the ways we can use technology to save people’s lives and provide them treatment as soon as possible. Government and medical personals often have access to GPS locations of some patients. The process we walked through in this article, is fundamentally the same as the one they follow to obtain potential infections. Luckily, thanks to libraries like Sklearn, we can use predefined models on our datasets and obtain results with few lines of code.
[1] “A Density-Based Algorithm for Discovering Clusters in Large Spatial Databases with Noise” Ester, M., H. P. Kriegel, J. Sander, and X. Xu, In Proceedings of the 2nd International Conference on Knowledge Discovery and Data Mining, Portland, OR, AAAI Press, pp. 226–231. 1996
[2] “DBSCAN revisited, revisited: why and how you should (still) use DBSCAN. Schubert, E., Sander, J., Ester, M., Kriegel, H. P., & Xu, X. (2017). In ACM Transactions on Database Systems (TODS), 42(3), 19.
[3] Sklearn Documentation.