Photo by Rachit Tank on Unsplash

Analizando un grupo de Whatsapp

Daniel Eduardo Fernandez Villalobos
MCD-UNISON
Published in
7 min readDec 14, 2020

--

Trabajo basado en “Whatsapp Group Chat Analysis using Python and Plotly” de Saiteja Kura.

Importando datos

La aplicación de Whatsapp tiene una opción para exportar cualquier chat a un archivo de formato .txt, si consideramos una línea de nuestro archivo podremos obtener información relevante de las columnas, las cuales están conformadas por:

{fecha} {hora} {autor} {mensaje}

aaaa/mm/dd hh:mm autor mensaje

Importando librerías

Haremos un código que pueda identificar cada línea con un formato de fecha, esto haría que cada mensaje sea único. Obtendremos la fecha, la hora, el autor y el mensaje de nuestro archivo de texto.

def startsWithDateAndTime(s):
# regex pattern for date.(Works only for android. IOS Whatsapp export format is different. Will update the code soon
pattern = '^\d{2}/\d{2}/\d{2} \d{2}:\d{2} - '
result = re.match(pattern, s)
if result:
return True
return False

# Finds username of any given format.
def FindAuthor(s):
patterns = [
'([\w]+):', # First Name
'([\w]+[\s]+[\w]+):', # First Name + Last Name
'([\w]+[\s]+[\w]+[\s]+[\w]+):', # First Name + Middle Name + Last Name
'([+]\d{2} \d{5} \d{5}):', # Mobile Number (India)
'([+]\d{2} \d{3} \d{3} \d{4}):', # Mobile Number (US)
'([\w]+)[\u263a-\U0001f999]+:', # Name and Emoji
]
pattern = '^' + '|'.join(patterns)
result = re.match(pattern, s)
if result:
return True
return False

def getDataPoint(line):
splitLine = line.split(' - ')
dateTime = splitLine[0]
date, time = dateTime.split(' ')
message = ' '.join(splitLine[1:])
if FindAuthor(message):
splitMessage = message.split(': ')
author = splitMessage[0]
message = ' '.join(splitMessage[1:])
else:
author = None
return date, time, author, message
parsedData = [] # List to keep track of data so it can be used by a Pandas dataframe
# Upload your file here
conversationPath = r'C:/Users/Daniel Fernandez/Desktop/Ingenieria/Analizando-un-grupo/grupo_whatsapp.txt' # chat file
with open(conversationPath, encoding="utf-8") as fp:
fp.readline() # Skipping first line of the file because contains information related to something about end-to-end encryption
messageBuffer = []
date, time, author = None, None, None
while True:
line = fp.readline()
if not line:
break
line = line.strip()
if startsWithDateAndTime(line):
if len(messageBuffer) > 0:
parsedData.append([date, time, author, ' '.join(messageBuffer)])
messageBuffer.clear()
date, time, author, message = getDataPoint(line)
messageBuffer.append(message)
else:
messageBuffer.append(line)

df = pd.DataFrame(parsedData, columns=['Date', 'Time', 'Author', 'Message']) # Initialising a pandas Dataframe.
df["Date"] = pd.to_datetime(df["Date"])

Cambiaremos los nombres de los participantes del chat y los remplazaremos por otros usando nombres de bandas de música.

# Defining names and aliases
names = list(df.Author.unique())
aliases = list(['Misfits', 'Korn', 'Slipknot', 'Queen', 'Rammstein', 'Megadeath', 'Metallica'])
# Replacing for Author
df.Author.replace(names, aliases, inplace=True)
# Replacing within messages
for (name, alias) in zip(names, aliases):
df.Message = df.Message.str.replace(name, alias)

Ahora nuestro grupo se ve de la siguiente forma:

df.head()
df.info()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 899 entries, 0 to 898
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Date 899 non-null datetime64[ns]
1 Time 899 non-null object
2 Author 899 non-null object
3 Message 899 non-null object
dtypes: datetime64[ns](1), object(3)
memory usage: 28.2+ KB

Podemos observar que tenemos 899 mensajes totales, y usaremos lo siguiente para descartar valores nulos.

df = df.dropna()

Estadísticas del grupo

Para encontrar el número de cuantos emojis se han usado crearemos una columna que cuente con los emojis para cada mensaje, se hará lo mismo para los URLs enviados

def split_count(text):emoji_list = []
data = regex.findall(r'\X', text)
for word in data:
if any(char in emoji.UNICODE_EMOJI for char in word):
emoji_list.append(word)
return emoji_listtotal_messages = df.shape[0]
media_messages = df[df['Message'] == '<Media omitted>'].shape[0]
df["emoji"] = df["Message"].apply(split_count)
emojis = sum(df['emoji'].str.len())
URLPATTERN = r'(https?://\S+)'
df['urlcount'] = df.Message.apply(lambda x: re.findall(URLPATTERN, x)).str.len()
links = np.sum(df.urlcount)

Aquí se muestran la cantidad de mesanjes, emojis y URL enviados.

print("Estadisticas del grupo")
print("Mensajes:", total_messages)
print("Media:", media_messages)
print("Emojis:", emojis)
print("Links:", links)
Estadisticas del grupo
Mensajes: 899
Media: 0
Emojis: 213
Links: 2

Separaremos los mensajes de multimedia omitidos en un dataframe distinto.

media_messages_df = df[df['Message'] == '<Media omitted>']
messages_df = df.drop(media_messages_df.index)

Haremos la cuenta de cuantas palabras y letras uso cada autor en su mensaje añadiendo 2 columnas al dataframe:

messages_df['Letter_Count'] = messages_df['Message'].apply(lambda s : len(s))
messages_df['Word_Count'] = messages_df['Message'].apply(lambda s : len(s.split(' ')))

Ahora nuestros datos se ven de la siguiente manera.

messages_df

Estadísticas por autor

Revisaremos por autor la cantidad de mensajes y emojis que se han utilizado

# Creates a list of unique Authors - ['Manikanta', 'Teja Kura', .........]
l = messages_df.Author.unique()
for i in range(len(l)):
# Filtering out messages of particular user
req_df= messages_df[messages_df["Author"] == l[i]]
# req_df will contain messages of only one particular user
print(f'Stats of {l[i]} -')
# shape will print number of rows which indirectly means the number of messages
print('Messages Sent', req_df.shape[0])
#Word_Count contains of total words in one message. Sum of all words/ Total Messages will yield words per message
words_per_message = (np.sum(req_df['Word_Count']))/req_df.shape[0]
print('Words per message', words_per_message)
#media conists of media messages
media = media_messages_df[media_messages_df['Author'] == l[i]].shape[0]
print('Media Messages Sent', media)
# emojis conists of total emojis
emojis = sum(req_df['emoji'].str.len())
print('Emojis Sent', emojis)
#links consist of total links
links = sum(req_df["urlcount"])
print('Links Sent', links)
print()
Stats of Misfits -
Messages Sent 251
Words per message 9.183266932270916
Media Messages Sent 0
Emojis Sent 63
Links Sent 1

Stats of Korn -
Messages Sent 143
Words per message 5.335664335664336
Media Messages Sent 0
Emojis Sent 54
Links Sent 0

Stats of Slipknot -
Messages Sent 41
Words per message 6.463414634146342
Media Messages Sent 0
Emojis Sent 9
Links Sent 0

Stats of Queen -
Messages Sent 31
Words per message 8.0
Media Messages Sent 0
Emojis Sent 3
Links Sent 0

Stats of Rammstein -
Messages Sent 158
Words per message 6.8734177215189876
Media Messages Sent 0
Emojis Sent 39
Links Sent 0

Stats of Megadeath -
Messages Sent 63
Words per message 5.507936507936508
Media Messages Sent 0
Emojis Sent 17
Links Sent 0

Stats of Metallica -
Messages Sent 212
Words per message 9.391509433962264
Media Messages Sent 0
Emojis Sent 28
Links Sent 1

Estadísticas de emojis

Anteriormente obtuvimos la cantidad de emojis enviados en toda la conversación del grupo, aquí veremos cuál fue la cantidad de emojis diferentes usados.

total_emojis_list = list(set([a for b in messages_df.emoji for a in b]))
total_emojis = len(total_emojis_list)
print(total_emojis)
out[]47

Emojis más usados en el grupo

Crearemos una lista con los diferentes emojis usados y cuantas veces han aparecido a lo largo de la conversación.

total_emojis_list = list([a for b in messages_df.emoji for a in b])
emoji_dict = dict(Counter(total_emojis_list))
emoji_dict = sorted(emoji_dict.items(), key=lambda x: x[1], reverse=True)
emoji_df = pd.DataFrame(emoji_dict, columns=['emoji', 'count'])
emoji_df

Se harán algunas gráficas de indicar el uso de los emojis en el grupo y para cada autor.

fig = px.pie(emoji_df, values='count', names='emoji',
title='Emoji Distribution')
fig.update_traces(textposition='inside', textinfo='percent+label')
fig.show()
l = messages_df.Author.unique()
for i in range(len(l)):
dummy_df = messages_df[messages_df['Author'] == l[i]]
total_emojis_list = list([a for b in dummy_df.emoji for a in b])
emoji_dict = dict(Counter(total_emojis_list))
emoji_dict = sorted(emoji_dict.items(), key=lambda x: x[1], reverse=True)
print('Emoji Distribution for', l[i])
author_emoji_df = pd.DataFrame(emoji_dict, columns=['emoji', 'count'])
fig = px.pie(author_emoji_df, values='count', names='emoji')
fig.update_traces(textposition='inside', textinfo='percent+label')
fig.show()
Emoji Distribution for Misfits
Emoji Distribution for Korn
Emoji Distribution for Slipknot
Emoji Distribution for Queen
Emoji Distribution for Rammstein
Emoji Distribution for Megadeath
Emoji Distribution for Metallica

Nube de palabras

Una nube de palabras es una representación visual de las palabras utilizadas en un texto, donde el tamaño de la palabra es proporcional a la frecuencia en lz que fue usada en los mensajes del grupo.

text = " ".join(review for review in messages_df.Message)
print ("There are {} words in all the messages.".format(len(text)))
out[]There are 40510 words in all the messages.

Tenemos una cantidad 40510 palabras en todo nuestro chat. Usaremos una lista predeterminada para las stopwords.

stopwords = set(STOPWORDS)
#stopwords.update(["ra", "ga", "na", "ani", "em", "ki", "ah","ha","la","eh","ne","le"])
# Generate a word cloud image
wordcloud = WordCloud(stopwords=stopwords, background_color="white").generate(text)
# Display the generated image:
# the matplotlib way:

plt.figure( figsize=(10,5))
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis("off")
plt.show()

Algunas estadísticas mas

Veremos la cantidad de menajes enviados por días, la frecuencia de las horas y fechas en las que el grupo estuvo más activo.

date_df = messages_df.groupby("Date").sum()
date_df.reset_index(inplace=True)
fig = px.line(date_df, x="Date", y="Word_Count", title='Number of Messages as time moves on.')fig.update_xaxes(nticks=20)
fig.show()
messages_df['Date'].value_counts().head(10).plot.barh()
plt.xlabel('Number of Messages')
plt.ylabel('Date')
messages_df['Time'].value_counts().head(10).plot.barh() 
plt.xlabel('Number of messages')
plt.ylabel('Time')

--

--