diff --git a/README.rst b/README.rst
index c03886b..a87601f 100644
--- a/README.rst
+++ b/README.rst
@@ -35,15 +35,15 @@ Sensitivity and Epsilon Analysis
* Sensitivity : In a single time stamp, ``1`` merchant can come only once in a particular zip code but can appear in upto ``3`` zip codes. So, if we wanted to release measures about a single zip code sensitivity would be ``1`` but since we want to release data for all zip codes, the sensitivity used for each zip code is ``3``.
* Scaling with Time: For multiple time stamps, sensitivity is ``3 * no_of_time_stamps``.
* Epsilon Budget: The epsilon spent for each query is ``∈``.
-* Scale Calculation: ``Scale = (sqrt(3) * no_of_time_stamps) / ∈``.
+* Scale Calculation: ``Scale = (3 * no_of_time_stamps* upper_bound) / ∈``.
-Mobility Detection (Airline Merch Category)
--------------------------------------------
+Mobility Detection
+------------------
Description
-This analysis tracks mobility by monitoring differential private time series release of financial transactions in the "Airlines" category, which reflects the transportation sector.
+This analysis tracks mobility by monitoring differential private time series release of financial transactions in the ``retail_and_recreation``, ``grocery_and_pharmacy`` and ``transit_stations`` super categories which matches with google mobility data for easy validation.
Assumptions
@@ -54,17 +54,18 @@ Assumptions
Algorithm
#. Add City Column: A new ``city`` column is added based on postal codes (``make_preprocess_location``).
+#. Add Super Category Column : A new ``merch_super_category`` column is added for classifying transactions into retail_and_recreation, grocery_and_pharmacy and transit_stations categories (``make_preprocess_merchant_mobility``).
#. Filter for City: Data for the selected city is filtered (``make_filter``).
-#. Filter for Airline Category: Only transactions in the ``Airline`` category are considered (``make_filter``).
+#. Filter for super category: data is filtered for retail_and_recreation, grocery_and_pharmacy and transit_stations categories (``make_filter``).
#. Filter by Time Frame: Data is filtered for the selected time frame (``make_truncate_time``).
#. Transaction Summing & Noise Addition: Sum the number of transactions by postal code for each timestep and add Gaussian noise (``make_private_sum_by``).
Sensitivity and Epsilon Analysis
-* Sensitivity per Merchant: Sensitivity is 3 for each merchant in the ``Airline`` category.
+* Sensitivity per Merchant: Sensitivity is 3 for each merchant.
* Scaling with Time: For multiple timesteps, sensitivity is ``3 * no_of_time_steps``.
* Epsilon Budget: The epsilon spent per timestep is ∈ .
-* Scale Calculation: ``Scale = (3 * no_of_time_steps) / ∈``.
+* Scale Calculation: ``Scale = (3 * no_of_time_steps* upper_bound) / ∈``.
Validation
@@ -100,7 +101,7 @@ Sensitivity and Epsilon Analysis
* Sensitivity per Category : Sensitivity is ``3`` for each category (essential or luxurious goods).
* Scaling with Time : For multiple timesteps, sensitivity is ``3 * no_of_time_steps``.
* Epsilon Budget : The epsilon spent per timestep is ∈.
-* Scale Calculation : ``Scale = (3 * no_of_time_steps) / ∈``.
+* Scale Calculation : ``Scale = (3 * no_of_time_steps* upper_bound) / ∈``.
diff --git a/dist/dp_epidemiology-0.0.8-py3-none-any.whl b/dist/dp_epidemiology-0.0.8-py3-none-any.whl
deleted file mode 100644
index 70fa40f..0000000
Binary files a/dist/dp_epidemiology-0.0.8-py3-none-any.whl and /dev/null differ
diff --git a/dist/dp_epidemiology-0.0.8.tar.gz b/dist/dp_epidemiology-0.0.8.tar.gz
deleted file mode 100644
index 11e66c3..0000000
Binary files a/dist/dp_epidemiology-0.0.8.tar.gz and /dev/null differ
diff --git a/dist/dp_epidemiology-0.0.9-py3-none-any.whl b/dist/dp_epidemiology-0.0.9-py3-none-any.whl
new file mode 100644
index 0000000..f58ac60
Binary files /dev/null and b/dist/dp_epidemiology-0.0.9-py3-none-any.whl differ
diff --git a/dist/dp_epidemiology-0.0.9.tar.gz b/dist/dp_epidemiology-0.0.9.tar.gz
new file mode 100644
index 0000000..03eb266
Binary files /dev/null and b/dist/dp_epidemiology-0.0.9.tar.gz differ
diff --git a/docs/requirements.txt b/docs/requirements.txt
index e69de29..ffba590 100644
Binary files a/docs/requirements.txt and b/docs/requirements.txt differ
diff --git a/docs/usage.rst b/docs/usage.rst
index d6cf342..46413c6 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -61,13 +61,14 @@ For example:
To do mobility inference,
-you can use the ``mobility_analyzer.mobility_analyzer()`` function to generate differential private time series of trnsactional data in the "Airlines" category:
+you can use the ``mobility_analyzer.mobility_analyzer()`` function to generate differential private time series of trnsactional data in the ``retail_and_recreation``, ``grocery_and_pharmacy`` and ``transit_stations`` super categories:
.. autofunction:: mobility_analyzer.mobility_analyzer
The ``df`` parameter take pandas dataframe as input with columns ``[ "ID", "date", "merch_category", "merch_postal_code", "transaction_type", "spendamt", "nb_transactions"]``.
The ``start_date`` and ``end_date`` parameters take the start and end date of the time frame for which the analysis is to be done.
The ``city`` parameter takes the name of the city for which the analysis is to be done.
+The ``category`` parameter takes the value of ``retail_and_recreation``, ``grocery_and_pharmacy`` or ``transit_stations`` for which the analysis is to be done.
The ``epsilon`` parameter takes the value of epsilon for differential privacy.
For example:
@@ -75,7 +76,7 @@ For example:
>>> from DP_epidemiology import mobility_analyzer
>>> from datetime import datetime
>>> df = pd.read_csv('data.csv')
->>> mobility_analyzer.mobility_analyzer(df,datetime(2020, 9, 1),datetime(2021, 3, 31),"Medellin",10)
+>>> mobility_analyzer.mobility_analyzer(df,datetime(2020, 9, 1),datetime(2021, 3, 31),"Medellin","retail_and_recreation",10)
nb_transactions date
0 1258 2020-09-01
1 1328 2020-09-08
diff --git a/pyproject.toml b/pyproject.toml
index d052481..e100188 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "DP_epidemiology"
-version = "0.0.8"
+version = "0.0.9"
dependencies = [
"pandas>=2.1.4",
@@ -16,7 +16,8 @@ dependencies = [
"dash",
"nbformat",
"scipy",
- "matplotlib"
+ "matplotlib",
+ "dtw",
]
authors = [
diff --git a/src/DP_epidemiology/__pycache__/contact_matrix.cpython-310.pyc b/src/DP_epidemiology/__pycache__/contact_matrix.cpython-310.pyc
index 2bf2272..5b18607 100644
Binary files a/src/DP_epidemiology/__pycache__/contact_matrix.cpython-310.pyc and b/src/DP_epidemiology/__pycache__/contact_matrix.cpython-310.pyc differ
diff --git a/src/DP_epidemiology/__pycache__/hotspot_analyzer.cpython-310.pyc b/src/DP_epidemiology/__pycache__/hotspot_analyzer.cpython-310.pyc
index e4e7260..1037b28 100644
Binary files a/src/DP_epidemiology/__pycache__/hotspot_analyzer.cpython-310.pyc and b/src/DP_epidemiology/__pycache__/hotspot_analyzer.cpython-310.pyc differ
diff --git a/src/DP_epidemiology/__pycache__/mobility_analyzer.cpython-310.pyc b/src/DP_epidemiology/__pycache__/mobility_analyzer.cpython-310.pyc
index cab8470..06fa566 100644
Binary files a/src/DP_epidemiology/__pycache__/mobility_analyzer.cpython-310.pyc and b/src/DP_epidemiology/__pycache__/mobility_analyzer.cpython-310.pyc differ
diff --git a/src/DP_epidemiology/__pycache__/pandemic_adherence_analyzer.cpython-310.pyc b/src/DP_epidemiology/__pycache__/pandemic_adherence_analyzer.cpython-310.pyc
index 7a71a7e..354cf23 100644
Binary files a/src/DP_epidemiology/__pycache__/pandemic_adherence_analyzer.cpython-310.pyc and b/src/DP_epidemiology/__pycache__/pandemic_adherence_analyzer.cpython-310.pyc differ
diff --git a/src/DP_epidemiology/__pycache__/utilities.cpython-310.pyc b/src/DP_epidemiology/__pycache__/utilities.cpython-310.pyc
index 651b5c4..182b321 100644
Binary files a/src/DP_epidemiology/__pycache__/utilities.cpython-310.pyc and b/src/DP_epidemiology/__pycache__/utilities.cpython-310.pyc differ
diff --git a/src/DP_epidemiology/hotspot_analyzer.py b/src/DP_epidemiology/hotspot_analyzer.py
index 45a702c..4804d76 100644
--- a/src/DP_epidemiology/hotspot_analyzer.py
+++ b/src/DP_epidemiology/hotspot_analyzer.py
@@ -25,7 +25,7 @@ def hotspot_analyzer(df:pd.DataFrame, start_date:datetime,end_date:datetime,city
nb_timesteps = (end_date - start_date).days // 7
"""scale calculation"""
- scale=(np.sqrt(3.0)*nb_timesteps*upper_bound)/epsilon
+ scale=(3.0*nb_timesteps*upper_bound)/epsilon
new_df=df.copy()
diff --git a/src/DP_epidemiology/mobility_analyzer.py b/src/DP_epidemiology/mobility_analyzer.py
index 7ca1cd2..e59144a 100644
--- a/src/DP_epidemiology/mobility_analyzer.py
+++ b/src/DP_epidemiology/mobility_analyzer.py
@@ -5,6 +5,8 @@
from datetime import datetime
import scipy.stats as stats
import opendp.prelude as dp
+import matplotlib.pyplot as plt
+from dtw import dtw,accelerated_dtw
dp.enable_features("contrib", "floating-point", "honest-but-curious")
@@ -28,7 +30,7 @@ def mobility_analyzer_airline(df:pd.DataFrame,start_date:datetime,end_date:datet
nb_timesteps = (end_date - start_date).days // 7
"""scale calculation"""
- scale=(np.sqrt(3.0)*nb_timesteps*upper_bound)/epsilon
+ scale=(3.0*nb_timesteps*upper_bound)/epsilon
new_df=df.copy()
@@ -60,7 +62,7 @@ def mobility_analyzer(df:pd.DataFrame,start_date:datetime,end_date:datetime,city
nb_timesteps = (end_date - start_date).days // 7
"""scale calculation"""
- scale=(np.sqrt(3.0)*nb_timesteps*upper_bound)/epsilon
+ scale=(3.0*nb_timesteps*upper_bound)/epsilon
new_df=df.copy()
@@ -85,4 +87,15 @@ def mobility_validation_with_google_mobility(df_transactional_data:pd.DataFrame,
# print(df_transactional_mobility.head())
# print(df_google_mobility.head())
r, p = stats.pearsonr(df_transactional_mobility['nb_transactions'][:length], df_google_mobility[category][:length])
- print(f"Scipy computed Pearson r: {r} and p-value: {p}")
\ No newline at end of file
+ print(f"Scipy computed Pearson r: {r} and p-value: {p}")
+
+ d1 = df_transactional_mobility['nb_transactions'][:length].interpolate().values
+ d2 = df_google_mobility[category][:length].interpolate().values
+ d, cost_matrix, acc_cost_matrix, path = accelerated_dtw(d1,d2, dist='euclidean')
+
+ plt.imshow(acc_cost_matrix.T, origin='lower', cmap='gray', interpolation='nearest')
+ plt.plot(path[0], path[1], 'w')
+ plt.xlabel('Subject1')
+ plt.ylabel('Subject2')
+ plt.title(f'DTW Minimum Path with minimum distance: {np.round(d,2)}')
+ plt.show()
\ No newline at end of file
diff --git a/src/DP_epidemiology/pandemic_adherence_analyzer.py b/src/DP_epidemiology/pandemic_adherence_analyzer.py
index 75e966c..64507c0 100644
--- a/src/DP_epidemiology/pandemic_adherence_analyzer.py
+++ b/src/DP_epidemiology/pandemic_adherence_analyzer.py
@@ -26,7 +26,7 @@ def pandemic_adherence_analyzer(df:pd.DataFrame,start_date:datetime,end_date:dat
nb_timesteps = (end_date - start_date).days // 7
"""scale calculation"""
- scale=(np.sqrt(3.0)*nb_timesteps*upper_bound)/epsilon
+ scale=(3.0*nb_timesteps*upper_bound)/epsilon
new_df=df.copy()
diff --git a/src/DP_epidemiology/utilities.py b/src/DP_epidemiology/utilities.py
index 99555c4..3c663c7 100644
--- a/src/DP_epidemiology/utilities.py
+++ b/src/DP_epidemiology/utilities.py
@@ -118,15 +118,15 @@ def function(df):
def make_private_sum_by(column, by, bounds, scale):
"""Create a measurement that computes the grouped bounded sum of `column`"""
- space = dp.vector_domain(dp.atom_domain(T=int)), dp.l2_distance(T=float)
- m_gauss = space >> dp.m.then_gaussian(scale)
+ space = dp.vector_domain(dp.atom_domain(T=int)), dp.l1_distance(T=int)
+ m_lap = space >> dp.m.then_laplace(scale)
t_sum = make_sum_by(column, by, bounds)
def function(df):
exact = t_sum(df)
# print(exact)
noisy_sum = pd.Series(
- np.maximum(m_gauss(exact.to_numpy().flatten()), 0),
+ np.maximum(m_lap(exact.to_numpy().flatten()), 0),
)
# print(noisy_sum)
noisy_sum=noisy_sum.to_frame(name=column)
@@ -138,7 +138,7 @@ def function(df):
input_metric=dp.symmetric_distance(),
output_measure=dp.zero_concentrated_divergence(T=float),
function=function,
- privacy_map=lambda d_in: m_gauss.map(t_sum.map(d_in)),
+ privacy_map=lambda d_in: m_lap.map(t_sum.map(d_in)),
)
def make_filter(column,entry):
diff --git a/src/DP_epidemiology/viz.py b/src/DP_epidemiology/viz.py
index 8369230..4e62d24 100644
--- a/src/DP_epidemiology/viz.py
+++ b/src/DP_epidemiology/viz.py
@@ -96,60 +96,62 @@ def update_graph(start_date, end_date, epsilon, city):
return app
-def create_mobility_dash_app(df:pd.DataFrame):
+def create_mobility_dash_app(df: pd.DataFrame):
cities = {
"Medellin": (6.2476, -75.5658),
"Bogota": (4.7110, -74.0721),
"Brasilia": (-15.7975, -47.8919),
"Santiago": (-33.4489, -70.6693)
- }
+ }
+
app = dash.Dash(__name__)
- category_list = ['grocery_and_pharmacy', 'transit_stations', 'retail_and_recreation',"other"]
+ category_list = ['grocery_and_pharmacy', 'transit_stations', 'retail_and_recreation', "other"]
+
app.layout = html.Div([
- dcc.DatePickerSingle(
- id='start-date-picker',
- date='2019-01-01'
- ),
- dcc.DatePickerSingle(
- id='end-date-picker',
- date='2019-12-31'
- ),
- dcc.Slider(
- id='epsilon-slider',
- min=0,
- max=10,
- step=0.1,
- value=1,
- marks={i: str(i) for i in range(11)}
- ),
- dcc.Dropdown(
- id='city-dropdown',
- options=[{'label': city, 'value': city} for city in cities.keys()],
- value='Medellin'
- ),
- dcc.Dropdown(
- id='category-list-dropdown',
- options=[{'label': category, 'value': category} for category in category_list],
- value='transit_stations'
- ),
- dcc.Graph(id='mobility-graph')
- ])
+ dcc.DatePickerSingle(
+ id='start-date-picker',
+ date='2019-01-01'
+ ),
+ dcc.DatePickerSingle(
+ id='end-date-picker',
+ date='2019-12-31'
+ ),
+ dcc.Slider(
+ id='epsilon-slider',
+ min=0,
+ max=10,
+ step=0.1,
+ value=1,
+ marks={i: str(i) for i in range(11)}
+ ),
+ dcc.Dropdown(
+ id='city-dropdown',
+ options=[{'label': city, 'value': city} for city in cities.keys()],
+ value='Medellin'
+ ),
+ dcc.Dropdown(
+ id='category-list-dropdown',
+ options=[{'label': category, 'value': category} for category in category_list],
+ value='transit_stations'
+ ),
+ dcc.Graph(id='mobility-graph')
+ ])
# Callback to update the graph based on input values
@app.callback(
Output('mobility-graph', 'figure'),
[Input('start-date-picker', 'date'),
- Input('end-date-picker', 'date'),
- Input('city-dropdown', 'value'),
- Input('category-list-dropdown', 'value'),
- Input('epsilon-slider', 'value')]
+ Input('end-date-picker', 'date'),
+ Input('city-dropdown', 'value'),
+ Input('category-list-dropdown', 'value'),
+ Input('epsilon-slider', 'value')]
)
- def update_graph(start_date, end_date, city_filter,category, epsilon):
+ def update_graph(start_date, end_date, city_filter, category, epsilon):
# Convert date strings to datetime objects
start_date = datetime.strptime(start_date, '%Y-%m-%d')
end_date = datetime.strptime(end_date, '%Y-%m-%d')
- # Call the mobility_analyser function
+ # Call the mobility_analyzer function
filtered_df = mobility_analyzer(df, start_date, end_date, city_filter, category, epsilon)
# Plot using Plotly Express
@@ -161,76 +163,186 @@ def update_graph(start_date, end_date, city_filter,category, epsilon):
labels={'nb_transactions': 'Number of Transactions', 'date': 'Date'}
)
+ # Add events for Bogotá
+ if city_filter == "Bogota":
+ events = [
+ ("Isolation Start Drill", "2020-03-20"),
+ ("National Quarantine", "2020-03-26"),
+ ("Gender Restriction", "2020-04-16"),
+ ("Day Without VAT (IVA)", "2020-06-19"),
+ ("Lockdown 1", "2020-07-15"),
+ ("Lockdown 2", "2020-07-30"),
+ ("Lockdown 3", "2020-08-13"),
+ ("Lockdown 4", "2020-08-20"),
+ ("End of National Quarantine", "2020-09-04"),
+ ("Day Without VAT", "2020-11-19"),
+ ("Candle Day", "2020-12-07"),
+ ("Start of Novenas", "2020-12-16"),
+ ("Lockdown 1 (2021)", "2021-01-05"),
+ ("Lockdown 2 (2021)", "2021-01-12"),
+ ("Lockdown 3 (2021)", "2021-01-18"),
+ ("Lockdown 4 (2021)", "2021-01-28"),
+ ("Holy Week", "2021-03-28"),
+ ("Model 4x3", "2021-04-06"),
+ ("Model 4x3 (Extension)", "2021-04-06"),
+ ("Vaccination Stage 1", "2021-02-18"),
+ ("Vaccination Stage 2", "2021-03-08"),
+ ("Vaccination Stage 3", "2021-05-22"),
+ ("Vaccination Stage 4", "2021-06-17"),
+ ("Vaccination Stage 5", "2021-07-17"),
+ ("Riots and Social Unrest", "2021-05-01")
+ ]
+
+ for event, date in events:
+ fig.add_shape(
+ type="line",
+ x0=date,
+ y0=0,
+ x1=date,
+ y1=1,
+ xref='x',
+ yref='paper',
+ line=dict(color="Red", width=2, dash="dash")
+ )
+ fig.add_annotation(
+ x=date,
+ y=1,
+ xref='x',
+ yref='paper',
+ text=event,
+ showarrow=True,
+ arrowhead=1,
+ ax=-10,
+ ay=-40,
+ font=dict(color="Red")
+ )
+
return fig
+
return app
-def create_pandemic_adherence_dash_app(df:pd.DataFrame):
+def create_pandemic_adherence_dash_app(df: pd.DataFrame):
cities = {
"Medellin": (6.2476, -75.5658),
"Bogota": (4.7110, -74.0721),
"Brasilia": (-15.7975, -47.8919),
"Santiago": (-33.4489, -70.6693)
- }
- entry_types=["luxury","essential","other"]
+ }
+ entry_types = ["luxury", "essential", "other"]
app = dash.Dash(__name__)
-
+
app.layout = html.Div([
- dcc.DatePickerSingle(
- id='start-date-picker',
- date='2019-01-01'
- ),
- dcc.DatePickerSingle(
- id='end-date-picker',
- date='2019-12-31'
- ),
- dcc.Slider(
- id='epsilon-slider',
- min=0,
- max=10,
- step=0.1,
- value=1,
- marks={i: str(i) for i in range(11)}
- ),
- dcc.Dropdown(
- id='city-dropdown',
- options=[{'label': city, 'value': city} for city in cities.keys()],
- value='Medellin'
- ),
- dcc.Dropdown(
- id='entry-type-dropdown',
- options=[{'label': entry_type, 'value': entry_type} for entry_type in entry_types],
- value='luxury'
- ),
- dcc.Graph(id='pandemic-adherence-graph')
- ])
+ dcc.DatePickerSingle(
+ id='start-date-picker',
+ date='2019-01-01'
+ ),
+ dcc.DatePickerSingle(
+ id='end-date-picker',
+ date='2019-12-31'
+ ),
+ dcc.Slider(
+ id='epsilon-slider',
+ min=0,
+ max=10,
+ step=0.1,
+ value=1,
+ marks={i: str(i) for i in range(11)}
+ ),
+ dcc.Dropdown(
+ id='city-dropdown',
+ options=[{'label': city, 'value': city} for city in cities.keys()],
+ value='Medellin'
+ ),
+ dcc.Dropdown(
+ id='entry-type-dropdown',
+ options=[{'label': entry_type, 'value': entry_type} for entry_type in entry_types],
+ value='luxury'
+ ),
+ dcc.Graph(id='pandemic-adherence-graph')
+ ])
# Callback to update the graph based on input values
@app.callback(
Output('pandemic-adherence-graph', 'figure'),
[Input('start-date-picker', 'date'),
- Input('end-date-picker', 'date'),
- Input('city-dropdown', 'value'),
- Input('entry-type-dropdown', 'value'),
- Input('epsilon-slider', 'value')]
+ Input('end-date-picker', 'date'),
+ Input('city-dropdown', 'value'),
+ Input('entry-type-dropdown', 'value'),
+ Input('epsilon-slider', 'value')]
)
- def update_graph(start_date, end_date, city_filter,essential_or_luxury, epsilon):
+ def update_graph(start_date, end_date, city_filter, essential_or_luxury, epsilon):
# Convert date strings to datetime objects
start_date = datetime.strptime(start_date, '%Y-%m-%d')
end_date = datetime.strptime(end_date, '%Y-%m-%d')
- # Call the mobility_analyser function
- filtered_df = pandemic_adherence_analyzer(df, start_date, end_date, city_filter,essential_or_luxury, epsilon)
+ # Call the pandemic_adherence_analyzer function
+ filtered_df = pandemic_adherence_analyzer(df, start_date, end_date, city_filter, essential_or_luxury, epsilon)
# Plot using Plotly Express
fig = px.line(
filtered_df,
x='date',
y='nb_transactions',
- title=f"Pandemic Stage Analysis for {city_filter} from {start_date.date()} to {end_date.date()} with epsilon={epsilon}",
+ title=f"Pandemic adherence Analysis for {city_filter} from {start_date.date()} to {end_date.date()} with epsilon={epsilon}",
labels={'nb_transactions': 'Number of Transactions', 'date': 'Date'}
)
+ # Add events for Bogotá
+ if city_filter == "Bogota":
+ events = [
+ ("Isolation Start Drill", "2020-03-20"),
+ ("National Quarantine", "2020-03-26"),
+ ("Gender Restriction", "2020-04-16"),
+ ("Day Without VAT (IVA)", "2020-06-19"),
+ ("Lockdown 1", "2020-07-15"),
+ ("Lockdown 2", "2020-07-30"),
+ ("Lockdown 3", "2020-08-13"),
+ ("Lockdown 4", "2020-08-20"),
+ ("End of National Quarantine", "2020-09-04"),
+ ("Day Without VAT", "2020-11-19"),
+ ("Candle Day", "2020-12-07"),
+ ("Start of Novenas", "2020-12-16"),
+ ("Lockdown 1 (2021)", "2021-01-05"),
+ ("Lockdown 2 (2021)", "2021-01-12"),
+ ("Lockdown 3 (2021)", "2021-01-18"),
+ ("Lockdown 4 (2021)", "2021-01-28"),
+ ("Holy Week", "2021-03-28"),
+ ("Model 4x3", "2021-04-06"),
+ ("Model 4x3 (Extension)", "2021-04-06"),
+ ("Vaccination Stage 1", "2021-02-18"),
+ ("Vaccination Stage 2", "2021-03-08"),
+ ("Vaccination Stage 3", "2021-05-22"),
+ ("Vaccination Stage 4", "2021-06-17"),
+ ("Vaccination Stage 5", "2021-07-17"),
+ ("Riots and Social Unrest", "2021-05-01")
+ ]
+
+ for event, date in events:
+ fig.add_shape(
+ type="line",
+ x0=date,
+ y0=0,
+ x1=date,
+ y1=1,
+ xref='x',
+ yref='paper',
+ line=dict(color="Red", width=2, dash="dash")
+ )
+ fig.add_annotation(
+ x=date,
+ y=1,
+ xref='x',
+ yref='paper',
+ text=event,
+ showarrow=True,
+ arrowhead=1,
+ ax=-10,
+ ay=-40,
+ font=dict(color="Red")
+ )
+
return fig
+
return app
def create_contact_matrix_dash_app(df:pd.DataFrame):
@@ -375,7 +487,7 @@ def update_graph(start_date, end_date, city_filter, category, epsilon):
offset = filtered_df_transactional["date"].iloc[0]
filtered_df_google = preprocess_google_mobility(df_google_mobility_data, start_date, end_date, city_filter, category, offset)
- # Create the plot
+ # Create the plot with two y-axes
fig = go.Figure()
# Add transactional mobility data
@@ -383,7 +495,8 @@ def update_graph(start_date, end_date, city_filter, category, epsilon):
x=filtered_df_transactional['date'],
y=filtered_df_transactional['nb_transactions'],
mode='lines',
- name='Transactional Mobility'
+ name='Transactional Mobility',
+ yaxis='y1'
))
# Add Google mobility data
@@ -391,15 +504,14 @@ def update_graph(start_date, end_date, city_filter, category, epsilon):
x=filtered_df_google['date'],
y=filtered_df_google[category],
mode='lines',
- name='Google Mobility'
+ name='Google Mobility',
+ yaxis='y2'
))
- # Update layout
+ # Update layout for two y-axes
fig.update_layout(
title=f"Mobility Analysis for {city_filter} and category {category} from {start_date.date()} to {end_date.date()} with epsilon={epsilon}",
xaxis_title='Date',
- # yaxis_title='Mobility Change',
- # legend_title='Data Source'
yaxis=dict(
title='Transactional Mobility',
titlefont=dict(color='blue'),
diff --git a/tests/test_viz.ipynb b/tests/test_viz.ipynb
index 3e3d84a..60fe32d 100644
--- a/tests/test_viz.ipynb
+++ b/tests/test_viz.ipynb
@@ -28,21 +28,261 @@
"outputs": [],
"source": [
"path = \"C:\\\\Users\\kshub\\\\OneDrive\\\\Documents\\\\PET_phase_2\\\\Technical_Phase_Data\\\\technical_phase_data.csv\"\n",
- "df_tran = pd.read_csv(path)\n",
- "df_mobility = pd.read_csv(\"C:\\\\Users\\\\kshub\\\\OneDrive\\\\Documents\\\\PET_phase_2\\\\Global_Mobility_Report (1).csv\", low_memory=False)\n"
+ "df_tran = pd.read_csv(path)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " country_region_code | \n",
+ " country_region | \n",
+ " sub_region_1 | \n",
+ " sub_region_2 | \n",
+ " metro_area | \n",
+ " iso_3166_2_code | \n",
+ " census_fips_code | \n",
+ " place_id | \n",
+ " date | \n",
+ " retail_and_recreation_percent_change_from_baseline | \n",
+ " grocery_and_pharmacy_percent_change_from_baseline | \n",
+ " parks_percent_change_from_baseline | \n",
+ " transit_stations_percent_change_from_baseline | \n",
+ " workplaces_percent_change_from_baseline | \n",
+ " residential_percent_change_from_baseline | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 | \n",
+ " AE | \n",
+ " United Arab Emirates | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " ChIJvRKrsd9IXj4RpwoIwFYv0zM | \n",
+ " 2020-02-15 | \n",
+ " 0.0 | \n",
+ " 4.0 | \n",
+ " 5.0 | \n",
+ " 0.0 | \n",
+ " 2.0 | \n",
+ " 1.0 | \n",
+ "
\n",
+ " \n",
+ " 1 | \n",
+ " AE | \n",
+ " United Arab Emirates | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " ChIJvRKrsd9IXj4RpwoIwFYv0zM | \n",
+ " 2020-02-16 | \n",
+ " 1.0 | \n",
+ " 4.0 | \n",
+ " 4.0 | \n",
+ " 1.0 | \n",
+ " 2.0 | \n",
+ " 1.0 | \n",
+ "
\n",
+ " \n",
+ " 2 | \n",
+ " AE | \n",
+ " United Arab Emirates | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " ChIJvRKrsd9IXj4RpwoIwFYv0zM | \n",
+ " 2020-02-17 | \n",
+ " -1.0 | \n",
+ " 1.0 | \n",
+ " 5.0 | \n",
+ " 1.0 | \n",
+ " 2.0 | \n",
+ " 1.0 | \n",
+ "
\n",
+ " \n",
+ " 3 | \n",
+ " AE | \n",
+ " United Arab Emirates | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " ChIJvRKrsd9IXj4RpwoIwFYv0zM | \n",
+ " 2020-02-18 | \n",
+ " -2.0 | \n",
+ " 1.0 | \n",
+ " 5.0 | \n",
+ " 0.0 | \n",
+ " 2.0 | \n",
+ " 1.0 | \n",
+ "
\n",
+ " \n",
+ " 4 | \n",
+ " AE | \n",
+ " United Arab Emirates | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " ChIJvRKrsd9IXj4RpwoIwFYv0zM | \n",
+ " 2020-02-19 | \n",
+ " -2.0 | \n",
+ " 0.0 | \n",
+ " 4.0 | \n",
+ " -1.0 | \n",
+ " 2.0 | \n",
+ " 1.0 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " country_region_code country_region sub_region_1 sub_region_2 \\\n",
+ "0 AE United Arab Emirates NaN NaN \n",
+ "1 AE United Arab Emirates NaN NaN \n",
+ "2 AE United Arab Emirates NaN NaN \n",
+ "3 AE United Arab Emirates NaN NaN \n",
+ "4 AE United Arab Emirates NaN NaN \n",
+ "\n",
+ " metro_area iso_3166_2_code census_fips_code place_id \\\n",
+ "0 NaN NaN NaN ChIJvRKrsd9IXj4RpwoIwFYv0zM \n",
+ "1 NaN NaN NaN ChIJvRKrsd9IXj4RpwoIwFYv0zM \n",
+ "2 NaN NaN NaN ChIJvRKrsd9IXj4RpwoIwFYv0zM \n",
+ "3 NaN NaN NaN ChIJvRKrsd9IXj4RpwoIwFYv0zM \n",
+ "4 NaN NaN NaN ChIJvRKrsd9IXj4RpwoIwFYv0zM \n",
+ "\n",
+ " date retail_and_recreation_percent_change_from_baseline \\\n",
+ "0 2020-02-15 0.0 \n",
+ "1 2020-02-16 1.0 \n",
+ "2 2020-02-17 -1.0 \n",
+ "3 2020-02-18 -2.0 \n",
+ "4 2020-02-19 -2.0 \n",
+ "\n",
+ " grocery_and_pharmacy_percent_change_from_baseline \\\n",
+ "0 4.0 \n",
+ "1 4.0 \n",
+ "2 1.0 \n",
+ "3 1.0 \n",
+ "4 0.0 \n",
+ "\n",
+ " parks_percent_change_from_baseline \\\n",
+ "0 5.0 \n",
+ "1 4.0 \n",
+ "2 5.0 \n",
+ "3 5.0 \n",
+ "4 4.0 \n",
+ "\n",
+ " transit_stations_percent_change_from_baseline \\\n",
+ "0 0.0 \n",
+ "1 1.0 \n",
+ "2 1.0 \n",
+ "3 0.0 \n",
+ "4 -1.0 \n",
+ "\n",
+ " workplaces_percent_change_from_baseline \\\n",
+ "0 2.0 \n",
+ "1 2.0 \n",
+ "2 2.0 \n",
+ "3 2.0 \n",
+ "4 2.0 \n",
+ "\n",
+ " residential_percent_change_from_baseline \n",
+ "0 1.0 \n",
+ "1 1.0 \n",
+ "2 1.0 \n",
+ "3 1.0 \n",
+ "4 1.0 "
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "import pandas as pd\n",
+ "\n",
+ "# Define the path to the CSV file\n",
+ "file_path = \"C:\\\\Users\\\\kshub\\\\OneDrive\\\\Documents\\\\PET_phase_2\\\\Global_Mobility_Report (1).csv\"\n",
+ "\n",
+ "# Initialize an empty list to store the chunks\n",
+ "chunks = []\n",
+ "\n",
+ "# Read the CSV file in chunks\n",
+ "chunk_size = 10000 # Adjust the chunk size as needed\n",
+ "for chunk in pd.read_csv(file_path, chunksize=chunk_size, low_memory=False):\n",
+ " chunks.append(chunk)\n",
+ "\n",
+ "# Concatenate the chunks into a single DataFrame\n",
+ "df_mobility = pd.concat(chunks, ignore_index=True)\n",
+ "\n",
+ "# Display the first few rows of the DataFrame\n",
+ "df_mobility.head()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "df_mobility = pd.read_csv(\"C:\\\\Users\\\\kshub\\\\OneDrive\\\\Documents\\\\PET_phase_2\\\\Global_Mobility_Report (1).csv\", low_memory=False)\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "Scipy computed Pearson r: 0.3056463348470299 and p-value: 0.03886009367628722\n"
+ "Scipy computed Pearson r: 0.2726605353538447 and p-value: 0.06675963402694846\n"
]
+ },
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "