2021-01-05 17:00:45 +01:00
|
|
|
from typing import List
|
|
|
|
|
|
|
|
import pandas as pd
|
|
|
|
|
2021-04-06 17:12:36 +02:00
|
|
|
from config.models import SMS, Call, Participant
|
2021-01-05 17:00:45 +01:00
|
|
|
from setup import db_engine, session
|
|
|
|
|
2021-04-06 16:23:19 +02:00
|
|
|
call_types = {1: "incoming", 2: "outgoing", 3: "missed"}
|
|
|
|
sms_types = {1: "received", 2: "sent"}
|
|
|
|
|
2021-01-05 17:00:45 +01:00
|
|
|
|
|
|
|
def get_call_data(usernames: List) -> pd.DataFrame:
|
2021-04-06 14:32:18 +02:00
|
|
|
"""
|
|
|
|
Read the data from the calls table and return it in a dataframe.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
usernames: List
|
|
|
|
A list of usernames to put into the WHERE condition.
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
df_calls: pd.DataFrame
|
|
|
|
A dataframe of call data.
|
|
|
|
"""
|
2021-01-05 17:00:45 +01:00
|
|
|
query_calls = (
|
|
|
|
session.query(Call, Participant.username)
|
|
|
|
.filter(Participant.id == Call.participant_id)
|
|
|
|
.filter(Participant.username.in_(usernames))
|
|
|
|
)
|
|
|
|
with db_engine.connect() as connection:
|
|
|
|
df_calls = pd.read_sql(query_calls.statement, connection)
|
|
|
|
return df_calls
|
2021-02-01 18:24:24 +01:00
|
|
|
|
|
|
|
|
2021-04-06 16:50:40 +02:00
|
|
|
def get_sms_data(usernames: List) -> pd.DataFrame:
|
|
|
|
"""
|
|
|
|
Read the data from the sms table and return it in a dataframe.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
usernames: List
|
|
|
|
A list of usernames to put into the WHERE condition.
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
df_sms: pd.DataFrame
|
|
|
|
A dataframe of call data.
|
|
|
|
"""
|
|
|
|
query_sms = (
|
|
|
|
session.query(SMS, Participant.username)
|
|
|
|
.filter(Participant.id == SMS.participant_id)
|
|
|
|
.filter(Participant.username.in_(usernames))
|
|
|
|
)
|
|
|
|
with db_engine.connect() as connection:
|
|
|
|
df_sms = pd.read_sql(query_sms.statement, connection)
|
|
|
|
return df_sms
|
|
|
|
|
|
|
|
|
2021-02-01 18:24:24 +01:00
|
|
|
def enumerate_contacts(comm_df: pd.DataFrame) -> pd.DataFrame:
|
2021-04-06 14:32:18 +02:00
|
|
|
"""
|
|
|
|
Count contacts (callers, senders) and enumerate them by their frequency.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
comm_df: pd.DataFrame
|
|
|
|
A dataframe of calls or SMSes.
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
comm_df: pd.DataFrame
|
|
|
|
The altered dataframe with the column contact_id, arranged by frequency.
|
|
|
|
"""
|
2021-04-06 11:48:37 +02:00
|
|
|
contact_counts = (
|
2021-04-09 16:01:53 +02:00
|
|
|
comm_df.groupby(
|
|
|
|
["participant_id", "trace"]
|
|
|
|
) # We want to count rows by participant_id and trace
|
|
|
|
.size() # Count rows
|
|
|
|
.reset_index() # Make participant_id a regular column.
|
|
|
|
.rename(columns={0: "freq"})
|
|
|
|
.sort_values(["participant_id", "freq"], ascending=False)
|
|
|
|
# First sort by participant_id and then by call frequency.
|
2021-04-06 11:48:37 +02:00
|
|
|
)
|
2021-04-09 16:01:53 +02:00
|
|
|
# We now have a frequency table of different traces (contacts) *within* each participant_id.
|
|
|
|
# Next, enumerate these contacts.
|
|
|
|
# In other words, recode the contacts into integers from 0 to n_contacts,
|
|
|
|
# so that the first one is contacted the most often.
|
|
|
|
contact_ids = (
|
2021-05-04 17:23:52 +02:00
|
|
|
contact_counts.groupby("participant_id") # Group again for enumeration.
|
|
|
|
.cumcount() # Enumerate (count) rows *within* participants.
|
2021-04-09 16:01:53 +02:00
|
|
|
.to_frame("contact_id")
|
|
|
|
)
|
|
|
|
contact_counts = contact_counts.join(contact_ids)
|
|
|
|
# Add these contact_ids to the temporary (grouped) data frame.
|
|
|
|
comm_df = comm_df.merge(contact_counts, on=["participant_id", "trace"])
|
|
|
|
# Add these contact_ids to the original data frame.
|
2021-02-01 18:24:24 +01:00
|
|
|
return comm_df
|
2021-04-06 16:23:19 +02:00
|
|
|
|
|
|
|
|
|
|
|
def count_comms(comm_df: pd.DataFrame) -> pd.DataFrame:
|
2021-04-06 16:54:08 +02:00
|
|
|
"""
|
|
|
|
Calculate frequencies (and duration) of messages (or calls), grouped by their types.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
comm_df: pd.DataFrame
|
|
|
|
A dataframe of calls or SMSes.
|
2021-04-06 16:23:19 +02:00
|
|
|
|
2021-04-06 16:54:08 +02:00
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
comm_features: pd.DataFrame
|
|
|
|
A list of communication features for every participant.
|
2021-04-09 15:33:52 +02:00
|
|
|
These are:
|
|
|
|
* the number of messages by type (received, sent),
|
|
|
|
* the number of calls by type (incoming, outgoing missed), and
|
|
|
|
* the duration of calls by type.
|
2021-04-06 16:54:08 +02:00
|
|
|
"""
|
2021-04-06 16:23:19 +02:00
|
|
|
if "call_type" in comm_df:
|
|
|
|
comm_counts = (
|
|
|
|
comm_df.value_counts(subset=["participant_id", "call_type"])
|
|
|
|
.unstack()
|
|
|
|
.rename(columns=call_types)
|
|
|
|
.add_prefix("no_")
|
|
|
|
)
|
|
|
|
comm_duration = (
|
|
|
|
comm_df.groupby(["participant_id", "call_type"])
|
|
|
|
.sum()["call_duration"]
|
|
|
|
.unstack()
|
|
|
|
.rename(columns=call_types)
|
|
|
|
.add_prefix("duration_")
|
|
|
|
)
|
|
|
|
comm_features = comm_counts.join(comm_duration)
|
2021-04-06 17:12:36 +02:00
|
|
|
try:
|
|
|
|
comm_features.drop(columns="duration_" + call_types[3], inplace=True)
|
2021-04-06 16:54:08 +02:00
|
|
|
# The missed calls are always of 0 duration.
|
2021-04-06 17:12:36 +02:00
|
|
|
except KeyError:
|
|
|
|
pass
|
2021-04-06 16:54:08 +02:00
|
|
|
# If there were no missed calls, this exception is raised.
|
|
|
|
# But we are dropping the column anyway, so no need to deal with the exception.
|
2021-04-06 16:23:19 +02:00
|
|
|
elif "message_type" in comm_df:
|
|
|
|
comm_counts = (
|
|
|
|
comm_df.value_counts(subset=["participant_id", "message_type"])
|
|
|
|
.unstack()
|
|
|
|
.rename(columns=sms_types)
|
|
|
|
.add_prefix("no_")
|
|
|
|
)
|
|
|
|
comm_features = comm_counts
|
|
|
|
else:
|
|
|
|
raise KeyError("The dataframe contains neither call_type or message_type")
|
|
|
|
return comm_features
|